1. 什么是 DTD
DTD(Document Type Definition,文档类型定义)是一种用于定义 XML 或 HTML 文档结构的规则集合。它规定了文档中可以出现哪些元素、元素的排列顺序、元素的属性以及元素之间的嵌套关系,是文档验证和结构化的重要基础。
简单来说,DTD 的作用包括:
- 定义文档中允许出现的元素和属性。
- 规定元素的嵌套顺序和出现次数。
- 为解析器提供验证依据,确保文档符合预期结构。
- 帮助不同系统之间共享和交换结构化数据。
DTD 既可以在文档内部声明,也可以作为外部文件被多个文档引用,是 XML 技术体系中最基础也最重要的组成部分之一。
2. DTD 的构建模块
DTD 的核心构建模块主要包括元素、属性、实体和 PCDATA 等。理解这些模块是掌握 DTD 的关键。
| 构建模块 | 说明 | 示例 |
|---|---|---|
| 元素 | 文档中的基本结构单元,对应 XML 标签 | <title>、<author>、<book> |
| 属性 | 元素的附加信息,通过属性列表声明 | id、lang、type |
| 实体 | 用于定义可复用的文本片段或特殊字符 | <、©、自定义实体 |
| PCDATA | 可解析字符数据,即元素内的文本内容 | 书名、作者名等文本 |
| CDATA | 字符数据,解析器不解析其中的内容 | 代码片段、特殊格式文本 |
下面逐一深入介绍这些构建模块。
3. 元素声明
元素是 DTD 中最核心的构建模块。通过<!ELEMENT>声明,可以定义元素的名称和内容模型。
3.1 基本元素声明
<!ELEMENT book (title, author, price)> <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT price (#PCDATA)>上面的声明表示:book元素必须依次包含title、author和price三个子元素,而这三个子元素的内容都是可解析的文本(PCDATA)。
3.2 元素内容模型
DTD 支持多种内容模型,用于控制元素的嵌套规则。
<!-- 空元素:不包含任何内容 --> <!ELEMENT br EMPTY> <!-- 仅包含文本内容 --> <!ELEMENT name (#PCDATA)> <!-- 仅包含子元素,不包含文本 --> <!ELEMENT catalog (book+)> <!-- 包含文本和子元素的混合内容 --> <!ELEMENT description (#PCDATA | highlight)*> <!-- 任意内容 --> <!ELEMENT note ANY>3.3 子元素出现次数
通过修饰符可以控制子元素出现的次数,这是 DTD 构建模块中非常实用的能力。
<!-- 逗号表示顺序:必须按顺序出现 --> <!ELEMENT book (title, author, price)> <!-- 竖线表示选择:只能出现其中一个 --> <!ELEMENT contact (phone | email)> <!-- 无修饰符:必须出现且只能出现一次 --> <!ELEMENT title (#PCDATA)> <!-- 问号:出现零次或一次 --> <!ELEMENT book (title, author?, price)> <!-- 星号:出现零次或多次 --> <!ELEMENT catalog (book*)> <!-- 加号:出现一次或多次 --> <!ELEMENT catalog (book+)>下表总结了各种修饰符的含义:
| 修饰符 | 含义 | 示例 |
|---|---|---|
| 无 | 必须出现且只能出现一次 | title |
| ? | 出现零次或一次 | author? |
| * | 出现零次或多次 | book* |
| + | 出现一次或多次 | book+ |
4. 属性声明
属性用于为元素提供附加信息。在 DTD 中,通过<!ATTLIST>声明属性列表。
4.1 基本属性声明
<!ELEMENT book (title, author, price)> <!ATTLIST book id ID #REQUIRED lang CDATA "zh-CN" type (paper | ebook) "paper" >上面的声明定义了book元素的三个属性:
id:类型为 ID,必须提供(#REQUIRED)。lang:类型为 CDATA,默认值为 "zh-CN"。type:枚举类型,可选值为 paper 或 ebook,默认值为 paper。
4.2 属性类型
DTD 支持多种属性类型,每种类型都有特定的用途。
<!ATTLIST book id ID #REQUIRED ref IDREF #IMPLIED refs IDREFS #IMPLIED title CDATA #IMPLIED lang NMTOKEN #IMPLIED langs NMTOKENS #IMPLIED type (paper | ebook) "paper" note ENTITY #IMPLIED notes ENTITIES #IMPLIED flag NOTATION #IMPLIED >常用属性类型说明:
| 类型 | 说明 | 示例 |
|---|---|---|
| CDATA | 字符数据,最常用的类型 | lang="zh-CN" |
| ID | 文档内唯一的标识符 | id="book-001" |
| IDREF | 引用文档中另一个元素的 ID | ref="book-001" |
| IDREFS | 多个 ID 引用,用空格分隔 | refs="book-001 book-002" |
| NMTOKEN | 名称标记,类似变量名 | lang="zh-CN" |
| NMTOKENS | 多个名称标记 | langs="zh-CN en-US" |
| 枚举 | 从给定列表中选一个值 | type="paper" |
4.3 属性默认值
属性默认值决定了属性是否必须出现以及缺省时的处理方式。
<!ATTLIST book id ID #REQUIRED lang CDATA #IMPLIED type CDATA "paper" fixed CDATA #FIXED "v1.0" >#REQUIRED:属性必须出现,否则文档无效。#IMPLIED:属性可以省略,解析器不提供默认值。默认值:属性省略时使用指定的默认值。#FIXED:属性值固定,文档中必须使用该值。
5. 实体声明
实体是 DTD 中用于定义可复用文本片段或特殊字符的构建模块。实体分为内部实体和外部实体。
5.1 内部实体
内部实体在 DTD 内部直接定义,使用<!ENTITY>声明。
<!ENTITY author "张三"> <!ENTITY company "示例科技有限公司"> <!ENTITY copyright "版权所有 © 2026">在 XML 文档中引用实体:
<book> <title>DTD 实战指南</title> <author>&author;</author> <publisher>&company;</publisher> <copyright>©right;</copyright> </book>解析后,&author;会被替换为 "张三",&company;会被替换为 "示例科技有限公司"。
5.2 外部实体
外部实体引用外部文件中的内容,使用SYSTEM或PUBLIC关键字。
<!ENTITY logo SYSTEM "logo.png"> <!ENTITY footer SYSTEM "footer.xml"> <!ENTITY style PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">5.3 预定义实体
XML 提供了一些预定义实体,用于表示特殊字符,这些实体在 DTD 中可以直接使用。
| 实体 | 字符 | 说明 |
|---|---|---|
| < | < | 小于号 |
| > | > | 大于号 |
| & | & | 和号 |
| " | " | 双引号 |
| ' | ' | 单引号 |
6. PCDATA 与 CDATA
PCDATA 和 CDATA 是 DTD 中两种重要的数据构建模块,理解它们的区别对正确设计 DTD 至关重要。
6.1 PCDATA
PCDATA(Parsed Character Data,可解析字符数据)是元素内会被解析器解析的文本内容。解析器会识别其中的实体引用和标记。
<!ELEMENT title (#PCDATA)>在文档中使用:
<title>DTD & XML 实战</title>解析后,&会被解析为&,最终文本为 "DTD & XML 实战"。
6.2 CDATA
CDATA(Character Data,字符数据)是解析器不解析的文本内容,常用于包含大量特殊字符的代码片段。
<!ELEMENT code (#PCDATA)>在文档中使用 CDATA 段:
<code><![CDATA[ if (a < b && c > d) { console.log("条件成立"); } ]]></code>CDATA 段中的<、>和&都会被原样保留,无需转义。
7. 完整 DTD 实例
下面通过一个完整的图书目录 DTD 实例,综合运用前面介绍的所有构建模块。
7.1 定义 DTD 文件
<!-- books.dtd --> <!ELEMENT catalog (book+)> <!ELEMENT book (title, author+, publisher?, price, description?)> <!ATTLIST book id ID #REQUIRED lang CDATA "zh-CN" type (paper | ebook) "paper" > <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT publisher (#PCDATA)> <!ELEMENT price (#PCDATA)> <!ELEMENT description (#PCDATA | highlight)*> <!ELEMENT highlight (#PCDATA)> <!ATTLIST price currency CDATA "CNY" > <!ENTITY company "示例出版社"> <!ENTITY copyright "版权所有 © 2026 &company;">7.2 编写符合 DTD 的 XML 文档
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE catalog SYSTEM "books.dtd"> <catalog> <book id="book-001" lang="zh-CN" type="paper"> <title>DTD 构建模块实战</title> <author>张三</author> <author>李四</author> <publisher>&company;</publisher> <price currency="CNY">59.00</price> <description> 本书系统讲解 DTD 的 <highlight>元素、属性、实体</highlight> 等核心构建模块。 </description> </book> <book id="book-002" lang="en-US" type="ebook"> <title>XML Schema Essentials</title> <author>Jane Smith</author> <price currency="USD">29.99</price> </book> </catalog>7.3 验证文档
可以使用支持 DTD 的解析器验证文档是否符合定义。下面是一个使用 Python 的验证示例:
import xml.etree.ElementTree as ET try: tree = ET.parse('catalog.xml') root = tree.getroot() print('XML 文档解析成功') for book in root.findall('book'): print(f"图书 ID: {book.get('id')}") title = book.find('title').text print(f"书名: {title}") except ET.ParseError as e: print(f'XML 解析失败: {e}')运行结果:
XML 文档解析成功 图书 ID: book-001 书名: DTD 构建模块实战 图书 ID: book-002 书名: XML Schema Essentials