web-dev-qa-db-de.com

Vergleichen von zwei XML-Dateien und Generieren einer dritten mit XMLDiff in C #

Ich versuche, einen einfachen Algorithmus zu schreiben, um zwei XML-Dateien mit exakt denselben Knoten und Strukturen zu lesen, die jedoch nicht notwendigerweise dieselben Daten in den untergeordneten Knoten und nicht in derselben Reihenfolge haben. Wie kann ich eine einfache Implementierung zum Erstellen eines dritten temporären XML erstellen, bei dem es sich um den Unterschied zwischen den beiden ersten handelt, wobei Microsoft XML Diff .DLL verwendet wird?

XML Diff auf MSDN:

XML-Vergleichs- und Patch-Tool

XML-Vergleichs- und Patch-GUI-Tool

beispiel-XML-Code der zwei verschiedenen zu vergleichenden XML-Dateien:

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

Ergebnis gesucht (Unterschied zwischen den beiden)

<?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 diesem Fall würde ich wahrscheinlich XSLT verwenden, um die resultierende XML- "Differential" -Datei in eine sortierte HTML-Datei zu konvertieren, aber ich bin noch nicht dort. Alles, was ich tun möchte, ist, in der dritten XML-Datei die Differenz jedes numerischen Werts jedes Knotens anzuzeigen, beginnend mit dem untergeordneten Knoten "GP".

C # -Code habe ich bisher:

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

Das habe ich bis jetzt, aber das Ergebnis ist Müll ... Beachten Sie, dass für jeden "Player" -Knoten die ersten drei KinderNICHTzu vergleichen sind ... Wie kann ich das implementieren?

18
JF Beaulieu

Okay ... Ich entschied mich schließlich für eine reine C # -Lösung, um die beiden XML-Dateien zu vergleichen, ohne die XML-Diff/Patch-DLL und keine XSL-Transformationen zu verwenden. Ich werde zwar im nächsten Schritt XSL-Transformationen benötigen, um die XML-Datei für die Anzeige in HTML umzuwandeln, aber ich habe einen Algorithmus gefunden, der nur System.Xml und System.Xml.XPath verwendet.

Hier ist mein Algorithmus:

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

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

Ich habe die Implementierung für die CalculateDifferential () -Methode nicht gezeigt, sie ist ziemlich kryptisch, aber sie ist schnell und effizient. Auf diese Weise konnte ich die gewünschten Ergebnisse erzielen, ohne eine andere Referenz als das strikte Minimum zu verwenden, ohne XSL verwenden zu müssen.

3
JF Beaulieu

Es gibt zwei unmittelbare Lösungen:

Lösung 1 .

Sie können zunächst eine einfache Transformation auf die beiden Dokumente anwenden, um die Elemente zu löschen, die nicht miteinander verglichen werden sollen. Vergleichen Sie dann die Ergebnisse in zwei Dokumenten - genau mit Ihrem aktuellen Code. Hier ist die Transformation:

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

Wenn diese Umwandlung auf das bereitgestellte XML-Dokument angewendet wird :

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

Das gewünschte Ergebnisdokument wird erstellt :

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

Lösung 2.

Dies ist eine vollständige XSLT 1.0-Lösung (nur der Einfachheit halber ist das zweite XML-Dokument in den Transformationscode eingebettet):

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

Wenn diese Transformation auf dasselbe erste Dokument wie oben angewendet wird, werden die korrekten Diffgramme erzeugt :

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

So funktioniert das :

  1. Die Umwandlung wird auf das erste Dokument angewendet , wobei das zweite Dokument als Parameter übergeben wird.

  2. Dies erzeugt ein XML-Dokument, dessen einzige Blattelementknoten diejenigen sind, die einen anderen Wert als die entsprechenden Blattelementknoten im zweiten Dokument haben.

  3. Die gleiche Verarbeitung wie in 1. wird ausgeführt, diesmal jedoch im zweiten Dokument , wobei das erste Dokument als Parameter übergeben wird.

  4. Dies erzeugt ein zweites Diffgram : ein XML-Dokument, dessen einzige Blattelementknoten diejenigen sind, die einen anderen Wert ** als die entsprechenden Blattelementknoten im ersten Dokument haben

11

Verwenden von XSLT Ich habe eine Microsoft-kompatible XSLT 1.0-Lösung geschrieben, die einen Baumvergleichsalgorithmus verwendet, um Unterschiede in zwei beliebigen XML-Dateien festzustellen. Ich habe das Blatt in meine Bibliothek geschrieben. Es gibt Knoten mit Unterschieden aus. Wenn jedoch keine Übereinstimmung gefunden wird, werden die gleichgeordneten Knoten gesucht. Die Variable am oberen Rand des Arbeitsblatts legt die Eingabemaske fest, mit der verglichen werden soll.

Es ist effizient mit nur wenigen Einschränkungen.

https://github.com/sflynn1812/xslt-diff

0
Stephen Flynn