关于java:DOM处理后XML属性的顺序

Order of XML attributes after DOM processing

通过标准DOM处理XML时,不能保证序列顺序后的属性顺序。最后,这是我在使用标准java XML Transform API序列化输出时刚刚意识到的。

但是我确实需要保持秩序。我想知道在Java上是否有可能保持通过DOM API处理的XML文件的属性的原始顺序,或以任何方式强制执行该顺序(也许通过使用替代的序列化API来设置该顺序)一种财产)。就我而言,处理减少了用一堆属性来更改同一元素序列的某些属性(不是全部)的值,并可能插入更多元素的可能性。

有什么"简便"的方法,还是我必须定义自己的XSLT转换样式表以指定输出并更改整个输入XML文件?

更新我必须感谢您的所有回答。现在,答案似乎比我预期的更加明显。我从没有对属性顺序给予任何关注,因为我以前从不需要它。

要求属性顺序的主要原因是生成的XML文件看起来只是不同。目标是一个包含数百个警报的配置文件(每个警报均由一组属性定义)。该文件通常随时间进行很少的修改,但是使它保持有序是很方便的,因为当我们需要修改某些内容时,它是手工编辑的。有时,某些项目需要对该文件进行少量修改,例如将属性之一设置为客户特定的代码。

我刚刚开发了一个小应用程序,用于将原始文件(所有项目通用)与每个项目的特定部分合并(修改某些属性的值),因此特定于项目的文件将获取基本文件的更新(新的警报定义或某些属性)值错误修正)。我要求有序属性的主要动机是能够通过文本比较工具(例如Winmerge)来检查应用程序的输出是否再次原始文件。如果格式(主要是属性顺序)保持不变,则可以轻松发现差异。

我真的以为这是可能的,因为XML处理程序(例如XML Spy)使您可以编辑XML文件并应用一些排序(网格模式)。也许我唯一的选择是使用这些程序之一来手动修改输出文件。


抱歉地说,但是答案比"不,您不能做"或"为什么首先需要这样做?"更微妙。

简短的答案是" DOM不允许您这样做,但SAX可以"。

这是因为DOM并不关心属性顺序,因为就标准而言,它毫无意义,并且到XSL掌握输入流时,信息已经丢失。
实际上,大多数XSL引擎都会优雅地保留输入流属性的顺序(例如,
Xalan-C(一种情况除外)或Xalan-J(总是)。特别是如果您使用

据我所知,没有保留属性顺序的情况是这样。
-如果输入流是DOM
-Xalan-C:如果您按字面意义插入结果树标记(例如

这是SAX的一个示例,用于记录(也禁止DTD干扰)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的人都知道,您通常希望确保"新"结果树与"旧"结果树相似或相同。当结果树大约有一百万行时,XML diff工具就显得太笨拙了……
在这些情况下,保留属性顺序很有帮助。

希望这可以帮助 ;-)


查看XML建议的第3.1节。它说:"请注意,开始标记或空元素标记中属性规范的顺序并不重要。"

如果某个软件要求XML元素上的属性以特定顺序显示,则该软件未在处理XML,而是在处理表面上看起来像XML的文本。它需要修复。

如果无法修复它,并且您必须生成符合其要求的文件,则无法可靠地使用标准XML工具来生成这些文件。例如,您可以尝试(按照您的建议)使用XSLT以定义的顺序生成属性,例如:

1
2
3
4
5
<test>
   <xsl:attribute name="foo"/>
   <xsl:attribute name="bar"/>
   <xsl:attribute name="baz"/>
</test>

只是发现XSLT处理器发出此消息:

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

因为处理器使用的DOM按标签名称的字母顺序排列属性。 (这是XML DOM之间的常见但非普遍的行为。)

但我想强调一点。如果某个软件在某一方面违反了XML建议,则它可能在其他方面也违反了XML建议。如果在以错误的顺序提供属性时中断它,则如果用单引号定界属性,或者如果属性值包含字符实体,或者XML建议说XML文档中的其他任何内容,它也可能中断。可以做到这一点,该软件的作者可能没有想到。


XML Canonicalisation产生一致的属性顺序,主要是允许人们检查部分或全部XML上的签名,尽管还有其他潜在用途。这可能适合您的目的。


过分强调罗伯特·罗斯尼(Robert Rossney)所说的话是不可能的,但我会尽力的。 ;-)

国际标准的好处是,当每个人都遵循国际标准时,生活就会变得美好。我们所有的软件都和平相处。

XML必须成为我们拥有的最重要的标准之一。它是诸如SOAP之类的"旧网络"内容的基础,而仍然是RSS和Atom之类的" web 2.0"内容的基础。由于明确的标准,XML能够在不同平台之间进行互操作。

如果我们一点一点地放弃XML,那么我们将陷入一种情况,即XML的生产者将无法假定XML的消费者将能够消费其内容。这会对整个行业造成灾难性的影响。

