Files
2025-04-07 17:12:14 -04:00

372 lines
14 KiB
C++

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include "comment.h"
// Function to parse a string field from json line
std::string parseStringField(const std::string& json, const std::string& field) {
std::string field_name = "\"" + field + "\": \"";
size_t pos = json.find(field_name);
if (pos == std::string::npos) {
return "";
}
pos += field_name.length();
size_t end = json.find("\"", pos);
return json.substr(pos, end - pos);
}
// Function to parse a numeric field from json line
int parseNumericField(const std::string& json, const std::string& field) {
std::string field_name = "\"" + field + "\": ";
size_t pos = json.find(field_name);
if (pos == std::string::npos) {
return 0;
}
pos += field_name.length();
size_t end = json.find(",", pos);
if (end == std::string::npos) {
end = json.find("}", pos);
}
return std::stoi(json.substr(pos, end - pos));
}
// Function to parse a boolean field from json line
bool parseBooleanField(const std::string& json, const std::string& field) {
std::string field_name = "\"" + field + "\": ";
size_t pos = json.find(field_name);
if (pos == std::string::npos) {
return false;
}
pos += field_name.length();
return json.substr(pos, 4) == "true";
}
// Function to recursively display comments with proper indentation
void displayComment(Comment* comment, std::ofstream& output, int indent_level = 0) {
if (comment == nullptr) {
return;
}
// Create indentation string
std::string indent(indent_level * 4, ' ');
// Display the comment
output << indent << comment->author << " " << comment->published_date << std::endl;
output << indent << comment->comment << std::endl;
output << indent << "👍 " << comment->like_count << std::endl;
// Display reply count if there are replies
// We use the original reply_count from the JSON, which includes all descendants
if (comment->reply_count > 0) {
if (comment->reply_count == 1) {
output << indent << comment->reply_count << " reply" << std::endl;
} else {
output << indent << comment->reply_count << " replies" << std::endl;
}
}
// Recursively display child comments with increased indentation
for (size_t i = 0; i < comment->children.size(); ++i) {
displayComment(comment->children[i], output, indent_level + 1);
}
}
// Function to recursively delete a comment and all its descendants
void deleteCommentRecursive(Comment* comment, std::map<std::string, Comment*>& comment_map) {
if (comment == nullptr) {
return;
}
// First recursively delete all children
for (size_t i = 0; i < comment->children.size(); ++i) {
deleteCommentRecursive(comment->children[i], comment_map);
}
// Remove this comment from the map
comment_map.erase(comment->comment_id);
}
// Function to read and parse the JSON file
void parseJsonFile(const std::string& jsonFile,
std::map<std::string, Comment*>& comment_map,
std::vector<Comment*>& root_comments) {
// Open the json file
std::ifstream json_stream(jsonFile);
if (!json_stream.is_open()) {
std::cerr << "Failed to open the JSON file." << std::endl;
exit(1);
}
// Read the entire json file into a string
std::string json_content;
std::string line;
while (std::getline(json_stream, line)) {
json_content += line;
}
json_stream.close();
// Find all comments in the json content
size_t pos = 0;
while ((pos = json_content.find("{\"video_id\":", pos)) != std::string::npos) {
// Find the end of this comment
size_t end = json_content.find("}", pos);
if (end == std::string::npos) break;
end++; // Include the closing brace
// Extract this comment's json string
std::string comment_json = json_content.substr(pos, end - pos);
// Parse the fields from the json
std::string video_id = parseStringField(comment_json, "video_id");
std::string author = parseStringField(comment_json, "author");
std::string comment_id = parseStringField(comment_json, "comment_id");
int like_count = parseNumericField(comment_json, "like_count");
int reply_count = parseNumericField(comment_json, "reply_count");
bool is_reply = parseBooleanField(comment_json, "is_reply");
std::string parent_comment_id = parseStringField(comment_json, "parent_comment_id");
std::string published_date = parseStringField(comment_json, "published_date");
std::string crawled_date = parseStringField(comment_json, "crawled_date");
bool is_video_owner = parseBooleanField(comment_json, "is_video_owner");
std::string comment_text = parseStringField(comment_json, "comment");
// Create a new Comment object
Comment* comment = new Comment(video_id, author, comment_id, like_count, reply_count,
is_reply, parent_comment_id, published_date, crawled_date,
is_video_owner, comment_text);
// Add to the map
comment_map[comment_id] = comment;
// Move to the next comment
pos = end;
}
}
// Function to build the comment tree
void buildCommentTree(std::map<std::string, Comment*>& comment_map,
std::vector<Comment*>& root_comments) {
std::map<std::string, Comment*>::iterator it;
for (it = comment_map.begin(); it != comment_map.end(); ++it) {
Comment* comment = it->second;
if (comment->is_reply) {
// This comment is a reply to another comment
std::map<std::string, Comment*>::iterator parent_it = comment_map.find(comment->parent_comment_id);
if (parent_it != comment_map.end()) {
parent_it->second->addChild(comment);
}
} else {
// This comment is a direct response to the video
root_comments.push_back(comment);
}
}
}
// Function to process a reply to video operation
void processReplyToVideo(const std::string& line,
std::map<std::string, Comment*>& comment_map,
std::vector<Comment*>& root_comments) {
std::istringstream iss(line);
std::string command, comment_id, author, comment_text;
iss >> command >> comment_id >> author;
// Skip whitespace to get to the comment text
iss >> std::ws;
// Read the comment text (enclosed in double quotes)
if (iss.peek() == '"') {
iss.get(); // Consume the opening double quote
std::getline(iss, comment_text, '"'); // Read until the closing double quote
}
// Create a new comment
Comment* new_comment = new Comment("", author, comment_id, 0, 0, false, "", "0 seconds ago", "", false, comment_text);
// Add to the map and root comments
comment_map[comment_id] = new_comment;
root_comments.push_back(new_comment);
}
// Function to process a reply to comment operation
void processReplyToComment(const std::string& line,
std::map<std::string, Comment*>& comment_map) {
std::istringstream iss(line);
std::string command, parent_id, comment_id, author, comment_text;
iss >> command >> parent_id >> comment_id >> author;
// Skip whitespace to get to the comment text
iss >> std::ws;
// Read the comment text (enclosed in double quotes)
if (iss.peek() == '"') {
iss.get(); // Consume the opening double quote
std::getline(iss, comment_text, '"'); // Read until the closing double quote
}
// Create a new comment
Comment* new_comment = new Comment("", author, comment_id, 0, 0, true, parent_id, "0 seconds ago", "", false, comment_text);
// Add to the map
comment_map[comment_id] = new_comment;
// Add as a child to the parent comment
std::map<std::string, Comment*>::iterator parent_it = comment_map.find(parent_id);
if (parent_it != comment_map.end()) {
parent_it->second->addChild(new_comment);
// Increment the parent's reply_count
parent_it->second->reply_count++;
}
}
// Function to process a like comment operation
void processLikeComment(const std::string& line,
std::map<std::string, Comment*>& comment_map) {
std::istringstream iss(line);
std::string command, comment_id;
iss >> command >> comment_id;
// Like the comment
std::map<std::string, Comment*>::iterator it = comment_map.find(comment_id);
if (it != comment_map.end()) {
it->second->like();
}
}
// Function to process a delete comment operation
void processDeleteComment(const std::string& line,
std::map<std::string, Comment*>& comment_map,
std::vector<Comment*>& root_comments) {
std::istringstream iss(line);
std::string command, comment_id;
iss >> command >> comment_id;
// Find the comment to delete
std::map<std::string, Comment*>::iterator it = comment_map.find(comment_id);
if (it != comment_map.end()) {
Comment* comment_to_delete = it->second;
// If this is a root comment, remove it from root_comments
std::vector<Comment*>::iterator root_it = std::find(root_comments.begin(), root_comments.end(), comment_to_delete);
if (root_it != root_comments.end()) {
root_comments.erase(root_it);
} else {
// If not a root comment, remove it from its parent's children
std::map<std::string, Comment*>::iterator parent_it = comment_map.find(comment_to_delete->parent_comment_id);
if (parent_it != comment_map.end()) {
std::vector<Comment*>& siblings = parent_it->second->children;
std::vector<Comment*>::iterator sibling_it = std::find(siblings.begin(), siblings.end(), comment_to_delete);
if (sibling_it != siblings.end()) {
siblings.erase(sibling_it);
// Decrement the parent's reply count
parent_it->second->reply_count--;
}
}
}
// Recursively delete this comment and all its descendants
deleteCommentRecursive(comment_to_delete, comment_map);
delete comment_to_delete;
}
}
// Function to process a display comment operation
void processDisplayComment(const std::string& line,
std::map<std::string, Comment*>& comment_map,
std::ofstream& output_file) {
std::istringstream iss(line);
std::string command, comment_id;
iss >> command >> comment_id;
// Display the comment and its descendants
std::map<std::string, Comment*>::iterator it = comment_map.find(comment_id);
if (it != comment_map.end()) {
displayComment(it->second, output_file);
}
}
// Function to process all operations from the operations file
void processOperations(const std::string& operationsFile,
const std::string& outputFile,
std::map<std::string, Comment*>& comment_map,
std::vector<Comment*>& root_comments) {
// Open the operations file
std::ifstream ops_file(operationsFile);
if (!ops_file.is_open()) {
std::cerr << "Failed to open the operations file." << std::endl;
exit(1);
}
// Open the output file
std::ofstream output_file(outputFile);
if (!output_file.is_open()) {
std::cerr << "Failed to open the output file." << std::endl;
exit(1);
}
// Process each operation in the operations file
std::string line;
while (std::getline(ops_file, line)) {
std::istringstream iss(line);
std::string command;
iss >> command;
if (command == "reply_to_video") {
processReplyToVideo(line, comment_map, root_comments);
}
else if (command == "reply_to_comment") {
processReplyToComment(line, comment_map);
}
else if (command == "like_comment") {
processLikeComment(line, comment_map);
}
else if (command == "delete_comment") {
processDeleteComment(line, comment_map, root_comments);
}
else if (command == "display_comment") {
processDisplayComment(line, comment_map, output_file);
}
}
// Close files
ops_file.close();
output_file.close();
}
// Function to clean up memory
void cleanupMemory(std::vector<Comment*>& root_comments) {
for (size_t i = 0; i < root_comments.size(); ++i) {
delete root_comments[i];
}
}
int main(int argc, char* argv[]) {
// Check if the correct number of command line arguments is provided
if (argc != 4) {
std::cerr << "Usage: " << argv[0] << " input1.json input2.txt output.txt" << std::endl;
return 1;
}
// Parse command line arguments
std::string jsonFile = argv[1];
std::string operationsFile = argv[2];
std::string outputFile = argv[3];
// Data structures to store comments
std::map<std::string, Comment*> comment_map; // Map comment_id to Comment
std::vector<Comment*> root_comments; // Comments that are direct responses to the video
// Parse the JSON file
parseJsonFile(jsonFile, comment_map, root_comments);
// Build the comment tree
buildCommentTree(comment_map, root_comments);
// Process operations
processOperations(operationsFile, outputFile, comment_map, root_comments);
// Clean up memory
cleanupMemory(root_comments);
return 0;
}