Dot-Net

比較 XElement 對象的最佳方法

  • September 6, 2011

在單元測試中,我將一個XElement對象與我期望的對象進行比較。.ToString()我使用的方法是呼叫XElement對象並將其與硬編碼的字元串值進行比較。這種方法結果很不舒服,因為我總是要注意字元串中的格式。

我檢查了 XElement.DeepEquals() 方法,但出於任何原因它沒有幫助。

有誰知道我應該使用什麼最好的方法?

我發現這篇優秀的文章很有用。它包含一個程式碼範例,該範例實現了在比較之前規範化 XML 樹的替代方法XNode.DeepEquals,這使得非語義內容無關緊要。

為了說明,XNode.DeepEquals這些語義等效文件的實現返回 false:

XElement root1 = XElement.Parse("<Root a='1' b='2'><Child>1</Child></Root>");
XElement root2 = XElement.Parse("<Root b='2' a='1'><Child>1</Child></Root>");

但是,使用DeepEqualsWithNormalization本文中的實現,您將獲得該值true,因為屬性的順序被認為不重要。這個實現包括在下面。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

public static class MyExtensions
{
   public static string ToStringAlignAttributes(this XDocument document)
   {
       XmlWriterSettings settings = new XmlWriterSettings();
       settings.Indent = true;
       settings.OmitXmlDeclaration = true;
       settings.NewLineOnAttributes = true;
       StringBuilder stringBuilder = new StringBuilder();
       using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder, settings))
           document.WriteTo(xmlWriter);
       return stringBuilder.ToString();
   }
}

class Program
{
   private static class Xsi
   {
       public static XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";

       public static XName schemaLocation = xsi + "schemaLocation";
       public static XName noNamespaceSchemaLocation = xsi + "noNamespaceSchemaLocation";
   }

   public static XDocument Normalize(XDocument source, XmlSchemaSet schema)
   {
       bool havePSVI = false;
       // validate, throw errors, add PSVI information
       if (schema != null)
       {
           source.Validate(schema, null, true);
           havePSVI = true;
       }
       return new XDocument(
           source.Declaration,
           source.Nodes().Select(n =>
           {
               // Remove comments, processing instructions, and text nodes that are
               // children of XDocument.  Only white space text nodes are allowed as
               // children of a document, so we can remove all text nodes.
               if (n is XComment || n is XProcessingInstruction || n is XText)
                   return null;
               XElement e = n as XElement;
               if (e != null)
                   return NormalizeElement(e, havePSVI);
               return n;
           }
           )
       );
   }

   public static bool DeepEqualsWithNormalization(XDocument doc1, XDocument doc2,
       XmlSchemaSet schemaSet)
   {
       XDocument d1 = Normalize(doc1, schemaSet);
       XDocument d2 = Normalize(doc2, schemaSet);
       return XNode.DeepEquals(d1, d2);
   }

   private static IEnumerable<XAttribute> NormalizeAttributes(XElement element,
       bool havePSVI)
   {
       return element.Attributes()
               .Where(a => !a.IsNamespaceDeclaration &&
                   a.Name != Xsi.schemaLocation &&
                   a.Name != Xsi.noNamespaceSchemaLocation)
               .OrderBy(a => a.Name.NamespaceName)
               .ThenBy(a => a.Name.LocalName)
               .Select(
                   a =>
                   {
                       if (havePSVI)
                       {
                           var dt = a.GetSchemaInfo().SchemaType.TypeCode;
                           switch (dt)
                           {
                               case XmlTypeCode.Boolean:
                                   return new XAttribute(a.Name, (bool)a);
                               case XmlTypeCode.DateTime:
                                   return new XAttribute(a.Name, (DateTime)a);
                               case XmlTypeCode.Decimal:
                                   return new XAttribute(a.Name, (decimal)a);
                               case XmlTypeCode.Double:
                                   return new XAttribute(a.Name, (double)a);
                               case XmlTypeCode.Float:
                                   return new XAttribute(a.Name, (float)a);
                               case XmlTypeCode.HexBinary:
                               case XmlTypeCode.Language:
                                   return new XAttribute(a.Name,
                                       ((string)a).ToLower());
                           }
                       }
                       return a;
                   }
               );
   }

   private static XNode NormalizeNode(XNode node, bool havePSVI)
   {
       // trim comments and processing instructions from normalized tree
       if (node is XComment || node is XProcessingInstruction)
           return null;
       XElement e = node as XElement;
       if (e != null)
           return NormalizeElement(e, havePSVI);
       // Only thing left is XCData and XText, so clone them
       return node;
   }

   private static XElement NormalizeElement(XElement element, bool havePSVI)
   {
       if (havePSVI)
       {
           var dt = element.GetSchemaInfo();
           switch (dt.SchemaType.TypeCode)
           {
               case XmlTypeCode.Boolean:
                   return new XElement(element.Name,
                       NormalizeAttributes(element, havePSVI),
                       (bool)element);
               case XmlTypeCode.DateTime:
                   return new XElement(element.Name,
                       NormalizeAttributes(element, havePSVI),
                       (DateTime)element);
               case XmlTypeCode.Decimal:
                   return new XElement(element.Name,
                       NormalizeAttributes(element, havePSVI),
                       (decimal)element);
               case XmlTypeCode.Double:
                   return new XElement(element.Name,
                       NormalizeAttributes(element, havePSVI),
                       (double)element);
               case XmlTypeCode.Float:
                   return new XElement(element.Name,
                       NormalizeAttributes(element, havePSVI),
                       (float)element);
               case XmlTypeCode.HexBinary:
               case XmlTypeCode.Language:
                   return new XElement(element.Name,
                       NormalizeAttributes(element, havePSVI),
                       ((string)element).ToLower());
               default:
                   return new XElement(element.Name,
                       NormalizeAttributes(element, havePSVI),
                       element.Nodes().Select(n => NormalizeNode(n, havePSVI))
                   );
           }
       }
       else
       {
           return new XElement(element.Name,
               NormalizeAttributes(element, havePSVI),
               element.Nodes().Select(n => NormalizeNode(n, havePSVI))
           );
       }
   }
}

引用自:https://stackoverflow.com/questions/7318157