#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "json.hpp" // nlohmann::json (单头文件版) #include "pq_app.h" #include "tinyxml2.h" // tinyxml2 /* * main.cpp * * 本文件是“映射解析 + 报表生成”的核心,也保留了命令行入口。 * * 主流程: * 1. loadValueMetas() 读取 ICD/XML 模板,把每个 的 name、desc、DO、DA、 * Topic、DataType、Item、Sequence 等上下文整理成 ValueMeta。 * 2. readJsonDataFile() 读取 DATATopic 的 jsondata,flattenJson() 压平成 * Value_... -> value 的键值对,mergeSequences() 把连续编号合并成数组。 * 3. buildItemsForSheet() 按 SheetSpec 生成每张工作表的 OutItem 行。 * 4. addOriginFileToContext() 读取 TraceTopic 的 origin/MMS 数据,matchOrigin() * 用 DO + DA + 相位 + 序列范围匹配最终值和原始值。 * 5. writeExcelWorkbook() 输出 Excel 2003 XML 格式的 .xls,供 Excel/GUI 预览打开。 * * 标志/转换要点: * - Sequence/@value == 7 代表星形接线 star。 * - Sequence/@value == 112 代表角形接线 delta。 * - DATA_TYPE "1" 会规范成 "01",用于匹配统计/闪变/实时 Sheet。 * - 指标名前缀 G_/MAX_/MIN_ 分别转换成 95值/最大值/最小值;无前缀默认平均值。 * - DA 中 phs* 是相位占位符,星形转换为 phsA/phsB/phsC,角形转换为 * phsAB/phsBC/phsCA。 * - Value 名称中的 %start,end% 是 XML 序列模板;运行时显示成 [start-end]。 * * 阅读本文件时可留意的 C++/库语法: * - using json = nlohmann::json 给长类型起别名;json::parse/contains/value 完成 JSON 解析; * - struct ValueMeta/OutItem/WorkbookContext 是逐阶段的数据模型,数据按主流程向后传递; * - const T& 表示只读引用,避免复制大型字符串/容器;T* 常用于可为空的输出或上下文; * - std::vector 保持有序行,map/set 保持键有序,unordered_map 用于高频散列查找; * - std::regex 解析键名、范围和路径;std::stable_sort 在键相等时保留原始先后顺序; * - lambda 用于局部排序/筛选/写行逻辑,捕获的 &ctx 等对象只在当前调用期间有效; * - 输出的 .xls 实质是 Excel 2003 XML,writeSsCell 等函数直接写 SpreadsheetML 标签。 */ using json = nlohmann::json; namespace fs = std::filesystem; /* * XML 节点的完整业务元信息。 * * 输入:loadValueMetas() 在递归遍历 XML 时填充。 * 输出:后续用于把 jsondata 的 key 映射成 DO/DA、中文描述、接线方式、Topic/DataType。 */ struct ValueMeta { std::string name; // Value/@name,可能是普通指标 V1,也可能是序列模板 MAX_V_%2,50%。 std::string desc; // Value/@desc,最终表格“名称”列主要来自这里。 std::string doPath; // Value/@DO,IEC 61850 MMS 对象路径前半段。 std::string daPath; // Value/@DA,IEC 61850 MMS 数据属性路径后半段。 std::string wiring = "common"; // star/delta/common:由 Sequence/@value 推断。 std::string topic; // 父级 Topic/@name,区分 HISDATA/RTDATA。 std::string dataType; // 父级 DataType/@value,区分 01/02/03。 std::string itemName; // 父级 Item/@name,如 V/I/PQ/F_S/F_L。 std::string seqValue; // 父级 Sequence/@value,后续判断 T 相或接线方式会用到。 bool hasOffset = false; // Value/@Offset 是否存在。 int offset = 0; // 最终值偏移量,用于把最终值数组范围挪到 origin 对齐范围。 bool isSequence = false; // name 是否匹配 %start,end% 序列模板。 int seqStart = 0; // 例如 %2,50% 里的 2。 int seqEnd = 0; // 例如 %2,50% 里的 50。 std::string seqPrefixName; // 例如 MAX_V_%2,50% 的前缀 MAX_V_。 }; /** * Function: readAll * * @brief 读取或加载 “readAll” 对应的数据,并转换为本模块后续逻辑使用的内存结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param p 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string readAll(const fs::path& p) { std::ifstream ifs(p, std::ios::binary); if (!ifs) throw std::runtime_error("Failed to open: " + p.string()); std::ostringstream oss; oss << ifs.rdbuf(); return oss.str(); } /** * Function: ltrimBOM * * @brief 实现解析/报表层的 “ltrimBOM” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string ltrimBOM(std::string s) { const std::string bom = "\xEF\xBB\xBF"; if (s.rfind(bom, 0) == 0) s.erase(0, bom.size()); return s; } // DO/DA 中有时会出现 A||A||B 这种重复候选;这里保持顺序并去重,减少表格噪声。 /** * Function: collapseRepeatedAlternatives * * @brief 规范化 “collapseRepeatedAlternatives” 对应的字符串或标识,减少格式差异对匹配造成的影响。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string collapseRepeatedAlternatives(const std::string& s) { std::vector parts; std::unordered_set seen; size_t start = 0; while (true) { size_t pos = s.find("||", start); std::string part = pos == std::string::npos ? s.substr(start) : s.substr(start, pos - start); if (!seen.count(part)) { seen.insert(part); parts.push_back(part); } if (pos == std::string::npos) break; start = pos + 2; } std::ostringstream oss; for (size_t i = 0; i < parts.size(); ++i) { if (i) oss << "||"; oss << parts[i]; } return oss.str(); } /** * Function: toStr * * @brief 实现解析/报表层的 “toStr” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param v 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string toStr(const json& v) { if (v.is_null()) return "null"; if (v.is_boolean()) return v.get() ? "true" : "false"; if (v.is_number_integer()) return std::to_string(v.get()); if (v.is_number_unsigned()) return std::to_string(v.get()); if (v.is_number_float()) { std::ostringstream oss; oss.setf(std::ios::fixed); oss.precision(6); oss << v.get(); std::string s = oss.str(); while (s.find('.') != std::string::npos && !s.empty() && s.back() == '0') s.pop_back(); if (!s.empty() && s.back() == '.') s.pop_back(); if (s.empty()) s = "0"; return s; } if (v.is_string()) return v.get(); return v.dump(); } /* * 把嵌套 JSON 压平成下划线连接的 key。 * * 输入示例:{"Value":{"V":{"A":{"MAX_V1":12}}}} * 输出示例:Value_V_A_MAX_V1 -> 12 * * 数组会直接转成逗号分隔值,这样后续单元格可以拆成多列展示。 */ /** * Function: flattenJson * * @brief 实现解析/报表层的 “flattenJson” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param j 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param prefix 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param out 输出参数;函数在成功或失败时写入结果或诊断信息。 */ static void flattenJson(const json& j, const std::string& prefix, std::vector>& out) { if (j.is_object()) { for (auto it = j.begin(); it != j.end(); ++it) { std::string key = prefix.empty() ? it.key() : (prefix + "_" + it.key()); flattenJson(it.value(), key, out); } return; } if (j.is_array()) { std::ostringstream oss; for (size_t i = 0; i < j.size(); ++i) { if (i) oss << ","; oss << toStr(j[i]); } out.push_back({prefix, oss.str()}); return; } out.push_back({prefix, toStr(j)}); } /* * 读取 XML:Value/@name -> 完整元信息列表。 * * 输入:xmlPath,通常是下载的 ICD/XML 映射文件。 * 输出:unordered_map>。同一个 name 可能在不同 Topic、 * DataType 或接线方式下出现,所以值是 vector。 * * 递归 walk 的上下文参数含义: * wiring 当前 Sequence 推断出的接线方式,默认 common; * topic 最近一个父级 Topic/@name; * dataType 最近一个父级 DataType/@value; * itemName 最近一个父级 Item/@name; * seqValue 最近一个父级 Sequence/@value。 */ /** * Function: loadValueMetas * * @brief 读取或加载 “loadValueMetas” 对应的数据,并转换为本模块后续逻辑使用的内存结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param xmlPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::unordered_map> loadValueMetas(const fs::path& xmlPath) { std::unordered_map> mp; tinyxml2::XMLDocument doc; std::string xmlContent = readAll(xmlPath); if (doc.Parse(xmlContent.c_str()) != tinyxml2::XML_SUCCESS) { throw std::runtime_error("Failed to parse xml: " + xmlPath.string()); } std::regex seqRe(R"(^(.*)%(\d+),(\d+)%$)"); std::function walk = [&](tinyxml2::XMLElement* e, std::string wiring, std::string topic, std::string dataType, std::string itemName, std::string seqValue) { for (; e; e = e->NextSiblingElement()) { std::string nodeName = e->Name() ? e->Name() : ""; std::string nextWiring = wiring; std::string nextTopic = topic; std::string nextDataType = dataType; std::string nextItemName = itemName; std::string nextSeqValue = seqValue; // Topic/DataType/Item/Sequence 不是最终数据项,而是给子孙 Value 提供上下文。 if (nodeName == "Topic") { const char* name = e->Attribute("name"); nextTopic = name ? name : ""; } else if (nodeName == "DataType") { const char* value = e->Attribute("value"); nextDataType = value ? value : ""; } else if (nodeName == "Item") { const char* name = e->Attribute("name"); nextItemName = name ? name : ""; } if (nodeName == "Sequence") { const char* seqValue = e->Attribute("value"); nextSeqValue = seqValue ? seqValue : ""; // 项目约定:7=星形接线,112=角形接线;其他 Sequence 保持 common。 if (seqValue && std::string(seqValue) == "7") nextWiring = "star"; else if (seqValue && std::string(seqValue) == "112") nextWiring = "delta"; } if (nodeName == "Value") { const char* name = e->Attribute("name"); const char* desc = e->Attribute("desc"); const char* doPath = e->Attribute("DO"); const char* daPath = e->Attribute("DA"); const char* offset = e->Attribute("Offset"); if (name) { ValueMeta vm; vm.name = name; vm.desc = desc ? desc : ""; vm.doPath = doPath ? collapseRepeatedAlternatives(doPath) : ""; vm.daPath = daPath ? collapseRepeatedAlternatives(daPath) : ""; vm.wiring = nextWiring; vm.topic = nextTopic; vm.dataType = nextDataType; vm.itemName = nextItemName; vm.seqValue = nextSeqValue; if (offset) { vm.hasOffset = true; vm.offset = std::stoi(offset); } std::smatch m; // name 中的 %start,end% 表示一个序列模板,后面要和 jsondata 的 [start-end] 对齐。 if (std::regex_match(vm.name, m, seqRe)) { vm.isSequence = true; vm.seqPrefixName = m[1].str(); // 例如 MAX_V vm.seqStart = std::stoi(m[2].str()); vm.seqEnd = std::stoi(m[3].str()); } mp[vm.name].push_back(vm); } } walk(e->FirstChildElement(), nextWiring, nextTopic, nextDataType, nextItemName, nextSeqValue); } }; walk(doc.RootElement(), "common", "", "", "", ""); return mp; } // 相位 static const std::vector PHASES = {"A", "B", "C", "T"}; /** * Function: phaseRank * * @brief 实现解析/报表层的 “phaseRank” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::unordered_map phaseRank() { std::unordered_map m; for (int i = 0; i < (int)PHASES.size(); ++i) m[PHASES[i]] = i; return m; } // 从 key 中提取 “相位之后的 metricName” // 例如:Value_V_A_MAX_V1 -> MAX_V1 /** * Function: metricAfterPhase * * @brief 实现解析/报表层的 “metricAfterPhase” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string metricAfterPhase(const std::string& key) { std::string base = key; auto bpos = base.find('['); if (bpos != std::string::npos) base = base.substr(0, bpos); std::vector parts; std::stringstream ss(base); std::string tok; while (std::getline(ss, tok, '_')) parts.push_back(tok); auto pr = phaseRank(); int lastPhaseI = -1; for (int i = 0; i < (int)parts.size(); ++i) { if (pr.find(parts[i]) != pr.end()) lastPhaseI = i; } if (lastPhaseI == -1 || lastPhaseI + 1 >= (int)parts.size()) return ""; std::string m = parts[lastPhaseI + 1]; for (int i = lastPhaseI + 2; i < (int)parts.size(); ++i) m += "_" + parts[i]; return m; } // 尝试把 key 识别成序列: // 1) ..._ // 2) ..._ (如 ..._V2, ..._VA50) /** * Function: parseSequenceKey * * @brief 解析 “parseSequenceKey” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @param groupId 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param idx 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool parseSequenceKey(const std::string& key, std::string& groupId, int& idx) { auto posBracket = key.find('['); std::string base = (posBracket == std::string::npos) ? key : key.substr(0, posBracket); static const std::regex re_underscore_num(R"(^(.*)_(\d+)$)"); static const std::regex re_letters_num(R"(^([A-Za-z]+)(\d+)$)"); std::smatch m; if (std::regex_match(base, m, re_underscore_num)) { groupId = m[1].str(); idx = std::stoi(m[2].str()); return true; } auto lastUnd = base.rfind('_'); if (lastUnd != std::string::npos) { std::string pre = base.substr(0, lastUnd); std::string last = base.substr(lastUnd + 1); std::smatch m2; if (std::regex_match(last, m2, re_letters_num)) { groupId = pre + "_" + m2[1].str(); idx = std::stoi(m2[2].str()); return true; } } return false; } /* * 合并 jsondata 中的序列项。 * * 输入:flattenJson() 得到的 key/value 列表,例如: * Value_V_A_MAX_V_2=... * Value_V_A_MAX_V_3=... * * 输出:合并成 Value_V_A_MAX_V[2-3]=v2,v3。 * * 保护规则: * 如果相位后的 metricName 已经是 XML 中定义的真实指标名,例如 V1/MAX_V1/G_V1, * 就不能把它误当成“序列编号 1”合并,否则会把普通指标压成数组。 */ static std::vector> mergeSequences(const std::vector>& flat, const std::unordered_set& xmlNames) { struct SeqGroup { size_t firstPos; std::map idx2val; }; std::unordered_map groups; std::vector isSeq(flat.size(), false); std::vector key2group(flat.size()); for (size_t i = 0; i < flat.size(); ++i) { const auto& k = flat[i].first; // ✅ 如果 XML 定义了该指标(例如 V1/MAX_V1/...),就不要当作序列 std::string mname = metricAfterPhase(k); if (!mname.empty() && xmlNames.count(mname)) { continue; } std::string gid; int idx = 0; if (parseSequenceKey(k, gid, idx)) { isSeq[i] = true; key2group[i] = gid; auto& g = groups[gid]; if (g.idx2val.empty()) g.firstPos = i; g.idx2val[idx] = flat[i].second; } } std::unordered_map mergeable; for (auto& kv : groups) { mergeable[kv.first] = (kv.second.idx2val.size() >= 2); } std::vector> out; std::unordered_map emitted; for (size_t i = 0; i < flat.size(); ++i) { if (isSeq[i] && mergeable[key2group[i]]) { const std::string& gid = key2group[i]; if (emitted[gid]) continue; emitted[gid] = true; const auto& g = groups[gid]; int start = g.idx2val.begin()->first; int end = g.idx2val.rbegin()->first; std::ostringstream oss; for (int x = start; x <= end; ++x) { if (x != start) oss << ","; auto it = g.idx2val.find(x); oss << (it == g.idx2val.end() ? "null" : it->second); } std::ostringstream key; key << gid << "[" << start << "-" << end << "]"; out.push_back({key.str(), oss.str()}); } else { out.push_back(flat[i]); } } return out; } struct ParsedKey { std::string phase; // A/B/C/T/"" std::string stat; // 95值/平均值/最大值/最小值 std::string baseName; // 去掉 G_/MAX_/MIN_ 后的 name }; /* * 从 jsondata key 中提取排序和展示维度。 * * 输入:Value_V_A_MAX_V1、Value_I_T_G_THD 等。 * 输出: * phase A/B/C/T; * stat 由指标名前缀转换:G_=95值,MAX_=最大值,MIN_=最小值,默认平均值; * baseName 去掉统计前缀后的基础指标名,用于匹配 XML Value/@name。 */ /** * Function: parseForSort * * @brief 解析 “parseForSort” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static ParsedKey parseForSort(const std::string& key) { std::string base = key; auto bpos = base.find('['); if (bpos != std::string::npos) base = base.substr(0, bpos); std::vector parts; std::stringstream ss(base); std::string tok; while (std::getline(ss, tok, '_')) parts.push_back(tok); auto pr = phaseRank(); int lastPhaseI = -1; std::string phase; for (int i = 0; i < (int)parts.size(); ++i) { if (pr.find(parts[i]) != pr.end()) { lastPhaseI = i; phase = parts[i]; } } std::string metric; if (lastPhaseI != -1 && lastPhaseI + 1 < (int)parts.size()) { metric = parts[lastPhaseI + 1]; for (int i = lastPhaseI + 2; i < (int)parts.size(); ++i) metric += "_" + parts[i]; } else { metric = parts.empty() ? "" : parts.back(); } ParsedKey pk; pk.phase = phase; pk.stat = "平均值"; pk.baseName = metric; auto startsWith = [&](const std::string& s, const std::string& pre) { return s.size() >= pre.size() && s.compare(0, pre.size(), pre) == 0; }; if (startsWith(metric, "G_")) { pk.stat = "95值"; pk.baseName = metric.substr(2); } else if (startsWith(metric, "MAX_")) { pk.stat = "最大值"; pk.baseName = metric.substr(4); } else if (startsWith(metric, "MIN_")) { pk.stat = "最小值"; pk.baseName = metric.substr(4); } return pk; } /** * Function: cleanDescSuffix * * @brief 规范化 “cleanDescSuffix” 对应的字符串或标识,减少格式差异对匹配造成的影响。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param desc 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string cleanDescSuffix(std::string desc) { static const std::regex re(R"((平均值|最大值|最小值|95值)$)"); return std::regex_replace(desc, re, ""); } /** * Function: getDisplayWidth * * @brief 实现解析/报表层的 “getDisplayWidth” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 整数状态、索引或计数;入口函数以 0 表示成功。 */ static int getDisplayWidth(const std::string& s) { int w = 0; for (size_t i = 0; i < s.size(); ) { unsigned char c = static_cast(s[i]); if (c < 0x80) { // ASCII w += 1; ++i; } else if ((c & 0xE0) == 0xC0 && i + 1 < s.size()) { // 2字节UTF-8 w += 2; i += 2; } else if ((c & 0xF0) == 0xE0 && i + 2 < s.size()) { // 3字节UTF-8,中文常见 w += 2; i += 3; } else if ((c & 0xF8) == 0xF0 && i + 3 < s.size()) { // 4字节UTF-8 w += 2; i += 4; } else { w += 1; ++i; } } return w; } /** * Function: appendPad * * @brief 向目标结构追加或保证存在 “appendPad” 所描述的数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param oss 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param targetWidth 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void appendPad(std::ostringstream& oss, const std::string& s, int targetWidth) { oss << s; int pad = targetWidth - getDisplayWidth(s); if (pad < 1) pad = 1; // 至少留 1 个空格,避免粘连 oss << std::string(pad, ' '); } /** * Function: startsWithStr * * @brief 实现解析/报表层的 “startsWithStr” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pre 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool startsWithStr(const std::string& s, const std::string& pre) { return s.size() >= pre.size() && s.compare(0, pre.size(), pre) == 0; } // 从类似 Value_V_A_MAX_V[2-50] 中取出区间 /** * Function: parseKeyRange * * @brief 解析 “parseKeyRange” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @param start 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param end 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool parseKeyRange(const std::string& key, int& start, int& end) { std::smatch m; std::regex re(R"(\[(\d+)-(\d+)\])"); if (std::regex_search(key, m, re)) { start = std::stoi(m[1].str()); end = std::stoi(m[2].str()); return true; } return false; } // 给普通项找 meta;找不到时,再去匹配序列模板项 /** * Function: trimRightUnderscore * * @brief 规范化 “trimRightUnderscore” 对应的字符串或标识,减少格式差异对匹配造成的影响。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string trimRightUnderscore(std::string s) { while (!s.empty() && s.back() == '_') { s.pop_back(); } return s; } /** * Function: rawMetricNameFromKey * * @brief 实现解析/报表层的 “rawMetricNameFromKey” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string rawMetricNameFromKey(const std::string& key) { return metricAfterPhase(key); } /** * Function: preferredMetaName * * @brief 实现解析/报表层的 “preferredMetaName” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param rawMetric 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pk 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string preferredMetaName(const std::string& rawMetric, const ParsedKey& pk) { if (startsWithStr(rawMetric, "G_")) return pk.baseName; return rawMetric.empty() ? pk.baseName : rawMetric; } /** * Function: secondaryMetaName * * @brief 实现解析/报表层的 “secondaryMetaName” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param rawMetric 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pk 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string secondaryMetaName(const std::string& rawMetric, const ParsedKey& pk) { std::string preferred = preferredMetaName(rawMetric, pk); std::string secondary = (preferred == pk.baseName) ? rawMetric : pk.baseName; return secondary == preferred ? "" : secondary; } /** * Function: isNeutralDesc * * @brief 判断 “isNeutralDesc” 所表达的条件;该函数只读取输入,不负责修改业务状态。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param desc 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool isNeutralDesc(const std::string& desc) { return desc.find("95值") == std::string::npos && desc.find("平均值") == std::string::npos && desc.find("最大值") == std::string::npos && desc.find("最小值") == std::string::npos; } /** * Function: statMatchesMeta * * @brief 实现解析/报表层的 “statMatchesMeta” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pk 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool statMatchesMeta(const ValueMeta& vm, const ParsedKey& pk) { return !pk.stat.empty() && vm.desc.find(pk.stat) != std::string::npos; } /** * Function: daMatchesPhase * * @brief 实现解析/报表层的 “daMatchesPhase” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param daPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool daMatchesPhase(const std::string& daPath, const std::string& phase) { bool phased = daPath.find("phs*") != std::string::npos; if (phase == "A" || phase == "B" || phase == "C") return phased; if (phase == "T") return !phased; return true; } /** * Function: wiringAllowed * * @brief 实现解析/报表层的 “wiringAllowed” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool wiringAllowed(const ValueMeta& vm, const std::string& wiring) { if (wiring.empty() || wiring == "any") return true; return vm.wiring == wiring || vm.wiring == "common"; } struct MetaFilter { std::string topic; std::string dataType; }; /** * Function: metaFilterAllowed * * @brief 实现解析/报表层的 “metaFilterAllowed” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param filter 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool metaFilterAllowed(const ValueMeta& vm, const MetaFilter& filter) { if (!filter.topic.empty() && vm.topic != filter.topic) return false; if (!filter.dataType.empty() && vm.dataType != filter.dataType) return false; return true; } // 多个 ValueMeta 都可能同名时,用相位、统计类型、Offset、Tot 等特征打分选最像的那一个。 /** * Function: scoreMetaForKey * * @brief 实现解析/报表层的 “scoreMetaForKey” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pk 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 整数状态、索引或计数;入口函数以 0 表示成功。 */ static int scoreMetaForKey(const ValueMeta& vm, const ParsedKey& pk) { int score = 0; if (daMatchesPhase(vm.daPath, pk.phase)) score += 20; if (!pk.stat.empty() && vm.desc.find(pk.stat) != std::string::npos) score += 8; if (startsWithStr(pk.stat, "95") && isNeutralDesc(vm.desc)) score += 6; if (vm.hasOffset) score += 5; if (pk.phase == "T" && vm.doPath.find("Tot") != std::string::npos) score += 4; if ((pk.phase == "A" || pk.phase == "B" || pk.phase == "C") && vm.doPath.find("Tot") == std::string::npos) score += 4; return score; } /** * Function: bestMeta * * @brief 实现解析/报表层的 “bestMeta” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param candidates 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pk 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static const ValueMeta* bestMeta(const std::vector& candidates, const ParsedKey& pk) { const ValueMeta* best = nullptr; int bestScore = -1; bool hasStatMatched = false; for (const ValueMeta* vm : candidates) { if (statMatchesMeta(*vm, pk)) { hasStatMatched = true; break; } } for (const ValueMeta* vm : candidates) { if (hasStatMatched && !statMatchesMeta(*vm, pk)) continue; int score = scoreMetaForKey(*vm, pk); if (!best || score > bestScore) { best = vm; bestScore = score; } } return best; } /** * Function: collectByName * * @brief 遍历输入结构并收集 “collectByName” 所需的候选数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param metas 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param name 待解析、比较、显示或发送的 UTF-8 文本。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param filter 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param out 输出参数;函数在成功或失败时写入结果或诊断信息。 */ static void collectByName(const std::unordered_map>& metas, const std::string& name, const std::string& wiring, const MetaFilter& filter, std::vector& out) { if (name.empty()) return; auto it = metas.find(name); if (it == metas.end()) return; for (const auto& vm : it->second) { if (wiringAllowed(vm, wiring) && metaFilterAllowed(vm, filter)) out.push_back(&vm); } } /** * Function: collectSequenceCandidates * * @brief 遍历输入结构并收集 “collectSequenceCandidates” 所需的候选数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param metas 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wantedName 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pk 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param filter 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param keyStart 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param keyEnd 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector collectSequenceCandidates( const std::unordered_map>& metas, const std::string& wantedName, const ParsedKey& pk, const std::string& wiring, const MetaFilter& filter, int keyStart, int keyEnd) { std::vector out; std::string wanted = trimRightUnderscore(wantedName); for (const auto& kv : metas) { for (const ValueMeta& vm : kv.second) { if (!vm.isSequence) continue; if (!wiringAllowed(vm, wiring)) continue; if (!metaFilterAllowed(vm, filter)) continue; if (vm.seqStart != keyStart || vm.seqEnd != keyEnd) continue; std::string xmlPrefix = trimRightUnderscore(vm.seqPrefixName); std::string xmlBase = xmlPrefix; if (startsWithStr(xmlBase, "G_")) xmlBase = xmlBase.substr(2); else if (startsWithStr(xmlBase, "MAX_")) xmlBase = xmlBase.substr(4); else if (startsWithStr(xmlBase, "MIN_")) xmlBase = xmlBase.substr(4); if (xmlPrefix == wanted || xmlBase == wanted) out.push_back(&vm); } } return out; } static const ValueMeta* findMetaForKey( const std::unordered_map>& metas, const std::string& key, const ParsedKey& pk, const std::string& wiring = "any", const MetaFilter& filter = {}) { std::string rawMetric = rawMetricNameFromKey(key); std::string preferred = preferredMetaName(rawMetric, pk); std::string secondary = secondaryMetaName(rawMetric, pk); // 第一优先级:key 已经是 [start-end] 数组时,匹配 XML 的 %start,end% 序列模板。 int keyStart = 0, keyEnd = 0; if (parseKeyRange(key, keyStart, keyEnd)) { std::string seqPreferred = rawMetric.empty() ? preferred : rawMetric; std::string seqSecondary = (seqPreferred == preferred) ? secondary : preferred; auto seqCandidates = collectSequenceCandidates(metas, seqPreferred, pk, wiring, filter, keyStart, keyEnd); if (seqCandidates.empty()) seqCandidates = collectSequenceCandidates(metas, seqSecondary, pk, wiring, filter, keyStart, keyEnd); if (!seqCandidates.empty()) return bestMeta(seqCandidates, pk); } // 第二优先级:按指标名精确匹配。G_ 前缀会优先转成 baseName,因为 95 值常复用同一 XML 描述。 std::vector exactCandidates; collectByName(metas, preferred, wiring, filter, exactCandidates); if (exactCandidates.empty()) collectByName(metas, secondary, wiring, filter, exactCandidates); return bestMeta(exactCandidates, pk); } /** * Function: parseDaValueOffset * * @brief 解析 “parseDaValueOffset” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param daPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param offset 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool parseDaValueOffset(const std::string& daPath, int& offset) { std::smatch m; std::regex re(R"(%(-?\d+))"); if (!std::regex_search(daPath, m, re)) return false; offset = std::stoi(m[1].str()); return true; } // 把 XML 中序列 DA,如 phs*Har[%-2]$mag$f,按“数组基准 + 取值偏移量”改显示成 phs*Har[0-48]$mag$f /** * Function: rewriteDaRangeForDisplay * * @brief 实现解析/报表层的 “rewriteDaRangeForDisplay” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param daPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param keyStart 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param keyEnd 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string rewriteDaRangeForDisplay(const std::string& daPath, const ValueMeta& vm, int keyStart, int keyEnd) { if (!vm.isSequence) return daPath; (void)keyStart; (void)keyEnd; int valueOffset = 0; parseDaValueOffset(daPath, valueOffset); int dispStart = vm.seqStart + valueOffset; int dispEnd = vm.seqEnd + valueOffset; std::smatch m; std::regex re(R"(\[%(-?\d+)\])"); if (std::regex_search(daPath, m, re)) { std::ostringstream oss; oss << "[" << dispStart << "-" << dispEnd << "]"; return std::regex_replace(daPath, re, oss.str(), std::regex_constants::format_first_only); } return daPath; } // 在当前目录找一个后缀文件(找不到就抛) /** * Function: findFirstByExt * * @brief 在现有数据结构中查找 “findFirstByExt” 对应的对象或位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ext 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static fs::path findFirstByExt(const std::string& ext) { for (auto& e : fs::directory_iterator(fs::current_path())) { if (!e.is_regular_file()) continue; if (e.path().extension() == ext) return e.path(); } throw std::runtime_error("No file found with extension: " + ext + " in current folder"); } /** * Function: findFirstInputTxt * * @brief 在现有数据结构中查找 “findFirstInputTxt” 对应的对象或位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static fs::path findFirstInputTxt() { for (auto& e : fs::directory_iterator(fs::current_path())) { if (!e.is_regular_file()) continue; if (e.path().extension() != ".txt") continue; std::string name = e.path().filename().string(); if (name == "final_sorted.txt") continue; // 跳过输出文件 return e.path(); } throw std::runtime_error("No input .txt found in current folder"); } struct HeaderItem { std::string key; std::string val; }; /** * Function: isHeaderKey * * @brief 判断 “isHeaderKey” 所表达的条件;该函数只读取输入,不负责修改业务状态。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool isHeaderKey(const std::string& key) { return key == "DATA_TYPE" || key == "Monitor" || key == "Value_FLAG" || key == "Value_TIME" || key == "Value_interval"; } // 【新增】相别排序:A相 -> B相 -> C相 -> T相 /** * Function: phaseOrderOf * * @brief 实现解析/报表层的 “phaseOrderOf” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 整数状态、索引或计数;入口函数以 0 表示成功。 */ static int phaseOrderOf(const std::string& phase) { if (phase == "A") return 1; if (phase == "B") return 2; if (phase == "C") return 3; if (phase == "T") return 0; return 99; } struct OutItem { std::string stat; std::string category; int phaseOrder; // 【新增】A/B/C/T 排序 std::string attrSort; std::string phase; std::string line; std::string label; std::string arrayBase; std::string valueOffset; std::string finalOffset; std::string configText; std::string finalText; std::string doPath; std::string daPath; std::string wiring = "common"; // star/delta/common bool hasOffset = false; int offset = 0; }; /* * 一张输出工作表的筛选条件。 * * topic/dataType/wiring 决定从 XML ValueMeta 中取哪些项; * showStatBlocks 决定统计类 Sheet 是否按 95值/平均值/最大值/最小值分段显示; * frontType 用于 GUI 选择统计(cfg_stat_data)或实时(cfg_3s_data)时裁剪 Sheet。 */ struct SheetSpec { std::string name; std::string topic; std::string dataType; std::string wiring; bool showStatBlocks = false; std::string frontType; }; struct SheetStats { std::string sheetName; int normal = 0; int abnormal = 0; int missing = 0; }; struct OriginRecord { std::string path; std::string value; std::string reportName; int sourceOrder = 0; }; struct OriginHeader { std::string title; std::string fileName; int sourceOrder = 0; std::vector fields; }; struct OriginData { std::vector records; std::vector headers; std::unordered_map> pathToRecords; std::unordered_set used; // matchOrigin 命中后标记,最后可输出“已匹配/未匹配”清单。 }; struct OriginMatch { std::string path; std::string value; std::string reportName; int sourceOrder = -1; int rangeStart = std::numeric_limits::min(); int rangeEnd = std::numeric_limits::min(); std::vector valueIndexes; }; struct ComparePlan { int originPickStart = std::numeric_limits::min(); int originPickEnd = std::numeric_limits::min(); }; struct JsonDataBucket { std::vector headers; std::unordered_map valueByKey; bool hasData = false; }; struct WorkbookContext { fs::path xmlPath; std::unordered_map> valueMetas; std::unordered_set xmlNames; // XML 中存在的原始 name,用于避免序列误合并。 std::unordered_map dataByType; // DATA_TYPE -> jsondata 桶。 OriginData originData; bool hasOrigin = false; std::string frontType; }; /** * Function: jsonScalarForOutput * * @brief 实现解析/报表层的 “jsonScalarForOutput” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param v 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string jsonScalarForOutput(const json& v) { if (v.is_string()) return v.get(); return v.dump(); } /** * Function: reportNameFromRoot * * @brief 实现解析/报表层的 “reportNameFromRoot” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param root 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param sourceOrder 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string reportNameFromRoot(const json& root, const fs::path& originPath, int sourceOrder) { if (root.is_object() && root.contains("rpt_id")) return jsonScalarForOutput(root["rpt_id"]); if (root.is_object() && root.contains("rpt_name")) return jsonScalarForOutput(root["rpt_name"]); if (root.is_object() && root.contains("report_name")) return jsonScalarForOutput(root["report_name"]); if (root.is_object() && root.contains("rpt_no")) return "rpt_no=" + jsonScalarForOutput(root["rpt_no"]); return originPath.filename().u8string(); } /** * Function: originHeaderKeys * * @brief 实现解析/报表层的 “originHeaderKeys” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static const std::vector& originHeaderKeys() { static const std::vector keys = { "rpt_no", "data_time", "mp_id", "rpt_id", "voltage_level", "v_wiring_type" }; return keys; } /** * Function: originHeaderDisplayKeys * * @brief 实现解析/报表层的 “originHeaderDisplayKeys” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static const std::vector& originHeaderDisplayKeys() { static const std::vector keys = { "rpt_no", "data_time", "mp_id", "voltage_level", "v_wiring_type" }; return keys; } /** * Function: rootHeaderValue * * @brief 实现解析/报表层的 “rootHeaderValue” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param root 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string rootHeaderValue(const json& root, const std::string& key) { if (!root.is_object()) return ""; auto it = root.find(key); if (it == root.end()) return ""; return jsonScalarForOutput(*it); } /** * Function: appendOriginHeader * * @brief 向目标结构追加或保证存在 “appendOriginHeader” 所描述的数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param data 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param sourceOrder 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param root 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param reportName 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void appendOriginHeader(OriginData& data, const fs::path& originPath, int sourceOrder, const json& root, const std::string& reportName) { OriginHeader header; header.title = reportName.empty() ? originPath.filename().u8string() : reportName; header.fileName = originPath.filename().u8string(); header.sourceOrder = sourceOrder; for (const auto& key : originHeaderKeys()) { header.fields.push_back({key, rootHeaderValue(root, key)}); } data.headers.push_back(std::move(header)); } /** * Function: parseJsonRoots * * @brief 解析 “parseJsonRoots” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param text 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector parseJsonRoots(const std::string& text) { std::vector roots; size_t pos = 0; while (pos < text.size()) { while (pos < text.size() && std::isspace(static_cast(text[pos]))) ++pos; if (pos >= text.size()) break; char open = text[pos]; if (open != '{' && open != '[') { throw std::runtime_error("origin file contains unexpected content outside JSON object"); } // origin 文件可能是多个 JSON 对象直接拼接;这里用括号深度扫描出每个完整根对象。 bool inString = false; bool escape = false; int depth = 0; size_t end = pos; for (; end < text.size(); ++end) { char c = text[end]; if (inString) { if (escape) escape = false; else if (c == '\\') escape = true; else if (c == '"') inString = false; continue; } if (c == '"') { inString = true; } else if (c == '{' || c == '[') { ++depth; } else if (c == '}' || c == ']') { --depth; if (depth == 0) { ++end; break; } } } if (depth != 0) throw std::runtime_error("origin file contains incomplete JSON object"); roots.push_back(json::parse(text.substr(pos, end - pos))); pos = end; } return roots; } /** * Function: appendOriginRoot * * @brief 向目标结构追加或保证存在 “appendOriginRoot” 所描述的数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param data 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param sourceOrder 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param root 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 */ static void appendOriginRoot(OriginData& data, const fs::path& originPath, int sourceOrder, const json& root) { std::string reportName = reportNameFromRoot(root, originPath, sourceOrder); appendOriginHeader(data, originPath, sourceOrder, root, reportName); // 常见 origin 结构是 { "mms_json": { "path": value } };如果没有 mms_json,则直接把根对象当路径表。 const json* mms = &root; if (root.is_object() && root.contains("mms_json") && root["mms_json"].is_object()) { mms = &root["mms_json"]; } if (!mms->is_object()) { throw std::runtime_error("origin file does not contain an object mms_json: " + originPath.string()); } for (auto it = mms->begin(); it != mms->end(); ++it) { OriginRecord rec; rec.path = it.key(); rec.value = jsonScalarForOutput(it.value()); rec.reportName = reportName; rec.sourceOrder = sourceOrder; size_t idx = data.records.size(); data.records.push_back(rec); data.pathToRecords[rec.path].push_back(idx); } } /** * Function: appendOriginData * * @brief 向目标结构追加或保证存在 “appendOriginData” 所描述的数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param data 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param sourceOrder 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void appendOriginData(OriginData& data, const fs::path& originPath, int sourceOrder) { std::string raw = ltrimBOM(readAll(originPath)); for (const auto& root : parseJsonRoots(raw)) { appendOriginRoot(data, originPath, sourceOrder, root); } } /** * Function: loadOriginData * * @brief 读取或加载 “loadOriginData” 对应的数据,并转换为本模块后续逻辑使用的内存结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param originPaths 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static OriginData loadOriginData(const std::vector& originPaths) { OriginData data; for (size_t i = 0; i < originPaths.size(); ++i) { appendOriginData(data, originPaths[i], static_cast(i)); } return data; } /** * Function: loadOriginData * * @brief 读取或加载 “loadOriginData” 对应的数据,并转换为本模块后续逻辑使用的内存结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static OriginData loadOriginData(const fs::path& originPath) { std::vector paths{originPath}; return loadOriginData(paths); } /** * Function: firstUnusedRecord * * @brief 实现解析/报表层的 “firstUnusedRecord” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param path 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static const OriginRecord* firstUnusedRecord(OriginData* origin, const std::string& path) { if (!origin) return nullptr; auto it = origin->pathToRecords.find(path); if (it == origin->pathToRecords.end()) return nullptr; for (size_t idx : it->second) { if (!origin->used.count(idx)) return &origin->records[idx]; } if (!it->second.empty()) return &origin->records[it->second.front()]; return nullptr; } /** * Function: markRecordUsed * * @brief 实现解析/报表层的 “markRecordUsed” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param rec 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void markRecordUsed(OriginData* origin, const OriginRecord* rec) { if (!origin || !rec) return; size_t idx = static_cast(rec - origin->records.data()); if (idx < origin->records.size()) origin->used.insert(idx); } /** * Function: statInstanceSuffix * * @brief 实现解析/报表层的 “statInstanceSuffix” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param stat 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string statInstanceSuffix(const std::string& stat) { if (stat == "平均值") return "1"; if (stat == "最大值") return "2"; if (stat == "最小值") return "3"; if (stat == "95值") return "4"; return ""; } /* * 把统计类型转换到 DO 实例号。 * * 规则来自原始路径约定:平均/最大/最小/95值分别用 1/2/3/4 替换 DO 头部末尾的 0。 * 例如 xxx0$MX$... + 最大值 -> xxx2$MX$... */ /** * Function: applyStatInstance * * @brief 把 “applyStatInstance” 对应的变更应用到模型、控件或输出文档。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param doPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param stat 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string applyStatInstance(const std::string& doPath, const std::string& stat) { std::string suffix = statInstanceSuffix(stat); if (suffix.empty()) return doPath; size_t dollar = doPath.find('$'); std::string head = dollar == std::string::npos ? doPath : doPath.substr(0, dollar); std::string tail = dollar == std::string::npos ? "" : doPath.substr(dollar); if (!head.empty() && head.back() == '0') { head.back() = suffix[0]; return head + tail; } return doPath; } /** * Function: phaseTokenForDa * * @brief 实现解析/报表层的 “phaseTokenForDa” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string phaseTokenForDa(const std::string& phase, const std::string& wiring) { // 角形接线的 A/B/C 在 DA 中对应线电压 AB/BC/CA;星形对应普通相 A/B/C。 if (wiring == "delta") { if (phase == "A") return "phsAB"; if (phase == "B") return "phsBC"; if (phase == "C") return "phsCA"; } if (phase == "A") return "phsA"; if (phase == "B") return "phsB"; if (phase == "C") return "phsC"; return "phs"; } /** * Function: concreteDaPath * * @brief 实现解析/报表层的 “concreteDaPath” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param daPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string concreteDaPath(std::string daPath, const std::string& phase, const std::string& wiring) { size_t pos = 0; const std::string from = "phs*"; const std::string to = phaseTokenForDa(phase, wiring); while ((pos = daPath.find(from, pos)) != std::string::npos) { daPath.replace(pos, from.size(), to); pos += to.size(); } return daPath; } /** * Function: makeOriginPath * * @brief 构造 “makeOriginPath” 对应的对象、消息、窗口控件或输出结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param doPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param daPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param stat 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string makeOriginPath(const std::string& doPath, const std::string& daPath, const std::string& stat, const std::string& phase, const std::string& wiring) { if (doPath.empty() || daPath.empty()) return ""; // origin 的 MMS 路径由 DO 与具体相位后的 DA 拼接,stat 预留给 DO 实例号转换。 return doPath + "$" + concreteDaPath(daPath, phase, wiring); } /** * Function: makeOriginPathCandidates * * @brief 构造 “makeOriginPathCandidates” 对应的对象、消息、窗口控件或输出结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param doPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param daPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param stat 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector makeOriginPathCandidates(const std::string& doPath, const std::string& daPath, const std::string& stat, const std::string& phase, const std::string& wiring) { std::vector paths; std::string primary = makeOriginPath(doPath, daPath, stat, phase, wiring); if (!primary.empty()) paths.push_back(primary); return paths; } /** * Function: parseOriginRangePath * * @brief 解析 “parseOriginRangePath” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param path 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param before 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param start 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param end 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param after 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool parseOriginRangePath(const std::string& path, std::string& before, int& start, int& end, std::string& after) { std::smatch m; static const std::regex re(R"(^(.+)\[(\d+)-(\d+)\](.*)$)"); if (!std::regex_match(path, m, re)) return false; before = m[1].str(); start = std::stoi(m[2].str()); end = std::stoi(m[3].str()); after = m[4].str(); return start <= end; } /** * Function: parseOriginIndexedPath * * @brief 解析 “parseOriginIndexedPath” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param path 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param before 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param after 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param index 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool parseOriginIndexedPath(const std::string& path, const std::string& before, const std::string& after, int& index) { if (path.size() < before.size() + after.size() + 3) return false; if (path.compare(0, before.size(), before) != 0) return false; if (!after.empty() && path.compare(path.size() - after.size(), after.size(), after) != 0) { return false; } size_t midStart = before.size(); size_t midEnd = after.empty() ? path.size() : path.size() - after.size(); if (midEnd <= midStart + 2 || path[midStart] != '[' || path[midEnd - 1] != ']') return false; std::string number = path.substr(midStart + 1, midEnd - midStart - 2); if (number.empty() || !std::all_of(number.begin(), number.end(), [](unsigned char ch) { return std::isdigit(ch) != 0; })) { return false; } index = std::stoi(number); return true; } /** * Function: matchOrigin * * @brief 比较或匹配 “matchOrigin” 涉及的数据,返回匹配结果或构造匹配信息。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param doPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param daPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param stat 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pickStartOverride 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pickEndOverride 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static OriginMatch matchOrigin(OriginData* origin, const std::string& doPath, const std::string& daPath, const std::string& stat, const std::string& phase, const std::string& wiring, int pickStartOverride = std::numeric_limits::min(), int pickEndOverride = std::numeric_limits::min()) { OriginMatch match; if (!origin) return match; /* * 匹配步骤: * 1. 根据 DO/DA/stat/phase/wiring 生成候选 origin 路径; * 2. 若候选路径是 [start-end] 序列,按 sourceOrder 分组,选覆盖目标区间最多的报告; * 3. 将选中报告中的连续索引拼成逗号值,并记录 valueIndexes 用于最终值对齐; * 4. 普通路径则取第一个未使用记录,避免同一路径在多个输出行重复占用。 */ for (const std::string& path : makeOriginPathCandidates(doPath, daPath, stat, phase, wiring)) { if (path.empty()) continue; std::string before, after; int start = 0, end = 0; if (parseOriginRangePath(path, before, start, end, after)) { int pickStart = pickStartOverride == std::numeric_limits::min() ? start : pickStartOverride; int pickEnd = pickEndOverride == std::numeric_limits::min() ? end : pickEndOverride; struct IndexedRecord { int index = 0; size_t recordIndex = 0; }; std::map> bySource; for (size_t recIndex = 0; recIndex < origin->records.size(); ++recIndex) { int indexedPath = 0; if (!parseOriginIndexedPath(origin->records[recIndex].path, before, after, indexedPath)) continue; bySource[origin->records[recIndex].sourceOrder].push_back({indexedPath, recIndex}); } int bestSource = -1; int bestIntersection = 0; bool bestHasPickStart = false; for (const auto& kv : bySource) { int intersection = 0; bool hasPickStart = false; for (const auto& rec : kv.second) { if (rec.index >= pickStart && rec.index <= pickEnd) ++intersection; if (rec.index == pickStart) hasPickStart = true; } if (intersection <= 0) continue; if (bestSource < 0 || (hasPickStart && !bestHasPickStart) || (hasPickStart == bestHasPickStart && intersection > bestIntersection)) { bestSource = kv.first; bestIntersection = intersection; bestHasPickStart = hasPickStart; } } if (bestSource >= 0) { std::map indexToRecord; int displayStart = std::numeric_limits::max(); int displayEnd = std::numeric_limits::min(); for (const auto& rec : bySource[bestSource]) { if (indexToRecord.count(rec.index)) continue; indexToRecord[rec.index] = rec.recordIndex; displayStart = std::min(displayStart, rec.index); displayEnd = std::max(displayEnd, rec.index); } std::ostringstream values; bool any = false; std::string reportName; for (int i = displayStart; i <= displayEnd; ++i) { if (i != displayStart) values << ","; match.valueIndexes.push_back(i); auto it = indexToRecord.find(i); if (it == indexToRecord.end()) continue; const OriginRecord& rec = origin->records[it->second]; any = true; values << rec.value; if (reportName.empty()) reportName = rec.reportName; markRecordUsed(origin, &rec); } if (any) { match.path = before + "[" + std::to_string(displayStart) + "-" + std::to_string(displayEnd) + "]" + after; match.value = values.str(); match.reportName = reportName; match.sourceOrder = bestSource; match.rangeStart = displayStart; match.rangeEnd = displayEnd; return match; } } continue; } const OriginRecord* rec = firstUnusedRecord(origin, path); if (rec) { match.path = path; match.value = rec->value; match.reportName = rec->reportName; match.sourceOrder = rec->sourceOrder; markRecordUsed(origin, rec); return match; } } return match; } /** * Function: utf8CharLen * * @brief 实现解析/报表层的 “utf8CharLen” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param c 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static size_t utf8CharLen(unsigned char c) { if (c < 0x80) return 1; if ((c & 0xE0) == 0xC0) return 2; if ((c & 0xF0) == 0xE0) return 3; if ((c & 0xF8) == 0xF0) return 4; return 1; } /** * Function: wrapDisplay * * @brief 实现解析/报表层的 “wrapDisplay” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param width 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector wrapDisplay(const std::string& s, int width) { std::vector lines; if (s.empty()) { lines.push_back(""); return lines; } std::string current; int currentWidth = 0; for (size_t i = 0; i < s.size(); ) { size_t len = utf8CharLen(static_cast(s[i])); if (i + len > s.size()) len = 1; std::string ch = s.substr(i, len); int chWidth = getDisplayWidth(ch); if (!current.empty() && currentWidth + chWidth > width) { lines.push_back(current); current.clear(); currentWidth = 0; } current += ch; currentWidth += chWidth; i += len; } if (!current.empty()) lines.push_back(current); return lines; } /** * Function: padCell * * @brief 实现解析/报表层的 “padCell” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param width 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string padCell(const std::string& s, int width) { int pad = width - getDisplayWidth(s); return s + std::string(pad > 0 ? pad : 0, ' '); } static std::string arrayBaseForMeta(const ValueMeta* vm); static std::string valueOffsetForDa(const std::string& daPath); static std::string finalOffsetForMeta(const ValueMeta* vm); /** * Function: makeCompareLine * * @brief 构造 “makeCompareLine” 对应的对象、消息、窗口控件或输出结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param item 当前处理的数据行、单元格、XML 节点或模型项。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string makeCompareLine(const OutItem& item, OriginData* origin) { OriginMatch om = matchOrigin(origin, item.doPath, item.daPath, item.stat, item.phase, item.wiring); const int NAME_W = 30; const int CONFIG_W = 62; const int PATH_W = 50; const int FINAL_W = 70; const int ORIGIN_W = 70; std::vector nameLines = wrapDisplay(item.label, NAME_W); std::vector configLines = wrapDisplay(item.configText, CONFIG_W); std::vector pathLines = wrapDisplay(om.path.empty() ? "" : ("\"" + om.path + "\""), PATH_W); std::vector finalLines = wrapDisplay(item.finalText, FINAL_W); std::vector originLines = wrapDisplay(om.value, ORIGIN_W); size_t rows = std::max({nameLines.size(), configLines.size(), pathLines.size(), finalLines.size(), originLines.size()}); std::ostringstream oss; for (size_t i = 0; i < rows; ++i) { if (i) oss << "\n"; oss << "| " << padCell(i < nameLines.size() ? nameLines[i] : "", NAME_W) << " | " << padCell(i < configLines.size() ? configLines[i] : "", CONFIG_W) << " | " << padCell(i < pathLines.size() ? pathLines[i] : "", PATH_W) << " | " << padCell(i < finalLines.size() ? finalLines[i] : "", FINAL_W) << " | " << padCell(i < originLines.size() ? originLines[i] : "", ORIGIN_W) << " |"; } return oss.str(); } /** * Function: makeOutItemForWiring * * @brief 构造 “makeOutItemForWiring” 对应的对象、消息、窗口控件或输出结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param valueMetas 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @param val 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param pk 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param category 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param filter 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param requireMeta 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param out 输出参数;函数在成功或失败时写入结果或诊断信息。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool makeOutItemForWiring(const std::unordered_map>& valueMetas, const std::string& key, const std::string& val, const ParsedKey& pk, const std::string& category, const std::string& wiring, const MetaFilter& filter, bool requireMeta, OutItem& out) { const ValueMeta* vm = findMetaForKey(valueMetas, key, pk, wiring, filter); if (requireMeta && !vm) return false; // label 用“去掉统计后缀的 desc + stat”组成,例如“电压最大值”。 std::string desc = vm ? vm->desc : ""; std::string descClean = cleanDescSuffix(desc); std::string attrSort = !descClean.empty() ? descClean : pk.baseName; std::string label = (attrSort.empty() ? pk.baseName : attrSort) + pk.stat; std::string doDisp = vm ? vm->doPath : ""; std::string daDisp = vm ? vm->daPath : ""; int keyStart = 0, keyEnd = 0; if (vm && vm->isSequence && parseKeyRange(key, keyStart, keyEnd)) { // XML 中 DA 写的是 [%offset] 占位符,表格展示时改成最终可对齐的 [start-end] 范围。 daDisp = rewriteDaRangeForDisplay(daDisp, *vm, keyStart, keyEnd); } std::ostringstream oss; const int DO_W = 32; const int DA_W = 38; const int DESC_W = 42; std::string doStr = "DO=\"" + doDisp + "\""; std::string daStr = "DA=\"" + daDisp + "\""; std::string showKey = key; if (showKey.compare(0, 6, "Value_") == 0) showKey.erase(0, 6); appendPad(oss, doStr, DO_W); appendPad(oss, daStr, DA_W); appendPad(oss, label, DESC_W); std::string finalText = showKey + " = " + val; oss << finalText; out = { pk.stat, category, phaseOrderOf(pk.phase), attrSort, pk.phase, oss.str(), label, arrayBaseForMeta(vm), valueOffsetForDa(vm ? vm->daPath : ""), finalOffsetForMeta(vm), doStr + " " + daStr, finalText, doDisp, daDisp, wiring, vm && vm->hasOffset, vm ? vm->offset : 0 }; return true; } static std::string replaceKeyRange(const std::string& key, int start, int end); /** * Function: arrayBaseForMeta * * @brief 实现解析/报表层的 “arrayBaseForMeta” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string arrayBaseForMeta(const ValueMeta* vm) { if (!vm || !vm->isSequence) return "0"; return std::to_string(vm->seqStart) + "-->" + std::to_string(vm->seqEnd); } /** * Function: valueOffsetForDa * * @brief 实现解析/报表层的 “valueOffsetForDa” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param daPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string valueOffsetForDa(const std::string& daPath) { std::smatch m; static const std::regex re(R"(%(-?\d+))"); if (!std::regex_search(daPath, m, re)) return "0"; return std::to_string(std::stoi(m[1].str())); } /** * Function: finalOffsetForMeta * * @brief 实现解析/报表层的 “finalOffsetForMeta” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string finalOffsetForMeta(const ValueMeta* vm) { if (!vm || !vm->hasOffset) return "0"; return std::to_string(vm->offset); } /** * Function: metaNameToKeyName * * @brief 实现解析/报表层的 “metaNameToKeyName” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string metaNameToKeyName(const ValueMeta& vm) { std::smatch m; static const std::regex re(R"(^(.*)%(\d+),(\d+)%$)"); if (std::regex_match(vm.name, m, re)) { std::string prefix = m[1].str(); if (!prefix.empty() && prefix.back() == '_') prefix.pop_back(); return prefix + "[" + m[2].str() + "-" + m[3].str() + "]"; } return vm.name; } /** * Function: dataKeyForMeta * * @brief 实现解析/报表层的 “dataKeyForMeta” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string dataKeyForMeta(const ValueMeta& vm, const std::string& phase) { std::string key = "Value_" + vm.itemName; if (!phase.empty()) key += "_" + phase; key += "_" + metaNameToKeyName(vm); return key; } /** * Function: lookupNameForMeta * * @brief 在现有数据结构中查找 “lookupNameForMeta” 对应的对象或位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string lookupNameForMeta(const ValueMeta& vm) { std::string name = metaNameToKeyName(vm); if (!vm.hasOffset) return name; int start = 0, end = 0; if (!parseKeyRange(name, start, end)) return name; return replaceKeyRange(name, start + vm.offset, end + vm.offset); } /** * Function: lookupDataKeyForMeta * * @brief 在现有数据结构中查找 “lookupDataKeyForMeta” 对应的对象或位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string lookupDataKeyForMeta(const ValueMeta& vm, const std::string& phase) { std::string key = "Value_" + vm.itemName; if (!phase.empty()) key += "_" + phase; key += "_" + lookupNameForMeta(vm); return key; } /** * Function: displayKeyForMeta * * @brief 实现解析/报表层的 “displayKeyForMeta” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string displayKeyForMeta(const ValueMeta& vm, const std::string& phase) { std::string key = vm.itemName; if (!phase.empty()) key += "_" + phase; key += "_" + metaNameToKeyName(vm); return key; } /** * Function: phasesForMeta * * @brief 实现解析/报表层的 “phasesForMeta” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param sheetWiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector phasesForMeta(const ValueMeta& vm, const std::string& sheetWiring) { if (vm.seqValue == "8") return {"T"}; if (vm.wiring == "star" || vm.wiring == "delta") return {"A", "B", "C"}; if (sheetWiring == "star" || sheetWiring == "delta") return {"A", "B", "C"}; return {""}; } /** * Function: categoryForKeyAndItem * * @brief 实现解析/报表层的 “categoryForKeyAndItem” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @param itemName 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string categoryForKeyAndItem(const std::string& key, const std::string& itemName) { if (itemName == "V" || itemName == "I" || itemName == "PQ" || itemName == "F_S" || itemName == "F_L") return itemName; if (key.find("_V_") != std::string::npos) return "V"; if (key.find("_I_") != std::string::npos) return "I"; if (key.find("PQ_") != std::string::npos || key.find("_PQ_") != std::string::npos) return "PQ"; return itemName.empty() ? "Z" : itemName; } /** * Function: makeOutItemFromMeta * * @brief 构造 “makeOutItemFromMeta” 对应的对象、消息、窗口控件或输出结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param sheetWiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param valueByKey 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static OutItem makeOutItemFromMeta(const ValueMeta& vm, const std::string& phase, const std::string& sheetWiring, const std::unordered_map& valueByKey) { std::string dataKey = dataKeyForMeta(vm, phase); // lookupDataKeyForMeta 会把 XML Offset 应用到取数 key 上,使最终值和 XML 定义对齐。 auto it = valueByKey.find(lookupDataKeyForMeta(vm, phase)); std::string val = it == valueByKey.end() ? "" : it->second; ParsedKey pk = parseForSort(dataKey); if (pk.phase.empty()) pk.phase = phase; std::string descClean = cleanDescSuffix(vm.desc); std::string attrSort = !descClean.empty() ? descClean : metaNameToKeyName(vm); std::string label = vm.desc.empty() ? (attrSort + pk.stat) : vm.desc; if (vm.topic == "RTDATA" || vm.dataType == "02" || vm.dataType == "03") { label = vm.desc.empty() ? attrSort : vm.desc; } std::string daDisp = vm.daPath; int keyStart = 0, keyEnd = 0; if (vm.isSequence && parseKeyRange(dataKey, keyStart, keyEnd)) { daDisp = rewriteDaRangeForDisplay(daDisp, vm, keyStart, keyEnd); } std::string doStr = "DO=\"" + vm.doPath + "\""; std::string daStr = "DA=\"" + daDisp + "\""; std::string displayKey = displayKeyForMeta(vm, phase); std::string finalText = displayKey + " = " + val; std::string category = categoryForKeyAndItem(dataKey, vm.itemName); return { pk.stat, category, phaseOrderOf(pk.phase), attrSort, pk.phase, "", label, arrayBaseForMeta(&vm), valueOffsetForDa(vm.daPath), finalOffsetForMeta(&vm), doStr + " " + daStr, finalText, vm.doPath, daDisp, sheetWiring, vm.hasOffset, vm.offset }; } /** * Function: alreadyAddedMetaRow * * @brief 实现解析/报表层的 “alreadyAddedMetaRow” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param seen 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param vm 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param phase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param sheetWiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool alreadyAddedMetaRow(std::unordered_set& seen, const ValueMeta& vm, const std::string& phase, const std::string& sheetWiring) { std::string key = vm.topic + "|" + sheetWiring + "|" + vm.itemName + "|" + vm.seqValue + "|" + phase + "|" + vm.name + "|" + vm.doPath + "|" + vm.daPath; if (seen.count(key)) return true; seen.insert(key); return false; } /** * Function: specAllowsDataType * * @brief 实现解析/报表层的 “specAllowsDataType” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param spec 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param dataType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool specAllowsDataType(const SheetSpec& spec, const std::string& dataType) { std::stringstream ss(spec.dataType); std::string part; while (std::getline(ss, part, ',')) { if (part == dataType) return true; } return false; } /** * Function: buildItemsForSheet * * @brief 构造 “buildItemsForSheet” 对应的对象、消息、窗口控件或输出结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param valueMetas 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param spec 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param valueByKey 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector buildItemsForSheet(const std::unordered_map>& valueMetas, const SheetSpec& spec, const std::unordered_map& valueByKey) { std::vector out; std::unordered_set seen; // 以 XML 定义为主生成行,即使 jsondata 缺值也保留表格行,后续用红色标出缺失。 for (const auto& kv : valueMetas) { for (const ValueMeta& vm : kv.second) { if (vm.topic != spec.topic || !specAllowsDataType(spec, vm.dataType)) continue; if (!wiringAllowed(vm, spec.wiring)) continue; if (vm.doPath.empty() && vm.daPath.empty()) continue; for (const std::string& phase : phasesForMeta(vm, spec.wiring)) { if (alreadyAddedMetaRow(seen, vm, phase, spec.wiring)) continue; out.push_back(makeOutItemFromMeta(vm, phase, spec.wiring, valueByKey)); } } } return out; } /** * Function: htmlEscape * * @brief 实现解析/报表层的 “htmlEscape” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string htmlEscape(const std::string& s) { std::string out; out.reserve(s.size()); for (char c : s) { if (c == '&') out += "&"; else if (c == '<') out += "<"; else if (c == '>') out += ">"; else if (c == '"') out += """; else out += c; } return out; } /** * Function: splitCommaValues * * @brief 实现解析/报表层的 “splitCommaValues” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector splitCommaValues(const std::string& s) { std::vector values; std::stringstream ss(s); std::string item; while (std::getline(ss, item, ',')) values.push_back(item); if (values.empty()) values.push_back(""); return values; } /** * Function: trimValue * * @brief 规范化 “trimValue” 对应的字符串或标识,减少格式差异对匹配造成的影响。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param value 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string trimValue(std::string value) { value.erase(value.begin(), std::find_if(value.begin(), value.end(), [](unsigned char ch) { return std::isspace(ch) == 0; })); value.erase(std::find_if(value.rbegin(), value.rend(), [](unsigned char ch) { return std::isspace(ch) == 0; }).base(), value.end()); return value; } /** * Function: lowerValue * * @brief 规范化 “lowerValue” 对应的字符串或标识,减少格式差异对匹配造成的影响。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param value 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string lowerValue(std::string value) { std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); return value; } /** * Function: parseNumberValue * * @brief 解析 “parseNumberValue” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param text 待解析、比较、显示或发送的 UTF-8 文本。 * @param out 输出参数;函数在成功或失败时写入结果或诊断信息。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool parseNumberValue(const std::string& text, long double& out) { std::string s = trimValue(text); if (s.empty()) return false; errno = 0; char* end = nullptr; const char* begin = s.c_str(); long double value = std::strtold(begin, &end); if (begin == end) return false; while (end && *end && std::isspace(static_cast(*end))) ++end; if (end && *end) return false; if (errno == ERANGE) return false; if (!std::isfinite(static_cast(value))) return false; out = value; return true; } /** * Function: fixedSixNumber * * @brief 实现解析/报表层的 “fixedSixNumber” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param value 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string fixedSixNumber(long double value) { std::ostringstream oss; oss.setf(std::ios::fixed, std::ios::floatfield); oss << std::setprecision(6) << static_cast(value); std::string out = oss.str(); if (out == "-0.000000") out = "0.000000"; return out; } /** * Function: roundOriginValueForDisplay * * @brief 实现解析/报表层的 “roundOriginValueForDisplay” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param value 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string roundOriginValueForDisplay(const std::string& value) { long double number = 0; if (!parseNumberValue(value, number)) return trimValue(value); return fixedSixNumber(number); } /** * Function: roundOriginValuesForDisplay * * @brief 实现解析/报表层的 “roundOriginValuesForDisplay” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param values 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector roundOriginValuesForDisplay(const std::vector& values) { std::vector out; out.reserve(values.size()); for (const auto& value : values) out.push_back(roundOriginValueForDisplay(value)); return out; } /** * Function: compareValueKey * * @brief 比较或匹配 “compareValueKey” 涉及的数据,返回匹配结果或构造匹配信息。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param value 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string compareValueKey(const std::string& value) { std::string trimmed = trimValue(value); long double number = 0; // 数字统一为 6 位小数比较,避免 "1" 与 "1.000000" 被误判不同;非数字则小写比较。 if (parseNumberValue(trimmed, number)) return fixedSixNumber(number); return lowerValue(trimmed); } /** * Function: hasNullValue * * @brief 判断 “hasNullValue” 所表达的条件;该函数只读取输入,不负责修改业务状态。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param values 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool hasNullValue(const std::vector& values) { for (const auto& value : values) { if (lowerValue(trimValue(value)) == "null") return true; } return false; } /** * Function: valuesEqualForCompare * * @brief 实现解析/报表层的 “valuesEqualForCompare” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param finalValues 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originValues 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originStartIndex 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool valuesEqualForCompare(const std::vector& finalValues, const std::vector& originValues, int originStartIndex = 0) { if (originStartIndex < 0) return false; if (originValues.size() < static_cast(originStartIndex) + finalValues.size()) return false; for (size_t i = 0; i < finalValues.size(); ++i) { if (compareValueKey(finalValues[i]) != compareValueKey(originValues[originStartIndex + i])) return false; } return true; } /** * Function: displayIndexForOriginIndex * * @brief 实现解析/报表层的 “displayIndexForOriginIndex” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param originIndexes 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originIndex 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 整数状态、索引或计数;入口函数以 0 表示成功。 */ static int displayIndexForOriginIndex(const std::vector& originIndexes, int originIndex) { auto it = std::find(originIndexes.begin(), originIndexes.end(), originIndex); if (it == originIndexes.end()) return -1; return static_cast(std::distance(originIndexes.begin(), it)); } /** * Function: valuesEqualForCompareByOriginIndex * * @brief 实现解析/报表层的 “valuesEqualForCompareByOriginIndex” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param finalValues 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originValues 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originIndexes 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originPickStart 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool valuesEqualForCompareByOriginIndex(const std::vector& finalValues, const std::vector& originValues, const std::vector& originIndexes, int originPickStart) { if (originPickStart == std::numeric_limits::min()) { return valuesEqualForCompare(finalValues, originValues, 0); } if (originIndexes.empty()) { int originStartIndex = originPickStart < 0 ? -1 : originPickStart; return valuesEqualForCompare(finalValues, originValues, originStartIndex); } for (size_t i = 0; i < finalValues.size(); ++i) { int displayIndex = displayIndexForOriginIndex(originIndexes, originPickStart + static_cast(i)); if (displayIndex < 0 || displayIndex >= static_cast(originValues.size())) return false; if (compareValueKey(finalValues[i]) != compareValueKey(originValues[displayIndex])) return false; } return true; } /** * Function: parseArrayBaseRange * * @brief 解析 “parseArrayBaseRange” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param arrayBase 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param start 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param end 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool parseArrayBaseRange(const std::string& arrayBase, int& start, int& end) { std::smatch m; static const std::regex re(R"(^\s*(-?\d+)\s*-->\s*(-?\d+)\s*$)"); if (!std::regex_match(arrayBase, m, re)) return false; start = std::stoi(m[1].str()); end = std::stoi(m[2].str()); return start <= end; } /** * Function: parseOffsetText * * @brief 解析 “parseOffsetText” 对应的文本或结构,提取经过校验的业务字段。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param text 待解析、比较、显示或发送的 UTF-8 文本。 * @param fallback 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 整数状态、索引或计数;入口函数以 0 表示成功。 */ static int parseOffsetText(const std::string& text, int fallback = 0) { try { if (trimValue(text).empty()) return fallback; return std::stoi(trimValue(text)); } catch (...) { return fallback; } } /** * Function: comparePlanForItem * * @brief 比较或匹配 “comparePlanForItem” 涉及的数据,返回匹配结果或构造匹配信息。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param finalKey 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param item 当前处理的数据行、单元格、XML 节点或模型项。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static ComparePlan comparePlanForItem(const std::string& finalKey, const OutItem& item) { ComparePlan plan; (void)finalKey; int baseStart = 0; int baseEnd = 0; if (!parseArrayBaseRange(item.arrayBase, baseStart, baseEnd)) return plan; int valueOffset = parseOffsetText(item.valueOffset, 0); // originPickStart/End 表示原始值中真正要拿来和最终值第 1 列比较的数组下标区间。 plan.originPickStart = baseStart + valueOffset; plan.originPickEnd = baseEnd + valueOffset; return plan; } /** * Function: matchStyleForValues * * @brief 比较或匹配 “matchStyleForValues” 涉及的数据,返回匹配结果或构造匹配信息。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param hasOriginContext 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param hasOriginMatch 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param finalValues 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originDisplayValues 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originStartIndex 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string matchStyleForValues(bool hasOriginContext, bool hasOriginMatch, const std::vector& finalValues, const std::vector& originDisplayValues, int originStartIndex = 0) { if (hasNullValue(finalValues)) return "MatchRed"; if (!hasOriginContext) return ""; if (!hasOriginMatch) return "MatchRed"; return valuesEqualForCompare(finalValues, originDisplayValues, originStartIndex) ? "MatchGreen" : "MatchOrange"; } /** * Function: addRowStats * * @brief 向目标结构追加或保证存在 “addRowStats” 所描述的数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param stats 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param rowStyle 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void addRowStats(SheetStats* stats, const std::string& rowStyle) { if (!stats) return; if (rowStyle == "MatchGreen") ++stats->normal; else if (rowStyle == "MatchOrange") ++stats->abnormal; else if (rowStyle == "MatchRed") ++stats->missing; } /** * Function: cellAttrs * * @brief 实现解析/报表层的 “cellAttrs” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param styleId 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param extra 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string cellAttrs(const std::string& styleId, const std::string& extra = "") { std::string attrs; if (!styleId.empty()) attrs += " ss:StyleID=\"" + styleId + "\""; attrs += extra; return attrs; } /** * Function: splitFinalText * * @brief 实现解析/报表层的 “splitFinalText” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param text 待解析、比较、显示或发送的 UTF-8 文本。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @param value 待解析、比较、显示或发送的 UTF-8 文本。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool splitFinalText(const std::string& text, std::string& key, std::string& value) { size_t pos = text.find(" = "); if (pos == std::string::npos) { key = text; value.clear(); return false; } key = text.substr(0, pos); value = text.substr(pos + 3); return true; } /** * Function: replaceKeyRange * * @brief 实现解析/报表层的 “replaceKeyRange” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @param start 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @param end 索引、数量、范围或偏移参数;具体边界由函数体校验。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string replaceKeyRange(const std::string& key, int start, int end) { std::regex re(R"(\[(\d+)-(\d+)\])"); std::ostringstream oss; oss << "[" << start << "-" << end << "]"; return std::regex_replace(key, re, oss.str(), std::regex_constants::format_first_only); } /** * Function: offsetFinalKey * * @brief 实现解析/报表层的 “offsetFinalKey” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @param item 当前处理的数据行、单元格、XML 节点或模型项。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string offsetFinalKey(const std::string& key, const OutItem& item) { if (!item.hasOffset) return key; int start = 0, end = 0; if (!parseKeyRange(key, start, end)) return key; return replaceKeyRange(key, start + item.offset, end + item.offset); } /** * Function: originSeriesKey * * @brief 实现解析/报表层的 “originSeriesKey” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param finalKey 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param item 当前处理的数据行、单元格、XML 节点或模型项。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string originSeriesKey(const std::string& finalKey, const OutItem& item) { int start = 0, end = 0; if (!parseKeyRange(item.daPath, start, end)) return finalKey; return replaceKeyRange(finalKey, start, end); } /** * Function: originSeriesKeyForMatch * * @brief 实现解析/报表层的 “originSeriesKeyForMatch” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param finalKey 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param item 当前处理的数据行、单元格、XML 节点或模型项。 * @param match 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string originSeriesKeyForMatch(const std::string& finalKey, const OutItem& item, const OriginMatch& match) { if (match.rangeStart != std::numeric_limits::min() && match.rangeEnd != std::numeric_limits::min()) { return replaceKeyRange(finalKey, match.rangeStart, match.rangeEnd); } return originSeriesKey(finalKey, item); } /** * Function: writeSsCell * * @brief 把 “writeSsCell” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ofs 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param value 待解析、比较、显示或发送的 UTF-8 文本。 * @param attrs 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void writeSsCell(std::ofstream& ofs, const std::string& value, const std::string& attrs = "") { ofs << "" << htmlEscape(value) << ""; } /** * Function: writeSsRowStart * * @brief 把 “writeSsRowStart” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ofs 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param height 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void writeSsRowStart(std::ofstream& ofs, int height = 0) { if (height > 0) ofs << ""; else ofs << ""; } /** * Function: displayLabelForSheet * * @brief 实现解析/报表层的 “displayLabelForSheet” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param item 当前处理的数据行、单元格、XML 节点或模型项。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string displayLabelForSheet(const OutItem& item) { if (item.phase == "A" || item.phase == "B" || item.phase == "C") { return item.phase + "相" + item.label; } return item.label; } /** * Function: originHeaderFieldValue * * @brief 实现解析/报表层的 “originHeaderFieldValue” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param header 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param key 待解析、比较、显示或发送的 UTF-8 文本。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string originHeaderFieldValue(const OriginHeader& header, const std::string& key) { for (const auto& field : header.fields) { if (field.key == key) return field.val; } return ""; } /** * Function: visibleOriginHeadersForSheet * * @brief 实现解析/报表层的 “visibleOriginHeadersForSheet” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param items 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector visibleOriginHeadersForSheet(OriginData* origin, const std::vector& items, const std::string& wiring) { std::vector visible; if (!origin) return visible; OriginData probe = *origin; std::set usedOrders; for (const auto& item : items) { if (item.wiring != wiring) continue; OriginMatch om = matchOrigin(&probe, item.doPath, item.daPath, item.stat, item.phase, item.wiring); if (!om.value.empty() && om.sourceOrder >= 0) usedOrders.insert(om.sourceOrder); } for (const auto& header : origin->headers) { if (usedOrders.count(header.sourceOrder)) visible.push_back(header); } return visible; } /** * Function: writeDataWorksheet * * @brief 把 “writeDataWorksheet” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ofs 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param sheetName 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param wiring 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param headers 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param blocks 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param items 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param showStatBlocks 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param stats 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void writeDataWorksheet(std::ofstream& ofs, const std::string& sheetName, const std::string& wiring, const std::vector& headers, const std::vector& blocks, const std::vector& items, OriginData* origin, bool showStatBlocks, SheetStats* stats) { const int maxValues = 64; std::vector visibleOriginHeaders = visibleOriginHeadersForSheet(origin, items, wiring); const int totalCols = std::max(9 + maxValues, 3 + static_cast(visibleOriginHeaders.size())); /* * 每个数据项写两行: * 第一行“最终值”:来自 jsondata,按逗号拆到 值1..值64; * 第二行“原始值”:来自 origin,按同一比较区间对齐展示。 * 行颜色: * MatchGreen 最终值与原始值一致; * MatchOrange 找到原始值但内容不一致; * MatchRed 最终值为 null 或没有匹配到原始值; * OriginPickBlue 标出序列比较时真正对齐起点。 */ ofs << "\n"; ofs << ""; ofs << ""; ofs << ""; for (int i = 0; i < maxValues; ++i) ofs << ""; ofs << "\n"; writeSsRowStart(ofs); writeSsCell(ofs, "头部信息", " ss:StyleID=\"Section\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\""); ofs << "\n"; size_t originHeaderRows = visibleOriginHeaders.empty() ? 0 : originHeaderDisplayKeys().size() + 1; size_t headerRows = std::max(headers.size(), originHeaderRows); for (size_t row = 0; row < headerRows; ++row) { writeSsRowStart(ofs); if (row < headers.size()) { writeSsCell(ofs, headers[row].key, " ss:StyleID=\"Default\""); writeSsCell(ofs, headers[row].val, " ss:StyleID=\"Default\""); } else { writeSsCell(ofs, "", " ss:StyleID=\"Default\""); writeSsCell(ofs, "", " ss:StyleID=\"Default\""); } if (!visibleOriginHeaders.empty()) { if (row == 0) { writeSsCell(ofs, "原始报告", " ss:StyleID=\"Section\""); for (const auto& oh : visibleOriginHeaders) { writeSsCell(ofs, oh.fileName.empty() ? oh.title : oh.fileName, " ss:StyleID=\"Section\""); } } else { size_t fieldIndex = row - 1; const auto& displayKeys = originHeaderDisplayKeys(); std::string key = fieldIndex < displayKeys.size() ? displayKeys[fieldIndex] : ""; writeSsCell(ofs, key, " ss:StyleID=\"Default\""); for (const auto& oh : visibleOriginHeaders) { writeSsCell(ofs, key.empty() ? "" : originHeaderFieldValue(oh, key), " ss:StyleID=\"Default\""); } } } ofs << "\n"; } writeSsRowStart(ofs); writeSsCell(ofs, "名称", " ss:StyleID=\"Head\""); writeSsCell(ofs, "数组基准", " ss:StyleID=\"Head\""); writeSsCell(ofs, "取值偏移量", " ss:StyleID=\"Head\""); writeSsCell(ofs, "最终值偏移", " ss:StyleID=\"Head\""); writeSsCell(ofs, "配置项", " ss:StyleID=\"Head\""); writeSsCell(ofs, "报告名", " ss:StyleID=\"Head\""); writeSsCell(ofs, "原始路径", " ss:StyleID=\"Head\""); writeSsCell(ofs, "类型", " ss:StyleID=\"Head\""); writeSsCell(ofs, "字段", " ss:StyleID=\"Head\""); for (int i = 1; i <= maxValues; ++i) writeSsCell(ofs, "值" + std::to_string(i), " ss:StyleID=\"Head\""); ofs << "\n"; auto writeItems = [&](const std::string& statFilter) { std::string lastCategory; std::string lastPhase; for (const auto& item : items) { if (!statFilter.empty() && item.stat != statFilter) continue; if (item.wiring != wiring) continue; if (item.category != lastCategory) { writeSsRowStart(ofs); writeSsCell(ofs, item.category, " ss:StyleID=\"Sub\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\""); ofs << "\n"; lastCategory.clear(); lastPhase.clear(); } if (item.phase != lastPhase) { writeSsRowStart(ofs); writeSsCell(ofs, item.phase + " 相", " ss:StyleID=\"Sub\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\""); ofs << "\n"; } std::string finalKey, finalValue; splitFinalText(item.finalText, finalKey, finalValue); finalKey = offsetFinalKey(finalKey, item); ComparePlan comparePlan = comparePlanForItem(finalKey, item); OriginMatch om = matchOrigin(origin, item.doPath, item.daPath, item.stat, item.phase, item.wiring, comparePlan.originPickStart, comparePlan.originPickEnd); std::vector finalValues = splitCommaValues(finalValue); std::vector originValuesRaw = splitCommaValues(om.value); std::vector originValues = roundOriginValuesForDisplay(originValuesRaw); bool hasOriginMatch = origin && !om.value.empty(); int finalRangeStart = 0; int finalRangeEnd = 0; bool isSequenceRow = parseKeyRange(finalKey, finalRangeStart, finalRangeEnd); int originStartIndex = -1; if (comparePlan.originPickStart != std::numeric_limits::min() && om.rangeStart != std::numeric_limits::min()) { originStartIndex = displayIndexForOriginIndex(om.valueIndexes, comparePlan.originPickStart); } std::string rowStyle; if (hasNullValue(finalValues)) rowStyle = "MatchRed"; else if (!origin) rowStyle = ""; else if (!hasOriginMatch) rowStyle = "MatchRed"; else rowStyle = valuesEqualForCompareByOriginIndex(finalValues, originValues, om.valueIndexes, comparePlan.originPickStart) ? "MatchGreen" : "MatchOrange"; addRowStats(stats, rowStyle); std::string originKey = originSeriesKeyForMatch(finalKey, item, om); writeSsRowStart(ofs); writeSsCell(ofs, displayLabelForSheet(item), cellAttrs(rowStyle, " ss:MergeDown=\"1\"")); writeSsCell(ofs, item.arrayBase, cellAttrs(rowStyle, " ss:MergeDown=\"1\"")); writeSsCell(ofs, item.valueOffset, cellAttrs(rowStyle, " ss:MergeDown=\"1\"")); writeSsCell(ofs, item.finalOffset, cellAttrs(rowStyle, " ss:MergeDown=\"1\"")); writeSsCell(ofs, item.configText, cellAttrs(rowStyle, " ss:MergeDown=\"1\"")); writeSsCell(ofs, om.reportName, cellAttrs(rowStyle, " ss:MergeDown=\"1\"")); writeSsCell(ofs, om.path.empty() ? "" : om.path, cellAttrs(rowStyle, " ss:MergeDown=\"1\"")); writeSsCell(ofs, "最终值", cellAttrs(rowStyle)); writeSsCell(ofs, finalKey, cellAttrs(rowStyle)); for (int i = 0; i < maxValues; ++i) writeSsCell(ofs, i < (int)finalValues.size() ? finalValues[i] : "", cellAttrs(rowStyle)); ofs << "\n"; writeSsRowStart(ofs); writeSsCell(ofs, "原始值", cellAttrs(rowStyle, " ss:Index=\"8\"")); writeSsCell(ofs, om.value.empty() ? "" : originKey, cellAttrs(rowStyle)); for (int i = 0; i < maxValues; ++i) { std::string attrs = (isSequenceRow && hasOriginMatch && i == originStartIndex) ? " ss:StyleID=\"OriginPickBlue\"" : cellAttrs(rowStyle); writeSsCell(ofs, i < (int)originValues.size() ? originValues[i] : "", attrs); } ofs << "\n"; lastCategory = item.category; lastPhase = item.phase; } }; if (showStatBlocks) { for (const auto& stat : blocks) { writeSsRowStart(ofs); writeSsCell(ofs, stat, " ss:StyleID=\"Section\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\""); ofs << "\n"; writeItems(stat); } } else { writeItems(""); } ofs << "
\n"; } /** * Function: writeOriginRecordWorksheet * * @brief 把 “writeOriginRecordWorksheet” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ofs 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param sheetName 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param matched 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param roundValues 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void writeOriginRecordWorksheet(std::ofstream& ofs, const OriginData& origin, const std::string& sheetName, bool matched, bool roundValues) { const int maxValues = 50; ofs << "\n"; ofs << ""; for (int i = 0; i < maxValues; ++i) ofs << ""; ofs << "\n"; writeSsRowStart(ofs); writeSsCell(ofs, "原始路径", " ss:StyleID=\"Head\""); writeSsCell(ofs, "报告名", " ss:StyleID=\"Head\""); writeSsCell(ofs, "icd中代表的字段", " ss:StyleID=\"Head\""); writeSsCell(ofs, "原始值", " ss:StyleID=\"Head\""); for (int i = 1; i <= maxValues; ++i) writeSsCell(ofs, "值" + std::to_string(i), " ss:StyleID=\"Head\""); ofs << "\n"; for (size_t i = 0; i < origin.records.size(); ++i) { bool isUsed = origin.used.count(i) != 0; if (isUsed != matched) continue; const auto& rec = origin.records[i]; std::vector originValues = splitCommaValues(rec.value); if (roundValues) originValues = roundOriginValuesForDisplay(originValues); writeSsRowStart(ofs); writeSsCell(ofs, rec.path); writeSsCell(ofs, rec.reportName); writeSsCell(ofs, ""); writeSsCell(ofs, rec.value); for (int j = 0; j < maxValues; ++j) writeSsCell(ofs, j < (int)originValues.size() ? originValues[j] : ""); ofs << "\n"; } ofs << "
\n"; } /** * Function: writeMatchedWorksheet * * @brief 把 “writeMatchedWorksheet” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ofs 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void writeMatchedWorksheet(std::ofstream& ofs, const OriginData& origin) { writeOriginRecordWorksheet(ofs, origin, "已匹配的原始数据", true, true); } /** * Function: writeUnmatchedWorksheet * * @brief 把 “writeUnmatchedWorksheet” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ofs 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void writeUnmatchedWorksheet(std::ofstream& ofs, const OriginData& origin) { writeOriginRecordWorksheet(ofs, origin, "未匹配的原始数据", false, true); } /** * Function: writeSummaryWorksheet * * @brief 把 “writeSummaryWorksheet” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ofs 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param stats 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void writeSummaryWorksheet(std::ofstream& ofs, const std::vector& stats) { ofs << "\n"; ofs << "\n"; writeSsRowStart(ofs); writeSsCell(ofs, "工作表", " ss:StyleID=\"Head\""); writeSsCell(ofs, "数据正常个数", " ss:StyleID=\"Head\""); writeSsCell(ofs, "数据异常个数", " ss:StyleID=\"Head\""); writeSsCell(ofs, "数据缺失个数", " ss:StyleID=\"Head\""); writeSsCell(ofs, "总数", " ss:StyleID=\"Head\""); ofs << "\n"; int totalNormal = 0; int totalAbnormal = 0; int totalMissing = 0; for (const auto& item : stats) { int total = item.normal + item.abnormal + item.missing; totalNormal += item.normal; totalAbnormal += item.abnormal; totalMissing += item.missing; writeSsRowStart(ofs); writeSsCell(ofs, item.sheetName); writeSsCell(ofs, std::to_string(item.normal)); writeSsCell(ofs, std::to_string(item.abnormal)); writeSsCell(ofs, std::to_string(item.missing)); writeSsCell(ofs, std::to_string(total)); ofs << "\n"; } writeSsRowStart(ofs); writeSsCell(ofs, "合计", " ss:StyleID=\"Section\""); writeSsCell(ofs, std::to_string(totalNormal), " ss:StyleID=\"Section\""); writeSsCell(ofs, std::to_string(totalAbnormal), " ss:StyleID=\"Section\""); writeSsCell(ofs, std::to_string(totalMissing), " ss:StyleID=\"Section\""); writeSsCell(ofs, std::to_string(totalNormal + totalAbnormal + totalMissing), " ss:StyleID=\"Section\""); ofs << "\n"; ofs << "
\n"; } /** * Function: headersForSheet * * @brief 实现解析/报表层的 “headersForSheet” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param headers 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param dataType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::vector headersForSheet(const std::vector& headers, const std::string& dataType) { std::vector out = headers; std::string displayDataType = dataType; std::replace(displayDataType.begin(), displayDataType.end(), ',', '/'); bool found = false; for (auto& h : out) { if (h.key == "DATA_TYPE") { h.val = displayDataType; found = true; break; } } if (!found) out.insert(out.begin(), {"DATA_TYPE", displayDataType}); return out; } /** * Function: specAllowsFrontType * * @brief 实现解析/报表层的 “specAllowsFrontType” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param spec 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param frontType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ static bool specAllowsFrontType(const SheetSpec& spec, const std::string& frontType) { return frontType.empty() || spec.frontType.empty() || spec.frontType == frontType; } /** * Function: sortOutItems * * @brief 实现解析/报表层的 “sortOutItems” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param items 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void sortOutItems(std::vector& items) { std::unordered_map statRank{ {"95值", 0}, {"平均值", 1}, {"最大值", 2}, {"最小值", 3} }; std::sort(items.begin(), items.end(), [&](const OutItem& a, const OutItem& b) { int sa = statRank.count(a.stat) ? statRank[a.stat] : 99; int sb = statRank.count(b.stat) ? statRank[b.stat] : 99; if (sa != sb) return sa < sb; static std::map catRank = { {"V", 0}, {"I", 1}, {"PQ", 2}, {"F_S", 3}, {"F_L", 4}, {"Z", 99} }; int ca = catRank.count(a.category) ? catRank[a.category] : 90; int cb = catRank.count(b.category) ? catRank[b.category] : 90; if (ca != cb) return ca < cb; if (a.phaseOrder != b.phaseOrder) return a.phaseOrder < b.phaseOrder; if (a.attrSort != b.attrSort) return a.attrSort < b.attrSort; return a.configText < b.configText; }); } /** * Function: writeExcelWorkbook * * @brief 把 “writeExcelWorkbook” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param outPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param blocks 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param valueMetas 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param dataByType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param origin 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param frontType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void writeExcelWorkbook(const fs::path& outPath, const std::vector& blocks, const std::unordered_map>& valueMetas, const std::unordered_map& dataByType, OriginData* origin, const std::string& frontType) { std::ofstream ofs(outPath, std::ios::binary); if (!ofs) throw std::runtime_error("Failed to open output file: " + outPath.string()); ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; ofs << "\n"; // SheetSpec 是报表目录:统计数据和实时数据各自按接线方式、DATA_TYPE 分 Sheet。 std::vector sheets = { {"星形接线", "HISDATA", "01", "star", true, "cfg_stat_data"}, {"角型接线", "HISDATA", "01", "delta", true, "cfg_stat_data"}, {"星型闪变02", "HISDATA", "02", "star", false, "cfg_stat_data"}, {"角形闪变02", "HISDATA", "02", "delta", false, "cfg_stat_data"}, {"星型闪变03", "HISDATA", "03", "star", false, "cfg_stat_data"}, {"角形闪变03", "HISDATA", "03", "delta", false, "cfg_stat_data"}, {"星型实时", "RTDATA", "01", "star", false, "cfg_3s_data"}, {"角形实时", "RTDATA", "01", "delta", false, "cfg_3s_data"}, {"星型实时闪变02", "RTDATA", "02", "star", false, "cfg_3s_data"}, {"角形实时闪变02", "RTDATA", "02", "delta", false, "cfg_3s_data"}, {"星型实时闪变03", "RTDATA", "03", "star", false, "cfg_3s_data"}, {"角形实时闪变03", "RTDATA", "03", "delta", false, "cfg_3s_data"} }; const std::vector emptyHeaders; const std::unordered_map emptyValues; std::vector summaryStats; for (const auto& spec : sheets) { if (!specAllowsFrontType(spec, frontType)) continue; auto bucketIt = dataByType.find(spec.dataType); const std::vector& headers = bucketIt == dataByType.end() ? emptyHeaders : bucketIt->second.headers; const std::unordered_map& valueByKey = bucketIt == dataByType.end() ? emptyValues : bucketIt->second.valueByKey; std::vector sheetItems = buildItemsForSheet(valueMetas, spec, valueByKey); sortOutItems(sheetItems); SheetStats stats; stats.sheetName = spec.name; writeDataWorksheet(ofs, spec.name, spec.wiring, headersForSheet(headers, spec.dataType), blocks, sheetItems, origin, spec.showStatBlocks, &stats); summaryStats.push_back(stats); } writeSummaryWorksheet(ofs, summaryStats); OriginData emptyOrigin; const OriginData& originForRawSheets = origin ? *origin : emptyOrigin; writeMatchedWorksheet(ofs, originForRawSheets); writeUnmatchedWorksheet(ofs, originForRawSheets); ofs << "\n"; } /** * Function: statBlocks * * @brief 实现解析/报表层的 “statBlocks” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static const std::vector& statBlocks() { static const std::vector blocks = {"95值", "平均值", "最大值", "最小值"}; return blocks; } /** * Function: writeWorkbookStage * * @brief 把 “writeWorkbookStage” 对应的内存数据序列化并写入目标位置。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ctx 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param outPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 */ static void writeWorkbookStage(WorkbookContext& ctx, const fs::path& outPath) { // 每次重写工作簿前清空 used,让匹配过程从头计算,避免多次刷新后状态累积。 if (ctx.hasOrigin) ctx.originData.used.clear(); OriginData* origin = ctx.hasOrigin ? &ctx.originData : nullptr; writeExcelWorkbook(outPath, statBlocks(), ctx.valueMetas, ctx.dataByType, origin, ctx.frontType); } /** * Function: createWorkbookTemplateFromXml * * @brief 构造 “createWorkbookTemplateFromXml” 对应的对象、消息、窗口控件或输出结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param xmlPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param outPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param frontType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static WorkbookContext createWorkbookTemplateFromXml(const fs::path& xmlPath, const fs::path& outPath, const std::string& frontType = "") { WorkbookContext ctx; ctx.xmlPath = xmlPath; ctx.frontType = frontType; ctx.valueMetas = loadValueMetas(xmlPath); ctx.xmlNames.reserve(ctx.valueMetas.size() * 2); // xmlNames 只保存 XML 原名,用于 mergeSequences 防止 V1/G_V1 被误合并。 for (const auto& kv : ctx.valueMetas) ctx.xmlNames.insert(kv.first); writeWorkbookStage(ctx, outPath); return ctx; } /** * Function: collectDataTypedJsonRoots * * @brief 遍历输入结构并收集 “collectDataTypedJsonRoots” 所需的候选数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param node 当前处理的数据行、单元格、XML 节点或模型项。 * @param roots 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void collectDataTypedJsonRoots(const json& node, std::vector& roots) { if (node.is_object()) { if (node.contains("DATA_TYPE")) { roots.push_back(&node); return; } for (auto it = node.begin(); it != node.end(); ++it) collectDataTypedJsonRoots(it.value(), roots); } else if (node.is_array()) { for (const auto& item : node) collectDataTypedJsonRoots(item, roots); } } /** * Function: normalizeDataType * * @brief 规范化 “normalizeDataType” 对应的字符串或标识,减少格式差异对匹配造成的影响。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param dataType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string normalizeDataType(std::string dataType) { dataType.erase(std::remove_if(dataType.begin(), dataType.end(), [](unsigned char c) { return std::isspace(c) != 0; }), dataType.end()); if (dataType.size() == 1 && std::isdigit(static_cast(dataType[0]))) { dataType = "0" + dataType; } return dataType.empty() ? "01" : dataType; } /** * Function: dataTypeFromMerged * * @brief 实现解析/报表层的 “dataTypeFromMerged” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param merged 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static std::string dataTypeFromMerged(const std::vector>& merged) { for (const auto& kv : merged) { if (kv.first == "DATA_TYPE") return normalizeDataType(kv.second); } return "01"; } /** * Function: applyJsonDataToWorkbook * * @brief 把 “applyJsonDataToWorkbook” 对应的变更应用到模型、控件或输出文档。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ctx 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param data 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param outPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 */ static void applyJsonDataToWorkbook(WorkbookContext& ctx, const json& data, const fs::path& outPath) { std::vector roots; collectDataTypedJsonRoots(data, roots); if (roots.empty()) roots.push_back(&data); for (const json* root : roots) { std::vector> flat; flattenJson(*root, "", flat); auto merged = mergeSequences(flat, ctx.xmlNames); std::string dataType = dataTypeFromMerged(merged); JsonDataBucket& bucket = ctx.dataByType[dataType]; // 同一个 DATA_TYPE 多次出现时保留第一份,避免 MQ 重复消息覆盖已生成结果。 if (bucket.hasData) continue; bucket.headers.clear(); bucket.valueByKey.clear(); bucket.valueByKey.reserve(merged.size() * 2); for (const auto& kv : merged) { bucket.valueByKey[kv.first] = kv.second; if (isHeaderKey(kv.first)) bucket.headers.push_back({kv.first, kv.second}); } bucket.hasData = true; } writeWorkbookStage(ctx, outPath); } /** * Function: applyOriginJsonToWorkbook * * @brief 把 “applyOriginJsonToWorkbook” 对应的变更应用到模型、控件或输出文档。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ctx 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originRoot 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param sourceOrder 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param outPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 */ static void applyOriginJsonToWorkbook(WorkbookContext& ctx, const json& originRoot, const fs::path& originPath, int sourceOrder, const fs::path& outPath) { appendOriginRoot(ctx.originData, originPath, sourceOrder, originRoot); ctx.hasOrigin = true; writeWorkbookStage(ctx, outPath); } /** * Function: addOriginJsonToContext * * @brief 向目标结构追加或保证存在 “addOriginJsonToContext” 所描述的数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ctx 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originRoot 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param sourceOrder 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void addOriginJsonToContext(WorkbookContext& ctx, const json& originRoot, const fs::path& originPath, int sourceOrder) { appendOriginRoot(ctx.originData, originPath, sourceOrder, originRoot); ctx.hasOrigin = true; } /** * Function: readJsonDataFile * * @brief 读取或加载 “readJsonDataFile” 对应的数据,并转换为本模块后续逻辑使用的内存结构。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param txtPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。 */ static json readJsonDataFile(const fs::path& txtPath) { std::string raw = ltrimBOM(readAll(txtPath)); try { return json::parse(raw); } catch (...) { std::vector roots = parseJsonRoots(raw); if (roots.size() == 1) return roots.front(); return roots; } } /** * Function: applyOriginFileToWorkbook * * @brief 把 “applyOriginFileToWorkbook” 对应的变更应用到模型、控件或输出文档。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ctx 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param sourceOrder 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param outPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 */ static void applyOriginFileToWorkbook(WorkbookContext& ctx, const fs::path& originPath, int sourceOrder, const fs::path& outPath) { std::string raw = ltrimBOM(readAll(originPath)); for (const auto& root : parseJsonRoots(raw)) { applyOriginJsonToWorkbook(ctx, root, originPath, sourceOrder, outPath); } } /** * Function: addOriginFileToContext * * @brief 向目标结构追加或保证存在 “addOriginFileToContext” 所描述的数据。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param ctx 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param sourceOrder 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 */ static void addOriginFileToContext(WorkbookContext& ctx, const fs::path& originPath, int sourceOrder) { std::string raw = ltrimBOM(readAll(originPath)); for (const auto& root : parseJsonRoots(raw)) { addOriginJsonToContext(ctx, root, originPath, sourceOrder); } } /** * Function: pqRunParser * * @brief 实现解析/报表层的 “pqRunParser” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param xmlPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param jsonPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param originPaths 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param outPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param err 输出参数;函数在成功或失败时写入结果或诊断信息。 * @param frontType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ bool pqRunParser(const std::string& xmlPath, const std::string& jsonPath, const std::vector& originPaths, const std::string& outPath, std::string* err, const std::string& frontType) { std::vector jsonPaths; if (!jsonPath.empty()) jsonPaths.push_back(jsonPath); return pqRunParser(xmlPath, jsonPaths, originPaths, outPath, err, frontType); } /** * Function: pqRunParser * * @brief 实现解析/报表层的 “pqRunParser” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param xmlPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param jsonPaths 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param originPaths 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param outPath 文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。 * @param err 输出参数;函数在成功或失败时写入结果或诊断信息。 * @param frontType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 条件成立或操作成功时为 true,否则为 false。 */ bool pqRunParser(const std::string& xmlPath, const std::vector& jsonPaths, const std::vector& originPaths, const std::string& outPath, std::string* err, const std::string& frontType) { try { fs::path xmlFile = fs::u8path(xmlPath); fs::path output = outPath.empty() ? fs::path("final_sorted.xls") : fs::u8path(outPath); // 解析顺序固定:先 XML 建模板,再填 jsondata,最后追加 origin 并重写带对比结果的工作簿。 WorkbookContext ctx = createWorkbookTemplateFromXml(xmlFile, output, frontType); for (const auto& jsonPath : jsonPaths) { if (!jsonPath.empty()) applyJsonDataToWorkbook(ctx, readJsonDataFile(fs::u8path(jsonPath)), output); } for (size_t i = 0; i < originPaths.size(); ++i) { if (!originPaths[i].empty()) addOriginFileToContext(ctx, fs::u8path(originPaths[i]), static_cast(i)); } if (!originPaths.empty()) writeWorkbookStage(ctx, output); return true; } catch (const std::exception& ex) { if (err) *err = ex.what(); return false; } } /** * Function: main * * @brief 命令行入口;无参数时转入 Win32 GUI,有参数时执行兼容的批处理解析流程。 * @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。 * @param argc 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @param argv 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。 * @return 整数状态、索引或计数;入口函数以 0 表示成功。 */ int main(int argc, char** argv) { try { if (argc == 1) { // 无参数启动 GUI;带参数则走下面的旧命令行解析模式。 return runPqGuiApp(); } fs::path xmlFile, txtFile; std::vector originFiles; bool hasOrigin = false; const fs::path outPath = "final_sorted.xls"; if (argc >= 2) { // ✅ Windows 中文路径/文件名:用 u8path 解析 xmlFile = fs::u8path(argv[1]); if (argc >= 3) txtFile = fs::u8path(argv[2]); if (argc >= 4) { hasOrigin = true; for (int i = 3; i < argc; ++i) { originFiles.push_back(fs::u8path(argv[i])); } } } else { xmlFile = findFirstByExt(".xml"); } std::cout << "[INFO] XML: " << xmlFile << "\n"; WorkbookContext ctx = createWorkbookTemplateFromXml(xmlFile, outPath); std::cout << "[OK] Template generated: " << outPath << "\n"; if (txtFile.empty()) txtFile = findFirstInputTxt(); std::cout << "[INFO] TXT: " << txtFile << "\n"; applyJsonDataToWorkbook(ctx, readJsonDataFile(txtFile), outPath); std::cout << "[OK] JSON data applied: " << outPath << "\n"; if (hasOrigin) { std::cout << "[INFO] ORIGIN:"; for (const auto& originFile : originFiles) std::cout << " " << originFile; std::cout << "\n"; for (size_t i = 0; i < originFiles.size(); ++i) { addOriginFileToContext(ctx, originFiles[i], static_cast(i)); std::cout << "[OK] Origin loaded: " << originFiles[i] << "\n"; } writeWorkbookStage(ctx, outPath); std::cout << "[OK] Origin data applied: " << outPath << "\n"; } std::cout << "[OK] Generated: final_sorted.xls\n"; return 0; } catch (const std::exception& ex) { std::cerr << "[ERR] " << ex.what() << "\n"; std::cerr << "Usage:\n" << " pq_tool [data.txt] [origin1.txt origin2.txt ...]\n" << "Or put one .xml and one .txt in current folder and run without args.\n"; return 1; } }