Объединить два XML-файла в Java - PullRequest
13 голосов
/ 15 марта 2009

У меня есть два XML-файла схожей структуры, которые я хочу объединить в один файл. В настоящее время я использую EL4J XML Merge , с которым я столкнулся в этом уроке. Однако он не объединяется, как я ожидаю, например, основная проблема заключается в том, что он не объединяет оба файла в один элемент, который также содержит 1, 2, 3 и 4. Вместо этого он просто отбрасывает 1 и 2 или 3 и 4 в зависимости от того, какой файл объединяется первым.

Так что я был бы признателен всем, кто имеет опыт работы с XML Merge, если бы они могли сказать мне, что я могу делать неправильно, или, в качестве альтернативы, кто-нибудь знает хороший XML API для Java, который был бы способен объединять файлы по мере необходимости

Большое спасибо за вашу помощь заранее

Edit:

Может действительно сделать несколько хороших советов по этому поводу, поэтому добавил награду. Я попробовал предложение 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 ]

11 голосов
/ 31 марта 2009

Не очень элегантно, но вы можете сделать это с помощью парсера 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);
  }

}

Это предполагает, что вы можете хранить как минимум два документа в оперативной памяти одновременно.

6 голосов
/ 18 апреля 2011

Я использую XSLT для объединения файлов XML. Это позволяет мне настроить операцию слияния, чтобы просто отбросить содержимое вместе или слить на определенном уровне. Это немного больше работы (и синтаксис XSLT является своего рода особенным), но очень гибкий. Несколько вещей, которые вам нужны здесь

а) Включить дополнительный файл б) Скопировать оригинальный файл 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

3 голосов
/ 14 июня 2009

Спасибо всем за их предложения, к сожалению, ни один из предложенных методов не оказался подходящим в конце, так как мне нужно было иметь правила для того, как различные узлы структуры были простыми.

Итак, я взял DTD, относящийся к XML-файлам, которые я объединял, и создал ряд классов, отражающих структуру. Из этого я использовал XStream для десериализации файла XML обратно в классы.

Таким образом, я аннотировал свои классы, превращая их в процесс использования комбинации правил, назначенных с аннотациями, и некоторого отражения для объединения Объектов, а не объединения реальной структуры XML.

Если кто-то заинтересован в коде, который в данном случае объединяет файлы Nmap XML, см. http://fluxnetworks.co.uk/NmapXMLMerge.tar.gz коды не идеальные, и я признаю, что они не очень гибкие, но они определенно работают. Я планирую переопределить систему с помощью автоматического разбора DTD, когда у меня будет немного свободного времени.

2 голосов
/ 17 августа 2012

Вот как это должно выглядеть при использовании 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 
2 голосов
/ 27 марта 2009

Это может помочь, если вы четко определились с результатом, которого хотите достичь. Это то, что вы просите?

Док. A:

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

Док. B:

<root>
  <d/>
</root>

Объединенный результат:

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

Вас беспокоит масштабирование больших документов?

Самый простой способ реализовать это в Java - использовать потоковый анализатор XML (google для «java StAX»). Если вы используете библиотеку javax.xml.stream, вы обнаружите, что XMLEventWriter имеет удобный метод XMLEventWriter # add (XMLEvent). Все, что вам нужно сделать, это перебрать элементы верхнего уровня в каждом документе и добавить их к вашему автору записи, используя этот метод для генерации вашего объединенного результата. Единственная интересная часть - реализация логики чтения, которая рассматривает (вызывает только «добавление») только узлы верхнего уровня.

Я недавно реализовал этот метод, если вам нужны подсказки.

1 голос
/ 16 марта 2009

Я посмотрел на ссылку; Странно, что XMLMerge не будет работать, как ожидалось. Ваш пример кажется простым. Вы читали раздел, озаглавленный Использование объявлений XPath с XmlMerge ? Используя пример, попытайтесь установить XPath для результатов и настроить его на слияние. Если я правильно читаю документ, это будет выглядеть примерно так:

XPath.resultsNode=results
action.resultsNode=MERGE
0 голосов
/ 24 августа 2018

Иногда вам нужно просто объединить 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();
        }
    }
0 голосов
/ 01 апреля 2009

Вы можете попробовать Dom4J , который предоставляет очень хорошие средства для извлечения информации с использованием запросов XPath, а также позволяет очень легко писать XML. Вам просто нужно немного поиграться с API, чтобы выполнить свою работу

0 голосов
/ 31 марта 2009

Итак, вы заинтересованы только в объединении элементов «результатов»? Все остальное игнорируется? Тот факт, что input0 имеет , а input1 имеет , а ожидаемый результат имеет , по-видимому, предполагает это.

Если вы не беспокоитесь о масштабировании и хотите быстро решить эту проблему, то я бы предложил написать фрагмент кода для конкретной проблемы, который использует простую библиотеку, такую ​​как JDOM, для рассмотрения входных данных и записи выходных результатов.

Попытка написать универсальный инструмент, который был бы достаточно «умным» для обработки всех возможных случаев слияния, заняла бы довольно много времени - вам пришлось бы предоставить возможность конфигурации для определения правил слияния. Если вы точно знаете, как будут выглядеть ваши данные, и точно знаете, как должно выполняться слияние, тогда я мог бы представить, что ваш алгоритм будет проходить каждый ввод XML и записывать в один вывод XML.

0 голосов
/ 27 марта 2009

В дополнение к использованию Stax (что имеет смысл), возможно, было бы проще с StaxMate (http://staxmate.codehaus.org/Tutorial).. Просто создайте 2 SMInputCursors и дочерний курсор, если это необходимо. А затем типичную сортировку слиянием с 2 курсорами Аналогично обходу документов DOM рекурсивным способом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...