3488 lines
182 KiB
C++
3488 lines
182 KiB
C++
#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 的 jsondata,flattenJson() 压平成
|
||
* Value_... -> value 的键值对,mergeSequences() 把连续编号合并成数组。
|
||
* 3. buildItemsForSheet() 按 SheetSpec 生成每张工作表的 OutItem 行。
|
||
* 4. addOriginFileToContext() 读取 TraceTopic 的 origin/MMS 数据,matchOrigin()
|
||
* 用 DO + DA + 相位 + 序列范围匹配最终值和原始值。
|
||
* 5. writeExcelWorkbook() 输出 Excel 2003 XML 格式的 .xls,供 Excel/GUI 预览打开。
|
||
*
|
||
* 标志/转换要点:
|
||
* - Sequence/@value == 7 代表星形接线 star。
|
||
* - Sequence/@value == 112 代表角形接线 delta。
|
||
* - DATA_TYPE "1" 会规范成 "01",用于匹配统计/闪变/实时 Sheet。
|
||
* - 指标名前缀 G_/MAX_/MIN_ 分别转换成 95值/最大值/最小值;无前缀默认平均值。
|
||
* - DA 中 phs* 是相位占位符,星形转换为 phsA/phsB/phsC,角形转换为
|
||
* phsAB/phsBC/phsCA。
|
||
* - Value 名称中的 %start,end% 是 XML 序列模板;运行时显示成 [start-end]。
|
||
*
|
||
* 阅读本文件时可留意的 C++/库语法:
|
||
* - using json = nlohmann::json 给长类型起别名;json::parse/contains/value 完成 JSON 解析;
|
||
* - struct ValueMeta/OutItem/WorkbookContext 是逐阶段的数据模型,数据按主流程向后传递;
|
||
* - const T& 表示只读引用,避免复制大型字符串/容器;T* 常用于可为空的输出或上下文;
|
||
* - std::vector 保持有序行,map/set 保持键有序,unordered_map 用于高频散列查找;
|
||
* - std::regex 解析键名、范围和路径;std::stable_sort 在键相等时保留原始先后顺序;
|
||
* - lambda 用于局部排序/筛选/写行逻辑,捕获的 &ctx 等对象只在当前调用期间有效;
|
||
* - 输出的 .xls 实质是 Excel 2003 XML,writeSsCell 等函数直接写 SpreadsheetML 标签。
|
||
*/
|
||
|
||
using json = nlohmann::json;
|
||
namespace fs = std::filesystem;
|
||
|
||
/*
|
||
* XML <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/@DO,IEC 61850 MMS 对象路径前半段。
|
||
std::string daPath; // Value/@DA,IEC 61850 MMS 数据属性路径后半段。
|
||
std::string wiring = "common"; // star/delta/common:由 Sequence/@value 推断。
|
||
std::string topic; // 父级 Topic/@name,区分 HISDATA/RTDATA。
|
||
std::string dataType; // 父级 DataType/@value,区分 01/02/03。
|
||
std::string itemName; // 父级 Item/@name,如 V/I/PQ/F_S/F_L。
|
||
std::string seqValue; // 父级 Sequence/@value,后续判断 T 相或接线方式会用到。
|
||
bool hasOffset = false; // Value/@Offset 是否存在。
|
||
int offset = 0; // 最终值偏移量,用于把最终值数组范围挪到 origin 对齐范围。
|
||
|
||
bool isSequence = false; // name 是否匹配 %start,end% 序列模板。
|
||
int seqStart = 0; // 例如 %2,50% 里的 2。
|
||
int seqEnd = 0; // 例如 %2,50% 里的 50。
|
||
std::string seqPrefixName; // 例如 MAX_V_%2,50% 的前缀 MAX_V_。
|
||
};
|
||
/**
|
||
* Function: readAll
|
||
*
|
||
* @brief 读取或加载 “readAll” 对应的数据,并转换为本模块后续逻辑使用的内存结构。
|
||
* @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。
|
||
* @param p 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
|
||
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
|
||
*/
|
||
static std::string readAll(const fs::path& p) {
|
||
std::ifstream ifs(p, std::ios::binary);
|
||
if (!ifs) throw std::runtime_error("Failed to open: " + p.string());
|
||
std::ostringstream oss;
|
||
oss << ifs.rdbuf();
|
||
return oss.str();
|
||
}
|
||
/**
|
||
* Function: ltrimBOM
|
||
*
|
||
* @brief 实现解析/报表层的 “ltrimBOM” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。
|
||
* @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。
|
||
* @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
|
||
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
|
||
*/
|
||
static std::string ltrimBOM(std::string s) {
|
||
const std::string bom = "\xEF\xBB\xBF";
|
||
if (s.rfind(bom, 0) == 0) s.erase(0, bom.size());
|
||
return s;
|
||
}
|
||
|
||
// DO/DA 中有时会出现 A||A||B 这种重复候选;这里保持顺序并去重,减少表格噪声。
|
||
/**
|
||
* Function: collapseRepeatedAlternatives
|
||
*
|
||
* @brief 规范化 “collapseRepeatedAlternatives” 对应的字符串或标识,减少格式差异对匹配造成的影响。
|
||
* @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。
|
||
* @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
|
||
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
|
||
*/
|
||
static std::string collapseRepeatedAlternatives(const std::string& s) {
|
||
std::vector<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)});
|
||
}
|
||
|
||
/*
|
||
* 读取 XML:Value/@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 += "&";
|
||
else if (c == '<') out += "<";
|
||
else if (c == '>') out += ">";
|
||
else if (c == '"') out += """;
|
||
else out += c;
|
||
}
|
||
return out;
|
||
}
|
||
/**
|
||
* Function: splitCommaValues
|
||
*
|
||
* @brief 实现解析/报表层的 “splitCommaValues” 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。
|
||
* @details 本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。
|
||
* @param s 调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。
|
||
* @return 构造、解析或查找到的结果;所有权与生命周期遵循返回类型。
|
||
*/
|
||
static std::vector<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;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|