XmlDiff.cs
上传用户:hbhltzc
上传日期:2022-06-04
资源大小:1925k
文件大小:64k
源码类别:

xml/soap/webservice

开发平台:

Visual C++

  1. //------------------------------------------------------------------------------
  2. // <copyright file="XmlDiff.cs" company="Microsoft">
  3. //     Copyright (c) Microsoft Corporation.  All rights reserved.
  4. // </copyright>                                                                
  5. //------------------------------------------------------------------------------
  6. //#define MEASURE_PERF
  7. #define VERIFY_HASH_VALUES
  8. using System;
  9. using System.IO;
  10. using System.Xml;
  11. using System.Timers;
  12. using System.Diagnostics;
  13. using System.Collections;
  14. using System.Security.Permissions;
  15. namespace Microsoft.XmlDiffPatch
  16. {
  17.     
  18. //
  19. // XmlDiffOptions
  20. //
  21. /// <include file='docXmlDiffOptions.uex' path='docs/doc[@for="XmlDiffOptions"]/*' />
  22. /// <summary>
  23. /// Options for comparing XML documents. 
  24. /// </summary>
  25. public enum XmlDiffOptions 
  26. {
  27.     None                 = 0x0,
  28.     IgnoreChildOrder     = 0x1,
  29.     IgnoreComments       = 0x2,
  30.     IgnorePI             = 0x4,
  31.     IgnoreWhitespace     = 0x8,
  32.     IgnoreNamespaces     = 0x10,
  33.     IgnorePrefixes       = 0x20,
  34.     IgnoreXmlDecl        = 0x40,
  35.     IgnoreDtd            = 0x80,
  36. }
  37. //
  38. // XmlDiffAlgorithm
  39. //
  40. /// <include file='docXmlDiffAlgorithm.uex' path='docs/doc[@for="XmlDiffAlgorithm"]/*' />
  41. /// <summary>
  42. ///   Types of algorithms that can be used for comparing XML documents by XmlDiff. Auto means XmlDiff will
  43. ///   automatically decide which algorithm to use for the particular case depending on the assumed number 
  44. ///   of changes.
  45. /// </summary>
  46. public enum XmlDiffAlgorithm {
  47.     Auto,
  48.     Fast,
  49.     Precise,
  50. }
  51. // 
  52. // XmlDiffOperation
  53. //
  54. // BEWARE: MinimalTreeDistanceAlgo.OperationCost is indexed by this enum.
  55. // If you change this, make the appropriate changes to the MinimalTreeDistanceAlgo.OperationCost too.
  56. internal enum XmlDiffOperation 
  57.     Match                      = 0,
  58.     Add                        = 1,
  59.     Remove                     = 2,
  60. ChangeElementName          = 3,
  61. ChangeElementAttr1         = 4,
  62. ChangeElementAttr2         = 5,
  63. ChangeElementAttr3         = 6,
  64. ChangeElementNameAndAttr1  = 7,
  65. ChangeElementNameAndAttr2  = 8,
  66. ChangeElementNameAndAttr3  = 9,
  67. ChangePI                   = 10,
  68. ChangeER                   = 11,
  69.     ChangeCharacterData        = 12,
  70.     ChangeXmlDeclaration       = 13,
  71.     ChangeDTD                  = 14,
  72.     
  73.     Undefined                  = 15,
  74.     ChangeAttr                 = 16,
  75. }
  76. //
  77. // XmlDiffDescriptorType
  78. //
  79. internal enum XmlDiffDescriptorType
  80. {
  81.     Move,
  82.     PrefixChange,
  83.     NamespaceChange,
  84. }
  85. //
  86. // XmlDiffNodeType
  87. //
  88. internal enum XmlDiffNodeType
  89. {
  90.     XmlDeclaration        = -2,
  91.     DocumentType          = -1,
  92.     None                  = 0,
  93.     Element               = XmlNodeType.Element,
  94.     Attribute             = XmlNodeType.Attribute,
  95.     Text                  = XmlNodeType.Text,
  96.     CDATA                 = XmlNodeType.CDATA,
  97.     Comment               = XmlNodeType.Comment,
  98.     Document              = XmlNodeType.Document,
  99.     EntityReference       = XmlNodeType.EntityReference,
  100.     ProcessingInstruction = XmlNodeType.ProcessingInstruction,
  101.     SignificantWhitespace = XmlNodeType.SignificantWhitespace,
  102.     Namespace             = 100,
  103.     ShrankNode            = 101,
  104. }
  105. internal enum TriStateBool {
  106.     Yes,
  107.     No,
  108.     DontKnown,
  109. }
  110. //////////////////////////////////////////////////////////////////
  111. // XmlPatch
  112. //
  113. /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff"]/*' />
  114. /// <summary>
  115. ///    Compares two documents or fragments. 
  116. /// </summary>
  117. public class XmlDiff
  118. {
  119.     private class XmlDiffNodeListMember
  120.     {
  121.         internal XmlDiffNode            _node;
  122.         internal XmlDiffNodeListMember  _next;
  123.         internal XmlDiffNodeListMember( XmlDiffNode node, XmlDiffNodeListMember next )
  124.         {
  125.             Debug.Assert( node != null );
  126.             _node = node;
  127.             _next = next;
  128.         }
  129.     }
  130.     private class XmlDiffNodeListHead
  131.     {
  132.         internal XmlDiffNodeListMember _first;
  133.         internal XmlDiffNodeListMember _last;
  134.         internal XmlDiffNodeListHead( XmlDiffNodeListMember firstMember )
  135.         {
  136.             Debug.Assert( firstMember != null );
  137.             _first = firstMember;
  138.             _last = firstMember;
  139.         }
  140.     }
  141. // Fields
  142. // Options flags
  143.     bool _bIgnoreChildOrder  = false;
  144.     bool _bIgnoreComments    = false;
  145.     bool _bIgnorePI          = false;
  146.     bool _bIgnoreWhitespace  = false;
  147.     bool _bIgnoreNamespaces  = false;
  148.     bool _bIgnorePrefixes    = false;
  149.     bool _bIgnoreXmlDecl     = false;
  150.     bool _bIgnoreDtd         = false;
  151.     XmlDiffAlgorithm _algorithm = XmlDiffAlgorithm.Auto;
  152.     // compared documents
  153.     internal XmlDiffDocument _sourceDoc = null;
  154.     internal XmlDiffDocument _targetDoc = null;
  155.     // nodes sorted according to post-order numbering
  156.     internal XmlDiffNode[] _sourceNodes = null;
  157.     internal XmlDiffNode[] _targetNodes = null;
  158.     internal TriStateBool _fragments = TriStateBool.DontKnown;
  159. private const int MininumNodesForQuicksort = 5;
  160.     private const int MaxTotalNodesCountForTreeDistance = 256;
  161.     // Tracing
  162. #if DEBUG
  163.     internal static BooleanSwitch T_Phases = new BooleanSwitch( "Phases", "Traces the current phase of the algorithm, number of compared nodes etc." );
  164.     internal static BooleanSwitch T_LoadedDoc = new BooleanSwitch( "Loaded Document", "Dumps the loaded xml document." );
  165.     internal static BooleanSwitch T_ForestDistance = new BooleanSwitch( "Forest Distance", "Trace the _forestDist array in each call of ComputeTreeDistance." );
  166.     internal static BooleanSwitch T_EditScript = new BooleanSwitch( "Edit Script", "Traces the edit script." );
  167.     internal static BooleanSwitch T_SubtreeMatching = new BooleanSwitch( "Identical subtrees matching", "Traces nodes that are shrinked when identical subtrees are matched." );
  168.     internal static BooleanSwitch T_Tree = new BooleanSwitch( "Tree", "Traces tree that is passed to tree distance algorithm." );
  169. #endif
  170.     // Performance measurement
  171. #if MEASURE_PERF
  172.     public XmlDiffPerf _xmlDiffPerf = new XmlDiffPerf();
  173. #endif
  174. // Constructors
  175.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.XmlDiff1"]/*' />
  176.     /// <summary>
  177.     ///    Constructs XmlDiff object with default options.
  178.     /// </summary>
  179.     public XmlDiff() 
  180.     {
  181. #if DEBUG
  182.         // TODO: this is temporary until I figure out why the config file does not work
  183.         EnableTraceSwitches();
  184. #endif
  185.     }
  186.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.XmlDiff2"]/*' />
  187.     /// <summary>
  188.     ///    Constructs XmlDiff object with the given options. The values of XmlDiffOptions
  189.     ///    may be combined using the operator '|'.
  190.     /// </summary>
  191.     public XmlDiff( XmlDiffOptions options ) : this()
  192.     {
  193.         Options = options;
  194.     }
  195. // Properties
  196.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.NamespaceUri"]/*' />
  197.     /// <summary>
  198.     ///    XmlDiff namespace. The diffgram nodes belongs to this namespace.
  199.     /// </summary>
  200.     public const string NamespaceUri        = "http://schemas.microsoft.com/xmltools/2002/xmldiff";
  201.     internal const string Prefix            = "xd";
  202.     internal const string XmlnsNamespaceUri = "http://www.w3.org/2000/xmlns/";
  203. // Options flags
  204.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.IgnoreChildren"]/*' />
  205.     /// <summary>
  206.     ///    If true, the order of child nodes of each element will be ignored when comparing 
  207.     ///    the documents/fragments.
  208.     /// </summary>
  209.     public bool IgnoreChildOrder { get { return _bIgnoreChildOrder; } set { _bIgnoreChildOrder = value; } }
  210.     
  211.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.IgnoreComments"]/*' />
  212.     /// <summary>
  213.     ///    If true, all comments in the compared documents/fragments will be ignored.
  214.     /// </summary>
  215.     public bool IgnoreComments   { get { return _bIgnoreComments; }   set { _bIgnoreComments = value; } }
  216.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.IgnorePI"]/*' />
  217.     /// <summary>
  218.     ///    If true, all processing instructions in the compared documents/fragments will be ignored.
  219.     /// </summary>
  220.     public bool IgnorePI  { get { return _bIgnorePI ; }   set { _bIgnorePI = value; } }
  221.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.IgnoreWhitespace"]/*' />
  222.     /// <summary>
  223.     ///    If true, all whitespace nodes in the compared documents/fragments will be ignored. Also, all
  224.     ///    text nodes and values of attributes will be normalized; whitespace sequences will be replaced
  225.     ///    by single space and beginning and trailing whitespaces will be trimmed.
  226.     /// </summary>
  227.     public bool IgnoreWhitespace { get { return _bIgnoreWhitespace; } set { _bIgnoreWhitespace = value; } }
  228.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.IgnoreNamespace"]/*' />
  229.     /// <summary>
  230.     ///    If true, the namespaces will be ignored when comparing the names of elements and attributes.
  231.     ///    This also mean that the prefixes will be ignored too as if the IgnorePrefixes option is true.
  232.     /// </summary>
  233.     public bool IgnoreNamespaces { get { return _bIgnoreNamespaces; } set { _bIgnoreNamespaces = value; } }
  234.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.IgnorePrefixes"]/*' />
  235.     /// <summary>
  236.     ///    If true, the prefixes will be ignored when comparing the names of elements and attributes. 
  237.     ///    The namespaces will not ne ignored unless IgnoreNamespaces flag is true.
  238.     /// </summary>
  239.     public bool IgnorePrefixes   { get { return _bIgnorePrefixes;   } set { _bIgnorePrefixes = value; } }
  240.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.IgnoreXmlDecl"]/*' />
  241.     /// <summary>
  242.     ///    If true, the xml declarations will not be compared.
  243.     /// </summary>
  244.     public bool IgnoreXmlDecl    { get { return _bIgnoreXmlDecl;    } set { _bIgnoreXmlDecl = value; } }
  245.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.IgnoreDtd"]/*' />
  246.     /// <summary>
  247.     ///    If true, the xml declarations will not be compared.
  248.     /// </summary>
  249.     public bool IgnoreDtd        { get { return _bIgnoreDtd;        } set { _bIgnoreDtd = value; } }
  250.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.Options"]/*' />
  251.     /// <summary>
  252.     ///    Options used when comparing xml documents/fragments.
  253.     /// </summary>
  254. public XmlDiffOptions Options 
  255. {
  256.         set {
  257.             IgnoreChildOrder = ( ( (int)value & (int)(XmlDiffOptions.IgnoreChildOrder) ) > 0 ) ;
  258.             IgnoreComments   = ( ( (int)value & (int)(XmlDiffOptions.IgnoreComments)   ) > 0 ) ;
  259.             IgnorePI         = ( ( (int)value & (int)(XmlDiffOptions.IgnorePI)         ) > 0 ) ;
  260.             IgnoreWhitespace = ( ( (int)value & (int)(XmlDiffOptions.IgnoreWhitespace) ) > 0 ) ;
  261.             IgnoreNamespaces = ( ( (int)value & (int)(XmlDiffOptions.IgnoreNamespaces) ) > 0 ) ;
  262.             IgnorePrefixes   = ( ( (int)value & (int)(XmlDiffOptions.IgnorePrefixes)   ) > 0 ) ;
  263.             IgnoreXmlDecl    = ( ( (int)value & (int)(XmlDiffOptions.IgnoreXmlDecl)    ) > 0 ) ;
  264.             IgnoreDtd        = ( ( (int)value & (int)(XmlDiffOptions.IgnoreDtd)        ) > 0 ) ;
  265.         }
  266.     }
  267.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.Algorithm"]/*' />
  268.     /// <summary>
  269.     ///    Algorithm that will be used for XML comparison.
  270.     /// </summary>
  271.     public XmlDiffAlgorithm Algorithm {
  272.         get { 
  273.             return _algorithm;
  274.         }
  275.         set {
  276.             _algorithm = value;
  277.         }
  278.     }
  279. // Methods
  280.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.Compare1"]/*' />
  281.     /// <summary>
  282.     ///    Compares two XML documents or fragments.
  283.     /// </summary>
  284.     /// <param name="sourceFile">The original xml document or fragment filename</param>
  285.     /// <param name="changedFile">The changed xml document or fragment filename.</param>
  286.     /// <param name="bFragments">If true, the passed files contain xml fragments; otherwise the files must contain xml documents.</param>
  287.     /// <returns>True, if the documents/fragments are identical.</returns>
  288.     public bool Compare( string sourceFile, string changedFile, bool bFragments ) 
  289.     {
  290.         return Compare( sourceFile, changedFile, bFragments, null );
  291.     }
  292.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.Compare2"]/*' />
  293.     /// <summary>
  294.     ///    Compares two XML documents or fragments. 
  295.     ///    If the diffgramWriter parameter is not null it will contain the list of changes 
  296.     ///    between the two XML documents/fragments (diffgram).
  297.     /// </summary>
  298.     /// <param name="sourceFile">The original xml document or fragment filename</param>
  299.     /// <param name="changedFile">The changed xml document or fragment filename.</param>
  300.     /// <param name="bFragments">If true, the passed files contain xml fragments; otherwise the files must contain xml documents.</param>
  301.     /// <param name="diffgramWriter">XmlWriter object for returning the list of changes (diffgram).</param>
  302.     /// <returns>True, if the documents/fragments are identical.</returns>
  303.     public bool Compare( string sourceFile, string changedFile, bool bFragments, XmlWriter diffgramWriter ) 
  304.     {
  305.         if ( sourceFile == null )
  306.             throw new ArgumentNullException( "sourceFile" );
  307.         if ( changedFile == null )
  308.             throw new ArgumentNullException( "changedFile" );
  309.         XmlReader sourceReader = null;
  310.         XmlReader targetReader = null;
  311.         try
  312.         {
  313.             _fragments = bFragments ? TriStateBool.Yes : TriStateBool.No;
  314.             if ( bFragments )
  315.                 OpenFragments( sourceFile, changedFile, ref sourceReader, ref targetReader );
  316.             else
  317.                 OpenDocuments( sourceFile, changedFile, ref sourceReader, ref targetReader );
  318.             return Compare( sourceReader, targetReader, diffgramWriter );
  319.         }
  320.         finally
  321.         {
  322.             if ( sourceReader != null ) {
  323.                 sourceReader.Close();
  324.                 sourceReader = null;
  325.             }
  326.             if ( targetReader != null ) {
  327.                 targetReader.Close();
  328.                 targetReader = null;
  329.             }
  330.         }
  331.     }
  332.     private void OpenDocuments( String sourceFile, String changedFile, 
  333.                                 ref XmlReader sourceReader, ref XmlReader changedReader )
  334.     {
  335.         XmlTextReader tr = new XmlTextReader( sourceFile );
  336.         tr.XmlResolver = null;
  337.         sourceReader = tr;
  338.         
  339.         tr = new XmlTextReader( changedFile );
  340.         tr.XmlResolver = null;
  341.         changedReader = tr;
  342.     }
  343.     private void OpenFragments( String sourceFile, String changedFile, 
  344.                                 ref XmlReader sourceReader, ref XmlReader changedReader )
  345.     {
  346.         FileStream sourceStream = null;
  347.         FileStream changedStream = null;
  348.         try
  349.         {
  350.             XmlNameTable nameTable = new NameTable();
  351.             XmlParserContext sourceParserContext = new XmlParserContext( nameTable,
  352.                                                                         new XmlNamespaceManager( nameTable ), 
  353.                                                                         string.Empty, 
  354.                                                                         System.Xml.XmlSpace.Default );
  355.             XmlParserContext changedParserContext = new XmlParserContext( nameTable,
  356.                                                                         new XmlNamespaceManager( nameTable ), 
  357.                                                                         string.Empty, 
  358.                                                                         System.Xml.XmlSpace.Default );
  359.             sourceStream = new FileStream( sourceFile, FileMode.Open, FileAccess.Read );
  360.             changedStream = new FileStream( changedFile, FileMode.Open, FileAccess.Read );
  361.             XmlTextReader tr = new XmlTextReader( sourceStream, XmlNodeType.Element, sourceParserContext );
  362.             tr.XmlResolver = null;
  363.             sourceReader = tr;
  364.             tr = new XmlTextReader( changedStream, XmlNodeType.Element, changedParserContext ); 
  365.             tr.XmlResolver = null;
  366.             changedReader = tr;
  367.         }
  368.         catch 
  369.         {
  370.             if ( sourceStream != null )
  371.                 sourceStream.Close();
  372.             if ( changedStream != null )
  373.                 changedStream.Close();
  374.             throw;
  375.         }
  376.     }
  377.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.Compare3"]/*' />
  378.     /// <summary>
  379.     ///    Compares two XML documents or fragments.
  380.     /// </summary>
  381.     /// <param name="sourceReader">XmlReader representing the original xml document or fragment.</param>
  382.     /// <param name="changedFile">XmlReaser representing the changed xml document or fragment.</param>
  383.     /// <returns>True, if the documents/fragments are identical.</returns>
  384.     public bool Compare( XmlReader sourceReader, XmlReader changedReader ) 
  385.     {
  386.         return Compare( sourceReader, changedReader, null );
  387.     }
  388.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.Compare4"]/*' />
  389.     /// <summary>
  390.     ///    Compares two XML documents or fragments.
  391.     ///    If the diffgramWriter parameter is not null it will contain the list of changes 
  392.     ///    between the two XML documents/fragments (diffgram).
  393.     /// </summary>
  394.     /// <param name="sourceReader">XmlReader representing the original xml document or fragment.</param>
  395.     /// <param name="changedFile">XmlReaser representing the changed xml document or fragment.</param>
  396.     /// <param name="diffgramWriter">XmlWriter object for returning the list of changes (diffgram).</param>
  397.     /// <returns>True, if the documents/fragments are identical.</returns>
  398.     public bool Compare( XmlReader sourceReader, XmlReader changedReader, XmlWriter diffgramWriter ) 
  399.     {
  400.         if ( sourceReader == null )
  401.             throw new ArgumentNullException( "sourceReader" );
  402.         if ( changedReader == null )
  403.             throw new ArgumentNullException( "changedReader" );
  404.         try
  405.         {
  406.             XmlHash xmlHash = new XmlHash( this );
  407. #if MEASURE_PERF
  408.             _xmlDiffPerf.Clean();
  409.             int startTickCount = Environment.TickCount;
  410. #endif
  411.             // load source document
  412.             _sourceDoc = new XmlDiffDocument( this );
  413.             _sourceDoc.Load( sourceReader, xmlHash );
  414. #if DEBUG
  415.             Trace.WriteLineIf( T_Phases.Enabled, "* Source document loaded: " + _sourceDoc.NodesCount + " nodes." );
  416. #endif
  417.         
  418.             // load target document
  419.             _targetDoc = new XmlDiffDocument( this );
  420.             _targetDoc.Load( changedReader, xmlHash );
  421.             if ( _fragments == TriStateBool.DontKnown ) {
  422.                 _fragments = ( _sourceDoc.IsFragment || _targetDoc.IsFragment ) ? TriStateBool.Yes : TriStateBool.No;
  423.             }
  424. #if DEBUG
  425.             Trace.WriteLineIf( T_Phases.Enabled, "* Target document loaded: " + _targetDoc.NodesCount + " nodes." );
  426. #endif
  427. #if MEASURE_PERF
  428.             _xmlDiffPerf._loadTime = Environment.TickCount - startTickCount;
  429. #endif
  430.             // compare
  431.             return Diff( diffgramWriter );
  432.         }
  433.         finally
  434.         {
  435.             _sourceDoc = null;
  436.             _targetDoc = null;
  437.         }
  438.     }
  439.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.Compare5"]/*' />
  440.     /// <summary>
  441.     ///    Compares two XML nodes.
  442.     ///    If the diffgramWriter parameter is not null it will contain the list of changes 
  443.     ///    between the two XML documents/fragments (diffgram).
  444.     /// </summary>
  445.     /// <param name="sourceNode">Original XML node</param>
  446.     /// <param name="changedNode">Changed XML node</param>
  447.     /// <param name="diffgramWriter">XmlWriter object for returning the list of changes (diffgram).</param>
  448.     /// <returns>True, if the documents/fragments are identical.</returns>
  449.     public bool Compare( XmlNode sourceNode, XmlNode changedNode )
  450.     {
  451.         return Compare( sourceNode, changedNode, null );
  452.     }
  453.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.Compare6"]/*' />
  454.     /// <summary>
  455.     ///    Compares two XML nodes.
  456.     ///    If the diffgramWriter parameter is not null it will contain the list of changes 
  457.     ///    between the two XML documents/fragments (diffgram).
  458.     /// </summary>
  459.     /// <param name="sourceNode">Original XML node</param>
  460.     /// <param name="changedNode">Changed XML node</param>
  461.     /// <param name="diffgramWriter">XmlWriter object for returning the list of changes (diffgram).</param>
  462.     /// <returns>True, if the documents/fragments are identical.</returns>
  463.     public bool Compare( XmlNode sourceNode, XmlNode changedNode, XmlWriter diffgramWriter )
  464.     {
  465.         if ( sourceNode == null )
  466.             throw new ArgumentNullException( "sourceNode" );
  467.         if ( changedNode == null )
  468.             throw new ArgumentNullException( "changedNode" );
  469.         try
  470.         {
  471.             XmlHash xmlHash = new XmlHash( this );
  472. #if MEASURE_PERF
  473.             _xmlDiffPerf.Clean();
  474.             int startTickCount = Environment.TickCount;
  475. #endif
  476.             // load source document
  477.             _sourceDoc = new XmlDiffDocument( this );
  478.             _sourceDoc.Load( sourceNode, xmlHash );
  479. #if DEBUG
  480.             Trace.WriteLineIf( T_Phases.Enabled, "* Source document loaded: " + _sourceDoc.NodesCount + " nodes." );
  481. #endif
  482.         
  483.             // load target document
  484.             _targetDoc = new XmlDiffDocument( this );
  485.             _targetDoc.Load( changedNode, xmlHash );
  486.             _fragments = ( sourceNode.NodeType != XmlNodeType.Document || 
  487.                            changedNode.NodeType != XmlNodeType.Document ) ? TriStateBool.Yes : TriStateBool.No;
  488. #if DEBUG
  489.             Trace.WriteLineIf( T_Phases.Enabled, "* Target document loaded: " + _targetDoc.NodesCount + " nodes." );
  490. #endif
  491. #if MEASURE_PERF
  492.             _xmlDiffPerf._loadTime = Environment.TickCount - startTickCount;
  493. #endif
  494.             // compare
  495.             return Diff( diffgramWriter );
  496.         }
  497.         finally
  498.         {
  499.             _sourceDoc = null;
  500.             _targetDoc = null;
  501.         }
  502.     }
  503.     private bool Diff( XmlWriter diffgramWriter )
  504.     {
  505.         try
  506.         {
  507. #if MEASURE_PERF 
  508.             int startTickCount = Environment.TickCount;
  509. #endif
  510.             // compare hash values of root nodes and return if same (the hash values were computed during load)
  511.             if ( IdenticalSubtrees( _sourceDoc, _targetDoc ) )
  512.             {
  513. if ( diffgramWriter != null )
  514. {
  515. Diffgram emptyDiffgram = new DiffgramGenerator( this ).GenerateEmptyDiffgram();
  516. emptyDiffgram.WriteTo( diffgramWriter );
  517. diffgramWriter.Flush();
  518. }
  519. #if DEBUG
  520.                 Trace.WriteLineIf( T_Phases.Enabled, "* Done." );
  521. #endif
  522. #if MEASURE_PERF 
  523.                 _xmlDiffPerf._identicalOrNoDiffWriterTime = Environment.TickCount - startTickCount;
  524. #endif
  525.                 return true;
  526.             }
  527. else if ( diffgramWriter == null )
  528.             {
  529. #if DEBUG
  530.                 Trace.WriteLineIf( T_Phases.Enabled, "* Done." );
  531. #endif
  532. #if MEASURE_PERF 
  533.                 _xmlDiffPerf._identicalOrNoDiffWriterTime = Environment.TickCount - startTickCount;
  534. #endif
  535. return false;
  536.             }
  537. #if MEASURE_PERF 
  538.             _xmlDiffPerf._identicalOrNoDiffWriterTime = Environment.TickCount - startTickCount;
  539. #endif
  540.             // Match & shrink identical subtrees
  541. #if DEBUG
  542.             Trace.WriteLineIf( T_Phases.Enabled, "* Matching identical subtrees..." );
  543. #endif
  544. #if MEASURE_PERF 
  545.             startTickCount = Environment.TickCount;
  546. #endif
  547.             MatchIdenticalSubtrees();
  548. #if MEASURE_PERF 
  549.             _xmlDiffPerf._matchTime = Environment.TickCount - startTickCount;
  550. #endif
  551. #if DEBUG
  552.             Trace.WriteLineIf( T_Phases.Enabled, "* Source document shrinked: " + _sourceDoc.NodesCount + " nodes" );
  553.             Trace.WriteLineIf( T_Phases.Enabled, "* Target document shrinked: " + _targetDoc.NodesCount + " nodes" );
  554. #endif
  555.             Diffgram diffgram = null;
  556.             // Choose algorithm
  557.             switch ( _algorithm ) {
  558.                 case XmlDiffAlgorithm.Fast:
  559.                     diffgram = WalkTreeAlgorithm();
  560.                     break;
  561.                 case XmlDiffAlgorithm.Precise:
  562.                     diffgram = ZhangShashaAlgorithm();
  563.                     break;
  564.                 case XmlDiffAlgorithm.Auto:
  565.                     if ( _sourceDoc.NodesCount + _targetDoc.NodesCount <= MaxTotalNodesCountForTreeDistance )
  566.                         diffgram = ZhangShashaAlgorithm();
  567.                     else
  568.                         diffgram = WalkTreeAlgorithm();
  569.                     break;
  570.                 default:
  571.                     Debug.Assert( false );
  572.                     break;
  573.             }
  574.             // Output the diffgram
  575. #if MEASURE_PERF 
  576.             startTickCount = Environment.TickCount;
  577. #endif
  578.             Debug.Assert( diffgramWriter != null );
  579.             diffgram.WriteTo( diffgramWriter );
  580.             diffgramWriter.Flush();
  581. #if MEASURE_PERF 
  582.             _xmlDiffPerf._diffgramSaveTime = Environment.TickCount - startTickCount;
  583. #endif
  584. #if DEBUG
  585.             Trace.WriteLineIf( T_Phases.Enabled, "* Done." );
  586. #endif
  587.         }
  588.         finally
  589.         {
  590.             _sourceNodes = null;
  591.             _targetNodes = null;
  592.         }
  593.         return false;
  594.     }
  595.     Diffgram ZhangShashaAlgorithm()
  596.     {
  597.         // Preprocess the trees for the tree-to-tree comparison algorithm and diffgram generation.
  598.         // This includes post-order numbering of all nodes (the source and target nodes are stored
  599.         // in post-order in the _sourceNodes and _targetNodes arrays).
  600. #if DEBUG
  601.         Trace.WriteLineIf( T_Phases.Enabled, "* Using Zhang-Shasha Algorithm" );
  602.         Trace.WriteLineIf( T_Phases.Enabled, "* Preprocessing trees..." );
  603. #endif
  604. #if MEASURE_PERF 
  605.         int startTickCount = Environment.TickCount;
  606. #endif
  607.         PreprocessTree( _sourceDoc, ref _sourceNodes );
  608.         PreprocessTree( _targetDoc, ref _targetNodes );
  609. #if MEASURE_PERF 
  610.         _xmlDiffPerf._preprocessTime = Environment.TickCount - startTickCount;
  611. #endif
  612.         // Find minimal edit distance between the trees
  613. #if DEBUG
  614.         Trace.WriteLineIf( T_Phases.Enabled, "* Computing minimal tree distance..." );
  615.         if ( T_Tree.Enabled ) 
  616.         {
  617.             Trace.WriteLine( "Source tree: " );
  618.             _sourceDoc.Dump( string.Empty );
  619.             Trace.WriteLine( "Target tree: " );
  620.             _targetDoc.Dump( string.Empty );
  621.         }
  622. #endif
  623. #if MEASURE_PERF 
  624.         startTickCount = Environment.TickCount;
  625. #endif
  626.         EditScript editScript = ( new MinimalTreeDistanceAlgo( this ) ).FindMinimalDistance();
  627.         Debug.Assert( editScript != null );
  628. #if MEASURE_PERF 
  629.         _xmlDiffPerf._treeDistanceTime = Environment.TickCount - startTickCount;
  630. #endif
  631. #if DEBUG 
  632.         if ( T_EditScript.Enabled )
  633.         {
  634.             Trace.Write( "nMinimal edit script: n" ); 
  635.             editScript.Dump();
  636.         }
  637. #endif
  638.         // Generate the diffgram
  639. #if DEBUG
  640.         Trace.WriteLineIf( T_Phases.Enabled, "* Generating diffgram..." );
  641. #endif
  642. #if MEASURE_PERF 
  643.         startTickCount = Environment.TickCount;
  644. #endif
  645.         Diffgram diffgram = new DiffgramGenerator( this ).GenerateFromEditScript( editScript );
  646. #if MEASURE_PERF 
  647.         _xmlDiffPerf._diffgramGenerationTime = Environment.TickCount - startTickCount;
  648. #endif
  649.         return diffgram;
  650.     }
  651.     private void PreprocessTree( XmlDiffDocument doc, ref XmlDiffNode[] postOrderArray )
  652.     {
  653.         // allocate the array for post-ordered nodes.
  654.         // The index 0 is not used; this is to have the consistent indexing of all arrays in the algorithm;
  655.         postOrderArray = new XmlDiffNode[ doc.NodesCount + 1 ];
  656.         postOrderArray[0] = null;
  657.         // recursivelly process all nodes
  658.         int index = 1;
  659.         PreprocessNode( doc, ref postOrderArray, ref index );
  660.         // root node is a 'key root' node
  661.         doc._bKeyRoot = true;
  662.         Debug.Assert( index - 1 == doc.NodesCount );
  663.     }
  664.     private void PreprocessNode( XmlDiffNode node, ref XmlDiffNode[] postOrderArray, ref int currentIndex )
  665.     {
  666.         // process children
  667. if ( node.HasChildNodes )
  668. {
  669. Debug.Assert( node.FirstChildNode != null );
  670. #if DEBUG
  671.             int nodesCount = 0;
  672. #endif
  673. XmlDiffNode curChild = node.FirstChildNode;
  674. curChild._bKeyRoot = false;
  675. for (;;)
  676. {
  677. PreprocessNode( curChild, ref postOrderArray, ref currentIndex );
  678. #if DEBUG
  679. nodesCount += curChild.NodesCount;
  680. #endif
  681. curChild = curChild._nextSibling;
  682.                 // 'key root' node is the root node and each node that has a previous sibling node
  683. if ( curChild != null ) 
  684.                     curChild._bKeyRoot = true;
  685. else break;
  686. }
  687.             // leftist leaf in the subtree rooted at 'node'
  688. node.Left = node.FirstChildNode.Left; 
  689. #if DEBUG
  690. Debug.Assert( node.NodesCount == nodesCount + 1 );
  691. #endif
  692. }
  693. else
  694. {
  695.             // leftist leaf in the subtree rooted at 'node'
  696. node.Left = currentIndex; 
  697.             // total number of nodes in the subtree rooted at 'node'
  698. node.NodesCount = 1;
  699. }
  700. #if DEBUG
  701.     node._index = currentIndex;
  702. #endif
  703.         // put the node in post-order array
  704.         Debug.Assert( postOrderArray.Length > currentIndex );
  705.         postOrderArray[ currentIndex++ ] = node;
  706.     }
  707.     // Finds identical subtrees in both trees and skrinks them into XmlDiffShrankNode instances
  708.     private void MatchIdenticalSubtrees()
  709.     {
  710.         Hashtable sourceUnmatchedNodes = new Hashtable( 16 );
  711.         Hashtable targetUnmatchedNodes = new Hashtable( 16 );
  712.         Queue sourceNodesToExpand = new Queue( 16 );
  713.         Queue targetNodesToExpand = new Queue( 16 );
  714.         sourceNodesToExpand.Enqueue( _sourceDoc );
  715.         targetNodesToExpand.Enqueue( _targetDoc );
  716.         AddNodeToHashTable( sourceUnmatchedNodes, _sourceDoc );
  717.         AddNodeToHashTable( targetUnmatchedNodes, _targetDoc );
  718.         while ( sourceNodesToExpand.Count > 0  ||
  719.                 targetNodesToExpand.Count > 0 )
  720.         {
  721.             // Expand next level of source nodes and add them to the sourceUnmatchedNodes hashtable.
  722.             // Leave the parents of expanded nodes in the sourceNodesToExpand queue for later use.
  723.             {
  724.                 IEnumerator en = sourceNodesToExpand.GetEnumerator();
  725.                 while ( en.MoveNext() )
  726.                 {
  727.                     XmlDiffParentNode sourceParentNode = (XmlDiffParentNode) en.Current;
  728.                     Debug.Assert( !sourceParentNode._bExpanded );
  729.                     sourceParentNode._bExpanded = true;
  730.                     
  731.                     if ( !sourceParentNode.HasChildNodes )
  732.                         continue;
  733.                     XmlDiffNode curSourceNode = sourceParentNode._firstChildNode;
  734.                     while ( curSourceNode != null )
  735.                     {
  736.                         AddNodeToHashTable( sourceUnmatchedNodes, curSourceNode );
  737.                         curSourceNode = curSourceNode._nextSibling;
  738.                     }
  739.                 }
  740.             }
  741.             // Expand next level of target nodes and try to match them against the sourceUnmatchedNodes hashtable.
  742.             // to find matching node. 
  743.             int count = targetNodesToExpand.Count;
  744.             for ( int i = 0; i < count; i++ )
  745.             {
  746.                 XmlDiffParentNode targetParentNode = (XmlDiffParentNode) targetNodesToExpand.Dequeue();
  747.                 Debug.Assert( !targetParentNode._bExpanded );
  748.                 if ( !NodeInHashTable( targetUnmatchedNodes, targetParentNode ) ) {
  749.                     continue;
  750.                 }
  751.                 targetParentNode._bExpanded = true;
  752.                 
  753.                 if ( !targetParentNode.HasChildNodes )
  754.                     continue;
  755.                 XmlDiffNode curTargetNode = targetParentNode._firstChildNode;
  756.                 while ( curTargetNode != null )
  757.                 {
  758.                     Debug.Assert( !( curTargetNode is XmlDiffAttributeOrNamespace ) );
  759.                     // try to match
  760.                     XmlDiffNode firstSourceNode = null;
  761.                     XmlDiffNodeListHead matchingSourceNodes = (XmlDiffNodeListHead) sourceUnmatchedNodes[ curTargetNode.HashValue ];
  762.                     if ( matchingSourceNodes != null  ) {
  763.                         // find matching node and remove it from the hashtable
  764.                         firstSourceNode = HTFindAndRemoveMatchingNode( sourceUnmatchedNodes, matchingSourceNodes, curTargetNode );
  765.                     }
  766.                     
  767.                     // no match
  768.                     if ( firstSourceNode == null || 
  769.                          // do not shrink xml declarations and DTD
  770.                          (int)curTargetNode.NodeType < 0 )
  771.                     {
  772.                         if ( curTargetNode.HasChildNodes )
  773.                             targetNodesToExpand.Enqueue( curTargetNode );
  774.                         else
  775.                             curTargetNode._bExpanded = true;
  776.                         AddNodeToHashTable( targetUnmatchedNodes, curTargetNode );
  777.                         curTargetNode = curTargetNode._nextSibling;
  778.                         continue;
  779.                     }
  780.                     HTRemoveAncestors( sourceUnmatchedNodes, firstSourceNode );
  781.                     HTRemoveDescendants( sourceUnmatchedNodes, firstSourceNode );
  782.                     HTRemoveAncestors( targetUnmatchedNodes, curTargetNode );
  783.                     // there are no target node descendants in the hash table
  784.                     // find matching interval - starts at startSourceNode and startTargetNode
  785.                     XmlDiffNode firstTargetNode = curTargetNode;
  786.                     XmlDiffNode lastSourceNode = firstSourceNode;
  787.                     XmlDiffNode lastTargetNode = firstTargetNode;
  788.                     curTargetNode = curTargetNode._nextSibling;
  789.                     XmlDiffNode curSourceNode = firstSourceNode._nextSibling;
  790.                     while ( curTargetNode != null  &&  
  791.                             curSourceNode != null  &&
  792.                             curSourceNode.NodeType != XmlDiffNodeType.ShrankNode )
  793.                     {
  794.                         // still matches and the nodes has not been matched elsewhere
  795.                         if ( IdenticalSubtrees( curSourceNode, curTargetNode )  &&
  796.                              sourceUnmatchedNodes.Contains( curSourceNode.HashValue ) )
  797.                         {
  798.                             HTRemoveNode( sourceUnmatchedNodes, curSourceNode );
  799.                             HTRemoveDescendants( sourceUnmatchedNodes, curSourceNode );
  800.                         }
  801.                         // no match -> end of interval
  802.                         else
  803.                             break;
  804.                         lastSourceNode = curSourceNode;
  805.                         curSourceNode = curSourceNode._nextSibling;
  806.                         //Debug.Assert( curSourceNode == null || curSourceNode.NodeType != XmlDiffNodeType.ShrankNode );
  807.                         lastTargetNode = curTargetNode;
  808.                         curTargetNode = curTargetNode._nextSibling;
  809.                         //Debug.Assert( curTargetNode == null || curTargetNode.NodeType != XmlDiffNodeType.ShrankNode );
  810.                     }
  811.                     if ( firstSourceNode != lastSourceNode || 
  812.                          firstSourceNode.NodeType != XmlDiffNodeType.Element ) {
  813.                         ShrinkNodeInterval( firstSourceNode, lastSourceNode, firstTargetNode, lastTargetNode);
  814.                     }
  815.                     else {
  816.                         XmlDiffElement e = (XmlDiffElement)firstSourceNode;
  817.                         if ( e.FirstChildNode != null || e._attributes != null ) {
  818.                             ShrinkNodeInterval( firstSourceNode, lastSourceNode, firstTargetNode, lastTargetNode);
  819.                         }
  820.                     }
  821.                 }
  822.             }
  823.             // Walk through the newly expanded source nodes (=children of nodes in sourceNodesToExpand queue)
  824.             // and try to match them against targetUnmatchedNodes hashtable.
  825.             count = sourceNodesToExpand.Count;
  826.             for ( int i = 0; i < count; i++ )
  827.             {
  828.                 XmlDiffParentNode sourceParentNode = (XmlDiffParentNode) sourceNodesToExpand.Dequeue();
  829.                 Debug.Assert( sourceParentNode._bExpanded );
  830.                 if ( !sourceParentNode.HasChildNodes )
  831.                     continue;
  832.                 XmlDiffNode curSourceNode = sourceParentNode._firstChildNode;
  833.                 while ( curSourceNode != null )
  834.                 {
  835.                     // it it's an attribute or the node has already been matched -> continue
  836.                     Debug.Assert( ! ( curSourceNode is XmlDiffAttributeOrNamespace ) ) ;
  837.                     if ( curSourceNode is XmlDiffShrankNode ||
  838.                          !NodeInHashTable( sourceUnmatchedNodes, curSourceNode ) )
  839.                     {
  840.                         curSourceNode = curSourceNode._nextSibling;
  841.                         continue;
  842.                     }
  843.                     // try to match
  844.                     XmlDiffNode firstTargetNode = null;
  845.                     XmlDiffNodeListHead matchingTargetNodes = (XmlDiffNodeListHead) targetUnmatchedNodes[ curSourceNode.HashValue ];
  846.                     if ( matchingTargetNodes != null  ) {
  847.                         // find matching node and remove it from the hashtable
  848.                         firstTargetNode = HTFindAndRemoveMatchingNode( targetUnmatchedNodes, matchingTargetNodes, curSourceNode );
  849.                     }
  850.                     
  851.                     // no match
  852.                     if ( firstTargetNode == null || 
  853.                          // do not shrink xml declarations and DTD
  854.                          (int)curSourceNode.NodeType < 0 )
  855.                     {
  856.                         if ( curSourceNode.HasChildNodes )
  857.                             sourceNodesToExpand.Enqueue( curSourceNode );
  858.                         else
  859.                             curSourceNode._bExpanded = true;
  860.                         curSourceNode = curSourceNode._nextSibling;
  861.                         continue;
  862.                     }
  863.                     HTRemoveAncestors( targetUnmatchedNodes, firstTargetNode );
  864.                     HTRemoveDescendants( targetUnmatchedNodes, firstTargetNode );
  865.                     if ( !HTRemoveNode( sourceUnmatchedNodes, curSourceNode ) )
  866.                         Debug.Assert( false );
  867.                     HTRemoveAncestors( sourceUnmatchedNodes, curSourceNode );
  868.                     // there are no source node descendants in the hash table
  869.                     Debug.Assert( !( curSourceNode is XmlDiffAttributeOrNamespace ) );
  870.                     // find matching interval - starts at startSourceNode and startTargetNode
  871.                     XmlDiffNode firstSourceNode = curSourceNode;
  872.                     XmlDiffNode lastSourceNode = firstSourceNode;
  873.                     XmlDiffNode lastTargetNode = firstTargetNode;
  874.                     curSourceNode = curSourceNode._nextSibling;
  875.                     XmlDiffNode curTargetNode = firstTargetNode._nextSibling;
  876.                     while ( curSourceNode != null  && 
  877.                             curTargetNode != null  &&
  878.                             curTargetNode.NodeType != XmlDiffNodeType.ShrankNode )
  879.                     {
  880.                         // still matches and the nodes has not been matched elsewhere
  881.                         if ( IdenticalSubtrees( curSourceNode, curTargetNode ) &&
  882.                             sourceUnmatchedNodes.Contains( curSourceNode.HashValue ) &&
  883.                             targetUnmatchedNodes.Contains( curTargetNode.HashValue ) )
  884.                         {
  885.                             HTRemoveNode( sourceUnmatchedNodes, curSourceNode );
  886.                             HTRemoveDescendants( sourceUnmatchedNodes, curSourceNode );
  887.                             HTRemoveNode( targetUnmatchedNodes, curTargetNode );
  888.                             HTRemoveDescendants( targetUnmatchedNodes, curTargetNode );
  889.                         }
  890.                         // no match -> end of interval
  891.                         else {
  892.                             break;
  893.                         }
  894.                         lastSourceNode = curSourceNode;
  895.                         curSourceNode = curSourceNode._nextSibling;
  896.                         lastTargetNode = curTargetNode;
  897.                         curTargetNode = curTargetNode._nextSibling;
  898.                     }
  899.                     if ( firstSourceNode != lastSourceNode || 
  900.                          firstSourceNode.NodeType != XmlDiffNodeType.Element ) {
  901.                         ShrinkNodeInterval( firstSourceNode, lastSourceNode, firstTargetNode, lastTargetNode);
  902.                     }
  903.                     else {
  904.                         XmlDiffElement e = (XmlDiffElement)firstSourceNode;
  905.                         if ( e.FirstChildNode != null || e._attributes != null ) {
  906.                             ShrinkNodeInterval( firstSourceNode, lastSourceNode, firstTargetNode, lastTargetNode);
  907.                         }
  908.                     }
  909.                 }
  910.             }
  911.         }
  912.     }
  913.     private void AddNodeToHashTable( Hashtable hashtable, XmlDiffNode node )
  914.     {
  915.         Debug.Assert( hashtable != null );
  916.         Debug.Assert( node != null );
  917.         Debug.Assert( node.NodeType != XmlDiffNodeType.ShrankNode );
  918.         ulong hashValue = node.HashValue;
  919.         XmlDiffNodeListHead nodeListHead = (XmlDiffNodeListHead) hashtable[ hashValue ];
  920.         if ( nodeListHead == null ) {
  921.             hashtable[ hashValue ] = new XmlDiffNodeListHead( new XmlDiffNodeListMember( node, null ) ); 
  922.         }
  923.         else 
  924.         {
  925.             XmlDiffNodeListMember newMember = new XmlDiffNodeListMember( node, null );
  926.             nodeListHead._last._next = newMember;
  927.             nodeListHead._last = newMember;
  928.         }
  929.     }
  930.     private bool HTRemoveNode( Hashtable hashtable, XmlDiffNode node )
  931.     {
  932.         Debug.Assert( hashtable != null );
  933.         Debug.Assert( node != null );
  934.         XmlDiffNodeListHead xmlNodeListHead = (XmlDiffNodeListHead) hashtable[ node.HashValue ];
  935.         if ( xmlNodeListHead == null ) {
  936.             return false;
  937.         }
  938.         XmlDiffNodeListMember xmlNodeList = xmlNodeListHead._first;
  939.         if ( xmlNodeList._node == node )
  940.         {
  941.             if ( xmlNodeList._next == null ) {
  942.                 hashtable.Remove( node.HashValue );
  943.             }
  944.             else {
  945.                 Debug.Assert( xmlNodeListHead._first != xmlNodeListHead._last );
  946.                 xmlNodeListHead._first = xmlNodeList._next;
  947.             }
  948.         }
  949.         else
  950.         {
  951.             if ( xmlNodeList._next == null ) {
  952.                 return false;
  953.             }
  954.             while ( xmlNodeList._next._node != node )
  955.             {
  956.                 xmlNodeList = xmlNodeList._next;
  957.                 if ( xmlNodeList._next == null ) {
  958.                     return false;
  959.                 }
  960.             }
  961.             xmlNodeList._next = xmlNodeList._next._next;
  962.             if ( xmlNodeList._next == null ) {
  963.                 xmlNodeListHead._last = xmlNodeList;
  964.             }
  965.         }
  966.         return true;
  967.     }
  968.     private bool NodeInHashTable( Hashtable hashtable, XmlDiffNode node ) {
  969.         XmlDiffNodeListHead nodeListHeader = (XmlDiffNodeListHead)hashtable[node.HashValue];
  970.         
  971.         if ( nodeListHeader == null ) {
  972.             return false;
  973.         }
  974.         XmlDiffNodeListMember nodeList = nodeListHeader._first;
  975.         while ( nodeList != null ) {
  976.             if ( nodeList._node == node ) {
  977.                 return true;
  978.             }
  979.             nodeList = nodeList._next;
  980.         }
  981.         return false;
  982.     }
  983.     // Shrinks the interval of nodes in one or mode XmlDiffShrankNode instances;
  984.     // The shrank interval can contain only adjacent nodes => the position of two adjacent nodes differs by 1.
  985.     private void ShrinkNodeInterval( XmlDiffNode firstSourceNode, XmlDiffNode lastSourceNode, 
  986.                                      XmlDiffNode firstTargetNode, XmlDiffNode lastTargetNode )
  987.     {
  988. XmlDiffNode sourcePreviousSibling = null;
  989. XmlDiffNode targetPreviousSibling = null;
  990.         // calculate subtree hash value
  991.         ulong hashValue = 0;
  992.         XmlDiffNode curNode = firstSourceNode;
  993.         for (;;) {
  994.             hashValue += ( hashValue << 7 ) + curNode.HashValue;
  995.             if ( curNode == lastSourceNode )
  996.                 break;
  997.             curNode = curNode._nextSibling;
  998.         }
  999. #if DEBUG
  1000.         // calculate hash value of the second subtree and make sure they are the same
  1001.         ulong hashValue2 = 0;
  1002.         curNode = firstTargetNode;
  1003.         for (;;) {
  1004.             hashValue2 += ( hashValue2 << 7 ) + curNode.HashValue;
  1005.             if ( curNode == lastTargetNode )
  1006.                 break;
  1007.             curNode = curNode._nextSibling;
  1008.         }
  1009.         Debug.Assert( hashValue == hashValue2 );
  1010. #endif
  1011.         // IgnoreChildOrder -> the nodes has been sorted by name/value before comparing.
  1012.         // 'Unsort' the matching interval of nodes (=sort by node position) to
  1013.         // group adjacent nodes that can be shrank.
  1014.         if ( IgnoreChildOrder  &&  firstSourceNode != lastSourceNode )
  1015.         {
  1016. Debug.Assert( firstTargetNode != lastTargetNode );
  1017.             SortNodesByPosition( ref firstSourceNode, ref lastSourceNode, ref sourcePreviousSibling );
  1018.             SortNodesByPosition( ref firstTargetNode, ref lastTargetNode, ref targetPreviousSibling );
  1019.         }
  1020. #if DEBUG
  1021.         Trace.WriteIf( T_SubtreeMatching.Enabled, "Shrinking nodes: " );
  1022.         XmlDiffNode node = firstSourceNode;
  1023.         for (;;)
  1024.         {
  1025.             Trace.WriteIf( T_SubtreeMatching.Enabled, node.OuterXml );
  1026.             if ( node == lastSourceNode )
  1027.                 break;
  1028.             node = node._nextSibling;
  1029.         }
  1030.         Trace.WriteIf( T_SubtreeMatching.Enabled, "n" );
  1031. #endif
  1032.         // replace the interval by XmlDiffShrankNode instance
  1033. XmlDiffShrankNode sourceShrankNode = ReplaceNodeIntervalWithShrankNode( firstSourceNode, 
  1034.                                                                                 lastSourceNode, 
  1035.                                                                                 sourcePreviousSibling,
  1036.                                                                                 hashValue );
  1037. XmlDiffShrankNode targetShrankNode = ReplaceNodeIntervalWithShrankNode( firstTargetNode, 
  1038.                                                                                 lastTargetNode, 
  1039.                                                                                 targetPreviousSibling,
  1040.                                                                                 hashValue );
  1041. sourceShrankNode.MatchingShrankNode = targetShrankNode;
  1042. targetShrankNode.MatchingShrankNode = sourceShrankNode;
  1043.     }
  1044. private XmlDiffShrankNode ReplaceNodeIntervalWithShrankNode( XmlDiffNode firstNode, 
  1045.                                                          XmlDiffNode lastNode,
  1046.                                                          XmlDiffNode previousSibling,
  1047.                                                                  ulong hashValue )
  1048. {
  1049. XmlDiffShrankNode shrankNode = new XmlDiffShrankNode( firstNode, lastNode, hashValue );
  1050. XmlDiffParentNode parent = firstNode._parent;
  1051. // find previous sibling node
  1052. if ( previousSibling == null  &&
  1053.  firstNode != parent._firstChildNode )
  1054. {
  1055. previousSibling = parent._firstChildNode;
  1056. while ( previousSibling._nextSibling != firstNode )
  1057. previousSibling = previousSibling._nextSibling;
  1058. }
  1059. // insert shrank node
  1060. if ( previousSibling == null )
  1061. {
  1062. Debug.Assert( firstNode == parent._firstChildNode );
  1063. shrankNode._nextSibling = parent._firstChildNode;
  1064. parent._firstChildNode = shrankNode;
  1065. }
  1066. else
  1067. {
  1068. shrankNode._nextSibling = previousSibling._nextSibling;
  1069. previousSibling._nextSibling = shrankNode;
  1070. }
  1071.         shrankNode._parent = parent;
  1072. // remove the node interval & count the total number of nodes
  1073. XmlDiffNode tmpNode;
  1074.         int totalNodesCount = 0;
  1075.         do
  1076.         {
  1077.             tmpNode = shrankNode._nextSibling;
  1078.             totalNodesCount += tmpNode.NodesCount;
  1079. shrankNode._nextSibling = shrankNode._nextSibling._nextSibling;
  1080.         } while ( tmpNode != lastNode );
  1081.         // adjust nodes count
  1082.         Debug.Assert( totalNodesCount > 0 );
  1083.         if ( totalNodesCount > 1 )
  1084.         {
  1085.             totalNodesCount--;
  1086.             while ( parent != null )
  1087.             {
  1088.                 parent.NodesCount -= totalNodesCount;
  1089.                 parent = parent._parent;
  1090.             }
  1091.         }
  1092. return shrankNode;
  1093. }
  1094.     private XmlDiffNode HTFindAndRemoveMatchingNode( Hashtable hashtable, XmlDiffNodeListHead nodeListHead, XmlDiffNode nodeToMatch )
  1095.     {
  1096.         Debug.Assert( hashtable != null );
  1097.         Debug.Assert( nodeListHead != null );
  1098.         // find matching node in the list
  1099.         XmlDiffNodeListMember nodeList = nodeListHead._first;
  1100.         XmlDiffNode node = nodeList._node;
  1101.         if ( IdenticalSubtrees( node, nodeToMatch ) ) {
  1102.             // remove the node itself
  1103.             if ( nodeList._next == null ) {
  1104.                 hashtable.Remove( node.HashValue );
  1105.             }
  1106.             else {
  1107.                 Debug.Assert( nodeListHead._first != nodeListHead._last );
  1108.                 nodeListHead._first = nodeList._next;
  1109.             }
  1110.             return node;
  1111.         } 
  1112.         else {
  1113.             while ( nodeList._next != null ) {
  1114.                 if ( IdenticalSubtrees( nodeList._node, nodeToMatch ) ) {
  1115.                     nodeList._next = nodeList._next._next;
  1116.                     if ( nodeList._next == null ) {
  1117.                         nodeListHead._last = nodeList;
  1118.                     }
  1119.                     return node;
  1120.                 }
  1121.             }
  1122.             return null;
  1123.         }
  1124.     }
  1125.     private void HTRemoveAncestors( Hashtable hashtable, XmlDiffNode node )
  1126.     {
  1127.         XmlDiffNode curAncestorNode = node._parent;
  1128.         while ( curAncestorNode != null )
  1129.         {
  1130.             if ( !HTRemoveNode( hashtable, curAncestorNode ) )
  1131.                 break;
  1132.             curAncestorNode._bSomeDescendantMatches = true;
  1133.             curAncestorNode = curAncestorNode._parent;
  1134.         }
  1135.     }
  1136.     private void HTRemoveDescendants( Hashtable hashtable, XmlDiffNode parent ) {
  1137.         if ( !parent._bExpanded || !parent.HasChildNodes )
  1138.             return;
  1139.         XmlDiffNode curNode = parent.FirstChildNode;
  1140.         for (;;)
  1141.         {
  1142.             Debug.Assert( curNode != null );
  1143.             if ( curNode._bExpanded  &&  curNode.HasChildNodes )
  1144.             {
  1145.                 curNode = ((XmlDiffParentNode) curNode)._firstChildNode;
  1146.                 continue;
  1147.             }
  1148.             HTRemoveNode( hashtable, curNode );
  1149.         TryNext:
  1150.             if ( curNode._nextSibling != null )
  1151.             {
  1152.                 curNode = curNode._nextSibling;
  1153.                 continue;
  1154.             }
  1155.             else if ( curNode._parent != parent )
  1156.             {
  1157.                 curNode = curNode._parent;
  1158.                 goto TryNext;
  1159.             }
  1160.             else {
  1161.                 break;
  1162.             }
  1163.         }
  1164.     }
  1165.     private void RemoveDescendantsFromHashTable( Hashtable hashtable, XmlDiffNode parentNode )
  1166.     {
  1167.         
  1168.     }
  1169.     internal static void SortNodesByPosition( ref XmlDiffNode firstNode,
  1170.                                               ref XmlDiffNode lastNode,
  1171.                                               ref XmlDiffNode firstPreviousSibbling )
  1172.     {
  1173.         XmlDiffParentNode parent = firstNode._parent;
  1174.         
  1175.         // find previous sibling node for the first node
  1176. if ( firstPreviousSibbling == null  &&
  1177.  firstNode != parent._firstChildNode )
  1178. {
  1179. firstPreviousSibbling = parent._firstChildNode;
  1180. while ( firstPreviousSibbling._nextSibling != firstNode )
  1181. firstPreviousSibbling = firstPreviousSibbling._nextSibling;
  1182. }
  1183.         // save the next sibling node for the last node
  1184.         XmlDiffNode lastNextSibling = lastNode._nextSibling;
  1185.         lastNode._nextSibling = null;
  1186. // count the number of nodes to sort
  1187. int count = 0;
  1188. XmlDiffNode curNode = firstNode;
  1189. while ( curNode != null )
  1190. {
  1191.             count++;
  1192. curNode = curNode._nextSibling;
  1193. }
  1194. Debug.Assert( count > 0 );
  1195.         if ( count >= MininumNodesForQuicksort ) 
  1196.             QuickSortNodes( ref firstNode, ref lastNode, count, firstPreviousSibbling, lastNextSibling );
  1197.         else
  1198.     SlowSortNodes( ref firstNode, ref lastNode, firstPreviousSibbling, lastNextSibling );
  1199. }
  1200. static private void SlowSortNodes( ref XmlDiffNode firstNode, ref XmlDiffNode lastNode, 
  1201.                         XmlDiffNode firstPreviousSibbling, XmlDiffNode lastNextSibling )
  1202. {
  1203.         Debug.Assert( firstNode != null );
  1204.         Debug.Assert( lastNode != null );
  1205.         XmlDiffNode firstSortedNode = firstNode;
  1206.         XmlDiffNode lastSortedNode = firstNode;
  1207.         XmlDiffNode nodeToSort = firstNode._nextSibling;
  1208. lastSortedNode._nextSibling = null;
  1209.         while ( nodeToSort != null )
  1210.         {
  1211.             XmlDiffNode curNode = firstSortedNode;
  1212.             if ( nodeToSort.Position < firstSortedNode.Position )
  1213.             {
  1214.                 XmlDiffNode tmpNode = nodeToSort._nextSibling;
  1215.                 
  1216.                 nodeToSort._nextSibling = firstSortedNode;
  1217.                 firstSortedNode = nodeToSort;
  1218.                 nodeToSort = tmpNode;
  1219.             }
  1220.             else
  1221.             {
  1222.                 while ( curNode._nextSibling != null &&
  1223.                         nodeToSort.Position > curNode._nextSibling.Position )
  1224.                     curNode = curNode._nextSibling;
  1225.                 XmlDiffNode tmpNode = nodeToSort._nextSibling;
  1226.                 if ( curNode._nextSibling == null )
  1227.                     lastSortedNode = nodeToSort;
  1228.                 nodeToSort._nextSibling = curNode._nextSibling;
  1229.                 curNode._nextSibling = nodeToSort;
  1230.                 nodeToSort = tmpNode;
  1231.             }
  1232.         }
  1233.         // reconnect the sorted part in the tree
  1234.         if ( firstPreviousSibbling == null )
  1235.             firstNode._parent._firstChildNode = firstSortedNode;
  1236.         else
  1237.             firstPreviousSibbling._nextSibling = firstSortedNode;
  1238.         lastSortedNode._nextSibling = lastNextSibling;
  1239.         // return
  1240.         firstNode = firstSortedNode;
  1241.         lastNode = lastSortedNode;
  1242. }
  1243. static private void QuickSortNodes( ref XmlDiffNode firstNode, ref XmlDiffNode lastNode, 
  1244.                          int count, XmlDiffNode firstPreviousSibbling, XmlDiffNode lastNextSibling )
  1245. {
  1246. Debug.Assert( count >= MininumNodesForQuicksort );
  1247. Debug.Assert( MininumNodesForQuicksort >= 2 );
  1248. // allocate & fill in the array
  1249. XmlDiffNode[] sortArray = new XmlDiffNode[ count ];
  1250. {
  1251. XmlDiffNode curNode = firstNode;
  1252. for ( int i = 0; i < count; i++, curNode = curNode._nextSibling )
  1253. {
  1254. Debug.Assert( curNode != null );
  1255. sortArray[i] = curNode;
  1256. }
  1257. }
  1258. // sort
  1259. QuickSortNodesRecursion( ref sortArray, 0, count - 1 );
  1260. // link the nodes
  1261. for ( int i = 0; i < count - 1; i++ )
  1262. sortArray[i]._nextSibling = sortArray[i+1];
  1263.         if ( firstPreviousSibbling == null )
  1264.             firstNode._parent._firstChildNode = sortArray[0];
  1265.         else
  1266.             firstPreviousSibbling._nextSibling = sortArray[0];
  1267.         sortArray[count-1]._nextSibling = lastNextSibling;
  1268.         // return
  1269.         firstNode = sortArray[0];
  1270.         lastNode = sortArray[count-1];
  1271. }
  1272. static private void QuickSortNodesRecursion( ref XmlDiffNode[] sortArray, int firstIndex, int lastIndex )
  1273. {
  1274. Debug.Assert( firstIndex < lastIndex );
  1275. int pivotPosition = sortArray[ ( firstIndex + lastIndex ) / 2 ].Position;
  1276. int i = firstIndex;
  1277. int j = lastIndex;
  1278. while ( i < j )
  1279. {
  1280. while ( sortArray[i].Position < pivotPosition ) i++;
  1281. while ( sortArray[j].Position > pivotPosition ) j--;
  1282. if ( i < j )
  1283. {
  1284. XmlDiffNode tmpNode = sortArray[i];
  1285. sortArray[i] = sortArray[j];
  1286. sortArray[j] = tmpNode;
  1287. i++;
  1288. j--;
  1289. }
  1290. else if ( i == j )
  1291. {
  1292. i++;
  1293. j--;
  1294. }
  1295. }
  1296. if ( firstIndex < j )
  1297. QuickSortNodesRecursion( ref sortArray, firstIndex, j );
  1298. if ( i < lastIndex )
  1299. QuickSortNodesRecursion( ref sortArray, i, lastIndex );
  1300. }
  1301.     // returs true if the two subtrees are identical
  1302.     private bool IdenticalSubtrees( XmlDiffNode node1, XmlDiffNode node2 )
  1303.     {
  1304.         if ( node1.HashValue != node2.HashValue )
  1305.             return false;
  1306.         else
  1307. #if VERIFY_HASH_VALUES
  1308.             return CompareSubtrees( node1, node2 );
  1309. #else
  1310.             return true;
  1311. #endif
  1312.     }
  1313.     // compares two subtrees and returns true if they are identical
  1314.     private bool CompareSubtrees( XmlDiffNode node1, XmlDiffNode node2 )
  1315.     {
  1316. Debug.Assert( node1.NodeType != XmlDiffNodeType.Namespace );
  1317. Debug.Assert( node2.NodeType != XmlDiffNodeType.Namespace );
  1318.         if ( !node1.IsSameAs( node2, this ) )
  1319.             return false;
  1320.         if ( !node1.HasChildNodes )
  1321.             return true;
  1322.         XmlDiffNode childNode1 = ((XmlDiffParentNode)node1).FirstChildNode;
  1323.         XmlDiffNode childNode2 = ((XmlDiffParentNode)node2).FirstChildNode;
  1324.         while ( childNode1 != null &&
  1325.                 childNode2 != null )
  1326.         {
  1327.             if ( !CompareSubtrees( childNode1, childNode2 )) 
  1328.                 return false;
  1329.             childNode1 = childNode1._nextSibling;
  1330.             childNode2 = childNode2._nextSibling;
  1331.         }
  1332.         Debug.Assert( childNode1 == null  &&  childNode2 == null );
  1333.         return ( childNode1 == childNode2 );
  1334.     }
  1335. // Static methods
  1336.     static internal bool IsChangeOperation( XmlDiffOperation op )
  1337.     {
  1338.         return ( (int)op >= (int)XmlDiffOperation.ChangeElementName ) &&
  1339.                ( (int)op <= (int)XmlDiffOperation.ChangeDTD );
  1340.     }
  1341.     static internal bool IsChangeOperationOnAttributesOnly( XmlDiffOperation op )
  1342.     {
  1343.         return (int)op >= (int)XmlDiffOperation.ChangeElementAttr1 &&
  1344.                (int)op <= (int)XmlDiffOperation.ChangeElementAttr3;
  1345.     }
  1346.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.ParseOptions"]/*' />
  1347.     /// <summary>
  1348.     ///    Translates string representation of XmlDiff options into XmlDiffOptions enum.
  1349.     /// </summary>
  1350.     /// <param name="options">Value of the 'options' attribute of the 'xd:xmldiff' element in diffgram.</param>
  1351.     static public XmlDiffOptions ParseOptions( string options )
  1352.     {
  1353.         if ( options == null )
  1354.             throw new ArgumentNullException( "options" );
  1355.         if ( options == XmlDiffOptions.None.ToString() ) 
  1356.             return XmlDiffOptions.None;
  1357.         else {
  1358.             XmlDiffOptions optionsEnum = XmlDiffOptions.None;
  1359.             int j = 0, i = 0;
  1360.             while ( i < options.Length )
  1361.             {
  1362.                 j = options.IndexOf( ' ', i );
  1363.                 if ( j == -1 ) 
  1364.                     j = options.Length;
  1365.                 string opt = options.Substring( i, j-i );
  1366.                 switch ( opt )
  1367.                 {
  1368.                     case "IgnoreChildOrder": optionsEnum |= XmlDiffOptions.IgnoreChildOrder;  break;
  1369.                     case "IgnoreComments":   optionsEnum |= XmlDiffOptions.IgnoreComments;    break;
  1370.                     case "IgnoreNamespaces": optionsEnum |= XmlDiffOptions.IgnoreNamespaces;  break;
  1371.                     case "IgnorePI":         optionsEnum |= XmlDiffOptions.IgnorePI;          break;
  1372.                     case "IgnorePrefixes":   optionsEnum |= XmlDiffOptions.IgnorePrefixes;    break;
  1373.                     case "IgnoreWhitespace": optionsEnum |= XmlDiffOptions.IgnoreWhitespace;  break;
  1374.                     case "IgnoreXmlDecl":    optionsEnum |= XmlDiffOptions.IgnoreXmlDecl;     break;
  1375.                     case "IgnoreDtd":        optionsEnum |= XmlDiffOptions.IgnoreDtd;         break;
  1376.                     default:
  1377.                         throw new ArgumentException("options" );
  1378.                 }
  1379.                 i = j + 1;
  1380.             }    
  1381.             return optionsEnum;
  1382.         }
  1383.     }
  1384. internal string GetXmlDiffOptionsString()
  1385. {
  1386.         string options = string.Empty;
  1387. if ( _bIgnoreChildOrder )    options += XmlDiffOptions.IgnoreChildOrder.ToString() + " ";
  1388. if ( _bIgnoreComments )      options += XmlDiffOptions.IgnoreComments.ToString() + " ";
  1389. if ( _bIgnoreNamespaces )    options += XmlDiffOptions.IgnoreNamespaces.ToString() + " ";
  1390. if ( _bIgnorePI )            options += XmlDiffOptions.IgnorePI.ToString() + " ";
  1391. if ( _bIgnorePrefixes  )     options += XmlDiffOptions.IgnorePrefixes.ToString() + " ";
  1392. if ( _bIgnoreWhitespace )    options += XmlDiffOptions.IgnoreWhitespace.ToString() + " ";
  1393. if ( _bIgnoreXmlDecl )       options += XmlDiffOptions.IgnoreXmlDecl.ToString() + " ";
  1394.         if ( _bIgnoreDtd     )       options += XmlDiffOptions.IgnoreDtd.ToString() + " ";
  1395.         if ( options == string.Empty )      options = XmlDiffOptions.None.ToString();
  1396. options.Trim();
  1397. return options;
  1398. }
  1399.     /// <include file='docXmlDiff.uex' path='docs/doc[@for="XmlDiff.VerifySource"]/*' />
  1400.     /// <summary>
  1401.     ///    Given a diffgram, this method verifies whether the given document/node is the original
  1402.     ///    source document/node for the diffgram. 
  1403.     /// </summary>
  1404.     /// <param name="node">Document/node to be verified.</param>
  1405.     /// <param name="hashValue">Value of the 'srcDocHash' attribute of the 'xd:xmldiff' element in diffgram.
  1406.     /// This is the hash value of the original source document. The document/node is verified if it yields
  1407.     /// the same hash value.</param>
  1408.     /// <param name="options">XmlDiff options selected when the document/node was compared. The hash value 
  1409.     /// of the document/node depends on these options.</param>
  1410.     /// <returns>True if the given document is the original source document for the diffgram.</returns>
  1411.     static public bool VerifySource( XmlNode node, ulong hashValue, XmlDiffOptions options )
  1412.     {
  1413.         if ( node == null )
  1414.             throw new ArgumentNullException( "node" );
  1415.         ulong computedHashValue = new XmlHash().ComputeHash( node, options );
  1416.         return hashValue == computedHashValue;
  1417.     }
  1418.     internal static string NormalizeText( string text )
  1419.     {
  1420.         char[] chars = text.ToCharArray();
  1421.         int i = 0;
  1422.         int j = 0;
  1423.         for (;;)
  1424.         {
  1425.             while ( j < chars.Length  &&  IsWhitespace( text[j] ) )
  1426.                 j++;
  1427.             while ( j < chars.Length && !IsWhitespace( text[j] ) )
  1428.                 chars[i++]=chars[j++];
  1429.             if ( j < chars.Length )
  1430.             {
  1431.                 chars[i++]=' ';
  1432.                 j++;
  1433.             }
  1434.             else
  1435.             {
  1436.                 if ( j == 0 )
  1437.                     return string.Empty;
  1438.                 if ( IsWhitespace( chars[j-1] ) && i > 0 )
  1439.                     i--;
  1440.                 return new string( chars, 0, i );
  1441.             }
  1442.         }
  1443.     }
  1444.     internal static string NormalizeXmlDeclaration( string value )
  1445.     {
  1446.         value = value.Replace( ''', '"' );
  1447.         return NormalizeText( value );
  1448.     }
  1449.     internal static bool IsWhitespace( char c )
  1450.     {
  1451.         return ( c == ' ' ||
  1452.                  c == 't' ||
  1453.                  c == 'n' ||
  1454.                  c == 'r' );
  1455.     }
  1456.     private Diffgram WalkTreeAlgorithm() {
  1457. #if DEBUG
  1458.         Trace.WriteLineIf( T_Phases.Enabled, "* Using WalkTree Algorithm" );
  1459. #endif
  1460.         return ( new DiffgramGenerator( this ) ).GenerateFromWalkTree();
  1461.     }
  1462. #if DEBUG
  1463.     private static void EnableTraceSwitches()
  1464.     {
  1465.         T_Phases.Enabled            = false;
  1466.         T_LoadedDoc.Enabled         = false;
  1467.         T_ForestDistance.Enabled    = false;
  1468.         T_EditScript.Enabled        = false;
  1469.         T_SubtreeMatching.Enabled   = false;
  1470.         T_Tree.Enabled              = false;
  1471.     }
  1472. #endif
  1473. }
  1474. }