优化了xml修改和表格逆生成功能
This commit is contained in:
166
source/main.cpp
166
source/main.cpp
@@ -22,26 +22,57 @@
|
||||
#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]。
|
||||
*/
|
||||
|
||||
using json = nlohmann::json;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/*
|
||||
* XML <Value> 节点的完整业务元信息。
|
||||
*
|
||||
* 输入:loadValueMetas() 在递归遍历 XML 时填充。
|
||||
* 输出:后续用于把 jsondata 的 key 映射成 DO/DA、中文描述、接线方式、Topic/DataType。
|
||||
*/
|
||||
struct ValueMeta {
|
||||
std::string name;
|
||||
std::string desc;
|
||||
std::string doPath;
|
||||
std::string daPath;
|
||||
std::string wiring = "common"; // star/delta/common
|
||||
std::string topic;
|
||||
std::string dataType;
|
||||
std::string itemName;
|
||||
std::string seqValue;
|
||||
bool hasOffset = false;
|
||||
int offset = 0;
|
||||
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;
|
||||
int seqStart = 0; // 例如 %2,50% 里的 2
|
||||
int seqEnd = 0; // 例如 %2,50% 里的 50
|
||||
std::string seqPrefixName; // 例如 MAX_V
|
||||
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_。
|
||||
};
|
||||
|
||||
static std::string readAll(const fs::path& p) {
|
||||
@@ -58,6 +89,7 @@ static std::string ltrimBOM(std::string s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// DO/DA 中有时会出现 A||A||B 这种重复候选;这里保持顺序并去重,减少表格噪声。
|
||||
static std::string collapseRepeatedAlternatives(const std::string& s) {
|
||||
std::vector<std::string> parts;
|
||||
std::unordered_set<std::string> seen;
|
||||
@@ -100,6 +132,14 @@ static std::string toStr(const json& v) {
|
||||
return v.dump();
|
||||
}
|
||||
|
||||
/*
|
||||
* 把嵌套 JSON 压平成下划线连接的 key。
|
||||
*
|
||||
* 输入示例:{"Value":{"V":{"A":{"MAX_V1":12}}}}
|
||||
* 输出示例:Value_V_A_MAX_V1 -> 12
|
||||
*
|
||||
* 数组会直接转成逗号分隔值,这样后续单元格可以拆成多列展示。
|
||||
*/
|
||||
static void flattenJson(const json& j, const std::string& prefix,
|
||||
std::vector<std::pair<std::string, std::string>>& out) {
|
||||
if (j.is_object()) {
|
||||
@@ -121,7 +161,20 @@ static void flattenJson(const json& j, const std::string& prefix,
|
||||
out.push_back({prefix, toStr(j)});
|
||||
}
|
||||
|
||||
// 读取 XML:Value/@name -> 完整元信息(desc/DO/DA)
|
||||
/*
|
||||
* 读取 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。
|
||||
*/
|
||||
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;
|
||||
@@ -148,6 +201,7 @@ static std::unordered_map<std::string, std::vector<ValueMeta>> loadValueMetas(co
|
||||
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 : "";
|
||||
@@ -161,6 +215,7 @@ static std::unordered_map<std::string, std::vector<ValueMeta>> loadValueMetas(co
|
||||
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";
|
||||
}
|
||||
@@ -189,6 +244,7 @@ static std::unordered_map<std::string, std::vector<ValueMeta>> loadValueMetas(co
|
||||
}
|
||||
|
||||
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
|
||||
@@ -270,7 +326,19 @@ static bool parseSequenceKey(const std::string& key, std::string& groupId, int&
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ 合并序列:但如果 metricName 在 xmlNames 里存在(比如 V1/MAX_V1/G_V1),则禁止合并
|
||||
/*
|
||||
* 合并 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) {
|
||||
@@ -341,6 +409,15 @@ struct ParsedKey {
|
||||
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。
|
||||
*/
|
||||
static ParsedKey parseForSort(const std::string& key) {
|
||||
std::string base = key;
|
||||
auto bpos = base.find('[');
|
||||
@@ -495,6 +572,7 @@ static bool metaFilterAllowed(const ValueMeta& vm, const MetaFilter& filter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 多个 ValueMeta 都可能同名时,用相位、统计类型、Offset、Tot 等特征打分选最像的那一个。
|
||||
static int scoreMetaForKey(const ValueMeta& vm, const ParsedKey& pk) {
|
||||
int score = 0;
|
||||
if (daMatchesPhase(vm.daPath, pk.phase)) score += 20;
|
||||
@@ -581,6 +659,7 @@ static const ValueMeta* findMetaForKey(
|
||||
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;
|
||||
@@ -590,6 +669,7 @@ static const ValueMeta* findMetaForKey(
|
||||
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);
|
||||
@@ -693,6 +773,13 @@ struct OutItem {
|
||||
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;
|
||||
@@ -727,7 +814,7 @@ 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;
|
||||
std::unordered_set<size_t> used; // matchOrigin 命中后标记,最后可输出“已匹配/未匹配”清单。
|
||||
};
|
||||
|
||||
struct OriginMatch {
|
||||
@@ -754,8 +841,8 @@ struct JsonDataBucket {
|
||||
struct WorkbookContext {
|
||||
fs::path xmlPath;
|
||||
std::unordered_map<std::string, std::vector<ValueMeta>> valueMetas;
|
||||
std::unordered_set<std::string> xmlNames;
|
||||
std::unordered_map<std::string, JsonDataBucket> dataByType;
|
||||
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;
|
||||
@@ -831,6 +918,7 @@ static std::vector<json> parseJsonRoots(const std::string& text) {
|
||||
throw std::runtime_error("origin file contains unexpected content outside JSON object");
|
||||
}
|
||||
|
||||
// origin 文件可能是多个 JSON 对象直接拼接;这里用括号深度扫描出每个完整根对象。
|
||||
bool inString = false;
|
||||
bool escape = false;
|
||||
int depth = 0;
|
||||
@@ -867,6 +955,7 @@ static void appendOriginRoot(OriginData& data, const fs::path& originPath, int s
|
||||
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"];
|
||||
@@ -932,6 +1021,12 @@ static std::string statInstanceSuffix(const std::string& stat) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
* 把统计类型转换到 DO 实例号。
|
||||
*
|
||||
* 规则来自原始路径约定:平均/最大/最小/95值分别用 1/2/3/4 替换 DO 头部末尾的 0。
|
||||
* 例如 xxx0$MX$... + 最大值 -> xxx2$MX$...
|
||||
*/
|
||||
static std::string applyStatInstance(const std::string& doPath, const std::string& stat) {
|
||||
std::string suffix = statInstanceSuffix(stat);
|
||||
if (suffix.empty()) return doPath;
|
||||
@@ -947,6 +1042,7 @@ static std::string applyStatInstance(const std::string& doPath, const std::strin
|
||||
}
|
||||
|
||||
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";
|
||||
@@ -975,6 +1071,7 @@ static std::string makeOriginPath(const std::string& doPath,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1039,6 +1136,13 @@ static OriginMatch matchOrigin(OriginData* origin,
|
||||
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;
|
||||
|
||||
@@ -1220,6 +1324,7 @@ static bool makeOutItemForWiring(const std::unordered_map<std::string, std::vect
|
||||
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;
|
||||
@@ -1230,6 +1335,7 @@ static bool makeOutItemForWiring(const std::unordered_map<std::string, std::vect
|
||||
|
||||
int keyStart = 0, keyEnd = 0;
|
||||
if (vm && vm->isSequence && parseKeyRange(key, keyStart, keyEnd)) {
|
||||
// XML 中 DA 写的是 [%offset] 占位符,表格展示时改成最终可对齐的 [start-end] 范围。
|
||||
daDisp = rewriteDaRangeForDisplay(daDisp, *vm, keyStart, keyEnd);
|
||||
}
|
||||
|
||||
@@ -1352,6 +1458,7 @@ static OutItem makeOutItemFromMeta(const ValueMeta& vm,
|
||||
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;
|
||||
|
||||
@@ -1423,6 +1530,7 @@ static std::vector<OutItem> buildItemsForSheet(const std::unordered_map<std::str
|
||||
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;
|
||||
@@ -1515,6 +1623,7 @@ static std::vector<std::string> roundOriginValuesForDisplay(const std::vector<st
|
||||
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);
|
||||
}
|
||||
@@ -1587,6 +1696,7 @@ static ComparePlan comparePlanForItem(const std::string& finalKey, const OutItem
|
||||
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;
|
||||
@@ -1715,6 +1825,16 @@ static void writeDataWorksheet(std::ofstream& ofs,
|
||||
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\"/>";
|
||||
@@ -2014,6 +2134,7 @@ static void writeExcelWorkbook(const fs::path& outPath,
|
||||
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"},
|
||||
@@ -2060,6 +2181,7 @@ static const std::vector<std::string>& statBlocks() {
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -2071,6 +2193,7 @@ static WorkbookContext createWorkbookTemplateFromXml(const fs::path& xmlPath, co
|
||||
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;
|
||||
@@ -2117,6 +2240,7 @@ static void applyJsonDataToWorkbook(WorkbookContext& ctx, const json& data, cons
|
||||
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();
|
||||
@@ -2200,6 +2324,7 @@ bool pqRunParser(const std::string& xmlPath,
|
||||
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);
|
||||
@@ -2218,6 +2343,7 @@ bool pqRunParser(const std::string& xmlPath,
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc == 1) {
|
||||
// 无参数启动 GUI;带参数则走下面的旧命令行解析模式。
|
||||
return runPqGuiApp();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user