我们应该非常有力地向任何不按标准处理XML的代码编写者退缩。我了解到,在当前经济困难时期,我们不愿冒犯"拒绝"来冒犯客户和商业伙伴。但是在这种情况下,我认为这是值得的。如果我们必须为每个业务合作伙伴手工制作XML,那么财务状况将更加糟糕。

因此,不要"启用"不了解XML的公司。向他们发送标准,并突出显示相应的行。他们需要停止认为XML只是带有尖括号的文本。它的行为根本不像带有尖括号的文本。

这似乎没有借口。即使最小的嵌入式设备也可以在其中具有全功能的XML解析器实现。我还没有听到无法解析标准XML的充分理由,即使人们无法负担得起全功能的DOM实现。


您确实不需要保持任何顺序。据我所知,在验证XML文档时也没有架构考虑属性顺序。听起来好像在处理另一端的XML都没有使用正确的DOM来解析结果。

我想一个选择是使用字符串构建来手动构建文档,但是我强烈建议不要这样做。


我有同样的问题。我想修改XML属性,但由于差异而想保持顺序。我用StAX实现了这一目标。您必须使用XMLStreamReader和XMLStreamWriter(基于Cursor的解决方案)。当您获得START_ELEMENT事件类型时,光标将保留属性的索引。因此,您可以进行适当的修改并将它们"按顺序"写入输出文件。

看这篇文章/讨论。您可以看到如何按顺序读取开始元素的属性。


Robert Rossney说得很好:如果您依赖属性的排序,那么您实际上并不是在处理XML,而是在处理类似于XML的东西。

我至少可以想到两个您可能会关心属性排序的原因。可能还有其他的,但是至少对于这两个,我可以建议其他选择:

  • 您正在使用具有相同名称的多个属性实例:

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

    这只是普通的无效XML。如果DOM处理器完全处理文档,则它可能会丢弃所有这些值之一。代替此,您想使用子元素:

    1
    2
    3
    4
    5
    <foo>
        <myChild="a"/>
        <myChild="b"/>
        <myChild="c"/>
    </foo>
  • 您假设某种区别适用于首先出现的属性。通过其他属性或子元素将其明确显示。例如:

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

  • 作品种类...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    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"));
            }
        }
    }

    在我的简单测试中,它显示:

    1
    2
    3
    4
    5
    6
    7
    8
    *** Sort by id ***
    1 : 100.00
    2 : 5.10
    3 : 29.99
    *** Sort by price ***
    2 : 5.10
    3 : 29.99
    1 : 100.00


    您仍然可以使用标准的DOM和Transformation API来执行此操作,方法是使用一种快速而肮脏的解决方案,例如我正在描述的解决方案:

    我们知道,转换API解决方案按字母顺序对属性进行排序。您可以在属性名称前添加一些易于删除的字符串,以便按所需顺序输出它们。在大多数情况下,简单的前缀" a _"," b_"等就足够了,并且可以使用一个内衬正则表达式轻松将其从输出xml中剥离。

    如果要加载xml并重新保存并想要保留属性顺序,则可以使用相同的原理,方法是首先修改输入xml文本中的属性名称,然后将其解析为Document对象。再次,基于xml的文本处理进行此修改。这可能很棘手,但是可以再次使用正则表达式通过检测元素及其属性字符串来完成。请注意,这是一个肮脏的解决方案。自己解析XML时会遇到很多陷阱,即使是像这样简单的事情,因此如果决定实现这一点,请务必小心。


    我认为我可以找到一些关心属性顺序的正当理由:

    • 您可能期望人们不得不一次或一次手动读取,诊断或编辑XML数据;在这种情况下,可读性很重要,属性的一致和逻辑顺序对此有所帮助;
    • 您可能需要与某些(可能是错误地)关心订单的工具或服务进行通信;要求提供者更正其代码可能不是一种选择:尝试从政府机构那里提出要求,而用户以电子方式交付大量财务文件的截止日期越来越近了!

    看来Alain Pannetier的解决方案是正确的方法。

    另外,您可能想看看DecentXML。它使您可以完全控制XML的格式,即使它不兼容DOM。如果要修改一些手工编辑的XML而又不丢失格式,则特别有用。


    我有一个非常类似的问题。首先,我必须具有相同的属性。
    范例:

    1
    2
    <h50row a="1" xidx="1" c="1"></h50row>
    <h50row a="2" b="2" xidx="2"></h50row>

    必须成为

    1
    2
    <h50row xidx="1" a="1" c="1"></h50row>
    <h50row xidx="2" a="2" b="2"></h50row>

    我找到了一个正则表达式的解决方案:

    1
    2
    test ="<h50row a="1" xidx="1" c="1"></h50row>";
    test = test.replaceAll("(<h5.*row)(.*)(.xidx="\\w*")([^>]*)(>)","$1$3$2$4$5");

    希望你觉得这个有用