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.Text; 6 using System.IO; 7 using System.Diagnostics; 8 9 [assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAssembly] 10 11 namespace System.Xml.XmlDiff 12 { 13 internal enum DiffType 14 { 15 None, 16 Success, 17 Element, 18 Whitespace, 19 Comment, 20 PI, 21 Text, 22 CData, 23 Attribute, 24 NS, 25 Prefix, 26 SourceExtra, 27 TargetExtra, 28 NodeType 29 } 30 31 public class XmlDiff 32 { 33 private XmlDiffDocument _SourceDoc; 34 private XmlDiffDocument _TargetDoc; 35 36 private XmlWriter _Writer; // Writer to write out the result 37 private StringBuilder _Output; 38 39 // Option Flags 40 private XmlDiffOption _XmlDiffOption = XmlDiffOption.None; 41 private bool _IgnoreEmptyElement = true; 42 private bool _IgnoreWhitespace = true; 43 private bool _IgnoreComments = false; 44 private bool _IgnoreAttributeOrder = true; 45 private bool _IgnoreNS = true; 46 private bool _IgnorePrefix = true; 47 private bool _IgnoreDTD = true; 48 private bool _IgnoreChildOrder = true; 49 XmlDiff()50 public XmlDiff() 51 { 52 } 53 54 public XmlDiffOption Option 55 { 56 get 57 { 58 return _XmlDiffOption; 59 } 60 set 61 { 62 _XmlDiffOption = value; 63 IgnoreEmptyElement = (((int)_XmlDiffOption) & ((int)XmlDiffOption.IgnoreEmptyElement)) > 0; 64 IgnoreWhitespace = (((int)_XmlDiffOption) & ((int)XmlDiffOption.IgnoreWhitespace)) > 0; 65 IgnoreComments = (((int)_XmlDiffOption) & ((int)XmlDiffOption.IgnoreComments)) > 0; 66 IgnoreAttributeOrder = (((int)_XmlDiffOption) & ((int)XmlDiffOption.IgnoreAttributeOrder)) > 0; 67 IgnoreNS = (((int)_XmlDiffOption) & ((int)XmlDiffOption.IgnoreNS)) > 0; 68 IgnorePrefix = (((int)_XmlDiffOption) & ((int)XmlDiffOption.IgnorePrefix)) > 0; 69 IgnoreDTD = (((int)_XmlDiffOption) & ((int)XmlDiffOption.IgnoreDTD)) > 0; 70 IgnoreChildOrder = (((int)_XmlDiffOption) & ((int)XmlDiffOption.IgnoreChildOrder)) > 0; 71 } 72 } 73 74 internal bool IgnoreEmptyElement 75 { 76 get { return _IgnoreEmptyElement; } 77 set { _IgnoreEmptyElement = value; } 78 } 79 80 internal bool IgnoreComments 81 { 82 get { return _IgnoreComments; } 83 set { _IgnoreComments = value; } 84 } 85 86 internal bool IgnoreAttributeOrder 87 { 88 get { return _IgnoreAttributeOrder; } 89 set { _IgnoreAttributeOrder = value; } 90 } 91 92 internal bool IgnoreWhitespace 93 { 94 get { return _IgnoreWhitespace; } 95 set { _IgnoreWhitespace = value; } 96 } 97 98 internal bool IgnoreNS 99 { 100 get { return _IgnoreNS; } 101 set { _IgnoreNS = value; } 102 } 103 104 internal bool IgnorePrefix 105 { 106 get { return _IgnorePrefix; } 107 set { _IgnorePrefix = value; } 108 } 109 110 internal bool IgnoreDTD 111 { 112 get { return _IgnoreDTD; } 113 set { _IgnoreDTD = value; } 114 } 115 116 internal bool IgnoreChildOrder 117 { 118 get { return _IgnoreChildOrder; } 119 set { _IgnoreChildOrder = value; } 120 } 121 InitFiles()122 private void InitFiles() 123 { 124 this._SourceDoc = new XmlDiffDocument(); 125 this._SourceDoc.Option = this._XmlDiffOption; 126 this._TargetDoc = new XmlDiffDocument(); 127 this._TargetDoc.Option = this.Option; 128 _Output = new StringBuilder(String.Empty); 129 } 130 Compare(Stream source, Stream target)131 public bool Compare(Stream source, Stream target) 132 { 133 InitFiles(); 134 135 this._SourceDoc.Load(XmlReader.Create(source)); 136 this._TargetDoc.Load(XmlReader.Create(target)); 137 return Diff(); 138 } 139 Compare(XmlReader source, XmlReader target)140 public bool Compare(XmlReader source, XmlReader target) 141 { 142 InitFiles(); 143 this._SourceDoc.Load(source); 144 this._TargetDoc.Load(target); 145 return Diff(); 146 } 147 Compare(XmlReader source, XmlReader target, XmlDiffAdvancedOptions advOptions)148 public bool Compare(XmlReader source, XmlReader target, XmlDiffAdvancedOptions advOptions) 149 { 150 InitFiles(); 151 this._SourceDoc.Load(source); 152 this._TargetDoc.Load(target); 153 XmlDiffNavigator nav = this._SourceDoc.CreateNavigator(); 154 if (advOptions.IgnoreChildOrderExpr != null && advOptions.IgnoreChildOrderExpr != "") 155 { 156 this._SourceDoc.SortChildren(); 157 this._TargetDoc.SortChildren(); 158 } 159 160 return Diff(); 161 } 162 Diff()163 private bool Diff() 164 { 165 bool flag = false; 166 XmlWriterSettings xws = new XmlWriterSettings(); 167 xws.ConformanceLevel = ConformanceLevel.Auto; 168 xws.CheckCharacters = false; 169 _Writer = XmlWriter.Create(new StringWriter(_Output), xws); 170 _Writer.WriteStartElement(String.Empty, "Root", String.Empty); 171 172 flag = CompareChildren(this._SourceDoc, this._TargetDoc); 173 174 _Writer.WriteEndElement(); 175 _Writer.Dispose(); 176 return flag; 177 } 178 179 // This function is being called recursively to compare the children of a certain node. 180 // When calling this function the navigator should be pointing at the parent node whose children 181 // we wants to compare and return the navigator back to the same node before exiting from it. CompareChildren(XmlDiffNode sourceNode, XmlDiffNode targetNode)182 private bool CompareChildren(XmlDiffNode sourceNode, XmlDiffNode targetNode) 183 { 184 XmlDiffNode sourceChild = sourceNode.FirstChild; 185 XmlDiffNode targetChild = targetNode.FirstChild; 186 187 bool flag = true; 188 bool tempFlag = true; 189 190 DiffType result; 191 192 // Loop to compare all the child elements of the parent node 193 while (sourceChild != null || targetChild != null) 194 { 195 //Console.WriteLine( (sourceChild!=null)?(sourceChild.NodeType.ToString()):"null" ); 196 //Console.WriteLine( (targetChild!=null)?(targetChild.NodeType.ToString()):"null" ); 197 if (sourceChild != null) 198 { 199 if (targetChild != null) 200 { 201 // Both Source and Target Read successful 202 if ((result = CompareNodes(sourceChild, targetChild)) == DiffType.Success) 203 { 204 // Child nodes compared successfully, write out the result 205 WriteResult(sourceChild, targetChild, DiffType.Success); 206 // Check whether this Node has Children, if it does call CompareChildren recursively 207 if (sourceChild.FirstChild != null) 208 tempFlag = CompareChildren(sourceChild, targetChild); 209 else if (targetChild.FirstChild != null) 210 { 211 WriteResult(null, targetChild, DiffType.TargetExtra); 212 tempFlag = false; 213 } 214 // set the compare flag 215 flag = (flag && tempFlag); 216 217 // Writing out End Element to the result 218 if (sourceChild.NodeType == XmlDiffNodeType.Element && !(sourceChild is XmlDiffEmptyElement)) 219 { 220 XmlDiffElement sourceElem = sourceChild as XmlDiffElement; 221 XmlDiffElement targetElem = targetChild as XmlDiffElement; 222 Debug.Assert(sourceElem != null); 223 Debug.Assert(targetElem != null); 224 _Writer.WriteStartElement(String.Empty, "Node", String.Empty); 225 _Writer.WriteAttributeString(String.Empty, "SourceLineNum", String.Empty, sourceElem.EndLineNumber.ToString()); 226 _Writer.WriteAttributeString(String.Empty, "SourceLinePos", String.Empty, sourceElem.EndLinePosition.ToString()); 227 _Writer.WriteAttributeString(String.Empty, "TargetLineNum", String.Empty, targetElem.EndLineNumber.ToString()); 228 _Writer.WriteAttributeString(String.Empty, "TargetLinePos", String.Empty, targetElem.EndLinePosition.ToString()); 229 _Writer.WriteStartElement(String.Empty, "Diff", String.Empty); 230 _Writer.WriteEndElement(); 231 232 _Writer.WriteStartElement(String.Empty, "Lexical-equal", String.Empty); 233 _Writer.WriteCData("</" + sourceElem.Name + ">"); 234 _Writer.WriteEndElement(); 235 _Writer.WriteEndElement(); 236 } 237 238 // Move to Next child 239 sourceChild = sourceChild.NextSibling; 240 targetChild = targetChild.NextSibling; 241 } 242 else 243 { 244 // Child nodes not matched, start the recovery process 245 bool recoveryFlag = false; 246 247 // Try to match the source node with the target nodes at the same level 248 XmlDiffNode backupTargetChild = targetChild.NextSibling; 249 while (!recoveryFlag && backupTargetChild != null) 250 { 251 if (CompareNodes(sourceChild, backupTargetChild) == DiffType.Success) 252 { 253 recoveryFlag = true; 254 do 255 { 256 WriteResult(null, targetChild, DiffType.TargetExtra); 257 targetChild = targetChild.NextSibling; 258 } while (targetChild != backupTargetChild); 259 break; 260 } 261 backupTargetChild = backupTargetChild.NextSibling; 262 } 263 264 // If not recovered, try to match the target node with the source nodes at the same level 265 if (!recoveryFlag) 266 { 267 XmlDiffNode backupSourceChild = sourceChild.NextSibling; 268 while (!recoveryFlag && backupSourceChild != null) 269 { 270 if (CompareNodes(backupSourceChild, targetChild) == DiffType.Success) 271 { 272 recoveryFlag = true; 273 do 274 { 275 WriteResult(sourceChild, null, DiffType.SourceExtra); 276 sourceChild = sourceChild.NextSibling; 277 } while (sourceChild != backupSourceChild); 278 break; 279 } 280 backupSourceChild = backupSourceChild.NextSibling; 281 } 282 } 283 284 // If still not recovered, write both of them as different nodes and move on 285 if (!recoveryFlag) 286 { 287 WriteResult(sourceChild, targetChild, result); 288 289 // Check whether this Node has Children, if it does call CompareChildren recursively 290 if (sourceChild.FirstChild != null) 291 { 292 tempFlag = CompareChildren(sourceChild, targetChild); 293 } 294 else if (targetChild.FirstChild != null) 295 { 296 WriteResult(null, targetChild, DiffType.TargetExtra); 297 tempFlag = false; 298 } 299 300 // Writing out End Element to the result 301 bool bSourceNonEmpElemEnd = (sourceChild.NodeType == XmlDiffNodeType.Element && !(sourceChild is XmlDiffEmptyElement)); 302 bool bTargetNonEmpElemEnd = (targetChild.NodeType == XmlDiffNodeType.Element && !(targetChild is XmlDiffEmptyElement)); 303 if (bSourceNonEmpElemEnd || bTargetNonEmpElemEnd) 304 { 305 XmlDiffElement sourceElem = sourceChild as XmlDiffElement; 306 XmlDiffElement targetElem = targetChild as XmlDiffElement; 307 _Writer.WriteStartElement(String.Empty, "Node", String.Empty); 308 _Writer.WriteAttributeString(String.Empty, "SourceLineNum", String.Empty, (sourceElem != null) ? sourceElem.EndLineNumber.ToString() : "-1"); 309 _Writer.WriteAttributeString(String.Empty, "SourceLinePos", String.Empty, (sourceElem != null) ? sourceElem.EndLinePosition.ToString() : "-1"); 310 _Writer.WriteAttributeString(String.Empty, "TargetLineNum", String.Empty, (targetElem != null) ? targetElem.EndLineNumber.ToString() : "-1"); 311 _Writer.WriteAttributeString(String.Empty, "TargetLinePos", String.Empty, (targetElem != null) ? targetElem.EndLineNumber.ToString() : "-1"); 312 _Writer.WriteStartElement(String.Empty, "Diff", String.Empty); 313 _Writer.WriteAttributeString(String.Empty, "DiffType", String.Empty, GetDiffType(result)); 314 if (bSourceNonEmpElemEnd) 315 { 316 _Writer.WriteStartElement(String.Empty, "File1", String.Empty); 317 _Writer.WriteCData("</" + sourceElem.Name + ">"); 318 _Writer.WriteEndElement(); 319 } 320 321 if (bTargetNonEmpElemEnd) 322 { 323 _Writer.WriteStartElement(String.Empty, "File2", String.Empty); 324 _Writer.WriteCData("</" + targetElem.Name + ">"); 325 _Writer.WriteEndElement(); 326 } 327 328 _Writer.WriteEndElement(); 329 330 _Writer.WriteStartElement(String.Empty, "Lexical-equal", String.Empty); 331 _Writer.WriteEndElement(); 332 _Writer.WriteEndElement(); 333 } 334 335 sourceChild = sourceChild.NextSibling; 336 targetChild = targetChild.NextSibling; 337 } 338 339 flag = false; 340 } 341 } 342 else 343 { 344 // SourceRead NOT NULL targetRead is NULL 345 WriteResult(sourceChild, null, DiffType.SourceExtra); 346 flag = false; 347 sourceChild = sourceChild.NextSibling; 348 } 349 } 350 else if (targetChild != null) 351 { 352 // SourceRead is NULL and targetRead is NOT NULL 353 WriteResult(null, targetChild, DiffType.TargetExtra); 354 flag = false; 355 targetChild = targetChild.NextSibling; 356 } 357 else 358 { 359 //Both SourceRead and TargetRead is NULL 360 Debug.Assert(false, "Impossible Situation for comparison"); 361 } 362 } 363 return flag; 364 } 365 366 // This function compares the two nodes passed to it, depending upon the options set by the user. CompareNodes(XmlDiffNode sourceNode, XmlDiffNode targetNode)367 private DiffType CompareNodes(XmlDiffNode sourceNode, XmlDiffNode targetNode) 368 { 369 if (sourceNode.NodeType != targetNode.NodeType) 370 { 371 return DiffType.NodeType; 372 } 373 switch (sourceNode.NodeType) 374 { 375 case XmlDiffNodeType.Element: 376 XmlDiffElement sourceElem = sourceNode as XmlDiffElement; 377 XmlDiffElement targetElem = targetNode as XmlDiffElement; 378 Debug.Assert(sourceElem != null); 379 Debug.Assert(targetElem != null); 380 if (!IgnoreNS) 381 { 382 if (sourceElem.NamespaceURI != targetElem.NamespaceURI) 383 return DiffType.NS; 384 } 385 if (!IgnorePrefix) 386 { 387 if (sourceElem.Prefix != targetElem.Prefix) 388 return DiffType.Prefix; 389 } 390 391 if (sourceElem.LocalName != targetElem.LocalName) 392 return DiffType.Element; 393 if (!IgnoreEmptyElement) 394 { 395 if ((sourceElem is XmlDiffEmptyElement) != (targetElem is XmlDiffEmptyElement)) 396 return DiffType.Element; 397 } 398 if (!CompareAttributes(sourceElem, targetElem)) 399 { 400 return DiffType.Attribute; 401 } 402 break; 403 case XmlDiffNodeType.Text: 404 case XmlDiffNodeType.Comment: 405 case XmlDiffNodeType.WS: 406 XmlDiffCharacterData sourceText = sourceNode as XmlDiffCharacterData; 407 XmlDiffCharacterData targetText = targetNode as XmlDiffCharacterData; 408 Debug.Assert(sourceText != null); 409 Debug.Assert(targetText != null); 410 if (IgnoreWhitespace) 411 { 412 if (sourceText.Value.Trim() == targetText.Value.Trim()) 413 return DiffType.Success; 414 } 415 else 416 { 417 if (sourceText.Value == targetText.Value) 418 return DiffType.Success; 419 } 420 if (sourceText.NodeType == XmlDiffNodeType.Text || sourceText.NodeType == XmlDiffNodeType.WS)//should ws nodes also as text nodes??? 421 return DiffType.Text; 422 else if (sourceText.NodeType == XmlDiffNodeType.Comment) 423 return DiffType.Comment; 424 else if (sourceText.NodeType == XmlDiffNodeType.CData) 425 return DiffType.CData; 426 else 427 return DiffType.None; 428 case XmlDiffNodeType.PI: 429 XmlDiffProcessingInstruction sourcePI = sourceNode as XmlDiffProcessingInstruction; 430 XmlDiffProcessingInstruction targetPI = targetNode as XmlDiffProcessingInstruction; 431 Debug.Assert(sourcePI != null); 432 Debug.Assert(targetPI != null); 433 if (sourcePI.Name != targetPI.Name || sourcePI.Value != targetPI.Value) 434 return DiffType.PI; 435 break; 436 default: 437 break; 438 } 439 440 return DiffType.Success; 441 } 442 443 // 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)444 private void WriteResult(XmlDiffNode sourceNode, XmlDiffNode targetNode, DiffType result) 445 { 446 _Writer.WriteStartElement(String.Empty, "Node", String.Empty); 447 _Writer.WriteAttributeString(String.Empty, "SourceLineNum", String.Empty, (sourceNode != null) ? sourceNode.LineNumber.ToString() : "-1"); 448 _Writer.WriteAttributeString(String.Empty, "SourceLinePos", String.Empty, (sourceNode != null) ? sourceNode.LinePosition.ToString() : "-1"); 449 _Writer.WriteAttributeString(String.Empty, "TargetLineNum", String.Empty, (targetNode != null) ? targetNode.LineNumber.ToString() : "-1"); 450 _Writer.WriteAttributeString(String.Empty, "TargetLinePos", String.Empty, (targetNode != null) ? targetNode.LinePosition.ToString() : "-1"); 451 if (result == DiffType.Success) 452 { 453 _Writer.WriteStartElement(String.Empty, "Diff", String.Empty); 454 _Writer.WriteEndElement(); 455 456 _Writer.WriteStartElement(String.Empty, "Lexical-equal", String.Empty); 457 if (sourceNode.NodeType == XmlDiffNodeType.CData) 458 { 459 _Writer.WriteString("<![CDATA["); 460 _Writer.WriteCData(GetNodeText(sourceNode, result)); 461 _Writer.WriteString("]]>"); 462 } 463 else 464 { 465 _Writer.WriteCData(GetNodeText(sourceNode, result)); 466 } 467 _Writer.WriteEndElement(); 468 } 469 else 470 { 471 _Writer.WriteStartElement(String.Empty, "Diff", String.Empty); 472 _Writer.WriteAttributeString(String.Empty, "DiffType", String.Empty, GetDiffType(result)); 473 474 if (sourceNode != null) 475 { 476 _Writer.WriteStartElement(String.Empty, "File1", String.Empty); 477 if (sourceNode.NodeType == XmlDiffNodeType.CData) 478 { 479 _Writer.WriteString("<![CDATA["); 480 _Writer.WriteCData(GetNodeText(sourceNode, result)); 481 _Writer.WriteString("]]>"); 482 } 483 else 484 { 485 486 _Writer.WriteString(GetNodeText(sourceNode, result)); 487 } 488 _Writer.WriteEndElement(); 489 } 490 491 if (targetNode != null) 492 { 493 _Writer.WriteStartElement(String.Empty, "File2", String.Empty); 494 if (targetNode.NodeType == XmlDiffNodeType.CData) 495 { 496 _Writer.WriteString("<![CDATA["); 497 _Writer.WriteCData(GetNodeText(targetNode, result)); 498 _Writer.WriteString("]]>"); 499 } 500 else 501 { 502 _Writer.WriteString(GetNodeText(targetNode, result)); 503 } 504 _Writer.WriteEndElement(); 505 } 506 507 _Writer.WriteEndElement(); 508 509 _Writer.WriteStartElement(String.Empty, "Lexical-equal", String.Empty); 510 _Writer.WriteEndElement(); 511 } 512 _Writer.WriteEndElement(); 513 } 514 515 // This is a helper function for WriteResult. It gets the Xml representation of the different node we wants 516 // to write out and all it's children. GetNodeText(XmlDiffNode diffNode, DiffType result)517 private String GetNodeText(XmlDiffNode diffNode, DiffType result) 518 { 519 string text = string.Empty; 520 switch (diffNode.NodeType) 521 { 522 case XmlDiffNodeType.Element: 523 if (result == DiffType.SourceExtra || result == DiffType.TargetExtra) 524 return diffNode.OuterXml; 525 StringWriter str = new StringWriter(); 526 XmlWriter writer = XmlWriter.Create(str); 527 XmlDiffElement diffElem = diffNode as XmlDiffElement; 528 Debug.Assert(diffNode != null); 529 writer.WriteStartElement(diffElem.Prefix, diffElem.LocalName, diffElem.NamespaceURI); 530 XmlDiffAttribute diffAttr = diffElem.FirstAttribute; 531 while (diffAttr != null) 532 { 533 writer.WriteAttributeString(diffAttr.Prefix, diffAttr.LocalName, diffAttr.NamespaceURI, diffAttr.Value); 534 diffAttr = (XmlDiffAttribute)diffAttr.NextSibling; 535 } 536 if (diffElem is XmlDiffEmptyElement) 537 { 538 writer.WriteEndElement(); 539 text = str.ToString(); 540 } 541 else 542 { 543 text = str.ToString(); 544 text += ">"; 545 } 546 writer.Dispose(); 547 break; 548 case XmlDiffNodeType.CData: 549 text = ((XmlDiffCharacterData)diffNode).Value; 550 break; 551 default: 552 text = diffNode.OuterXml; 553 break; 554 } 555 return text; 556 } 557 558 // 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)559 private bool CompareAttributes(XmlDiffElement sourceElem, XmlDiffElement targetElem) 560 { 561 Debug.Assert(sourceElem != null); 562 Debug.Assert(targetElem != null); 563 if (sourceElem.AttributeCount != targetElem.AttributeCount) 564 return false; 565 566 if (sourceElem.AttributeCount == 0) 567 return true; 568 569 XmlDiffAttribute sourceAttr = sourceElem.FirstAttribute; 570 XmlDiffAttribute targetAttr = targetElem.FirstAttribute; 571 572 while (sourceAttr != null && targetAttr != null) 573 { 574 if (!IgnoreNS) 575 { 576 if (sourceAttr.NamespaceURI != targetAttr.NamespaceURI) 577 return false; 578 } 579 580 if (!IgnorePrefix) 581 { 582 if (sourceAttr.Prefix != targetAttr.Prefix) 583 return false; 584 } 585 586 if (sourceAttr.LocalName != targetAttr.LocalName || sourceAttr.Value != targetAttr.Value) 587 return false; 588 sourceAttr = (XmlDiffAttribute)(sourceAttr.NextSibling); 589 targetAttr = (XmlDiffAttribute)(targetAttr.NextSibling); 590 } 591 return true; 592 } 593 ToXml()594 public string ToXml() 595 { 596 if (_Output != null) 597 return _Output.ToString(); 598 return string.Empty; 599 } 600 ToXml(Stream stream)601 public void ToXml(Stream stream) 602 { 603 StreamWriter writer = new StreamWriter(stream); 604 writer.Write("<?xml-stylesheet type='text/xsl' href='diff.xsl'?>"); 605 writer.Write(ToXml()); 606 writer.Dispose(); 607 } 608 GetDiffType(DiffType type)609 private String GetDiffType(DiffType type) 610 { 611 switch (type) 612 { 613 case DiffType.None: 614 return String.Empty; 615 case DiffType.Element: 616 return "1"; 617 case DiffType.Whitespace: 618 return "2"; 619 case DiffType.Comment: 620 return "3"; 621 case DiffType.PI: 622 return "4"; 623 case DiffType.Text: 624 return "5"; 625 case DiffType.Attribute: 626 return "6"; 627 case DiffType.NS: 628 return "7"; 629 case DiffType.Prefix: 630 return "8"; 631 case DiffType.SourceExtra: 632 return "9"; 633 case DiffType.TargetExtra: 634 return "10"; 635 case DiffType.NodeType: 636 return "11"; 637 case DiffType.CData: 638 return "12"; 639 default: 640 return String.Empty; 641 } 642 } 643 } 644 } 645