Java XML DOM:id属性如何特殊?
Document
类的javadoc在getElementById
下有以下注释。
注意:除非如此定义,否则名称为“ID”或“id”的属性不是ID类型
所以,我在DOM中读了一个XHTML文档(使用Xerces 2.9.1)。
该文档中有一个简单的旧
。
我调用getElementById("fribble")
,它返回null。
我使用XPath来获取“// * [id =’fribble’]”,一切都很好。
那么,问题是,是什么导致DocumentBuilder
实际将ID属性标记为“如此定义?”
要使getElementById()
调用起作用, Document
必须知道其节点的类型,并且目标节点必须是XML ID类型才能找到它的方法。 它通过关联的模式了解其元素的类型。 如果未设置模式,或者未将id
属性声明为XML ID类型,则getElementById()
将永远不会找到它。
我的猜测是你的文档不知道p
元素的id
属性属于XML ID类型(是吗?)。 您可以使用getChildNodes()
和其他DOM遍历函数导航到DOM中的节点,并尝试在id属性上调用Attr.isId()
来确定。
从getElementById javadoc:
期望DOM实现使用属性Attr.isId来确定属性是否为ID类型。
注意:除非如此定义,否则名称为“ID”或“id”的属性不是ID类型。
如果您使用DocumentBuilder
将XML解析为DOM,请确保在调用newDocumentBuilder()之前在DocumentBuilderFactory上调用setSchema(schema)
),以确保您从工厂获得的构建器知道元素类型。
这些属性是特殊的,因为它们的类型而不是因为它们的名称 。
XML中的ID
虽然很容易将属性视为name="value"
,并且值是一个简单的字符串,但这不是完整的故事 – 还有一个与属性关联的属性类型 。
当涉及XML Schema时,这很容易理解,因为XML Schema支持XML元素和XML属性的数据类型。 XML属性被定义为简单类型(例如xs:string,xs:integer,xs:dateTime,xs:anyURI)。 这里讨论的属性是使用xs:ID
内置数据类型定义的(请参阅XML Schema Part 2:数据类型的第3.3.8节 )。
... ...
尽管DTD不支持XML Schema中的rich数据类型,但它确实支持一组有限的属性类型 (在XML 1.0的3.3.1节中定义)。 这里讨论的属性是使用ID
的属性类型定义的。
使用上述XML Schema或DTD,以下元素将由ID值“xyz”标识。
在不知道XML Schema或DTD的情况下,无法分辨什么是ID,什么不是:
- 名称为“id”的属性不一定具有ID的属性类型 ; 和
- 名称不是“id”的属性可能具有ID的属性类型 !
为了改善这种情况,随后发明了xml:id
(参见xml:id W3C Recommendation )。 这是一个始终具有相同前缀和名称的属性,旨在将其视为属性类型为ID的属性 。 但是,它是否依赖于使用的解析器是否知道xml:id
。 由于许多解析器最初是在定义xml:id
之前编写的,因此可能不支持它。
Java中的ID
在Java中, getElementById()
通过查找类型 ID的属性来查找元素,而不是查找名称为“id”的属性。
在上面的示例中, getElementById("xyz")
将返回该foo
元素,即使其上的属性名称不是“id”(假设DOM知道bar
具有ID的属性类型 )。
那么DOM如何知道属性具有哪种属性类型 ? 有三种方法:
- 为解析器提供XML Schema( 示例 )
- 为解析器提供DTD
- 向DOM明确指出它被视为ID的属性类型。
第三个选项是使用org.w3c.dom.Element
类中的setIdAttribute()
或setIdAttributeNS()
或setIdAttributeNode()
方法完成的。
Document doc; Element fooElem; doc = ...; // load XML document instance fooElem = ...; // locate the element node "foo" in doc fooElem.setIdAttribute("bar", true); // without this, 'found' would be null Element found = doc.getElementById("xyz");
必须为每个具有这些类型属性之一的元素节点执行此操作。 没有简单的内置方法可以使具有给定名称(例如“id”)的所有属性都具有属性类型 ID。
第三种方法仅在调用getElementById()
的代码与创建DOM的代码分开的情况下才有用。 如果它是相同的代码,它已经找到了设置ID属性的元素,因此不太可能需要调用getElementById()
。
另外,请注意这些方法不在原始DOM规范中。 getElementById
是在DOM级别2中引入的。
XPath中的ID
原始问题中的XPath给出了结果,因为它只匹配属性名称 。
要匹配属性类型 ID值,需要使用XPath id
函数(它是XPath 1.0中的节点集函数之一):
id("xyz")
如果已经使用过,那么XPath会得到与getElementById()
相同的结果(即找不到匹配项)。
XML中的ID继续
应强调ID的两个重要特征。
首先, 属性类型 ID的所有属性的值必须对整个XML文档是唯一的 。 在以下示例中,如果personId
和companyId
都具有ID的属性类型 ,则添加另一个companyId为id24601的公司将是错误的,因为它将是现有ID值的副本。 即使属性名称不同,重要的是属性类型 。
... ... ... ...
其次, 属性是在元素而不是整个XML文档上定义的。 因此,在不同元素上具有相同属性名称的属性可能具有不同的属性类型属性。 在下面的示例XML文档中,如果只有alpha/@bar
的属性类型为ID(并且没有其他属性),则getElementById("xyz")
将返回一个元素,但getElementById("abc")
将不返回(因为beta/@bar
不属于属性类型 ID)。 此外,属性gamma/@bar
与alpha/@bar
具有相同的值也不是错误,因为XML不是属性类型 ID,所以在XML文档中ID的唯一性中不考虑该值。
ID属性不是名称为“ID”的属性,它是由DTD或模式声明为ID属性的属性。 例如,html 4 DTD描述了它:
相应的xpath表达式实际上是id('fribble')
,它应返回与getElementById
相同的结果。 为此,与文档关联的dtd或模式必须将属性声明为ID类型。
如果您控制查询的xml,您还可以尝试根据http://www.w3.org/TR/xml-id/将属性重命名为xml:id
。
以下将允许您通过id获取元素:
public static Element getElementById(Element rootElement, String id) { try { String path = String.format("//*[@id = '%1$s' or @Id = '%1$s' or @ID = '%1$s' or @iD = '%1$s' ]", id); XPath xPath = XPathFactory.newInstance().newXPath(); NodeList nodes = (NodeList)xPath.evaluate(path, rootElement, XPathConstants.NODESET); return (Element) nodes.item(0); } catch (Exception e) { return null; } }