2つのXMLドキュメントを比較するツールを知っている人はいますか。あざけるのをビレイ…もっとあります。順序に関係なく、ファイル1の各ノードがファイル2にもあることを確認するものが必要です。 XML Spyは、[子ノードの順序を無視する]オプションでそれを行うと思いましたが、そうではありませんでした。以下は同じと見なされます。
<Node>
<Child name="Alpha"/>
<Child name="Beta"/>
<Child name="Charlie"/>
</Node>
<Node>
<Child name="Beta"/>
<Child name="Charlie"/>
<Child name="Alpha"/>
</Node>
" XML diff tool "をグーグルで検索すると、十分な結果が得られます。そのうちの1つは OxygenXml 、私が頻繁に使用するツールです。 Microsofts XML Diff and Patch Tool を試すこともできます。
幸運を。
xmldiffs
と呼ばれるこのための簡単なpythonツールを書きました:
要素と属性の順序を無視して、2つのXMLファイルを比較します。
使用法:
xmldiffs [OPTION] FILE1 FILE2
追加のオプションはすべて
diff
コマンドに渡されます。
https://github.com/joh/xmldiffs で入手してください
Beyond Compare を使用すると、File Formats
-設定でXML Sort
変換を使用できます。このオプションを使用すると、XMLの子はdiffの前にソートされます。
Beyond Compareの試用版/ポータブルバージョンは 利用可能 です。
これには XMLUnit を使用します。これは、異なる順序の要素に対応できるためです。
今晩も同様のニーズがあり、自分の要件に合うものが見つかりませんでした。
私の回避策は、差分を取りたい2つのXMLファイルを、要素名のアルファベット順に並べ替えることでした。両方が一貫した順序になったら、通常のビジュアル差分ツールを使用して、並べ替えられた2つのファイルを差分できます。
このアプローチが他の人にとって役立つと思われる場合は、並べ替えを行うために作成したpythonスクリプトを http://dalelane.co.uk/blog/?p)で共有しました。 = 3225
簡単なJavaプログラムを作成しました。2つのXMLをHashMapに格納し、キーを要素のXPath(要素のテキスト値を含む)として、値をその要素の出現回数として格納しました。次に、キーセットと値の両方について2つのHashMapを比較しました。
/ ** *テキスト値を含み、ネストされたノードを含まない要素のマップを作成します。
*ここで、マップのキーは、要素のテキスト値と連結された要素のXPATHであり、要素の値は、その要素の出現回数です。
* * @param xmlContent * @return * @throws ParserConfigurationException * @throws SAXException * @throws IOException * /
private static Map<String, Long> getMapOfElementsOfXML(String xmlContent)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc1 = db.parse(new ByteArrayInputStream(xmlContent.getBytes()));
NodeList entries = doc1.getElementsByTagName("*");
Map<String, Long> mapElements = new HashMap<>();
for (int i = 0; i < entries.getLength(); i++) {
Element element = (Element) entries.item(i);
if (element.getChildNodes().getLength() == 1&&element.getTextContent()!=null) {
final String elementWithXPathAndValue = getXPath(element.getParentNode())
+ "/"
+ element.getParentNode().getNodeName()
+ "/"
+ element.getTagName()
+ "/"
+ element.getTextContent();
Long countValue = mapElements.get(elementWithXPathAndValue);
if (countValue == null) {
countValue = Long.valueOf(0l);
} else {
++countValue;
}
mapElements.put(elementWithXPathAndValue, countValue);
}
}
return mapElements;
}
static String getXPath(Node node) {
Node parent = node.getParentNode();
if (parent == null) {
return "";
}
return getXPath(parent) + "/" + parent.getNodeName();
}
完全なプログラムはここにあります https://comparetwoxmlsignoringstanzaordering.blogspot.com/2018/12/Java-program-to-compare-two-xmls.html
C#を使用すると、これを実行して、後で任意の差分ツールと比較できます。
public void Run()
{
LoadSortAndSave(@".. first file ..");
LoadSortAndSave(@".. second file ..");
}
public void LoadSortAndSave(String path)
{
var xdoc = XDocument.Load(path);
SortXml(xdoc.Root);
File.WriteAllText(path + ".sorted", xdoc.ToString());
}
private void SortXml(XContainer parent)
{
var elements = parent.Elements()
.OrderBy(e => e.Name.LocalName)
.ToArray();
Array.ForEach(elements, e => e.Remove());
foreach (var element in elements)
{
parent.Add(element);
SortXml(element);
}
}
これがSWI-Prologを使用した差分ソリューションです
:- use_module(library(xpath)).
load_trees(XmlRoot1, XmlRoot2) :-
load_xml('./xml_source_1.xml', XmlRoot1, _),
load_xml('./xml_source_2.xml', XmlRoot2, _).
find_differences(Reference, Root1, Root2) :-
xpath(Root1, //'Child'(@name=Name), Node),
not(xpath(Root2, //'Child'(@name=Name), Node)),
writeln([Reference, Name, Node]).
diff :-
load_trees(Root1, Root2),
(find_differences('1', Root1, Root2) ; find_differences('2', Root2, Root1)).
Prologは、ファイル1とファイル2のノードに一致するようにName変数を統合します。Node変数の統合は、「差分」検出を行います。
以下に出力例を示します。
% file 1 and file 2 have no differences
?- diff.
false.
% "Alpha" was updated in file 2
?- diff.
[1,Alpha,element(Child,[name=Alpha],[])]
[2,Alpha,element(Child,[name=Alpha,age=7],[])]
false.
(非常に)迅速で汚いアプローチとして、私はこれをピンチで行いました:
C1に、次の式を入力します。
=IF(ISERROR(VLOOKUP(B1,FILE1,1,FALSE)),"DIFF","")
D1で、フォーラムに参加します。
=IF(ISERROR(VLOOKUP(A1,FILE2,1,FALSE)),"DIFF","")
これにより、一方のファイルには表示され、もう一方のファイルには表示されない行が強調表示されます。それは一気に整頓されていませんが、時々あなたはあなたが持っているもので作業しなければならないだけです。
私は最近ここで同様の答えを出しました( 要素の順序を無視してXMLファイルを比較するためのLinux用のオープンソースコマンドラインツール )、しかし私はより詳細を提供します...
2つのツリーを一緒にウォークするプログラムを作成する場合は、ツリー間の「一致」を識別し、一致しないノードを処理するためのロジックをカスタマイズできます。これがxslt2.0の例です(すみません、長すぎます):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:set="http://exslt.org/sets"
xmlns:primary="primary"
xmlns:control="control"
xmlns:util="util"
exclude-result-prefixes="xsl xs set primary control">
<!-- xml diff tool
import this stylesheet from another and call the "compare" template with two args:
primary: the root of the primary tree to submit to comparison
control: the root of the control tree to compare against
the two trees will be walked together. the primary tree will be walked in document order, matching elements
and attributes from the control tree along the way, building a tree of common content, with appendages
containing primary and control only content. that tree will then be used to generate the diff.
the process of matching involves finding, for an element or attribute in the primary tree, the
equivalent element or attribute in the control tree, *at the same level*, and *regardless of ordering*.
matching logic is encoded as templates with mode="find-match", providing a hook to wire in specific
matching logic for particular elements or attributes. for example, an element may "match" based on an
@id attribute value, irrespective of element ordering; encode this in a mode="find-match" template.
the treatment of diffs is encoded as templates with mode="primary-only" and "control-only", providing
hooks for alternate behavior upon encountering differences.
-->
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:param name="full" select="false()"/><!-- whether to render the full doc, as opposed to just diffs -->
<xsl:template match="/">
<xsl:call-template name="compare">
<xsl:with-param name="primary" select="*/*[1]"/><!-- first child of root element, for example -->
<xsl:with-param name="control" select="*/*[2]"/><!-- second child of root element, for example -->
</xsl:call-template>
</xsl:template>
<!-- OVERRIDES: templates that can be overridden to provide targeted matching logic and diff treatment -->
<!-- default find-match template for elements
(by default, for "complex" elements, name has to match, for "simple" elements, name and value do)
for context node (from "primary"), choose from among $candidates (from "control") which one matches
(override with more specific match patterns to effect alternate behavior for targeted elements)
-->
<xsl:template match="*" mode="find-match" as="element()?">
<xsl:param name="candidates" as="element()*"/>
<xsl:choose>
<xsl:when test="text() and count(node()) = 1"><!-- simple content -->
<xsl:sequence select="$candidates[node-name(.) = node-name(current())][text() and count(node()) = 1][. = current()][1]"/>
</xsl:when>
<xsl:when test="not(node())"><!-- empty -->
<xsl:sequence select="$candidates[node-name(.) = node-name(current())][not(node())][1]"/>
</xsl:when>
<xsl:otherwise><!-- presumably complex content -->
<xsl:sequence select="$candidates[node-name(.) = node-name(current())][1]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- default find-match template for attributes
(by default, name and value have to match)
for context attr (from "primary"), choose from among $candidates (from "control") which one matches
(override with more specific match patterns to effect alternate behavior for targeted attributes)
-->
<xsl:template match="@*" mode="find-match" as="attribute()?">
<xsl:param name="candidates" as="attribute()*"/>
<xsl:sequence select="$candidates[. = current()][node-name(.) = node-name(current())][1]"/>
</xsl:template>
<!-- default primary-only template (override with more specific match patterns to effect alternate behavior) -->
<xsl:template match="@* | *" mode="primary-only">
<xsl:apply-templates select="." mode="illegal-primary-only"/>
</xsl:template>
<!-- write out a primary-only diff -->
<xsl:template match="@* | *" mode="illegal-primary-only">
<primary:only>
<xsl:copy-of select="."/>
</primary:only>
</xsl:template>
<!-- default control-only template (override with more specific match patterns to effect alternate behavior) -->
<xsl:template match="@* | *" mode="control-only">
<xsl:apply-templates select="." mode="illegal-control-only"/>
</xsl:template>
<!-- write out a control-only diff -->
<xsl:template match="@* | *" mode="illegal-control-only">
<control:only>
<xsl:copy-of select="."/>
</control:only>
</xsl:template>
<!-- end OVERRIDES -->
<!-- MACHINERY: for walking the primary and control trees together, finding matches and recursing -->
<!-- compare "primary" and "control" trees (this is the root of comparison, so CALL THIS ONE !) -->
<xsl:template name="compare">
<xsl:param name="primary"/>
<xsl:param name="control"/>
<!-- write the xml diff into a variable -->
<xsl:variable name="diff">
<xsl:call-template name="match-children">
<xsl:with-param name="primary" select="$primary"/>
<xsl:with-param name="control" select="$control"/>
</xsl:call-template>
</xsl:variable>
<!-- "print" the xml diff as textual output -->
<xsl:apply-templates select="$diff" mode="print">
<xsl:with-param name="render" select="$full"/>
</xsl:apply-templates>
</xsl:template>
<!-- assume primary (context) element and control element match, so render the "common" element and recurse -->
<xsl:template match="*" mode="common">
<xsl:param name="control"/>
<xsl:copy>
<xsl:call-template name="match-attributes">
<xsl:with-param name="primary" select="@*"/>
<xsl:with-param name="control" select="$control/@*"/>
</xsl:call-template>
<xsl:choose>
<xsl:when test="text() and count(node()) = 1">
<xsl:value-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="match-children">
<xsl:with-param name="primary" select="*"/>
<xsl:with-param name="control" select="$control/*"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<!-- find matches between collections of attributes in primary vs control -->
<xsl:template name="match-attributes">
<xsl:param name="primary" as="attribute()*"/>
<xsl:param name="control" as="attribute()*"/>
<xsl:param name="primaryCollecting" as="attribute()*"/>
<xsl:choose>
<xsl:when test="$primary and $control">
<xsl:variable name="this" select="$primary[1]"/>
<xsl:variable name="match" as="attribute()?">
<xsl:apply-templates select="$this" mode="find-match">
<xsl:with-param name="candidates" select="$control"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:choose>
<xsl:when test="$match">
<xsl:copy-of select="$this"/>
<xsl:call-template name="match-attributes">
<xsl:with-param name="primary" select="subsequence($primary, 2)"/>
<xsl:with-param name="control" select="remove($control, 1 + count(set:leading($control, $match)))"/>
<xsl:with-param name="primaryCollecting" select="$primaryCollecting"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="match-attributes">
<xsl:with-param name="primary" select="subsequence($primary, 2)"/>
<xsl:with-param name="control" select="$control"/>
<xsl:with-param name="primaryCollecting" select="$primaryCollecting | $this"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$primaryCollecting | $primary">
<xsl:apply-templates select="$primaryCollecting | $primary" mode="primary-only"/>
</xsl:if>
<xsl:if test="$control">
<xsl:apply-templates select="$control" mode="control-only"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- find matches between collections of elements in primary vs control -->
<xsl:template name="match-children">
<xsl:param name="primary" as="node()*"/>
<xsl:param name="control" as="element()*"/>
<xsl:variable name="this" select="$primary[1]" as="node()?"/>
<xsl:choose>
<xsl:when test="$primary and $control">
<xsl:variable name="match" as="element()?">
<xsl:apply-templates select="$this" mode="find-match">
<xsl:with-param name="candidates" select="$control"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:choose>
<xsl:when test="$match">
<xsl:apply-templates select="$this" mode="common">
<xsl:with-param name="control" select="$match"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$this" mode="primary-only"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="match-children">
<xsl:with-param name="primary" select="subsequence($primary, 2)"/>
<xsl:with-param name="control" select="if (not($match)) then $control else remove($control, 1 + count(set:leading($control, $match)))"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$primary">
<xsl:apply-templates select="$primary" mode="primary-only"/>
</xsl:when>
<xsl:when test="$control">
<xsl:apply-templates select="$control" mode="control-only"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<!-- end MACHINERY -->
<!-- PRINTERS: print templates for writing out the diff -->
<xsl:template match="*" mode="print">
<xsl:param name="depth" select="-1"/>
<xsl:param name="render" select="false()"/>
<xsl:param name="lineLeader" select="' '"/>
<xsl:param name="rest" as="element()*"/>
<xsl:if test="$render or descendant::primary:* or descendant::control:*">
<xsl:call-template name="whitespace">
<xsl:with-param name="indent" select="$depth"/>
<xsl:with-param name="leadChar" select="$lineLeader"/>
</xsl:call-template>
<xsl:text><</xsl:text>
<xsl:value-of select="name(.)"/>
<xsl:apply-templates select="@* | primary:*[@*] | control:*[@*]" mode="print">
<xsl:with-param name="depth" select="$depth"/>
<xsl:with-param name="render" select="$render"/>
<xsl:with-param name="lineLeader" select="$lineLeader"/>
</xsl:apply-templates>
<xsl:choose>
<xsl:when test="text() and count(node()) = 1"><!-- field element (just textual content) -->
<xsl:text>></xsl:text>
<xsl:value-of select="."/>
<xsl:text></</xsl:text>
<xsl:value-of select="name(.)"/>
<xsl:text>></xsl:text>
</xsl:when>
<xsl:when test="count(node()) = 0"><!-- empty (self-closing) element -->
<xsl:text>/></xsl:text>
</xsl:when>
<xsl:otherwise><!-- complex content -->
<xsl:text>> </xsl:text>
<xsl:apply-templates select="*[not(self::primary:* and @*) and not(self::control:* and @*)]" mode="print">
<xsl:with-param name="depth" select="$depth + 1"/>
<xsl:with-param name="render" select="$render"/>
<xsl:with-param name="lineLeader" select="$lineLeader"/>
</xsl:apply-templates>
<xsl:call-template name="whitespace">
<xsl:with-param name="indent" select="$depth"/>
<xsl:with-param name="leadChar" select="$lineLeader"/>
</xsl:call-template>
<xsl:text></</xsl:text>
<xsl:value-of select="name(.)"/>
<xsl:text>></xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
</xsl:if>
<!-- write the rest of the elements, if any -->
<xsl:apply-templates select="$rest" mode="print">
<xsl:with-param name="depth" select="$depth"/>
<xsl:with-param name="render" select="$render"/>
<xsl:with-param name="lineLeader" select="$lineLeader"/>
<xsl:with-param name="rest" select="()"/><!-- avoid implicit param pass to recursive call! -->
</xsl:apply-templates>
</xsl:template>
<xsl:template match="@*" mode="print">
<xsl:param name="depth" select="0"/>
<xsl:param name="render" select="false()"/>
<xsl:param name="lineLeader" select="' '"/>
<xsl:param name="rest" as="attribute()*"/>
<xsl:if test="$render">
<xsl:text> </xsl:text>
<xsl:call-template name="whitespace">
<xsl:with-param name="indent" select="$depth + 3"/>
<xsl:with-param name="leadChar" select="$lineLeader"/>
</xsl:call-template>
<xsl:value-of select="name(.)"/>
<xsl:text>="</xsl:text>
<xsl:value-of select="."/>
<xsl:text>"</xsl:text>
</xsl:if>
<xsl:apply-templates select="$rest" mode="print">
<xsl:with-param name="depth" select="$depth"/>
<xsl:with-param name="render" select="$render"/>
<xsl:with-param name="lineLeader" select="$lineLeader"/>
<xsl:with-param name="rest" select="()"/><!-- avoid implicit param pass to recursive call! -->
</xsl:apply-templates>
</xsl:template>
<xsl:template match="primary:* | control:*" mode="print">
<xsl:param name="depth"/>
<xsl:variable name="diffType" select="util:diff-type(.)"/>
<xsl:variable name="primary" select="self::primary:*"/>
<xsl:variable name="lineLeader" select="if ($primary) then '+' else '-'"/>
<!-- only if this is the first in a sequence of control::* elements, since the rest are handled along with the first... -->
<xsl:if test="util:diff-type(preceding-sibling::*[1]) != $diffType">
<xsl:if test="@*">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:call-template name="diffspace">
<xsl:with-param name="indent" select="if (@*) then $depth + 3 else $depth"/>
<xsl:with-param name="primary" select="$primary"/>
</xsl:call-template>
<b><i><!-- ... --></i></b><!-- something to identify diff sections in output -->
<xsl:if test="node()">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:variable name="rest" select="set:leading(following-sibling::*, following-sibling::*[util:diff-type(.) != $diffType])"/>
<xsl:apply-templates select="@* | node()" mode="print">
<xsl:with-param name="depth" select="$depth"/>
<xsl:with-param name="render" select="true()"/>
<xsl:with-param name="lineLeader" select="$lineLeader"/>
<xsl:with-param name="rest" select="$rest/@* | $rest/*"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template name="whitespace">
<xsl:param name="indent" select="0" as="xs:integer"/>
<xsl:param name="leadChar" select="' '"/>
<xsl:choose>
<xsl:when test="$indent > 0">
<xsl:value-of select="$leadChar"/>
<xsl:text> </xsl:text>
<xsl:for-each select="0 to $indent - 1">
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="0 to $indent">
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="diffspace">
<xsl:param name="indent" select="0" as="xs:integer"/>
<xsl:param name="primary" select="false()"/>
<xsl:for-each select="0 to $indent">
<xsl:choose>
<xsl:when test="$primary">
<xsl:text>++</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>--</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<!-- just an "enum" for deciding whether to group adjacent diffs -->
<xsl:function name="util:diff-type" as="xs:integer">
<xsl:param name="construct"/>
<xsl:sequence select="if ($construct/self::primary:*[@*]) then 1 else
if ($construct/self::control:*[@*]) then 2 else
if ($construct/self::primary:*) then 3 else
if ($construct/self::control:*) then 4 else
if ($construct) then 5 else 0"/>
</xsl:function>
<!-- end PRINTERS -->
</xsl:stylesheet>
あなたの入力に基づいて、この入力例を検討してください。
<test>
<Node>
<Child name="Alpha"/>
<Child name="Beta"/>
<Child name="Charlie"/>
</Node>
<Node>
<Child name="Beta"/>
<Child name="Charlie"/>
<Child name="Alpha"/>
</Node>
</test>
スタイルシートをそのままにして、例に適用した場合の出力は次のとおりです。
<Node>
<Child
++++++++<!-- ... -->
+ name="Alpha"
--------<!-- ... -->
- name="Beta">
</Child>
<Child
++++++++<!-- ... -->
+ name="Beta"
--------<!-- ... -->
- name="Charlie">
</Child>
<Child
++++++++<!-- ... -->
+ name="Charlie"
--------<!-- ... -->
- name="Alpha">
</Child>
</Node>
ただし、このカスタムテンプレートを追加すると、次のようになります。
<xsl:template match="Child" mode="find-match" as="element()?">
<xsl:param name="candidates" as="element()*"/>
<xsl:sequence select="$candidates[@name = current()/@name][1]"/>
</xsl:template>
これは、@name
属性に基づいてChild
要素に一致することを示しているため、出力は得られません(つまり、差分がありません)。