Сравнение двух файлов XML и создание третьего с XMLDiff в C #

17

Я пытаюсь написать простой алгоритм для чтения двух файлов XML с теми же узлами и структурой, но не обязательно с теми же данными внутри дочерних узлов, а не с тем же порядком. Как я могу создать простую реализацию для создания третьего временного XML, являющегося дифференциалом между двумя первыми, с использованием XML Diff .DLL Microsoft?

XML Diff на MSDN:

Ссылка

Ссылка

образец XML-кода двух разных XML-файлов для сравнения:

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

Требуемый результат (разница между двумя)

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

В этом случае я, вероятно, использовал бы XSLT, чтобы преобразовать полученный в результате XML «дифференциальный» файл в отсортированный HTML-файл, но я еще не был там. Все, что я хочу сделать, это показать в третьем XML-файле разницу в каждом численном значении каждого узла, начиная с дочернего узла «GP».

Код C #, который у меня есть:

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

Это то, что у меня есть до сих пор, но результаты - мусор ... обратите внимание, что для каждого узла «Player» первые три дочерних элемента имеют NOT для сравнения ... Как я могу реализовать это?

    
задан JF Beaulieu 04.01.2011 в 03:37
источник
  • Хороший вопрос, +1. См. Мой ответ для двух решений: один со вспомогательным преобразованием XSLT для создания двух новых XML-документов, имеющих только элементы, которые нужно сравнить, а другое решение - полностью XSLT. :) –  Dimitre Novatchev 04.01.2011 в 05:02

2 ответа

3

Хорошо ... Я, наконец, решил использовать чистое решение C # для сравнения двух файлов XML, не используя XML Diff / Patch .dll и даже не нуждаясь в использовании XSL-преобразований. Мне понадобится преобразование XSL на следующем шаге, хотя для преобразования Xml в HTML для целей просмотра, но я вычислил алгоритм, использующий ничего, кроме System.Xml и System.Xml.XPath.

Вот мой алгоритм:

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:

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

Я пощадил, чтобы показать реализацию метода CalculateDifferential (), он довольно загадочный, но он быстрый и эффективный. Таким образом, я мог бы получить желаемые результаты без использования каких-либо других ссылок, но строгий минимум, без использования XSL ...

    
ответ дан JFB 06.01.2011 в 05:21
  • вы также можете показать метод CalculateDifferential ()? Что это делает? –  Niloofar 20.12.2017 в 16:48
11

Существует два решения:

Решение 1 .

Вы можете сначала применить простое преобразование к двум документам, которые будут удалять элементы, которые не следует сравнивать. Затем сравните результаты с двумя документами - точно с вашим текущим кодом. Вот трансформация:

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

Когда это преобразование применяется к предоставленному 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>

выдается желаемый результирующий документ :

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

Решение 2.

Это полное решение XSLT 1.0 (только для удобства, второй XML-документ встроен в код преобразования):

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

, когда это преобразование применяется в том же первом документе, что и выше, создаются правильные разногласия :

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

Как это работает :

  1. Преобразование применяется к первому документу , передавая второй документ в качестве параметра.

  2. Это создает документ XML, единственными узлами из листового элемента которого являются разные значения , чем соответствующие узлы листового элемента во втором документе.

  3. Та же обработка выполняется как в 1. выше, но на этот раз во втором документе , передавая первый документ в качестве параметра.

  4. Это создает второй diffgram : XML-документ, единственным узлом которого является листовой элемент, который имеет другое значение **, чем соответствующие узлы листового элемента в первом документе

ответ дан Dimitre Novatchev 04.01.2011 в 05:01
  • +1. Действительно как второе решение. –  Flack 04.01.2011 в 14:23
  • Отличное решение ... Как мне перейти, передав второй документ в качестве параметра без его внедрения в код преобразования xsl? –  JF Beaulieu 04.01.2011 в 18:41
  • P.S .: Я изменил свой первоначальный пост с более подробным рассмотрением того, какой файл продукта Xml должен быть в функции двух первых. Я никогда не экспериментировал с Xsl, но мне удалось применить первое преобразование к двум документам XML. Я все еще могу отслеживать, какой игрок я манипулирую из-за атрибута «Ранг» в узле «Игрок». Теперь я не могу понять, как реализовать решение 2. используя XSL и C # ... –  JF Beaulieu 04.01.2011 в 19:00
  • P.P.S .: Проблема здесь заключается в том, что после выполнения XSL-преобразования двух документов Xml не будет никакого способа определить, какой игрок является игроком, потому что ряды игроков могут измениться. Единственный способ сопоставить двух игроков - это совпадение: 1. их имя и 2. их команда, но эти поля были удалены после преобразования. Единственный способ идентифицировать игроков таким образом - это обратиться к двум предыдущим документам Xml и соответствовать рангу с рангом в преобразованных документах Xml. Phhhheeewww !!! –  JF Beaulieu 04.01.2011 в 19:14
  • @Kaeso: вы можете поместить идентификационную информацию в комментарий - я думаю, что XmlDiff имеет возможность игнорировать узлы комментариев. –  Dimitre Novatchev 04.01.2011 в 20:11
Показать остальные комментарии