优化了xml修改和表格逆生成功能
This commit is contained in:
@@ -14,6 +14,26 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* icd_mapper.cpp
|
||||
*
|
||||
* 功能:把外部 ICD/XML 中 LN/DOI 的 desc 描述补写到工作簿
|
||||
* “未匹配的原始数据”工作表的“icd中代表的字段”列。
|
||||
*
|
||||
* 输入:
|
||||
* icdPath 外部 ICD/XML 文件,可能是 UTF-8,也可能声明/实际使用 GBK/GB2312。
|
||||
* workbookPath main.cpp 生成的 Excel 2003 XML 格式 .xls。
|
||||
*
|
||||
* 输出:
|
||||
* 直接修改 workbookPath。message 返回解析结果、匹配数量或错误原因。
|
||||
*
|
||||
* 核心匹配规则:
|
||||
* 1. 从 ICD 的每个 LN 节点读取 lnClass + inst,并遍历其 DOI 子节点;
|
||||
* 2. 组成 DO key:lnClass + inst + "$MX$" + DOI/@name;
|
||||
* 3. 工作簿未匹配页的“原始路径”如果等于该 key,或以 key + "$" 开头,
|
||||
* 就认为该原始 MMS 路径属于这个 DOI,把 DOI/@desc 写入目标列。
|
||||
*/
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
@@ -46,6 +66,7 @@ bool declaresGbEncoding(const std::string& text) {
|
||||
}
|
||||
|
||||
bool isLikelyUtf8(const std::string& text) {
|
||||
// 只做 UTF-8 字节合法性判断,不判断语义;合法就优先按 UTF-8 给 tinyxml2。
|
||||
int remaining = 0;
|
||||
for (unsigned char c : text) {
|
||||
if (remaining == 0) {
|
||||
@@ -63,6 +84,7 @@ bool isLikelyUtf8(const std::string& text) {
|
||||
}
|
||||
|
||||
std::string ansiToUtf8(const std::string& text) {
|
||||
// 936 是 Windows 简体中文代码页,兼容常见 GBK/GB2312 ICD 文件。
|
||||
if (text.empty()) return "";
|
||||
int wideLen = MultiByteToWideChar(936, 0, text.data(), (int)text.size(), nullptr, 0);
|
||||
if (wideLen <= 0) return text;
|
||||
@@ -76,6 +98,7 @@ std::string ansiToUtf8(const std::string& text) {
|
||||
|
||||
std::string loadXmlText(const fs::path& path) {
|
||||
std::string raw = readAllBinary(path);
|
||||
// ICD 声明 GB 编码或字节本身不像 UTF-8 时,先转成 UTF-8 再交给 tinyxml2。
|
||||
if (declaresGbEncoding(raw) || !isLikelyUtf8(raw)) return ansiToUtf8(raw);
|
||||
return raw;
|
||||
}
|
||||
@@ -106,6 +129,7 @@ void collectIcdDescriptions(const tinyxml2::XMLElement* element,
|
||||
std::string doiName = attr(child, "name");
|
||||
std::string desc = attr(child, "desc");
|
||||
if (doiName.empty() || desc.empty()) continue;
|
||||
// 这里构造的是 origin 原始路径前缀。后面路径可继续带 $mag$f 等 DA 后缀。
|
||||
std::string key = lnClass + inst + "$MX$" + doiName;
|
||||
descByDo[key] = desc;
|
||||
}
|
||||
@@ -155,6 +179,7 @@ tinyxml2::XMLElement* cellAt(tinyxml2::XMLElement* row, int col) {
|
||||
|
||||
tinyxml2::XMLElement* ensureCell(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* row, int col) {
|
||||
if (tinyxml2::XMLElement* existing = cellAt(row, col)) return existing;
|
||||
// Excel XML 允许通过 ss:Index 跳列;补列时直接设置 ss:Index,避免改动前面已有单元格。
|
||||
tinyxml2::XMLElement* cell = doc.NewElement("Cell");
|
||||
cell->SetAttribute("ss:Index", col);
|
||||
row->InsertEndChild(cell);
|
||||
@@ -175,6 +200,7 @@ std::vector<std::string> sortedKeysByLength(const std::unordered_map<std::string
|
||||
std::vector<std::string> keys;
|
||||
keys.reserve(values.size());
|
||||
for (const auto& kv : values) keys.push_back(kv.first);
|
||||
// 长路径优先,避免短 key 先命中长 key 的前缀。
|
||||
std::sort(keys.begin(), keys.end(), [](const std::string& a, const std::string& b) {
|
||||
if (a.size() != b.size()) return a.size() > b.size();
|
||||
return a < b;
|
||||
@@ -183,6 +209,7 @@ std::vector<std::string> sortedKeysByLength(const std::unordered_map<std::string
|
||||
}
|
||||
|
||||
bool pathMatchesDo(const std::string& rawPath, const std::string& doKey) {
|
||||
// rawPath 可以正好是 DO,也可以是 DO$DA...;必须在 "$" 边界匹配,避免误命中相似前缀。
|
||||
return rawPath == doKey ||
|
||||
(rawPath.size() > doKey.size() &&
|
||||
rawPath.compare(0, doKey.size(), doKey) == 0 &&
|
||||
@@ -230,6 +257,7 @@ bool pqApplyIcdDescriptionsToWorkbook(const std::string& icdPath,
|
||||
const std::string& workbookPath,
|
||||
std::string* message) {
|
||||
try {
|
||||
// 第一步:解析 ICD,得到 “DO 路径前缀 -> 中文描述”。
|
||||
auto descByDo = parseIcdDescriptions(fs::u8path(icdPath));
|
||||
if (descByDo.empty()) {
|
||||
if (message) *message = "ICD 中没有解析到 LN/DOI desc";
|
||||
@@ -237,6 +265,7 @@ bool pqApplyIcdDescriptionsToWorkbook(const std::string& icdPath,
|
||||
}
|
||||
std::vector<std::string> orderedKeys = sortedKeysByLength(descByDo);
|
||||
|
||||
// 第二步:读取 main.cpp 输出的工作簿。该 .xls 实际是 XML,可直接用 tinyxml2 修改。
|
||||
tinyxml2::XMLDocument doc;
|
||||
std::string workbookXml = readAllBinary(fs::u8path(workbookPath));
|
||||
tinyxml2::XMLError rc = doc.Parse(workbookXml.c_str(), workbookXml.size());
|
||||
@@ -245,6 +274,7 @@ bool pqApplyIcdDescriptionsToWorkbook(const std::string& icdPath,
|
||||
return false;
|
||||
}
|
||||
|
||||
// 第三步:只处理“未匹配的原始数据”页,因为已匹配页已经有明确映射关系。
|
||||
tinyxml2::XMLElement* sheet = findWorksheet(doc, "未匹配的原始数据");
|
||||
if (!sheet) {
|
||||
if (message) *message = "没有找到 sheet: 未匹配的原始数据";
|
||||
@@ -259,6 +289,7 @@ bool pqApplyIcdDescriptionsToWorkbook(const std::string& icdPath,
|
||||
return false;
|
||||
}
|
||||
|
||||
// 第四步:逐行读取原始路径,按 DO 前缀回填 DOI 描述。
|
||||
int matched = 0;
|
||||
for (tinyxml2::XMLElement* row = header ? header->NextSiblingElement("Row") : nullptr;
|
||||
row;
|
||||
|
||||
Reference in New Issue
Block a user