DOM处理后的XML属性的顺序

在通过标准DOM处理XML时,序列化后的属性顺序不能保证。 最后,我使用标准的Java XML Transform API来序列化输出。

不过我确实需要保留一个命令。 我想知道在Java中是否存在任何可能性来保持通过DOM API处理的XML文件的属性的原始顺序,或者以任何方式强制执行命令(也许通过使用替代的序列化API来让您设置类财产)。 在我的情况下,处理可以减less使用一堆属性来改变一系列相同元素的一些属性(不是全部)的值,也可以插入更多的元素。

有没有“简单”的方法,或者我必须定义我自己的XSLT转换样式表来指定输出和改变整个inputXML文件?

更新我必须感谢你的答案。 现在的答案似乎比我预料的更为明显。 我从来没有注意到属性顺序,因为我以前从来没有需要它。

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

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

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

很抱歉,但答案比“不可以”或“你为什么需要这样做呢?”更微妙。

简短的答案是“DOM不会让你这样做,但SAX会”。

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

就我所知,属性顺序不被保留的情况是。 – 如果inputstream是一个DOM – Xalan-C:如果直接插入结果树标签(例如<elem att1={@att1} .../>

这里是SAX的一个例子,logging(抑制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 ); 

我还想指出,在许多反对者的意图下,有些情况下属性顺序很重要。

回归testing是一个明显的例子。 无论谁被调用来优化不太好的XSL,都知道你通常要确保“新”结果树与“旧”结果树类似或相同。 而当结果树大约一百万行时,XML diff工具被certificate太笨拙了……在这种情况下,保留属性顺序是非常有帮助的。

希望这可以帮助 ;-)

查看XMLbuild议的第3.1节。 它说:“请注意,开始标签或空白标签中属性规格的顺序并不重要。”

如果一个软件需要一个XML元素的属性以特定的顺序出现,那么这个软件并不处理XML,它处理的文本看起来很像XML。 它需要被修复。

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

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

只是发现XSLT处理器发出这个:

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

因为处理器正在使用的DOM按标签名称的字母顺序sorting属性。 (这是常见的,但不是XML DOM中的通用行为。)

但我想强调一下。 如果一个软件在一个方面违反了XML的build议,可能在其他方面违反了它。 如果按照错误的顺序提供属性,如果它以错误的顺序提供属性,那么如果用单引号分隔属性,或属性值包含字符实体,或者XMLbuild议指出XML文档中有十几种其他属性可以做这个软件的作者大概没有想过。

XML规范化导致了一致的属性sorting,主要是为了允许在一些或全部XML上检查签名,尽pipe还有其他潜在的用途。 这可能适合你的目的。

过分强调罗伯特·罗斯尼所说的话是不可能的,但我会尽力的。 😉

国际标准的好处是,当大家跟随他们时,生活就是美好的。 我们所有的软件都和平相处。

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

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

对于那些按照标准编写不处理XML的代码的人,我们应该强有力地推回去。 我明白在这个经济时代,不愿冒犯客户和商业伙伴说“不”。 但在这种情况下,我认为这是值得的。 如果我们必须为每个业务合作伙伴手工制作XML,那么我们的财务状况会更糟。

所以,不要“启用”不懂XML的公司。 给他们发送标准,并突出显示相应的行。 他们需要停止思考,XML只是带尖括号的文本。 它只是不像文字和尖括号一样。

这不是有这个借口。 即使是最小的embedded式设备也可以具有全function的XMLparsing器实现。 我还没有听到一个很好的理由不能parsing标准的XML,即使一个人不能提供一个function齐全的DOM实现。

你真的不应该保持任何秩序。 据我所知,在validationXML文档时,没有模式考虑属性顺序。 这听起来像是另一端处理XML的东西不是使用合适的DOM来parsing结果。

我想一个select是手动build立文件使用stringbuild设,但我强烈build议不要这样做。

罗伯特·罗斯尼(Robert Rossney)说得很好:如果你依赖于属性的sorting,那么你并不是真正处理XML,而是看起来像XML。

我可以想到至less有两个原因,你可能会关心属性sorting。 也许还有其他的,但至less对于这两个我可以build议的替代scheme:

  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" /> 

我有同样的确切问题。 我想修改XML属性,但想要保持秩序,因为差异。 我使用StAX来实现这一点。 您必须使用XMLStreamReader和XMLStreamWriter(基于光标的解决scheme)。 当您获得START_ELEMENT事件types时,光标将保留属性的索引。 因此,您可以进行适当的修改并将其写入输出文件“按顺序”。

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

你仍然可以使用标准的DOM和转换API来做到这一点,通过使用像我所描述的一个快速和肮脏的解决scheme:

我们知道转换API解决scheme按字母顺序排列属性。 您可以在属性名称前添加一些易于剥离的string,以便按照您要的顺序输出。 在大多数情况下,简单的前缀“a_”“b_”等就足够了,并且可以使用单线程正则expression式从输出xml中轻松地除去。

如果您正在加载xml并重新保存并希望保留属性顺序,则可以使用相同的原则,方法是首先修改inputxml文本中的属性名称,然后将其parsing为Document对象。 再次,基于xml的文本处理进行修改。 这可能是棘手的,但可以通过检测元素及其属性string,再次使用正则expression式来完成。 请注意这是一个肮脏的解决scheme。 当你自己parsingXML时,有很多缺陷,即使是像这样简单的东西,所以要小心如果你决定实现这个。

那种作品…

 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) - eg 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")); } } } 

在我的简单testing中,它打印:

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

我想我可以find一些关于属性顺序的正当理由:

  • 您可能希望人们不得不手动读取,诊断或编辑XML数据; 在这种情况下,可读性将是重要的,并且一致的和合乎逻辑的属性sorting有助于这一点。
  • 您可能需要与某些工具或服务进行交stream,这些工具或服务(错误地)关心订单; 要求提供者纠正其代码可能不是一个select:试图从政府机构问,而你的用户的电子交付一堆财政文件的最后期限越来越近!

Alain Pannetier的解决scheme似乎是要走的路。

另外,你可能想看看DecentXML ; 它使您可以完全控制XML是如何格式化的,尽pipe它不是DOM兼容的。 如果您想要修改某些手动编辑的XML而不丢失格式,则特别有用。

我有一个相当类似的问题。 我需要始终具有相同的属性。 例如:

 <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> 

我find了一个正则expression式的解决scheme:

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

希望你find这个有用的