2009-09-24 6 views
8

Je compare deux xml et je dois imprimer la différence. Comment puis-je y parvenir en utilisant LINQ. Je sais que je peux utiliser XML diff patch par Microsoft mais je préfère utiliser LINQ. Si vous avez une autre idée que je vais mettre en œuvre cetteComparer deux xml et imprimer la différence en utilisant LINQ

// Première Xml

<Books> 
<book> 
    <id="20504" image="C01" name="C# in Depth"> 
</book> 
<book> 
    <id="20505" image="C02" name="ASP.NET"> 
</book> 
<book> 
    <id="20506" image="C03" name="LINQ in Action "> 
</book> 
<book> 
    <id="20507" image="C04" name="Architecting Applications"> 
</book> 
</Books> 

// Deuxième Xml

<Books> 
    <book> 
    <id="20504" image="C011" name="C# in Depth"> 
    </book> 
    <book> 
    <id="20505" image="C02" name="ASP.NET 2.0"> 
    </book> 
    <book> 
    <id="20506" image="C03" name="LINQ in Action "> 
    </book> 
    <book> 
    <id="20508" image="C04" name="Architecting Applications"> 
    </book> 
</Books> 

Je veux comparer deux résultats xml et impression comme ça.

Issued  Issue Type    IssueInFirst IssueInSecond 

1   image is different  C01    C011 
2   name is different  ASP.NET   ASP.NET 2.0 
3   id is different  20507   20508 
+4

Quelle est la complexité du XML? Si c'est * juste * root/record/@ attrib c'est probablement faisable. –

+1

(le xml est invalide, btw) –

+0

Salut Marc c'est un exemple très simple dans actuall xml son petit peu complexe. – NETQuestion

Répondre

1

Voici la solution:

//sanitised xmls: 
string s1 = @"<Books> 
       <book id='20504' image='C01' name='C# in Depth'/> 
       <book id='20505' image='C02' name='ASP.NET'/> 
       <book id='20506' image='C03' name='LINQ in Action '/> 
       <book id='20507' image='C04' name='Architecting Applications'/> 
       </Books>"; 
string s2 = @"<Books> 
        <book id='20504' image='C011' name='C# in Depth'/> 
        <book id='20505' image='C02' name='ASP.NET 2.0'/> 
        <book id='20506' image='C03' name='LINQ in Action '/> 
        <book id='20508' image='C04' name='Architecting Applications'/> 
       </Books>"; 

XDocument xml1 = XDocument.Parse(s1); 
XDocument xml2 = XDocument.Parse(s2); 

//get cartesian product (i think) 
var result1 = from xmlBooks1 in xml1.Descendants("book") 
       from xmlBooks2 in xml2.Descendants("book") 
       select new { 
          book1 = new { 
             id=xmlBooks1.Attribute("id").Value, 
             image=xmlBooks1.Attribute("image").Value, 
             name=xmlBooks1.Attribute("name").Value 
             }, 
          book2 = new { 
             id=xmlBooks2.Attribute("id").Value, 
             image=xmlBooks2.Attribute("image").Value, 
             name=xmlBooks2.Attribute("name").Value 
             } 
          }; 

//get every record that has at least one attribute the same, but not all 
var result2 = from i in result1 
       where (i.book1.id == i.book2.id 
         || i.book1.image == i.book2.image 
         || i.book1.name == i.book2.name) && 
         !(i.book1.id == i.book2.id 
         && i.book1.image == i.book2.image 
         && i.book1.name == i.book2.name) 
       select i; 



foreach (var aa in result2) 
{ 
    //you do the output :D 
} 

Les deux déclarations LINQ pourraient probablement être fusionnés, mais je laisse cela comme un exercice pour vous.

+0

Je serais surpris si cela fonctionne réellement comme demandé Voulez-vous vraiment une jointure croisée (produit cartésien)? – dahlbyk

+0

Oui, cela fonctionne. La prochaine fois, vous pouvez vérifier vous-même, avant de commenter.Maintenant, laissons "passer en revue" votre solution. –

+0

Il produit le même résultat pour cet exemple, oui. Mais cela ne résout pas le problème général tel que je le comprends. Par exemple, supposons que le livre de xml2 avec id = 20508 soit une faute de frappe et que l'entrée suivante ait les données 'réelles' 20508 dans chaque source. Votre solution renverrait deux lignes; le mien en retournerait un. Les deux réponses correctes en fonction de la question. – dahlbyk

1

