DOM处理后的XML属性顺序

时间:2009-04-07 15:42:15

标签: java xml dom

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

但是我确实需要保留订单。我想知道Java是否有任何可能性来保持通过DOM API处理的XML文件的原始属性顺序,或任何强制命令的方式(可能通过使用替代序列化API来设置此一种财产)。在我的情况下,处理减少以改变具有一堆属性的相同元素序列的某些属性(不是全部)的值,并且可能插入更多元素。

有没有“简单”的方法,还是我必须定义自己的XSLT转换样式表来指定输出并更改整个输入XML文件?

更新我必须感谢您的所有答案。答案似乎比我预期的更明显。我从未关注属性顺序,因为我之前从未需要它。

要求属性顺序的主要原因是生成的XML文件看起来不同。目标是一个包含数百个警报的配置文件(每个警报由一组属性定义)。这个文件通常随着时间的推移几乎没有什么修改,但是保持它的顺序是很方便的,因为当我们需要修改它的东西时,它是手工编辑的。有时,一些项目需要对此文件进行轻微修改,例如将其中一个属性设置为客户特定代码。

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

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

11 个答案:

答案 0 :(得分:24)

请参阅XML建议的第3.1节。它说,“请注意,start-tag或empty-element标记中的属性规范顺序并不重要。”

如果一个软件需要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建议,则可能在其他方面违反了该建议。如果在以错误的顺序提供属性时它会中断,如果使用单引号分隔属性,或者属性值包含字符实体,或者XML建议说XML文档中的任何其他内容,它可能也会中断可以做到这个软件的作者可能没有考虑过。

答案 1 :(得分:24)

很抱歉地说,但答案比“不能你不能”或“你为什么首先需要这样做?”更加微妙。

简短的回答是“DOM不会允许你这样做,但SAX会”。

这是因为DOM不关心属性顺序,因为就标准而言它是没有意义的,并且当XSL获得输入流时,信息已经丢失。 大多数XSL引擎实际上会优雅地保留输入流属性顺序(例如, Xalan-C(除了一种情况)或Xalan-J(总是))。特别是如果您使用<xsl:copy*>

据我所知,属性顺序未保留的情况是。    - 如果输入流是DOM    - Xalan-C:如果你按字面插入结果树标签(例如<elem att1={@att1} .../>

以下是SAX的一个示例,用于记录(也禁止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的人都知道你通常希望确保“新”结果树与“旧”结果树相似或相同。当结果树大约有一百万行时,XML diff工具证明太笨重了...... 在这些情况下,保留属性顺序非常有帮助。

希望这会有所帮助; - )

答案 2 :(得分:9)

XML规范化导致一致的属性排序,主要是为了允许人们检查某些或所有XML的签名,尽管还有其他潜在的用途。这可能适合您的目的。

答案 3 :(得分:8)

不可能过分强调罗伯特罗斯尼刚刚说的话,但我会尝试。 ; - )

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

XML必须是我们拥有的最重要的标准之一。它是像SOAP这样的“旧网”东西的基础,还有像RSS和Atom这样的“web 2.0”。这是因为XML能够在不同平台之间进行互操作的明确标准。

如果我们一点一点地放弃XML,我们将陷入一种情况,即XML的生产者将无法假设XML的使用者能够使用他们的内容。这将对该行业产生灾难性影响。

对于根据标准编写不处理XML的代码的任何人,我们应该非常有力地推迟。我理解,在这些经济时代,人们不愿意冒犯“不”来冒犯客户和商业伙伴。但在这种情况下,我认为这是值得的。如果我们不得不为每个业务合作伙伴手工制作XML,那么我们的财务状况会更差。

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

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:1)

罗伯特·罗斯尼说得很好:如果你依赖于属性的排序,你就不是真正处理XML,而是看起来像XML。

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

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

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

    这只是简单无效的XML; DOM处理器可能会丢弃除了其中一个值之外的所有值 - 如果它根本处理文档的话。而不是这个,你想要使用子元素:

    <foo>
        <myChild="a"/>
        <myChild="b"/>
        <myChild="c"/>
    </foo>
    
  2. 您假设某种区别适用于首先出现的属性。通过其他属性或子元素使其显式化。例如:

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

答案 6 :(得分:1)

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

看看这个article/discussion。您可以看到如何按顺序读取起始元素的属性。

答案 7 :(得分:0)

我想我可以找到关于属性顺序的一些有效理由:

  • 您可能希望人类不得不手动阅读,诊断或编辑XML数据;可读性在该实例中很重要,并且属性的一致和逻辑顺序有助于此;
  • 您可能需要与某些工具或服务(非常错误地)关心订单进行沟通;要求提供商更正其代码可能不是一个选择:尝试从政府机构那里询问,而您的用户在电子方式提交一堆财务文件的截止日期越来越近了!

似乎Alain Pannetier's solution是要走的路。

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

答案 8 :(得分:0)

你仍然可以使用标准的DOM和Transformation API,使用像我描述的那样快速而肮脏的解决方案:

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

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

答案 9 :(得分:0)

有点作品......

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

答案 10 :(得分:-1)

我有一个非常相似的问题。我需要首先拥有相同的属性。 示例:

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

必须成为

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

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

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

希望你能找到这个有用的