Java XML DOM:id属性如何特殊?

时间:2010-08-06 11:45:35

标签: java dom

Document类的javadoc在getElementById下有以下注释。

  

注意:名称为“ID”或“id”的属性不是ID类型,除非如此定义

所以,我在DOM中读了一个XHTML文档(使用Xerces 2.9.1)。

该文档中有一个简单的<p id='fribble'>

我调用getElementById("fribble"),然后返回null。

我使用XPath来获取“// * [id ='fribble']”,一切都很好。

所以,问题是,是什么原因导致DocumentBuilder实际将ID属性标记为'如此定义?'

5 个答案:

答案 0 :(得分:50)

这些属性因其类型而特殊,而不是因为其名称

XML中的ID

虽然很容易将属性视为name="value",并且值是一个简单的字符串,但这不是完整的故事 - 还有一个与属性关联的属性类型

当涉及XML Schema时,这很容易理解,因为XML Schema支持XML元素和XML属性的数据类型。 XML属性被定义为简单类型(例如xs:string,xs:integer,xs:dateTime,xs:anyURI)。此处讨论的属性使用xs:ID内置数据类型定义(请参阅section 3.3.8 of the XML Schema Part 2: Datatypes)。

<xs:element name="foo">
  <xs:complexType>
   ...
   <xs:attribute name="bar" type="xs:ID"/>
   ...
  </xs:complexType>
</xs:element>

虽然DTD不支持XML Schema中的rich数据类型,但它支持一组有限的属性类型(在section 3.3.1 of XML 1.0中定义)。此处讨论的属性是使用ID属性类型定义的。

<!ATTLIST foo  bar ID #IMPLIED>

使用上述XML Schema或DTD,以下元素将由ID值“xyz”标识。

<foo bar="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如何知道属性的属性类型?有三种方式:

  1. 为解析器(example
  2. 提供XML架构
  3. 为解析器提供DTD
  4. 向DOM明确指出它被视为ID的属性类型。
  5. 第三个选项是使用org.w3c.dom.Element class上的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中引入了id

    XPath中的ID

    原始问题中的XPath给出了结果,因为它只匹配属性 name

    要匹配属性类型 ID值,需要使用XPath id("xyz") 函数(它是DOM level 2之一):

    getElementById()

    如果已经使用过,那么XPath会得到与personId相同的结果(即找不到匹配项)。

    XML中的ID

    应强调ID的两个重要特征。

    首先,属性类型 ID的所有属性的值必须对整个XML文档唯一。在以下示例中,如果companyIdcompanyId都具有ID的属性类型,则添加另一个<test1> <person personId="id24600">...</person> <person personId="id24601">...</person> <company companyId="id12345">...</company> <company companyId="id12346">...</company> </test1> 为id24601的公司将会出错,因为它将是现有ID值的副本。即使属性名称不同,重要的是属性类型

    alpha/@bar

    其次,属性是在元素上定义的,而不是整个XML文档。因此,在不同元素上具有相同属性名称的属性可能具有不同的属性类型属性。在以下示例XML文档中,如果只有getElementById("xyz")具有ID的属性类型(并且没有其他属性),getElementById("abc")将返回一个元素,但beta/@bar不会(因为gamma/@bar不是属性类型 ID)。此外,属性alpha/@bar<test2> <alpha bar="xyz"/> <beta bar="abc"/> <gamma bar="xyz"/> </test2> 具有相同的值也不是错误,因为它不是,所以在XML文档中ID的唯一性中不考虑该值属性类型 ID。

    {{1}}

答案 1 :(得分:17)

对于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),以确保您从工厂获得的构建器是了解元素类型。

答案 2 :(得分:4)

ID属性不是名称为“ID”的属性,它是由DTD或模式声明为ID属性的属性。例如,html 4 DTD描述了它:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

答案 3 :(得分:3)

相应的xpath表达式实际上是id('fribble'),它应返回与getElementById相同的结果。为此,与文档关联的dtd或模式必须将属性声明为ID类型。

如果您控制查询的xml,您还可以尝试按http://www.w3.org/TR/xml-id/将属性重命名为xml:id

答案 4 :(得分:1)

以下内容允许您通过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;
    }
}