DTD 完整示例
本章定位 :通过两个贴近真实业务的完整 DTD 设计实例——TV 节目表和报纸文章——把前面三章分散的 DTD 知识点(元素声明、属性声明、实体声明)串联起来,形成"从需求到 DTD"的完整建模能力。
定义与作用
单独的 DTD 语法知识是碎片化的:你记住了 <!ELEMENT>、<!ATTLIST>、<!ENTITY> 怎么写,但面对一个真实需求时仍不知从何下手。
DTD 完整示例 的目的:展示如何从一个真实业务场景出发,识别实体、定义元素内容模型、分配属性、建立 ID/IDREF 关联——最终产出一份可运行、可验证的完整 XML+DTD。
核心原理:DTD 建模的思维流程
图解释 :DTD 建模不是直接写声明,而是从业务需求出发的七步结构化流程。每一步都有明确的决策点,而非拍脑袋。
完整示例一:TV 节目表
场景说明
飞翔科技的内部电视台需要一份 XML 格式的"每周节目表"。 黄俪 负责设计格式,需要包含频道信息、节目时间、演职人员,且多个节目可能引用同一个演员。
DTD 设计
tvschedule.dtd :
<!ELEMENT tvschedule (channel+)>
<!-- 频道 -->
<!ELEMENT channel (name, program+)>
<!ATTLIST channel
id ID #REQUIRED
>
<!ELEMENT name (#PCDATA)>
<!-- 节目 -->
<!ELEMENT program (title, start_time, end_time, description?, cast?, rating?)>
<!ATTLIST program
pid ID #REQUIRED
type (新闻|综艺|电视剧|体育|纪录片) #REQUIRED
is_rerun (yes|no) "no"
>
<!ELEMENT title (#PCDATA)>
<!ELEMENT start_time (#PCDATA)>
<!ELEMENT end_time (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT rating (#PCDATA)>
<!-- 演职人员(用 IDREF 实现交叉引用) -->
<!ELEMENT cast (actor_ref+)>
<!ELEMENT actor_ref EMPTY>
<!ATTLIST actor_ref
refid IDREF #REQUIRED
>
<!-- 演员信息(独立定义,可被多个节目引用) -->
<!ELEMENT actors (actor+)>
<!ELEMENT actor (actor_name, actor_role?)>
<!ATTLIST actor
aid ID #REQUIRED
>
<!ELEMENT actor_name (#PCDATA)>
<!ELEMENT actor_role (#PCDATA)>
设计要点解析 :
- 频道 id → ID 类型 :每个频道有唯一标识,
channel/@id用 ID 类型保证唯一性 - 节目 type → 枚举 :节目类型固定,用枚举约束防止输入错误
- 演员引用 → IDREF :
actor_ref/@refid引用actor/@aid,一个演员可被多个节目引用,避免数据重复 - 默认值 :
is_rerun默认"no",大多数节目不是重播,减少 XML 编写量
XML 实例
tvschedule.xml (内部 DTD 方式):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tvschedule [
<!-- 此处贴入 tvschedule.dtd 的全部声明 -->
<!ELEMENT tvschedule (channel+, actors)>
<!ELEMENT channel (name, program+)>
<!ATTLIST channel id ID #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT program (title, start_time, end_time, description?, cast?, rating?)>
<!ATTLIST program
pid ID #REQUIRED
type (新闻|综艺|电视剧|体育|纪录片) #REQUIRED
is_rerun (yes|no) "no"
>
<!ELEMENT title (#PCDATA)>
<!ELEMENT start_time (#PCDATA)>
<!ELEMENT end_time (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT rating (#PCDATA)>
<!ELEMENT cast (actor_ref+)>
<!ELEMENT actor_ref EMPTY>
<!ATTLIST actor_ref refid IDREF #REQUIRED>
<!ELEMENT actors (actor+)>
<!ELEMENT actor (actor_name, actor_role?)>
<!ATTLIST actor aid ID #REQUIRED>
<!ELEMENT actor_name (#PCDATA)>
<!ELEMENT actor_role (#PCDATA)>
]>
<tvschedule>
<!-- 演员名单(在节目表之后,但 DTD 不强制顺序) -->
<actors>
<actor aid="A01">
<actor_name>大翔</actor_name>
<actor_role>主持人</actor_role>
</actor>
<actor aid="A02">
<actor_name>白歌</actor_name>
<actor_role>嘉宾</actor_role>
</actor>
<actor aid="A03">
<actor_name>黄俪</actor_name>
<!-- role 可选,暂无 -->
</actor>
</actors>
<!-- 频道1 -->
<channel id="C01">
<name>飞翔科技综合频道</name>
<program pid="P001" type="新闻">
<title>每周技术资讯</title>
<start_time>2026-06-15 08:00</start_time>
<end_time>2026-06-15 08:30</end_time>
<description>本周行业动态总览,涵盖 AI、云计算、芯片领域</description>
<cast>
<actor_ref refid="A01"/>
<actor_ref refid="A02"/>
</cast>
<rating>8.5</rating>
</program>
<program pid="P002" type="综艺">
<title>码农脱口秀</title>
<start_time>2026-06-15 20:00</start_time>
<end_time>2026-06-15 21:00</end_time>
<cast>
<actor_ref refid="A01"/>
</cast>
<rating>9.2</rating>
</program>
</channel>
<!-- 频道2 -->
<channel id="C02">
<name>飞翔科技技术频道</name>
<program pid="P003" type="体育" is_rerun="yes">
<title>程序员马拉松回放</title>
<start_time>2026-06-15 14:00</start_time>
<end_time>2026-06-15 15:30</end_time>
<cast>
<actor_ref refid="A03"/>
</cast>
</program>
</channel>
</tvschedule>
IDREF 交叉引用效果 :大翔(aid=A01)同时出现在 P001 和 P002 两个节目中,修改演员信息只需改一处。
完整示例二:报纸文章
场景说明
飞翔科技的企业内刊用 XML 排版。 小崔 负责定义 DTD,要求支持多篇文章、作者信息独立维护、文章间可相互引用。
DTD 设计
newspaper.dtd :
<!-- 报纸根元素 -->
<!ELEMENT newspaper (metadata, articles, authors)>
<!-- 元信息 -->
<!ELEMENT metadata (paper_name, issue_date, edition)>
<!ELEMENT paper_name (#PCDATA)>
<!ELEMENT issue_date (#PCDATA)>
<!ELEMENT edition (#PCDATA)>
<!-- 文章列表 -->
<!ELEMENT articles (article+)>
<!-- 单篇文章 -->
<!ELEMENT article (headline, subtitle?, byline, body, related_links?)>
<!ATTLIST article
art_id ID #REQUIRED
section (要闻|技术|人物|评论|副刊) #REQUIRED
status (draft|review|published) "draft"
>
<!ELEMENT headline (#PCDATA)>
<!ELEMENT subtitle (#PCDATA)>
<!-- 署名:引用作者 ID -->
<!ELEMENT byline (author_ref+)>
<!ELEMENT author_ref EMPTY>
<!ATTLIST author_ref
refid IDREF #REQUIRED
>
<!-- 正文(混合内容:文字 + 引用链接) -->
<!ELEMENT body (#PCDATA | related_article)*>
<!ELEMENT related_article EMPTY>
<!ATTLIST related_article
refid IDREF #REQUIRED
description CDATA #IMPLIED
>
<!ELEMENT related_links (related_article*)>
<!-- 作者信息(独立维护) -->
<!ELEMENT authors (author+)>
<!ELEMENT author (full_name, title, department?)>
<!ATTLIST author
auth_id ID #REQUIRED
>
<!ELEMENT full_name (#PCDATA)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT department (#PCDATA)>
设计要点解析 :
- 混合内容 :
(#PCDATA | related_article)*允许正文中穿插文章引用链接 - 文章状态 :
status属性用枚举区分草稿/审核/已发布,适合内刊的审核流程 - 作者与文章分离 :作者用独立
authors块维护,文章用 IDREF 引用
XML 实例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE newspaper SYSTEM "newspaper.dtd">
<newspaper>
<metadata>
<paper_name>乐途日报</paper_name>
<issue_date>2026-06-15</issue_date>
<edition>第128期</edition>
</metadata>
<authors>
<author auth_id="AU01">
<full_name>黄俪</full_name>
<title>高级编辑</title>
<department>编辑部</department>
</author>
<author auth_id="AU02">
<full_name>小崔</full_name>
<title>专栏作者</title>
</author>
</authors>
<articles>
<article art_id="ART001" section="要闻" status="published">
<headline>飞翔科技发布智能工单系统 2.0</headline>
<subtitle>以 AI 驱动,效率提升 300%</subtitle>
<byline>
<author_ref refid="AU01"/>
</byline>
<body>
飞翔科技今日正式发布智能工单系统 2.0 版本。
该系统基于大语言模型实现自动化工单分类与派发。
相关报道请参见
<related_article refid="ART002" description="技术深度解析"/>
</body>
</article>
<article art_id="ART002" section="技术" status="published">
<headline>智能工单系统背后的 AI 技术</headline>
<byline>
<author_ref refid="AU02"/>
</byline>
<body>
本文深入解析智能工单系统的核心技术架构,
包括 NLP 分类模型、知识图谱构建、实时派单算法。
</body>
</article>
<!-- 更多文章... -->
</articles>
</newspaper>
操作结果
| 验证项 | 结果 |
|---|---|
| ID 唯一性 | art_id、auth_id 各不重复,验证通过 |
| IDREF 有效性 | author_ref/@refid 对应到存在的 auth_id,验证通过 |
| 枚举约束 | section="要闻" 在允许范围内,验证通过 |
| 默认值 | article/@status 省略时默认为 "draft" |
| 混合内容 | <body> 中文本与 <related_article> 交替出现,验证通过 |
易错场景
IDREF 引用不存在的 ID
<author_ref refid="AU99"/> <!-- ID 列表中无 AU99 -->
DTD 验证器会报错:IDREF attribute refid references an unknown ID "AU99"。解决:确保所有 IDREF 指向的元素都已声明且 ID 存在。
多个元素使用相同的 ID 值
<actor aid="A01">...</actor>
<actor aid="A01">...</actor> <!-- ID 重复! -->
ID 类型要求值在 整个文档 范围内唯一(不仅是同级元素),重复会导致验证失败。
忘记在 DTD 中声明引用目标
<!ELEMENT author_ref EMPTY>
<!ATTLIST author_ref refid IDREF #REQUIRED>
<!-- 忘记声明 author 元素及其 aid 属性 -->
DTD 只校验 IDREF 的语法形式(是否是合法 ID 格式),但 真正的引用完整性校验需要在 XML 实例文档中完成 。确保被引用的元素也在 DTD 中完整声明。
面试考点
| 考点 | 参考答案要点 |
|---|---|
| 给出一个场景(如"学生选课系统"),请设计 DTD | 识别实体(学生、课程、选课记录)→ 确定层级 → 用 ID/IDREF 建立关联 → 枚举约束课程类型/院系 |
| IDREF 和普通 CDATA 引用有什么区别? | IDREF 受 DTD 验证器约束——引用的 ID 必须存在且唯一;CDATA 没有此类约束 |
| 混合内容声明的写法? | `(#PCDATA |
| 内部 DTD vs 外部 DTD 各适合什么场景? | 内部 DTD:单文件自包含,适合小规模示例和教学;外部 DTD:多文档共享同一结构,适合企业级应用 |