12

要素の順序を無視して XML ファイルを比較するオープン ソースのコマンドライン ツール (Linux 用) はありますか?

入力ファイルの例a.xml:

<tag name="AAA">
  <attr name="b" value="1"/>
  <attr name="c" value="2"/>
  <attr name="a" value="3"/>
</tag>

<tag name="BBB">
  <attr name="x" value="111"/>
  <attr name="z" value="222"/>
</tag>
<tag name="BBB">
  <attr name="x" value="333"/>
  <attr name="z" value="444"/>
</tag>

b.xml:

<tag name="AAA">
  <attr name="a" value="3"/>
  <attr name="b" value="1"/>
  <attr name="c" value="2"/>
</tag>

<tag name="BBB">
  <attr name="z" value="444"/>
  <attr name="x" value="333"/>
</tag>
<tag name="BBB">
  <attr name="x" value="111"/>
  <attr name="z" value="222"/>
</tag>

したがって、これら 2 つのファイルを比較しても、違いは出力されません。最初に XSLT を使用してファイルを並べ替えようとしました。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="WINDOWS-1252" omit-xml-declaration="no" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*">
        <xsl:sort select="@*" />
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

しかし問題は、要素<tag name="BBB">にはソートがないことです。それらは、入力された順序で単純に出力されます。

diffXmlxDiff、を既に見ましたがXMLUnitxmlstarletこれらのどれも問題を解決しません。diff の出力は、たとえばdiff.

要素の順序の差分をソートまたは無視する方法についてのヒントはありますか? ありがとう!

4

6 に答える 6

0

まず、XMLの例はルート要素がないため、有効ではありません。ルート要素を追加しました。これはa.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <tag name="AAA">
        <attr name="b" value="1"/>
        <attr name="c" value="2"/>
        <attr name="a" value="3"/>
    </tag>
    <tag name="BBB">
        <attr name="x" value="111"/>
        <attr name="z" value="222"/>
    </tag>
    <tag name="BBB">
        <attr name="x" value="333"/>
        <attr name="z" value="444"/>
    </tag>
</root>

そしてこれはb.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <tag name="AAA">
        <attr name="a" value="3"/>
        <attr name="b" value="1"/>
        <attr name="c" value="2"/>
    </tag>
    <tag name="BBB">
        <attr name="z" value="444"/>
        <attr name="x" value="333"/>
    </tag>
    <tag name="BBB">
        <attr name="x" value="111"/>
        <attr name="z" value="222"/>
    </tag>
</root>

同じname属性を持つ兄弟をマージし、タグ名と値でソートすることにより、比較用の標準形を作成できます。

同じ名前の兄弟要素をマージするには、前の兄弟と同じ名前の要素を無視して、残りを取得する必要があります。これは、次のXpathによって2番目の要素レベルで実行できます。

*[not(@name = preceding-sibling::*/@name)]

この名前の親を持つすべての子要素を選択するには、これらの要素の名前を取得する必要があります。その後、名前と値で並べ替える必要があります。これにより、両方のファイルを次の標準形式に変換できます。

<?xml version="1.0" encoding="WINDOWS-1252"?>
<root>
    <tag name="AAA">
        <attr name="a" value="3"/>
        <attr name="b" value="1"/>
        <attr name="c" value="2"/>
    </tag>
    <tag name="BBB">
        <attr name="x" value="111"/>
        <attr name="x" value="333"/>
        <attr name="z" value="222"/>
        <attr name="z" value="444"/>
    </tag>
</root>

これにより、変換が行われます。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" encoding="WINDOWS-1252" omit-xml-declaration="no" indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="/root">
        <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:for-each select="*[not(@name = preceding-sibling::*/@name)]">
                    <xsl:variable name="name" select="@name"/>
                    <xsl:copy>
                        <xsl:copy-of select="@*"/>
                        <xsl:for-each select="../*[@name = $name]/*">
                            <xsl:sort select="@name"/>
                            <xsl:sort select="@value"/>
                            <xsl:copy>
                                <xsl:copy-of select="@*"/>
                            </xsl:copy>
                        </xsl:for-each>
                    </xsl:copy>
                </xsl:for-each>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
于 2012-09-25T16:51:56.453 に答える
0

