/// <summary> /// Compares two <see cref="XObject">XObjects</see>for equality. /// </summary> /// <param name="obj1">The first <see cref="XObject" />.</param> /// <param name="obj2">The second <see cref="XObject" />.</param> /// <param name="options">The comparison options.</param> /// <param name="stringComparer">The string comparer.</param> /// <returns>The <see cref="XObject"/> at which the first mismatch /// occurred; otherwise <see langword="null"/>.</returns> /// <remarks><para>The order of the parameters maters for ceratin options.</para></remarks> public static Tuple <XObject, XObject> DeepEquals( [CanBeNull] this XObject obj1, [CanBeNull] XObject obj2, XObjectComparisonOptions options = XObjectComparisonOptions.Default, [CanBeNull] StringComparer stringComparer = null) { if (stringComparer == null) { stringComparer = StringComparer.CurrentCulture; } // Normalize flags bool ignoreAttributes = (options & XObjectComparisonOptions.IgnoreAttributes) == XObjectComparisonOptions.IgnoreAttributes; bool ignoreAdditionalAttributes = ignoreAttributes || ((options & XObjectComparisonOptions.IgnoreAdditionalAttributes) == XObjectComparisonOptions.IgnoreAdditionalAttributes); bool ignoreAttributeOrder = ignoreAdditionalAttributes || ((options & XObjectComparisonOptions.IgnoreAttributeOrder) == XObjectComparisonOptions.IgnoreAttributeOrder); bool ignoreElements = (options & XObjectComparisonOptions.IgnoreElements) == XObjectComparisonOptions.IgnoreElements; bool ignoreAdditionalElements = ignoreElements || ((options & XObjectComparisonOptions.IgnoreAdditionalElements) == XObjectComparisonOptions.IgnoreAdditionalElements); bool ignoreElementOrder = ignoreAdditionalElements || ((options & XObjectComparisonOptions.IgnoreElementOrder) == XObjectComparisonOptions.IgnoreElementOrder); bool ignoreComments = (options & XObjectComparisonOptions.IgnoreComments) == XObjectComparisonOptions.IgnoreComments; bool ignoreAdditionalCommentss = ignoreComments || ((options & XObjectComparisonOptions.IgnoreAdditionalComments) == XObjectComparisonOptions.IgnoreAdditionalComments); bool ignoreCommentOrder = ignoreAdditionalCommentss || ((options & XObjectComparisonOptions.IgnoreCommentOrder) == XObjectComparisonOptions.IgnoreCommentOrder); bool ignoreText = (options & XObjectComparisonOptions.IgnoreText) == XObjectComparisonOptions.IgnoreText; bool ignoreAdditionalText = ignoreText || ((options & XObjectComparisonOptions.IgnoreAdditionalText) == XObjectComparisonOptions.IgnoreAdditionalText); bool ignoreTextOrder = ignoreAdditionalText || ((options & XObjectComparisonOptions.IgnoreTextOrder) == XObjectComparisonOptions.IgnoreTextOrder); bool ignoreTextOutsideOfChildren = ignoreText || ((options & XObjectComparisonOptions.IgnoreTextOutsideOfChildren) == XObjectComparisonOptions.IgnoreTextOutsideOfChildren); bool ignoreProcessingInstructions = (options & XObjectComparisonOptions.IgnoreProcessingInstructions) == XObjectComparisonOptions.IgnoreProcessingInstructions; bool ignoreAdditionalProcessingInstructionss = ignoreProcessingInstructions || ((options & XObjectComparisonOptions.IgnoreAdditionalProcessingInstructions) == XObjectComparisonOptions.IgnoreAdditionalProcessingInstructions); bool ignoreProcessingInstructionOrder = ignoreAdditionalProcessingInstructionss || ((options & XObjectComparisonOptions.IgnoreProcessingInstructionOrder) == XObjectComparisonOptions.IgnoreProcessingInstructionOrder); bool ignoreDocumentTypes = (options & XObjectComparisonOptions.IgnoreDocumentTypes) == XObjectComparisonOptions.IgnoreDocumentTypes; bool ignoreAdditionalDocumentTypess = ignoreDocumentTypes || ((options & XObjectComparisonOptions.IgnoreAdditionalDocumentTypes) == XObjectComparisonOptions.IgnoreAdditionalDocumentTypes); bool ignoreAllChildren = ignoreElements && ignoreProcessingInstructions && ignoreDocumentTypes && ignoreComments && ignoreText; bool allOrderSignificant = !ignoreElementOrder && !ignoreProcessingInstructionOrder && !ignoreCommentOrder && !ignoreTextOrder && !ignoreTextOutsideOfChildren; Stack <XObject, XObject> stack = new Stack <XObject, XObject>(); stack.Push(obj1, obj2); while (stack.TryPop(out obj1, out obj2)) { // Generic equality checks if (ReferenceEquals(obj1, obj2)) { continue; } if (ReferenceEquals(obj1, null) || ReferenceEquals(obj2, null) || obj1.NodeType != obj2.NodeType) { return(new Tuple <XObject, XObject>(obj1, obj2)); } XContainer container1 = null; XContainer container2 = null; // Perform type specific checks. switch (obj1.NodeType) { case XmlNodeType.Document: // Process as container container1 = (XContainer)obj1; container2 = (XContainer)obj2; break; case XmlNodeType.Element: XElement el1 = (XElement)obj1; XElement el2 = (XElement)obj2; if (el1.Name != el2.Name) { return(new Tuple <XObject, XObject>(obj1, obj2)); } if (!ignoreAttributes) { // Add attributes to stack for processing if (ignoreAttributeOrder) { // Build dictionary of attributes Dictionary <XName, XAttribute> el2Attributes = el2.Attributes() // ReSharper disable once PossibleNullReferenceException .ToDictionary(a => a.Name); // Ignore attribute order foreach (XAttribute attribute1 in el1.Attributes()) { XAttribute attribute2; // ReSharper disable once PossibleNullReferenceException if (!el2Attributes.TryGetValue(attribute1.Name, out attribute2) || !stringComparer.Equals(attribute1.Value, attribute2.Value)) { return(new Tuple <XObject, XObject>(attribute1, attribute2)); } // ReSharper disable once PossibleNullReferenceException el2Attributes.Remove(attribute2.Name); } // If additional attributes on the second element are not being ignored, check if we // additional attributes. if (!ignoreAdditionalAttributes && el2Attributes.Count > 0) { return(new Tuple <XObject, XObject>(null, el2Attributes.Values.First())); } } else { // Attribute order matters XAttribute attribute1 = el1.FirstAttribute; XAttribute attribute2 = el2.FirstAttribute; while (attribute1 != null) { // We have less attributes on the second element than the first if (attribute2 == null || !stringComparer.Equals(attribute1.Value, attribute2.Value)) { return(new Tuple <XObject, XObject>(attribute1, attribute2)); } attribute1 = attribute1.NextAttribute; attribute2 = attribute2.NextAttribute; } if (attribute2 != null) { return(new Tuple <XObject, XObject>(null, attribute2)); } } } // Process as container container1 = el1; container2 = el2; break; case XmlNodeType.Attribute: XAttribute attr1 = (XAttribute)obj1; XAttribute attr2 = (XAttribute)obj2; if (!ignoreAttributes && (attr1.Name != attr2.Name || !stringComparer.Equals(attr1.Value, attr2.Value))) { return(new Tuple <XObject, XObject>(attr1, attr2)); } break; case XmlNodeType.ProcessingInstruction: XProcessingInstruction pi1 = (XProcessingInstruction)obj1; XProcessingInstruction pi2 = (XProcessingInstruction)obj2; if (!ignoreProcessingInstructions && (!stringComparer.Equals(pi1.Target, pi2.Target) || !stringComparer.Equals(pi1.Data, pi2.Data))) { return(new Tuple <XObject, XObject>(pi1, pi2)); } break; case XmlNodeType.DocumentType: XDocumentType dt1 = (XDocumentType)obj1; XDocumentType dt2 = (XDocumentType)obj2; if (!ignoreDocumentTypes && (!stringComparer.Equals(dt1.Name, dt2.Name) || !stringComparer.Equals(dt1.PublicId, dt2.PublicId) || !stringComparer.Equals(dt1.SystemId, dt2.SystemId) || !stringComparer.Equals(dt1.InternalSubset, dt2.InternalSubset))) { return(new Tuple <XObject, XObject>(dt1, dt2)); } break; case XmlNodeType.Text: case XmlNodeType.CDATA: XText txt1 = (XText)obj1; XText txt2 = (XText)obj2; if (!ignoreText && !stringComparer.Equals(txt1.Value, txt2.Value)) { return(new Tuple <XObject, XObject>(txt1, txt2)); } break; case XmlNodeType.Comment: XComment comment1 = (XComment)obj1; XComment comment2 = (XComment)obj2; if (!ignoreComments && !stringComparer.Equals(comment1.Value, comment2.Value)) { return(new Tuple <XObject, XObject>(comment1, comment2)); } break; } // If we're not a container, or we're ignoring container contents, we can move on. if (container1 == null || ignoreAllChildren) { continue; } if (allOrderSignificant) { // We care about the order of everything XNode n1 = container1.FirstNode; XNode n2 = container2.FirstNode; while (n1 != null) { if (n2 == null || n1.NodeType != n2.NodeType) { return(new Tuple <XObject, XObject>(n1, n2)); } stack.Push(n1, n2); n1 = n1.NextNode; n2 = n2.NextNode; } if (n2 != null) { return(new Tuple <XObject, XObject>(null, n2)); } continue; } // We care about the order of somethings, but not others. List <XElement> elements1; List <XElement> elements2; if (!ignoreElements && ignoreElementOrder) { elements1 = new List <XElement>(); elements2 = new List <XElement>(); } else { elements1 = elements2 = null; } List <XProcessingInstruction> processingInstructions1; List <XProcessingInstruction> processingInstructions2; if (!ignoreProcessingInstructions && ignoreProcessingInstructionOrder) { processingInstructions1 = new List <XProcessingInstruction>(); processingInstructions2 = new List <XProcessingInstruction>(); } else { processingInstructions1 = processingInstructions2 = null; } List <XComment> comments1; List <XComment> comments2; if (!ignoreComments && ignoreCommentOrder) { comments1 = new List <XComment>(); comments2 = new List <XComment>(); } else { comments1 = comments2 = null; } List <XText> texts1; List <XText> texts2; if (!ignoreText && ignoreTextOrder) { texts1 = new List <XText>(); texts2 = new List <XText>(); } else { texts1 = texts2 = null; } XDocumentType documentType1 = null; XDocumentType documentType2 = null; bool hasChildren = false; bool hasText = false; // Build child lists from first node List <XObject> l1 = new List <XObject>(); XNode n = container1.FirstNode; while (n != null) { switch (n.NodeType) { case XmlNodeType.Element: hasChildren = true; if (elements1 != null) { elements1.Add((XElement)n); } else if (!ignoreElements) { l1.Add(n); } break; case XmlNodeType.Text: case XmlNodeType.CDATA: hasText = true; if (texts1 != null) { texts1.Add((XText)n); } else if (!ignoreText) { l1.Add(n); } break; case XmlNodeType.ProcessingInstruction: if (processingInstructions1 != null) { processingInstructions1.Add((XProcessingInstruction)n); } else if (!ignoreProcessingInstructions) { l1.Add(n); } break; case XmlNodeType.Comment: if (comments1 != null) { comments1.Add((XComment)n); } else if (!ignoreComments) { l1.Add(n); } break; case XmlNodeType.DocumentType: if (!ignoreDocumentTypes) { documentType1 = (XDocumentType)n; } break; } n = n.NextNode; } // Build child lists from second node List <XObject> l2 = new List <XObject>(); n = container2.FirstNode; while (n != null) { switch (n.NodeType) { case XmlNodeType.Element: hasChildren = true; if (elements2 != null) { elements2.Add((XElement)n); } else if (!ignoreElements) { l2.Add(n); } break; case XmlNodeType.Text: case XmlNodeType.CDATA: hasText = true; if (texts2 != null) { texts2.Add((XText)n); } else if (!ignoreText) { l2.Add(n); } break; case XmlNodeType.ProcessingInstruction: if (processingInstructions2 != null) { processingInstructions2.Add((XProcessingInstruction)n); } else if (!ignoreProcessingInstructions) { l2.Add(n); } break; case XmlNodeType.Comment: if (comments2 != null) { comments2.Add((XComment)n); } else if (!ignoreComments) { l2.Add(n); } break; case XmlNodeType.DocumentType: if (!ignoreDocumentTypes) { documentType2 = (XDocumentType)n; } break; } n = n.NextNode; } // Add document type comparison if necessary (note we never consider order of the document type as it // always appears before the root node. if (!ignoreDocumentTypes) { if (documentType1 != null) { if (documentType2 == null || !stringComparer.Equals(documentType1.Name, documentType2.Name) || !stringComparer.Equals(documentType1.PublicId, documentType2.PublicId) || !stringComparer.Equals(documentType1.SystemId, documentType2.SystemId) || !stringComparer.Equals(documentType1.InternalSubset, documentType2.InternalSubset)) { return(new Tuple <XObject, XObject>(documentType1, documentType2)); } // Don't push onto stack, as we've already done the comparison at this point. } else if (!ignoreAdditionalDocumentTypess && documentType2 != null) { return(new Tuple <XObject, XObject>(null, documentType2)); } } // Add order specific nodes to stack for processing. bool stripText = hasChildren && hasText && ignoreTextOutsideOfChildren; List <XObject> .Enumerator l1Enumerator = l1.GetEnumerator(); List <XObject> .Enumerator l2Enumerator = l2.GetEnumerator(); while (l1Enumerator.MoveNext()) { if (stripText && l1Enumerator.Current is XText) { continue; } do { if (!l2Enumerator.MoveNext()) { return(new Tuple <XObject, XObject>(l1Enumerator.Current, null)); } } while (stripText && l2Enumerator.Current is XText); if (l1Enumerator.Current.NodeType != l2Enumerator.Current.NodeType) { return(new Tuple <XObject, XObject>(l1Enumerator.Current, l2Enumerator.Current)); } stack.Push(l1Enumerator.Current, l2Enumerator.Current); } while (l2Enumerator.MoveNext()) { if (!stripText || !(l2Enumerator.Current is XText)) { return(new Tuple <XObject, XObject>(null, l2Enumerator.Current)); } } // Add order independent nodes for processing // ReSharper disable PossibleNullReferenceException if (elements1 != null) { foreach (XElement e1 in elements1) { XElement e2; if (!elements2.TryRemove(e => e.Name == e1.Name, out e2)) { return(new Tuple <XObject, XObject>(e1, null)); } stack.Push(e1, e2); } if (!ignoreAdditionalElements && texts2.Count > 0) { return(new Tuple <XObject, XObject>(null, elements2.First())); } } if (texts1 != null) { foreach (XText t1 in texts1) { XText t2; if (!texts2.TryRemove(t => stringComparer.Equals(t1.Value, t.Value), out t2)) { return(new Tuple <XObject, XObject>(t1, null)); } // Don't push onto stack, as we've already done the comparison at this point. } if (!ignoreAdditionalText && texts2.Count > 0) { return(new Tuple <XObject, XObject>(null, texts2.First())); } } if (comments1 != null) { foreach (XComment c1 in comments1) { XComment c2; if (!comments2.TryRemove(c => stringComparer.Equals(c1.Value, c.Value), out c2)) { return(new Tuple <XObject, XObject>(c1, null)); } // Don't push onto stack, as we've already done the comparison at this point. } if (!ignoreAdditionalCommentss && comments2.Count > 0) { return(new Tuple <XObject, XObject>(null, comments2.First())); } } if (processingInstructions1 != null) { foreach (XProcessingInstruction pi1 in processingInstructions1) { XProcessingInstruction pi2; if (!processingInstructions2.TryRemove(pi => stringComparer.Equals(pi.Target, pi1.Target) && stringComparer.Equals(pi.Data, pi1.Data), out pi2)) { return(new Tuple <XObject, XObject>(pi1, null)); } // Don't push onto stack, as we've already done the comparison at this point. } if (!ignoreAdditionalProcessingInstructionss && processingInstructions2.Count > 0) { return(new Tuple <XObject, XObject>(null, processingInstructions2.First())); } } // ReSharper restore PossibleNullReferenceException } // Successful comparisons result in null. return(null); }
/// <summary> /// Compares two <see cref="XObject">XObjects</see>for equality. /// </summary> /// <param name="obj1">The first <see cref="XObject" />.</param> /// <param name="obj2">The second <see cref="XObject" />.</param> /// <param name="options">The comparison options.</param> /// <param name="stringComparer">The string comparer.</param> /// <returns>The <see cref="XObject"/> at which the first mismatch /// occurred; otherwise <see langword="null"/>.</returns> /// <remarks><para>The order of the parameters maters for ceratin options.</para></remarks> public static Tuple<XObject, XObject> DeepEquals( [CanBeNull] this XObject obj1, [CanBeNull] XObject obj2, XObjectComparisonOptions options = XObjectComparisonOptions.Default, [CanBeNull] StringComparer stringComparer = null) { if (stringComparer == null) stringComparer = StringComparer.CurrentCulture; // Normalize flags bool ignoreAttributes = (options & XObjectComparisonOptions.IgnoreAttributes) == XObjectComparisonOptions.IgnoreAttributes; bool ignoreAdditionalAttributes = ignoreAttributes || ((options & XObjectComparisonOptions.IgnoreAdditionalAttributes) == XObjectComparisonOptions.IgnoreAdditionalAttributes); bool ignoreAttributeOrder = ignoreAdditionalAttributes || ((options & XObjectComparisonOptions.IgnoreAttributeOrder) == XObjectComparisonOptions.IgnoreAttributeOrder); bool ignoreElements = (options & XObjectComparisonOptions.IgnoreElements) == XObjectComparisonOptions.IgnoreElements; bool ignoreAdditionalElements = ignoreElements || ((options & XObjectComparisonOptions.IgnoreAdditionalElements) == XObjectComparisonOptions.IgnoreAdditionalElements); bool ignoreElementOrder = ignoreAdditionalElements || ((options & XObjectComparisonOptions.IgnoreElementOrder) == XObjectComparisonOptions.IgnoreElementOrder); bool ignoreComments = (options & XObjectComparisonOptions.IgnoreComments) == XObjectComparisonOptions.IgnoreComments; bool ignoreAdditionalCommentss = ignoreComments || ((options & XObjectComparisonOptions.IgnoreAdditionalComments) == XObjectComparisonOptions.IgnoreAdditionalComments); bool ignoreCommentOrder = ignoreAdditionalCommentss || ((options & XObjectComparisonOptions.IgnoreCommentOrder) == XObjectComparisonOptions.IgnoreCommentOrder); bool ignoreText = (options & XObjectComparisonOptions.IgnoreText) == XObjectComparisonOptions.IgnoreText; bool ignoreAdditionalText = ignoreText || ((options & XObjectComparisonOptions.IgnoreAdditionalText) == XObjectComparisonOptions.IgnoreAdditionalText); bool ignoreTextOrder = ignoreAdditionalText || ((options & XObjectComparisonOptions.IgnoreTextOrder) == XObjectComparisonOptions.IgnoreTextOrder); bool ignoreTextOutsideOfChildren = ignoreText || ((options & XObjectComparisonOptions.IgnoreTextOutsideOfChildren) == XObjectComparisonOptions.IgnoreTextOutsideOfChildren); bool ignoreProcessingInstructions = (options & XObjectComparisonOptions.IgnoreProcessingInstructions) == XObjectComparisonOptions.IgnoreProcessingInstructions; bool ignoreAdditionalProcessingInstructionss = ignoreProcessingInstructions || ((options & XObjectComparisonOptions.IgnoreAdditionalProcessingInstructions) == XObjectComparisonOptions.IgnoreAdditionalProcessingInstructions); bool ignoreProcessingInstructionOrder = ignoreAdditionalProcessingInstructionss || ((options & XObjectComparisonOptions.IgnoreProcessingInstructionOrder) == XObjectComparisonOptions.IgnoreProcessingInstructionOrder); bool ignoreDocumentTypes = (options & XObjectComparisonOptions.IgnoreDocumentTypes) == XObjectComparisonOptions.IgnoreDocumentTypes; bool ignoreAdditionalDocumentTypess = ignoreDocumentTypes || ((options & XObjectComparisonOptions.IgnoreAdditionalDocumentTypes) == XObjectComparisonOptions.IgnoreAdditionalDocumentTypes); bool ignoreAllChildren = ignoreElements && ignoreProcessingInstructions && ignoreDocumentTypes && ignoreComments && ignoreText; bool allOrderSignificant = !ignoreElementOrder && !ignoreProcessingInstructionOrder && !ignoreCommentOrder && !ignoreTextOrder && !ignoreTextOutsideOfChildren; Stack<XObject, XObject> stack = new Stack<XObject, XObject>(); stack.Push(obj1, obj2); while (stack.TryPop(out obj1, out obj2)) { // Generic equality checks if (ReferenceEquals(obj1, obj2)) continue; if (ReferenceEquals(obj1, null) || ReferenceEquals(obj2, null) || obj1.NodeType != obj2.NodeType) return new Tuple<XObject, XObject>(obj1, obj2); XContainer container1 = null; XContainer container2 = null; // Perform type specific checks. switch (obj1.NodeType) { case XmlNodeType.Document: // Process as container container1 = (XContainer)obj1; container2 = (XContainer)obj2; break; case XmlNodeType.Element: XElement el1 = (XElement)obj1; XElement el2 = (XElement)obj2; if (el1.Name != el2.Name) return new Tuple<XObject, XObject>(obj1, obj2); if (!ignoreAttributes) { // Add attributes to stack for processing if (ignoreAttributeOrder) { // Build dictionary of attributes Dictionary<XName, XAttribute> el2Attributes = el2.Attributes() // ReSharper disable once PossibleNullReferenceException .ToDictionary(a => a.Name); // Ignore attribute order foreach (XAttribute attribute1 in el1.Attributes()) { XAttribute attribute2; // ReSharper disable once PossibleNullReferenceException if (!el2Attributes.TryGetValue(attribute1.Name, out attribute2) || !stringComparer.Equals(attribute1.Value, attribute2.Value)) return new Tuple<XObject, XObject>(attribute1, attribute2); // ReSharper disable once PossibleNullReferenceException el2Attributes.Remove(attribute2.Name); } // If additional attributes on the second element are not being ignored, check if we // additional attributes. if (!ignoreAdditionalAttributes && el2Attributes.Count > 0) return new Tuple<XObject, XObject>(null, el2Attributes.Values.First()); } else { // Attribute order matters XAttribute attribute1 = el1.FirstAttribute; XAttribute attribute2 = el2.FirstAttribute; while (attribute1 != null) { // We have less attributes on the second element than the first if (attribute2 == null || !stringComparer.Equals(attribute1.Value, attribute2.Value)) return new Tuple<XObject, XObject>(attribute1, attribute2); attribute1 = attribute1.NextAttribute; attribute2 = attribute2.NextAttribute; } if (attribute2 != null) return new Tuple<XObject, XObject>(null, attribute2); } } // Process as container container1 = el1; container2 = el2; break; case XmlNodeType.Attribute: XAttribute attr1 = (XAttribute)obj1; XAttribute attr2 = (XAttribute)obj2; if (!ignoreAttributes && (attr1.Name != attr2.Name || !stringComparer.Equals(attr1.Value, attr2.Value))) return new Tuple<XObject, XObject>(attr1, attr2); break; case XmlNodeType.ProcessingInstruction: XProcessingInstruction pi1 = (XProcessingInstruction)obj1; XProcessingInstruction pi2 = (XProcessingInstruction)obj2; if (!ignoreProcessingInstructions && (!stringComparer.Equals(pi1.Target, pi2.Target) || !stringComparer.Equals(pi1.Data, pi2.Data))) return new Tuple<XObject, XObject>(pi1, pi2); break; case XmlNodeType.DocumentType: XDocumentType dt1 = (XDocumentType)obj1; XDocumentType dt2 = (XDocumentType)obj2; if (!ignoreDocumentTypes && (!stringComparer.Equals(dt1.Name, dt2.Name) || !stringComparer.Equals(dt1.PublicId, dt2.PublicId) || !stringComparer.Equals(dt1.SystemId, dt2.SystemId) || !stringComparer.Equals(dt1.InternalSubset, dt2.InternalSubset))) return new Tuple<XObject, XObject>(dt1, dt2); break; case XmlNodeType.Text: case XmlNodeType.CDATA: XText txt1 = (XText)obj1; XText txt2 = (XText)obj2; if (!ignoreText && !stringComparer.Equals(txt1.Value, txt2.Value)) return new Tuple<XObject, XObject>(txt1, txt2); break; case XmlNodeType.Comment: XComment comment1 = (XComment)obj1; XComment comment2 = (XComment)obj2; if (!ignoreComments && !stringComparer.Equals(comment1.Value, comment2.Value)) return new Tuple<XObject, XObject>(comment1, comment2); break; } // If we're not a container, or we're ignoring container contents, we can move on. if (container1 == null || ignoreAllChildren) continue; if (allOrderSignificant) { // We care about the order of everything XNode n1 = container1.FirstNode; XNode n2 = container2.FirstNode; while (n1 != null) { if (n2 == null || n1.NodeType != n2.NodeType) return new Tuple<XObject, XObject>(n1, n2); stack.Push(n1, n2); n1 = n1.NextNode; n2 = n2.NextNode; } if (n2 != null) return new Tuple<XObject, XObject>(null, n2); continue; } // We care about the order of somethings, but not others. List<XElement> elements1; List<XElement> elements2; if (!ignoreElements && ignoreElementOrder) { elements1 = new List<XElement>(); elements2 = new List<XElement>(); } else elements1 = elements2 = null; List<XProcessingInstruction> processingInstructions1; List<XProcessingInstruction> processingInstructions2; if (!ignoreProcessingInstructions && ignoreProcessingInstructionOrder) { processingInstructions1 = new List<XProcessingInstruction>(); processingInstructions2 = new List<XProcessingInstruction>(); } else processingInstructions1 = processingInstructions2 = null; List<XComment> comments1; List<XComment> comments2; if (!ignoreComments && ignoreCommentOrder) { comments1 = new List<XComment>(); comments2 = new List<XComment>(); } else comments1 = comments2 = null; List<XText> texts1; List<XText> texts2; if (!ignoreText && ignoreTextOrder) { texts1 = new List<XText>(); texts2 = new List<XText>(); } else texts1 = texts2 = null; XDocumentType documentType1 = null; XDocumentType documentType2 = null; bool hasChildren = false; bool hasText = false; // Build child lists from first node List<XObject> l1 = new List<XObject>(); XNode n = container1.FirstNode; while (n != null) { switch (n.NodeType) { case XmlNodeType.Element: hasChildren = true; if (elements1 != null) elements1.Add((XElement)n); else if (!ignoreElements) l1.Add(n); break; case XmlNodeType.Text: case XmlNodeType.CDATA: hasText = true; if (texts1 != null) texts1.Add((XText)n); else if (!ignoreText) l1.Add(n); break; case XmlNodeType.ProcessingInstruction: if (processingInstructions1 != null) processingInstructions1.Add((XProcessingInstruction)n); else if (!ignoreProcessingInstructions) l1.Add(n); break; case XmlNodeType.Comment: if (comments1 != null) comments1.Add((XComment)n); else if (!ignoreComments) l1.Add(n); break; case XmlNodeType.DocumentType: if (!ignoreDocumentTypes) documentType1 = (XDocumentType)n; break; } n = n.NextNode; } // Build child lists from second node List<XObject> l2 = new List<XObject>(); n = container2.FirstNode; while (n != null) { switch (n.NodeType) { case XmlNodeType.Element: hasChildren = true; if (elements2 != null) elements2.Add((XElement)n); else if (!ignoreElements) l2.Add(n); break; case XmlNodeType.Text: case XmlNodeType.CDATA: hasText = true; if (texts2 != null) texts2.Add((XText)n); else if (!ignoreText) l2.Add(n); break; case XmlNodeType.ProcessingInstruction: if (processingInstructions2 != null) processingInstructions2.Add((XProcessingInstruction)n); else if (!ignoreProcessingInstructions) l2.Add(n); break; case XmlNodeType.Comment: if (comments2 != null) comments2.Add((XComment)n); else if (!ignoreComments) l2.Add(n); break; case XmlNodeType.DocumentType: if (!ignoreDocumentTypes) documentType2 = (XDocumentType)n; break; } n = n.NextNode; } // Add document type comparison if necessary (note we never consider order of the document type as it // always appears before the root node. if (!ignoreDocumentTypes) { if (documentType1 != null) { if (documentType2 == null || !stringComparer.Equals(documentType1.Name, documentType2.Name) || !stringComparer.Equals(documentType1.PublicId, documentType2.PublicId) || !stringComparer.Equals(documentType1.SystemId, documentType2.SystemId) || !stringComparer.Equals(documentType1.InternalSubset, documentType2.InternalSubset)) return new Tuple<XObject, XObject>(documentType1, documentType2); // Don't push onto stack, as we've already done the comparison at this point. } else if (!ignoreAdditionalDocumentTypess && documentType2 != null) return new Tuple<XObject, XObject>(null, documentType2); } // Add order specific nodes to stack for processing. bool stripText = hasChildren && hasText && ignoreTextOutsideOfChildren; List<XObject>.Enumerator l1Enumerator = l1.GetEnumerator(); List<XObject>.Enumerator l2Enumerator = l2.GetEnumerator(); while (l1Enumerator.MoveNext()) { if (stripText && l1Enumerator.Current is XText) continue; do { if (!l2Enumerator.MoveNext()) return new Tuple<XObject, XObject>(l1Enumerator.Current, null); } while (stripText && l2Enumerator.Current is XText); if (l1Enumerator.Current.NodeType != l2Enumerator.Current.NodeType) return new Tuple<XObject, XObject>(l1Enumerator.Current, l2Enumerator.Current); stack.Push(l1Enumerator.Current, l2Enumerator.Current); } while (l2Enumerator.MoveNext()) { if (!stripText || !(l2Enumerator.Current is XText)) return new Tuple<XObject, XObject>(null, l2Enumerator.Current); } // Add order independent nodes for processing // ReSharper disable PossibleNullReferenceException if (elements1 != null) { foreach (XElement e1 in elements1) { XElement e2; if (!elements2.TryRemove(e => e.Name == e1.Name, out e2)) return new Tuple<XObject, XObject>(e1, null); stack.Push(e1, e2); } if (!ignoreAdditionalElements && texts2.Count > 0) return new Tuple<XObject, XObject>(null, elements2.First()); } if (texts1 != null) { foreach (XText t1 in texts1) { XText t2; if (!texts2.TryRemove(t => stringComparer.Equals(t1.Value, t.Value), out t2)) return new Tuple<XObject, XObject>(t1, null); // Don't push onto stack, as we've already done the comparison at this point. } if (!ignoreAdditionalText && texts2.Count > 0) return new Tuple<XObject, XObject>(null, texts2.First()); } if (comments1 != null) { foreach (XComment c1 in comments1) { XComment c2; if (!comments2.TryRemove(c => stringComparer.Equals(c1.Value, c.Value), out c2)) return new Tuple<XObject, XObject>(c1, null); // Don't push onto stack, as we've already done the comparison at this point. } if (!ignoreAdditionalCommentss && comments2.Count > 0) return new Tuple<XObject, XObject>(null, comments2.First()); } if (processingInstructions1 != null) { foreach (XProcessingInstruction pi1 in processingInstructions1) { XProcessingInstruction pi2; if (!processingInstructions2.TryRemove(pi => stringComparer.Equals(pi.Target, pi1.Target) && stringComparer.Equals(pi.Data, pi1.Data), out pi2)) return new Tuple<XObject, XObject>(pi1, null); // Don't push onto stack, as we've already done the comparison at this point. } if (!ignoreAdditionalProcessingInstructionss && processingInstructions2.Count > 0) return new Tuple<XObject, XObject>(null, processingInstructions2.First()); } // ReSharper restore PossibleNullReferenceException } // Successful comparisons result in null. return null; }