1つのファイルにマージしたい同様の構造の2つのXMLファイルがあります。現在、このチュートリアルで出会った EL4J XML Merge を使用しています。ただし、たとえば、主な問題は、両方のファイルのを1つの要素(1、2、3、4を含む要素)にマージしないことです。代わりに、1と2、または3と4のいずれかを破棄します。最初にマージされるファイルによって異なります。
したがって、XMLマージの経験がある人なら誰でも、私が間違っている可能性があることを教えてくれるか、あるいはJava)の優れたXMLAPIを知っている人がいればありがたいです。必要に応じてファイルをマージしますか?
事前にご協力いただきありがとうございます
編集:
これを行う上でいくつかの良い提案を本当に行うことができるので、賞金を追加しました。 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>
あまりエレガントではありませんが、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つのドキュメントをRAMに同時に保持できることを前提としています。
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を使用して2番目のファイルを指すことができます
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シェルの例)。
#!/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
残念ながら、提案してくれたすべての人に感謝します。構造のさまざまなノードが単純化される方法のルールが必要だったため、提案された方法はいずれも最終的には適切であることがわかりませんでした。
だから私がしたことは、私がマージしていたXMLファイルに関連するDTDを取得し、そこから構造を反映するいくつかのクラスを作成することでした。これから、 XStream を使用してXMLファイルをクラスに逆シリアル化しました。
このようにして、クラスに注釈を付け、実際のXML構造をマージするのではなく、オブジェクトをマージするために、注釈といくつかのリフレクションで割り当てられたルールの組み合わせを使用するプロセスにしました。
この場合にマージするコードに興味がある場合は、Nmap XMLファイルを参照してください http://fluxnetworks.co.uk/NmapXMLMerge.tar.gz コード完璧ではなく、それほど柔軟ではないことは認めますが、間違いなく機能します。空き時間があるときにDTDを自動的に解析して、システムを再実装する予定です。
あなたが達成したい結果について明確に述べていれば、それは助けになるかもしれません。これはあなたが求めているものですか?
Doc A:
<root>
<a/>
<b>
<c/>
</b>
</root>
Doc B:
<root>
<d/>
</root>
マージされた結果:
<root>
<a/>
<b>
<c/>
</b>
<d/>
</root>
大きなドキュメントのスケーリングについて心配していますか?
これをJavaで実装する最も簡単な方法は、ストリーミングXMLパーサー(「JavaStAX」のグーグル)を使用することです。javax.xml.streamライブラリを使用すると、XMLEventWriterが見つかります。便利なメソッドXMLEventWriter#add(XMLEvent)があります。各ドキュメントの最上位要素をループし、このメソッドを使用してそれらをライターに追加するだけで、マージされた結果が生成されます。唯一のファンキーな部分は、リーダーロジックの実装です。これは、最上位ノードでのみ考慮されます( 'add'を呼び出すだけです)。
ヒントが必要な場合は、最近このメソッドを実装しました。
XMLマージを使用すると次のようになります。
action.default=MERGE
xpath.info=/run/info
action.info=PRESERVE
xpath.result=/run/Host/results/result
action.result=MERGE
matcher.result=ID
//結果ノードにIDマッチャーを設定し、//情報ノードに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
参照されているリンクを調べました。 XMLMergeが期待どおりに機能しないのは奇妙なことです。あなたの例は簡単に思えます。 XmlMergeでのXPath宣言の使用 というタイトルのセクションを読みましたか?この例を使用して、結果のXPathを設定し、マージするように設定してみてください。ドキュメントを正しく読んでいると、次のようになります。
XPath.resultsNode=results
action.resultsNode=MERGE
XMLドキュメントをオブジェクトに脱滅菌し、個々のオブジェクトをプログラムでコレクションに「マージ」するJavaアプリを作成して、コレクションオブジェクトをシリアル化してXMLに戻すことができる場合があります。すべてが「マージ」されたファイル。
[〜#〜] jaxb [〜#〜] APIには、XMLドキュメント/スキーマをJavaクラスに変換できるツールがいくつかあります。「xjc」ツールはXMLドキュメントから直接クラスを作成できるのか、それとも最初にスキーマを生成する必要があるのかは思い出せませんが、これは可能です。XMLドキュメントからスキーマを生成できるツールは他にもあります。
これがお役に立てば幸いです...これがあなたが探していたものかどうかはわかりません。
Dom4J を試すことができます。これは、XPathクエリを使用して情報を抽出するための非常に優れた手段を提供し、XMLを非常に簡単に記述できるようにします。あなたはあなたの仕事をするためにしばらくの間APIをいじくり回す必要があります
たとえば、次のように、XMLファイルを1つに連結する必要がある場合があります。
ファイル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();
}
}
それで、あなたは「結果」要素をマージすることにのみ興味がありますか?他のすべては無視されますか? input0には<infotype = "a" />があり、input1には<info type = "b" />があり、期待される結果には<info type = "a" />があるという事実はこれを示唆しているようです。
スケーリングについて心配せず、この問題をすばやく解決したい場合は、JDOMなどの単純なライブラリを使用して入力を検討し、出力結果を書き込む、問題固有のコードを作成することをお勧めします。
考えられるすべてのマージケースを処理するのに十分な「スマート」な汎用ツールを作成しようとすると、かなり時間がかかります。マージルールを定義するための構成機能を公開する必要があります。データがどのようになるかを正確に知っていて、マージを実行する必要がある方法を正確に知っている場合、アルゴリズムが各XML入力をウォークし、単一のXML出力に書き込むと思います。
Stax(これは理にかなっています)を使用することに加えて、StaxMate( http://staxmate.codehaus.org/Tutorial )を使用するとおそらく簡単になります。 2つのSMInputCursorと、必要に応じて子カーソルを作成するだけです。次に、2つのカーソルを使用した一般的なマージソート。再帰下降方式でDOMドキュメントをトラバースするのと似ています。