2009-03-15 117 views
13

Tengo dos archivos XML de estructura similar que deseo fusionar en un archivo. Actualmente estoy usando EL4J XML Merge que encontré en este tutorial. Sin embargo, no se fusiona como esperaba, por ejemplo, el problema principal es que no fusiona los archivos de ambos en un elemento alias uno que contiene 1, 2, 3 y 4. En su lugar descarta 1 o 2 o 3 y 4 dependiendo de qué archivo se fusionó primero.Fusionar dos archivos XML en Java

Así que agradecería a cualquiera que tenga experiencia con XML Merge si me pueden decir lo que podría estar haciendo mal o, alternativamente, alguien sabe de una buena API XML para Java que sería capaz de fusionar los archivos como yo ¿exigir?

Muchas Gracias por su ayuda por adelantado

Editar:

realmente podría hacer con algunas buenas sugerencias sobre cómo hacer esto por lo que añaden una recompensa. Intenté la sugerencia de jdigital pero todavía tengo problemas con la fusión de XML.

A continuación se muestra un ejemplo del tipo de estructura de archivos XML que intento fusionar.

<run xmloutputversion="1.02"> 
    <info type="a" /> 
    <debugging level="0" /> 
    <host starttime="1237144741" endtime="1237144751"> 
     <status state="up" reason="somereason"/> 
     <something avalue="test" test="alpha" /> 
     <target> 
      <system name="computer" /> 
     </target> 
     <results> 
      <result id="1"> 
       <state value="test" /> 
       <service value="gamma" /> 
      </result> 
      <result id="2"> 
       <state value="test4" /> 
       <service value="gamma4" /> 
      </result> 
     </results> 
     <times something="0" /> 
    </host> 
    <runstats> 
     <finished time="1237144751" timestr="Sun Mar 15 19:19:11 2009"/> 
     <result total="0" /> 
    </runstats> 
</run> 

<run xmloutputversion="1.02"> 
    <info type="b" /> 
    <debugging level="0" /> 
    <host starttime="1237144741" endtime="1237144751"> 
     <status state="down" reason="somereason"/> 
     <something avalue="test" test="alpha" /> 
     <target> 
      <system name="computer" /> 
     </target> 
     <results> 
      <result id="3"> 
       <state value="testagain" /> 
       <service value="gamma2" /> 
      </result> 
      <result id="4"> 
       <state value="testagain4" /> 
       <service value="gamma4" /> 
      </result> 
     </results> 
     <times something="0" /> 
    </host> 
    <runstats> 
     <finished time="1237144751" timestr="Sun Mar 15 19:19:11 2009"/> 
     <result total="0" /> 
    </runstats> 
</run> 

Resultados previstos

<run xmloutputversion="1.02"> 
    <info type="a" /> 
    <debugging level="0" /> 
    <host starttime="1237144741" endtime="1237144751"> 
     <status state="down" reason="somereason"/> 
     <status state="up" reason="somereason"/> 
     <something avalue="test" test="alpha" /> 
     <target> 
      <system name="computer" /> 
     </target> 
     <results> 
      <result id="1"> 
       <state value="test" /> 
       <service value="gamma" /> 
      </result> 
      <result id="2"> 
       <state value="test4" /> 
       <service value="gamma4" /> 
      </result> 
      <result id="3"> 
       <state value="testagain" /> 
       <service value="gamma2" /> 
      </result> 
      <result id="4"> 
       <state value="testagain4" /> 
       <service value="gamma4" /> 
      </result> 
     </results> 
     <times something="0" /> 
    </host> 
    <runstats> 
     <finished time="1237144751" timestr="Sun Mar 15 19:19:11 2009"/> 
     <result total="0" /> 
    </runstats> 
</run> 
+0

¿Es posible añadir el resultado deseado? –

+0

Han agregado el resultado esperado, la suma de resultados en el nodo de resultados es la cosa más críptica. –

Respuesta

0

Usted puede ser capaz de escribir una aplicación Java que deserilizes los documentos XML en objetos, a continuación, "Combinar" los objetos individuales mediante programación en una colección. A continuación, puede serializar el objeto de colección de nuevo en un archivo XML con todo "fusionado".

La API JAXB tiene algunas herramientas que pueden convertir un documento/esquema XML en clases Java. La herramienta "xjc" podría hacer esto, aunque no recuerdo si puede crear clases directamente desde el documento XML, o si primero debe generar un esquema. Existen herramientas que pueden generar un esquema a partir de un documento XML.

Espero que esto ayude ... no estoy seguro si esto es lo que estabas buscando.

+0

