2009-03-15 19 views
13

Ho due file XML di struttura simile che desidero unire in un unico file. Attualmente sto usando EL4J XML Merge che ho trovato in questo tutorial. Tuttavia non si fonde come mi aspetto che per le istanze il problema principale è il suo non unire entrambi i file in un unico elemento che contiene 1, 2, 3 e 4. Invece scarta solo 1 e 2 o 3 e 4 a seconda del file che viene prima unito.Unisci due file XML in Java

Quindi sarei grato a chiunque abbia esperienza con XML Merge se potesse dirmi cosa potrei fare in modo errato o in alternativa qualcuno conosca una buona API XML per Java che sarebbe in grado di unire i file come richiedono?

Molte grazie per il vostro aiuto in anticipo

Edit:

potrebbe davvero fare con alcuni buoni suggerimenti su come fare questo in modo da aggiungere una taglia. Ho provato il suggerimento di jdigital ma ho ancora problemi con l'unione XML.

Di seguito è riportato un esempio del tipo di struttura dei file XML che sto cercando di unire.

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

uscita prevista

<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

Potrebbe aggiungere il risultato desiderato? –

+0

Hanno aggiunto l'output previsto l'aggiunta di risultati nel nodo risultati è la cosa più cricuale. –

risposta

0

Potreste essere in grado di scrivere un'applicazione Java che deserilizes i documenti XML in oggetti, poi "Merge" i singoli oggetti di programmazione in una collezione. È quindi possibile serializzare l'oggetto di raccolta in un file XML con tutto "unito".

L'API JAXB ha alcuni strumenti che possono convertire un documento/schema XML in classi java. Lo strumento "xjc" potrebbe essere in grado di farlo, anche se non ricordo se è possibile creare classi direttamente dal documento XML, o se si deve prima generare uno schema. Ci sono strumenti là fuori che possono generare uno schema da un documento XML.

Spero che questo aiuti ... non so se questo è quello che stavi cercando.

+0

Grazie per la tua risposta non è proprio quello che avevo in mente, ma manterrà come opzione se nessuno si presenta con un'altra soluzione. –

1

Ho dato un'occhiata al link di riferimento; è strano che XMLMerge non funzioni come previsto. Il tuo esempio sembra semplice. Hai letto la sezione Using XPath declarations with XmlMerge? Utilizzando l'esempio, prova ad impostare un XPath per i risultati e impostalo per unire. Se sto leggendo il documento in modo corretto, sarebbe simile a questa:

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

Ho provato questo, ma purtroppo non funziona, sfortunatamente, cercherò di vedere se riesco a trovare una documentazione migliore per questo. –

2

potrebbe essere utile se si dovesse esplicito circa il risultato che si è interessato a raggiungere. E 'questo quello che stai chiedendo?

Doc A:

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

Doc B:

<root> 
    <d/> 
</root> 

risultato unito:

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

Sei preoccupato per il ridimensionamento di documenti di grandi dimensioni?

Il modo più semplice per implementare questo in Java consiste nell'utilizzare un parser XML di streaming (google per "java StAX"). Se usi javax.xml.la libreria di flusso scoprirà che XMLEventWriter ha un metodo conveniente XMLEventWriter # add (XMLEvent). Tutto quello che devi fare è scorrere gli elementi di livello superiore in ogni documento e aggiungerli al tuo writer usando questo metodo per generare il tuo risultato unito. L'unica parte funky è l'implementazione della logica del lettore che considera solo (solo chiamate 'aggiungi') sui nodi di livello superiore.

Recentemente ho implementato questo metodo se avete bisogno di suggerimenti.

-6

Avete considerato di non preoccuparvi di analizzare correttamente l'XML e di trattare i file come grandi stringhe lunghe e di usare noiose cose vecchie come le mappe hash e le espressioni regolari ...? Questo potrebbe essere uno di quei casi in cui gli acronimi fantasiosi con X in loro rendono il lavoro più faticoso di quanto dovrebbe essere.

Ovviamente questo dipende un po 'dalla quantità di dati che è effettivamente necessario analizzare durante l'unione. Ma dal suono delle cose, la risposta a questo non è molto.

+0

puoi garantire che la stringa diritta rigenera il codice XML corretto?Quante convalide e test siete disposti a mettere su quella soluzione contro le "troppe" dell'utilizzo dello strumento X che prenderà in carico quel responsabile? – Newtopian

+0

Se i file di esempio forniti sono rappresentativi, e il requisito è come indicato, allora penso, sì, posso. Se c'è una parte nascosta del problema (file in diversi formati, molte convalide richieste), il più pratico potrebbe essere quello di analizzare "correttamente". –

0

Oltre a utilizzare Stax (che ha senso), probabilmente sarebbe più semplice con StaxMate (http://staxmate.codehaus.org/Tutorial). Basta creare 2 SMInputCursors e il cursore figlio se necessario. E poi tipico merge sort con 2 cursori. Simile al passaggio dei documenti DOM in modo ricorsivo-discendente.

+0

L'URL indicato (http://staxmate.codehaus.org) sembra richiedere l'autenticazione. Potresti verificare e aggiornare il link, per favore. – rexford

+0

Esatto, Codehaus è stato chiuso, purtroppo. Il progetto è stato spostato su https://github.com/FasterXML/StaxMate. Grazie per la segnalazione. – StaxMan

11

Non molto elegante, ma si potrebbe fare questo con il parser DOM e 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); 
    } 

} 

Questo presuppone che si può tenere almeno due dei documenti in RAM contemporaneamente.

