2011-01-04 15 views
17

Próbuję napisać prosty algorytm do odczytu dwóch plików XML z dokładnie tymi samymi węzłami i strukturą, ale niekoniecznie te same dane w węzłach potomnych, a nie w tej samej kolejności . Jak mogę utworzyć prostą implementację do tworzenia trzeciego, tymczasowego XML będącego różnicą między dwoma pierwszymi, używając XML Diff .DLL XML?Porównywanie dwóch plików XML i generowanie trzeciego z XMLDiff w C#

XML Diff na MSDN:

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

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

przykładowy kod XML z dwóch różnych plików XML do porównania:

<?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> 

Wynik chciał (różnica między nimi)

<?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> 

W tym przypadku prawdopodobnie użyłbym XSLT do przekonwertowania wynikowego pliku XML "różnicowego" do posortowanego pliku HTML, ale jeszcze go nie ma. Wszystko, co chcę zrobić, to wyświetlić w trzecim pliku XML różnicę każdej wartości liczbowej każdego węzła, zaczynając od węzła potomnego "GP".

C# kod mam tak daleko:

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); 
} 

To, co mam tak daleko, ale wyniki są śmieci ... pamiętać, że dla każdego węzła „Gracz”, pierwsze trzy Childs mieć NIE do porównania ... Jak mogę to wdrożyć?

+0

Dobre pytanie, +1. Zobacz moją odpowiedź na dwa rozwiązania: jeden z pomocniczą transformacją XSLT w celu utworzenia dwóch nowych dokumentów XML mających tylko elementy, które powinny być porównywane, innym rozwiązaniem jest całkowicie XSLT. :) –

Odpowiedz

3

Okay .. W końcu zdecydowałem się na czyste rozwiązanie C# do porównywania dwóch plików XML, bez używania XML Diff/Patch .dll i bez potrzeby używania transformacji XSL. Będę potrzebował transformacji XSL w następnym kroku, aby przekonwertować Xml na HTML dla celów oglądania, ale wymyśliłem algorytm, który używa niczego oprócz System.Xml i System.Xml.XPath.

Oto mój algorytm:

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(); 
    } 
} 

XML Wyniki:

<?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> 

I oszczędzić pokazać implementację metody CalculateDifferential(), to raczej tajemniczy, ale jest szybki i wydajny . W ten sposób mogłem uzyskać pożądane wyniki bez użycia żadnego innego odniesienia, ale ścisłe minimum, bez konieczności używania XSL ...

+0

możesz również pokazać metodę CalculateDifferential()? co to robi? – Niloofar

11

Istnieją dwa rozwiązania: natychmiastowe

Rozwiązanie 1.

Najpierw można zastosować prostą transformację do dwóch dokumentów, które usuwają elementy, które nie powinny być porównywane. Następnie porównaj wyniki dwóch dokumentów - dokładnie z bieżącym kodem. Oto transformacja:

<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> 

Kiedy transformacja ta jest stosowana do dostarczonego dokumentu 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> 

WANTED dokument wynikający produkowanego:

<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> 

Rozwiązanie 2.

To jest kompletny XSLT 1.0 rozwiązanie (tylko dla wygody, drugi dokument XML jest osadzony w kodzie transformacji):

<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> 

kiedy ta transformacja jest stosowane na tym samym pierwszym dokumencie, jak wyżej, prawidłowa diffgrams są produkowane:

<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> 

Jak to działa:

  1. Transformacja jest stosowana w pierwszym dokumencie, przekazując drugi dokument jako parametr.

  2. To daje dokumentu XML, którego jedynym elementem liść węzły są te, które mają inną wartość niż odpowiadająca węzłów elementów liść w drugim dokumencie.

  3. To samo przetwarzanie odbywa się jak w punkcie 1. powyżej, ale tym razem na drugim dokumencie, mijając pierwszy dokument jako parametr.

  4. To daje drugą DiffGram: dokument XML, którego jedynym elementem liść węzły są te, które mają inną wartość niż ** odpowiednich węzłów elementów liść w pierwszym dokumencie

+2

+1. Naprawdę lubię drugie rozwiązanie. – Flack

+0

Doskonałe rozwiązanie ... Jak przejść, przekazując drugi dokument jako parametr bez osadzania go w kodzie transformacji xsl? –

+0

P.S .: Zmodyfikowałem mój początkowy wpis, bardziej szczegółowo przyglądając się temu, jaki plik Xml produktu musi być w funkcji dwóch pierwszych. Nigdy nie eksperymentowałem z Xsl, ale udało mi się zastosować pierwszą transformację do dwóch dokumentów XML. Nadal mogę śledzić, który gracz manipuluję z powodu atrybutu "Ranga" w węźle "Gracz". Teraz nie mogę wymyślić jak zaimplementować rozwiązanie 2. używając XSL i C# ... –

Powiązane problemy