基本可用。

This commit is contained in:
2026-03-10 14:32:11 +08:00
parent b24bc8a75d
commit d2fc41496e
13 changed files with 302 additions and 42 deletions

View File

@@ -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": [

View File

@@ -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

View File

@@ -14,6 +14,7 @@ conan install . -s build_type=Release --build=missing
"appId": "xxx",
"appKey": "",
"appSecret": "xxx",
"type": 0
"type": 0,
"interval: 200
}
```

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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
View 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
View 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
View 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;
}

View File

@@ -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;
}