diff --git a/.vscode/settings.json b/.vscode/settings.json index 5c5f875..b85f722 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,9 +21,14 @@ ], //"visualizerFile": "${workspaceRoot}/.vscode/qt6.natvis", "args": [ - "--test-request", + "-w", + "新建 Microsoft Excel 工作表.xlsx", "-c", - "baidu_fanyi.json" + "baidu_fanyi.json", + "-s", + "Sheet1", + "-r", + "1,1,7,4" ] }, "cmake.configureArgs": [ diff --git a/CMakeLists.txt b/CMakeLists.txt index d3e370c..5feb30c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,8 @@ if (MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() -set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/) +set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib/) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/) find_package(CURL CONFIG REQUIRED) find_package(OpenSSL CONFIG REQUIRED) @@ -29,14 +29,17 @@ bf.config.cpp bf.config.h bf.request.cpp bf.request.h bf.interface.cpp bf.interface.h bf.util.cpp bf.util.h +bf.xlnt.cpp bf.xlnt.h ) add_executable(baidu_fanyi main.cpp ${SOURCES}) +add_executable(cli_test cli_test.cpp) target_link_libraries(baidu_fanyi PRIVATE xlnt::xlnt OpenSSL::SSL OpenSSL::Crypto CURL::libcurl nlohmann_json::nlohmann_json CLI11::CLI11 spdlog::spdlog fmt::fmt ) +target_link_libraries(cli_test PRIVATE CLI11::CLI11) execute_process( COMMAND ${CMAKE_COMMAND} -E copy_if_different diff --git a/README.md b/README.md index 82cddd2..89f3031 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ conan install . -s build_type=Release --build=missing "appId": "xxx", "appKey": "", "appSecret": "xxx", - "type": 0 + "type": 0, + "interval: 200 } ``` diff --git a/bf.config.cpp b/bf.config.cpp index bc80248..c774219 100644 --- a/bf.config.cpp +++ b/bf.config.cpp @@ -34,6 +34,12 @@ bool BF_Config::parseConfig(const std::string& file) } appKey = configJson["appKey"]; + if (configJson.contains("interval")) { + interval = configJson["interval"]; + } else { + gLogger->info("Using default interval: {}", interval); + } + if (!configJson.contains("appSecret")) { gLogger->error("Missing 'appSecret' field in config file"); return false; diff --git a/bf.config.h b/bf.config.h index 918d0de..55dd137 100644 --- a/bf.config.h +++ b/bf.config.h @@ -13,6 +13,8 @@ struct XlsxTask { int startCol; int endRow; int endCol; + std::string from; + std::string to; }; class BF_Config @@ -26,6 +28,7 @@ public: std::string appId; std::string appKey; std::string appSecret; + int interval = 1000; BF_Type type = BFT_COMMON; }; diff --git a/bf.request.cpp b/bf.request.cpp index 1f76cba..5da8e86 100644 --- a/bf.request.cpp +++ b/bf.request.cpp @@ -1,6 +1,7 @@ #include "bf.request.h" #include +#include #include "bf.util.h" @@ -58,4 +59,62 @@ size_t BF_Request::writeData(void* buffer, size_t size, size_t nmemb, std::strin auto totalSize = size * nmemb; str->append(static_cast(buffer), totalSize); return totalSize; +} + +bool BF_Request::HandleStr(const std::string& jsonMsg, std::string& out) +{ + out.clear(); + + if (jsonMsg.empty()) { + gLogger->error("JSON消息为空"); + return false; + } + + try { + nlohmann::json j = nlohmann::json::parse(jsonMsg); + + if (j.contains("error_code")) { + std::string errorCode = j["error_code"]; + std::string errorMsg = j.value("error_msg", "未知错误"); + gLogger->error("翻译API返回错误: code={}, msg={}", errorCode, errorMsg); + return false; + } + + if (!j.contains("trans_result")) { + gLogger->error("JSON中缺少trans_result字段"); + return false; + } + + const auto& transResult = j["trans_result"]; + if (!transResult.is_array() || transResult.empty()) { + gLogger->error("trans_result不是有效数组或为空"); + return false; + } + + std::vector translations; + for (const auto& item : transResult) { + if (item.contains("dst")) { + translations.push_back(item["dst"].get()); + } + } + + if (translations.empty()) { + gLogger->error("未找到有效的翻译结果"); + return false; + } + for (size_t i = 0; i < translations.size(); ++i) { + out += translations[i]; + if (i < translations.size() - 1) { + out += "\n"; + } + } + return true; + + } catch (const nlohmann::json::exception& e) { + gLogger->error("JSON解析失败: {}, 原始数据: {}", e.what(), jsonMsg); + return false; + } catch (const std::exception& e) { + gLogger->error("处理JSON时发生异常: {}, 原始数据: {}", e.what(), jsonMsg); + return false; + } } \ No newline at end of file diff --git a/bf.request.h b/bf.request.h index a620c5d..39b1c3c 100644 --- a/bf.request.h +++ b/bf.request.h @@ -17,6 +17,9 @@ public: std::string getResult(const std::string& url) override; std::string doReady(const std::string& words, const std::string& from, const std::string& to) override; +public: + static bool HandleStr(const std::string& jsonMsg, std::string& out); + private: static size_t writeData(void* buffer, size_t size, size_t nmemb, std::string* str); diff --git a/bf.util.cpp b/bf.util.cpp index ee596af..a48d31d 100644 --- a/bf.util.cpp +++ b/bf.util.cpp @@ -80,3 +80,27 @@ std::string BF_Util::urlEncode(const std::string& data) } return os.str(); } + +std::vector BF_Util::split(const std::string& str, const std::string& delim) +{ + std::vector ret; + size_t start = 0; + size_t end = str.find(delim); + while (end != std::string::npos) { + ret.push_back(str.substr(start, end - start)); + start = end + delim.length(); + end = str.find(delim, start); + } + ret.push_back(str.substr(start)); + return ret; +} + +void BF_Util::trim(std::string& str) +{ + auto left = std::find_if_not(str.begin(), str.end(), [](unsigned char ch) { return std::isspace(ch); }); + str.erase(str.begin(), left); + if (!str.empty()) { + auto right = std::find_if_not(str.rbegin(), str.rend(), [](unsigned char ch) { return std::isspace(ch); }).base(); + str.erase(right, str.end()); + } +} diff --git a/bf.util.h b/bf.util.h index 12864cb..bc13c66 100644 --- a/bf.util.h +++ b/bf.util.h @@ -4,6 +4,7 @@ #include #include #include +#include extern std::shared_ptr gLogger; @@ -18,6 +19,8 @@ public: static std::string MD5(const std::string& data); static std::string RandomStrNum(); static std::string urlEncode(const std::string& data); + static std::vector split(const std::string& str, const std::string& delim); + static void trim(std::string& str); }; #endif \ No newline at end of file diff --git a/bf.xlnt.cpp b/bf.xlnt.cpp new file mode 100644 index 0000000..2c8b040 --- /dev/null +++ b/bf.xlnt.cpp @@ -0,0 +1,97 @@ +#include "bf.xlnt.h" + +void BF_Xlnt::Reset() +{ + fanyiHistory_.clear(); +} + +void BF_Xlnt::Set(BF_Interface* request, std::shared_ptr bfConfig) +{ + request_ = request; + bfConfig_ = bfConfig; +} + +bool BF_Xlnt::parseXlsx(const std::string& file, std::vector& tasks, const std::string& sheetName) +{ + xlnt::workbook wb; + xlnt::worksheet ws; + std::string out; + try { + wb.load(file); + ws = wb.sheet_by_title(sheetName); + for (auto& task : tasks) { + for (int i = task.startRow; i <= task.endRow; i++) { + for (int j = task.startCol; j <= task.endCol; j++) { + gLogger->debug("=================================================="); + if (Fanyi(i, j, ws, task, out) && !fanyiHistory_.count(task.from)) { + fanyiHistory_[task.from] = out; + } + std::this_thread::sleep_for(std::chrono::milliseconds(bfConfig_->interval)); + } + } + } + } catch (const std::exception& e) { + gLogger->error(e.what()); + } + + wb.save(fmt::format("{}_out.xlsx", file)); + return true; +} + +bool BF_Xlnt::Fanyi(int i, int j, xlnt::worksheet& ws, const XlsxTask& task, std::string& out) +{ + xlnt::cell_reference cr; + if (!GetCell(i, j, ws, cr)) { + gLogger->error("获取数据失败:[{}][{}]", i, j); + return false; + } + xlnt::cell cell = ws.cell(cr); + std::string text = cell.value(); + BF_Util::trim(text); + if (text.empty()) { + gLogger->warn("数据为空,跳过:[{}][{}]", i, j); + return false; + } + if (fanyiHistory_.count(text)) { + out = fanyiHistory_[text]; + gLogger->debug("使用历史数据结果:[{}][{}][{}]", i, j, text); + cell.value(out); + return true; + } + auto readyData = request_->doReady(text, task.from, task.to); + if (readyData.empty()) { + gLogger->warn("doReady数据为空,跳过:[{}][{}][{}]", i, j, text); + return false; + } + auto data = fmt::format("{}{}", bfConfig_->baseUrl, readyData); + std::string result = request_->getResult(data); + if (!BF_Request::HandleStr(result, out)) { + return false; + } + gLogger->info("翻译成功:[{}][{}][{}][{}]", i, j, text, out); + cell.value(out); + return true; +} + +bool BF_Xlnt::GetCell(int i, int j, xlnt::worksheet& ws, xlnt::cell_reference& out) +{ + try { + auto cell = ws.cell(j, i); + if (!cell.is_merged()) { + out = cell.reference(); + return true; + } + bool isFind = false; + for (const auto& range : ws.merged_ranges()) { + if (range.contains(cell.reference())) { + out = range.top_left(); + isFind = true; + break; + } + } + return isFind; + } catch (const std::exception& e) { + gLogger->error(e.what()); + } + return false; +} diff --git a/bf.xlnt.h b/bf.xlnt.h new file mode 100644 index 0000000..ad68971 --- /dev/null +++ b/bf.xlnt.h @@ -0,0 +1,32 @@ +#ifndef BF_XLNT_H +#define BF_XLNT_H + +#include +#include + +#include "bf.config.h" +#include "bf.request.h" +#include "bf.util.h" + +class BF_Xlnt +{ +public: + BF_Xlnt() = default; + +public: + void Reset(); + void Set(BF_Interface* request, std::shared_ptr bfConfig); + bool parseXlsx(const std::string& file, std::vector& tasks, const std::string& sheetName); + +private: + bool Fanyi(int i, int j, xlnt::worksheet& ws, const XlsxTask& task, std::string& out); + bool GetCell(int i, int j, xlnt::worksheet& ws, xlnt::cell_reference& out); + +private: + BF_Interface* request_{}; + std::shared_ptr bfConfig_{}; + + std::unordered_map fanyiHistory_{}; +}; + +#endif \ No newline at end of file diff --git a/cli_test.cpp b/cli_test.cpp new file mode 100644 index 0000000..48dbd3a --- /dev/null +++ b/cli_test.cpp @@ -0,0 +1,29 @@ +#include +#include + +#ifdef _WIN32 +#include "Windows.h" +#endif + +int main(int argc, char** argv) +{ +#ifdef _WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif + + CLI::App app("BF Translate"); + argv = app.ensure_utf8(argv); + + std::string configFile; + app.add_option("-c,--config", configFile, "Config file")->required(); + + std::string xlsxFile; + app.add_option("-x,--xlsx", xlsxFile, "Xlsx file")->required(); + + CLI11_PARSE(app, argc, argv); + + std::cout << "configFile: " << configFile << "\n" + << "xlsxFile: " << xlsxFile << "\n"; + + return 0; +} \ No newline at end of file diff --git a/main.cpp b/main.cpp index d358910..705769a 100644 --- a/main.cpp +++ b/main.cpp @@ -2,10 +2,10 @@ #include #include #include -#include #include "bf.request.h" #include "bf.util.h" +#include "bf.xlnt.h" #ifdef _WIN32 #include "Windows.h" @@ -17,6 +17,7 @@ int main(int argc, char** argv) SetConsoleOutputCP(CP_UTF8); #endif CLI::App app{"百度翻译xlsx批量工具。"}; + argv = app.ensure_utf8(argv); std::string workbook; std::string sheetName; @@ -25,11 +26,11 @@ int main(int argc, char** argv) bool testRequest = false; std::vector tasks; // 存储多个字符串 - app.add_option("-t,--task", tasks, "要处理的行列范围(起始行,起始列,结束行,结束列),如:1,1,10,10)")->multi_option_policy(); + app.add_option("-r,--task", tasks, "要处理的行列范围(起始行,起始列,结束行,结束列),如:1,1,10,10)")->multi_option_policy(); app.add_option("-s,--sheet", sheetName, "工作表名称"); - app.add_option("-w,--workbook", workbook, "工作簿"); + app.add_option("-w,--workbook", workbook, "工作簿")->check(CLI::ExistingFile); app.add_option("-f,--from", from, "源语言(默认auto)"); - app.add_option("--to-language,--to", to, "目标语言(默认en)"); + app.add_option("-p,--to", to, "目标语言(默认en)"); app.add_flag("--test-request", testRequest, "测试请求"); // 添加其他可选参数 @@ -51,6 +52,7 @@ int main(int argc, char** argv) // } // return 0; + gLogger->debug("开始处理xlsx文件:{}", workbook); BF_Interface* request = new BF_Request(); std::shared_ptr del(nullptr, [request](void*) { delete request; }); @@ -72,42 +74,35 @@ int main(int argc, char** argv) return 0; } - xlnt::workbook wb; - xlnt::worksheet ws; - try { - wb.load(""); - ws = wb.sheet_by_title(sheetName); - std::vector tasks; - for (auto& task : tasks) { - for (int i = task.startRow; i <= task.endRow; i++) { - for (int j = task.startCol; j <= task.endCol; j++) { - xlnt::cell cell = ws.cell(i, j); - std::string text = cell.value(); - if (text.empty()) { - gLogger->error("数据为空,跳过:[{}][{}]", i, j); - continue; - } - auto readyData = request->doReady(text, from, to); - if (readyData.empty()) { - gLogger->error("doReady数据为空,跳过:[{}][{}][{}]", i, j, text); - continue; - } - auto data = fmt::format("{}{}", bc->baseUrl, readyData); - std::string result = request->getResult(data); - if (result.empty()) { - gLogger->error("getResult数据为空,跳过:[{}][{}][{}]", i, j, text); - continue; - } - gLogger->info("翻译成功:[{}][{}][{}][{}]", i, j, text, result); - cell.value(result); - } - } + std::vector xlsxTasks; + for (const auto& task : tasks) { + auto v = BF_Util::split(task, ","); + if (v.size() != 4) { + gLogger->error("任务格式错误:{}", task); + return -1; } - } catch (const std::exception& e) { - gLogger->error(e.what()); + XlsxTask xlsxTask; + xlsxTask.startRow = std::stoi(v[0]); + xlsxTask.startCol = std::stoi(v[1]); + xlsxTask.endRow = std::stoi(v[2]); + xlsxTask.endCol = std::stoi(v[3]); + xlsxTask.from = from; + xlsxTask.to = to; + xlsxTasks.push_back(xlsxTask); + } + + if (xlsxTasks.empty()) { + gLogger->error("没有指定任务!"); + return -1; + } + + std::shared_ptr xlnt = std::make_shared(); + xlnt->Set(request, bc); + if (!xlnt->parseXlsx(workbook, xlsxTasks, sheetName)) { + gLogger->error("处理xlsx文件失败!"); + return -1; } - wb.save(fmt::format("{}_out.xlsx", workbook)); gLogger->info("结束!"); return 0; } \ No newline at end of file