Объединение двух файлов XML в Java

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

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

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

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>

Ответ 1

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

}

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

Ответ 2

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

a) Включите дополнительный файл б) Скопируйте исходный файл 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. Объединяющая часть является файловой. Предположим, что у вас есть элементы событий с атрибутом идентификатора события. Вы не хотите дублировать идентификаторы. Шаблон будет выглядеть следующим образом:

 <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 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

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

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

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

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

Ответ 4

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

Doc A:

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

Doc B:

<root>
  <d/>
</root>

Слияние результатов:

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

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

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

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

Ответ 5

Вот как это должно выглядеть с помощью 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 для //result node и установить действие PRESERVE для //info node. Также будьте осторожны, что .properties Использование XML Merge чувствительно к регистру - вы должны использовать "xpath", а не "XPath" в своих .properties.

Не забудьте указать параметр -config следующим образом:

java -cp lib\xmlmerge-full.jar; ch.elca.el4j.services.xmlmerge.tool.XmlMergeTool -config xmlmerge.properties example1.xml example2.xml 

Ответ 6

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

XPath.resultsNode=results
action.resultsNode=MERGE

Ответ 7

Возможно, вы сможете написать приложение java, которое дезертизирует XML-документы в объекты, а затем "объединить" отдельные объекты программно в коллекцию. Затем вы можете сериализовать объект коллекции обратно в файл XML со всем, что было "слито".

JAXB В API есть несколько инструментов, которые могут конвертировать XML-документ/схему в классы Java. Инструмент "xjc" мог бы это сделать, хотя я не могу вспомнить, можете ли вы создавать классы непосредственно из документа XML, или если вам нужно сначала создать схему. Есть инструменты, которые могут генерировать схему из документа XML.

Надеюсь, это поможет... не уверен, что это то, что вы искали.

Ответ 8

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

Ответ 9

Итак, вы заинтересованы только в объединении элементов "результатов"? Все остальное игнорируется? Тот факт, что input0 имеет тип < info type = "a" / " > и input1, имеет < info type =" b "/" > , и ожидаемый результат имеет < info type = "a" /" > , кажется, предлагает это,

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

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

Ответ 10

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

Ответ 11

Иногда вам нужно просто конкатенировать 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();
        }
    }

Ответ 12

Считаете ли вы, что вы просто не разбираетесь в синтаксическом анализе XML "правильно" и просто обрабатываете файлы как большие длинные строки и используете скучные старые вещи, такие как хеш-карты и регулярные выражения...? Это может быть один из тех случаев, когда причудливые акронимы с X в них просто делают работу более запутанной, чем это должно быть.

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