web-dev-qa-db-ja.com

DOM処理後のXML属性の順序

標準のDOMを使用してXMLを処理する場合、逆シリアル化した後の属性の順序は保証されません。最後に、標準のJava XML Transform APIを使用して出力をシリアル化するときに私が気付いたのはこれだけです。

しかし、私は注文を守る必要があります。 JavaにDOM APIによって処理されたXMLファイルの属性の元の順序を維持する可能性があるかどうか、または順序を強制する方法(おそらくこの種類のプロパティを設定できる代替シリアル化API。私の場合、処理が減少して、一連の属性を持つ同じ要素のシーケンスの一部(すべてではない)の属性の値が変更され、さらにいくつかの要素が挿入される場合があります。 。

「簡単な」方法はありますか、または出力を指定して入力XMLファイル全体を変更するために独自のXSLT変換スタイルシートを定義する必要がありますか?

更新私はあなたのすべての答えに感謝しなければなりません。答えは今、私が予想したよりも明白に思えます。以前は属性の順序が必要でなかったため、属性の順序に注意を払うことはありませんでした。

属性の順序を要求する主な理由は、結果のXMLファイルが見た目が異なるためです。ターゲットは、何百ものアラームを保持する構成ファイルです(すべてのアラームは、一連の属性によって定義されます)。このファイルは通常、時間の経過に伴う変更はほとんどありませんが、何かを変更する必要がある場合は手動で編集する必要があるため、順序付けしておくと便利です。時々、一部のプロジェクトでは、属性の1つを顧客固有のコードに設定するなど、このファイルを少し変更する必要があります。

私は、元のファイル(すべてのプロジェクトに共通)を各プロジェクトの特定の部分とマージする(いくつかの属性の値を変更する)小さなアプリケーションを開発したので、プロジェクト固有のファイルは、ベースのファイル(新しいアラーム定義またはいくつかの属性)の更新を取得します値のバグ修正)。順序付けされた属性を要求する私の主な動機は、テキスト比較ツール(Winmergeなど)を使用して、元のファイルに対してアプリケーションの出力をチェックできるようにすることです。フォーマット(主に属性の順序)が同じである場合、違いを簡単に見つけることができます。

XML SpyなどのXML処理プログラムを使用すると、XMLファイルを編集して順序付けを適用できるため(グリッドモード)、これは可能だと本当に思っていました。多分私の唯一の選択は、これらのプログラムの1つを使用して手動で出力ファイルを変更することです。

42

申し訳ありませんが、答えは「できない」または「そもそもなぜこれを行う必要があるのですか」よりも微妙です。

短い答えは「DOMはそれを許可しませんが、SAXは許可します」です。

これは、DOMは属性の順序を考慮しないためです。これは、標準に関しては意味がなく、XSLが入力ストリームを取得するまでに、情報はすでに失われているためです。ほとんどのXSLエンジンは、実際には入力ストリーム属性の順序を適切に保持します(たとえば、Xalan-C(1つの場合を除く)またはXalan-J(常に))。特に<xsl:copy*>を使用する場合。

