在解析XML文档时,很多人只关注元素节点和属性节点,却忽略了DOM树中的另一个成员:DocumentType节点。它对应XML文档开头的文档类型声明,也就是通常所说的DTD(Document Type Definition)。DOM规范专门为它定义了一个DocumentType接口,通过这个接口可以读取DTD的名称、内部子集、外部标识符以及实体和记号的定义信息。本文将完整梳理这个接口包含的内容,并配合代码示例说明如何在Java和浏览器环境中使用它。

DocumentType接口的继承关系与节点特征
DocumentType接口继承自Node接口,因此它具备普通DOM节点的基本能力,比如nodeType属性返回10(DOCUMENT_TYPE_NODE),nodeName属性返回DTD的名称。在DOM Level 2之后,它还继承自DocumentType所属的扩展体系,使得接口可以直接暴露DTD的详细结构。
需要注意的一点是,DocumentType节点在DOM树中的位置非常特殊:它必须出现在Document节点的直接子节点列表中,并且一个文档最多只能有一个DocumentType节点。可以通过document.getDoctype()方法获取它,如果文档没有DTD声明,该方法返回null。
另外,DocumentType节点是只读的,不能向它的entities或notations集合中添加或修改成员。DOM规范将其设计为对DTD信息的静态映射,这一点与Element、Text等可操作节点形成鲜明对比。这种只读设计的原因在于,修改DTD往往意味着整个文档的有效性规则都发生了变化,DOM核心模块并不负责重新校验。
DocumentType接口包含的核心属性详解
按照DOM规范的定义,DocumentType接口主要包含六个属性,每个属性都对应DTD声明中的一块信息,下面逐个说明。
第一个是name属性,它返回文档类型声明中根元素的名称。例如对于声明<!DOCTYPE catalog SYSTEM "catalog.dtd">,name属性的值就是字符串catalog。这个属性永远不会为null,是接口中最基础的信息。
第二和第三个是一对标识符:publicId和systemId。publicId返回外部DTD的公共标识符,也就是PUBLIC关键字后面的字符串;systemId返回系统标识符,即DTD文件的URI。如果DTD声明中没有对应部分,这两个属性分别返回null。对于内部子集形式的DTD,这两个属性都会是null。
第四个是internalSubset属性,它返回DTD内部子集的完整文本内容,包括包围它的方括号。如果DTD没有内部子集,则返回null。值得提醒的是,internalSubset是DOM Level 2才引入的,部分浏览器的早期实现不支持它。
最后两个是集合类型的属性:entities和notations。entities返回一个NamedNodeMap集合,包含DTD中声明的一般实体(ENTITY声明),每个成员都是Entity节点,可以通过getNamedItem方法按名称查找;notations同样返回NamedNodeMap,包含NOTATION声明的记号信息,每个成员是Notation节点。这两个集合都是只读的,且不包含参数实体。
在浏览器中使用JavaScript访问DTD信息
在现代浏览器中,使用DOMParser解析XML字符串后,可以通过doctype属性访问DocumentType节点。下面是一个完整的示例,演示如何读取各个属性的值。
var xmlText = '<?xml version="1.0" encoding="UTF-8"?>'
+ '<!DOCTYPE catalog SYSTEM "catalog.dtd" ['
+ '<!ENTITY copyright "Copyright ipipp.com">'
+ '<!NOTATION gif SYSTEM "image/gif">'
+ ']>'
+ '<catalog><book/></catalog>';
var parser = new DOMParser();
var doc = parser.parseFromString(xmlText, "application/xml");
var dt = doc.doctype;
console.log(dt.name); // catalog
console.log(dt.systemId); // catalog.dtd
console.log(dt.publicId); // null
console.log(dt.internalSubset); // 内部子集文本
// 遍历DTD中定义的实体
var entities = dt.entities;
for (var i = 0; i < entities.length; i++) {
console.log(entities.item(i).nodeName);
}
// 按名称查找特定实体
var ent = entities.getNamedItem("copyright");
console.log(ent != null ? "找到实体" : "未找到实体");
// 遍历记号声明
var notations = dt.notations;
for (var j = 0; j < notations.length; j++) {
console.log(notations.item(j).nodeName);
}
上面的代码展示了浏览器环境下的标准用法。需要说明的是,部分浏览器出于安全考虑,对内部子集中的实体解析做了限制,internalSubset在某些实现中可能返回null或空字符串,实际开发时应做好兼容性判断。
如果只需要判断文档是否带有DTD,直接检查doc.doctype是否为null即可;如果要校验文档是否符合DTD规范,则需要借助解析器的校验功能,DocumentType接口本身只负责提供信息,不做有效性验证。
在Java解析器中读取DocumentType
Java平台下处理XML的主力是JAXP接口,无论底层使用DOM解析器还是SAX转换成DOM树,都可以通过getDoctype方法拿到DocumentType对象。下面的示例使用最常见的方式完成遍历。
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import java.io.File;
public class DTDInfoReader {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("catalog.xml"));
DocumentType dt = doc.getDoctype();
if (dt == null) {
System.out.println("文档没有DTD声明");
return;
}
System.out.println("DTD名称: " + dt.getName());
System.out.println("系统标识符: " + dt.getSystemId());
System.out.println("公共标识符: " + dt.getPublicId());
System.out.println("内部子集: " + dt.getInternalSubset());
// 枚举实体声明
NamedNodeMap entities = dt.getEntities();
for (int i = 0; i < entities.getLength(); i++) {
Node n = entities.item(i);
System.out.println("实体: " + n.getNodeName());
}
// 枚记号声明
NamedNodeMap notations = dt.getNotations();
for (int i = 0; i < notations.getLength(); i++) {
Node n = notations.item(i);
System.out.println("记号: " + n.getNodeName());
}
}
}
在Java中使用时有一个常见坑:如果解析器没有加载外部DTD,systemId属性仍然可以正确返回,因为它来自文档声明本身而非DTD文件内容;但entities和notations集合只有在解析器实际读取了DTD的情况下才会被填充。默认配置下DocumentBuilderFactory不会加载外部DTD,需要时可以设置相关工厂属性来启用。
另一个值得注意的点是,JDK早期版本的getInternalSubset实现存在差异,某些版本会省略方括号,跨版本部署时建议不要依赖该属性的精确格式,只把它当作参考信息使用。
使用场景与注意事项
DocumentType接口的典型应用场景包括:文档处理工具需要根据DTD名称分发到不同的处理逻辑;内容管理系统在导入XML时记录原始DTD信息;以及需要统计项目中实体定义使用情况的静态分析工具。在这些场景下,它提供的信息比手工解析DOCTYPE字符串更可靠。
使用时还需留意几点。第一,entities集合中的Entity节点本身也有children属性,理论上包含实体的替换文本,但多数解析器出于安全和性能考虑返回空节点列表,不应依赖它获取实体内容。第二,notations集合在Schema取代DTD的现代项目中越来越少见,但处理遗留文档时仍然有用。第三,XHTML文档的<!DOCTYPE html>在HTML解析模式下没有外部标识符,systemId和publicId的行为与XML模式不同,处理HTML文档时要区分对待。
总体来看,DocumentType接口虽然属性不多,但完整覆盖了DTD声明的各个组成部分:名称、内外部标识、内部子集、实体集合和记号集合。掌握这些属性的语义和兼容性差异,在需要读取文档类型信息的场合就能得心应手,不必再通过字符串匹配去解析DOCTYPE声明,代码也会更加健壮和规范。
DocumentType接口XML DOMDTD修改时间:2026-09-01 15:26:49