L'opération que vous voulez effectuer ici est un Zip pour coupler des éléments correspondants dans vos deux séquences de livres. Cet opérateur est d'être added in .NET 4.0, mais nous pouvons le simuler en utilisant le produit de saisir les indices de livres et de se joindre à ce:

var res = from b1 in xml1.Descendants("book") 
         .Select((b, i) => new { b, i }) 
      join b2 in xml2.Descendants("book") 
         .Select((b, i) => new { b, i }) 
      on b1.i equals b2.i 

Nous allons donc utiliser une seconde jointure pour comparer les valeurs des attributs par nom. Notez que ceci est une jointure interne; Si vous vouliez inclure des attributs manquants dans l'un ou dans l'autre, vous devriez faire un peu plus de travail.

  select new 
      { 
       Row = b1.i, 
       Diff = from a1 in b1.b.Attributes() 
        join a2 in b2.b.Attributes() 
         on a1.Name equals a2.Name 
        where a1.Value != a2.Value 
        select new 
        { 
         Name = a1.Name, 
         Value1 = a1.Value, 
         Value2 = a2.Value 
        } 
      }; 

Le résultat sera une collection imbriquée:

foreach (var b in res) 
{ 
    Console.WriteLine("Row {0}: ", b.Row); 
    foreach (var d in b.Diff) 
     Console.WriteLine(d); 
} 

Ou pour obtenir plusieurs lignes par livre:

var report = from r in res 
      from d in r.Diff 
      select new { r.Row, Diff = d }; 

foreach (var d in report) 
    Console.WriteLine(d); 

qui indique ce qui suit:

{ Row = 0, Diff = { Name = image, Value1 = C01, Value2 = C011 } } 
{ Row = 1, Diff = { Name = name, Value1 = ASP.NET, Value2 = ASP.NET 2.0 } } 
{ Row = 3, Diff = { Name = id, Value1 = 20507, Value2 = 20508 } } 
+0

Eh bien, la chose avec zip est qu'il joint le premier enregistrement de xml1 au premier enregistrement de xml2. Donc, si nous mélangeons un peu xml1 - disons que nous passons premier et deuxième nœuds - nous obtenons un résultat différent. C'est pourquoi vous avez besoin d'une jointure croisée. Il n'y a aucune raison de supposer (à partir de sa question et de ses commentaires) que seuls les nœuds correspondants doivent être comparés. –

+0

La question a été décrite comme diff. Dans un diff, l'ordre compte. – dahlbyk

1

Pour amusant, une solution générale pour gre La lecture de ga g du problème. Pour illustrer mon objection à cette approche, j'ai introduit une entrée «correcte» pour «PowerShell en action».

string s1 = @"<Books> 
    <book id='20504' image='C01' name='C# in Depth'/> 
    <book id='20505' image='C02' name='ASP.NET'/> 
    <book id='20506' image='C03' name='LINQ in Action '/> 
    <book id='20507' image='C04' name='Architecting Applications'/> 
    <book id='20508' image='C05' name='PowerShell in Action'/> 
    </Books>"; 
string s2 = @"<Books> 
    <book id='20504' image='C011' name='C# in Depth'/> 
    <book id='20505' image='C02' name='ASP.NET 2.0'/> 
    <book id='20506' image='C03' name='LINQ in Action '/> 
    <book id='20508' image='C04' name='Architecting Applications'/> 
    <book id='20508' image='C05' name='PowerShell in Action'/> 
    </Books>"; 

XDocument xml1 = XDocument.Parse(s1); 
XDocument xml2 = XDocument.Parse(s2); 

var res = from b1 in xml1.Descendants("book") 
      from b2 in xml2.Descendants("book") 
      let issues = from a1 in b1.Attributes() 
         join a2 in b2.Attributes() 
         on a1.Name equals a2.Name 
         select new 
         { 
          Name = a1.Name, 
          Value1 = a1.Value, 
          Value2 = a2.Value 
         } 
      where issues.Any(i => i.Value1 == i.Value2) 
      from issue in issues 
      where issue.Value1 != issue.Value2 
      select issue; 

qui indique ce qui suit:

{ Name = image, Value1 = C01, Value2 = C011 } 
{ Name = name, Value1 = ASP.NET, Value2 = ASP.NET 2.0 } 
{ Name = id, Value1 = 20507, Value2 = 20508 } 
{ Name = image, Value1 = C05, Value2 = C04 } 
{ Name = name, Value1 = PowerShell in Action, Value2 = Architecting Applications } 

Notez que les deux dernières entrées sont le « conflit » entre la faute de frappe 20508 et l'entrée 20508 par ailleurs correcte.

Questions connexes