1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Linq;
10 using System.Text;
11 using System.Xml;
12 using System.Xml.XPath;
13 
14 namespace System.ServiceModel.Syndication.Tests
15 {
16     internal enum DiffType
17     {
18         None,
19         Success,
20         Element,
21         Whitespace,
22         Comment,
23         PI,
24         Text,
25         CData,
26         Attribute,
27         NS,
28         Prefix,
29         SourceExtra,
30         TargetExtra,
31         NodeType
32     }
33 
34     public class XmlDiff
35     {
36         private XmlDiffDocument _SourceDoc;
37         private XmlDiffDocument _TargetDoc;
38 
39         private XmlTextWriter _Writer;     // Writer to write out the result
40         private StringBuilder _Output;
41 
42         // Option Flags
43         private XmlDiffOption _XmlDiffOption = XmlDiffOption.None;
44 
XmlDiff()45         public XmlDiff()
46         {
47             _XmlDiffOption = XmlDiffOption.IgnoreEmptyElement |
48                              XmlDiffOption.IgnoreWhitespace |
49                              XmlDiffOption.IgnoreAttributeOrder |
50                              XmlDiffOption.IgnoreNS |
51                              XmlDiffOption.IgnorePrefix |
52                              XmlDiffOption.IgnoreDTD |
53                              XmlDiffOption.IgnoreChildOrder;
54         }
55 
56         public XmlDiffOption Option
57         {
58             get
59             {
60                 return _XmlDiffOption;
61             }
62             set
63             {
64                 _XmlDiffOption = value;
65             }
66         }
67 
68         internal bool IgnoreEmptyElement
69         {
70             get { return ((_XmlDiffOption & XmlDiffOption.IgnoreEmptyElement) != 0); }
71         }
72 
73         internal bool IgnoreComments
74         {
75             get { return ((_XmlDiffOption & XmlDiffOption.IgnoreComments) != 0); }
76         }
77 
78         internal bool IgnoreAttributeOrder
79         {
80             get { return ((_XmlDiffOption & XmlDiffOption.IgnoreAttributeOrder) != 0); }
81         }
82 
83         internal bool IgnoreWhitespace
84         {
85             get { return ((_XmlDiffOption & XmlDiffOption.IgnoreWhitespace) != 0); }
86         }
87 
88         internal bool IgnoreNS
89         {
90             get { return ((_XmlDiffOption & XmlDiffOption.IgnoreNS) != 0); }
91         }
92 
93         internal bool IgnorePrefix
94         {
95             get { return ((_XmlDiffOption & XmlDiffOption.IgnorePrefix) != 0); }
96         }
97 
98         internal bool IgnoreDTD
99         {
100             get { return ((_XmlDiffOption & XmlDiffOption.IgnoreDTD) != 0); }
101         }
102 
103         internal bool IgnoreChildOrder
104         {
105             get { return ((_XmlDiffOption & XmlDiffOption.IgnoreChildOrder) != 0); }
106         }
107 
108         internal bool ConcatenateAdjacentTextNodes
109         {
110             get { return ((_XmlDiffOption & XmlDiffOption.ConcatenateAdjacentTextNodes) != 0); }
111         }
112 
113         internal bool TreatWhitespaceTextAsWSNode
114         {
115             get { return ((_XmlDiffOption & XmlDiffOption.TreatWhitespaceTextAsWSNode) != 0); }
116         }
117 
118         internal bool ParseAttributeValuesAsQName
119         {
120             get { return ((_XmlDiffOption & XmlDiffOption.ParseAttributeValuesAsQName) != 0); }
121         }
122 
123         internal bool DontWriteMatchingNodesToOutput
124         {
125             get { return ((_XmlDiffOption & XmlDiffOption.DontWriteMatchingNodesToOutput) != 0); }
126         }
127 
128         internal bool DontWriteAnythingToOutput
129         {
130             get { return ((_XmlDiffOption & XmlDiffOption.DontWriteAnythingToOutput) != 0); }
131         }
132 
InitFiles()133         private void InitFiles()
134         {
135             _SourceDoc = new XmlDiffDocument();
136             _SourceDoc.Option = _XmlDiffOption;
137             _TargetDoc = new XmlDiffDocument();
138             _TargetDoc.Option = this.Option;
139             _Output = new StringBuilder(String.Empty);
140         }
141 
Compare(String source, String target)142         public bool Compare(String source, String target)
143         {
144             InitFiles();
145             _SourceDoc.Load(source);
146             _TargetDoc.Load(target);
147             return Diff();
148         }
149 
Compare(XmlReader source, XmlReader target)150         public bool Compare(XmlReader source, XmlReader target)
151         {
152             InitFiles();
153             _SourceDoc.Load(source);
154             _TargetDoc.Load(target);
155             return Diff();
156         }
157 
Compare(XmlReader source, XmlReader target, XmlDiffAdvancedOptions advOptions)158         public bool Compare(XmlReader source, XmlReader target, XmlDiffAdvancedOptions advOptions)
159         {
160             InitFiles();
161 
162             _SourceDoc.Load(source);
163             _TargetDoc.Load(target);
164             XPathNavigator nav = _SourceDoc.CreateNavigator();
165 
166             if (!string.IsNullOrEmpty(advOptions.IgnoreChildOrderExpr))
167             {
168                 XPathExpression expr = GenerateXPathExpression(
169                     advOptions.IgnoreChildOrderExpr,
170                     advOptions,
171                     nav);
172 
173                 _SourceDoc.SortChildren(expr);
174                 _TargetDoc.SortChildren(expr);
175             }
176 
177             if (advOptions.IgnoreNodesExpr != null && advOptions.IgnoreNodesExpr != "")
178             {
179                 XPathExpression expr = GenerateXPathExpression(
180                     advOptions.IgnoreNodesExpr,
181                     advOptions,
182                     nav);
183 
184                 _SourceDoc.IgnoreNodes(expr);
185                 _TargetDoc.IgnoreNodes(expr);
186             }
187 
188             if (advOptions.IgnoreValuesExpr != null && advOptions.IgnoreValuesExpr != "")
189             {
190                 XPathExpression expr = GenerateXPathExpression(
191                     advOptions.IgnoreValuesExpr,
192                     advOptions,
193                     nav);
194 
195                 _SourceDoc.IgnoreValues(expr);
196                 _TargetDoc.IgnoreValues(expr);
197             }
198 
199             return Diff();
200         }
201 
GenerateXPathExpression(string expression, XmlDiffAdvancedOptions advOptions, XPathNavigator nav)202         private static XPathExpression GenerateXPathExpression(string expression, XmlDiffAdvancedOptions advOptions, XPathNavigator nav)
203         {
204             XPathExpression expr = nav.Compile(expression);
205 
206             if (advOptions.HadAddedNamespace())
207             {
208                 XmlNamespaceManager xnm = new XmlNamespaceManager(nav.NameTable);
209 
210                 foreach (KeyValuePair<string, string> each in advOptions.AddedNamespaces)
211                 {
212                     xnm.AddNamespace(each.Key, each.Value);
213                 }
214 
215                 expr.SetContext(xnm);
216             }
217 
218             return expr;
219         }
220 
221 
Diff()222         private bool Diff()
223         {
224             bool flag = false;
225             _Writer = new XmlTextWriter(new StringWriter(_Output));
226             _Writer.WriteStartElement(String.Empty, "Root", String.Empty);
227 
228             flag = CompareChildren(_SourceDoc, _TargetDoc);
229 
230             _Writer.WriteEndElement();
231             _Writer.Close();
232             return flag;
233         }
234 
235         //  This function is being called recursively to compare the children of a certain node.
236         //  When calling this function the navigator should be pointing at the parent node whose children
237         //  we wants to compare and return the navigator back to the same node before exiting from it.
CompareChildren(XmlDiffNode sourceNode, XmlDiffNode targetNode)238         private bool CompareChildren(XmlDiffNode sourceNode, XmlDiffNode targetNode)
239         {
240             XmlDiffNode sourceChild = sourceNode.FirstChild;
241             XmlDiffNode targetChild = targetNode.FirstChild;
242 
243             bool flag = true;
244             bool tempFlag = true;
245 
246             DiffType result;
247 
248             // Loop to compare all the child elements of the parent node
249             while (sourceChild != null || targetChild != null)
250             {
251                 //Console.WriteLine( (sourceChild!=null)?(sourceChild.NodeType.ToString()):"null" );
252                 //Console.WriteLine( (targetChild!=null)?(targetChild.NodeType.ToString()):"null" );
253                 if (sourceChild != null)
254                 {
255                     if (targetChild != null)
256                     {
257                         // Both Source and Target Read successful
258                         if ((result = CompareNodes(sourceChild, targetChild)) == DiffType.Success)
259                         {
260                             // Child nodes compared successfully, write out the result
261                             WriteResult(sourceChild, targetChild, DiffType.Success);
262                             // Check whether this Node has  Children, if it does call CompareChildren recursively
263                             if (sourceChild.FirstChild != null)
264                             {
265                                 tempFlag = CompareChildren(sourceChild, targetChild);
266                             }
267                             else if (targetChild.FirstChild != null)
268                             {
269                                 WriteResult(null, targetChild, DiffType.TargetExtra);
270                                 tempFlag = false;
271                             }
272                             // set the compare flag
273                             flag = (flag && tempFlag);
274 
275                             // Writing out End Element to the result
276                             if (sourceChild.NodeType == XmlDiffNodeType.Element && !(sourceChild is XmlDiffEmptyElement))
277                             {
278                                 XmlDiffElement sourceElem = sourceChild as XmlDiffElement;
279                                 XmlDiffElement targetElem = targetChild as XmlDiffElement;
280                                 Debug.Assert(sourceElem != null);
281                                 Debug.Assert(targetElem != null);
282                                 if (!DontWriteMatchingNodesToOutput && !DontWriteAnythingToOutput)
283                                 {
284                                     _Writer.WriteStartElement(String.Empty, "Node", String.Empty);
285                                     _Writer.WriteAttributeString(String.Empty, "SourceLineNum", String.Empty, sourceElem.EndLineNumber.ToString());
286                                     _Writer.WriteAttributeString(String.Empty, "SourceLinePos", String.Empty, sourceElem.EndLinePosition.ToString());
287                                     _Writer.WriteAttributeString(String.Empty, "TargetLineNum", String.Empty, targetElem.EndLineNumber.ToString());
288                                     _Writer.WriteAttributeString(String.Empty, "TargetLinePos", String.Empty, targetElem.EndLinePosition.ToString());
289                                     _Writer.WriteStartElement(String.Empty, "Diff", String.Empty);
290                                     _Writer.WriteEndElement();
291 
292                                     _Writer.WriteStartElement(String.Empty, "Lexical-equal", String.Empty);
293                                     _Writer.WriteCData("</" + sourceElem.Name + ">");
294                                     _Writer.WriteEndElement();
295                                     _Writer.WriteEndElement();
296                                 }
297                             }
298 
299                             // Move to Next child
300                             sourceChild = sourceChild.NextSibling;
301                             targetChild = targetChild.NextSibling;
302                         }
303                         else
304                         {
305                             // Child nodes not matched, start the recovery process
306                             bool recoveryFlag = false;
307 
308                             // Try to match the source node with the target nodes at the same level
309                             XmlDiffNode backupTargetChild = targetChild.NextSibling;
310                             while (!recoveryFlag && backupTargetChild != null)
311                             {
312                                 if (CompareNodes(sourceChild, backupTargetChild) == DiffType.Success)
313                                 {
314                                     recoveryFlag = true;
315                                     do
316                                     {
317                                         WriteResult(null, targetChild, DiffType.TargetExtra);
318                                         targetChild = targetChild.NextSibling;
319                                     } while (targetChild != backupTargetChild);
320                                     break;
321                                 }
322                                 backupTargetChild = backupTargetChild.NextSibling;
323                             }
324 
325                             // If not recovered, try to match the target node with the source nodes at the same level
326                             if (!recoveryFlag)
327                             {
328                                 XmlDiffNode backupSourceChild = sourceChild.NextSibling;
329                                 while (!recoveryFlag && backupSourceChild != null)
330                                 {
331                                     if (CompareNodes(backupSourceChild, targetChild) == DiffType.Success)
332                                     {
333                                         recoveryFlag = true;
334                                         do
335                                         {
336                                             WriteResult(sourceChild, null, DiffType.SourceExtra);
337                                             sourceChild = sourceChild.NextSibling;
338                                         } while (sourceChild != backupSourceChild);
339                                         break;
340                                     }
341                                     backupSourceChild = backupSourceChild.NextSibling;
342                                 }
343                             }
344 
345                             // If still not recovered, write both of them as different nodes and move on
346                             if (!recoveryFlag)
347                             {
348                                 WriteResult(sourceChild, targetChild, result);
349 
350                                 // Check whether this Node has  Children, if it does call CompareChildren recursively
351                                 if (sourceChild.FirstChild != null)
352                                 {
353                                     tempFlag = CompareChildren(sourceChild, targetChild);
354                                 }
355                                 else if (targetChild.FirstChild != null)
356                                 {
357                                     WriteResult(null, targetChild, DiffType.TargetExtra);
358                                     tempFlag = false;
359                                 }
360 
361                                 // Writing out End Element to the result
362                                 bool bSourceNonEmpElemEnd = (sourceChild.NodeType == XmlDiffNodeType.Element && !(sourceChild is XmlDiffEmptyElement));
363                                 bool bTargetNonEmpElemEnd = (targetChild.NodeType == XmlDiffNodeType.Element && !(targetChild is XmlDiffEmptyElement));
364                                 if (bSourceNonEmpElemEnd || bTargetNonEmpElemEnd)
365                                 {
366                                     XmlDiffElement sourceElem = sourceChild as XmlDiffElement;
367                                     XmlDiffElement targetElem = targetChild as XmlDiffElement;
368                                     if (!DontWriteAnythingToOutput)
369                                     {
370                                         _Writer.WriteStartElement(String.Empty, "Node", String.Empty);
371                                         _Writer.WriteAttributeString(String.Empty, "SourceLineNum", String.Empty, (sourceElem != null) ? sourceElem.EndLineNumber.ToString() : "-1");
372                                         _Writer.WriteAttributeString(String.Empty, "SourceLinePos", String.Empty, (sourceElem != null) ? sourceElem.EndLinePosition.ToString() : "-1");
373                                         _Writer.WriteAttributeString(String.Empty, "TargetLineNum", String.Empty, (targetElem != null) ? targetElem.EndLineNumber.ToString() : "-1");
374                                         _Writer.WriteAttributeString(String.Empty, "TargetLinePos", String.Empty, (targetElem != null) ? targetElem.EndLineNumber.ToString() : "-1");
375                                         _Writer.WriteStartElement(String.Empty, "Diff", String.Empty);
376                                         _Writer.WriteAttributeString(String.Empty, "DiffType", String.Empty, GetDiffType(result));
377                                         if (bSourceNonEmpElemEnd)
378                                         {
379                                             _Writer.WriteStartElement(String.Empty, "File1", String.Empty);
380                                             _Writer.WriteCData("</" + sourceElem.Name + ">");
381                                             _Writer.WriteEndElement();
382                                         }
383 
384                                         if (bTargetNonEmpElemEnd)
385                                         {
386                                             _Writer.WriteStartElement(String.Empty, "File2", String.Empty);
387                                             _Writer.WriteCData("</" + targetElem.Name + ">");
388                                             _Writer.WriteEndElement();
389                                         }
390 
391                                         _Writer.WriteEndElement();
392 
393                                         _Writer.WriteStartElement(String.Empty, "Lexical-equal", String.Empty);
394                                         _Writer.WriteEndElement();
395                                         _Writer.WriteEndElement();
396                                     }
397                                 }
398 
399                                 sourceChild = sourceChild.NextSibling;
400                                 targetChild = targetChild.NextSibling;
401                             }
402 
403                             flag = false;
404                         }
405                     }
406                     else
407                     {
408                         // SourceRead NOT NULL targetRead is NULL
409                         WriteResult(sourceChild, null, DiffType.SourceExtra);
410                         flag = false;
411                         sourceChild = sourceChild.NextSibling;
412                     }
413                 }
414                 else if (targetChild != null)
415                 {
416                     // SourceRead is NULL and targetRead is NOT NULL
417                     WriteResult(null, targetChild, DiffType.TargetExtra);
418                     flag = false;
419                     targetChild = targetChild.NextSibling;
420                 }
421                 else
422                 {
423                     //Both SourceRead and TargetRead is NULL
424                     Debug.Assert(false, "Impossible Situation for comparison");
425                 }
426             }
427             return flag;
428         }
429 
430         /// <summary>
431         /// Combines multiple adjacent text nodes into a single node.
432         /// Sometimes the binary reader/writer will break text nodes in multiple
433         /// nodes; i.e., if the user calls
434         /// <code>writer.WriteString(&quot;foo&quot;); writer.WriteString(&quot;bar&quot;)</code>
435         /// on the writer and then iterate calling reader.Read() on the document, the binary
436         /// reader will report two text nodes (&quot;foo&quot;, &quot;bar&quot;), while the
437         /// other readers will report a single text node (&quot;foobar&quot;) – notice that
438         /// this is not a problem; calling ReadString, or Read[Element]ContentAsString in
439         /// all readers will return &quot;foobar&quot;.
440         /// </summary>
441         /// <param name="node">the node to be normalized. Any siblings of
442         /// type XmlDiffCharacterData will be removed, with their values appended
443         /// to the value of this node</param>
DoConcatenateAdjacentTextNodes(XmlDiffCharacterData node)444         private void DoConcatenateAdjacentTextNodes(XmlDiffCharacterData node)
445         {
446             if (node.NextSibling == null) return;
447             if (!(node.NextSibling is XmlDiffCharacterData)) return;
448             StringBuilder sb = new StringBuilder();
449             sb.Append(node.Value);
450             XmlDiffCharacterData nextNode = (node.NextSibling as XmlDiffCharacterData);
451             while (nextNode != null)
452             {
453                 sb.Append(nextNode.Value);
454                 XmlDiffCharacterData curNextNode = nextNode;
455                 nextNode = (nextNode.NextSibling as XmlDiffCharacterData);
456                 curNextNode.ParentNode.DeleteChild(curNextNode);
457             }
458             node.Value = sb.ToString();
459         }
460 
461         // This function compares the two nodes passed to it, depending upon the options set by the user.
CompareNodes(XmlDiffNode sourceNode, XmlDiffNode targetNode)462         private DiffType CompareNodes(XmlDiffNode sourceNode, XmlDiffNode targetNode)
463         {
464             if (sourceNode.NodeType != targetNode.NodeType)
465             {
466                 return DiffType.NodeType;
467             }
468             switch (sourceNode.NodeType)
469             {
470                 case XmlDiffNodeType.Element:
471                     XmlDiffElement sourceElem = sourceNode as XmlDiffElement;
472                     XmlDiffElement targetElem = targetNode as XmlDiffElement;
473                     Debug.Assert(sourceElem != null);
474                     Debug.Assert(targetElem != null);
475                     if (!IgnoreNS)
476                     {
477                         if (sourceElem.NamespaceURI != targetElem.NamespaceURI)
478                         {
479                             return DiffType.NS;
480                         }
481                     }
482                     if (!IgnorePrefix)
483                     {
484                         if (sourceElem.Prefix != targetElem.Prefix)
485                         {
486                             return DiffType.Prefix;
487                         }
488                     }
489 
490                     if (sourceElem.LocalName != targetElem.LocalName)
491                     {
492                         return DiffType.Element;
493                     }
494                     if (!IgnoreEmptyElement)
495                     {
496                         if ((sourceElem is XmlDiffEmptyElement) != (targetElem is XmlDiffEmptyElement))
497                         {
498                             return DiffType.Element;
499                         }
500                     }
501                     if (!CompareAttributes(sourceElem, targetElem))
502                     {
503                         return DiffType.Attribute;
504                     }
505                     break;
506                 case XmlDiffNodeType.Text:
507                 case XmlDiffNodeType.Comment:
508                 case XmlDiffNodeType.WS:
509                     XmlDiffCharacterData sourceText = sourceNode as XmlDiffCharacterData;
510                     XmlDiffCharacterData targetText = targetNode as XmlDiffCharacterData;
511                     Debug.Assert(sourceText != null);
512                     Debug.Assert(targetText != null);
513 
514                     if (ConcatenateAdjacentTextNodes)
515                     {
516                         DoConcatenateAdjacentTextNodes(sourceText);
517                         DoConcatenateAdjacentTextNodes(targetText);
518                     }
519 
520                     if (IgnoreWhitespace)
521                     {
522                         if (sourceText.Value.Trim() == targetText.Value.Trim())
523                         {
524                             return DiffType.Success;
525                         }
526                     }
527                     else
528                     {
529                         if (sourceText.Value == targetText.Value)
530                         {
531                             return DiffType.Success;
532                         }
533                     }
534                     if (sourceText.NodeType == XmlDiffNodeType.Text || sourceText.NodeType == XmlDiffNodeType.WS) //should ws nodes also as text nodes???
535                     {
536                         return DiffType.Text;
537                     }
538                     else if (sourceText.NodeType == XmlDiffNodeType.Comment)
539                     {
540                         return DiffType.Comment;
541                     }
542                     else if (sourceText.NodeType == XmlDiffNodeType.CData)
543                     {
544                         return DiffType.CData;
545                     }
546                     else
547                     {
548                         return DiffType.None;
549                     }
550                 case XmlDiffNodeType.PI:
551                     XmlDiffProcessingInstruction sourcePI = sourceNode as XmlDiffProcessingInstruction;
552                     XmlDiffProcessingInstruction targetPI = targetNode as XmlDiffProcessingInstruction;
553                     Debug.Assert(sourcePI != null);
554                     Debug.Assert(targetPI != null);
555                     if (sourcePI.Name != targetPI.Name || sourcePI.Value != targetPI.Value)
556                     {
557                         return DiffType.PI;
558                     }
559                     break;
560                 default:
561                     break;
562             }
563 
564             return DiffType.Success;
565         }
566 
567         // This function writes the result in XML format so that it can be used by other applications to display the diff
WriteResult(XmlDiffNode sourceNode, XmlDiffNode targetNode, DiffType result)568         private void WriteResult(XmlDiffNode sourceNode, XmlDiffNode targetNode, DiffType result)
569         {
570             if (DontWriteAnythingToOutput) return;
571 
572             if (result != DiffType.Success || !DontWriteMatchingNodesToOutput)
573             {
574                 _Writer.WriteStartElement(String.Empty, "Node", String.Empty);
575                 _Writer.WriteAttributeString(String.Empty, "SourceLineNum", String.Empty, (sourceNode != null) ? sourceNode.LineNumber.ToString() : "-1");
576                 _Writer.WriteAttributeString(String.Empty, "SourceLinePos", String.Empty, (sourceNode != null) ? sourceNode.LinePosition.ToString() : "-1");
577                 _Writer.WriteAttributeString(String.Empty, "TargetLineNum", String.Empty, (targetNode != null) ? targetNode.LineNumber.ToString() : "-1");
578                 _Writer.WriteAttributeString(String.Empty, "TargetLinePos", String.Empty, (targetNode != null) ? targetNode.LinePosition.ToString() : "-1");
579             }
580             if (result == DiffType.Success)
581             {
582                 if (!DontWriteMatchingNodesToOutput)
583                 {
584                     _Writer.WriteStartElement(String.Empty, "Diff", String.Empty);
585                     _Writer.WriteEndElement();
586 
587                     _Writer.WriteStartElement(String.Empty, "Lexical-equal", String.Empty);
588                     if (sourceNode.NodeType == XmlDiffNodeType.CData)
589                     {
590                         _Writer.WriteString("<![CDATA[");
591                         _Writer.WriteCData(GetNodeText(sourceNode, result));
592                         _Writer.WriteString("]]>");
593                     }
594                     else
595                     {
596                         _Writer.WriteCData(GetNodeText(sourceNode, result));
597                     }
598                     _Writer.WriteEndElement();
599                 }
600             }
601             else
602             {
603                 _Writer.WriteStartElement(String.Empty, "Diff", String.Empty);
604                 _Writer.WriteAttributeString(String.Empty, "DiffType", String.Empty, GetDiffType(result));
605 
606                 if (sourceNode != null)
607                 {
608                     _Writer.WriteStartElement(String.Empty, "File1", String.Empty);
609                     if (sourceNode.NodeType == XmlDiffNodeType.CData)
610                     {
611                         _Writer.WriteString("<![CDATA[");
612                         _Writer.WriteCData(GetNodeText(sourceNode, result));
613                         _Writer.WriteString("]]>");
614                     }
615                     else
616                     {
617                         _Writer.WriteString(GetNodeText(sourceNode, result));
618                     }
619                     _Writer.WriteEndElement();
620                 }
621 
622                 if (targetNode != null)
623                 {
624                     _Writer.WriteStartElement(String.Empty, "File2", String.Empty);
625                     if (targetNode.NodeType == XmlDiffNodeType.CData)
626                     {
627                         _Writer.WriteString("<![CDATA[");
628                         _Writer.WriteCData(GetNodeText(targetNode, result));
629                         _Writer.WriteString("]]>");
630                     }
631                     else
632                     {
633                         _Writer.WriteString(GetNodeText(targetNode, result));
634                     }
635                     _Writer.WriteEndElement();
636                 }
637 
638                 _Writer.WriteEndElement();
639 
640                 _Writer.WriteStartElement(String.Empty, "Lexical-equal", String.Empty);
641                 _Writer.WriteEndElement();
642             }
643             if (result != DiffType.Success || !DontWriteMatchingNodesToOutput)
644             {
645                 _Writer.WriteEndElement();
646             }
647         }
648 
649         // This is a helper function for WriteResult.  It gets the Xml representation of the different node we wants
650         // to write out and all it's children.
GetNodeText(XmlDiffNode diffNode, DiffType result)651         private String GetNodeText(XmlDiffNode diffNode, DiffType result)
652         {
653             string text = string.Empty;
654             switch (diffNode.NodeType)
655             {
656                 case XmlDiffNodeType.Element:
657                     if (result == DiffType.SourceExtra || result == DiffType.TargetExtra)
658                         return diffNode.OuterXml;
659                     StringWriter str = new StringWriter();
660                     XmlTextWriter writer = new XmlTextWriter(str);
661                     XmlDiffElement diffElem = diffNode as XmlDiffElement;
662                     Debug.Assert(diffNode != null);
663                     writer.WriteStartElement(diffElem.Prefix, diffElem.LocalName, diffElem.NamespaceURI);
664                     XmlDiffAttribute diffAttr = diffElem.FirstAttribute;
665                     while (diffAttr != null)
666                     {
667                         writer.WriteAttributeString(diffAttr.Prefix, diffAttr.LocalName, diffAttr.NamespaceURI, diffAttr.Value);
668                         diffAttr = (XmlDiffAttribute)diffAttr.NextSibling;
669                     }
670                     if (diffElem is XmlDiffEmptyElement)
671                     {
672                         writer.WriteEndElement();
673                         text = str.ToString();
674                     }
675                     else
676                     {
677                         text = str.ToString();
678                         text += ">";
679                     }
680                     writer.Close();
681                     break;
682                 case XmlDiffNodeType.CData:
683                     text = ((XmlDiffCharacterData)diffNode).Value;
684                     break;
685                 default:
686                     text = diffNode.OuterXml;
687                     break;
688             }
689             return text;
690         }
691 
692         // This function is used to compare the attributes of an element node according to the options set by the user.
CompareAttributes(XmlDiffElement sourceElem, XmlDiffElement targetElem)693         private bool CompareAttributes(XmlDiffElement sourceElem, XmlDiffElement targetElem)
694         {
695             Debug.Assert(sourceElem != null);
696             Debug.Assert(targetElem != null);
697             if (sourceElem.AttributeCount != targetElem.AttributeCount)
698                 return false;
699 
700             if (sourceElem.AttributeCount == 0)
701                 return true;
702 
703             XmlDiffAttribute sourceAttr = sourceElem.FirstAttribute;
704             XmlDiffAttribute targetAttr = targetElem.FirstAttribute;
705 
706             const string xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
707 
708             if (!IgnoreAttributeOrder)
709             {
710                 while (sourceAttr != null && targetAttr != null)
711                 {
712                     if (!IgnoreNS)
713                     {
714                         if (sourceAttr.NamespaceURI != targetAttr.NamespaceURI)
715                         {
716                             return false;
717                         }
718                     }
719 
720                     if (!IgnorePrefix)
721                     {
722                         if (sourceAttr.Prefix != targetAttr.Prefix)
723                         {
724                             return false;
725                         }
726                     }
727 
728                     if (sourceAttr.NamespaceURI == xmlnsNamespace && targetAttr.NamespaceURI == xmlnsNamespace)
729                     {
730                         if (!IgnorePrefix && (sourceAttr.LocalName != targetAttr.LocalName))
731                         {
732                             return false;
733                         }
734                         if (!IgnoreNS && (sourceAttr.Value != targetAttr.Value))
735                         {
736                             return false;
737                         }
738                     }
739                     else
740                     {
741                         if (sourceAttr.LocalName != targetAttr.LocalName)
742                         {
743                             return false;
744                         }
745                         if (sourceAttr.Value != targetAttr.Value)
746                         {
747                             if (ParseAttributeValuesAsQName)
748                             {
749                                 if (sourceAttr.ValueAsQName != null)
750                                 {
751                                     if (!sourceAttr.ValueAsQName.Equals(targetAttr.ValueAsQName)) return false;
752                                 }
753                                 else
754                                 {
755                                     if (targetAttr.ValueAsQName != null) return false;
756                                 }
757                             }
758                             else
759                             {
760                                 return false;
761                             }
762                         }
763                     }
764                     sourceAttr = (XmlDiffAttribute)(sourceAttr.NextSibling);
765                     targetAttr = (XmlDiffAttribute)(targetAttr.NextSibling);
766                 }
767             }
768             else
769             {
770                 Hashtable sourceAttributeMap = new Hashtable();
771                 Hashtable targetAttributeMap = new Hashtable();
772 
773                 while (sourceAttr != null && targetAttr != null)
774                 {
775                     if (IgnorePrefix && sourceAttr.NamespaceURI == xmlnsNamespace)
776                     {
777                         // do nothing
778                     }
779                     else
780                     {
781                         string localNameAndNamespace = sourceAttr.LocalName + "<&&>" + sourceAttr.NamespaceURI;
782                         sourceAttributeMap.Add(localNameAndNamespace, sourceAttr);
783                     }
784                     if (IgnorePrefix && targetAttr.NamespaceURI == xmlnsNamespace)
785                     {
786                         // do nothing
787                     }
788                     else
789                     {
790                         string localNameAndNamespace = targetAttr.LocalName + "<&&>" + targetAttr.NamespaceURI;
791                         targetAttributeMap.Add(localNameAndNamespace, targetAttr);
792                     }
793 
794                     sourceAttr = (XmlDiffAttribute)(sourceAttr.NextSibling);
795                     targetAttr = (XmlDiffAttribute)(targetAttr.NextSibling);
796                 }
797 
798                 if (sourceAttributeMap.Count != targetAttributeMap.Count)
799                 {
800                     return false;
801                 }
802 
803                 foreach (string sourceKey in sourceAttributeMap.Keys)
804                 {
805                     if (!targetAttributeMap.ContainsKey(sourceKey))
806                     {
807                         return false;
808                     }
809                     sourceAttr = (XmlDiffAttribute)sourceAttributeMap[sourceKey];
810                     targetAttr = (XmlDiffAttribute)targetAttributeMap[sourceKey];
811 
812                     if (!IgnorePrefix)
813                     {
814                         if (sourceAttr.Prefix != targetAttr.Prefix)
815                         {
816                             return false;
817                         }
818                     }
819 
820                     if (sourceAttr.Value != targetAttr.Value)
821                     {
822                         if (ParseAttributeValuesAsQName)
823                         {
824                             if (sourceAttr.ValueAsQName != null)
825                             {
826                                 if (!sourceAttr.ValueAsQName.Equals(targetAttr.ValueAsQName)) return false;
827                             }
828                             else
829                             {
830                                 if (targetAttr.ValueAsQName != null) return false;
831                             }
832                         }
833                         else
834                         {
835                             return false;
836                         }
837                     }
838                 }
839             }
840             return true;
841         }
842 
ToXml()843         public string ToXml()
844         {
845             if (_Output != null)
846                 return _Output.ToString();
847             return string.Empty;
848         }
849 
ToXml(string fileName)850         public void ToXml(string fileName)
851         {
852             FileStream file;
853             file = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite);
854             StreamWriter writer = new StreamWriter(file);
855             writer.Write("<?xml-stylesheet type='text/xsl' href='diff.xsl'?>");
856             writer.Write(ToXml());
857             writer.Close();
858             file.Close();
859         }
860 
ToXml(Stream stream)861         public void ToXml(Stream stream)
862         {
863             StreamWriter writer = new StreamWriter(stream);
864             writer.Write("<?xml-stylesheet type='text/xsl' href='diff.xsl'?>");
865             writer.Write(ToXml());
866             writer.Close();
867         }
868 
GetDiffType(DiffType type)869         private String GetDiffType(DiffType type)
870         {
871             switch (type)
872             {
873                 case DiffType.None:
874                     return String.Empty;
875                 case DiffType.Element:
876                     return "1";
877                 case DiffType.Whitespace:
878                     return "2";
879                 case DiffType.Comment:
880                     return "3";
881                 case DiffType.PI:
882                     return "4";
883                 case DiffType.Text:
884                     return "5";
885                 case DiffType.Attribute:
886                     return "6";
887                 case DiffType.NS:
888                     return "7";
889                 case DiffType.Prefix:
890                     return "8";
891                 case DiffType.SourceExtra:
892                     return "9";
893                 case DiffType.TargetExtra:
894                     return "10";
895                 case DiffType.NodeType:
896                     return "11";
897                 case DiffType.CData:
898                     return "12";
899                 default:
900                     return String.Empty;
901             }
902         }
903     }
904 }
905