C++技术问询:如何移除输出文件中最后一个键值对后的逗号?
Hey there! Let's sort out that annoying trailing comma at the end of your map entries. The core issue right now is that you're writing lines one by one to the file without knowing which one will be the last. Here's a straightforward, reliable fix based on your existing code:
Approach: Collect All Lines First, Then Write With Adjustment
Instead of appending each line to the file immediately, gather all the generated map entry lines in a list first. Once you have all lines, you can easily modify the last one to remove the trailing comma before writing everything to the file.
Modified Code
First, update your analyze_file function to collect lines and handle the trailing comma removal. You can also remove the separate write_cpp_file function since we'll handle writing directly here:
#include <windows.h> #include <queue> #include <string> #include <iostream> #include <regex> #include <fstream> #include <iterator> #include <stdio.h> #include <vector> // Add this header for std::vector // I think MS's names for some things are obnoxious. const HANDLE HNULL = INVALID_HANDLE_VALUE; const int A_DIR = FILE_ATTRIBUTE_DIRECTORY; void analyze_file(std::string const &path, WIN32_FIND_DATA const &file) { std::string file_name = path + file.cFileName; std::cout << "processing file: " << file_name << "\n"; std::ifstream empSalariesOld(file_name); if (empSalariesOld) { std::string line; std::regex open_comment("(.*)(<!--)(.*)"); std::regex close_comment("(.*)(-->)(.*)"); std::regex string_tag("(.*)(<string name=)(.*)"); std::regex find1("<string name="); std::string replace1 = "{"; std::regex find2("\">"); std::string replace2 = "\",\""; std::regex find3("</string>"); std::string replace3 = "\"},"; // Collect all valid generated lines std::vector<std::string> generated_lines; while (getline(empSalariesOld, line)) { // Keep your original filtering logic if (!std::regex_match(line, open_comment) && !std::regex_match(line, close_comment) && std::regex_match(line, string_tag)) { std::string result = std::regex_replace(line, find1, replace1); result = std::regex_replace(result, find2, replace2); result = std::regex_replace(result, find3, replace3); // Only include lines from the target path (keep your original condition) if (path.find("values-en-rGB") != std::string::npos) { generated_lines.push_back(result); } } } // Write the processed lines to file if (!generated_lines.empty()) { std::fstream output_file("./output/result.cpp", std::ios::out | std::ios::app); if (output_file) { for (size_t i = 0; i < generated_lines.size(); ++i) { std::string current_line = generated_lines[i]; // Remove trailing comma from the last line if (i == generated_lines.size() - 1) { if (!current_line.empty() && current_line.back() == ',') { current_line.pop_back(); } } output_file << current_line << std::endl; } output_file.close(); } } } empSalariesOld.close(); } // Rest of your functions (convert, create_output_folder, main) remain unchanged void convert(std::string const &folder_name) { HANDLE finder; // for FindFirstFile WIN32_FIND_DATA file; // data about current file. std::priority_queue<std::string, std::vector<std::string>, std::greater<std::string> > dirs; dirs.push(folder_name); // start with passed directory do { std::string path = dirs.top();// retrieve directory to search dirs.pop(); if (path[path.size()-1] != '\\') // normalize the name. path += "\\"; std::string mask = path + "*"; // create mask for searching // traverse a directory. Search for sub-dirs separately, because we // don't want a mask to apply to directory names. "*.cpp" should find // "a\b.cpp", even though "a" doesn't match "*.cpp". // // First search for files: if (HNULL==(finder=FindFirstFile(mask.c_str(), &file))) continue; do { if (!(file.dwFileAttributes & A_DIR)) analyze_file(path, file); } while (FindNextFile(finder, &file)); FindClose(finder); // Then search for subdirectories: if (HNULL==(finder=FindFirstFile((path + "*").c_str(), &file))) continue; do { if ((file.dwFileAttributes & A_DIR) && (file.cFileName[0] != '.')) dirs.push(path + file.cFileName); } while (FindNextFile(finder, &file)); FindClose(finder); } while (!dirs.empty()); } void create_output_folder() { std::string command = "del /Q "; std::string path = "output\\*.cpp"; system(command.append(path).c_str()); CreateDirectory("output", NULL); } int main() { create_output_folder(); convert("./Strings"); std::cout << "finish convert" << "\n"; return 0; }
Key Changes Explained
- Added
std::vector<std::string> generated_lines: This collects all the valid map entry lines instead of writing them immediately, so we can easily identify the last line. - Moved the path check: The condition to only process
values-en-rGBpaths is now applied when adding lines to the collection, keeping your original filtering logic intact. - Trailing comma removal: When writing lines, we check if we're on the last line. If so, we remove the trailing comma (only if it exists, to avoid errors from empty lines).
- Removed
write_cpp_file: The file writing logic is integrated directly intoanalyze_filefor simplicity and better control over the output.
Alternative Approaches (For Large Files)
If you're working with extremely large files where collecting all lines in memory isn't feasible, you could:
- First pass: Count how many valid lines exist.
- Second pass: Write all lines except the last one normally, then process and write the last line without the comma.
But for most use cases, the first approach is simpler and efficient enough.
内容的提问来源于stack exchange,提问作者zoro

