372 lines
14 KiB
C++
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;
|
|
} |