Объединить два XML-файла в Java
У меня есть два XML-файла схожей структуры, которые я хочу объединить в один файл. В настоящее время я использую EL4J XML Merge, с которым я столкнулся в этом уроке. Однако он не сливается, как я ожидаю, например, основная проблема заключается в том, что он не объединяет оба файла в один элемент, который также содержит 1, 2, 3 и 4. Вместо этого он просто отбрасывает 1 и 2 или 3 и 4. в зависимости от того, какой файл объединяется первым.
Поэтому я был бы признателен всем, кто имеет опыт работы с XML Merge, если они могут сказать мне, что я могу делать неправильно или, альтернативно, кто-нибудь знает хороший XML API для Java, который мог бы объединять файлы по мере необходимости?
Большое спасибо за вашу помощь заранее
Редактировать:
Мог действительно сделать с некоторыми хорошими предложениями на это, поэтому добавил щедрость. Я попробовал предложение JDIGITAL, но все еще возникают проблемы с слиянием XML.
Ниже приведен пример типа структуры XML-файлов, которые я пытаюсь объединить.
<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>
Ожидаемый результат
<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>
12 ответов
Не очень элегантно, но вы можете сделать это с помощью парсера DOM и 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);
}
}
Это предполагает, что вы можете хранить как минимум два документа в оперативной памяти одновременно.
Я использую XSLT для объединения файлов XML. Это позволяет мне настроить операцию слияния, чтобы просто отбросить содержимое вместе или слить на определенном уровне. Это немного больше работы (и синтаксис XSLT является своего рода особенным), но очень гибкий. Несколько вещей, которые вам нужны здесь
a) Включите дополнительный файл b) Скопируйте оригинальный файл 1:1 c) Создайте точку слияния с избежанием дублирования или без него
а) в начале у меня
<xsl:param name="mDocName">yoursecondfile.xml</xsl:param>
<xsl:variable name="mDoc" select="document($mDocName)" />
это позволяет указать на второй файл, используя $mDoc
б) Инструкции по копированию исходного дерева 1: 1 представляют собой 2 шаблона:
<!-- 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>
Ничего другого вы получаете 1: 1 копию вашего первого исходного файла. Работает с любым типом XML. Часть слияния зависит от файла. Предположим, у вас есть элементы события с атрибутом идентификатора события. Вы не хотите дублировать идентификаторы. Шаблон будет выглядеть так:
<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>
Конечно, вы можете сравнивать другие вещи, такие как имена тегов и т. Д. Кроме того, вам решать, насколько глубоко произойдет слияние. Если у вас нет ключа для сравнения, конструкция становится проще, например для журнала:
<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>
Для запуска XSLT в Java используйте это:
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);
или вы загружаете Saxon SAX Parser и делаете это из командной строки (пример оболочки 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
Спасибо всем за их предложения, к сожалению, ни один из предложенных методов не оказался подходящим в конце, так как мне нужно было иметь правила для того, как различные узлы структуры были простыми.
Поэтому я взял DTD, относящийся к XML-файлам, которые я объединял, и создал ряд классов, отражающих структуру. Из этого я использовал XStream для десериализации файла XML обратно в классы.
Таким образом, я аннотировал свои классы, превращая их в процесс использования комбинации правил, назначенных с аннотациями, и некоторого отражения для объединения объектов, а не объединения реальной структуры XML.
Если кого-то интересует код, который в этом случае объединяет файлы Nmap XML, см. http://fluxnetworks.co.uk/NmapXMLMerge.tar.gz то коды не идеальны, и я признаю, что они не очень гибкие, но они определенно работают. Я планирую переопределить систему с автоматическим анализом DTD, когда у меня будет немного свободного времени.
Вот как это должно выглядеть при использовании XML Merge:
action.default=MERGE
xpath.info=/run/info
action.info=PRESERVE
xpath.result=/run/host/results/result
action.result=MERGE
matcher.result=ID
Вы должны установить ID matcher для // результирующего узла и установить действие PRESERVE для // информационного узла. Также имейте в виду, что в.properties XML Merge учитывается регистр - в ваших.properties вы должны использовать "xpath", а не "XPath".
Не забудьте определить параметр -config следующим образом:
java -cp lib\xmlmerge-full.jar; ch.elca.el4j.services.xmlmerge.tool.XmlMergeTool -config xmlmerge.properties example1.xml example2.xml
Это может помочь, если вы четко определились с результатом, которого вы заинтересованы в достижении. Это то, что вы просите?
Док А:
<root>
<a/>
<b>
<c/>
</b>
</root>
Док Б:
<root>
<d/>
</root>
Объединенный результат:
<root>
<a/>
<b>
<c/>
</b>
<d/>
</root>
Вы беспокоитесь о масштабировании для больших документов?
Самый простой способ реализовать это в Java - использовать анализатор потокового XML (google для "java StAX"). Если вы используете библиотеку javax.xml.stream, вы обнаружите, что XMLEventWriter имеет удобный метод XMLEventWriter#add(XMLEvent). Все, что вам нужно сделать, - это зациклить элементы верхнего уровня в каждом документе и добавить их к вашему устройству записи, используя этот метод для создания объединенного результата. Единственная интересная часть - реализация логики читателя, которая рассматривает (только вызывает 'add') узлы верхнего уровня.
Я недавно реализовал этот метод, если вам нужны подсказки.
Я взглянул на ссылочную ссылку; странно, что XMLMerge не будет работать, как ожидалось. Ваш пример кажется простым. Вы читали раздел, озаглавленный Использование объявлений XPath с XmlMerge? Используя пример, попытайтесь настроить XPath для результатов и настроить его для слияния. Если я правильно читаю документ, это будет выглядеть примерно так:
XPath.resultsNode=results
action.resultsNode=MERGE
Возможно, вы сможете написать приложение Java, которое десерилизует XML-документы в объекты, а затем программно "объединить" отдельные объекты в коллекцию. Затем вы можете сериализовать объект коллекции обратно в XML-файл со всем "объединенным".
JAXB API имеет несколько инструментов, которые могут преобразовывать документ / схему XML в классы Java. Инструмент "xjc" мог бы сделать это, хотя я не могу вспомнить, можете ли вы создавать классы непосредственно из документа XML или вам сначала нужно сгенерировать схему. Существуют инструменты, которые могут генерировать схему из документа XML.
Надеюсь, это поможет... не уверен, что это то, что вы искали.
Вы можете попробовать Dom4J, который предоставляет очень хорошие средства для извлечения информации с помощью запросов XPath, а также позволяет очень легко писать XML. Вам просто нужно немного поиграться с API, чтобы сделать свою работу
Итак, вы заинтересованы только в объединении элементов "результатов"? Все остальное игнорируется? Тот факт, что input0 имеет
Если вы не беспокоитесь о масштабировании и хотите быстро решить эту проблему, то я бы предложил написать фрагмент кода для конкретной проблемы, который использует простую библиотеку, такую как JDOM, для рассмотрения входных данных и записи выходных результатов.
Попытка написать универсальный инструмент, который был бы достаточно "умным" для обработки всех возможных случаев слияния, была бы довольно трудоемкой - вам пришлось бы предоставить возможность конфигурации для определения правил слияния. Если вы точно знаете, как будут выглядеть ваши данные, и точно знаете, как должно выполняться слияние, тогда я мог бы представить, что ваш алгоритм будет проходить каждый вход XML и записывать в один вывод XML.
В дополнение к использованию Stax (что имеет смысл), возможно, было бы проще с StaxMate ( http://staxmate.codehaus.org/Tutorial). Просто создайте 2 SMInputCursors и дочерний курсор, если это необходимо. А затем типичная сортировка слиянием с 2 курсорами. Аналогично обходу документов DOM в режиме рекурсивного спуска.
Иногда вам нужно просто объединить XML-файлы в один, например, с похожей структурой, например:
файл xml1
:
<root>
<level1>
...
</level1>
<!--many records-->
<level1>
...
</level1>
</root>
файл xml2
:
<root>
<level1>
...
</level1>
<!--many records-->
<level1>
...
</level1>
</root>
В этом случае следующая процедура, которая использует jdom2
Библиотека может помочь вам:
void concatXML(Path fSource,Path fDest) {
Document jdomSource = null;
Document jdomDest = null;
List<Element> elems = new LinkedList<Element>();
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();
}
}
Задумывались ли вы просто о том, чтобы не беспокоиться о "правильном" анализе XML и просто рассматривать файлы как большие длинные строки и использовать скучные старые вещи, такие как хэш-карты и регулярные выражения...? Это может быть одним из тех случаев, когда причудливые аббревиатуры с X в них просто делают работу более скудной, чем она должна быть.
Очевидно, это немного зависит от того, сколько данных вам действительно нужно проанализировать во время слияния. Но, судя по всему, ответ на этот вопрос невелик.