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

3488 lines
182 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

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

#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <functional>
#include <limits>
#include <iostream>
#include <map>
#include <regex>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "json.hpp" // nlohmann::json (单头文件版)
#include "pq_app.h"
#include "tinyxml2.h" // tinyxml2
/*
* main.cpp
*
* 本文件是“映射解析 + 报表生成”的核心,也保留了命令行入口。
*
* 主流程:
* 1. loadValueMetas() 读取 ICD/XML 模板,把每个 <Value> 的 name、desc、DO、DA、
* Topic、DataType、Item、Sequence 等上下文整理成 ValueMeta。
* 2. readJsonDataFile() 读取 DATATopic 的 jsondataflattenJson() 压平成
* 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 XMLwriteSsCell 等函数直接写 SpreadsheetML 标签。
*/
using json = nlohmann::json;
namespace fs = std::filesystem;
/*
* XML <Value> 节点的完整业务元信息。
*
* 输入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/@DOIEC 61850 MMS 对象路径前半段。
std::string daPath; // Value/@DAIEC 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<std::string> parts;
std::unordered_set<std::string> 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<bool>() ? "true" : "false";
if (v.is_number_integer()) return std::to_string(v.get<long long>());
if (v.is_number_unsigned()) return std::to_string(v.get<unsigned long long>());
if (v.is_number_float()) {
std::ostringstream oss;
oss.setf(std::ios::fixed);
oss.precision(6);
oss << v.get<double>();
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<std::string>();
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<std::pair<std::string, std::string>>& 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)});
}
/*
* 读取 XMLValue/@name -> 完整元信息列表。
*
* 输入xmlPath通常是下载的 ICD/XML 映射文件。
* 输出unordered_map<name, vector<ValueMeta>>。同一个 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<std::string, std::vector<ValueMeta>> loadValueMetas(const fs::path& xmlPath) {
std::unordered_map<std::string, std::vector<ValueMeta>> 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<void(tinyxml2::XMLElement*, std::string, std::string, std::string, std::string, std::string)> 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<std::string> PHASES = {"A", "B", "C", "T"};
/**
* Function: phaseRank
*
* @brief 实现解析/报表层的 “phaseRank” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。
* @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
static std::unordered_map<std::string, int> phaseRank() {
std::unordered_map<std::string, int> 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<std::string> 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) ..._<number>
// 2) ..._<LETTERS><number> (如 ..._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<std::pair<std::string, std::string>>
mergeSequences(const std::vector<std::pair<std::string, std::string>>& flat,
const std::unordered_set<std::string>& xmlNames) {
struct SeqGroup { size_t firstPos; std::map<int, std::string> idx2val; };
std::unordered_map<std::string, SeqGroup> groups;
std::vector<bool> isSeq(flat.size(), false);
std::vector<std::string> 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<std::string, bool> mergeable;
for (auto& kv : groups) {
mergeable[kv.first] = (kv.second.idx2val.size() >= 2);
}
std::vector<std::pair<std::string, std::string>> out;
std::unordered_map<std::string, bool> 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<std::string> 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<unsigned char>(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<const ValueMeta*>& 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<std::string, std::vector<ValueMeta>>& metas,
const std::string& name,
const std::string& wiring,
const MetaFilter& filter,
std::vector<const ValueMeta*>& 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<const ValueMeta*> collectSequenceCandidates(
const std::unordered_map<std::string, std::vector<ValueMeta>>& metas,
const std::string& wantedName,
const ParsedKey& pk,
const std::string& wiring,
const MetaFilter& filter,
int keyStart,
int keyEnd)
{
std::vector<const ValueMeta*> 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<std::string, std::vector<ValueMeta>>& 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<const ValueMeta*> 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<HeaderItem> fields;
};
struct OriginData {
std::vector<OriginRecord> records;
std::vector<OriginHeader> headers;
std::unordered_map<std::string, std::vector<size_t>> pathToRecords;
std::unordered_set<size_t> used; // matchOrigin 命中后标记,最后可输出“已匹配/未匹配”清单。
};
struct OriginMatch {
std::string path;
std::string value;
std::string reportName;
int sourceOrder = -1;
int rangeStart = std::numeric_limits<int>::min();
int rangeEnd = std::numeric_limits<int>::min();
std::vector<int> valueIndexes;
};
struct ComparePlan {
int originPickStart = std::numeric_limits<int>::min();
int originPickEnd = std::numeric_limits<int>::min();
};
struct JsonDataBucket {
std::vector<HeaderItem> headers;
std::unordered_map<std::string, std::string> valueByKey;
bool hasData = false;
};
struct WorkbookContext {
fs::path xmlPath;
std::unordered_map<std::string, std::vector<ValueMeta>> valueMetas;
std::unordered_set<std::string> xmlNames; // XML 中存在的原始 name用于避免序列误合并。
std::unordered_map<std::string, JsonDataBucket> 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<std::string>();
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<std::string>& originHeaderKeys() {
static const std::vector<std::string> 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<std::string>& originHeaderDisplayKeys() {
static const std::vector<std::string> 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<json> parseJsonRoots(const std::string& text) {
std::vector<json> roots;
size_t pos = 0;
while (pos < text.size()) {
while (pos < text.size() && std::isspace(static_cast<unsigned char>(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<fs::path>& originPaths) {
OriginData data;
for (size_t i = 0; i < originPaths.size(); ++i) {
appendOriginData(data, originPaths[i], static_cast<int>(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<fs::path> 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<size_t>(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<std::string> makeOriginPathCandidates(const std::string& doPath,
const std::string& daPath,
const std::string& stat,
const std::string& phase,
const std::string& wiring) {
std::vector<std::string> 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<int>::min(),
int pickEndOverride = std::numeric_limits<int>::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<int>::min() ? start : pickStartOverride;
int pickEnd = pickEndOverride == std::numeric_limits<int>::min() ? end : pickEndOverride;
struct IndexedRecord {
int index = 0;
size_t recordIndex = 0;
};
std::map<int, std::vector<IndexedRecord>> 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<int, size_t> indexToRecord;
int displayStart = std::numeric_limits<int>::max();
int displayEnd = std::numeric_limits<int>::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<std::string> wrapDisplay(const std::string& s, int width) {
std::vector<std::string> 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<unsigned char>(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<std::string> nameLines = wrapDisplay(item.label, NAME_W);
std::vector<std::string> configLines = wrapDisplay(item.configText, CONFIG_W);
std::vector<std::string> pathLines = wrapDisplay(om.path.empty() ? "" : ("\"" + om.path + "\""), PATH_W);
std::vector<std::string> finalLines = wrapDisplay(item.finalText, FINAL_W);
std::vector<std::string> 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<std::string, std::vector<ValueMeta>>& 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<std::string> 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<std::string, std::string>& 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<std::string>& 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<OutItem> buildItemsForSheet(const std::unordered_map<std::string, std::vector<ValueMeta>>& valueMetas,
const SheetSpec& spec,
const std::unordered_map<std::string, std::string>& valueByKey) {
std::vector<OutItem> out;
std::unordered_set<std::string> 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 += "&amp;";
else if (c == '<') out += "&lt;";
else if (c == '>') out += "&gt;";
else if (c == '"') out += "&quot;";
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<std::string> splitCommaValues(const std::string& s) {
std::vector<std::string> 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<char>(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<unsigned char>(*end))) ++end;
if (end && *end) return false;
if (errno == ERANGE) return false;
if (!std::isfinite(static_cast<double>(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<double>(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<std::string> roundOriginValuesForDisplay(const std::vector<std::string>& values) {
std::vector<std::string> 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<std::string>& 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<std::string>& finalValues,
const std::vector<std::string>& originValues,
int originStartIndex = 0) {
if (originStartIndex < 0) return false;
if (originValues.size() < static_cast<size_t>(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<int>& originIndexes, int originIndex) {
auto it = std::find(originIndexes.begin(), originIndexes.end(), originIndex);
if (it == originIndexes.end()) return -1;
return static_cast<int>(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<std::string>& finalValues,
const std::vector<std::string>& originValues,
const std::vector<int>& originIndexes,
int originPickStart) {
if (originPickStart == std::numeric_limits<int>::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<int>(i));
if (displayIndex < 0 || displayIndex >= static_cast<int>(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<std::string>& finalValues,
const std::vector<std::string>& 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<int>::min() &&
match.rangeEnd != std::numeric_limits<int>::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 << "<Cell" << attrs << "><Data ss:Type=\"String\">" << htmlEscape(value) << "</Data></Cell>";
}
/**
* 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 << "<Row ss:AutoFitHeight=\"0\" ss:Height=\"" << height << "\">";
else ofs << "<Row>";
}
/**
* 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<OriginHeader> visibleOriginHeadersForSheet(OriginData* origin,
const std::vector<OutItem>& items,
const std::string& wiring) {
std::vector<OriginHeader> visible;
if (!origin) return visible;
OriginData probe = *origin;
std::set<int> 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<HeaderItem>& headers,
const std::vector<std::string>& blocks,
const std::vector<OutItem>& items,
OriginData* origin,
bool showStatBlocks,
SheetStats* stats) {
const int maxValues = 64;
std::vector<OriginHeader> visibleOriginHeaders = visibleOriginHeadersForSheet(origin, items, wiring);
const int totalCols = std::max(9 + maxValues, 3 + static_cast<int>(visibleOriginHeaders.size()));
/*
* 每个数据项写两行:
* 第一行“最终值”:来自 jsondata按逗号拆到 值1..值64
* 第二行“原始值”:来自 origin按同一比较区间对齐展示。
* 行颜色:
* MatchGreen 最终值与原始值一致;
* MatchOrange 找到原始值但内容不一致;
* MatchRed 最终值为 null 或没有匹配到原始值;
* OriginPickBlue 标出序列比较时真正对齐起点。
*/
ofs << "<Worksheet ss:Name=\"" << htmlEscape(sheetName) << "\"><Table>\n";
ofs << "<Column ss:Width=\"210\"/><Column ss:Width=\"75\"/><Column ss:Width=\"85\"/><Column ss:Width=\"85\"/>";
ofs << "<Column ss:Width=\"310\"/><Column ss:Width=\"120\"/><Column ss:Width=\"280\"/>";
ofs << "<Column ss:Width=\"55\"/><Column ss:Width=\"150\"/>";
for (int i = 0; i < maxValues; ++i) ofs << "<Column ss:Width=\"90\"/>";
ofs << "\n";
writeSsRowStart(ofs);
writeSsCell(ofs, "头部信息", " ss:StyleID=\"Section\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\"");
ofs << "</Row>\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 << "</Row>\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 << "</Row>\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 << "</Row>\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 << "</Row>\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<std::string> finalValues = splitCommaValues(finalValue);
std::vector<std::string> originValuesRaw = splitCommaValues(om.value);
std::vector<std::string> 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<int>::min() &&
om.rangeStart != std::numeric_limits<int>::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 << "</Row>\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 << "</Row>\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 << "</Row>\n";
writeItems(stat);
}
} else {
writeItems("");
}
ofs << "</Table></Worksheet>\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 << "<Worksheet ss:Name=\"" << htmlEscape(sheetName) << "\"><Table>\n";
ofs << "<Column ss:Width=\"320\"/><Column ss:Width=\"120\"/><Column ss:Width=\"160\"/><Column ss:Width=\"220\"/>";
for (int i = 0; i < maxValues; ++i) ofs << "<Column ss:Width=\"90\"/>";
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 << "</Row>\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<std::string> 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 << "</Row>\n";
}
ofs << "</Table></Worksheet>\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<SheetStats>& stats) {
ofs << "<Worksheet ss:Name=\"统计信息\"><Table>\n";
ofs << "<Column ss:Width=\"180\"/><Column ss:Width=\"110\"/><Column ss:Width=\"110\"/><Column ss:Width=\"110\"/><Column ss:Width=\"90\"/>\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 << "</Row>\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 << "</Row>\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 << "</Row>\n";
ofs << "</Table></Worksheet>\n";
}
/**
* Function: headersForSheet
*
* @brief 实现解析/报表层的 “headersForSheet” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。
* @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。
* @param headers 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @param dataType 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
static std::vector<HeaderItem> headersForSheet(const std::vector<HeaderItem>& headers, const std::string& dataType) {
std::vector<HeaderItem> 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<OutItem>& items) {
std::unordered_map<std::string, int> 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<std::string,int> 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<std::string>& blocks,
const std::unordered_map<std::string, std::vector<ValueMeta>>& valueMetas,
const std::unordered_map<std::string, JsonDataBucket>& 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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
ofs << "<?mso-application progid=\"Excel.Sheet\"?>\n";
ofs << "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\" "
<< "xmlns:o=\"urn:schemas-microsoft-com:office:office\" "
<< "xmlns:x=\"urn:schemas-microsoft-com:office:excel\" "
<< "xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">\n";
ofs << "<Styles>\n";
ofs << "<Style ss:ID=\"Default\" ss:Name=\"Normal\"><Alignment ss:Horizontal=\"Left\" ss:Vertical=\"Center\"/><Borders><Border ss:Position=\"Bottom\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"/><Border ss:Position=\"Left\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"/><Border ss:Position=\"Right\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"/><Border ss:Position=\"Top\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"/></Borders><Font ss:FontName=\"SimSun\" ss:Size=\"11\"/></Style>\n";
ofs << "<Style ss:ID=\"Head\" ss:Parent=\"Default\"><Font ss:Bold=\"1\"/><Interior ss:Color=\"#D9EAD3\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"Section\" ss:Parent=\"Default\"><Font ss:Bold=\"1\"/><Interior ss:Color=\"#E8EEF7\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"Sub\" ss:Parent=\"Default\"><Font ss:Bold=\"1\"/><Interior ss:Color=\"#F4F4F4\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"MatchGreen\" ss:Parent=\"Default\"><Interior ss:Color=\"#D9EAD3\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"MatchOrange\" ss:Parent=\"Default\"><Interior ss:Color=\"#FCE4D6\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"MatchRed\" ss:Parent=\"Default\"><Interior ss:Color=\"#F4CCCC\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"OriginPickBlue\" ss:Parent=\"Default\"><Interior ss:Color=\"#CFE2F3\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "</Styles>\n";
// SheetSpec 是报表目录统计数据和实时数据各自按接线方式、DATA_TYPE 分 Sheet。
std::vector<SheetSpec> 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<HeaderItem> emptyHeaders;
const std::unordered_map<std::string, std::string> emptyValues;
std::vector<SheetStats> summaryStats;
for (const auto& spec : sheets) {
if (!specAllowsFrontType(spec, frontType)) continue;
auto bucketIt = dataByType.find(spec.dataType);
const std::vector<HeaderItem>& headers = bucketIt == dataByType.end() ? emptyHeaders : bucketIt->second.headers;
const std::unordered_map<std::string, std::string>& valueByKey =
bucketIt == dataByType.end() ? emptyValues : bucketIt->second.valueByKey;
std::vector<OutItem> 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 << "</Workbook>\n";
}
/**
* Function: statBlocks
*
* @brief 实现解析/报表层的 “statBlocks” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。
* @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
*/
static const std::vector<std::string>& statBlocks() {
static const std::vector<std::string> 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<const json*>& 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<unsigned char>(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<std::pair<std::string, std::string>>& 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<const json*> roots;
collectDataTypedJsonRoots(data, roots);
if (roots.empty()) roots.push_back(&data);
for (const json* root : roots) {
std::vector<std::pair<std::string, std::string>> 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<json> 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<std::string>& originPaths,
const std::string& outPath,
std::string* err,
const std::string& frontType) {
std::vector<std::string> 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<std::string>& jsonPaths,
const std::vector<std::string>& 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<int>(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<fs::path> 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<int>(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 <LN_D001.xml> [data.txt] [origin1.txt origin2.txt ...]\n"
<< "Or put one .xml and one .txt in current folder and run without args.\n";
return 1;
}
}