2011-01-04 2 views
17

Sto provando a scrivere un semplice algoritmo per leggere due file XML con gli stessi identici nodi e struttura ma non necessariamente gli stessi dati all'interno dei nodi figlio e non nello stesso ordine . Come potrei creare un'implementazione semplice per la creazione di un terzo XML temporaneo come differenziale tra i primi due, utilizzando Microsoft XML Diff .DLL?Confronto tra due file XML e generazione di un terzo con XMLDiff in C#

XML Diff su MSDN:

http://msdn.microsoft.com/en-us/library/aa302294.aspx

http://msdn.microsoft.com/en-us/library/aa302295.aspx

esempio di codice XML dei due file XML diversi per confrontare:

<?xml version="1.0" encoding="utf-8" ?> 
<Stats Date="2011-01-01"> 
<Player Rank="1"> 
    <Name>Sidney Crosby</Name> 
    <Team>PIT</Team> 
    <Pos>C</Pos> 
    <GP>39</GP> 
    <G>32</G> 
    <A>33</A> 
    <PlusMinus>20</PlusMinus> 
    <PIM>29</PIM> 
</Player> 
</Stats> 

<?xml version="1.0" encoding="utf-8" ?> 
<Stats Date="2011-01-10"> 
<Player Rank="1"> 
    <Name>Sidney Crosby</Name> 
    <Team>PIT</Team> 
    <Pos>C</Pos> 
    <GP>42</GP> 
    <G>35</G> 
    <A>34</A> 
    <PlusMinus>22</PlusMinus> 
    <PIM>30</PIM> 
</Player> 
</Stats> 

risultato voluto (differenza tra i due)

<?xml version="1.0" encoding="utf-8" ?> 
<Stats Date="2011-01-10"> 
<Player Rank="1"> 
    <Name>Sidney Crosby</Name> 
    <Team>PIT</Team> 
    <Pos>C</Pos> 
    <GP>3</GP> 
    <G>3</G> 
    <A>1</A> 
    <PlusMinus>2</PlusMinus> 
    <PIM>1</PIM> 
</Player> 
</Stats> 

In questo caso, probabilmente utilizzerei XSLT per convertire il file "differenziale" XML risultante in un file HTML ordinato, ma non ci sono ancora. Tutto quello che voglio fare è visualizzare nel terzo file XML la differenza di ogni valore numerico di ogni nodo, partendo dal nodo figlio "GP".

codice C# che ho finora:

private void CompareXml(string file1, string file2) 
{ 

    XmlReader reader1 = XmlReader.Create(new StringReader(file1)); 
    XmlReader reader2 = XmlReader.Create(new StringReader(file2)); 

    string diffFile = StatsFile.XmlDiffFilename; 
    StringBuilder differenceStringBuilder = new StringBuilder(); 

    FileStream fs = new FileStream(diffFile, FileMode.Create); 
    XmlWriter diffGramWriter = XmlWriter.Create(fs); 

    XmlDiff xmldiff = new XmlDiff(XmlDiffOptions.IgnoreChildOrder | 
          XmlDiffOptions.IgnoreNamespaces | 
          XmlDiffOptions.IgnorePrefixes); 
    bool bIdentical = xmldiff.Compare(file1, file2, false, diffGramWriter); 

    diffGramWriter.Close(); 

    // cleaning up after we are done with the xml diff file 
    File.Delete(diffFile); 
} 

Questo è quello che ho finora, ma i risultati è spazzatura ... notare che per ogni nodo "Player", i primi tre bambini hanno NON da confrontare ... Come posso implementarlo?

+0

Buona domanda, +1. Vedere la mia risposta per due soluzioni: una con una trasformazione XSLT ausiliaria per creare due nuovi documenti XML con solo gli elementi che dovrebbero essere confrontati, l'altra soluzione è completamente XSLT. :) –

risposta

3

okay .. Ho finalmente optato per una soluzione C# pura per confrontare i due file XML, senza utilizzare il file XML Diff/Patch .dll e senza nemmeno dover utilizzare le trasformazioni XSL. Avrò bisogno di trasformazioni XSL nel passaggio successivo, tuttavia, per convertire l'Xml in HTML a scopo di visualizzazione, ma ho trovato un algoritmo che utilizza nient'altro che System.Xml e System.Xml.XPath.

Ecco il mio algoritmo:

