1. 什么是 DTD 实体
DTD(Document Type Definition,文档类型定义)实体是 XML 中用于定义可复用内容片段的一种机制。实体可以理解为一种"宏"或"占位符",通过预先定义一段内容,在文档中引用时会被替换为实际的内容。合理使用实体可以显著减少重复代码,提高文档的可维护性。
DTD 实体主要分为两大类:内部实体和外部实体。内部实体在 DTD 内部直接定义内容,而外部实体则引用外部文件或资源。此外,还有参数实体,它专门用于 DTD 内部,帮助构建更灵活的 DTD 结构。
2. 实体的基本语法
在 DTD 中,实体通过<!ENTITY>声明来定义。其基本语法如下:
<!ENTITY 实体名称 "实体内容">在 XML 文档中,通过&实体名称;来引用已定义的实体。下面是一个最简单的内部实体示例:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 公司 [ <!ENTITY 公司名 "北京某某科技有限公司"> <!ENTITY 地址 "北京市海淀区中关村大街 1 号"> ]> <公司> <名称>&公司名;</名称> <地址>&地址;</地址> </公司>当 XML 解析器处理上述文档时,&公司名;会被替换为"北京某某科技有限公司",&地址;会被替换为"北京市海淀区中关村大街 1 号"。这样,当公司信息需要变更时,只需修改 DTD 中的实体定义,而不必逐一修改文档中的每个引用位置。
3. 内部实体
内部实体是最常用的实体类型,其内容直接写在 DTD 声明中。内部实体可以包含文本、特殊字符的转义形式,甚至可以引用其他实体。
3.1 基本文本实体
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 产品 [ <!ENTITY 产品名 "智能手表 Pro"> <!ENTITY 版本 "v2.0"> <!ENTITY 价格 "¥1999"> ]> <产品> <名称>&产品名;</名称> <版本号>&版本;</版本号> <售价>&价格;</售价> </产品>3.2 包含特殊字符的实体
当实体内容包含 XML 特殊字符(如<、>、&)时,必须使用对应的实体引用进行转义:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 说明 [ <!ENTITY 警告 "使用前请阅读 <说明书>,并确保 <电源> 已连接"> ]> <说明> <内容>&警告;</内容> </说明>解析后,&警告;会被替换为:使用前请阅读 <说明书>,并确保 <电源> 已连接。
3.3 实体嵌套引用
实体可以引用其他已定义的实体,形成嵌套关系:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 公司信息 [ <!ENTITY 公司名 "某某集团"> <!ENTITY 子公司 "某某科技子公司"> <!ENTITY 完整名称 "&公司名;旗下&子公司;"> ]> <公司信息> <名称>&完整名称;</名称> </公司信息>解析后,&完整名称;会被替换为"某某集团旗下某某科技子公司"。需要注意的是,实体引用不能形成循环依赖,否则会导致解析错误。
4. 外部实体
外部实体通过SYSTEM关键字引用外部文件或资源。外部实体又分为外部文本实体和外部二进制实体。
4.1 外部文本实体
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 报告 [ <!ENTITY 引言 SYSTEM "intro.txt"> <!ENTITY 结论 SYSTEM "conclusion.txt"> ]> <报告> <前言>&引言;</前言> <总结>&结论;</总结> </报告>在上述示例中,intro.txt和conclusion.txt是外部文本文件,解析器会读取这些文件的内容并替换到对应的实体引用位置。
4.2 外部二进制实体
外部二进制实体用于引用非文本资源,如图片、音频等。二进制实体不能直接在 XML 内容中引用,通常用于 DTD 属性声明中:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 产品 [ <!ENTITY 产品图片 SYSTEM "product.jpg" NDATA JPEG> <!NOTATION JPEG SYSTEM "image/jpeg"> <!ELEMENT 产品 EMPTY> <!ATTLIST 产品 图片 ENTITY #REQUIRED > ]> <产品 图片="产品图片"/>这里通过NDATA关键字指定了二进制数据的类型,并使用<!NOTATION>声明了对应的 MIME 类型。
5. 参数实体
参数实体是 DTD 内部使用的实体,用于提高 DTD 的可复用性和可维护性。参数实体的定义和引用都发生在 DTD 内部,使用%符号进行标识。
5.1 参数实体的定义与引用
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 图书 [ <!ENTITY % 通用属性 "作者 CDATA #IMPLIED 出版社 CDATA #IMPLIED"> <!ELEMENT 图书 (书名, 简介)> <!ATTLIST 图书 %通用属性;> <!ELEMENT 书名 (#PCDATA)> <!ELEMENT 简介 (#PCDATA)> ]> <图书 作者="张三" 出版社="清华大学出版社"> <书名>XML 实战指南</书名> <简介>本书系统讲解 XML 核心技术。</简介> </图书>在上述示例中,参数实体%通用属性;在<!ATTLIST>声明中被展开为"作者 CDATA #IMPLIED 出版社 CDATA #IMPLIED"。
5.2 参数实体复用 DTD 片段
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 订单 [ <!ENTITY % 地址元素 "<!ELEMENT 地址 (街道, 城市, 邮编)>"> %地址元素; <!ELEMENT 街道 (#PCDATA)> <!ELEMENT 城市 (#PCDATA)> <!ELEMENT 邮编 (#PCDATA)> <!ELEMENT 订单 (收货地址, 发货地址)> <!ELEMENT 收货地址 (地址)> <!ELEMENT 发货地址 (地址)> ]> <订单> <收货地址> <地址> <街道>中关村大街 1 号</街道> <城市>北京</城市> <邮编>100080</邮编> </地址> </收货地址> <发货地址> <地址> <街道>科技园路 88 号</街道> <城市>上海</城市> <邮编>200233</邮编> </地址> </发货地址> </订单>通过参数实体,可以将重复的 DTD 片段抽取出来,实现一次定义、多处复用,大大简化 DTD 的编写和维护。
6. 预定义实体
XML 规范预定义了五个实体,用于表示 XML 语法中的特殊字符。这些实体无需在 DTD 中声明即可直接使用:
| 实体引用 | 字符 | 含义 |
|---|---|---|
< | < | 小于号 |
> | > | 大于号 |
& | & | 和号 |
' | ' | 单引号 |
" | " | 双引号 |
例如,要在 XML 内容中显示"a < b",必须写成:
<p>a < b</p>7. 实体在 DTD 中的实际应用场景
7.1 统一管理版权信息
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 文章 [ <!ENTITY 版权 "版权所有 © 2026 某某技术社区"> <!ENTITY 作者 "李四"> <!ELEMENT 文章 (标题, 作者, 正文, 版权声明)> <!ELEMENT 标题 (#PCDATA)> <!ELEMENT 作者 (#PCDATA)> <!ELEMENT 正文 (#PCDATA)> <!ELEMENT 版权声明 (#PCDATA)> ]> <文章> <标题>DTD 实体入门</标题> <作者>&作者;</作者> <正文>本文介绍 DTD 实体的基本概念与用法。</正文> <版权声明>&版权;</版权声明> </文章>7.2 构建可配置的 XML 配置模板
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 系统配置 [ <!ENTITY 数据库地址 "jdbc:mysql://localhost:3306/app_db"> <!ENTITY 数据库用户 "root"> <!ENTITY 数据库密码 "123456"> <!ENTITY 缓存大小 "256"> <!ELEMENT 系统配置 (数据库, 缓存)> <!ELEMENT 数据库 (地址, 用户, 密码)> <!ELEMENT 地址 (#PCDATA)> <!ELEMENT 用户 (#PCDATA)> <!ELEMENT 密码 (#PCDATA)> <!ELEMENT 缓存 (大小)> <!ELEMENT 大小 (#PCDATA)> ]> <系统配置> <数据库> <地址>&数据库地址;</地址> <用户>&数据库用户;</用户> <密码>&数据库密码;</密码> </数据库> <缓存> <大小>&缓存大小;</大小> </缓存> </系统配置>8. 实体使用注意事项与安全风险
在使用 DTD 实体时,需要注意以下几点:
- 实体名称区分大小写:实体名称是大小写敏感的,
&Company;和&company;是两个不同的实体。 - 避免循环引用:实体之间不能形成循环引用,否则解析器会报错。
- 外部实体安全风险:外部实体可能被恶意利用,导致 XXE(XML 外部实体注入)攻击。攻击者可以通过构造恶意的外部实体引用,读取服务器上的敏感文件或发起 SSRF 请求。在解析不可信的 XML 时,应禁用外部实体解析。
- 实体内容中的特殊字符:实体内容中的
<、>、&等字符必须使用对应的实体引用进行转义。 - 参数实体仅在 DTD 中使用:参数实体不能出现在 XML 文档内容中,只能在 DTD 内部使用。
下面是一个存在 XXE 风险的示例,实际开发中应避免直接解析此类文档:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 恶意 [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <恶意>&xxe;</恶意>在 Java 中使用 DocumentBuilderFactory 解析 XML 时,建议通过以下方式禁用外部实体:
import javax.xml.parsers.DocumentBuilderFactory; public class SecureXmlParser { public static DocumentBuilderFactory createSecureFactory() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); } catch (Exception e) { throw new RuntimeException("无法配置安全的 XML 解析器", e); } return factory; } }9. 总结
DTD 实体是 XML 文档中非常实用的功能,它允许开发者定义可复用的内容片段,从而减少重复、提高可维护性。本文从内部实体、外部实体、参数实体、预定义实体四个维度进行了系统讲解,并通过丰富的代码示例展示了实体的定义、引用和实际应用场景。在实际开发中,合理使用实体可以让 XML 文档更加简洁和灵活,但同时也要警惕外部实体带来的安全风险,确保在解析不可信 XML 时采取必要的防护措施。