乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • XML 基础语法

    • XML 概述
    • XML 文档结构
    • XML 元素
    • XML 属性
    • XML 语法规则
    • XML 命名空间
    • XML 注释与处理指令
  • DTD 与文档验证

    • DTD 概述
    • DTD 元素声明
    • DTD 属性声明
    • DTD 实体声明
    • DTD 元素与属性对比
    • DTD 完整示例
  • XML Schema 定义

    • XML Schema 概述
    • XSD 简单类型
    • XSD 复杂类型
    • XSD 命名空间与引用
  • XPath 节点定位

    • XPath 概述
    • XPath 路径表达式
    • XPath 谓词与函数
  • XSLT 转换

    • XSLT 概述
    • XSLT 模板与匹配
    • XSLT 控制结构
    • XSLT 输出控制
  • XML 解析技术

    • XML 解析概述
    • DOM 解析
    • SAX 解析
    • StAX 解析
    • XML 与 Java — JAXP
  • XML 在 Java 中的应用

    • Spring XML 配置
    • MyBatis XML 映射
    • pom.xml 与 Maven
    • web.xml 配置详解
  • 现代数据格式对比

    • XML 与 JSON 对比
    • XML 与 YAML 对比
  • XML 显示与浏览器集成

    • XML 在浏览器中的显示
    • XMLHttpRequest 与 AJAX
    • 服务器端 XML 处理
  • XML 进阶查询与链接

    • XQuery 查询语言
    • XLink 超链接
    • XML 验证工具使用
  • XML Web 服务(选读)

    • XML Web 服务概述
    • SOAP 协议详解
    • WSDL 服务描述
    • RSS 内容聚合
    • RDF 资源描述框架

DTD 元素与属性对比

本章定位 :理解 DTD 设计中"何时用子元素、何时用属性"这一核心设计决策——掌握元素与属性的语义差异、设计原则与典型误区,避免最常见的 DTD 建模错误。

定义与作用

在 DTD 中,数据可以用两种方式表示:

<!-- 方式一:属性 -->
<book title="XML入门指南" author="大翔"/>

<!-- 方式二:子元素 -->
<book>
  <title>XML入门指南</title>
  <author>大翔</author>
</book>

这两种方式都合法,但语义截然不同。 元素与属性的选择 是 DTD 设计中最常见的争议点,也是面试中的高频问题。

核心问题:什么时候应该把一段数据建模为子元素?什么时候应该建模为属性?

核心原理:语义差异模型

图解释 :数据建模决策树揭示了核心原则——能扩展、可重复、内容长、可能含子结构的用子元素;元数据性质的标识、修饰性简短数据用属性。

语法/结构要点

元素与属性的全方位对比

维度子元素属性
语义数据本身("是什么")数据的修饰/元信息("什么样")
可扩展性可以扩展,可添加子元素不能扩展,属性值是纯文本
可重复性可以多次出现同一元素上属性名唯一
多值支持自然地支持(多个子元素)不支持,只能一个值
特殊字符可通过 CDATA 包含 <、&必须转义 <→&lt; &→&amp;
空白处理保留空白空白被规范化
结构深度可嵌套多层平面结构,只能一层
顺序保证有明确的子元素顺序属性无序,排列任意
可编程访问通过 DOM childNodes 遍历通过 DOM getAttribute() 直接获取
DTD 声明<!ELEMENT><!ATTLIST>

典型设计原则

原则说明适用侧
核心数据用元素数据本身、需要展示、需要搜索的内容子元素
元数据用属性标识(ID)、引用(IDREF)、格式化提示属性
可扩展用元素未来可能需要添加子结构的子元素
简短约束用属性DTD 中属性声明能定义枚举、默认值属性
人类可读的关键信息用元素用户会直接看到的文本子元素
机器使用的标识用属性ID、类型码、常量属性

完整示例

场景说明

飞翔科技的 大翔 正在设计一个"员工信息"XML 格式。白歌审查 DTD 时,指出他把所有数据都塞成了属性——这是一种常见的设计错误。

操作前:过度使用属性的错误设计

employee_bad.xml :

<?xml version="1.0"?>
<!DOCTYPE employees [
  <!ELEMENT employees (employee*)>
  <!ELEMENT employee EMPTY>
  <!-- 所有数据全成属性 —— 大翔的错误设计 -->
  <!ATTLIST employee
    id       ID    #REQUIRED
    name     CDATA #REQUIRED
    age      CDATA #REQUIRED
    email    CDATA #REQUIRED
    bio      CDATA #IMPLIED
    skill1   CDATA #IMPLIED
    skill2   CDATA #IMPLIED
    skill3   CDATA #IMPLIED
  >
]>
<employees>
  <employee id="E001" name="大翔" age="28"
    email="daxiang@feixiang.com"
    bio="全栈工程师,擅长 Java 和 Python,5年经验"
    skill1="Java" skill2="Python" skill3="DevOps"/>
  <employee id="E002" name="白歌" age="31"
    email="baige@feixiang.com"
    bio="架构师,主导公司微服务转型"
    skill1="Spring" skill2="K8s" skill3=""/>
