Java XML DOM:id 属性有啥特别之处?

Posted

技术标签:

【中文标题】Java XML DOM:id 属性有啥特别之处?【英文标题】:Java XML DOM: how are id Attributes special?Java XML DOM:id 属性有什么特别之处? 【发布时间】:2011-03-26 07:18:21 【问题描述】:

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

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

所以,我将 Xhtml 文档读入 DOM(使用 Xerces 2.9.1)。

文档中有一个普通的旧 <p id='fribble'>

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

我使用 XPath 获取“//*[id='fribble']”,一切正常。

那么,问题是,是什么导致DocumentBuilder 实际上将 ID 属性标记为“如此定义”?

【问题讨论】:

【参考方案1】:

这些属性之所以特殊是因为它们的类型,而不是因为它们的名称

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 中的丰富数据类型,但它确实支持有限的一组属性类型(在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 为 attribute type 的属性。但是,它是否会取决于正在使用的解析器是否知道xml:id。由于许多解析器最初是在定义 xml:id 之前编写的,因此可能不受支持。

Java 中的 ID

在 Java 中,getElementById() 通过查找 type ID 的属性而不是 name 为“id”的属性来查找元素。

在上面的例子中,getElementById("xyz") 将返回 foo 元素,即使它上面的属性名称不是“id”(假设 DOM 知道 bar 具有 属性类型 的 ID)。

那么 DOM 是如何知道一个属性有什么属性类型的呢?有三种方式:

    向解析器提供 XML Schema (example) 向解析器提供 DTD 向 DOM 明确指出它被视为 ID 的属性类型。

第三个选项是使用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 是在 DOM level 2 中引入的。

XPath 中的 ID

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

要匹配属性类型 ID 值,需要使用 XPath id 函数(它是 Node Set Functions from XPath 1.0 之一):

id("xyz")

如果已使用,XPath 将给出与 getElementById() 相同的结果(即未找到匹配项)。

XML 中的 ID(续)

应突出显示 ID 的两个重要特征。

首先,属性类型ID的所有属性的值必须对整个XML文档是唯一的。在以下示例中,如果 personIdcompanyIdattribute type 为 ID,则添加另一个 ID 为 id24601 的 companyId 的公司将是错误的,因为它将与现有的 ID 值。即使属性名称不同,重要的是 属性类型

<test1>
 <person personId="id24600">...</person>
 <person personId="id24601">...</person>
 <company companyId="id12345">...</company>
 <company companyId="id12346">...</company>
</test1>

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

<test2>
  <alpha bar="xyz"/>
  <beta bar="abc"/>
  <gamma bar="xyz"/>
</test2>

【讨论】:

不幸的是,模式示例的链接现在已经失效了。【参考方案2】:

要使getElementById() 调用起作用,Document 必须知道其节点的类型,并且目标节点必须是 XML ID 类型,以便方法找到它。它通过关联的模式了解其元素的类型。如果未设置架构,或未将 id 属性声明为 XML ID 类型,getElementById() 将永远找不到它。

我的猜测是您的文档不知道p 元素的id 属性属于XML ID 类型(是吗?)。您可以使用getChildNodes() 和其他DOM 遍历函数导航到DOM 中的节点,并尝试在id 属性上调用Attr.isId() 来确定。

来自getElementByIdjavadoc:

DOM 实现预计将 使用属性 Attr.isId 来 确定属性是否属于类型 身份证。

注意:名称为“ID”的属性或 "id" 不是 ID 类型,除非是这样 已定义。

如果您使用DocumentBuilder 将您的 XML 解析为 DOM,请务必在调用 newDocumentBuilder() 之前在 DocumentBuilderFactory 上调用 setSchema(schema),以确保您从工厂获得的构建器知道元素类型.

【讨论】:

【参考方案3】:

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

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

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

以上是关于Java XML DOM:id 属性有啥特别之处?的主要内容,如果未能解决你的问题,请参考以下文章

java的xml的解析方式有啥,他们的解析流程是怎么样的,有啥区别

骨干视图中的 tagName、id 和 className 属性有啥用?虽然我们可以使用 el 访问 dom 元素

结构体有啥特别之处?

crontab 中的 % 有啥特别之处?

Generic.xaml 有啥特别之处?

使用 StringBuilder 而不是 StringBuffer 有啥特别之处 [重复]