如何为自己的XML格式编写一个DTD

来源:Reactjs教程作者:陈远山头衔:网络博主
导读:本期聚焦于陈远山创作的《如何为自己的XML格式编写一个DTD》,敬请观看详情。当我们自己设计一套XML数据格式时,如何保证交换双方写出来的文档结构一致、字段不缺不漏?DTD(文档类型定义)就是解决这个问题的经典工具。本文从DTD的基本语法入手,详细讲解元素声明、属性声明、实体定义的写法,再结合一个完整的图书管理XML实例,演示如何编写内部DTD与外部DTD、如何引用校验,并分析DTD与Schema的差异以及各自的适用场景,帮助你为自己的XML格式快速建立一套可校验的结构规范。

自己设计XML格式其实很简单,随手写一个标签嵌套的文档就能用。但一旦这个格式要在多个系统之间交换数据,问题就来了:对方发来的文档里少了一个必填字段怎么办?子元素的顺序乱了怎么办?靠口头约定或者文档说明来约束,永远不如让机器自动校验来得可靠。DTD(Document Type Definition,文档类型定义)就是XML自诞生起就内置的一套结构描述机制,写好一份DTD,任何符合规范的解析器都能据此判断一份XML文档是否合法。本文带你从零开始为自己的XML格式编写一份完整的DTD。

如何为自己的XML格式编写一个DTD

一、DTD能约束什么:先弄清它的能力边界

在动手写之前,先要清楚DTD到底能管哪些事。DTD的核心能力有三个:约束元素的结构(有哪些子元素、顺序如何、出现几次)、约束属性(属性名、类型、默认值、是否必须)以及定义实体(可复用的文本替换)。这三个能力覆盖了大多数数据格式校验的需求。

但DTD也有明显的短板。它没有数据类型的概念,无法区分一个元素的内容是整数还是日期,只能笼统地声明为文本;它不支持命名空间,对复杂的复合结构表达力有限;它不能约束某个元素的内容必须满足某种格式(比如必须是11位手机号)。如果这些限制你能接受,DTD依然是轻量、通用、所有解析器都支持的最好选择;如果需要强类型校验,那就该考虑XML Schema了,这一点后文会详细对比。

二、元素声明:DTD的核心语法

