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

1934 lines
72 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

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

#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <functional>
#include <iostream>
#include <map>
#include <regex>
#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
using json = nlohmann::json;
namespace fs = std::filesystem;
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;
bool isSequence = false;
int seqStart = 0; // 例如 %2,50% 里的 2
int seqEnd = 0; // 例如 %2,50% 里的 50
std::string seqPrefixName; // 例如 MAX_V
};
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();
}
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;
}
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();
}
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();
}
static void flattenJson(const json& j, const std::string& prefix,
std::vector<std::pair<std::string, std::string>>& out) {
if (j.is_object()) {
for (auto it = j.begin(); it != j.end(); ++it) {
std::string key = prefix.empty() ? it.key() : (prefix + "_" + it.key());
flattenJson(it.value(), key, out);
}
return;
}
if (j.is_array()) {
std::ostringstream oss;
for (size_t i = 0; i < j.size(); ++i) {
if (i) oss << ",";
oss << toStr(j[i]);
}
out.push_back({prefix, oss.str()});
return;
}
out.push_back({prefix, toStr(j)});
}
// 读取 XMLValue/@name -> 完整元信息desc/DO/DA
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;
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 : "";
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;
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"};
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
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)
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;
}
// ✅ 合并序列:但如果 metricName 在 xmlNames 里存在(比如 V1/MAX_V1/G_V1则禁止合并
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
};
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;
}
static std::string cleanDescSuffix(std::string desc) {
static const std::regex re(R"((平均值|最大值|最小值|95值)$)");
return std::regex_replace(desc, re, "");
}
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;
}
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, ' ');
}
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] 中取出区间
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找不到时再去匹配序列模板项
static std::string trimRightUnderscore(std::string s) {
while (!s.empty() && s.back() == '_') {
s.pop_back();
}
return s;
}
static std::string rawMetricNameFromKey(const std::string& key) {
return metricAfterPhase(key);
}
static std::string preferredMetaName(const std::string& rawMetric, const ParsedKey& pk) {
if (startsWithStr(rawMetric, "G_")) return pk.baseName;
return rawMetric.empty() ? pk.baseName : rawMetric;
}
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;
}
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;
}
static bool statMatchesMeta(const ValueMeta& vm, const ParsedKey& pk) {
return !pk.stat.empty() && vm.desc.find(pk.stat) != std::string::npos;
}
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;
}
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;
};
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;
}
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;
}
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;
}
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);
}
}
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);
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);
}
std::vector<const ValueMeta*> exactCandidates;
collectByName(metas, preferred, wiring, filter, exactCandidates);
if (exactCandidates.empty()) collectByName(metas, secondary, wiring, filter, exactCandidates);
return bestMeta(exactCandidates, pk);
}
// 把 XML 中序列 DA如 phs*Har[%-2]$mag$f改显示成 phs*Har[0-48]$mag$f
static std::string rewriteDaRangeForDisplay(const std::string& daPath,
const ValueMeta& vm,
int keyStart,
int keyEnd) {
if (!vm.isSequence) return daPath;
// 规则:
// XML 模板里是 [%-2]
// key 是 [2-50]
// 显示要变成 [0-48]
// 即keyStart - vm.seqStart, keyEnd - vm.seqStart
int dispStart = keyStart - vm.seqStart;
int dispEnd = keyEnd - vm.seqStart;
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;
}
// 在当前目录找一个后缀文件(找不到就抛)
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");
}
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;
};
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相
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;
};
struct SheetSpec {
std::string name;
std::string topic;
std::string dataType;
std::string wiring;
bool showStatBlocks = false;
std::string frontType;
};
struct OriginRecord {
std::string path;
std::string value;
std::string reportName;
int sourceOrder = 0;
};
struct OriginData {
std::vector<OriginRecord> records;
std::unordered_map<std::string, std::vector<size_t>> pathToRecords;
std::unordered_set<size_t> used;
};
struct OriginMatch {
std::string path;
std::string value;
std::string reportName;
};
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;
std::unordered_map<std::string, JsonDataBucket> dataByType;
OriginData originData;
bool hasOrigin = false;
std::string frontType;
};
static std::string jsonScalarForOutput(const json& v) {
if (v.is_string()) return v.get<std::string>();
return v.dump();
}
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();
}
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");
}
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;
}
static void appendOriginRoot(OriginData& data, const fs::path& originPath, int sourceOrder, const json& root) {
std::string reportName = reportNameFromRoot(root, originPath, sourceOrder);
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);
}
}
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);
}
}
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;
}
static OriginData loadOriginData(const fs::path& originPath) {
std::vector<fs::path> paths{originPath};
return loadOriginData(paths);
}
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;
}
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);
}
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 "";
}
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;
}
static std::string phaseTokenForDa(const std::string& phase, const std::string& wiring) {
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";
}
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;
}
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 "";
return doPath + "$" + concreteDaPath(daPath, phase, wiring);
}
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;
}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;
}
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) {
OriginMatch match;
if (!origin) return match;
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)) {
std::ostringstream values;
bool any = false;
std::string reportName;
for (int i = start; i <= end; ++i) {
if (i != start) values << ",";
std::string onePath = before + "[" + std::to_string(i) + "]" + after;
const OriginRecord* rec = firstUnusedRecord(origin, onePath);
if (rec) {
any = true;
values << rec->value;
if (reportName.empty()) reportName = rec->reportName;
markRecordUsed(origin, rec);
}
}
if (any) {
match.path = path;
match.value = values.str();
match.reportName = reportName;
return match;
}
continue;
}
const OriginRecord* rec = firstUnusedRecord(origin, path);
if (rec) {
match.path = path;
match.value = rec->value;
match.reportName = rec->reportName;
markRecordUsed(origin, rec);
return match;
}
}
return match;
}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;
}
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;
}
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);
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();
}
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;
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)) {
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);
static std::string arrayBaseForMeta(const ValueMeta* vm) {
if (!vm || !vm->isSequence) return "0";
return std::to_string(vm->seqStart) + "-->" + std::to_string(vm->seqEnd);
}
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()));
}
static std::string finalOffsetForMeta(const ValueMeta* vm) {
if (!vm || !vm->hasOffset) return "0";
return std::to_string(vm->offset);
}
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;
}
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;
}
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);
}
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;
}
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;
}
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 {""};
}
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;
}
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);
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
};
}
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;
}
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;
}
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;
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;
}
static std::string htmlEscape(const std::string& s) {
std::string out;
out.reserve(s.size());
for (char c : s) {
if (c == '&') out += "&amp;";
else if (c == '<') out += "&lt;";
else if (c == '>') out += "&gt;";
else if (c == '"') out += "&quot;";
else out += c;
}
return out;
}
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;
}
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;
}
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;
}
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;
}
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;
}
static std::string roundOriginValueForDisplay(const std::string& value) {
long double number = 0;
if (!parseNumberValue(value, number)) return trimValue(value);
return fixedSixNumber(number);
}
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;
}
static std::string compareValueKey(const std::string& value) {
std::string trimmed = trimValue(value);
long double number = 0;
if (parseNumberValue(trimmed, number)) return fixedSixNumber(number);
return lowerValue(trimmed);
}
static bool hasNullValue(const std::vector<std::string>& values) {
for (const auto& value : values) {
if (lowerValue(trimValue(value)) == "null") return true;
}
return false;
}
static bool valuesEqualForCompare(const std::vector<std::string>& finalValues,
const std::vector<std::string>& originValues) {
if (finalValues.size() != originValues.size()) return false;
for (size_t i = 0; i < finalValues.size(); ++i) {
if (compareValueKey(finalValues[i]) != compareValueKey(originValues[i])) return false;
}
return true;
}
static std::string matchStyleForValues(bool hasOrigin,
const std::vector<std::string>& finalValues,
const std::vector<std::string>& originDisplayValues) {
if (hasNullValue(finalValues)) return "MatchRed";
if (!hasOrigin) return "";
return valuesEqualForCompare(finalValues, originDisplayValues) ? "MatchGreen" : "MatchOrange";
}
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;
}
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;
}
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);
}
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);
}
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);
}
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>";
}
static void writeSsRowStart(std::ofstream& ofs, int height = 0) {
if (height > 0) ofs << "<Row ss:AutoFitHeight=\"0\" ss:Height=\"" << height << "\">";
else ofs << "<Row>";
}
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;
}
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 = true) {
const int maxValues = 50;
const int totalCols = 9 + maxValues;
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";
for (const auto& h : headers) {
writeSsRowStart(ofs);
writeSsCell(ofs, h.key, " ss:StyleID=\"Default\"");
writeSsCell(ofs, h.val, " ss:StyleID=\"Default\" ss:MergeAcross=\"" + std::to_string(totalCols - 2) + "\"");
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";
}
OriginMatch om = matchOrigin(origin, item.doPath, item.daPath, item.stat, item.phase, item.wiring);
std::string finalKey, finalValue;
splitFinalText(item.finalText, finalKey, finalValue);
finalKey = offsetFinalKey(finalKey, item);
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();
std::string rowStyle = matchStyleForValues(hasOriginMatch, finalValues, originValues);
std::string originKey = originSeriesKey(finalKey, item);
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) writeSsCell(ofs, i < (int)originValues.size() ? originValues[i] : "", cellAttrs(rowStyle));
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";
}
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\"/>";
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\"");
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, "");
for (int j = 0; j < maxValues; ++j) writeSsCell(ofs, j < (int)originValues.size() ? originValues[j] : "");
ofs << "</Row>\n";
}
ofs << "</Table></Worksheet>\n";
}
static void writeMatchedWorksheet(std::ofstream& ofs, const OriginData& origin) {
writeOriginRecordWorksheet(ofs, origin, "已匹配的原始数据", true, false);
}
static void writeUnmatchedWorksheet(std::ofstream& ofs, const OriginData& origin) {
writeOriginRecordWorksheet(ofs, origin, "未匹配的原始数据", false, true);
}
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;
}
static bool specAllowsFrontType(const SheetSpec& spec, const std::string& frontType) {
return frontType.empty() || spec.frontType.empty() || spec.frontType == frontType;
}
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;
});
}
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 << "</Styles>\n";
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;
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);
writeDataWorksheet(ofs, spec.name, spec.wiring, headersForSheet(headers, spec.dataType), blocks, sheetItems, origin, spec.showStatBlocks);
}
if (origin) {
writeMatchedWorksheet(ofs, *origin);
writeUnmatchedWorksheet(ofs, *origin);
}
ofs << "</Workbook>\n";
}
static const std::vector<std::string>& statBlocks() {
static const std::vector<std::string> blocks = {"95值", "平均值", "最大值", "最小值"};
return blocks;
}
static void writeWorkbookStage(WorkbookContext& ctx, const fs::path& outPath) {
if (ctx.hasOrigin) ctx.originData.used.clear();
OriginData* origin = ctx.hasOrigin ? &ctx.originData : nullptr;
writeExcelWorkbook(outPath, statBlocks(), ctx.valueMetas, ctx.dataByType, origin, ctx.frontType);
}
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);
for (const auto& kv : ctx.valueMetas) ctx.xmlNames.insert(kv.first);
writeWorkbookStage(ctx, outPath);
return ctx;
}
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);
}
}
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;
}
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";
}
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];
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);
}
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);
}
static void addOriginJsonToContext(WorkbookContext& ctx,
const json& originRoot,
const fs::path& originPath,
int sourceOrder) {
appendOriginRoot(ctx.originData, originPath, sourceOrder, originRoot);
ctx.hasOrigin = true;
}
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;
}
}
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);
}
}
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);
}
}
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);
}
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);
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;
}
}
int main(int argc, char** argv) {
try {
if (argc == 1) {
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;
}
}