並べ替え対象の要素の属性のシーケンスに基づいて並べ替えを要求しています。しかし、ここの最上位tag要素には属性が 1 つしかありません: name. tag複数の要素を異なる方法で並べ替えたい場合はname="BBB"、それらに個別の並べ替えキーを指定する必要があります。

あなたの例では、次のようなことを試してみますselect="concat(name(), @name, name(*[1]), *[1]/@name)"が、これは非常に浅いキーです。入力の最初の子の値を使用しますが、プロセス中に子の位置が変わる場合があります。(私よりもデータをよく知っている) 1 回のパスで各要素の適切なキーを計算できる場合もあれば、複数回のパスが必要な場合もあります。

于 2012-08-21T18:44:12.520 に答える
0

前処理するには、独自のインタープリターを作成する必要があります。XSLT はそれを行うための 1 つの方法です。私は XSLT の専門家ではありません。XSLT を使って物事を並べ替えることができるかどうかはわかりません。

これは、あなたが望むことを行うことができる簡単で汚いperlスクリプトです。実際の XML パーサーを使用する方がはるかに賢明であることに注意してください。私はどれにも精通していないので、自分でそれらを書くという私のひどい練習にあなたをさらしています. コメントに注意してください。あなたは警告されました。

#!/usr/bin/perl

use strict;
use warnings;

# NOTE: general wisdom - do not use simple homebrewed XML parsers like this one!
#
# This makes sweeping assumptions that are not production grade.  Including:
#   1. Assumption of one XML tag per line
#   2. Assumption that no XML tag contains a greater-than character
#      like <foo bar="<oops>" />
#   3. Assumes the XML is well-formed, nothing like <foo><bar>baz</foo></bar>

# recursive function to parse each tag.
sub parse_tag {
  my $tag_name = shift;
  my @level = (); # LOCAL: each recursive call has its OWN distinct @level
  while(<>) {
    chomp;

    # new open tag:  match new tag name, parse in recursive call
    if (m"<\s*([^\s/>]+)[^/>]*>") {
      push (@level, "$_\n" . parse_tag($1) );

    # close tag, verified by name, or else last line of input
    } elsif (m"<\s*/\s*$tag_name[\s>]"i or eof()) {
      # return all children, sorted and concatenated, then the end tag
      return join("\n", sort @level) . "\n$_";

    } else {
      push (@level, $_);
    }
  }
  return join("\n", sort @level);
}

# start with an impossible tag in case there is no root
print parse_tag("<root>");

名前を付けて保存し、xml_diff_prep.plこれを実行します。

$ diff -sq <(perl xml_diff_prep.pl a.xml) <(perl xml_diff_prep.pl b.xml)
Files /proc/self/fd/11 and /proc/self/fd/12 are identical

(私はフラグ-s-qフラグを明示的に使用しました。gvimdiff またはその他のユーティリティやフラグを使用できます。ファイル記述子によってファイルを識別することに注意してください。これは、bash トリックを使用して各入力でプリプロセッサ コマンドを実行したためです。 ll be in the same order you specified. この質問で要求された並べ替えにより、内容が予期しない場所にある可能性があることに注意してください。)

「オープン ソース」「コマンド ライン ツール」というあなたの要求を満たすために、このコードをBeerwareライセンス (BSD 2 条項、価値があると思われる場合は、私にビールを買ってください) の下でオープン ソースとしてリリースします。

于 2014-02-14T02:58:53.570 に答える
-1

あなたの例から、要素内の要素の並べ替えのみを気にしているように見えますが、要素自体の並べ替えは気にしていないようです。もしそうなら、(前の回答者が言ったように)ソートを使用する必要がありますが、要素や属性ではなく、要素に対して使用する必要があります。

「tag」や「attr」という名前の XML 要素を使用すると、多くの人が混乱するでしょう。これらは、XML で既に特定の意味を持つ用語であるためです。

あなたの構造が実際にあなたの例と同じである場合、より「XMLっぽい」表現は次のようになります。

<AAA b="1" c="2" a="3" />
<BBB x="111" z="222" />
<BBB x="333" z="444" />

はるかにコンパクトで、用語の競合を回避し、定義により属性を順序に依存しないようにします。つまり、既製の XML diff ユーティリティは、必要な効果を得ることができます。または、正規の XML に変換して、通常の差分を使用します。

于 2013-11-27T14:47:34.580 に答える