在Java中合并两个XML文件
我有两个类似结构的XML文件,我想合并成一个文件。 目前我正在使用本教程中遇到的EL4J XML合并 。 然而,它并没有像我预期的那样合并,主要的问题是它没有将两个文件合并成一个包含1,2,3和4的元素,而是只丢弃1和2或3和4取决于哪个文件被首先合并。
所以,如果能够告诉我我可能做错了什么,或者是否有人知道一个好的XML API for Java能够根据我的需要合并这些文件,我将不胜感激任何有过XML Merge经验的人。
很多感谢您的帮助提前
编辑:
真的可以做一些很好的build议做这个,所以增加了一个赏金。 我已经尝试了jdigital的build议,但仍然有与XML合并问题。
下面是我正在尝试合并的XML文件的结构types示例。
<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>
不是很优雅,但是你可以用DOMparsing器和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); } }
这假设您可以同时将至less两个文档保存在RAM中。
我使用XSLT来合并XML文件。 它允许我调整合并操作,只是将内容压缩在一起,或者在特定级别合并。 这是一个更多的工作(和XSLT语法是一种特殊),但超级灵活。 在这里你需要一些东西
a)包含一个额外的文件b)复制原始文件1:1 c)devise你的合并点,避免重复或不重复
a)一开始我有
<xsl:param name="mDocName">yoursecondfile.xml</xsl:param> <xsl:variable name="mDoc" select="document($mDocName)" />
这允许使用$ mDoc指向第二个文件
b)复制源树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副本。 适用于任何types的XML。 合并部分是文件特定的。 假设您有事件ID属性的事件元素。 你不想要重复的ID。 模板看起来像这样:
<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>
当然,你可以比较其他的东西,如标签名称等,也取决于你多深的合并发生。 如果你没有密钥进行比较,那么构造就变得更容易了,比如log:
<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>
要在Java中运行XSLT,请使用以下命令:
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 SAXparsing器并从命令行(Linux shell示例)执行:
#!/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!"
因人而异
不幸的是,感谢大家对他们的build议,最后没有任何方法可以certificate是合适的,因为我需要对结构不同的节点进行规划。
所以我所做的是将DTD与我正在合并的XML文件相关联,并从中创build了反映结构的许多类。 从这里我用XStream将XML文件反序列化成类。
这样我注释了我的类,使它成为一个使用注释和一些reflection的规则的组合来合并对象的过程,而不是合并实际的XML结构。
如果任何人有兴趣在这种情况下合并Nmap XML文件的代码请参阅http://fluxnetworks.co.uk/NmapXMLMerge.tar.gz代码不完美,我会承认不是大规模灵活,但它绝对有效。; 我计划重新实现这个系统,当我有一些空闲时间的时候,它会自动parsingDTD。
这是使用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匹配器,并为// info节点设置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
如果您明确了解您感兴趣的结果,可能会有所帮助。 这是你要求的吗?
文件A:
<root> <a/> <b> <c/> </b> </root>
文件B:
<root> <d/> </root>
合并结果:
<root> <a/> <b> <c/> </b> <d/> </root>
你担心缩放大文件吗?
在Java中实现这个最简单的方法是使用streamXMLparsing器(谷歌为“Java StAX”)。 如果你使用javax.xml.stream库,你会发现XMLEventWriter有一个方便的方法XMLEventWriter#add(XMLEvent)。 您只需循环遍历每个文档中的顶层元素,然后使用此方法将其添加到您的作者,以生成合并结果。 唯一时髦的部分是实现阅读器逻辑,只考虑顶级节点(仅调用“添加”)。
我最近实施了这个方法,如果你需要提示。
我看了一下引用的链接; XMLMerge无法按预期工作,这很奇怪。 你的例子似乎很简单。 您是否阅读过使用XmlMerge使用XPath声明的章节 ? 使用示例,尝试为结果设置XPath并将其设置为合并。 如果我正确地阅读文档,它会看起来像这样:
XPath.resultsNode=results action.resultsNode=MERGE
您可能可以编写一个将XML文档解压缩为对象的Java应用程序,然后以编程方式将各个对象“合并”为一个集合。 然后,您可以将集合对象序列化回一个XML文件,其中包含“合并”的所有内容。
JAXB API有一些工具可以将XML文档/模式转换为Java类。 “xjc”工具可能会这样做,但我不记得是否可以直接从XML文档创build类,或者必须先生成一个模式。 有一些工具可以从XML文档生成模式。
希望这有助于…不知道这是你在找什么。
除了使用Stax(这是有道理的),StaxMate可能会更容易( http://staxmate.codehaus.org/Tutorial )。 只需创build2个SMInputCursors,并且需要子游标。 然后用2个游标进行典型合并sorting。 类似于以recursion下降的方式遍历DOM文档。
那么,你只对合并“结果”元素感兴趣? 一切都被忽略了? input0有一个<info type =“a”/>和input1有一个<info type =“b”/>,预期的结果有一个<info type =“a”/>的事实似乎暗示了这一点。
如果你不担心缩放问题,并且你想快速解决这个问题,那么我会build议编写一个问题特定的代码,使用一个简单的库如JDOM来考虑input并写出输出结果。
试图编写一个“智能”的通用工具来处理所有可能的合并情况将非常耗时 – 您必须公开一个configuration能力来定义合并规则。 如果你确切地知道你的数据是什么样子的,而且你确切知道如何执行合并,那么我会想象你的algorithm将遍历每个XMLinput并写入到一个XML输出。
你可以试试Dom4J ,它提供了一个非常好的方法来使用XPath查询来提取信息,还允许你很容易地写XML。 你只需要玩一段时间的API来做你的工作
你有没有考虑过只是没有正确地parsingXML,而只是把文件当成长长的string,并使用无用的旧东西,如哈希映射和正则expression式……? 这可能是其中的一个例子,其中X的花式缩略词只是使工作摆脱比它需要。
显然,这取决于您在合并时实际需要parsing多less数据。 但通过事情的声音,答案并不多。