refactor(mms-mapping): 重构ICD到XML转换服务优化代码结构

- 移除IcdToXmlGenerateResult中未使用的iedName、ldInst、indexAnalysis、savedPath字段
- 注释掉相应的getter/setter方法减少代码冗余
- 重构IcdToXmlTaskAppService中generateFromIcd方法的业务逻辑
- 优化JsonToXmlConversionService的转换流程提高性能
- 添加详细的中文注释说明各个方法的功能和实现逻辑
- 调整规则匹配和XML生成的核心算法提升匹配准确性
- 修改未匹配规则的错误提示信息增加详细指标信息
This commit is contained in:
周宇 蔡
2026-05-27 08:45:36 +08:00
parent 8a92ff3be0
commit 58ca8b0c23
7 changed files with 280 additions and 715 deletions

View File

@@ -16,33 +16,10 @@ public class IcdToXmlResponseConverter {
IcdToXmlResponse response = new IcdToXmlResponse();
response.setStatus(result.getStatus());
response.setMessage(result.getMessage());
response.setIedName(result.getIedName());
response.setLdInst(result.getLdInst());
response.setSavedPath(result.getSavedPath());
//response.setIedName(result.getIedName());
//response.setLdInst(result.getLdInst());
response.getProblems().addAll(result.getProblems());
response.setMappingXml(result.getMappingXml());
if (result.getIndexAnalysis() != null && result.getIndexAnalysis().getCandidates() != null) {
for (IndexCandidate candidate : result.getIndexAnalysis().getCandidates()) {
IndexCandidateResponse candidateResponse = new IndexCandidateResponse();
candidateResponse.setGroupKey(candidate.getGroupKey());
candidateResponse.setGroupDesc(candidate.getGroupDesc());
candidateResponse.setReportCount(candidate.getReportCount());
candidateResponse.getTemplateLabels().addAll(candidate.getTemplateLabels());
if (candidate.getReports() != null) {
for (IndexCandidateReportItem item : candidate.getReports()) {
IndexCandidateReportItemResponse itemResponse = new IndexCandidateReportItemResponse();
itemResponse.setReportName(item.getReportName());
itemResponse.setDataSetName(item.getDataSetName());
itemResponse.setReportDesc(item.getReportDesc());
itemResponse.getAvailableLnInstValues().addAll(item.getAvailableLnInstValues());
candidateResponse.getReports().add(itemResponse);
}
}
response.getIndexCandidates().add(candidateResponse);
}
}
if (result.getMappingDocument() != null) {
MappingDocumentResponse doc = new MappingDocumentResponse();

View File

@@ -30,84 +30,44 @@ import java.util.List;
*/
@Component
public class JsonToXmlConversionService {
private final ObjectMapper objectMapper;
private final RuleBasedXmlMappingService ruleBasedXmlMappingService;
public JsonToXmlConversionService(RuleBasedXmlMappingService ruleBasedXmlMappingService) {
this.ruleBasedXmlMappingService = ruleBasedXmlMappingService;
this.objectMapper = new ObjectMapper();
// 配置ObjectMapper忽略未知属性避免反序列化时因字段不匹配而失败
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
* 从JSON字符串转换为XML文件
*/
public String convertFromJson(String mappingJson,
InputStream templateStream,
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
long startTime = System.currentTimeMillis();
System.out.println("[JSON转XML] 开始转换...");
try {
MappingDocument mappingDocument = objectMapper.readValue(mappingJson, MappingDocument.class);
long parseTime = System.currentTimeMillis();
System.out.println("[JSON转XML] JSON解析完成耗时: " + (parseTime - startTime) + " ms");
ConversionResult conversionResult = buildXmlFromMappingWithResult(mappingDocument, templateStream, ruleStreams, indexMapping);
long totalTime = System.currentTimeMillis() - startTime;
System.out.println("[JSON转XML] 转换完成,总耗时: " + totalTime + " ms");
if (conversionResult.getUnmatchedRuleDetails() != null && !conversionResult.getUnmatchedRuleDetails().isEmpty()) {
System.err.println("[JSON转XML] 警告: 有 " + conversionResult.getUnmatchedRuleDetails().size() + " 条规则未匹配到指标");
}
return conversionResult.getXmlContent();
} catch (Exception e) {
long totalTime = System.currentTimeMillis() - startTime;
System.err.println("[JSON转XML] 转换失败,已耗时: " + totalTime + " ms错误: " + e.getMessage());
throw e;
}
}
/**
* 从JSON字符串转换为XML并返回包含未匹配规则信息的完整结果。
*/
public ConversionResult convertFromJsonWithResult(String mappingJson,
InputStream templateStream,
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
public ConversionResult convertFromJsonWithResult(MappingDocument mappingDocument,
InputStream templateStream,
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
long startTime = System.currentTimeMillis();
System.out.println("[JSON转XML] 开始转换(带结果)...");
ConversionResult conversionResult = buildXmlFromMappingWithResult(mappingDocument, templateStream, ruleStreams, indexMapping);
try {
MappingDocument mappingDocument = objectMapper.readValue(mappingJson, MappingDocument.class);
long parseTime = System.currentTimeMillis();
System.out.println("[JSON转XML] JSON解析完成耗时: " + (parseTime - startTime) + " ms");
ConversionResult conversionResult = buildXmlFromMappingWithResult(mappingDocument, templateStream, ruleStreams, indexMapping);
long totalTime = System.currentTimeMillis() - startTime;
System.out.println("[JSON转XML] 转换完成,总耗时: " + totalTime + " ms");
if (conversionResult.getUnmatchedRuleDetails() != null && !conversionResult.getUnmatchedRuleDetails().isEmpty()) {
System.err.println("[JSON转XML] 警告: 有 " + conversionResult.getUnmatchedRuleDetails().size() + " 条规则未匹配到指标");
}
return conversionResult;
} catch (Exception e) {
long totalTime = System.currentTimeMillis() - startTime;
System.err.println("[JSON转XML] 转换失败,已耗时: " + totalTime + " ms错误: " + e.getMessage());
throw e;
if (conversionResult.getUnmatchedRuleDetails() != null && !conversionResult.getUnmatchedRuleDetails().isEmpty()) {
System.out.println("[JSON转XML] 警告: 有 " + conversionResult.getUnmatchedRuleDetails().size() + " 条规则未匹配到指标");
}
return conversionResult;
}
/**
* 将JSON字符串解析为MappingDocument对象
*/
public MappingDocument parseMappingJson(String mappingJson) throws Exception {
return objectMapper.readValue(mappingJson, MappingDocument.class);
}
/**
* 基于映射文档构建XML内容旧版方法仅返回XML内容
*/
private String buildXmlFromMapping(MappingDocument mappingDocument,
InputStream templateStream,
List<InputStream> ruleStreams,
@@ -116,42 +76,38 @@ public class JsonToXmlConversionService {
return buildXmlFromMappingWithResult(mappingDocument, templateStream, ruleStreams, indexMapping).getXmlContent();
}
private ConversionResult buildXmlFromMappingWithResult(MappingDocument mappingDocument,
/**
* 基于映射文档构建XML内容并返回完整结果
* 主要流程:读取模板 -> 填充IED信息 -> 填充报告控制块 -> 应用规则匹配
*/
public ConversionResult buildXmlFromMappingWithResult(MappingDocument mappingDocument,
InputStream templateStream,
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
long stepStart = System.currentTimeMillis();
// 读取XML模板内容为字符串
String templateContent = readInputStreamToString(templateStream);
long templateTime = System.currentTimeMillis();
System.out.println(" [步骤1] 读取模板完成,耗时: " + (templateTime - stepStart) + " ms");
// 填充IED名称和LD前缀信息到模板中
String xmlContent = fillIedInfo(templateContent, mappingDocument);
long iedTime = System.currentTimeMillis();
System.out.println(" [步骤2] 填充IED信息完成耗时: " + (iedTime - templateTime) + " ms");
// 填充报告控制块信息到XML中
xmlContent = fillReportControlsFromMapping(xmlContent, mappingDocument);
long reportTime = System.currentTimeMillis();
System.out.println(" [步骤3] 填充ReportControl完成耗时: " + (reportTime - iedTime) + " ms");
// 应用规则匹配将JSON中的指标数据映射到XML节点
RuleMatchingResult matchingResult = applyRulesFromMappingWithResult(xmlContent, mappingDocument, ruleStreams, indexMapping);
long rulesTime = System.currentTimeMillis();
System.out.println(" [步骤4] 应用规则完成,耗时: " + (rulesTime - reportTime) + " ms");
long totalStepTime = System.currentTimeMillis() - stepStart;
System.out.println(" [buildXmlFromMapping] 总计耗时: " + totalStepTime + " ms");
// 封装转换结果
ConversionResult result = new ConversionResult();
result.setXmlContent(matchingResult.getXmlContent());
result.setUnmatchedRuleDetails(matchingResult.getUnmatchedRuleDetails());
return result;
}
/**
* 将输入流读取为UTF-8编码的字符串
*/
private String readInputStreamToString(InputStream inputStream) throws Exception {
java.io.ByteArrayOutputStream result = new java.io.ByteArrayOutputStream();
byte[] buffer = new byte[1024];
@@ -162,6 +118,10 @@ public class JsonToXmlConversionService {
return result.toString(StandardCharsets.UTF_8.name());
}
/**
* 填充IED名称和LD前缀信息到XML模板中
* 通过正则表达式替换模板中的占位符
*/
private String fillIedInfo(String xmlContent, MappingDocument mappingDocument) {
if (mappingDocument == null) {
return xmlContent;
@@ -170,6 +130,7 @@ public class JsonToXmlConversionService {
String iedName = mappingDocument.getIed();
String ldPrefix = mappingDocument.getLd();
// 替换IED名称
if (iedName != null && !iedName.isEmpty()) {
xmlContent = xmlContent.replaceAll(
"<IED\\s+name=\"[^\"]*\"",
@@ -177,6 +138,7 @@ public class JsonToXmlConversionService {
);
}
// 替换LD前缀
if (ldPrefix != null && !ldPrefix.isEmpty()) {
xmlContent = xmlContent.replaceAll(
"<LDevice\\s+Prefix=\"[^\"]*\"",
@@ -187,6 +149,10 @@ public class JsonToXmlConversionService {
return xmlContent;
}
/**
* 从映射文档中提取报告控制块信息并填充到XML中
* 只处理特定类型的报告控制块如brcbFluc、brcbStHarm等
*/
private String fillReportControlsFromMapping(String xmlContent, MappingDocument mappingDocument) {
if (mappingDocument == null || mappingDocument.getReportMap() == null || mappingDocument.getReportMap().isEmpty()) {
return xmlContent;
@@ -195,10 +161,12 @@ public class JsonToXmlConversionService {
StringBuilder reportStatBuilder = new StringBuilder();
reportStatBuilder.append("\t\t<ReportStat>\n");
// 遍历报告映射项,构建报告控制块字符串
for (var reportItem : mappingDocument.getReportMap()) {
if (reportItem.getName() != null && !reportItem.getName().isEmpty()) {
String name = reportItem.getName();
// 只处理特定类型的报告控制块
if (name.contains("brcbFluc") ||
name.contains("brcbStHarm") ||
name.contains("brcbStIHarm") ||
@@ -219,6 +187,7 @@ public class JsonToXmlConversionService {
reportStatBuilder.append("\t\t</ReportStat>");
// 使用正则表达式替换XML中的ReportStat节点
xmlContent = xmlContent.replaceAll(
"<ReportStat>[\\s\\S]*?</ReportStat>",
reportStatBuilder.toString().replace("$", "\\$").replace("[", "\\[").replace("]", "\\]")
@@ -227,25 +196,32 @@ public class JsonToXmlConversionService {
return xmlContent;
}
/**
* 构建报告控制块字符串
* 格式LLN0$BR/RP$名称,超时时间,重传次数等参数
*/
private String buildReportControlString(ReportMapItem rc) {
StringBuilder sb = new StringBuilder();
// 构建报告控制块的基本路径LLN0$缓冲类型$名称
sb.append("LLN0$").append(rc.getBuffered() != null && rc.getBuffered().equals("BR") ? "BR$" : "RP$").append(rc.getName());
// 根据报告类型设置超时时间
if(rc.getName().contains("PLT") || rc.getName().contains("Flicker") || rc.getName().contains("PST") || rc.getName().contains("Fluc")){
sb.append(",600");
sb.append(",600"); // 闪变相关报告超时时间为600秒
}else{
sb.append(",60");
sb.append(",60"); // 其他报告超时时间为60秒
}
sb.append(",1");
sb.append(",0");
sb.append(",0");
sb.append(",0");
sb.append(",0");
sb.append(",yes");
sb.append(",1");
sb.append(",1");
sb.append(",1");
// 设置其他固定参数ReportControl:ID,RCBName,intgPd,dchg,qchg,dupd,period,gi,issuffixed,seqNum,timeStamp,reasonCode,dataSet,dataRef,bufOvfl,entryID,configRef,segmentation,FlickerFlag
sb.append(",1");// intgPd: 完整性周期(启用)
sb.append(",0"); // dchg: 数据变化触发(禁用)
sb.append(",0");// qchg: 品质变化触发(禁用)
sb.append(",0");// dupd: 数据更新触发(禁用)
sb.append(",0");// period: 周期性触发(禁用)
sb.append(",yes");// gi: 总召唤支持(启用)
sb.append(",1"); // issuffixed: 使用后缀命名(启用)
sb.append(",1");// seqNum: 包含序列号(启用)
sb.append(",1"); // timeStamp: 包含时间戳(启用)
sb.append(",1");
sb.append(",1");
sb.append(",0");
@@ -254,6 +230,7 @@ public class JsonToXmlConversionService {
sb.append(",1");
sb.append(",3");
// 根据报告类型设置最后一个参数
if(rc.getName().contains("PLT") || rc.getName().contains("Flicker")){
sb.append(",1");
}else if(rc.getName().contains("PST") || rc.getName().contains("Fluc")){
@@ -265,6 +242,9 @@ public class JsonToXmlConversionService {
return sb.toString();
}
/**
* 从映射文档应用规则到XML旧版方法
*/
private String applyRulesFromMapping(String xmlContent,
MappingDocument mappingDocument,
List<InputStream> ruleStreams,
@@ -273,48 +253,43 @@ public class JsonToXmlConversionService {
return applyRulesFromMappingWithResult(xmlContent, mappingDocument, ruleStreams, indexMapping).getXmlContent();
}
/**
* 从映射文档应用规则到XML并返回匹配结果
* 核心流程:合并规则 -> 提取指标 -> 匹配规则 -> 应用规则到XML
*/
private RuleMatchingResult applyRulesFromMappingWithResult(String xmlContent,
MappingDocument mappingDocument,
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
long stepStart = System.currentTimeMillis();
// 合并所有规则文件中的规则
var mergedRules = mergeAllRulesDesc(ruleStreams, indexMapping);
long mergeTime = System.currentTimeMillis();
System.out.println(" [规则合并] 耗时: " + (mergeTime - stepStart) + " ms规则数: " + mergedRules.size());
// 从映射文档中提取所有指标信息
var mappingMetrics = extractMetricsFromMapping(mappingDocument);
long extractTime = System.currentTimeMillis();
System.out.println(" [指标提取] 耗时: " + (extractTime - mergeTime) + " ms指标数: " + mappingMetrics.size());
System.out.println("========== 开始从JSON匹配规则 ==========");
System.out.println("规则总数: " + mergedRules.size());
System.out.println("JSON中指标总数: " + mappingMetrics.size());
// 执行规则匹配,获取匹配结果和未匹配规则详情
RuleMatchingResult matchingResult = findApplicableRulesDescWithUnmatched(mergedRules, mappingMetrics);
long matchTime = System.currentTimeMillis();
System.out.println(" [规则匹配] 耗时: " + (matchTime - extractTime) + " ms");
System.out.println("匹配成功: " + matchingResult.getApplicableRules().size() + " 条规则");
System.out.println("匹配失败: " + matchingResult.getUnmatchedRuleDetails().size() + " 条规则");
// 将匹配成功的规则应用到XML内容中
String resultXml = applyRulesToXml(xmlContent, matchingResult.getApplicableRules());
long applyTime = System.currentTimeMillis();
System.out.println(" [规则应用] 耗时: " + (applyTime - matchTime) + " ms");
long totalApplyTime = System.currentTimeMillis() - stepStart;
System.out.println(" [applyRulesFromMapping] 总计耗时: " + totalApplyTime + " ms");
System.out.println("========== JSON规则匹配结束 ==========\n");
matchingResult.setXmlContent(resultXml);
return matchingResult;
}
/**
* 合并所有规则文件中的规则按name+desc作为key
*/
private java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> mergeAllRules(
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
@@ -333,6 +308,9 @@ public class JsonToXmlConversionService {
return mergedRules;
}
/**
* 合并所有规则文件中的规则仅按desc作为key
*/
private java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> mergeAllRulesDesc(
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
@@ -351,6 +329,9 @@ public class JsonToXmlConversionService {
return mergedRules;
}
/**
* 解析单个规则文件按name+desc组合作为key
*/
private java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> parseRuleFile(
InputStream ruleStream,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
@@ -364,6 +345,7 @@ public class JsonToXmlConversionService {
String line;
String currentGroup = "";
// 定义Value标签的正则表达式模式用于解析规则文件中的<Value>节点
java.util.regex.Pattern valueRulePattern = java.util.regex.Pattern.compile(
"<Value\\s+" +
"name=\"([^\"]+)\"\\s+" +
@@ -381,6 +363,7 @@ public class JsonToXmlConversionService {
while ((line = reader.readLine()) != null) {
line = line.trim();
// 解析注释行确定当前规则所属的数据组MMXU、间谐波、MHAI等
if (line.startsWith("<!--")) {
if (line.contains("MMXU") || line.contains("基本数据")) {
currentGroup = "MMXU";
@@ -400,6 +383,7 @@ public class JsonToXmlConversionService {
continue;
}
// 跳过空行和非Value标签行
if (line.isEmpty() || !line.startsWith("<Value")) {
continue;
}
@@ -413,6 +397,7 @@ public class JsonToXmlConversionService {
String doPath = matcher.group(4) != null ? matcher.group(4) : "";
String daPath = matcher.group(5) != null ? matcher.group(5) : "";
// 如果配置了索引映射则应用索引映射转换DO路径
if (indexMapping != null && !doPath.isEmpty()) {
doPath = applyIndexMapping(doPath, currentGroup, indexMapping, rule.getName(), rule.getDesc());
}
@@ -424,6 +409,7 @@ public class JsonToXmlConversionService {
rule.setLimitDown(matcher.group(8));
rule.setCoefficient(matcher.group(9));
// 构建规则key并添加到规则集合中
String key = buildRuleKey(rule.getName(), rule.getDesc());
if (!key.isEmpty() && hasValidDoOrDa(rule)) {
rules.computeIfAbsent(key.toLowerCase(), k -> new ArrayList<>()).add(rule);
@@ -435,6 +421,9 @@ public class JsonToXmlConversionService {
return rules;
}
/**
* 解析单个规则文件仅按desc作为key用于描述匹配
*/
private java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> parseRuleFileDesc(
InputStream ruleStream,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
@@ -448,6 +437,7 @@ public class JsonToXmlConversionService {
String line;
String currentGroup = "";
// 定义Value标签的正则表达式模式用于解析规则文件中的<Value>节点
java.util.regex.Pattern valueRulePattern = java.util.regex.Pattern.compile(
"<Value\\s+" +
"name=\"([^\"]+)\"\\s+" +
@@ -465,6 +455,7 @@ public class JsonToXmlConversionService {
while ((line = reader.readLine()) != null) {
line = line.trim();
// 解析注释行,确定当前规则所属的数据组
if (line.startsWith("<!--")) {
if (line.contains("MMXU") || line.contains("基本数据")) {
currentGroup = "MMXU";
@@ -484,6 +475,7 @@ public class JsonToXmlConversionService {
continue;
}
// 跳过空行和非Value标签行
if (line.isEmpty() || !line.startsWith("<Value")) {
continue;
}
@@ -497,6 +489,7 @@ public class JsonToXmlConversionService {
String doPath = matcher.group(4) != null ? matcher.group(4) : "";
String daPath = matcher.group(5) != null ? matcher.group(5) : "";
// 如果配置了索引映射则应用索引映射转换DO路径
if (indexMapping != null && !doPath.isEmpty()) {
doPath = applyIndexMapping(doPath, currentGroup, indexMapping, rule.getName(), rule.getDesc());
}
@@ -508,6 +501,7 @@ public class JsonToXmlConversionService {
rule.setLimitDown(matcher.group(8));
rule.setCoefficient(matcher.group(9));
// 仅使用desc作为规则key
String key = rule.getDesc();
if (!key.isEmpty() && hasValidDoOrDa(rule)) {
rules.computeIfAbsent(key.toLowerCase(), k -> new ArrayList<>()).add(rule);
@@ -519,6 +513,10 @@ public class JsonToXmlConversionService {
return rules;
}
/**
* 应用索引映射将规则中的DO路径索引转换为实际使用的索引
* 例如将MMXU1转换为MMXU2根据用户选择的统计类型
*/
private String applyIndexMapping(String doPath, String currentGroup,
IcdToXmlMappingService.IndexMappingConfig indexMapping,
String name, String desc) {
@@ -526,7 +524,9 @@ public class JsonToXmlConversionService {
return doPath;
}
// 匹配带索引的逻辑节点名称如MMXU1、MHAI2等
java.util.regex.Pattern doIndexPattern = java.util.regex.Pattern.compile("(MMXU|MHAI|MSQI|MFLK|QVVR)(\\d+)");
// 匹配不带索引但以$结尾的逻辑节点名称
java.util.regex.Pattern doNoIndexPattern = java.util.regex.Pattern.compile("(MMXU|MHAI|MSQI|MFLK|QVVR)\\$");
java.util.regex.Matcher matcherWithIndex = doIndexPattern.matcher(doPath);
@@ -534,6 +534,7 @@ public class JsonToXmlConversionService {
String lnClass = matcherWithIndex.group(1);
int originalIndex = Integer.parseInt(matcherWithIndex.group(2));
// 根据统计类型平均值、最大值、最小值、95值确定新的索引
int newIndex = determineNewIndex(currentGroup, lnClass, originalIndex, indexMapping, name, desc);
if (newIndex >= 0) {
@@ -557,6 +558,10 @@ public class JsonToXmlConversionService {
return doPath;
}
/**
* 根据统计类型确定新的索引值
* 统计类型包括:平均值(avg)、最大值(max)、最小值(min)、95百分位值(g_)
*/
private int determineNewIndex(String currentGroup, String lnClass, int originalIndex,
IcdToXmlMappingService.IndexMappingConfig indexMapping,
String name, String desc) {
@@ -567,13 +572,15 @@ public class JsonToXmlConversionService {
String lowerName = (name != null ? name : "").toLowerCase();
String lowerDesc = (desc != null ? desc : "").toLowerCase();
// 判断统计类型
boolean isAverage = lowerName.contains("avg") || lowerDesc.contains("平均值");
boolean isMaximum = lowerName.startsWith("max_") || lowerDesc.contains("最大值");
boolean isMinimum = lowerName.startsWith("min_") || lowerDesc.contains("最小值");
boolean is95Percentile = lowerName.startsWith("g_") || lowerDesc.contains("95");
boolean is95Percentile = lowerName.startsWith("g_") || lowerDesc.contains("95");
boolean isInterHarmonic = lowerDesc.contains("间谐波");
// 根据不同的数据组和统计类型,从索引映射配置中获取对应的索引值
if ("MMXU".equals(currentGroup) || "MMXU".equals(lnClass)) {
IcdToXmlMappingService.IndexMappingConfig.StatIndex mmxu = indexMapping.getMmxu();
if (mmxu != null) {
@@ -635,6 +642,9 @@ public class JsonToXmlConversionService {
return originalIndex;
}
/**
* 构建规则key优先使用name如果name为空则使用desc
*/
private String buildRuleKey(String name, String desc) {
if (name != null && !name.isEmpty()) {
return name;
@@ -642,11 +652,19 @@ public class JsonToXmlConversionService {
return desc != null ? desc : "";
}
/**
* 检查规则是否有有效的DO或DA路径
*/
private boolean hasValidDoOrDa(RuleBasedXmlMappingService.ValueRule rule) {
return (rule.getDoPath() != null && !rule.getDoPath().isEmpty()) ||
(rule.getDaPath() != null && !rule.getDaPath().isEmpty());
}
/**
* 从映射文档中提取所有指标信息
* 遍历DataSetList -> InstList -> DoiList -> SdiList -> TypeList层级结构
* 构建以desc组合为key的指标映射表
*/
private java.util.Map<String, MetricInfo> extractMetricsFromMapping(MappingDocument mappingDocument) {
java.util.Map<String, MetricInfo> metrics = new java.util.HashMap<>();
@@ -654,19 +672,24 @@ public class JsonToXmlConversionService {
return metrics;
}
// 遍历数据集列表
for (var dataSetGroup : mappingDocument.getDataSetList()) {
if (dataSetGroup.getInstList() == null) continue;
// 遍历实例列表
for (var instItem : dataSetGroup.getInstList()) {
if (instItem.getDoiList() == null) continue;
// 遍历DOI列表
for (var doiItem : instItem.getDoiList()) {
String doPath = buildDoPath(dataSetGroup.getLnClass(), instItem.getInst(), doiItem.getName());
// 遍历SDI列表和Type列表构建完整的指标信息
if (doiItem.getSdiList() != null) {
for (var sdiItem : doiItem.getSdiList()) {
if (sdiItem.getTypeList() != null) {
for (var typeItem : sdiItem.getTypeList()) {
// 构建指标的唯一key基于desc组合
String fullKey = doiItem.getDesc() + instItem.getDesc()+typeItem.getDesc();
if(fullKey.contains("正序")){
fullKey = doiItem.getDesc() + instItem.getDesc()+sdiItem.getDesc()+typeItem.getDesc();
@@ -697,6 +720,9 @@ public class JsonToXmlConversionService {
return metrics;
}
/**
* 构建DO路径格式LN类名+LN实例+$MX+$DO名称
*/
private String buildDoPath(String lnClass, String lnInst, String doName) {
StringBuilder sb = new StringBuilder();
if (lnClass != null) {
@@ -716,22 +742,28 @@ public class JsonToXmlConversionService {
return sb.toString();
}
/**
* 构建DA路径处理相位名称标准化和谐波索引
*/
private String buildDaPath(String sdiName, String typeName, DoiItem doiItem) {
StringBuilder sb = new StringBuilder();
if (sdiName != null && !sdiName.isEmpty()) {
String normalizedSdi = normalizePhaseName(sdiName);
sb.append(normalizedSdi);
// 处理谐波数据的特殊索引格式
if (sdiName.endsWith("Har") && doiItem != null) {
int baseflag = doiItem.getBaseflag();
int start = doiItem.getStart();
if (baseflag == 1) {
// 间谐波使用特殊格式:[%-i]
boolean isInterHarmonic = isInterHarmonicMetric(doiItem);
int m = isInterHarmonic ? 0 : 2;
int i = m - start;
sb.append("[%-").append(i).append("]");
} else {
// 普通谐波使用格式:[start]
sb.append("[").append(start).append("]");
}
}
@@ -745,6 +777,9 @@ public class JsonToXmlConversionService {
return sb.toString();
}
/**
* 标准化相位名称将phsa、phsb、phsc统一为phs*
*/
private String normalizePhaseName(String name) {
if (name == null || name.isEmpty()) {
return name;
@@ -761,6 +796,9 @@ public class JsonToXmlConversionService {
return name;
}
/**
* 判断指标是否为间谐波类型
*/
private boolean isInterHarmonicMetric(DoiItem doiItem) {
if (doiItem == null || doiItem.getDesc() == null) {
return false;
@@ -768,6 +806,9 @@ public class JsonToXmlConversionService {
return doiItem.getDesc().toLowerCase().contains("间谐波");
}
/**
* 查找适用的规则(旧版方法)
*/
private java.util.Map<String, RuleBasedXmlMappingService.ValueRule> findApplicableRulesDesc(
java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> allRules,
java.util.Map<String, MetricInfo> mappingMetrics) {
@@ -775,6 +816,10 @@ public class JsonToXmlConversionService {
return findApplicableRulesDescWithUnmatched(allRules, mappingMetrics).getApplicableRules();
}
/**
* 查找适用的规则并返回未匹配规则详情
* 核心匹配逻辑:遍历所有规则,在指标映射表中查找匹配的指标
*/
private RuleMatchingResult findApplicableRulesDescWithUnmatched(
java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> allRules,
java.util.Map<String, MetricInfo> mappingMetrics) {
@@ -782,19 +827,23 @@ public class JsonToXmlConversionService {
java.util.Map<String, RuleBasedXmlMappingService.ValueRule> applicable = new java.util.HashMap<>();
List<UnmatchedRuleDetail> failedRuleDetails = new ArrayList<>();
// 遍历所有规则
for (var entry : allRules.entrySet()) {
String ruleKey = entry.getKey();
List<RuleBasedXmlMappingService.ValueRule> ruleVariants = entry.getValue();
boolean matched = false;
// 尝试匹配规则的任意一个候选变体
for (RuleBasedXmlMappingService.ValueRule rule : ruleVariants) {
if (rule.getName() == null || rule.getDoPath().isEmpty()) {
continue;
}
// 直接通过name在指标映射表中查找
if(mappingMetrics.containsKey(rule.getName())){
MetricInfo metric = mappingMetrics.get(rule.getName());
// 将匹配到的指标的DO和DA路径赋值给规则
rule.setDoPath(metric.getDoPath());
rule.setDaPath(metric.getDaPath());
applicable.put(ruleKey, rule);
@@ -807,6 +856,7 @@ public class JsonToXmlConversionService {
}
}
// 如果所有候选都未匹配,记录未匹配规则详情
if (!matched) {
UnmatchedRuleDetail detail = new UnmatchedRuleDetail();
detail.setRuleKey(ruleKey);
@@ -856,6 +906,9 @@ public class JsonToXmlConversionService {
return result;
}
/**
* 查找适用的规则(旧版方法,使用路径匹配)
*/
private java.util.Map<String, RuleBasedXmlMappingService.ValueRule> findApplicableRules(
java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> allRules,
java.util.Map<String, MetricInfo> mappingMetrics) {
@@ -877,6 +930,7 @@ public class JsonToXmlConversionService {
String ruleDo = normalizePath(rule.getDoPath());
String ruleDa = normalizePath(rule.getDaPath());
// 遍历所有指标,尝试通过路径模式匹配
for (var metricEntry : mappingMetrics.entrySet()) {
MetricInfo metric = metricEntry.getValue();
@@ -919,16 +973,24 @@ public class JsonToXmlConversionService {
return applicable;
}
/**
* 标准化路径字符串,转为小写并去除首尾空格
*/
private String normalizePath(String path) {
if (path == null) return "";
return path.trim().toLowerCase();
}
/**
* 判断路径是否匹配模式
* 支持通配符匹配,忽略数组索引部分
*/
private boolean matchesPattern(String pattern, String actual) {
if (pattern == null || actual == null) {
return pattern == null && actual == null;
}
// 移除数组索引部分(如[1]、[%-0]等)后再比较
String normalizedPattern = pattern.replaceAll("\\[[^\\]]*\\]", "");
if (normalizedPattern.isEmpty() || actual.isEmpty()) {
@@ -938,9 +1000,14 @@ public class JsonToXmlConversionService {
return actual.equals(normalizedPattern);
}
/**
* 将匹配的规则应用到XML内容中
* 通过正则表达式查找XML中的<Value>节点,并用匹配的规则替换
*/
private String applyRulesToXml(String xmlContent,
java.util.Map<String, RuleBasedXmlMappingService.ValueRule> applicableRules) {
// 定义XML Value标签的正则表达式模式
java.util.regex.Pattern xmlValuePattern = java.util.regex.Pattern.compile(
"<Value\\s+" +
"name=\"([^\"]+)\"\\s+" +
@@ -958,13 +1025,16 @@ public class JsonToXmlConversionService {
java.util.regex.Matcher matcher = xmlValuePattern.matcher(xmlContent);
StringBuffer result = new StringBuffer();
// 遍历所有匹配的Value节点
while (matcher.find()) {
String name = matcher.group(1);
String desc = matcher.group(2);
String type = matcher.group(3);
// 根据desc查找匹配的规则
RuleBasedXmlMappingService.ValueRule matchedRule = applicableRules.get(desc);
if (matchedRule != null) {
// 构建新的Value节点字符串使用规则中的DO、DA等信息
StringBuilder valueNode = new StringBuilder("<Value ");
valueNode.append("name=\"").append(escapeXml(name)).append("\" ");
valueNode.append("desc=\"").append(escapeXml(desc)).append("\" ");
@@ -998,6 +1068,9 @@ public class JsonToXmlConversionService {
return result.toString();
}
/**
* XML特殊字符转义
*/
private String escapeXml(String text) {
if (text == null) return "";
return text.replace("&", "&amp;")
@@ -1007,6 +1080,9 @@ public class JsonToXmlConversionService {
.replace("'", "&apos;");
}
/**
* 指标信息内部类存储从JSON中提取的指标数据
*/
static class MetricInfo {
private String lnClass;
private String lnInst;
@@ -1060,6 +1136,9 @@ public class JsonToXmlConversionService {
}
}
/**
* 规则匹配结果类,包含匹配成功的规则和未匹配的规则详情
*/
public static class RuleMatchingResult {
private java.util.Map<String, RuleBasedXmlMappingService.ValueRule> applicableRules;
private List<UnmatchedRuleDetail> unmatchedRuleDetails;
@@ -1090,6 +1169,9 @@ public class JsonToXmlConversionService {
}
}
/**
* 转换结果类包含生成的XML内容和未匹配的规则详情
*/
public static class ConversionResult {
private String xmlContent;
private List<UnmatchedRuleDetail> unmatchedRuleDetails;
@@ -1111,6 +1193,9 @@ public class JsonToXmlConversionService {
}
}
/**
* 未匹配规则详情类,记录匹配失败的规则信息
*/
public static class UnmatchedRuleDetail {
private String ruleKey;
private String ruleVariants;

View File

@@ -1,31 +1,26 @@
package com.njcn.gather.icd.mapping.component;
import com.njcn.gather.icd.mapping.config.MappingModuleConfig;
import com.njcn.gather.icd.mapping.pojo.bo.icd.*;
import com.njcn.gather.icd.mapping.utils.XmlTemplateParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.njcn.gather.icd.mapping.pojo.dto.*;
import com.njcn.gather.icd.mapping.pojo.bo.*;
import com.njcn.gather.icd.mapping.pojo.param.*;
import com.njcn.gather.icd.mapping.pojo.vo.*;
import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus;
@Component
public class RuleBasedXmlMappingService {
@Autowired
private IcdParserService icdParserService;
@Autowired
private MappingModuleConfig mappingModuleConfig;
private static final Pattern VALUE_RULE_PATTERN = Pattern.compile(
"<Value\\s+" +
"name=\"([^\"]+)\"\\s+" +
@@ -57,21 +52,28 @@ public class RuleBasedXmlMappingService {
private static final Pattern DO_INDEX_PATTERN = Pattern.compile("(MMXU|MHAI|MSQI|MFLK|QVVR)(\\d+)");
private static final Pattern DO_NO_INDEX_PATTERN = Pattern.compile("(MMXU|MHAI|MSQI|MFLK|QVVR)\\$");
public InputStream loadDefaultXmlFile( ) throws Exception {
ClassPathResource templateResource = new ClassPathResource("template/JiangSu_Config2.xml");
/**
* 加载JSON转XML使用的默认XML模板文件
* 路径通过MappingModuleConfig配置默认为template/JiangSu_Config2.xml
*/
public InputStream loadDefaultXmlFile() throws Exception {
ClassPathResource templateResource = new ClassPathResource(mappingModuleConfig.getJsonToXmlTemplatePath());
if (!templateResource.exists()) {
return null;
}
return templateResource.getInputStream();
}
public List<InputStream> loadDefaultRuleFile( ) throws Exception {
/**
* 加载JSON转XML使用的默认规则文件
* 路径通过MappingModuleConfig配置默认为template/默认规则.txt
*/
public List<InputStream> loadDefaultRuleFile() throws Exception {
ClassPathResource ruleResource = new ClassPathResource("template/默认规则.txt");
if ( !ruleResource.exists()) {
ClassPathResource ruleResource = new ClassPathResource(mappingModuleConfig.getJsonToXmlRulePath());
if (!ruleResource.exists()) {
return null;
}
@@ -81,117 +83,6 @@ public class RuleBasedXmlMappingService {
return ruleStreams;
}
public String generateWithRuleFiles(IcdDocument icdDocument,
InputStream templateStream,
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
Map<String, List<ValueRule>> mergedRules = mergeAllRules(ruleStreams, indexMapping);
Map<String, IcdMetricInfo> icdMetrics = extractIcdMetrics(icdDocument);
Map<String, ValueRule> applicableRules = findApplicableRules(mergedRules, icdMetrics);
XmlTemplateParser.ParseResult parseResult = new XmlTemplateParser().parse(templateStream);
String xmlContent = parseResult.getTemplateContent();
xmlContent = fillIedInfo(xmlContent, icdDocument);
xmlContent = fillReportControls(xmlContent, icdDocument);
xmlContent = applyRulesToXml(xmlContent, applicableRules);
Path tempPath = Files.createTempFile("rule_mapped_", ".xml");
Files.write(tempPath, xmlContent.getBytes(StandardCharsets.UTF_8));
return tempPath.toString();
}
private Map<String, List<ValueRule>> mergeAllRules(List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
Map<String, List<ValueRule>> mergedRules = new LinkedHashMap<>();
for (InputStream ruleStream : ruleStreams) {
Map<String, List<ValueRule>> rules = parseRuleFile(ruleStream, indexMapping);
for (Map.Entry<String, List<ValueRule>> entry : rules.entrySet()) {
String key = entry.getKey();
List<ValueRule> ruleList = entry.getValue();
mergedRules.computeIfAbsent(key, k -> new ArrayList<>()).addAll(ruleList);
}
}
return mergedRules;
}
private Map<String, List<ValueRule>> parseRuleFile(InputStream ruleStream,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
Map<String, List<ValueRule>> rules = new HashMap<>();
BufferedReader reader = new BufferedReader(new InputStreamReader(ruleStream, StandardCharsets.UTF_8));
String line;
String currentGroup = "";
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("<!--")) {
if (line.contains("MMXU") || line.contains("基本数据")) {
currentGroup = "MMXU";
} else if (line.contains("间谐波")) {
currentGroup = "INTER_HARMONIC";
} else if (line.contains("MHAI") && line.contains("谐波数据")) {
currentGroup = "MHAI";
} else if (line.contains("MSQI") || line.contains("序分量")) {
currentGroup = "MSQI";
} else if (line.contains("短闪") || line.contains("短时闪变")) {
currentGroup = "MFLK_SHORT";
} else if (line.contains("长闪") || line.contains("长时闪变")) {
currentGroup = "MFLK_LONG";
} else if (line.contains("QVVR") || line.contains("电压变动")) {
currentGroup = "QVVR";
}
continue;
}
if (line.isEmpty() || !line.startsWith("<Value")) {
continue;
}
Matcher matcher = VALUE_RULE_PATTERN.matcher(line);
if (matcher.find()) {
ValueRule rule = new ValueRule();
rule.setName(matcher.group(1));
rule.setDesc(matcher.group(2));
rule.setType(matcher.group(3));
String doPath = matcher.group(4) != null ? matcher.group(4) : "";
String daPath = matcher.group(5) != null ? matcher.group(5) : "";
if (indexMapping != null && !doPath.isEmpty()) {
doPath = applyIndexMapping(doPath, currentGroup, indexMapping, rule.getName(), rule.getDesc());
}
rule.setDoPath(doPath);
rule.setDaPath(daPath);
rule.setBaseFlag(matcher.group(6));
rule.setLimitUp(matcher.group(7));
rule.setLimitDown(matcher.group(8));
rule.setCoefficient(matcher.group(9));
String key = buildRuleKey(rule.getName(), rule.getDesc());
if (!key.isEmpty() && hasValidDoOrDa(rule)) {
rules.computeIfAbsent(key.toLowerCase(), k -> new ArrayList<>()).add(rule);
}
}
}
reader.close();
return rules;
}
private String applyIndexMapping(String doPath, String currentGroup,
IcdToXmlMappingService.IndexMappingConfig indexMapping,
String name, String desc) {
@@ -229,85 +120,6 @@ public class RuleBasedXmlMappingService {
return doPath;
}
private int determineNewIndexByName(String currentGroup, String lnClass,
IcdToXmlMappingService.IndexMappingConfig indexMapping,
String name, String desc) {
if (indexMapping == null || name == null) {
return -1;
}
String upperName = name.toUpperCase();
String lowerDesc = (desc != null ? desc : "").toLowerCase();
boolean isMaximum = upperName.startsWith("MAX_");
boolean isMinimum = upperName.startsWith("MIN_");
boolean is95Percentile = upperName.startsWith("G_");
boolean isAverage = !isMaximum && !isMinimum && !is95Percentile;
// 判断是否为间谐波指标:通过desc是否包含"间谐波"
boolean isInterHarmonic = lowerDesc.contains("间谐波");
if ("MMXU".equals(currentGroup) || "MMXU".equals(lnClass)) {
IcdToXmlMappingService.IndexMappingConfig.StatIndex mmxu = indexMapping.getMmxu();
if (mmxu != null) {
if (isAverage && mmxu.getAverage() != null) return mmxu.getAverage();
if (isMaximum && mmxu.getMaximum() != null) return mmxu.getMaximum();
if (isMinimum && mmxu.getMinimum() != null) return mmxu.getMinimum();
if (is95Percentile && mmxu.getPercentile95() != null) return mmxu.getPercentile95();
}
}
if ("INTER_HARMONIC".equals(currentGroup) || isInterHarmonic) {
IcdToXmlMappingService.IndexMappingConfig.StatIndex interHarmonic = indexMapping.getInterHarmonic();
if (interHarmonic != null) {
if (isAverage && interHarmonic.getAverage() != null) return interHarmonic.getAverage();
if (isMaximum && interHarmonic.getMaximum() != null) return interHarmonic.getMaximum();
if (isMinimum && interHarmonic.getMinimum() != null) return interHarmonic.getMinimum();
if (is95Percentile && interHarmonic.getPercentile95() != null) return interHarmonic.getPercentile95();
}
}
if ("MHAI".equals(currentGroup) && !isInterHarmonic) {
IcdToXmlMappingService.IndexMappingConfig.StatIndex mhai = indexMapping.getMhai();
if (mhai != null) {
if (isAverage && mhai.getAverage() != null) return mhai.getAverage();
if (isMaximum && mhai.getMaximum() != null) return mhai.getMaximum();
if (isMinimum && mhai.getMinimum() != null) return mhai.getMinimum();
if (is95Percentile && mhai.getPercentile95() != null) return mhai.getPercentile95();
}
}
if ("MSQI".equals(currentGroup) || "MSQI".equals(lnClass)) {
IcdToXmlMappingService.IndexMappingConfig.StatIndex msqi = indexMapping.getMsqi();
if (msqi != null) {
if (isAverage && msqi.getAverage() != null) return msqi.getAverage();
if (isMaximum && msqi.getMaximum() != null) return msqi.getMaximum();
if (isMinimum && msqi.getMinimum() != null) return msqi.getMinimum();
if (is95Percentile && msqi.getPercentile95() != null) return msqi.getPercentile95();
}
}
if ("MFLK_SHORT".equals(currentGroup) || "MFLK".equals(lnClass)) {
if (indexMapping.getMflkShort() != null) {
return indexMapping.getMflkShort();
}
}
if ("MFLK_LONG".equals(currentGroup)) {
if (indexMapping.getMflkLong() != null) {
return indexMapping.getMflkLong();
}
}
if ("QVVR".equals(currentGroup) || "QVVR".equals(lnClass)) {
if (indexMapping.getQvvr() != null) {
return indexMapping.getQvvr();
}
}
return -1;
}
private int determineNewIndex(String currentGroup, String lnClass, int originalIndex,
IcdToXmlMappingService.IndexMappingConfig indexMapping,
String name, String desc) {
@@ -387,40 +199,6 @@ public class RuleBasedXmlMappingService {
return originalIndex;
}
private Map<String, IcdMetricInfo> extractIcdMetrics(IcdDocument icdDocument) {
Map<String, IcdMetricInfo> metrics = new HashMap<>();
if (icdDocument == null || icdDocument.getLogicalNodes() == null) {
return metrics;
}
for (LnNode lnNode : icdDocument.getLogicalNodes()) {
if (lnNode.getDoiList() == null) continue;
for (DoiNode doiNode : lnNode.getDoiList()) {
String doPath = buildDoPath(lnNode, doiNode);
List<String> daPaths = collectAllDaPaths(doiNode.getChildren(), new ArrayList<>());
for (String daPath : daPaths) {
String fullKey = buildMetricKey(doPath, daPath);
IcdMetricInfo info = new IcdMetricInfo();
info.setLnClass(lnNode.getLnClass());
info.setLnInst(lnNode.getLnInst());
info.setDoName(doiNode.getName());
info.setDoPath(doPath);
info.setDaPath(daPath);
info.setPhase(extractPhase(daPath));
info.setType(extractType(daPath));
metrics.put(fullKey.toLowerCase(), info);
}
}
}
return metrics;
}
private List<String> collectAllDaPaths(List<DoiElementNode> nodes, List<String> currentPath) {
List<String> results = new ArrayList<>();
if (nodes == null) return results;
@@ -490,87 +268,6 @@ public class RuleBasedXmlMappingService {
return sb.toString();
}
private String buildMetricKey(String doPath, String daPath) {
return doPath + "|" + daPath;
}
private Map<String, ValueRule> findApplicableRules(Map<String, List<ValueRule>> allRules,
Map<String, IcdMetricInfo> icdMetrics) {
Map<String, ValueRule> applicable = new HashMap<>();
System.out.println("========== 开始匹配规则 ==========");
System.out.println("规则总数: " + allRules.size());
System.out.println("ICD指标总数: " + icdMetrics.size());
int matchedCount = 0;
int unmatchedCount = 0;
for (Map.Entry<String, List<ValueRule>> entry : allRules.entrySet()) {
String ruleKey = entry.getKey();
List<ValueRule> ruleVariants = entry.getValue();
boolean foundInIcd = false;
ValueRule matchedRule = null;
System.out.println("\n正在匹配规则: " + ruleKey + " (共" + ruleVariants.size() + "个候选)");
for (int i = 0; i < ruleVariants.size(); i++) {
ValueRule rule = ruleVariants.get(i);
if (rule.getDoPath() == null || rule.getDoPath().isEmpty()) {
System.out.println(" 候选[" + (i+1) + "] 跳过: DO为空");
continue;
}
String ruleDo = normalizePath(rule.getDoPath());
String ruleDa = normalizePath(rule.getDaPath());
System.out.println(" 尝试候选[" + (i+1) + "]: DO=" + rule.getDoPath() + " DA=" + rule.getDaPath());
for (Map.Entry<String, IcdMetricInfo> icdEntry : icdMetrics.entrySet()) {
String icdKey = icdEntry.getKey();
IcdMetricInfo icdMetric = icdEntry.getValue();
String icdDo = normalizePath(icdMetric.getDoPath());
String icdDa = normalizePath(icdMetric.getDaPath());
boolean doMatch = matchesPattern(ruleDo, icdDo);
boolean daMatch = matchesPattern(ruleDa, icdDa);
if (doMatch && daMatch) {
System.out.println(" ✓ 匹配成功! ICD Key: " + icdKey);
System.out.println(" ICD DO=" + icdMetric.getDoPath() + " DA=" + icdMetric.getDaPath());
foundInIcd = true;
matchedRule = rule;
break;
}
}
if (foundInIcd) {
System.out.println(" >> 规则 " + ruleKey + " 匹配成功,使用候选[" + (i+1) + "]");
break;
}
}
if (foundInIcd && matchedRule != null) {
applicable.put(ruleKey, matchedRule);
matchedCount++;
} else {
unmatchedCount++;
if (unmatchedCount <= 15) {
System.out.println("✗ 未匹配: " + ruleKey + " (共" + ruleVariants.size() + "个候选)");
for (ValueRule rule : ruleVariants) {
System.out.println(" 候选: DO=" + rule.getDoPath() + " DA=" + rule.getDaPath());
}
}
}
}
System.out.println("\n========== 规则匹配结束 ==========");
System.out.println("匹配成功: " + matchedCount + " 条规则");
System.out.println("未匹配: " + unmatchedCount + " 条规则");
return applicable;
}
private String normalizePath(String path) {
if (path == null) return "";
@@ -604,49 +301,6 @@ public class RuleBasedXmlMappingService {
return xmlContent;
}
private String fillReportControls(String xmlContent, IcdDocument icdDocument) {
if (icdDocument == null || icdDocument.getReportControls() == null || icdDocument.getReportControls().isEmpty()) {
return xmlContent;
}
// 构建 ReportStat 内容
StringBuilder reportStatBuilder = new StringBuilder();
reportStatBuilder.append("\t\t<ReportStat>\n");
for (ReportControlNode rc : icdDocument.getReportControls()) {
if (rc.getName() != null && !rc.getName().isEmpty()) {
String name = rc.getName();
// 只处理特定的 ReportControl 名称
if (name.contains("brcbFluc") ||
name.contains("brcbStHarm") ||
name.contains("brcbStIHarm") ||
name.contains("brcbStMMXU") ||
name.contains("brcbStMSQI") ||
name.contains("brcbPLT")||
name.contains("brcbPST")||
name.contains("brcbFlicker")||
name.contains("brcbStatistic") ){
// 构建 ReportControl 字符串格式:
// LLN0$BR$brcbFlickerData,600,0,0,1,0,0,yes,1,1,1,1,1,0,1,1,1,3,1
String reportControlStr = buildReportControlString(rc);
reportStatBuilder.append("\t\t\t<Report ReportControl=\"")
.append(escapeXml(reportControlStr))
.append("\" />\n");
}
}
}
reportStatBuilder.append("\t\t</ReportStat>");
// 替换原有的 <ReportStat>...</ReportStat> 部分
xmlContent = xmlContent.replaceAll(
"<ReportStat>[\\s\\S]*?</ReportStat>",
reportStatBuilder.toString().replace("$", "\\$").replace("[", "\\[").replace("]", "\\]")
);
return xmlContent;
}
private String buildReportControlString(ReportControlNode rc) {
// 格式LLN0$BR$brcbName,intgPd,dchg,qchg,dupd,period,gi,issuffixed,seqNum,timeStamp,reasonCode,dataSet,dataRef,bufOvfl,entryID,configRef,segmentation,FlickerFlag
@@ -704,51 +358,6 @@ public class RuleBasedXmlMappingService {
return actual.equals(normalizedPattern);
}
private String applyRulesToXml(String xmlContent, Map<String, ValueRule> applicableRules) {
Matcher matcher = XML_VALUE_PATTERN.matcher(xmlContent);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String name = matcher.group(1);
String desc = matcher.group(2);
String type = matcher.group(3);
String key = buildRuleKey(name, desc);
ValueRule matchedRule = applicableRules.get(key.toLowerCase());
if (matchedRule != null) {
StringBuilder valueNode = new StringBuilder("<Value ");
valueNode.append("name=\"").append(escapeXml(name)).append("\" ");
valueNode.append("desc=\"").append(escapeXml(desc)).append("\" ");
valueNode.append("type=\"").append(type).append("\" ");
if ((matchedRule.getDoPath() != null) && !matchedRule.getDoPath().isEmpty()) {
valueNode.append("DO=\"").append(escapeXml(matchedRule.getDoPath())).append("\" ");
}
if (matchedRule.getDaPath() != null && !matchedRule.getDaPath().isEmpty()) {
valueNode.append("DA=\"").append(escapeXml(matchedRule.getDaPath())).append("\" ");
}
if (matchedRule.getBaseFlag() != null && !matchedRule.getBaseFlag().isEmpty()) {
valueNode.append("BaseFlag=\"").append(escapeXml(matchedRule.getBaseFlag())).append("\" ");
}
if (matchedRule.getLimitUp() != null && !matchedRule.getLimitUp().isEmpty()) {
valueNode.append("LimitUp=\"").append(escapeXml(matchedRule.getLimitUp())).append("\" ");
}
if (matchedRule.getLimitDown() != null && !matchedRule.getLimitDown().isEmpty()) {
valueNode.append("LimitDown=\"").append(escapeXml(matchedRule.getLimitDown())).append("\" ");
}
if (matchedRule.getCoefficient() != null && !matchedRule.getCoefficient().isEmpty()) {
valueNode.append("Coefficient=\"").append(escapeXml(matchedRule.getCoefficient())).append("\" ");
}
valueNode.append("/>");
matcher.appendReplacement(result, Matcher.quoteReplacement(valueNode.toString()));
}
}
matcher.appendTail(result);
return result.toString();
}
private String buildRuleKey(String name, String desc) {
if (name != null && !name.isEmpty()) {
@@ -825,28 +434,4 @@ public class RuleBasedXmlMappingService {
public void setCoefficient(String coefficient) { this.coefficient = coefficient; }
}
static class IcdMetricInfo {
private String lnClass;
private String lnInst;
private String doName;
private String doPath;
private String daPath;
private String phase;
private String type;
public String getLnClass() { return lnClass; }
public void setLnClass(String lnClass) { this.lnClass = lnClass; }
public String getLnInst() { return lnInst; }
public void setLnInst(String lnInst) { this.lnInst = lnInst; }
public String getDoName() { return doName; }
public void setDoName(String doName) { this.doName = doName; }
public String getDoPath() { return doPath; }
public void setDoPath(String doPath) { this.doPath = doPath; }
public String getDaPath() { return daPath; }
public void setDaPath(String daPath) { this.daPath = daPath; }
public String getPhase() { return phase; }
public void setPhase(String phase) { this.phase = phase; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
}
}

View File

@@ -26,4 +26,12 @@ public class MappingModuleConfig {
/** 默认输出目录。 */
@Value("${icd.mapping.default-output-dir:}")
private String defaultOutputDir;
/** JSON转XML使用的默认XML模板路径。 */
@Value("${icd.mapping.json-to-xml-template-path:template/JiangSu_Config2.xml}")
private String jsonToXmlTemplatePath;
/** JSON转XML使用的默认规则文件路径。 */
@Value("${icd.mapping.json-to-xml-rule-path:template/默认规则.txt}")
private String jsonToXmlRulePath;
}

View File

@@ -12,14 +12,12 @@ import java.util.List;
public class IcdToXmlGenerateResult {
private GenerateStatus status;
private String message;
private String iedName;
private String ldInst;
private IndexAnalysis indexAnalysis;
private MappingDocument mappingDocument;
private String savedPath;
//private String iedName;
//private String ldInst;
private List<String> problems = new ArrayList<String>();
/** 生成成功后的 Xml 字符串。 */
private String mappingXml;
private MappingDocument mappingDocument;
public String getMappingXml() {
return mappingXml;
}
@@ -34,16 +32,13 @@ public class IcdToXmlGenerateResult {
public void setStatus(GenerateStatus status) { this.status = status; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getIedName() { return iedName; }
public void setIedName(String iedName) { this.iedName = iedName; }
public String getLdInst() { return ldInst; }
public void setLdInst(String ldInst) { this.ldInst = ldInst; }
public IndexAnalysis getIndexAnalysis() { return indexAnalysis; }
public void setIndexAnalysis(IndexAnalysis indexAnalysis) { this.indexAnalysis = indexAnalysis; }
// public String getIedName() { return iedName; }
// public void setIedName(String iedName) { this.iedName = iedName; }
// public String getLdInst() { return ldInst; }
// public void setLdInst(String ldInst) { this.ldInst = ldInst; }
public MappingDocument getMappingDocument() { return mappingDocument; }
public void setMappingDocument(MappingDocument mappingDocument) { this.mappingDocument = mappingDocument; }
public String getSavedPath() { return savedPath; }
public void setSavedPath(String savedPath) { this.savedPath = savedPath; }
public List<String> getProblems() { return problems; }
public void setProblems(List<String> problems) { this.problems = problems; }
}

View File

@@ -28,6 +28,8 @@ public class IcdToXmlTaskAppService {
private final IcdToXmlMappingService icdToXmlMappingService;
private final JsonToXmlConversionService jsonToXmlConversionService;
public IcdToXmlTaskAppService(IcdParserService icdParserService,
DefaultTemplateLoader defaultTemplateLoader,
IndexAnalysisService indexAnalysisService,
@@ -50,96 +52,6 @@ public class IcdToXmlTaskAppService {
this.jsonToXmlConversionService = jsonToXmlConversionService;
}
public IcdToXmlGenerateResult generateFromIcd(IcdToXmlGenerateCommand command) {
IcdToXmlGenerateResult result = new IcdToXmlGenerateResult();
try {
// ========== 第一步ICD → JSON ==========
// 1. 解析 ICD
IcdDocument icdDocument = icdParserService.parse(command.getFileBytes(), command.getFileName());
result.setIedName(icdDocument.getIedName());
result.setLdInst(icdDocument.getLdInst());
// 2. 加载 DefaultCfg.txt和默认配置
DefaultTemplate template = defaultTemplateLoader.load();
result.getProblems().addAll(template.verify());
InputStream templateStream = ruleBasedXmlMappingService.loadDefaultXmlFile();
if(templateStream==null){result.getProblems().add("缺少默认xml配置文件");}
List<InputStream> ruleStreams = ruleBasedXmlMappingService.loadDefaultRuleFile();
if(ruleStreams==null){result.getProblems().add("缺少默认规则配置文件");}
// 3. 分析索引候选
IndexAnalysis indexAnalysis = indexAnalysisService.analyze(icdDocument, template);
result.setIndexAnalysis(indexAnalysis);
result.getProblems().addAll(indexAnalysis.getProblems());
// 4. 如果没有提交任何绑定关系,则直接返回待匹配项
if (command.getIndexSelection() == null || command.getIndexSelection().isEmpty()) {
result.setStatus(GenerateStatus.NEED_INDEX_SELECTION);
result.setMessage("索引配置缺失或不合法,请根据候选信息完成标签与数字索引的绑定后重新提交");
return result;
}
// 5. 校验用户提交的绑定关系
ValidationResult validationResult = indexValidationService.validate(indexAnalysis, command.getIndexSelection());
if (!validationResult.isValid()) {
result.setStatus(GenerateStatus.NEED_INDEX_SELECTION);
result.setMessage("索引配置缺失或不合法,请根据候选信息完成标签与数字索引的绑定后重新提交");
result.getProblems().addAll(validationResult.getProblems());
return result;
}
// 6. 生成正式映射结构JSON中间态
MappingDocument mappingDocument = mappingGenerationService.generate(
icdDocument,
template,
indexAnalysis,
command.getIndexSelection(),
command.getVersion(),
command.getAuthor()
);
result.setMappingDocument(mappingDocument);
// 7. 序列化为JSON字符串中间产物
String mappingJson = mappingDocumentSerializer.toPrettyJson(mappingDocument);
// ========== 第二步JSON → XML ==========
// 8. 重新绑定正确索引
IcdToXmlMappingService.IndexMappingConfig mappingConfig = icdToXmlMappingService.buildIndexMappingFromSelection(command.getIndexSelection());
icdToXmlMappingService.setIndexMapping(mappingConfig);
// 9. 从JSON转换为XML带未匹配规则信息
JsonToXmlConversionService.ConversionResult conversionResult =
jsonToXmlConversionService.convertFromJsonWithResult(
mappingJson,
templateStream,
ruleStreams,
icdToXmlMappingService.getIndexMapping()
);
result.setSavedPath(conversionResult.getXmlContent());
// 10. 将未匹配的规则详细信息添加到 problems 中
if (conversionResult.getUnmatchedRuleDetails() != null && !conversionResult.getUnmatchedRuleDetails().isEmpty()) {
for (JsonToXmlConversionService.UnmatchedRuleDetail detail : conversionResult.getUnmatchedRuleDetails()) {
String problemMsg = "规则匹配失败: " + detail.getRuleKey() + " [" + detail.getRuleVariants() + "]";
result.getProblems().add(problemMsg);
}
}
result.setStatus(GenerateStatus.SUCCESS);
result.setMessage("映射生成成功");
return result;
} catch (Exception ex) {
result.setStatus(GenerateStatus.FAILED);
result.setMessage("映射生成失败:" + ex.getMessage());
result.getProblems().add(ex.getMessage());
return result;
}
}
/**
* 直接从 JSON 字符串生成 XML 文件。
*/
@@ -163,27 +75,30 @@ public class IcdToXmlTaskAppService {
return result;
}
// 2.JSON 转换为 XML带未匹配规则信息
JsonToXmlConversionService.ConversionResult conversionResult =
jsonToXmlConversionService.convertFromJsonWithResult(
mappingJson,
templateStream,
ruleStreams,
icdToXmlMappingService.getIndexMapping()
);
// 2.JSON字符串反序列化为MappingDocument对象
MappingDocument mappingDocument = jsonToXmlConversionService.parseMappingJson(mappingJson);
// 3.基于映射文档构建XML内容并获取匹配结果
JsonToXmlConversionService.ConversionResult conversionResult = jsonToXmlConversionService.convertFromJsonWithResult(mappingDocument, templateStream, ruleStreams, icdToXmlMappingService.getIndexMapping());
result.setMappingDocument(mappingDocument);
//result.setLdInst(mappingDocument.getLd());
result.setMappingXml(conversionResult.getXmlContent());
// 3. 将未匹配的规则详细信息添加到 problems 中
// 4. 将未匹配的规则详细信息添加到 problems 中
if (conversionResult.getUnmatchedRuleDetails() != null && !conversionResult.getUnmatchedRuleDetails().isEmpty()) {
for (JsonToXmlConversionService.UnmatchedRuleDetail detail : conversionResult.getUnmatchedRuleDetails()) {
String problemMsg = "规则匹配失败: " + detail.getRuleKey() + " [" + detail.getRuleVariants() + "]";
String problemMsg = "规则匹配失败指标: " + detail.getRuleKey() ;
result.getProblems().add(problemMsg);
}
}
result.setMessage("XML 生成成功,但存在未成功匹配指标");
}
else{
result.setMessage("XML 生成成功");
}
result.setStatus(GenerateStatus.SUCCESS);
result.setMessage("XML 生成成功");
return result;
} catch (Exception ex) {
result.setStatus(GenerateStatus.FAILED);

View File

@@ -166,21 +166,21 @@ public class JsonToXmlDebugRunner {
}
}
if (result.getStatus() == GenerateStatus.SUCCESS) {
System.out.println();
System.out.println("----- 生成的 XML 内容 -----");
String xmlContent = result.getMappingXml();
if (PRETTY_XML) {
// 简单格式化:每个标签独占一行
xmlContent = formatXml(xmlContent);
}
System.out.println(xmlContent);
System.out.println("--------------------------");
System.out.println();
System.out.println("XML 长度: " + result.getMappingXml().length() + " 字符");
}
// if (result.getStatus() == GenerateStatus.SUCCESS) {
// System.out.println();
// System.out.println("----- 生成的 XML 内容 -----");
// String xmlContent = result.getMappingXml();
//
// if (PRETTY_XML) {
// // 简单格式化:每个标签独占一行
// xmlContent = formatXml(xmlContent);
// }
//
// System.out.println(xmlContent);
// System.out.println("--------------------------");
// System.out.println();
// System.out.println("XML 长度: " + result.getMappingXml().length() + " 字符");
// }
}
/**