C# XML 差异算法

C# XML Diffing algorithm

我有两个 XML,在用户编辑它们之前和之后。我需要检查用户是否只添加了新元素,但没有删除或更改旧元素。

任何人都可以向我推荐一个好的算法来进行比较吗?

Ps: 我的 XML 有一个非常简单的模式,它们仅以一种天真的方式表示对象的结构(带有嵌套对象)。 允许的标签很少, 标签只能包含 标签、 标签或一个 标签。 标签只能包含一个字符串; 标签可以包含一个 标签和一个 标签(代表列表中对象的结构)。 标签中的字符串可以自由选择, 标签中的字符串只能是 "string" , "int" , "float" , "bool" , "date" 或 "composite".

举个例子:

 <object>
      <name>Person</name>
      <type>composite</type>

      <object>
            <name>Person_Name</name>
            <type>string</type>
      </object>

      <object>
            <name>Person_Surname</name>
            <type>string</type>
      </object>

      <object>
            <name>Person_Age</name>
            <type>int</type>
      </object>

      <object>
            <name>Person_Weight</name>
            <type>float</type>
      </object>

      <object>
            <name>Person_Address</name>
            <type>string</type>
      </object>

      <object>
            <name>Person_BirthDate</name>
            <type>date</type>
      </object>

      <list>
            <name>Person_PhoneNumbers</name>

            <object>
                  <name>Person_PhoneNumber</name>
                  <type>composite</type>

                  <object>
                        <name>Person_PhoneNumber_ProfileName</name>
                        <type>string</type>
                  </object>
                  <object>
                        <name>Person_PhoneNumber_CellNumber</name>
                        <type>string</type>
                  </object>
                  <object>
                        <name>Person_PhoneNumber_HomeNumber</name>
                        <type>string</type>
                  </object>
                  <object>
                        <name>Person_PhoneNumber_FaxNumber</name>
                        <type>string</type>
                  </object>
                  <object>
                        <name>Person_PhoneNumber_Mail</name>
                        <type>string</type>
                  </object>
                  <object>
                        <name>Person_PhoneNumber_Social</name>
                        <type>string</type>
                  </object>
                  <object>
                        <name>Person_PhoneNumber_IsActive</name>
                        <type>bool</type>
                  </object>
            </object>
      </list>
 </object>

你说:

I need to check that user have only added new elements 
but have not deleted or changed old ones.

你能更准确地表达你的意思吗?

例如,如果我在某处插入一个新的 "object" 元素,我已经更改了它里面的每个元素,对吧?包含它的列表和其他对象的数量。事实上,任何插入都是对根元素的改变。

因此,您可能想要而不是计算除了根元素之外什么都不改变的变化。将新项目添加到您显示的列表中怎么样?您想让列表算作已更改吗?或者,如果列表中的对象或列表本身被移动到新位置而其内容根本没有改变怎么办?

这些可能性中的每一种都非常容易编写,但必须首先决定什么算作更改。

例如,如果您只关心底层对象,而"the same"表示完全相同的文本内容(无属性、白色-space变体等),那么最简单的方法是将 "before" 文件加载到(名称,类型)对列表中;然后将 "after" 文件加载到一个类似但独立的列表中。对两个列表进行排序,然后 运行 同时将它们向下排序,并报告新列表中不在旧列表中的任何内容(以防万一,您可能也想报告任何删除)。

I need to check that user have only added new elements but have not deleted or changed old ones.

您可以将 2 个 XML 文件表示为对象。遍历节点,获取每个节点的子元素计数,并检查其子节点是否存在于另一个文件中。要比较 2 个复杂对象,可以使用 IEquatable.Equals() 接口方法。阅读here

下面的代码不关心 XML 文档的结构或特定元素存在的位置,因为每个元素都表示为一个 XElement 对象。它只知道 1.) 元素的名称,2.) 每个元素是否有子元素,3.) 是否有属性,4.) 是否有 innerxml,等等。 XML 的结构,您可以将每个级别表示为单个 class.

public class Program
{

    static void Main(string[] args)
    {
        XDocument xdoc1 = XDocument.Load("file1.xml");
        XDocument xdoc2 = XDocument.Load("file2.xml");

        RootElement file1 = new RootElement(xdoc1.Elements().First());
        RootElement file2 = new RootElement(xdoc2.Elements().First());

        bool isEqual = file1.Equals(file2);

        Console.ReadLine();
    }
}
public abstract class ElementBase<T>
{
    public string Name;
    public List<T> ChildElements;

    public ElementBase(XElement xElement)
    {

    }
}

public class RootElement : ElementBase<ChildElement>, IEquatable<RootElement>
{
    public RootElement(XElement xElement)
        : base(xElement)
    {
        ChildElements = new List<ChildElement>();
        Name = xElement.Name.ToString();

        foreach (XElement e in xElement.Elements())
        {
            ChildElements.Add(new ChildElement(e));
        }
    }

    public bool Equals(RootElement other)
    {
        bool flag = true;

        if (this.ChildElements.Count != other.ChildElements.Count())
        {
            //--Your error handling logic here
            flag = false;
        }

        List<ChildElement> otherChildElements = other.ChildElements;
        foreach (ChildElement c in this.ChildElements)
        {
            ChildElement otherElement = otherChildElements.FirstOrDefault(x => x.Name == c.Name);

            if (otherElement == null)
            {
                //--Your error handling logic here
                flag = false;
            }
            else
            {
                flag = c.Equals(otherElement) == false ? false : flag;
            }
        }

        return flag;
    }
}

public class ChildElement : ElementBase<ChildElement>, IEquatable<ChildElement>
{
    public ChildElement(XElement xElement)
        : base(xElement)
    {
        ChildElements = new List<ChildElement>();
        Name = xElement.Name.ToString();

        foreach (XElement e in xElement.Elements())
        {
            ChildElements.Add(new ChildElement(e));
        }
    }

    public bool Equals(ChildElement other)
    {
        bool flag = true;

        if (this.ChildElements.Count != other.ChildElements.Count())
        {
            //--Your error handling logic here
            flag = false;
        }

        List<ChildElement> otherList = other.ChildElements;

        foreach (ChildElement e in this.ChildElements)
        {
            ChildElement otherElement = otherList.FirstOrDefault(x => x.Name == e.Name);

            if (otherElement == null)
            {
                //--Your error handling logic here
                flag = false;
            }

            else
            {
                flag = e.Equals(otherElement) == false ? false : flag;
            }
        }

        return flag;
    }
}

如果您还想检查属性或 innerxml,您可以这样做。

public List<XAttribute> ElementAttributes = new List<XAttribute>();
    foreach (XAttribute attr in xElement.Attributes())
                {
                    ElementAttributes.Add(attr);
                }

List<XAttribute> otherAttributes = other.ElementAttributes;
                foreach (XAttribute attr in ElementAttributes)
                {
                    XAttribute otherAttribute = otherAttributes.FirstOrDefault(x => x.Name == attr.Name);

                    if (otherAttribute == null)
                    {
                        //--Your error handling logic here

                        flag = false;
                    }

                    else
                    {
                        if (otherAttribute.Value != attr.Value)
                        {
                            //--Your error handling logic here

                            flag = false;
                        }
                    }
                }