2011-05-18 15 views
5

Tengo dos archivos xml que tienen el mismo esquema y me gustaría fusionarlos en un solo archivo xml. ¿Hay una forma fácil de hacer esto?¿Cómo puedo fusionar archivos XML?

Por ejemplo,

<Root> 
    <LeafA> 
     <Item1 /> 
     <Item2 /> 
    </LeafA> 
    <LeafB> 
     <Item1 /> 
     <Item2 /> 
    </LeafB> 
</Root> 

+

<Root> 
    <LeafA> 
     <Item3 /> 
     <Item4 /> 
    </LeafA> 
    <LeafB> 
     <Item3 /> 
     <Item4 /> 
    </LeafB> 
</Root> 

= nuevo archivo que contiene

<Root> 
    <LeafA> 
     <Item1 /> 
     <Item2 /> 
     <Item3 /> 
     <Item4 /> 
    </LeafA> 
    <LeafB> 
     <Item1 /> 
     <Item2 /> 
     <Item3 /> 
     <Item4 /> 
    </LeafB> 
</Root> 
+2

Cortar y pegar dentro de un editor de texto? – BoltClock

+1

@BoltClock Prefiero una secuencia de comandos ya que estos archivos xml se generan automáticamente y cambian con frecuencia. En este momento mi pequeño tiene aproximadamente 2000 líneas de largo y contiene múltiples áreas que necesitan fusionarse. – Rachel

+0

¿Qué tipo de script? Si hay un idioma preferido para escribir este script, es posible que desee agregarlo a las etiquetas. – BoltClock

Respuesta

10

"Combinación automática de XML" suena como un requisito relativamente simple, pero cuando se analizan todos los detalles, se vuelve complejo bastante rápido. Combinar con C# o XSLT será mucho más fácil para tareas más específicas, como en el modelo answer para EF. El uso de herramientas para ayudar con una fusión manual también puede ser una opción (consulte this SO question).

Para la referencia (y para dar una idea de la complejidad) He aquí un ejemplo de código abierto del mundo Java: XML merging made easy

Volver a la pregunta original.Hay pocas áreas grises grandes en la especificación de la tarea: cuando se deben considerar 2 elementos equivalente (tienen el mismo nombre, coinciden los atributos seleccionados o todos, o también tienen la misma posición en el elemento padre); cómo manejar la situación cuando XML original o fusionada tiene múltiples equivalentes elementos etc.

El código siguiente es suponiendo que

  • que sólo se preocupan por los elementos en el momento
  • elementos son equivalentes si los nombres de los elementos, los nombres de los atributos y los valores de los atributos coinciden con
  • un elemento no tiene múltiples atributos con el mismo nombre
  • todos equivalentes elementos del documento combinado se combinarán con el primer elemento equivalente en el documento XML de origen.

.

// determine which elements we consider the same 
// 
private static bool AreEquivalent(XElement a, XElement b) 
{ 
    if(a.Name != b.Name) return false; 
    if(!a.HasAttributes && !b.HasAttributes) return true; 
    if(!a.HasAttributes || !b.HasAttributes) return false; 
    if(a.Attributes().Count() != b.Attributes().Count()) return false; 

    return a.Attributes().All(attA => b.Attributes(attA.Name) 
     .Count(attB => attB.Value == attA.Value) != 0); 
} 

// Merge "merged" document B into "source" A 
// 
private static void MergeElements(XElement parentA, XElement parentB) 
{ 
    // merge per-element content from parentB into parentA 
    // 
    foreach (XElement childB in parentB.DescendantNodes()) 
    { 
     // merge childB with first equivalent childA 
     // equivalent childB1, childB2,.. will be combined 
     // 
     bool isMatchFound = false; 
     foreach (XElement childA in parentA.Descendants()) 
     { 
      if (AreEquivalent(childA, childB)) 
      { 
       MergeElements(childA, childB); 
       isMatchFound = true; 
       break; 
      } 
     } 

     // if there is no equivalent childA, add childB into parentA 
     // 
     if (!isMatchFound) parentA.Add(childB); 
    } 
} 

se va a producir el resultado deseado con los fragmentos XML originales, pero si XMLs de entrada son más complejos y tienen elementos duplicados, el resultado será más interesante ...:

public static void Test() 
{ 
    var a = XDocument.Parse(@" 
    <Root> 
     <LeafA> 
      <Item1 /> 
      <Item2 /> 
      <SubLeaf><X/></SubLeaf> 
     </LeafA> 
     <LeafB> 
      <Item1 /> 
      <Item2 /> 
     </LeafB> 
    </Root>"); 
    var b = XDocument.Parse(@" 
    <Root> 
     <LeafB> 
      <Item5 /> 
      <Item1 /> 
      <Item6 /> 
     </LeafB> 
     <LeafA Name=""X""> 
      <Item3 /> 
     </LeafA> 
     <LeafA> 
      <Item3 /> 
     </LeafA> 
     <LeafA> 
      <SubLeaf><Y/></SubLeaf> 
     </LeafA> 
    </Root>"); 

    MergeElements(a.Root, b.Root); 
    Console.WriteLine("Merged document:\n{0}", a.Root); 
} 

Aquí hay documento combinado mostrando cómo equivalentes elementos de documento B se combinaron entre sí:

<Root> 
    <LeafA> 
    <Item1 /> 
    <Item2 /> 
    <SubLeaf> 
     <X /> 
     <Y /> 
    </SubLeaf> 
    <Item3 /> 
    </LeafA> 
    <LeafB> 
    <Item1 /> 
    <Item2 /> 
    <Item5 /> 
    <Item6 /> 
    </LeafB> 
    <LeafA Name="X"> 
    <Item3 /> 
    </LeafA> 
</Root> 
+1

Me gusta, gracias :) – Rachel

+1

Esto era exactamente lo que necesitaba. Muchas gracias. :) – Yogesh

+0

¡Perfecto! Esto es justo lo que estaba buscando, ¡muchas gracias! – Cranialsurge

1

Si el formato es siempre exactamente como esta no hay nada malo con este método:

Elimine las dos últimas líneas del primer archivo y anexe los segundos mientras elimina las dos primeras líneas.

Eche un vistazo a los comandos de Linux head y tail que pueden eliminar las primeras y las últimas dos líneas.

+0

Hay varias áreas en el archivo xml para fusionar, así que esto no funcionará. Expandiré mi ejemplo para mostrar que – Rachel

0

vimdiff file_a file_b tan sólo un ejemplo

BeyondCompare es un favorito cuando estoy en las ventanas http://www.scootersoftware.com/

+0

Eso solo me muestra las diferencias ... Quiero fusionar realmente los nodos, no hacer que resuelvan las diferencias. – Rachel

1

Es un simple transformación XSLT algo como esto (que se aplica a documentar a.xml):

<xsl:variable name="docB" select="document('b.xml')"/> 
<xsl:template match="Root"> 
    <Root><xsl:apply-templates/></Root> 
</xsl:template> 
<xsl:template match="Root/LeafA"> 
    <xsl:copy-of select="*"/> 
    <xsl:copy-of select="$docB/Root/LeafA/*"/> 
</xsl:template> 
<xsl:template match="Root/LeafB"> 
    <xsl:copy-of select="*"/> 
    <xsl:copy-of select="$docB/Root/LeafB/*"/> 
</xsl:template> 
+0

No entiendo cómo usar xlst ... ¿puede indicarme un buen punto de partida para esto? – Rachel

+0

En realidad, he estado tratando de descubrir xlst para el script que se encuentra aquí: http://www2.informatik.hu-berlin.de/~obecker/XSLT/#merge pero finalmente me rendí y acabo de hacer mi propio script C# . Gracias sin embargo. – Rachel

0

Terminé usando C# y creé una secuencia de comandos. Sabía que podía hacerlo cuando formulé la pregunta, pero quería saber si había una forma más rápida de hacerlo, ya que nunca trabajé con XML.

El guión pasó a lo largo de las líneas de este:

var a = new XmlDocument(); 
a.Load(PathToFile1); 

var b = new XmlDocument(); 
b.Load(PathToFile2); 

MergeNodes(
    a.SelectSingleNode(nodePath), 
    b.SelectSingleNode(nodePath).ChildNodes, 
    a); 

a.Save(PathToFile1); 

Y MergeNodes() parecía algo como esto:

private void MergeNodes(XmlNode parentNodeA, XmlNodeList childNodesB, XmlDocument parentA) 
{ 
    foreach (XmlNode oNode in childNodesB) 
    { 
     // Exclude container node 
     if (oNode.Name == "#comment") continue; 

     bool isFound = false; 
     string name = oNode.Attributes["Name"].Value; 

     foreach (XmlNode child in parentNodeA.ChildNodes) 
     { 
      if (child.Name == "#comment") continue; 

      // If node already exists and is unchanged, exit loop 
      if (child.OuterXml== oNode.OuterXml&& child.InnerXml == oNode.InnerXml) 
      { 
       isFound = true; 
       Console.WriteLine("Found::NoChanges::" + oNode.Name + "::" + name); 
       break; 
      } 

      // If node already exists but has been changed, replace it 
      if (child.Attributes["Name"].Value == name) 
      { 
       isFound = true; 
       Console.WriteLine("Found::Replaced::" + oNode.Name + "::" + name); 
       parentNodeA.ReplaceChild(parentA.ImportNode(oNode, true), child); 
      } 
     } 

     // If node does not exist, add it 
     if (!isFound) 
     { 
      Console.WriteLine("NotFound::Adding::" + oNode.Name + "::" + name); 
      parentNodeA.AppendChild(parentA.ImportNode(oNode, true)); 
     } 
    } 
} 

no es perfecto - Tengo que especificar manualmente los nodos que quiero fusionada, pero fue rápido y fácil para mí armar y dado que casi no tengo conocimiento de XML, estoy feliz :)

Realmente funciona mejor que solo se fusione los nodos especificados, ya que lo estoy usando para fusionar los archivos edmx de Entity Framework, y solo quiero fusionar los nodos SSDL, CDSL y MSL.

+0

@ "especificar manualmente los nodos": esto se espera en cierta medida, independientemente de si es C# o XSLT. De lo contrario, en el ejemplo original, es imposible saber si merge debería producir 2 hojas con 4 nodos cada una o 4 hojas con 2 nodos por hoja. –

+0

Los nodos deben fusionarse si la definición del nodo es idéntica. En el ejemplo anterior, debería producir 2 hojas con 4 nodos cada uno, ya que los dos nodos de hoja son idénticos. – Rachel

+0

Ah, ya veo, gracias. (Y probablemente * idéntico * significa * equivalente *?) En forma genérica, la pregunta original es un ejercicio muy interesante, pero parece que ya tienes una solución para un caso más específico, donde los elementos se identifican por @Nombre. –

0

La forma en que podría hacerlo, es cargar una conjunto de datos con el xml y fusionar los conjuntos de datos.

Dim dsFirst As New DataSet() 
    Dim dsMerge As New DataSet() 

    ' Create new FileStream with which to read the schema. 
    Dim fsReadXmlFirst As New System.IO.FileStream(myXMLfileFirst, System.IO.FileMode.Open) 
    Dim fsReadXmlMerge As New System.IO.FileStream(myXMLfileMerge, System.IO.FileMode.Open) 

    Try 
     dsFirst.ReadXml(fsReadXmlFirst) 

     dsMerge.ReadXml(fsReadXmlMerge) 

     Dim str As String = "Merge Table(0) Row Count = " & dsMerge.Tables(0).Rows.Count 
     str = str & Chr(13) & "Merge Table(1) Row Count = " & dsMerge.Tables(1).Rows.Count 
     str = str & Chr(13) & "Merge Table(2) Row Count = " & dsMerge.Tables(2).Rows.Count 

     MsgBox(str) 

     dsMerge.Merge(dsFirst, True) 

     DataGridParent.DataSource = dsMerge 
     DataGridParent.DataMember = "rulefile" 

     DataGridChild.DataSource = dsMerge 
     DataGridChild.DataMember = "rule" 

     str = "" 
     str = "Merge Table(0) Row Count = " & dsMerge.Tables(0).Rows.Count 
     str = str & Chr(13) & "Merge Table(1) Row Count = " & dsMerge.Tables(1).Rows.Count 
     str = str & Chr(13) & "Merge Table(2) Row Count = " & dsMerge.Tables(2).Rows.Count 

     MsgBox(str) 
Cuestiones relacionadas