当前位置: 首页 > news >正文

【加精】C# XML差异对比 (直接用)

C# XML差异对比

直接可用,使用灵活

    public class XmlDiffer{#region private objectprivate readonly XmlDocument sourceDoc = new XmlDocument();private readonly XmlDocument destinationDoc = new XmlDocument();private readonly StringBuilder report = new StringBuilder();#endregion#region constructorprivate readonly bool compareAttributes;private readonly bool compareContent;private readonly bool compareOrder;private readonly bool caseSensitive;private readonly HashSet<string> ignoredPaths;private readonly List<DiffRecord> diffRecords = new List<DiffRecord>();private readonly List<DiffTypeSummary> diffSummary = new List<DiffTypeSummary>();#endregion#region public objectpublic enum DiffType{ELEMENT_NAME_MISMATCH,ELEMENT_NAME_CASE_MISMATCH,CONTENT_MISMATCH,CHILD_ORDER_MISMATCH,MISSING_ATTRIBUTE,ATTRIBUTE_VALUE_MISMATCH,MISSING_ELEMENT}public enum ReportFormat{TXT,XML,JSON}public class CompareResult{//public string UniqueInfo { get; set; }public bool CompareFlag{get{return Summary == null ? false : (Summary.Count == 0 ? true : false);}}public List<DiffTypeSummary> Summary { get; set; }public List<DiffRecord> Diff { get; set; }}public class DiffTypeSummary{public string DiffType { get; set; }public int Count { get; set; }}public class DiffRecord{public string Path { get; set; }public string DiffType { get; set; }public string Expected { get; set; }public string Actual { get; set; }public string Suggestion { get; set; }}public ReportFormat reportFormat { get; set; } = ReportFormat.TXT;public CompareResult compareResult { get; set; }#endregion#region Public Function/// <summary>/// /// </summary>/// <param name="sourceXml">基准XML</param>/// <param name="destinationXml">与基准xml进行对比的xml</param>/// <param name="compareAttributes">比较属性</param>/// <param name="compareContent">比较内容</param>/// <param name="compareOrder">比较顺序</param>/// <param name="caseSensitive">大小写比较</param>/// <param name="ignoredPaths">需要忽略的指定路径</param>/// <param name="reportFormat">报告格式TXT,XML、Json)</param>/// <param name="isFilePath">string or xml file path</param>public XmlDiffer(string sourceXml, string destinationXml, bool compareAttributes = false, bool compareContent = false, bool compareOrder = false, bool caseSensitive = true, List<string> ignoredPaths = null, bool isFilePath = false){if (isFilePath){sourceDoc.Load(sourceXml);destinationDoc.Load(destinationXml);}else{sourceDoc.LoadXml(sourceXml);destinationDoc.LoadXml(destinationXml);}this.compareAttributes = compareAttributes;this.compareContent = compareContent;this.compareOrder = compareOrder;this.ignoredPaths = ignoredPaths != null ? new HashSet<string>(ignoredPaths) : new HashSet<string>();this.reportFormat = reportFormat;this.caseSensitive = caseSensitive;}public string Compare(){CompareNodes(sourceDoc.DocumentElement, destinationDoc.DocumentElement, sourceDoc.DocumentElement.Name);AppendStats();GetResultModel();return GetReportString(reportFormat);}#endregion#region Generate Reportpublic string GetReportString(ReportFormat outFormat = ReportFormat.TXT){switch (outFormat){case ReportFormat.JSON:return GenerateJsonReport();case ReportFormat.XML:return GenerateXmlReport();case ReportFormat.TXT:default:return report.ToString();}}private void GetResultModel(){CompareResult result = new CompareResult();result.Summary = diffSummary;result.Diff = diffRecords;compareResult = result;}private string GenerateJsonReport(){return Newtonsoft.Json.JsonConvert.SerializeObject(compareResult, Newtonsoft.Json.Formatting.Indented);}private string GenerateXmlReport(){var serializer = new XmlSerializer(typeof(CompareResult));using (var writer = new StringWriter()){serializer.Serialize(writer, compareResult);return writer.ToString();}}public void SaveReport(string filePath, ReportFormat outFormat = ReportFormat.TXT){var content = GetReportString(outFormat);File.WriteAllText(filePath, content, Encoding.UTF8);}#endregion#region Compare XMLprivate void CompareNodes(XmlNode sourceNode, XmlNode destNode, string path){if (ignoredPaths.Contains(path)) return;if (sourceNode != null && destNode != null){if (!string.Equals(sourceNode.Name, destNode.Name, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)){AppendMismatch(path, DiffType.ELEMENT_NAME_MISMATCH, sourceNode.Name, destNode.Name, $"缺失节点 {sourceNode.Name}");}if (compareAttributes){CompareAttributes(sourceNode, destNode, path);}// ✅ 只在叶子节点时比较文本内容if (compareContent && NodeHasChildNodes(sourceNode) == false && NodeHasChildNodes(destNode) == false){string sourceText = sourceNode.InnerText.Trim();string destText = destNode.InnerText.Trim();if (!string.IsNullOrEmpty(sourceText) || !string.IsNullOrEmpty(destText)){if (sourceText != destText){AppendMismatch(path, DiffType.CONTENT_MISMATCH, sourceText, destText, "请检查文本内容差异或修改文本内容为期望值");}}}var sourceChildren = GetChildElements(sourceNode);var destChildren = GetChildElements(destNode);if (compareOrder && !IsSameOrder(sourceChildren, destChildren)){AppendMismatch(path, DiffType.CHILD_ORDER_MISMATCH, "", "顺序不同", "检查子节点顺序");}var allKeys = new HashSet<string>(sourceChildren.Keys);allKeys.UnionWith(destChildren.Keys);var processedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); // 用于记录已处理大小写不一致的 keyforeach (var key in allKeys.ToList()) // 使用 ToList() 避免枚举修改异常{if (processedKeys.Contains(key)) continue;sourceChildren.TryGetValue(key, out var sList);destChildren.TryGetValue(key, out var dList);int maxCount = Math.Max(sList?.Count ?? 0, dList?.Count ?? 0);for (int i = 0; i < maxCount; i++){XmlNode sChild = i < (sList?.Count ?? 0) ? sList[i] : null;XmlNode dChild = i < (dList?.Count ?? 0) ? dList[i] : null;string newPath = GetUniquePath(sChild ?? dChild, i, path);if (ignoredPaths.Contains(newPath)) continue;if (sChild != null && dChild != null){CompareNodes(sChild, dChild, newPath);}else if (sChild != null){if (caseSensitive && TryMatchCaseInsensitive(key, destChildren, i, out var dCaseMismatch)){AppendMismatch(newPath, DiffType.ELEMENT_NAME_CASE_MISMATCH, key, dCaseMismatch.Name, "目标 XML 中节点名称大小写不一致,请统一大小写");// 标记已处理,避免重复processedKeys.Add(key);processedKeys.Add(dCaseMismatch.Name);continue;}AppendMissingElement(newPath, DiffType.MISSING_ELEMENT, sChild);}else if (dChild != null){if (caseSensitive && TryMatchCaseInsensitive(key, sourceChildren, i, out var sCaseMismatch)){AppendMismatch(newPath, DiffType.ELEMENT_NAME_CASE_MISMATCH, sCaseMismatch.Name, key, "目标 XML 中节点名称大小写不一致,请统一大小写");// 标记已处理,避免重复processedKeys.Add(key);processedKeys.Add(sCaseMismatch.Name);continue;}AppendMismatch(newPath, DiffType.ELEMENT_NAME_MISMATCH, "", dChild.Name, "目标 XML 中存在多余节点,请确认是否需要删除");}}}}else if (sourceNode != null){AppendMissingElement(path, DiffType.MISSING_ELEMENT, sourceNode);}else{AppendMismatch(path, DiffType.ELEMENT_NAME_MISMATCH, "", destNode.Name, "请手动检查此节点");}}private void CompareAttributes(XmlNode sourceNode, XmlNode destNode, string path){var sourceAttrs = sourceNode.Attributes;var destAttrs = destNode.Attributes;if (sourceAttrs == null) return;if (destNode.Name == "MsgBus"){}foreach (XmlAttribute sourceAttr in sourceAttrs){var destAttr = destAttrs?[sourceAttr.Name];if (destAttr == null){AppendMismatch(path, DiffType.MISSING_ATTRIBUTE, sourceAttr.Name, "", $"建议: 添加属性 {sourceAttr.Name}=\"{sourceAttr.Value}\"");}else if (sourceAttr.Value != destAttr.Value){AppendMismatch(path, DiffType.ATTRIBUTE_VALUE_MISMATCH, $"{sourceAttr.Name}=\"{sourceAttr.Value}\"", $"{destAttr.Name}=\"{destAttr.Value}\"", $"建议: 修改属性 {sourceAttr.Name} 为 \"{sourceAttr.Value}\"");}}}private Dictionary<string, List<XmlNode>> GetChildElements(XmlNode node){var dict = new Dictionary<string, List<XmlNode>>();foreach (XmlNode child in node.ChildNodes){if (child.NodeType == XmlNodeType.Element){string key = caseSensitive ? child.Name : child.Name.ToLower();if (!dict.ContainsKey(key))dict[key] = new List<XmlNode>();dict[key].Add(child);}}return dict;}private bool NodeHasChildNodes(XmlNode node){if (node.HasChildNodes && node.ChildNodes[0] != null && node.ChildNodes[0].Name != "#text"){return true;}else{return false;}}private bool IsSameOrder(Dictionary<string, List<XmlNode>> sourceChildren, Dictionary<string, List<XmlNode>> destChildren){var sourceList = sourceChildren.SelectMany(kvp => kvp.Value.Select(n => caseSensitive ? n.Name : n.Name.ToLower())).ToList();var destList = destChildren.SelectMany(kvp => kvp.Value.Select(n => caseSensitive ? n.Name : n.Name.ToLower())).ToList();return sourceList.SequenceEqual(destList);}private string GetUniquePath(XmlNode node, int index, string parentPath){string nodeName = caseSensitive ? node.Name : node.Name.ToLower();string attrInfo = node.Attributes?["id"]?.Value;string path = $"{parentPath}/{nodeName}[{index + 1}]";if (!string.IsNullOrEmpty(attrInfo))path += $"[@id='{attrInfo}']";return path;}private bool TryMatchCaseInsensitive(string key, Dictionary<string, List<XmlNode>> dict, int index, out XmlNode matchedNode){matchedNode = null;var matchKey = dict.Keys.FirstOrDefault(k =>string.Equals(k, key, StringComparison.OrdinalIgnoreCase) &&!string.Equals(k, key, StringComparison.Ordinal));if (matchKey != null && dict[matchKey].Count > index){matchedNode = dict[matchKey][index];return true;}return false;}#endregion#region Append differenceprivate void AppendMismatch(string path, DiffType type, string expected, string actual, string suggestion){AddDiffRecord(path, type, expected, actual, suggestion);}private void AppendMissingElement(string path, DiffType type, XmlNode missingNode){string expected = missingNode.OuterXml;string actual = "Null";string suggestion = $"添加缺失元素: {expected}";AddDiffRecord(path, type, expected, actual, suggestion);}private void AppendStats(){report.AppendLine("📊 差异统计:");if (diffSummary.Count > 0){report.AppendLine($"   ▸ 差异结果: 不匹配");}foreach (var kvp in diffSummary){report.AppendLine($"   ▸ {kvp.DiffType}: {kvp.Count} 项");}report.AppendLine();}private void AddDiffRecord(string path, DiffType type, string expected, string actual, string suggestion){// 文本报告report.AppendLine($"✓ [{path}] {type}");report.AppendLine($"   ▸ 期望: {expected}");report.AppendLine($"   ▸ 实际: {actual}");report.AppendLine($"   ▸ 建议: {suggestion}");report.AppendLine();// 统计if (diffSummary.Where(x => x.DiffType.Equals(type.ToString())).ToList().Count > 0){diffSummary.Where(s => s.DiffType == type.ToString()).ToList().ForEach(s => s.Count = s.Count + 1);}else{diffSummary.Add(new DiffTypeSummary { DiffType = type.ToString(), Count = 1 });}//if (!diffSummary.ContainsKey(type)) diffSummary[type] = 0;//diffSummary[type]++;// 结构化记录diffRecords.Add(new DiffRecord{Path = path,DiffType = type.ToString(),Expected = expected,Actual = actual,Suggestion = suggestion});}#endregion}
http://www.dtcms.com/a/539947.html

相关文章:

  • JavaScript eval函数
  • C++笔记(面向对象)对象和对象之间关系
  • 注册中心 eureka、nacos、consul、zookeeper、redis对比
  • c# 基于xml文件和devexpress插件 的工作流程配置
  • 【四川政务服务网-注册安全分析报告】
  • 基于海思AI ISP视频编解码IPC平台的算法承载方案
  • C语言入门(十二):函数的递归
  • 建设银行的网站模板下载免费网站
  • 小型企业网站设计教程app软件开发技术pdf百度云
  • uniapp安卓端+ fastapi(后端)获取到设备的ip
  • hardhat 搭建智能合约
  • 【开题答辩实录分享】以《智慧校园勤工俭学信息管理系统的设计与实现》为例进行答辩实录分享
  • Elasticsearch安装与配置全指南
  • BIM引擎中火焰模拟
  • SPI NOR Flash 家族的常见存储结构
  • billu_b0x 靶机渗透测试
  • RPA 如何成为 AI 智能体的落地引擎
  • 快递比价寄件系统技术解析:基于PHP+Vue+小程序的高效聚合配送解决方案
  • 巢湖市重点工程建设管理局网站易企秀网站怎么做轮播图
  • 免费画图网站微信公众官网登录入口
  • SAP SD借贷项凭证创建接口分享
  • uniapp(2)自定义tabbar
  • 技术实践:在基于 RISC-V 的 ESP32 上运行 MQTT over QUIC
  • 【Linux】编辑器vim的使用和理解gcc编译器
  • uniapp如何集成第三方库
  • 易灵思FPGA的RISC-V核操作函数
  • BOTA发布新一代力觉核心:Gen A六维力矩传感器,为下一代机器人研发,注入精准感知
  • 180课时吃透Go语言游戏后端开发14:map类型
  • AWS × Caddy:一键部署多站点反向代理 + 负载均衡网关(Terraform + ECS Fargate)
  • 外贸电子商务网站jcms内容管理系统