用Java合并两个XML文件

时间:2009-03-15 20:19:47

标签: java xml api parsing

我有两个结构相似的XML文件,我希望将它们合并到一个文件中。 目前我正在使用本教程中遇到的EL4J XML Merge。 然而,它并没有像我期望的那样合并实例主要问题是它没有将两个文件合并为一个元素,即包含1,2,3和4的元素。 相反,它只丢弃1和2或3和4,具体取决于首先合并的文件。

所以我会感谢任何有XML Merge经验的人,如果他们可以告诉我我可能做错了什么,或者有人知道一个优秀的XML API for 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 个答案:

答案 0 :(得分:11)

不是很优雅,但您可以使用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);
  }

}

这假设您可以同时在RAM中保存至少两个文档。

答案 1 :(得分:6)

我使用XSLT合并XML文件。它允许我调整合并操作,只是将内容一起猛击或在特定级别合并。这是一个更多的工作(和XSLT语法是一种特殊的),但超级灵活。你需要的一些东西

a)包含一个附加文件 b)以1:1复制原始文件 c)设置合并点,有或没有重复避免

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副本。适用于任何类型的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>

当然,您可以比较标签名称等其他内容。此外,由您决定合并的深度。如果您没有要比较的密钥,则构造变得更容易,例如对于日志:

 <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 SAX Parser并从命令行执行此操作(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!"

YMMV

答案 2 :(得分:3)

感谢大家的建议,遗憾的是,最终没有一种方法最终证明是合适的,因为我需要对结构中不同节点的方式有规则。

所以我所做的是获取与我正在合并的XML文件相关的DTD,并创建了许多反映结构的类。 由此我使用XStream将XML文件反序列化为类。

通过这种方式,我对类进行了注释,使其成为使用注释和注释分配的规则组合的过程,以便合并对象而不是合并实际的XML结构。

如果有人对代码感兴趣,在这种情况下合并Nmap XML文件,请参阅http://fluxnetworks.co.uk/NmapXMLMerge.tar.gz代码不完美,我承认不是非常灵活,但绝对有效。我打算重新实现系统,因为我有空闲时间自动解析DTD。

答案 3 :(得分:2)

如果您明确表示您有兴趣实现的结果,那么这可能有所帮助。这是你要的吗?

Doc A:

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

Doc B:

<root>
  <d/>
</root>

合并结果:

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

您是否担心扩展大型文档?

在Java中实现这一点的最简单方法是使用流式XML解析器(google for'java StAX')。如果使用javax.xml.stream库,您会发现XMLEventWriter有一个方便的方法XMLEventWriter #add(XMLEvent)。您所要做的就是遍历每个文档中的顶级元素,并使用此方法将它们添加到您的编写器以生成合并结果。唯一的时髦部分是实现仅在顶级节点上考虑(仅调用'add')的读取器逻辑。

如果您需要提示,我最近实施了此方法。

答案 4 :(得分:2)

这就是使用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 

答案 5 :(得分:1)

我看了一下引用的链接;奇怪的是,XMLMerge无法按预期工作。你的例子似乎很简单。您是否阅读了标题为Using XPath declarations with XmlMerge的部分?使用该示例,尝试为结果设置XPath并将其设置为合并。如果我正确地阅读文档,它将看起来像这样:

XPath.resultsNode=results
action.resultsNode=MERGE

答案 6 :(得分:0)

您可以编写一个将XML文档解密为对象的Java应用程序,然后以编程方式将各个对象“合并”到一个集合中。然后,您可以将集合对象序列化为XML文件,并将所有内容“合并”。

JAXB API有一些工具可以将XML文档/模式转换为java类。 “xjc”工具可能能够做到这一点,虽然我不记得你是否可以直接从XML文档创建类,或者你必须先生成一个模式。除了可以从XML文档生成模式之外,还有一些工具。

希望这会有所帮助......不确定这是否是您所寻找的。

答案 7 :(得分:0)

除了使用Stax(确实有意义)之外,使用StaxMate(http://staxmate.codehaus.org/Tutorial)可能会更容易。只需创建2个SMInputCursors和子游标即可。然后典型的合并排序与2个游标。类似于以递归下降方式遍历DOM文档。

答案 8 :(得分:0)

那么,你只对合并'结果'元素感兴趣吗?其他一切都被忽略了? input0具有&lt; info type =“a”/&gt;的事实和input1有一个&lt; info type =“b”/&gt;并且预期结果具有&lt; info type =“a”/&gt;似乎暗示了这一点。

如果您不担心扩展并希望快速解决此问题,那么我建议编写一个特定于问题的代码,使用像JDOM这样的简单库来考虑输入并写入输出结果。

尝试编写一个“智能”足以处理所有可能的合并情况的通用工具将非常耗时 - 您必须公开配置功能来定义合并规则。如果你确切知道你的数据会是什么样的,并且你确切知道合并需要如何执行,那么我想你的算法会遍历每个XML输入并写入单个XML输出。

答案 9 :(得分:0)

您可以尝试Dom4J,这提供了一种使用XPath查询提取信息的非常好的方法,并且还允许您非常轻松地编写XML。您只需要使用API​​一段时间来完成工作

答案 10 :(得分:0)

有时候您只需要将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();
        }
    }

答案 11 :(得分:-6)

你是否考虑过不打算“正确”解析XML,只是将文件视为大字符串并使用无聊的旧东西,如哈希映射和正则表达式......?这可能是其中带有X的花哨首字母缩略词使得工作比需要更加繁琐的情况之一。

显然,这确实取决于您在进行合并时实际需要解析的数据量。但通过事物的声音,答案并不多。