Gracias por su respuesta, no es realmente lo que tenía en mente, pero se mantendrá como una opción si a nadie se le ocurre otra solución. –

1

Eché un vistazo al enlace al que se hace referencia; es extraño que XMLMerge no funcione como se esperaba. Tu ejemplo parece directo. ¿Leyó la sección titulada Using XPath declarations with XmlMerge? Usando el ejemplo, intente configurar un XPath para los resultados y configúrelo para que se fusione. Si estoy leyendo el documento correctamente, se vería algo como esto:

XPath.resultsNode=results 
action.resultsNode=MERGE 
+0

Lo he intentado pero todavía no está funcionando, desafortunadamente, voy a echar un vistazo para ver si puedo encontrar alguna documentación mejor. –

2

Podría ayudar si usted fuera explícito sobre el resultado de que usted está interesado en lograr. ¿Es esto lo que estás pidiendo?

Doc A:

<root> 
    <a/> 
    <b> 
    <c/> 
    </b> 
</root> 

Doc B:

<root> 
    <d/> 
</root> 

resultado combinado:

<root> 
    <a/> 
    <b> 
    <c/> 
    </b> 
    <d/> 
</root> 

¿Está preocupado por la ampliación de documentos de gran tamaño?

La forma más fácil de implementar esto en Java es utilizar un analizador XML de transmisión (google para 'java StAX'). Si usa el javax.xmlen la biblioteca de flujo, encontrará que XMLEventWriter tiene un método conveniente XMLEventWriter # add (XMLEvent). Todo lo que tiene que hacer es recorrer los elementos de nivel superior en cada documento y agregarlos a su escritor utilizando este método para generar su resultado combinado. La única parte funky es implementar la lógica del lector que solo considera (solo llama 'agregar') en los nodos de nivel superior.

Recientemente implementé este método si necesita sugerencias.

-6

¿Ha considerado simplemente no molestarse en analizar el XML "correctamente" y tratar los archivos como cadenas largas y aburridas, como mapas hash y expresiones regulares ...? Este podría ser uno de esos casos en los que los acrónimos extravagantes con X en ellos simplemente hacen que el trabajo sea más difícil de lo que debería ser.

Obviamente, esto depende un poco de la cantidad de datos que realmente necesita analizar mientras realiza la fusión. Pero por el sonido de las cosas, la respuesta a eso no es mucho.

+0

¿se puede garantizar que la cadena recta regenere el XML correcto?¿Cuántas validaciones y pruebas está dispuesto a poner en esa solución frente al "problema" de usar la herramienta X que se encargará de eso? – Newtopian

+0

Si los archivos de ejemplo proporcionados son representativos y el requisito es el indicado, entonces creo que sí. Si hay alguna parte oculta del problema (archivos en diferentes formatos, mucha validación requerida), entonces la más práctica puede ser analizar "correctamente". –

0

