Как объединить два XML в Java
Я пытаюсь объединить два xmls в Java. Я использую STaX API для написания этих XML. Я много искал в интернете, как объединить xmls, но ни один из них не выглядит таким простым, как C#. Есть ли прямой способ сделать это в Java с использованием StAX? Вероятно, xslt не будет правильным решением, так как размер файла может быть большим.
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>
5 ответов
У меня есть решение, которое работает для меня. Теперь эксперты, пожалуйста, сообщите, если это путь.
Спасибо, нилеш
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();
Общим решением по-прежнему будет XSLT, но вам нужно сначала объединить два файла в один большой XML с помощью элемента-оболочки (XSLT работает с одним входным источником).
<root>
<TestCaseBlock>
<TestCase TestCaseID="1">
...
</TestCase>
</TestCaseBlock>
<TestCaseBlock>
<TestCase TestCaseID="2">
...
</TestCase>
</TestCaseBlock>
</root>
Затем просто выполните XSLT для match="//TestCase" и выгрузите все тестовые случаи, игнорируя, к какому блоку тестовых блоков они принадлежат.
И не беспокойтесь о производительности, пока не попробуете. XML API в JAva становятся намного лучше, чем в 2003 году.
Это таблица стилей, которая вам нужна:
<?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>
Проверено, все работает.
Кстати, этот XSLT был скомпилирован и выполнен на этом (маленьком) примере за 1 мс.
Если структура достаточно регулярна, чтобы вы могли использовать привязку данных, я бы на самом деле рассмотрел возможность связывания XML из обоих файлов в объекты с использованием JAXB, а затем слияния объектов и сериализации обратно в XML. Если размеры файлов велики, вы также можете просто связать поддеревья; для этого вы используете XMLStreamReader (от Stax api, javax.xml.stream) для итерации до элемента, являющегося корнем, привязки этого элемента (и его дочерних элементов) к нужному объекту, итерации к следующему корневому элементу.
Проверьте XmlCombiner, который является библиотекой Java, которая реализует слияние XML именно таким образом. Это свободно основано на подобной функциональности, предлагаемой библиотекой plexus-utils.
В вашем случае теги также должны быть сопоставлены на основе значения атрибута TestCaseID. Вот полный пример:
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);
Отказ от ответственности: я автор библиотеки.
Я думаю, что XSLT и SAX могут быть решением.
Если вы будете работать с Stream, то STaX - это решение, я прочитал учебник Sun, который мне кажется очень полезным: Sun Tutorail на STaX
до свидания
Вы можете рассматривать XML как текстовый файл и комбинировать их. Это очень быстро по сравнению с другими методами. Взгляните на код ниже:-
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;
}
}