元素声明使用<!ELEMENT>指令,格式为<!ELEMENT 元素名 内容模型>。最简单的情况是声明一个只包含文本的元素,用(#PCDATA)表示,例如<!ELEMENT title (#PCDATA)>表示title元素内部只能是纯文本,不能再嵌套其他标签。

如果元素包含子元素,就要写内容模型。DTD提供了几种符号控制子元素的出现方式:逗号表示按顺序出现,竖线表示二选一,问号表示零次或一次,星号表示零次或多次,加号表示一次或多次。比如<!ELEMENT book (title, author+, price?)>表示book元素必须依次包含一个title、一个或多个author、以及一个可选的price。掌握这几个符号,基本就能表达绝大多数结构需求了。

<!-- 只含文本的元素 -->
<!ELEMENT title (#PCDATA)>

<!-- 按顺序出现的子元素,price可选 -->
<!ELEMENT book (title, author+, price?)>

<!-- 二选一:电子书或纸质书 -->
<!ELEMENT book (title, (ebook | paperback))>

<!-- 任意内容:文本加任意子元素 -->
<!ELEMENT description ANY>

<!-- 空元素,比如图片占位 -->
<!ELEMENT cover EMPTY>

还有两个特殊的取值:ANY表示任意内容,几乎不做约束,一般只在过渡阶段使用;EMPTY表示空元素,常用于<cover/>这类只靠属性携带信息的标签。建议在设计阶段尽量少用ANY,否则DTD的校验价值会大打折扣。

三、属性声明与实体定义

属性用<!ATTLIST>声明,格式是<!ATTLIST 元素名 属性名 属性类型 约束>。属性类型常用的是CDATA(任意文本)、ID(文档内唯一标识)、IDREF/IDREFS(引用其他ID)、NMTOKEN(受限的字符串)。约束部分可以写#REQUIRED(必须出现)、#IMPLIED(可选)或者直接写默认值,写默认值时还可以加#FIXED表示值固定不可改。

<!ATTLIST book
    id      ID      #REQUIRED
    category CDATA   #IMPLIED
    lang    CDATA   "zh">

<!!-- cover是空元素,图片地址放属性里 -->
<!ATTLIST cover
    url     CDATA   #REQUIRED
    format  (jpg|png|webp) "jpg">

实体定义有点像编程里的常量,用<!ENTITY>声明后在文档中以&实体名;的形式引用。它最大的用处是定义可复用的文本片段,或者把特殊字符包装起来。比如版权信息在多处出现,定义一个实体引用即可,改的时候只改一处。

<!ENTITY copyright "Copyright 2024 Example Press">
<!ENTITY lt     "&#60;">

<!-- 文档中使用 -->
<footer>&copyright;</footer>

四、实战:为图书管理系统编写完整DTD

现在把前面的语法串起来,为一个图书管理格式编写完整的DTD。假设文档的根元素是library,下面挂多本书,每本书有标题、若干作者、价格、封面,书籍必须有唯一编号。我们先写成内部DTD,直接嵌入XML文档头部,方便快速测试。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library [
  <!ELEMENT library (book+)>
  <!ELEMENT book (title, author+, price?, cover?)>
  <!ELEMENT title (#PCDATA)>
  <!ELEMENT author (#PCDATA)>
  <!ELEMENT price (#PCDATA)>
  <!ELEMENT cover EMPTY>
  <!ATTLIST book id ID #REQUIRED>
  <!ATTLIST cover url CDATA #REQUIRED>
  <!ATTLIST price currency CDATA "CNY">
]>
<library>
  <book id="b001">
    <title>数据结构</title>
    <author>张三</author>
    <price>59.00</price>
  </book>
  <book id="b002">
    <title>编译原理</title>
    <author>李四</author>
    <author>王五</author>
  </book>
</library>

格式稳定之后,应该把DTD抽成独立文件供多方共享,这就是外部DTD。DTD文件单独保存为book.dtd,XML文档通过SYSTEM或PUBLIC标识引用它。SYSTEM后面跟具体路径,PUBLIC用于公开的标准格式,格式为PUBLIC 公共标识符 系统标识符

<!-- 引用本地DTD文件 -->
<!DOCTYPE library SYSTEM "book.dtd">

<!-- 引用公开标准DTD的写法 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

写完之后一定要验证。很多编辑器比如VS Code装上XML插件后可以实时提示校验错误;用Python的话,lxml库几行代码就能完成校验,下面这个脚本会打印文档是否合法以及具体错误位置。

from lxml import etree

dtd = etree.DTD("book.dtd")
doc = etree.parse("books.xml")

if dtd.validate(doc):
    print("文档合法")
else:
    for error in dtd.error_log:
        print(error)

五、DTD与XML Schema怎么选

最后聊聊选型问题。DTD的优势在于语法简单、篇幅短、历史久远、所有XML工具链都支持,XHTML、早期配置文件格式大量使用它。缺点前面提过:无数据类型、无命名空间支持、语法自成一体和XML风格不一致。

XML Schema(XSD)则补齐了这些短板,支持几十种内置数据类型、自定义类型、命名空间、更精细的出现次数控制,写法本身就是规范的XML。代价是语法复杂得多,一份功能等价的XSD往往比DTD长好几倍,学习成本也高。如果格式简单、只需管住结构和属性,DTD足够了;如果要做严格的数值范围、日期格式、跨命名空间校验,就直接上XSD。此外还有更轻量的RELAX NG,语法比XSD友好,也值得了解。

总的来说,为XML格式编写DTD的思路是:先画出理想文档的样例,再从根元素往下逐层声明元素和属性,最后用工具实际校验几份文档查漏补缺。一套好的结构约束,能让数据交换双方省去大量沟通成本,这比任何口头约定都靠谱。

XMLDTD文档类型定义修改时间:2026-09-03 22:21:15

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260903/49842.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。