+0

Questo sembra promettente, anche se sarebbe meglio essere più dinamici. Hai delle buone risorse per leggere di più su parser DOM e XPath. –

+0

C'è un buon tutorial su devWorks: http://www.ibm.com/developerworks/library/x-javaxpathapi.html – McDowell

+1

+1: sei il mio eroe! :) – carlspring

0

Quindi, ti interessa solo unire gli elementi "risultati"? Tutto il resto è ignorato? Il fatto che input0 abbia un tipo di informazione < = "a" /> e input1 ha un tipo di informazione < = "b" /> e il risultato previsto ha un tipo di informazione < = "un" /> sembra suggerire questo.

Se non si è preoccupati di ridimensionare e si desidera risolvere rapidamente questo problema, suggerirei di scrivere un bit di codice specifico del problema che utilizza una libreria semplice come JDOM per considerare gli input e scrivere il risultato di output.

Il tentativo di scrivere uno strumento generico che fosse abbastanza "intelligente" da gestire tutti i possibili casi di unione sarebbe molto dispendioso in termini di tempo: dovresti esporre una funzionalità di configurazione per definire le regole di unione. Se sai esattamente come saranno i tuoi dati e sai esattamente come deve essere eseguita l'unione, immagino che il tuo algoritmo possa gestire ogni input XML e scrivere su un singolo output XML.

+0

E 'un po' difficile da chiarire usando due file XML Potrei aver bisogno di pubblicare alcuni esempi è importante che alcuni gruppi come i nodi e il target si uniscano o aggiungano nuovi elementi in modo appropriato. Ma altre cose come le statistiche di corsa possono essere lasciate come un singolo gruppo. –

0

Si può provare Dom4J che fornisce un ottimo mezzo per estrarre informazioni usando XPath Queries e consente anche di scrivere XML molto facilmente. Hai solo bisogno di giocare con l'API per un po 'di tempo per fare il tuo lavoro

3

Grazie a tutti per i loro suggerimenti, purtroppo nessuno dei metodi suggeriti si è rivelato adatto alla fine, poiché avevo bisogno di avere regole per il modo in cui in cui diversi nodi della struttura in cui è stato creato.

Quindi quello che ho fatto è stato prendere il DTD relativo ai file XML che stavo unendo e da quello creare un numero di classi che riflettono la struttura. Da questo ho usato XStream per re-serializzare il file XML in classi.

In questo modo ho annotato le mie classi rendendolo un processo di utilizzo di una combinazione delle regole assegnate con annotazioni e una riflessione per unire gli Oggetti invece di unire la struttura XML effettiva.

Se qualcuno è interessato al codice che in questo caso unisce i file XML Nmap, vedere http://fluxnetworks.co.uk/NmapXMLMerge.tar.gz i codici non sono perfetti e non ammetto di essere flessibile ma sicuramente funziona. Sto pianificando di reimplementare il sistema con l'analisi automatica della DTD quando ho del tempo libero.

6

Uso XSLT per unire file XML. Mi consente di regolare l'operazione di unione per bloccare semplicemente il contenuto o per unire a un livello specifico. È un po 'più di lavoro (e la sintassi XSLT è un po' speciale) ma super flessibile. Un paio di cose è necessario qui

a) Includere un file aggiuntivo b) Copiare il file originale 1: 1 c) Progetta la tua punto di fusione con o senza la duplicazione evitamento

a) All'inizio devo

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

questo permette di puntare al secondo file utilizzando $ MDOC

b) le istruzioni per copiare un albero dei sorgenti 1: 1 sono 2 modelli:

<!-- 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 nient'altro si ottiene una copia 1: 1 del primo file sorgente. Funziona con qualsiasi tipo di XML. La parte di unione è specifica del file. Supponiamo che tu abbia elementi evento con un attributo ID evento. Non vuoi gli ID duplicati. Il modello sarebbe simile a questa:

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

Naturalmente è possibile confrontare altre cose come i nomi dei tag, ecc Inoltre è a voi quanto è profonda l'unione avviene. Se non si dispone di una chiave per confrontare, il costrutto diventa più facile ad es. per ceppo:

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

Per eseguire XSLT in Java utilizzare questo:

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 si scarica il Saxon SAX Parser e farlo dalla riga di comando (shell esempio 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

Come implementeresti questo codice? Non so molto su XSLT ma non vedo come si possa eseguire questo XSLT. – cjbarth

+2

Origine xmlSource = new StreamSource (xmlFile); Origine xsltSource = new StreamSource (xsltFile); Risultato xmlResult = new StreamResult (resultFile); TransformerFactory transFact = TransformerFactory.newInstance(); Trasformatore trans = transFact.newTransformer (xsltSource); Parametri // Carica se abbiamo se (ParameterMap! = Null) { per (Entry curParam: ParameterMap.entrySet()) { \t trans.setParameter (curParam.getKey(), curParam .getValue()); \t} } trans.transform (xmlSource, xmlResult); – stwissel

+0

+1 XSLT è sicuramente la strada da percorrere per l'operazione di unione XML – yegor256

2

Ecco come dovrebbe essere l'utilizzo di XML Merge:

action.default=MERGE 

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

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

È necessario impostare il controllo ID per // nodo risultato e impostare l'azione PRESERVE per // nodo informazioni. Fai attenzione anche al fatto che gli usi XML Merge sono case sensitive: devi usare "xpath" non "XPath" nei tuoi .properties.

Non dimenticare di definire parametri -config come questo:

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