Además de usar Stax (que tiene sentido), probablemente sería más fácil con StaxMate (http://staxmate.codehaus.org/Tutorial). Solo crea 2 SMInputCursors, y un cursor secundario si es necesario. Y luego tipo de fusión típica con 2 cursores. Similar a atravesar documentos DOM de manera recursiva y descendente.

+0

La URL dada (http://staxmate.codehaus.org) parece requerir autenticación. ¿Podría verificar y actualizar el enlace, por favor? – rexford

+0

Correcto, Codehaus cerró lamentablemente. El proyecto se ha movido a https://github.com/FasterXML/StaxMate. Gracias por señalar. – StaxMan

11

No es muy elegante, pero se puede hacer esto con el analizador DOM y XPath:

public class MergeXmlDemo { 

    public static void main(String[] args) throws Exception { 
    // proper error/exception handling omitted for brevity 
    File file1 = new File("merge1.xml"); 
    File file2 = new File("merge2.xml"); 
    Document doc = merge("/run/host/results", file1, file2); 
    print(doc); 
    } 

    private static Document merge(String expression, 
     File... files) throws Exception { 
    XPathFactory xPathFactory = XPathFactory.newInstance(); 
    XPath xpath = xPathFactory.newXPath(); 
    XPathExpression compiledExpression = xpath 
     .compile(expression); 
    return merge(compiledExpression, files); 
    } 

    private static Document merge(XPathExpression expression, 
     File... files) throws Exception { 
    DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory 
     .newInstance(); 
    docBuilderFactory 
     .setIgnoringElementContentWhitespace(true); 
    DocumentBuilder docBuilder = docBuilderFactory 
     .newDocumentBuilder(); 
    Document base = docBuilder.parse(files[0]); 

    Node results = (Node) expression.evaluate(base, 
     XPathConstants.NODE); 
    if (results == null) { 
     throw new IOException(files[0] 
      + ": expression does not evaluate to node"); 
    } 

    for (int i = 1; i < files.length; i++) { 
     Document merge = docBuilder.parse(files[i]); 
     Node nextResults = (Node) expression.evaluate(merge, 
      XPathConstants.NODE); 
     while (nextResults.hasChildNodes()) { 
     Node kid = nextResults.getFirstChild(); 
     nextResults.removeChild(kid); 
     kid = base.importNode(kid, true); 
     results.appendChild(kid); 
     } 
    } 

    return base; 
    } 

    private static void print(Document doc) throws Exception { 
    TransformerFactory transformerFactory = TransformerFactory 
     .newInstance(); 
    Transformer transformer = transformerFactory 
     .newTransformer(); 
    DOMSource source = new DOMSource(doc); 
    Result result = new StreamResult(System.out); 
    transformer.transform(source, result); 
    } 

} 

Esto supone que puede mantener al menos dos de los documentos en la memoria RAM de forma simultánea.

+0

Esto parece prometedor aunque sería mejor ser más dinámico. Tiene buenos recursos para leer más sobre el analizador DOM y XPath. –

+0

Hay un buen tutorial sobre devWorks: http://www.ibm.com/developerworks/library/x-javaxpathapi.html – McDowell

+1

+1: ¡Eres mi héroe! :) – carlspring

0

Entonces, ¿solo le interesa fusionar los elementos de 'resultados'? ¿Todo lo demás es ignorado? El hecho de que input0 tiene < tipo de información = "a" /> y input1 tiene < tipo de información = "b" /> y el resultado esperado tiene < tipo de información = "a" /> parece sugerir esto.

Si no está preocupado por la escala y desea resolver este problema rápidamente, le sugiero que escriba un código de problema específico que use una biblioteca simple como JDOM para considerar las entradas y escribir el resultado de la salida.

Intentar escribir una herramienta genérica que fuera lo suficientemente "inteligente" como para manejar todos los casos de fusión posibles consumiría mucho tiempo; habría que exponer una capacidad de configuración para definir las reglas de fusión. Si sabe exactamente cómo se verán sus datos y sabe exactamente cómo debe ejecutarse la combinación, me imagino que su algoritmo recorrerá cada entrada XML y escribirá en un solo resultado XML.

+0

Es un poco difícil aclarar el uso de dos archivos XML que podría necesitar para publicar algunos ejemplos. Es importante que algunos grupos como nodos y destino fusionen o agreguen nuevos elementos apropiadamente. Pero otras cosas como estadísticas de ejecución se pueden dejar como un solo grupo. –

0

Puede probar Dom4J que proporciona un medio muy bueno para extraer información utilizando consultas XPath y también le permite escribir XML muy fácilmente. Solo necesita jugar con la API por un tiempo para hacer su trabajo

3

Gracias a todos por sus sugerencias desafortunadamente ninguno de los métodos sugeridos resultó ser adecuado al final, ya que necesitaba tener reglas para el camino en el cual los diferentes nodos de la estructura estaban enredados.

Lo que hice fue tomar la DTD relacionada con los archivos XML que estaba fusionando y crear varias clases que reflejaran la estructura. De esto utilicé XStream para volver a serializar el archivo XML en clases.

De esta manera anoté mis clases, lo que hace que sea un proceso de utilizar una combinación de las reglas asignadas con anotaciones y algunas reflexiones para fusionar los Objetos en lugar de fusionar la estructura XML real.

Si alguien está interesado en el código que en este caso combina los archivos XML de Nmap, vea http://fluxnetworks.co.uk/NmapXMLMerge.tar.gz los códigos no son perfectos y admitiré que no es masivamente flexible pero definitivamente funciona. Estoy planeando volver a implementar el sistema analizando el DTD automáticamente cuando tenga algo de tiempo libre.

6

Uso XSLT para combinar archivos XML. Me permite ajustar la operación de fusión para unir el contenido o fusionarlo en un nivel específico. Es un poco más de trabajo (y la sintaxis XSLT es algo especial) pero súper flexible. Un par de cosas que necesita Estos

a) Incluir un archivo adicional b) Copia el archivo original 1: 1 c) El diseño de su punto de fusión con o sin evitar la duplicación

a) En el principio he

<xsl:param name="mDocName">yoursecondfile.xml</xsl:param> 
<xsl:variable name="mDoc" select="document($mDocName)" /> 

esto permite apuntar al segundo archivo utilizando $ mDoc

b) las instrucciones para copiar un árbol de fuentes de 1: 1 son 2 plantillas:

<!-- Copy everything including attributes as default action --> 
<xsl:template match="*"> 
    <xsl:element name="{name()}"> 
     <xsl:apply-templates select="@*" /> 
     <xsl:apply-templates /> 
    </xsl:element> 
</xsl:template> 

<xsl:template match="@*"> 
    <xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute> 
</xsl:template> 

Con nada más, obtiene una copia 1: 1 de su primer archivo fuente. Funciona con cualquier tipo de XML. La parte de fusión es específica del archivo. Supongamos que tiene elementos de evento con un atributo de ID de evento. No quieres ID duplicados. La plantilla se vería así:

<xsl:template match="events"> 
    <xsl:variable name="allEvents" select="descendant::*" /> 
    <events> 
     <!-- copies all events from the first file --> 
     <xsl:apply-templates /> 
     <!-- Merge the new events in. You need to adjust the select clause --> 
     <xsl:for-each select="$mDoc/logbook/server/events/event"> 
      <xsl:variable name="curID" select="@id" /> 
      <xsl:if test="not ($allEvents[@id=$curID]/@id = $curID)"> 
       <xsl:element name="event"> 
        <xsl:apply-templates select="@*" /> 
        <xsl:apply-templates /> 
       </xsl:element> 
      </xsl:if> 
     </xsl:for-each> 
    </properties> 
</xsl:template> 

Por supuesto se puede comparar otras cosas como los nombres de etiquetas, etc. También es hasta que sucede la profundidad de la fusión. Si no tiene una clave para comparar, la construcción se vuelve más fácil, p. para acceder:

<xsl:template match="logs"> 
    <xsl:element name="logs"> 
      <xsl:apply-templates select="@*" /> 
      <xsl:apply-templates /> 
      <xsl:apply-templates select="$mDoc/logbook/server/logs/log" /> 
    </xsl:element> 

Para ejecutar XSLT en Java utilizar este:

Source xmlSource = new StreamSource(xmlFile); 
    Source xsltSource = new StreamSource(xsltFile); 
    Result xmlResult = new StreamResult(resultFile); 
    TransformerFactory transFact = TransformerFactory.newInstance(); 
    Transformer trans = transFact.newTransformer(xsltSource); 
    // Load Parameters if we have any 
    if (ParameterMap != null) { 
     for (Entry<String, String> curParam : ParameterMap.entrySet()) { 
      trans.setParameter(curParam.getKey(), curParam.getValue()); 
     } 
    } 
    trans.transform(xmlSource, xmlResult); 

o descargar la Saxon SAX Parser y hacerlo desde la línea de comandos (ejemplo shell de Linux):

#!/bin/bash 
notify-send -t 500 -u low -i gtk-dialog-info "Transforming $1 with $2 into $3 ..." 
# That's actually the only relevant line below 
java -cp saxon9he.jar net.sf.saxon.Transform -t -s:$1 -xsl:$2 -o:$3 
notify-send -t 1000 -u low -i gtk-dialog-info "Extraction into $3 done!" 

YMMV

+0

¿Cómo implementar esto en el código? No sé mucho sobre XSLT, pero no veo cómo se podría ejecutar este XSLT. – cjbarth

+2

Fuente xmlSource = new StreamSource (xmlFile); Fuente xsltSource = new StreamSource (xsltFile); Resultado xmlResult = new StreamResult (resultFile); TransformerFactory transFact = TransformerFactory.newInstance(); Transformador trans = transFact.newTransformer (xsltSource); Parámetros // Cargar si tenemos alguna si (ParameterMap! = Null) { para (Entrada curParam: ParameterMap.entrySet()) { \t trans.setParameter (curParam.getKey(), curParam .getValue()); \t} } trans.transform (xmlSource, xmlResult); – stwissel

+0

+1 XSLT es definitivamente el camino a seguir para la operación de combinación XML – yegor256

2

Así es como debería verse con XML Merge:

action.default=MERGE 

xpath.info=/run/info 
action.info=PRESERVE 

xpath.result=/run/host/results/result 
action.result=MERGE 
matcher.result=ID 

Debe establecer la coincidencia de ID para // nodo resultado y establecer la acción PRESERVAR para // nodo de información. También tenga en cuenta que los usos de .properties XML Merge distinguen entre mayúsculas y minúsculas: debe usar "xpath" y no "XPath" en sus .properties.

No se olvide de definir parámetros -config así:

java -cp lib\xmlmerge-full.jar; ch.elca.el4j.services.xmlmerge.tool.XmlMergeTool -config xmlmerge.properties example1.xml example2.xml