基本可用。
This commit is contained in:
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,6 +14,7 @@ conan install . -s build_type=Release --build=missing
|
||||
"appId": "xxx",
|
||||
"appKey": "",
|
||||
"appSecret": "xxx",
|
||||
"type": 0
|
||||
"type": 0,
|
||||
"interval: 200
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "bf.request.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#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<char*>(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<std::string> translations;
|
||||
for (const auto& item : transResult) {
|
||||
if (item.contains("dst")) {
|
||||
translations.push_back(item["dst"].get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
24
bf.util.cpp
24
bf.util.cpp
@@ -80,3 +80,27 @@ std::string BF_Util::urlEncode(const std::string& data)
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> BF_Util::split(const std::string& str, const std::string& delim)
|
||||
{
|
||||
std::vector<std::string> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern std::shared_ptr<spdlog::logger> 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<std::string> split(const std::string& str, const std::string& delim);
|
||||
static void trim(std::string& str);
|
||||
};
|
||||
|
||||
#endif
|
||||
97
bf.xlnt.cpp
Normal file
97
bf.xlnt.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "bf.xlnt.h"
|
||||
|
||||
void BF_Xlnt::Reset()
|
||||
{
|
||||
fanyiHistory_.clear();
|
||||
}
|
||||
|
||||
void BF_Xlnt::Set(BF_Interface* request, std::shared_ptr<BF_Config> bfConfig)
|
||||
{
|
||||
request_ = request;
|
||||
bfConfig_ = bfConfig;
|
||||
}
|
||||
|
||||
bool BF_Xlnt::parseXlsx(const std::string& file, std::vector<XlsxTask>& 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<std::string>();
|
||||
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;
|
||||
}
|
||||
32
bf.xlnt.h
Normal file
32
bf.xlnt.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef BF_XLNT_H
|
||||
#define BF_XLNT_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <xlnt/xlnt.hpp>
|
||||
|
||||
#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<BF_Config> bfConfig);
|
||||
bool parseXlsx(const std::string& file, std::vector<XlsxTask>& 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<BF_Config> bfConfig_{};
|
||||
|
||||
std::unordered_map<std::string, std::string> fanyiHistory_{};
|
||||
};
|
||||
|
||||
#endif
|
||||
29
cli_test.cpp
Normal file
29
cli_test.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#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;
|
||||
}
|
||||
69
main.cpp
69
main.cpp
@@ -2,10 +2,10 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <xlnt/xlnt.hpp>
|
||||
|
||||
#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<std::string> 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<void> 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<XlsxTask> 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<std::string>();
|
||||
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<XlsxTask> 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<BF_Xlnt> xlnt = std::make_shared<BF_Xlnt>();
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user