问题
I am trying to merge two xmls in Java. I am using STaX API to write these XMLs. I searched a lot on internet on how to merge xmls but none seems as straight forward as C#. Is there any straight-forward way of doing this in Java using StAX? Probably xslt would not be the right solution since the file size can be big.
File1.xml
<TestCaseBlock>
<TestCase TestCaseID="1">
<Step ExecutionTime="2011-03-29 12:08:31 EST">
<Status>Passed</Status>
<Description>foo</Description>
<Expected>foo should pass</Expected>
<Actual>foo passed</Actual>
</Step>
</TestCase>
</TestCaseBlock>
File2.xml
<TestCaseBlock>
<TestCase TestCaseID="2">
<Step ExecutionTime="2011-03-29 12:08:32 EST">
<Status>Failed</Status>
<Description>test something</Description>
<Expected>something expected</Expected>
<Actual>not as expected</Actual>
</Step>
</TestCase>
</TestCaseBlock>
Merged.xml
<TestCaseBlock>
<TestCase TestCaseID="1">
<Step ExecutionTime="2011-03-29 12:08:33 EST">
<Status>Passed</Status>
<Description>foo</Description>
<Expected>foo should pass</Expected>
<Actual>foo passed</Actual>
</Step>
</TestCase>
<TestCase TestCaseID="2">
<Step ExecutionTime="2011-03-29 12:08:34 EST">
<Status>Failed</Status>
<Description>test something</Description>
<Expected>something expected</Expected>
<Actual>not as expected</Actual>
</Step>
</TestCase>
</TestCaseBlock>
回答1:
I have a solution that works for me. Now experts, please advise if this is the way to go.
Thanks, -Nilesh
XMLEventWriter eventWriter;
XMLEventFactory eventFactory;
XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
eventWriter = outputFactory.createXMLEventWriter(new FileOutputStream("testMerge1.xml"));
eventFactory = XMLEventFactory.newInstance();
XMLEvent newLine = eventFactory.createDTD("\n");
// Create and write Start Tag
StartDocument startDocument = eventFactory.createStartDocument();
eventWriter.add(startDocument);
eventWriter.add(newLine);
StartElement configStartElement = eventFactory.createStartElement("","","TestCaseBlock");
eventWriter.add(configStartElement);
eventWriter.add(newLine);
String[] filenames = new String[]{"test1.xml", "test2.xml","test3.xml"};
for(String filename:filenames){
XMLEventReader test = inputFactory.createXMLEventReader(filename,
new FileInputStream(filename));
while(test.hasNext()){
XMLEvent event= test.nextEvent();
//avoiding start(<?xml version="1.0"?>) and end of the documents;
if (event.getEventType()!= XMLEvent.START_DOCUMENT && event.getEventType() != XMLEvent.END_DOCUMENT)
eventWriter.add(event);
eventWriter.add(newLine);
test.close();
}
eventWriter.add(eventFactory.createEndElement("", "", "TestCaseBlock"));
eventWriter.add(newLine);
eventWriter.add(eventFactory.createEndDocument());
eventWriter.close();
回答2:
General solution still would be XSLT, but you'd need to combine two files into one big XML first with a wrapper element (XSLT works with one input source).
<root>
<TestCaseBlock>
<TestCase TestCaseID="1">
...
</TestCase>
</TestCaseBlock>
<TestCaseBlock>
<TestCase TestCaseID="2">
...
</TestCase>
</TestCaseBlock>
</root>
Then just do XSLT for match="//TestCase", and dump all test cases out, ignoring what test case block they belong to.
And don't worry about performance until you have tried. XML APIs in JAva are getting much better than in 2003.
This is stylesheet you need:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<TestCaseBlock>
<xsl:apply-templates/>
</TestCaseBlock>
</xsl:template>
<xsl:template match="//TestCase">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Tested, it works.
BTW, this XSLT was compiled and executed on this (small) example in 1ms.
回答3:
If structure is regular enough so you can use data binding, I would actually consider binding XML from both files into objects using JAXB, then merging objects, serializing back as XML. If file sizes are big you can also just bind sub-trees; for this you use XMLStreamReader (from Stax api, javax.xml.stream) to iterate to element that is the root, bind that element (and its children) to object you want, iterate to next root element.
回答4:
Check XmlCombiner which is a Java library that implements XML merging in exactly this way. It is loosely based on a similar functionality offered by plexus-utils library.
In your case the tags should be also matched based on the value of the attribute 'TestCaseID'. Here is the full example:
import org.atteo.xmlcombiner.XmlCombiner;
// create combiner
XmlCombiner combiner = new XmlCombiner("TestCaseID");
// combine files
combiner.combine(firstFile);
combiner.combine(secondFile);
// store the result
combiner.buildDocument(resultFile);
Disclaimer: I am the author of the library.
回答5:
I think XSLT and SAX could be a solution.
If you'll work with Stream that STaX is solution, I read Sun tutorial, I think is very helpful: Sun Tutorail on STaX
Bye
回答6:
You can consider XML as text file and combine them. That is very fast as compared to other methods. Please have a look at below code :-
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class XmlComb {
static Set<String> lstheader = new HashSet<String>();
public static void main(String[] args) throws IOException {
Map<String,List<String>> map1 = getMapXml("J:\\Users\\Documents\\XMLCombiner01\\src\\main\\resources\\File1.xml");
Map<String,List<String>> map2 = getMapXml("J:\\Users\\Documents\\XMLCombiner01\\src\\main\\resources\\File2.xml");
Map<String,List<String>> mapCombined = combineXML(map1, map2);
lstheader.forEach( lst -> {
System.out.println(lst);
});
try {
mapCombined.forEach((k,v) -> {
System.out.println(k);
v.forEach(val -> System.out.println(val));
});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Map<String,List<String>> combineXML(Map<String, List<String>> map1, Map<String, List<String>> map2 ) {
Map<String,List<String>> map2Modified = new TreeMap<String, List<String>>();
Map<String,List<String>> mapCombined = new TreeMap<String, List<String>>();
// --- Modifying map ---
for(String strKey2 : map2.keySet()) {
if(map1.containsKey(strKey2)) {
map2Modified.put(strKey2.split("\">")[0] + "_1\">", map2.get(strKey2));
}
else {
map2Modified.put(strKey2 , map2.get(strKey2));
}
}
//---- Combining map ---
map1.putAll(map2Modified);
return map1;
}
public static Map<String,List<String>> getMapXml(String strFilePath) throws IOException{
File file = new File(strFilePath);
BufferedReader br = new BufferedReader(new FileReader(file));
Map<String, List<String>> testMap = new TreeMap<String, List<String>>();
List<String> lst = null;
String st;
String strCatalogName = null;
while ((st = br.readLine()) != null) {
//System.out.println(st);
if(st.toString().contains("<TestCase")){
lst = new ArrayList<String>();
strCatalogName = st;
testMap.put(strCatalogName, lst);
}
else if(st.contains("</TestCase")){
lst.add(st);
testMap.put(strCatalogName,lst);
}
else {
if(lst != null){
lst.add(st);
}else {
lstheader.add(st);
}
}
}
return testMap;
}
}
来源:https://stackoverflow.com/questions/5681597/how-to-merge-two-xmls-in-java