Fusionar dos archivos XML en Java

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

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

Muchas gracias por su ayuda de antemano

Editar:

Realmente podría hacer algunas buenas sugerencias para hacer esto, así que agregó una recompensa. Intenté la sugerencia de jdigital pero todavía tengo problemas con la fusión de XML.

A continuación hay una muestra del tipo de estructura de archivos XML que bash fusionar.

                                                    

Rendimiento esperado

                                    

No es muy elegante, pero puedes 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 contener al menos dos de los documentos en RAM simultáneamente.

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 syntax XSLT es algo especial) pero súper flexible. Algunas cosas que necesitas aquí

a) Incluya un archivo adicional b) Copie el archivo original 1: 1 c) Diseñe su punto de fusión con o sin evitación de duplicaciones

a) Al principio tengo

 yoursecondfile.xml  

esto permite apuntar al segundo archivo usando $ mDoc

b) Las instrucciones para copiar un árbol de origen 1: 1 son 2 plantillas:

           

Con nada más, obtienes una copia 1: 1 de tu 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í:

                   

Por supuesto, puede comparar otras cosas como nombres de tags, etc. También depende de usted la profundidad de la fusión. Si no tiene una clave para comparar, la construcción se vuelve más fácil, por ejemplo, para el registro:

        

Para ejecutar XSLT en Java usa esto:

  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 curParam : ParameterMap.entrySet()) { trans.setParameter(curParam.getKey(), curParam.getValue()); } } trans.transform(xmlSource, xmlResult); 

o descarga el Saxon SAX Parser y hágalo desde la línea de comandos (ejemplo de 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

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

Entonces, lo que hice fue tomar la DTD relacionada con los archivos XML que estaba fusionando y crear varias clases que reflejaran la estructura. A partir 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 usar 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, consulte http://fluxnetworks.co.uk/NmapXMLMerge.tar.gz los códigos no son perfectos y admitiré que no son muy flexibles, pero definitivamente funcionan. Estoy planeando volver a implementar el sistema analizando el DTD automáticamente cuando tenga algo de tiempo libre.

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 de resultados 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 te olvides de definir el parámetro -config así:

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

Podría ser útil si fue explícito sobre el resultado que le interesa alcanzar. ¿Es esto lo que estás pidiendo?

Doc A:

       

Doc B:

    

Resultado combinado:

        

¿Le preocupa escalar para documentos grandes?

La forma más fácil de implementar esto en Java es usar un analizador XML de transmisión (google para ‘java StAX’). Si usa la biblioteca javax.xml.stream, 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.

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 Uso de declaraciones XPath con 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 

Es posible que pueda escribir una aplicación Java que deserilice los documentos XML en objetos y luego “fusionar” los objetos individuales mediante progtwigció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 de si esto es lo que estabas buscando.

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.

Entonces, ¿solo te interesa fusionar los elementos de ‘resultados’? ¿Todo lo demás es ignorado? El hecho de que input0 tiene un y input1 tiene un y el resultado esperado tiene un que parece sugerir esto.

Si no le preocupa el escalado y desea resolver este problema rápidamente, le sugiero que escriba un código de código específico del problema 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: tendrí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.

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 necesitas jugar con la API por un tiempo para hacer tu trabajo

A veces solo necesitas concatenar archivos XML en uno, por ejemplo, con una estructura similar, como esta:

Archivo xml1 :

   ...    ...   

Archivo xml2 :

   ...    ...   

En este caso, el siguiente procedimiento que usa la biblioteca jdom2 puede ayudarlo a:

 void concatXML(Path fSource,Path fDest) { Document jdomSource = null; Document jdomDest = null; List elems = new LinkedList(); SAXBuilder jdomBuilder = new SAXBuilder(); try { jdomSource = jdomBuilder.build(fSource.toFile()); jdomDest = jdomBuilder.build(fDest.toFile()); Element root = jdomDest.getRootElement(); root.detach(); String sourceNextElementName=((Element) jdomSource.getRootElement().getContent().get(1)).getName(); for (Element record:jdomSource.getRootElement().getDescendants(new ElementFilter(sourceNextElementName))) elems.add(record); for (Element elem : elems) (elem).detach(); root.addContent(elems); Document newDoc = new Document(root); XMLOutputter xmlOutput = new XMLOutputter(); xmlOutput.output(newDoc, System.out); xmlOutput.setFormat(Format.getPrettyFormat()); xmlOutput.output(newDoc, Files.newBufferedWriter(fDest, Charset.forName("UTF-8"))); } catch (Exception e) { e.printStackTrace(); } } 

¿Has considerado simplemente no molestarte en analizar el XML “correctamente” y simplemente tratar los archivos como cadenas largas y usar elementos aburridos 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.