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("foo"); writer.WriteString("bar")</code> 435 /// on the writer and then iterate calling reader.Read() on the document, the binary 436 /// reader will report two text nodes ("foo", "bar"), while the 437 /// other readers will report a single text node ("foobar") – notice that 438 /// this is not a problem; calling ReadString, or Read[Element]ContentAsString in 439 /// all readers will return "foobar". 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