Files
datatrace/source/icd_mapper.cpp
2026-07-14 17:02:23 +08:00

494 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "pq_app.h"
#include "tinyxml2.h"
#include <windows.h>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
#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 keylnClass + inst + "$MX$" + DOI/@name
* 3. 工作簿未匹配页的“原始路径”如果等于该 key或以 key + "$" 开头,
* 就认为该原始 MMS 路径属于这个 DOI把 DOI/@desc 写入目标列。
*
* 阅读本文件时可留意的 C++/库语法:
* - namespace fs = std::filesystem 是命名空间别名,后文 fs::path 即文件系统路径类型;
* - namespace { ... } 是匿名命名空间,使辅助函数只在当前编译单元内可见;
* - tinyxml2::XMLDocument 拥有解析后的节点XMLNode/XMLElement 指针不能脱离文档存活;
* - std::unordered_map 用散列表保存 ICD key -> desc适合大量精确查询
* - std::sort/std::transform 中的 [](...) { ... } 是短生命周期 lambda匿名函数
* - MultiByteToWideChar/WideCharToMultiByte 负责 Windows 本地代码页与 UTF-8 转换。
*/
namespace fs = std::filesystem;
namespace {
/**
* Function: readAllBinary
*
* @brief 读取或加载 “readAllBinary” 对应的数据,并转换为本模块后续逻辑使用的内存结构。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param path 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
std::string readAllBinary(const fs::path& path) {
std::ifstream ifs(path, std::ios::binary);
if (!ifs) throw std::runtime_error("Failed to open file: " + path.string());
std::ostringstream ss;
ss << ifs.rdbuf();
return ss.str();
}
/**
* Function: writeAllBinary
*
* @brief 把 “writeAllBinary” 对应的内存数据序列化并写入目标位置。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param path 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。
* @param text 待解析、比较、显示或发送的 UTF-8 文本。
*/
void writeAllBinary(const fs::path& path, const std::string& text) {
std::ofstream ofs(path, std::ios::binary);
if (!ofs) throw std::runtime_error("Failed to write file: " + path.string());
ofs << text;
}
/**
* Function: lowerAscii
*
* @brief 规范化 “lowerAscii” 对应的字符串或标识,减少格式差异对匹配造成的影响。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
std::string lowerAscii(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return s;
}
/**
* Function: declaresGbEncoding
*
* @brief 判断 “declaresGbEncoding” 所表达的条件;该函数只读取输入,不负责修改业务状态。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param text 待解析、比较、显示或发送的 UTF-8 文本。
* @return 条件成立或操作成功时为 true否则为 false。
*/
bool declaresGbEncoding(const std::string& text) {
std::string head = lowerAscii(text.substr(0, std::min<size_t>(text.size(), 256)));
return head.find("gb2312") != std::string::npos ||
head.find("gbk") != std::string::npos ||
head.find("gb18030") != std::string::npos;
}
/**
* Function: isLikelyUtf8
*
* @brief 判断 “isLikelyUtf8” 所表达的条件;该函数只读取输入,不负责修改业务状态。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param text 待解析、比较、显示或发送的 UTF-8 文本。
* @return 条件成立或操作成功时为 true否则为 false。
*/
bool isLikelyUtf8(const std::string& text) {
// 只做 UTF-8 字节合法性判断,不判断语义;合法就优先按 UTF-8 给 tinyxml2。
int remaining = 0;
for (unsigned char c : text) {
if (remaining == 0) {
if ((c & 0x80) == 0) continue;
if ((c & 0xE0) == 0xC0) remaining = 1;
else if ((c & 0xF0) == 0xE0) remaining = 2;
else if ((c & 0xF8) == 0xF0) remaining = 3;
else return false;
} else {
if ((c & 0xC0) != 0x80) return false;
--remaining;
}
}
return remaining == 0;
}
/**
* Function: ansiToUtf8
*
* @brief 实现 “ansiToUtf8” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param text 待解析、比较、显示或发送的 UTF-8 文本。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
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;
std::wstring wide(wideLen, L'\0');
MultiByteToWideChar(936, 0, text.data(), (int)text.size(), wide.data(), wideLen);
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wide.data(), wideLen, nullptr, 0, nullptr, nullptr);
std::string out(utf8Len, '\0');
WideCharToMultiByte(CP_UTF8, 0, wide.data(), wideLen, out.data(), utf8Len, nullptr, nullptr);
return out;
}
/**
* Function: loadXmlText
*
* @brief 读取或加载 “loadXmlText” 对应的数据,并转换为本模块后续逻辑使用的内存结构。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param path 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
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;
}
/**
* Function: localName
*
* @brief 实现 “localName” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param name 待解析、比较、显示或发送的 UTF-8 文本。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
std::string localName(const char* name) {
if (!name) return "";
std::string s(name);
size_t pos = s.find(':');
return pos == std::string::npos ? s : s.substr(pos + 1);
}
/**
* Function: attr
*
* @brief 实现 “attr” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param e 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @param name 待解析、比较、显示或发送的 UTF-8 文本。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
std::string attr(const tinyxml2::XMLElement* e, const char* name) {
const char* v = e ? e->Attribute(name) : nullptr;
return v ? v : "";
}
/**
* Function: collectIcdDescriptions
*
* @brief 遍历输入结构并收集 “collectIcdDescriptions” 所需的候选数据。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param element 当前处理的数据行、单元格、XML 节点或模型项。
* @param descByDo 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
*/
void collectIcdDescriptions(const tinyxml2::XMLElement* element,
std::unordered_map<std::string, std::string>& descByDo) {
if (!element) return;
if (localName(element->Name()) == "LN") {
std::string lnClass = attr(element, "lnClass");
std::string inst = attr(element, "inst");
if (!lnClass.empty()) {
for (const tinyxml2::XMLElement* child = element->FirstChildElement();
child;
child = child->NextSiblingElement()) {
if (localName(child->Name()) != "DOI") continue;
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;
}
}
}
for (const tinyxml2::XMLElement* child = element->FirstChildElement();
child;
child = child->NextSiblingElement()) {
collectIcdDescriptions(child, descByDo);
}
}
/**
* Function: parseIcdDescriptions
*
* @brief 解析 “parseIcdDescriptions” 对应的文本或结构,提取经过校验的业务字段。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param icdPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
std::unordered_map<std::string, std::string> parseIcdDescriptions(const fs::path& icdPath) {
tinyxml2::XMLDocument doc;
std::string xml = loadXmlText(icdPath);
tinyxml2::XMLError rc = doc.Parse(xml.c_str(), xml.size());
if (rc != tinyxml2::XML_SUCCESS) {
throw std::runtime_error("Failed to parse ICD XML: " + std::to_string((int)rc));
}
std::unordered_map<std::string, std::string> descByDo;
collectIcdDescriptions(doc.RootElement(), descByDo);
return descByDo;
}
/**
* Function: cellText
*
* @brief 实现 “cellText” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param cell 当前处理的数据行、单元格、XML 节点或模型项。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
std::string cellText(const tinyxml2::XMLElement* cell) {
const tinyxml2::XMLElement* data = cell ? cell->FirstChildElement("Data") : nullptr;
const char* text = data ? data->GetText() : nullptr;
return text ? text : "";
}
/**
* Function: cellIndex
*
* @brief 实现 “cellIndex” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param cell 当前处理的数据行、单元格、XML 节点或模型项。
* @param fallback 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @return 整数状态、索引或计数;入口函数以 0 表示成功。
*/
int cellIndex(const tinyxml2::XMLElement* cell, int fallback) {
const char* idx = cell ? cell->Attribute("ss:Index") : nullptr;
return idx && *idx ? std::max(1, std::atoi(idx)) : fallback;
}
/**
* Function: cellAt
*
* @brief 实现 “cellAt” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param row 当前处理的数据行、单元格、XML 节点或模型项。
* @param col 索引、数量、范围或偏移参数;具体边界由函数体校验。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
tinyxml2::XMLElement* cellAt(tinyxml2::XMLElement* row, int col) {
int current = 1;
for (tinyxml2::XMLElement* cell = row ? row->FirstChildElement("Cell") : nullptr;
cell;
cell = cell->NextSiblingElement("Cell")) {
current = cellIndex(cell, current);
if (current == col) return cell;
++current;
}
return nullptr;
}
/**
* Function: ensureCell
*
* @brief 向目标结构追加或保证存在 “ensureCell” 所描述的数据。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param doc tinyxml2 文档对象;节点的生命周期由该文档管理。
* @param row 当前处理的数据行、单元格、XML 节点或模型项。
* @param col 索引、数量、范围或偏移参数;具体边界由函数体校验。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
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);
return cell;
}
/**
* Function: setCellText
*
* @brief 把 “setCellText” 对应的变更应用到模型、控件或输出文档。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param doc tinyxml2 文档对象;节点的生命周期由该文档管理。
* @param cell 当前处理的数据行、单元格、XML 节点或模型项。
* @param text 待解析、比较、显示或发送的 UTF-8 文本。
*/
void setCellText(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* cell, const std::string& text) {
tinyxml2::XMLElement* data = cell->FirstChildElement("Data");
if (!data) {
data = doc.NewElement("Data");
data->SetAttribute("ss:Type", "String");
cell->InsertEndChild(data);
}
data->SetText(text.c_str());
}
/**
* Function: sortedKeysByLength
*
* @brief 实现 “sortedKeysByLength” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param values 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
std::vector<std::string> sortedKeysByLength(const std::unordered_map<std::string, std::string>& values) {
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;
});
return keys;
}
/**
* Function: pathMatchesDo
*
* @brief 实现 “pathMatchesDo” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param rawPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。
* @param doKey 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @return 条件成立或操作成功时为 true否则为 false。
*/
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 &&
rawPath[doKey.size()] == '$');
}
/**
* Function: descForPath
*
* @brief 实现 “descForPath” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param rawPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。
* @param descByDo 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @param orderedKeys 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
std::string descForPath(const std::string& rawPath,
const std::unordered_map<std::string, std::string>& descByDo,
const std::vector<std::string>& orderedKeys) {
for (const auto& key : orderedKeys) {
if (pathMatchesDo(rawPath, key)) {
auto it = descByDo.find(key);
return it == descByDo.end() ? "" : it->second;
}
}
return "";
}
/**
* Function: findWorksheet
*
* @brief 在现有数据结构中查找 “findWorksheet” 对应的对象或位置。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param doc tinyxml2 文档对象;节点的生命周期由该文档管理。
* @param sheetName 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
tinyxml2::XMLElement* findWorksheet(tinyxml2::XMLDocument& doc, const char* sheetName) {
tinyxml2::XMLElement* workbook = doc.FirstChildElement("Workbook");
for (tinyxml2::XMLElement* ws = workbook ? workbook->FirstChildElement("Worksheet") : nullptr;
ws;
ws = ws->NextSiblingElement("Worksheet")) {
const char* name = ws->Attribute("ss:Name");
if (name && std::string(name) == sheetName) return ws;
}
return nullptr;
}
/**
* Function: headerColumn
*
* @brief 实现 “headerColumn” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param headerRow 当前处理的数据行、单元格、XML 节点或模型项。
* @param title 待解析、比较、显示或发送的 UTF-8 文本。
* @return 整数状态、索引或计数;入口函数以 0 表示成功。
*/
int headerColumn(tinyxml2::XMLElement* headerRow, const std::string& title) {
int current = 1;
for (tinyxml2::XMLElement* cell = headerRow ? headerRow->FirstChildElement("Cell") : nullptr;
cell;
cell = cell->NextSiblingElement("Cell")) {
current = cellIndex(cell, current);
if (cellText(cell) == title) return current;
++current;
}
return 0;
}
} // namespace
/**
* Function: pqApplyIcdDescriptionsToWorkbook
*
* @brief 实现 “pqApplyIcdDescriptionsToWorkbook” 对应的模块内功能。
* @details 本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。
* @param icdPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。
* @param workbookPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。
* @param message 输出参数;函数在成功或失败时写入结果或诊断信息。
* @return 条件成立或操作成功时为 true否则为 false。
*/
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";
return false;
}
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());
if (rc != tinyxml2::XML_SUCCESS) {
if (message) *message = "表格文件解析失败: " + std::to_string((int)rc);
return false;
}
// 第三步:只处理“未匹配的原始数据”页,因为已匹配页已经有明确映射关系。
tinyxml2::XMLElement* sheet = findWorksheet(doc, "未匹配的原始数据");
if (!sheet) {
if (message) *message = "没有找到 sheet: 未匹配的原始数据";
return false;
}
tinyxml2::XMLElement* table = sheet->FirstChildElement("Table");
tinyxml2::XMLElement* header = table ? table->FirstChildElement("Row") : nullptr;
int pathCol = headerColumn(header, "原始路径");
int icdCol = headerColumn(header, "icd中代表的字段");
if (pathCol <= 0 || icdCol <= 0) {
if (message) *message = "未匹配 sheet 缺少 原始路径 或 icd中代表的字段 列";
return false;
}
// 第四步:逐行读取原始路径,按 DO 前缀回填 DOI 描述。
int matched = 0;
for (tinyxml2::XMLElement* row = header ? header->NextSiblingElement("Row") : nullptr;
row;
row = row->NextSiblingElement("Row")) {
tinyxml2::XMLElement* pathCell = cellAt(row, pathCol);
std::string rawPath = cellText(pathCell);
if (rawPath.empty()) continue;
std::string desc = descForPath(rawPath, descByDo, orderedKeys);
if (desc.empty()) continue;
tinyxml2::XMLElement* icdCell = ensureCell(doc, row, icdCol);
setCellText(doc, icdCell, desc);
++matched;
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
writeAllBinary(fs::u8path(workbookPath), printer.CStr());
if (message) {
*message = "ICD 导入完成DOI=" + std::to_string(descByDo.size()) +
",填入未匹配原始数据=" + std::to_string(matched);
}
return true;
} catch (const std::exception& e) {
if (message) *message = e.what();
return false;
}
}