</employees>

问题分析 :

  1. bio 很长,作为属性难以阅读和编辑
  2. skill 用 skill1/skill2/skill3 硬编码——员工可能只有 1 个技能或 5 个技能
  3. 属性值不能包含 < 或 &,bio 中的特殊字符必须转义
  4. 无法为技能或 bio 添加子结构(如 skill 的熟练度 level)

应用正确设计原则后

employee_good.xml :

<?xml version="1.0"?>
<!DOCTYPE employees [
  <!-- 员工列表 -->
  <!ELEMENT employees (employee*)>

  <!-- 员工:核心数据用子元素 -->
  <!ELEMENT employee (name, age, email, bio?, skills?)>
  <!ELEMENT name (#PCDATA)>
  <!ELEMENT age (#PCDATA)>
  <!ELEMENT email (#PCDATA)>
  <!ELEMENT bio (#PCDATA)>

  <!-- 技能:自然地支持多个且可扩展 -->
  <!ELEMENT skills (skill+)>
  <!ELEMENT skill (#PCDATA)>

  <!-- 属性:仅用于标识和元信息 -->
  <!ATTLIST employee
    id     ID     #REQUIRED    <!-- 唯一标识:属性 -->
    dept   (研发部|产品部|测试部) #REQUIRED    <!-- 枚举约束:属性 -->
    level  CDATA  #IMPLIED     <!-- 简短元信息:属性 -->
  >

  <!-- 技能的熟练度可以在未来扩展为子元素或属性 -->
  <!ATTLIST skill
    proficiency (初级|中级|高级|专家) "中级"
  >
]>
<employees>
  <employee id="E001" dept="研发部" level="T3">
    <name>大翔</name>
    <age>28</age>
    <email>daxiang@feixiang.com</email>
    <bio>全栈工程师,擅长 Java 和 Python,5年经验。
专注方向:后端架构 &amp; DevOps(转义后安全输出)</bio>
    <skills>
      <skill proficiency="高级">Java</skill>
      <skill proficiency="高级">Python</skill>
      <skill proficiency="中级">DevOps</skill>
    </skills>
  </employee>

  <employee id="E002" dept="研发部" level="T5">
    <name>白歌</name>
    <age>31</age>
    <email>baige@feixiang.com</email>
    <bio>架构师,主导公司微服务转型,深度参与 K8s 集群建设</bio>
    <skills>
      <skill proficiency="专家">Spring</skill>
      <skill proficiency="高级">K8s</skill>
    </skills>
  </employee>
</employees>

操作结果

对比维度错误设计正确设计
阅读体验bio 挤在一行属性中,难读结构清晰,分块明确
技能数量硬编码 3 个属性位灵活,有多少写多少
技能扩展无法添加熟练度已添加 proficiency 属性
特殊字符属性值不能含原始 < &子元素内容可含转义后的字符
搜索便利属性值无法用 XPath 匹配文本子元素天然支持 XPath 文本匹配

易错场景

属性全塞

把标题、正文、描述等所有数据都定义为属性——这是 XML 初学者最常见的设计错误。

<!-- 灾难性设计 — 不要这么做 -->
<book title="XML" author="大翔" chapter1="概述" chapter2="语法" ... price="59"/>

当一段数据可能变长、需要换行、可能包含特殊字符时,它就不适合成为属性。

用多个同名属性模拟多值

<!-- 错误:属性不允许重复 -->
<employee skill="Java" skill="Python" skill="DevOps"/>
<!-- XML 解析器报错:duplicate attribute "skill" -->

DTD/XML 规范中同一元素上属性名不能重复。多值必须用子元素。

ID/IDREF 绑定用错数据类型

<!ATTLIST order
  order_id CDATA #REQUIRED   <!-- 应该用 ID -->
  customer_ref CDATA #REQUIRED  <!-- 应该用 IDREF -->
>

当数据承担"唯一标识"和"跨元素引用"功能时,应该使用 ID/IDREF 类型以获得 DTD 验证器的约束检查。

面试考点

考点参考答案要点
何时用子元素、何时用属性?核心数据、可重复、可扩展、含结构的用子元素;标识、简短元信息、枚举约束用属性
属性相比子元素的 3 个关键限制?①不能重复(属性名唯一);②不能嵌套(平面结构);③不能直接包含 < &(必须转义)
为什么 DTD 设计不鼓励过度使用属性?难以扩展、阅读体验差、多值需要硬编码多个属性、无法满足复杂数据结构需求
属性有什么子元素没有的优势?DTD 中可声明枚举、默认值、ID/IDREF 类型校验;访问时 getAttribute() 比 childNodes 遍历快
上一页
DTD 实体声明
下一页
DTD 完整示例