私の知る限りでは、属性の順序が維持されないケースがあります。 -入力ストリームがDOMの場合-Xalan-C:結果ツリーのタグを文字どおりに挿入した場合(例:<elem att1={@att1} .../>

以下は、SAXを使用した1つの例です(DTDのしつこさを禁止しています)。

SAXParserFactory spf = SAXParserFactoryImpl.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(false);
spf.setFeature("http://xml.org/sax/features/validation", false);
spf.setFeature("http://Apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
spf.setFeature("http://Apache.org/xml/features/nonvalidating/load-external-dtd", false);
SAXParser sp = spf.newSAXParser() ;
Source src = new SAXSource ( sp.getXMLReader(), new InputSource( input.getAbsolutePath() ) ) ;
String resultFileName = input.getAbsolutePath().replaceAll(".xml$", ".cooked.xml" ) ;
Result result = new StreamResult( new File (resultFileName) ) ;
TransformerFactory tf = TransformerFactory.newInstance();
Source xsltSource = new StreamSource( new File ( COOKER_XSL ) );
xsl = tf.newTransformer( xsltSource ) ;
xsl.setParameter( "srcDocumentName", input.getName() ) ;
xsl.setParameter( "srcDocumentPath", input.getAbsolutePath() ) ;

xsl.transform(src, result );

指摘したいのは、多くの否定論者の意図により、属性の順序するが問題となるあるケースがあるということです。

回帰テストは明らかなケースです。うまく書かれていないXSLを最適化するために呼び出された人は誰でも、「新しい」結果ツリーが「古い」結果ツリーと類似または同一であることを確認したいことを知っています。そして、結果ツリーが約100万行になると、XML差分ツールは扱いにくいことがわかります...これらの場合、属性の順序を維持することは非常に役立ちます。

お役に立てれば ;-)

26
Alain Pannetier

XML勧告のセクション3.1をご覧ください。 「開始タグまたは空要素タグ内の属性指定の順序は重要ではないことに注意してください。」

特定の順序で表示するためにXML要素の属性を必要とするソフトウェアがある場合、そのソフトウェアはXMLを処理しておらず、表面的にはXMLのように見えるテキストを処理しています。修正する必要があります。

修正できず、要件に準拠したファイルを作成する必要がある場合、標準のXMLツールを使用してそれらのファイルを確実に作成することはできません。たとえば、XSLTを使用して定義された順序で属性を生成することをお勧めします(例:

<test>
   <xsl:attribute name="foo"/>
   <xsl:attribute name="bar"/>
   <xsl:attribute name="baz"/>
</test>

xSLTプロセッサがこれを発行することを見つけるだけです。

<test bar="" baz="" foo=""/>

プロセッサが使用しているDOMは、属性をタグ名のアルファベット順に並べるためです。 (これは、XML DOM間では一般的ですが、普遍的な動作ではありません。)

でも強調したいことがあります。ソフトウェアがXMLの推奨事項に1つの点で違反している場合、おそらく他の点でも違反しています。属性を間違った順序でフィードしたときに壊れる場合は、属性を一重引用符で区切るか、属性値に文字エンティティが含まれている場合や、XML勧告でXML文書に記載されている他の何十このソフトウェアの作者がおそらく考えていなかったことを行うことができます。

25
Robert Rossney

XMLの正規化により、一貫した属性の順序が得られます。主にXMLの一部またはすべてに対して署名を確認できるようにするためですが、他にも用途があります。これはあなたの目的に合うかもしれません。

9
Jon Hanna

Robert Rossneyが言ったことを過度に強調することはできませんが、私は試してみます。 ;-)

国際標準の利点は、誰もがそれに従うとき、人生は良いことです。私たちのすべてのソフトウェアは平和的に仲良くしています。

XMLは、私たちが持っている最も重要な標準の1つでなければなりません。それはSOAPのような「古いウェブ」のものの基礎であり、それでもRSSやAtomのような「ウェブ2.0」のものです。 XMLが異なるプラットフォーム間で相互運用できるのは、明確な標準のためです。

XMLを少しずつあきらめると、XMLのプロデューサーがXMLのコンシューマーがコンテンツをコンシューマーで消費できると想定できない状況に陥ります。これは業界に大きな影響を与えます。

標準に従ってXMLを処理しないコードを書く人には、非常に強力にプッシュバックする必要があります。このような経済状況の中で、「ノー」と言って顧客やビジネスパートナーの気分を害することに抵抗があることを理解しています。しかし、この場合、それだけの価値があると思います。ビジネスパートナーごとにXMLを手動で作成しなければならない場合、財務状況はさらに悪化します。

したがって、XMLを理解していない企業を「有効化」しないでください。適切な行を強調表示して、標準を送信します。彼らは、XMLが山かっこを含む単なるテキストであると考えるのをやめる必要があります。山括弧が含まれているテキストのようには動作しません。

これには言い訳がないようなものではありません。最小の組み込みデバイスでも、フル機能のXMLパーサー実装を組み込むことができます。完全な機能を備えたDOM実装を提供する余裕がないとしても、標準のXMLを解析できない正当な理由はまだ聞いていません。

8
John Saunders

あなたは本当にどんな種類の順番も守る必要はないはずです。私の知る限り、XMLドキュメントの検証時に属性の順序を考慮に入れるスキーマはありません。反対側でXMLを処理しているものは、適切なDOMを使用して結果を解析していないようです。

文字列の構築を使用して手動でドキュメントを構築することも1つのオプションだと思いますが、これはお勧めしません。

2
Soviut

私も同じ問題を抱えていました。 XML属性を変更したかったのですが、diffのために順序を維持したかったのです。私は StAX を使用してこれを達成しました。 XMLStreamReaderおよびXMLStreamWriter(カーソルベースのソリューション)を使用する必要があります。 START_ELEMENTイベントタイプを取得すると、カーソルは属性のインデックスを保持します。したがって、適切な変更を行い、それらを「順番に」出力ファイルに書き込むことができます。

これを見てください 記事/ディスカッション 。開始要素の属性を順番に読み取る方法を確認できます。

1
Bashir

私は属性の順序を気にするためのいくつかの正当な理由を見つけることができると思います:

  • 人間がXMLデータを手動で読み取り、診断、または編集する必要があることを予期している場合があります。その場合、可読性が重要であり、属性の一貫性のある論理的な順序付けが役立ちます。
  • 注文を(誤って)気にするツールやサービスと通信する必要がある場合があります。プロバイダーにコードの修正を依頼することはオプションではない可能性があります。一連の会計ドキュメントを電子的に配信するユーザーの期限が近づいている間、政府機関にそれを尋ねるようにしてください!

Alain Pannetier's solution のようです。

また、 DecentXML ;もご覧ください。 DOM互換ではない場合でも、XMLのフォーマット方法を完全に制御できます。書式を失わずに手動で編集したXMLを変更する場合に特に便利です。

1
Haroldo_OK

Robert Rossneyはそれをよく言っています。属性の順序に依存している場合、実際にはXMLを処理しているのではなく、XMLのように見えるものを処理しています。

属性の順序を気にする理由は少なくとも2つ考えられます。他にもあるかもしれませんが、少なくともこれら2つについては、代替案を提案できます。

  1. 同じ名前の属性の複数のインスタンスを使用しています:

    <foo myAttribute="a" myAttribute="b" myAttribute="c"/>
    

    これは単なる無効なXMLです。 DOMプロセッサは、ドキュメントを処理する場合、おそらくこれらの値の1つを除いてすべてをドロップします。これの代わりに、子要素を使用します。

    <foo>
        <myChild="a"/>
        <myChild="b"/>
        <myChild="c"/>
    </foo>
    
  2. 最初に来る属性に何らかの区別が適用されると想定しています。他の属性または子要素を使用して、これを明示的にします。例えば:

    <foo attr1="a" attr2="b" attr3="c" theMostImportantAttribute="attr1" />
    
1
Dan Breslau

これは、標準のDOMおよび変換APIを使用して、私が説明しているような迅速でダーティなソリューションを使用することで実現できます。

変換APIソリューションは属性をアルファベット順に並べます。属性名の前に、後でストリップしやすい文字列を付けて、希望する順序で出力されるようにすることができます。 「a _」「b_」などの単純な接頭辞は、ほとんどの状況で十分であり、1つのライナー正規表現を使用して出力XMLから簡単に取り除くことができます。

Xmlをロードして再保存し、属性の順序を保持したい場合は、同じ原理を使用できます。最初に入力xmlテキストの属性名を変更してから、それをDocumentオブジェクトに解析します。この場合も、XMLのテキスト処理に基づいてこの変更を行います。これは注意が必要ですが、正規表現を使用して要素とその属性文字列を検出することで実行できます。これは汚いソリューションであることに注意してください。 XMLを自分で解析する場合、これほど単純なものであっても、多くの落とし穴があるため、これを実装する場合は注意してください。

0
Radu Simionescu

作品の種類...

package mynewpackage;

// for the method
import Java.lang.reflect.Constructor;
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Comparator;
import Java.util.List;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

// for the test example
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import Java.io.StringReader;
import org.w3c.dom.Document;
import Java.math.BigDecimal;

public class NodeTools {
    /**
     * Method sorts any NodeList by provided attribute.
     * @param nl NodeList to sort
     * @param attributeName attribute name to use
     * @param asc true - ascending, false - descending
     * @param B class must implement Comparable and have Constructor(String) - e.g. Integer.class , BigDecimal.class etc
     * @return 
     */
    public static Node[] sortNodes(NodeList nl, String attributeName, boolean asc, Class<? extends Comparable> B)
    {        
        class NodeComparator<T> implements Comparator<T>
        {
            @Override
            public int compare(T a, T b)
            {
                int ret;
                Comparable bda = null, bdb = null;
                try{
                    Constructor bc = B.getDeclaredConstructor(String.class);
                    bda = (Comparable)bc.newInstance(((Element)a).getAttribute(attributeName));
                    bdb = (Comparable)bc.newInstance(((Element)b).getAttribute(attributeName));
                }
                catch(Exception e)
                {
                    return 0; // yes, ugly, i know :)
                }
                ret = bda.compareTo(bdb);
                return asc ? ret : -ret; 
            }
        }

        List<Node> x = new ArrayList<>();
        for(int i = 0; i < nl.getLength(); i++)
        {
            x.add(nl.item(i));
        }
        Node[] ret = new Node[x.size()];
        ret = x.toArray(ret);
        Arrays.sort(ret, new NodeComparator<Node>());
        return ret;
    }    

    public static void main(String... args)
    {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
        DocumentBuilder builder;
        String s = "<xml><item id=\"1\" price=\"100.00\" /><item id=\"3\" price=\"29.99\" /><item id=\"2\" price=\"5.10\" /></xml>";
        Document doc = null;
        try 
        {  
            builder = factory.newDocumentBuilder();  
            doc = builder.parse(new InputSource(new StringReader(s)));
        }
        catch(Exception e) { System.out.println("Alarm "+e); return; }

        System.out.println("*** Sort by id ***");
        Node[] ret = NodeTools.sortNodes(doc.getElementsByTagName("item"), "id", true, Integer.class);

        for(Node n: ret)
        {
            System.out.println(((Element)n).getAttribute("id")+" : "+((Element)n).getAttribute("price"));
        }

        System.out.println("*** Sort by price ***");
        ret = NodeTools.sortNodes(doc.getElementsByTagName("item"), "price", true, BigDecimal.class);
        for(Node n: ret)
        {
            System.out.println(((Element)n).getAttribute("id")+" : "+((Element)n).getAttribute("price"));
        }
    }
}

私の簡単なテストでは、次のように出力されます。

*** Sort by id ***
1 : 100.00
2 : 5.10
3 : 29.99
*** Sort by price ***
2 : 5.10
3 : 29.99
1 : 100.00
0