合并两个 XML 文件并添加缺少的标签和属性
Posted
技术标签:
【中文标题】合并两个 XML 文件并添加缺少的标签和属性【英文标题】:Merge two XML files and add missing tags and attributes 【发布时间】:2018-09-04 03:15:26 【问题描述】:我有两个基本格式相同的 XML 文件,但 Master.XML 中的一些标签和属性不包含在 Child.XML 中。
我需要将 XML 文件合并到一个新的 XML 文件中,其中缺少标签和属性。
如果 Master.XML 和 Child.XML 中的值不同,则应使用 Child.XML 中的值。
我尝试将 Union 和 Concat 与节点一起使用,但它不起作用。
master.DescendantNodes().Union(child.DescendantNodes());
任何建议都会有所帮助。
Master.XML
<SysConfig IsRuntime="False" BarcodeEnabled="false" version="1.2.0.0">
<DbPath>C:\Agilent_i1000\ICPT_DB.sqlite</DbPath>
<CardDiagonsticsDelayTime>10</CardDiagonsticsDelayTime>
<ScreenSpecs NameID="CoreID" XrelativeID="X" YrelativeID="Y">
<ScreenSpec Name="MainCtrlPanel" Xrelative="0" Yrelative="0" ></ScreenSpec>
<ScreenSpec Name="1" Xrelative="75" Yrelative="0" NotToUse="1"></ScreenSpec>
<ScreenSpec Name="2" Xrelative="75" Yrelative="25" NotToUse="1"></ScreenSpec>
</ScreenSpecs>
</SysConfig>
Child.XML
<SysConfig IsRuntime="False" BarcodeEnabled="false" version="1.2.0.0">
<CardDiagonsticsDelayTime>20</CardDiagonsticsDelayTime>
<ScreenSpecs NameID="CoreID" XrelativeID="X" YrelativeID="Y">
<ScreenSpec Name="MainCtrlPanel" Xrelative="0" Yrelative="0" ></ScreenSpec>
<ScreenSpec Name="1" Xrelative="100" Yrelative="0" ></ScreenSpec>
<ScreenSpec Name="2" Xrelative="75" Yrelative="25"></ScreenSpec>
<ScreenSpec Name="3" Xrelative="175" Yrelative="25"></ScreenSpec>
</ScreenSpecs>
</SysConfig>
预期输出
<SysConfig IsRuntime="False" BarcodeEnabled="false" version="1.2.0.0">
<DbPath>C:\Agilent_i1000\ICPT_DB.sqlite</DbPath>
<CardDiagonsticsDelayTime>20</CardDiagonsticsDelayTime>
<ScreenSpecs NameID="CoreID" XrelativeID="X" YrelativeID="Y">
<ScreenSpec Name="MainCtrlPanel" Xrelative="0" Yrelative="0" ></ScreenSpec>
<ScreenSpec Name="1" Xrelative="100" Yrelative="0" NotToUse="1" ></ScreenSpec>
<ScreenSpec Name="2" Xrelative="75" Yrelative="25" NotToUse="1"></ScreenSpec>
<ScreenSpec Name="3" Xrelative="175" Yrelative="25">
</ScreenSpec>
</ScreenSpecs>
</SysConfig>
【问题讨论】:
XSLT 或许可以为您解决这个问题。 会有单主多子场景吗? 【参考方案1】:这是一组能够合并两个 XML 文档的实用程序类。整个内容非常通用,但您可能需要对其进行修改以满足其他特定需求。
这是处理您的案例的代码:
var result = new XmlXPathDocument();
result.Load("master.xml");
var child = new XmlXPathDocument();
child.Load("child.xml");
// here we tell the class that "Name" is a discriminant attribute.
// two nodes at the same level with the same discriminant attributes are considered the same, so will be merged
// if we don't do this, they will considered different and they will be added if the set of their attributes is different
child.AddDiscriminantAttribute("Name", string.Empty);
child.InjectXml(result);
result.Save("output.xml");
这些类派生自标准 XmlDocument 类层次结构。他们使用自动计算的 XPATH 表达式来逐个节点地合并文档。
public class XmlXPathDocument : XmlDocument
public const string XmlNamespaceUri = "http://www.w3.org/2000/xmlns/";
public const string XmlNamespacePrefix = "xmlns";
internal List<Tuple<string, string>> _discriminantAttributes = new List<Tuple<string, string>>();
public XmlXPathDocument() => Construct();
public XmlXPathDocument(XmlNameTable nameTable) : base(nameTable) => Construct();
public XmlXPathDocument(XmlImplementation implementation) : base(implementation) => Construct();
protected virtual void Construct() => XPathNamespaceManager = new XmlNamespaceManager(new NameTable());
public virtual XmlNamespaceManager XPathNamespaceManager get; private set;
public override XmlElement CreateElement(string prefix, string localName, string namespaceURI) => new XmlXPathElement(prefix, localName, namespaceURI, this);
public override XmlCDataSection CreateCDataSection(string data) => new XmlXPathCDataSection(data, this);
public override XmlText CreateTextNode(string text) => new XmlXPathText(text, this);
public virtual void AddDiscriminantAttribute(string name, string namespaceURI)
if (name == null)
throw new ArgumentNullException(nameof(name));
_discriminantAttributes.Add(new Tuple<string, string>(name, namespaceURI));
public virtual bool IsDiscriminant(XmlAttribute attribute)
if (attribute == null)
throw new ArgumentNullException(nameof(attribute));
foreach (var pair in _discriminantAttributes)
string ns = Nullify(attribute.NamespaceURI);
string dns = Nullify(pair.Item2);
if (ns == dns && pair.Item1 == attribute.LocalName)
return true;
return false;
private static string Nullify(string text)
if (text == null)
return null;
text = text.Trim();
if (text.Length == 0)
return null;
return text;
internal string GetPrefix(string namespaceURI)
if (string.IsNullOrEmpty(namespaceURI))
return null;
string prefix = XPathNamespaceManager.LookupPrefix(namespaceURI);
if (!string.IsNullOrEmpty(prefix))
XPathNamespaceManager.AddNamespace(prefix, namespaceURI);
return prefix;
string newPrefix;
int index = 0;
do
newPrefix = "ns" + index;
if (XPathNamespaceManager.LookupNamespace(newPrefix) == null)
break;
index++;
while (true);
XPathNamespaceManager.AddNamespace(newPrefix, namespaceURI);
return newPrefix;
private static bool IsNamespaceAttribute(XmlAttribute attribute)
if (attribute == null)
return false;
return attribute.NamespaceURI == XmlNamespaceUri && attribute.Prefix == XmlNamespacePrefix;
private static IEnumerable<XmlAttribute> GetAttributes(IXmlXPathNode node)
var xe = node as XmlElement;
if (xe == null)
yield break;
foreach (XmlAttribute att in xe.Attributes)
yield return att;
private static XmlAttribute GetAttribute(IXmlXPathNode node, string name) => node is XmlElement xe ? xe.Attributes[name] : null;
private static XmlAttribute GetAttribute(IXmlXPathNode node, string localName, string ns) => node is XmlElement xe ? xe.Attributes[localName, ns] : null;
public virtual bool InjectXml(XmlDocument target)
if (target == null)
throw new ArgumentNullException(nameof(target));
if (DocumentElement == null)
return false;
bool changed = false;
foreach (XmlNode node in SelectNodes("//node()"))
var xelement = node as IXmlXPathNode;
if (xelement == null)
continue;
if (string.IsNullOrEmpty(xelement.XPathExpression))
continue;
XmlNode other = target.SelectSingleNode(xelement.XPathExpression, XPathNamespaceManager);
if (other != null)
if (other is XmlElement otherElement)
foreach (XmlAttribute att in GetAttributes(xelement))
if (IsNamespaceAttribute(att))
continue;
if (otherElement.Attributes[att.LocalName, att.NamespaceURI]?.Value != att.Value)
otherElement.SetAttribute(att.LocalName, att.NamespaceURI, att.Value);
changed = true;
continue;
if (node is XmlXPathElement element)
XmlElement parent = EnsureTargetParent(xelement, target, out changed);
XmlElement targetElement = target.CreateElement(element.LocalName, element.NamespaceURI);
changed = true;
if (parent == null)
target.AppendChild(targetElement);
else
parent.AppendChild(targetElement);
foreach (XmlAttribute att in GetAttributes(xelement))
if (IsNamespaceAttribute(att))
continue;
targetElement.SetAttribute(att.LocalName, att.NamespaceURI, att.Value);
continue;
if (node is XmlXPathCDataSection cdata)
XmlElement parent = EnsureTargetParent(xelement, target, out changed);
var targetCData = target.CreateCDataSection(cdata.Value);
changed = true;
if (parent == null)
target.AppendChild(targetCData);
AppendNextTexts(node, targetCData, target);
else
if (parent.ChildNodes.Count == 1 && parent.ChildNodes[0] is XmlCharacterData)
parent.RemoveChild(parent.ChildNodes[0]);
parent.AppendChild(targetCData);
AppendNextTexts(node, targetCData, parent);
continue;
if (node is XmlXPathText text)
XmlElement parent = EnsureTargetParent(xelement, target, out changed);
var targetText = target.CreateTextNode(text.Value);
changed = true;
if (parent == null)
target.AppendChild(targetText);
AppendNextTexts(node, targetText, target);
else
if (parent.ChildNodes.Count == 1 && parent.ChildNodes[0] is XmlCharacterData)
parent.RemoveChild(parent.ChildNodes[0]);
parent.AppendChild(targetText);
AppendNextTexts(node, targetText, parent);
continue;
return changed;
private static void AppendNextTexts(XmlNode textNode, XmlNode targetTextNode, XmlNode parent)
do
if (textNode.NextSibling is XmlText text)
var newText = targetTextNode.OwnerDocument.CreateTextNode(text.Value);
parent.AppendChild(newText);
else
var cdata = textNode.NextSibling as XmlCDataSection;
if (cdata == null)
break;
var newCData = targetTextNode.OwnerDocument.CreateCDataSection(cdata.Value);
parent.AppendChild(newCData);
textNode = textNode.NextSibling;
while (true);
private static XmlElement EnsureTargetParent(IXmlXPathNode element, XmlDocument target, out bool changed)
changed = false;
if (element.ParentNode is XmlXPathElement parent)
if (string.IsNullOrEmpty(parent.XPathExpression))
return null;
if (target.SelectSingleNode(parent.XPathExpression, element.OwnerDocument.XPathNamespaceManager) is XmlElement targetElement)
return targetElement;
var parentElement = EnsureTargetParent(parent, target, out changed);
targetElement = target.CreateElement(parent.LocalName, parent.NamespaceURI);
parentElement.AppendChild(targetElement);
changed = true;
return targetElement;
return target.DocumentElement;
public class XmlXPathElement : XmlElement, IXmlXPathNode
private Lazy<string> _xPathExpression;
public XmlXPathElement(string prefix, string localName, string namespaceURI, XmlXPathDocument doc) : base(prefix, localName, namespaceURI, doc)
_xPathExpression = new Lazy<string>(() => GetXPathExpression());
public new XmlXPathDocument OwnerDocument => (XmlXPathDocument)base.OwnerDocument;
public virtual string XPathExpression => _xPathExpression.Value;
private static string GetAttEscapedValue(string value)
if (value.IndexOf('\'') >= 0)
return "=\"" + value.Replace("\"", """) + "\"";
return "='" + value + "'";
private string GetDiscriminantAttributeXPath()
foreach (var att in OwnerDocument._discriminantAttributes)
XmlAttribute disc;
if (string.IsNullOrEmpty(att.Item2))
disc = GetAttributeNode(att.Item1);
else
disc = GetAttributeNode(att.Item1, att.Item2);
if (disc != null)
string newPrefix = OwnerDocument.GetPrefix(NamespaceURI);
string name = Name + "[@" + disc.Name + GetAttEscapedValue(disc.Value) + "]";
if (newPrefix != null)
name = newPrefix + ":" + name;
return name;
return null;
private string GetAttributesXPath()
if (Attributes.Count == 0)
return null;
var sb = new StringBuilder();
foreach (XmlAttribute att in Attributes)
if (sb.Length > 0)
sb.Append(" and ");
sb.Append("@");
sb.Append(att.Name);
sb.Append(GetAttEscapedValue(att.Value));
OwnerDocument.GetPrefix(att.NamespaceURI);
var text = sb.ToString().Trim();
if (text.Length == 0)
return null;
return "[" + text + "]";
private string GetXPath(XmlNodeList parentNodes)
string discriminant = GetDiscriminantAttributeXPath();
if (discriminant != null)
return discriminant;
string name = Name;
string newPrefix = OwnerDocument.GetPrefix(NamespaceURI);
if (newPrefix != null)
name = newPrefix + ":" + LocalName;
if (parentNodes.Count == 1)
return name;
var sameName = new List<XmlElement>();
foreach (XmlNode node in parentNodes)
if (node.NodeType != XmlNodeType.Element)
continue;
if (node.Name == Name)
sameName.Add((XmlElement)node);
if (sameName.Count == 1)
return name;
string byIndex = null;
var sameAtts = new List<XmlElement>();
for (int i = 0; i < sameName.Count; i++)
if (sameName[i] == this)
byIndex = name + "[" + (i + 1) + "]";
continue;
bool same = true;
foreach (XmlAttribute att in Attributes)
XmlAttribute sameAtt = sameName[i].Attributes[att.LocalName, att.NamespaceURI];
if (sameAtt == null || string.Compare(sameAtt.Value, att.Value, StringComparison.OrdinalIgnoreCase) != 0)
same = false;
break;
if (same)
sameAtts.Add(sameName[i]);
if (sameAtts.Count == 0)
return name + GetAttributesXPath();
return byIndex;
private string GetXPathExpression()
if (ParentNode == null)
string name = Name;
string newPrefix = OwnerDocument.GetPrefix(NamespaceURI);
if (newPrefix != null)
name = newPrefix + ":" + name;
return name;
string expr = GetXPath(ParentNode.ChildNodes);
if (ParentNode is XmlXPathElement parent)
expr = parent.XPathExpression + "/" + expr;
if (ParentNode.NodeType == XmlNodeType.Document)
expr = "/" + expr;
return expr;
public class XmlXPathText : XmlText, IXmlXPathNode
private Lazy<string> _xPathExpression;
public XmlXPathText(string data, XmlXPathDocument doc) : base(data, doc)
_xPathExpression = new Lazy<string>(() => GetTextXPathExpression(this));
public new XmlXPathDocument OwnerDocument => (XmlXPathDocument)base.OwnerDocument;
public virtual string XPathExpression => _xPathExpression.Value;
internal static string GetTextXPathExpression(XmlNode node)
if (node.ParentNode is IXmlXPathNode element)
return element.XPathExpression + "/text()";
return null;
public class XmlXPathCDataSection : XmlCDataSection, IXmlXPathNode
private Lazy<string> _xPathExpression;
public XmlXPathCDataSection(string data, XmlXPathDocument doc) : base(data, doc)
_xPathExpression = new Lazy<string>(() => XmlXPathText.GetTextXPathExpression(this));
public new XmlXPathDocument OwnerDocument => (XmlXPathDocument)base.OwnerDocument;
public virtual string XPathExpression => _xPathExpression.Value;
public interface IXmlXPathNode
string XPathExpression get;
XmlNode ParentNode get;
XmlXPathDocument OwnerDocument get;
【讨论】:
看到您的 ScreenSpecs 元素具有 NameID 属性,我相信您的文档中可能有多个这些节点。如果是这种情况,您还需要为 NameID 添加另一个判别属性,因为这将使您的 ScreenSpec 节点通过其 NameID 保持唯一。 @TwistedStem - 我(无意中)把它作为练习留给读者:-)。是的,确实,添加 NameID 似乎是个好主意,我只是测试了输出,没有深入了解它的语义。【参考方案2】:试试 xml linq:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
class Program
const string MASTER_FILENAME = @"c:\temp\test.xml";
const string CHILD_FILENAME = @"c:\temp\test1.xml";
static void Main(string[] args)
XDocument master = XDocument.Load(MASTER_FILENAME);
XElement masterSysConfig = master.Descendants("SysConfig").FirstOrDefault();
masterSysConfig.Nodes().Remove();
XDocument child = XDocument.Load(CHILD_FILENAME);
var children = child.Nodes().Cast<XElement>().Select(x => x).FirstOrDefault();
masterSysConfig.Add(children.Elements());
【讨论】:
评论不用于扩展讨论;这个对话是moved to chat。以上是关于合并两个 XML 文件并添加缺少的标签和属性的主要内容,如果未能解决你的问题,请参考以下文章