private void CompareXml(string file1, string file2) 
{ 
    // Load the documents 
    XmlDocument docXml1 = new XmlDocument(); 
    docXml1.Load(file1); 
    XmlDocument docXml2 = new XmlDocument(); 
    docXml2.Load(file2); 


    // Get a list of all player nodes 
    XmlNodeList nodes1 = docXml1.SelectNodes("/Stats/Player"); 
    XmlNodeList nodes2 = docXml2.SelectNodes("/Stats/Player"); 

    // Define a single node 
    XmlNode node1; 
    XmlNode node2; 

    // Get the root Xml element 
    XmlElement root1 = docXml1.DocumentElement; 
    XmlElement root2 = docXml2.DocumentElement; 

    // Get a list of all player names 
    XmlNodeList nameList1 = root1.GetElementsByTagName("Name"); 
    XmlNodeList nameList2 = root2.GetElementsByTagName("Name"); 

    // Get a list of all teams 
    XmlNodeList teamList1 = root1.GetElementsByTagName("Team"); 
    XmlNodeList teamList2 = root2.GetElementsByTagName("Team"); 

    // Create an XmlWriterSettings object with the correct options. 
    XmlWriter writer = null; 
    XmlWriterSettings settings = new XmlWriterSettings(); 
    settings.Indent = true; 
    settings.IndentChars = (" "); 
    settings.OmitXmlDeclaration = false; 

    // Create the XmlWriter object and write some content. 
    writer = XmlWriter.Create(StatsFile.XmlDiffFilename, settings); 
    writer.WriteStartElement("StatsDiff"); 


    // The compare algorithm 
    bool match = false; 
    int j = 0; 

    try 
    { 
     // the list has 500 players 
     for (int i = 0; i < 500; i++) 
     { 
      while (j < 500 && match == false) 
      { 
       // There is a match if the player name and team are the same in both lists 
       if (nameList1.Item(i).InnerText == nameList2.Item(j).InnerText) 
       { 
        if (teamList1.Item(i).InnerText == teamList2.Item(j).InnerText) 
        { 
         match = true; 
         node1 = nodes1.Item(i); 
         node2 = nodes2.Item(j); 
         // Call to the calculator and Xml writer 
         this.CalculateDifferential(node1, node2, writer); 
         j = 0; 
        } 
       } 
       else 
       { 
        j++; 
       } 
      } 
      match = false; 

     } 
     // end Xml document 
     writer.WriteEndElement(); 
     writer.Flush(); 
    } 
    finally 
    { 
     if (writer != null) 
      writer.Close(); 
    } 
} 

Risultati XML:

<?xml version="1.0" encoding="utf-8"?> 
<StatsDiff>  
    <Player Rank="1"> 
    <Name>Sidney Crosby</Name> 
    <Team>PIT</Team> 
    <Pos>C</Pos> 
    <GP>0</GP> 
    <G>0</G> 
    <A>0</A> 
    <Points>0</Points> 
    <PlusMinus>0</PlusMinus> 
    <PIM>0</PIM> 
    <PP>0</PP> 
    <SH>0</SH> 
    <GW>0</GW> 
    <OT>0</OT> 
    <Shots>0</Shots> 
    <ShotPctg>0</ShotPctg> 
    <ShiftsPerGame>0</ShiftsPerGame> 
    <FOWinPctg>0</FOWinPctg> 
    </Player> 

    <Player Rank="2"> 
    <Name>Steven Stamkos</Name> 
    <Team>TBL</Team> 
    <Pos>C</Pos> 
    <GP>1</GP> 
    <G>0</G> 
    <A>0</A> 
    <Points>0</Points> 
    <PlusMinus>0</PlusMinus> 
    <PIM>2</PIM> 
    <PP>0</PP> 
    <SH>0</SH> 
    <GW>0</GW> 
    <OT>0</OT> 
    <Shots>4</Shots> 
    <ShotPctg>-0,6000004</ShotPctg> 
    <ShiftsPerGame>-0,09999847</ShiftsPerGame> 
    <FOWinPctg>0,09999847</FOWinPctg> 
    </Player> 
[...] 
</StatsDiff> 

ho risparmiato per mostrare l'implementazione per il metodo CalculateDifferential(), è piuttosto criptico, ma è veloce ed efficiente . In questo modo ho potuto ottenere i risultati desiderati senza utilizzare altri riferimenti ma il minimo indispensabile, senza dover utilizzare XSL ...

+0

può mostrare il metodo CalculateDifferential() come bene? Che cosa fa? – Niloofar

11

ci sono due soluzioni immediate:

Soluzione 1.

È possibile applicare prima una semplice trasformazione ai due documenti che elimineranno gli elementi che non devono essere confrontati. Quindi, confronta i risultati con due documenti, esattamente con il codice corrente. Ecco la trasformazione:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 
<xsl:strip-space elements="*"/> 

<xsl:template match="node()|@*"> 
    <xsl:copy> 
    <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="Name|Team|Pos"/> 
</xsl:stylesheet> 

Quando questa trasformazione viene applicata al documento fornito XML:

<Stats Date="2011-01-01"> 
    <Player Rank="1"> 
     <Name>Sidney Crosby</Name> 
     <Team>PIT</Team> 
     <Pos>C</Pos> 
     <GP>39</GP> 
     <G>32</G> 
     <A>33</A> 
     <PlusMinus>20</PlusMinus> 
     <PIM>29</PIM> 
     <PP>10</PP> 
     <SH>1</SH> 
     <GW>3</GW> 
     <Shots>0</Shots> 
     <ShotPctg>154</ShotPctg> 
     <TOIPerGame>20.8</TOIPerGame> 
     <ShiftsPerGame>21:54</ShiftsPerGame> 
     <FOWinPctg>22.6</FOWinPctg> 
    </Player> 
</Stats> 

il documento risultante voluto è prodotto:

<Stats Date="2011-01-01"> 
    <Player Rank="1"> 
     <GP>39</GP> 
     <G>32</G> 
     <A>33</A> 
     <PlusMinus>20</PlusMinus> 
     <PIM>29</PIM> 
     <PP>10</PP> 
     <SH>1</SH> 
     <GW>3</GW> 
     <Shots>0</Shots> 
     <ShotPctg>154</ShotPctg> 
     <TOIPerGame>20.8</TOIPerGame> 
     <ShiftsPerGame>21:54</ShiftsPerGame> 
     <FOWinPctg>22.6</FOWinPctg> 
    </Player> 
</Stats> 

Solution 2.

Questo è un XSLT 1 completo.0 soluzione (per comodità, il secondo documento XML è incorporato nel codice di trasformazione):

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 
<xsl:strip-space elements="*"/> 

<xsl:variable name="vrtfDoc2"> 
    <Stats Date="2011-01-01"> 
    <Player Rank="2"> 
     <Name>John Smith</Name> 
     <Team>NY</Team> 
     <Pos>D</Pos> 
     <GP>38</GP> 
     <G>32</G> 
     <A>33</A> 
     <PlusMinus>15</PlusMinus> 
     <PIM>29</PIM> 
     <PP>10</PP> 
     <SH>1</SH> 
     <GW>4</GW> 
     <Shots>0</Shots> 
     <ShotPctg>158</ShotPctg> 
     <TOIPerGame>20.8</TOIPerGame> 
     <ShiftsPerGame>21:54</ShiftsPerGame> 
     <FOWinPctg>22.6</FOWinPctg> 
    </Player> 
    </Stats> 
</xsl:variable> 

<xsl:variable name="vDoc2" select= 
    "document('')/*/xsl:variable[@name='vrtfDoc2']/*"/> 

<xsl:template match="node()|@*" name="identity"> 
    <xsl:param name="pDoc2"/> 
    <xsl:copy> 
    <xsl:apply-templates select="node()|@*"> 
    <xsl:with-param name="pDoc2" select="$pDoc2"/> 
    </xsl:apply-templates> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="/"> 
    <xsl:apply-templates select="*"> 
    <xsl:with-param name="pDoc2" select="$vDoc2"/> 
    </xsl:apply-templates> 

    ----------------------- 

    <xsl:apply-templates select="$vDoc2"> 
    <xsl:with-param name="pDoc2" select="/*"/> 
    </xsl:apply-templates> 
</xsl:template> 

<xsl:template match="Player/*"> 
    <xsl:param name="pDoc2"/> 
    <xsl:if test= 
    "not(. = $pDoc2/*/*[name()=name(current())])"> 
    <xsl:call-template name="identity"/> 
    </xsl:if> 
</xsl:template> 

<xsl:template match="Name|Team|Pos" priority="20"/> 
</xsl:stylesheet> 

quando questa trasformazione viene applicata sullo stesso primo documento come sopra, DiffGram corretti sono prodotti:

<Stats Date="2011-01-01"> 
    <Player Rank="1"> 
     <GP>39</GP> 
     <PlusMinus>20</PlusMinus> 
     <GW>3</GW> 
     <ShotPctg>154</ShotPctg> 
    </Player> 
</Stats> 

    ----------------------- 

    <Stats xmlns:xsl="http://www.w3.org/1999/XSL/Transform" Date="2011-01-01"> 
    <Player Rank="2"> 
     <GP>38</GP> 
     <PlusMinus>15</PlusMinus> 
     <GW>4</GW> 
     <ShotPctg>158</ShotPctg> 
    </Player> 
</Stats> 

Come funziona:

  1. La trasformazione viene applicata sul primo documento, passando il secondo documento come parametro.

  2. Questo produce un documento XML il cui unico foglio elemento nodi sono quelli che hanno un valore diverso dei corrispondenti nodi elemento foglia nel secondo documento.

  3. La stessa procedura viene eseguita in 1. sopra, ma questa volta sul secondo documento, passando il primo documento come parametro.

  4. Questo produce una seconda diffgram: un documento XML il cui unico foglio elemento nodi sono quelli che hanno un valore diverso ** dei corrispondenti nodi elemento foglia nel primo documento

+2

+1. Piace davvero la seconda soluzione. – Flack

+0

Ottima soluzione ... Come passare il secondo documento come parametro senza incorporarlo nel codice di trasformazione xsl? –

+0

P.S .: Ho modificato il mio post iniziale con uno sguardo più dettagliato su ciò che il file Xml del prodotto deve essere in funzione dei primi due. Non ho mai sperimentato con Xsl, ma sono riuscito ad applicare una prima trasformazione ai due documenti XML. Posso ancora tenere traccia del giocatore che sto manipolando a causa dell'attributo "Classifica" nel nodo "Giocatore". Ora, non riesco a capire come implementare la soluzione 2. usando XSL e C# ... –