|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1. DTD简介及其在XML中的重要性
文档类型定义(Document Type Definition,DTD)是XML(eXtensible Markup Language)文档的一种规范,它定义了XML文档的结构、元素及其属性。DTD是XML文档结构验证的早期标准,虽然现在已被XML Schema(XSD)等更现代的技术所补充,但DTD因其简洁性和广泛兼容性仍然被广泛使用。
DTD的主要作用包括:
• 定义XML文档中允许的元素和属性
• 规定元素之间的嵌套关系和出现顺序
• 确保XML文档的结构符合预定义的规则
• 提高数据的结构化质量和一致性
通过正确实现DTD中的元素嵌套规则,我们可以确保XML文档不仅格式良好(well-formed),而且有效(valid),从而提升数据的完整性、一致性和可用性。
2. DTD的基本语法和结构
DTD可以在XML文档内部定义(内部DTD),也可以在外部文件中定义(外部DTD)。让我们先了解DTD的基本语法结构。
2.1 内部DTD
内部DTD直接包含在XML文档的DOCTYPE声明中,其基本语法如下:
例如:
- <!DOCTYPE book [
- <!ELEMENT book (title, author+, publisher, price)>
- <!ELEMENT title (#PCDATA)>
- <!ELEMENT author (#PCDATA)>
- <!ELEMENT publisher (#PCDATA)>
- <!ELEMENT price (#PCDATA)>
- ]>
复制代码
2.2 外部DTD
外部DTD存储在单独的文件中,通常以.dtd为扩展名。在XML文档中引用外部DTD的语法如下:
- <!DOCTYPE 根元素名 SYSTEM "DTD文件URL">
复制代码
例如:
- <!DOCTYPE book SYSTEM "book.dtd">
复制代码
2.3 DTD声明类型
DTD中主要有以下几种声明类型:
• <!ELEMENT>:定义元素及其内容模型
• <!ATTLIST>:定义元素的属性
• <!ENTITY>:定义实体(可重用的内容片段)
• <!NOTATION>:定义非XML数据的格式
3. 元素声明和嵌套规则的定义
元素声明是DTD中最重要的部分,它定义了元素的名称和内容模型。元素声明的基本语法是:
内容模型定义了元素中可以包含哪些子元素,以及这些子元素的排列顺序和出现次数。下面我们将详细探讨各种内容模型类型。
3.1 简单内容模型
简单内容模型指定元素只包含文本内容而不包含子元素。主要有以下几种:
• (#PCDATA):元素只能包含解析的字符数据(Parsed Character Data),即普通文本。
• EMPTY:元素不能包含任何内容,必须是空元素。
• ANY:元素可以包含任何内容,包括子元素和文本。
例如:
- <!ELEMENT title (#PCDATA)> <!-- title元素只能包含文本 -->
- <!ELEMENT img EMPTY> <!-- img元素必须是空元素 -->
- <!ELEMENT note ANY> <!-- note元素可以包含任何内容 -->
复制代码
3.2 复杂内容模型
复杂内容模型定义了元素可以包含的子元素及其排列规则。以下是几种常用的复杂内容模型:
序列使用逗号(,)分隔子元素,表示子元素必须按照指定的顺序出现。
例如:
- <!ELEMENT book (title, author, publisher, price)>
复制代码
这个声明表示book元素必须包含且仅包含四个子元素:title、author、publisher和price,并且必须按照这个顺序出现。
对应的XML示例:
- <book>
- <title>XML Guide</title>
- <author>John Doe</author>
- <publisher>Tech Press</publisher>
- <price>29.99</price>
- </book>
复制代码
选择使用竖线(|)分隔子元素,表示在这些子元素中必须选择一个出现。
例如:
- <!ELEMENT contact (phone | email | address)>
复制代码
这个声明表示contact元素必须包含且仅包含一个子元素:phone、email或address中的一个。
对应的XML示例:
- <contact>
- <email>john@example.com</email>
- </contact>
复制代码
混合内容允许元素同时包含文本和子元素。混合内容模型使用(#PCDATA, ...)的形式,其中子元素用竖线分隔,并且整个列表后面必须跟一个星号(*)。
例如:
- <!ELEMENT description (#PCDATA | emphasis | code)*>
复制代码
这个声明表示description元素可以包含文本、emphasis元素或code元素,这些内容可以按任意顺序出现,出现次数不限。
对应的XML示例:
- <description>
- This is a <emphasis>very important</emphasis> note about the <code>function()</code>.
- </description>
复制代码
4. 元素出现次数的控制
DTD提供了几种符号来控制元素的出现次数,这些符号可以附加在元素名称后面:
• 无符号:元素必须出现且仅出现一次
• ?:元素可以出现零次或一次(可选)
• *:元素可以出现零次或多次(可选且可重复)
• +:元素必须出现一次或多次(必须至少出现一次,可重复)
4.1 出现次数示例
让我们通过一些示例来理解这些符号的用法:
- <!ELEMENT book (title, author+, publisher?, price*)>
复制代码
这个声明表示:
• title元素必须出现且仅出现一次
• author元素必须出现至少一次(可以多次)
• publisher元素可以出现零次或一次(可选)
• price元素可以出现零次或多次(可选且可重复)
对应的XML示例(有效):
- <book>
- <title>XML Guide</title>
- <author>John Doe</author>
- <publisher>Tech Press</publisher>
- <price>29.99</price>
- <price>19.99</price>
- </book>
复制代码
另一个有效示例:
- <book>
- <title>XML Guide</title>
- <author>John Doe</author>
- <author>Jane Smith</author>
- </book>
复制代码
5. 复杂嵌套规则的实现
在实际应用中,我们经常需要定义复杂的嵌套规则。下面通过一个完整的示例来展示如何实现复杂的元素嵌套规则。
5.1 示例:图书目录DTD
假设我们要定义一个图书目录的DTD,其中包含图书、作者和出版社等信息。
- <!DOCTYPE catalog [
- <!-- 根元素catalog包含一个或多个book元素 -->
- <!ELEMENT catalog (book+)>
-
- <!-- book元素包含title、一个或多个author、可选的publisher和price -->
- <!ELEMENT book (title, author+, publisher?, price)>
-
- <!-- title元素只包含文本 -->
- <!ELEMENT title (#PCDATA)>
-
- <!-- author元素包含name和可选的bio -->
- <!ELEMENT author (name, bio?)>
-
- <!-- name元素只包含文本 -->
- <!ELEMENT name (#PCDATA)>
-
- <!-- bio元素可以包含文本和emphasis元素 -->
- <!ELEMENT bio (#PCDATA | emphasis)*>
-
- <!-- emphasis元素只包含文本 -->
- <!ELEMENT emphasis (#PCDATA)>
-
- <!-- publisher元素包含name和address -->
- <!ELEMENT publisher (name, address)>
-
- <!-- publisher的name元素只包含文本 -->
- <!ELEMENT name (#PCDATA)>
-
- <!-- address元素包含street、city和country -->
- <!ELEMENT address (street, city, country)>
-
- <!-- street、city和country元素只包含文本 -->
- <!ELEMENT street (#PCDATA)>
- <!ELEMENT city (#PCDATA)>
- <!ELEMENT country (#PCDATA)>
-
- <!-- price元素只包含文本 -->
- <!ELEMENT price (#PCDATA)>
- ]>
复制代码
5.2 对应的XML示例
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE catalog [
- <!-- 这里插入上面定义的DTD -->
- ]>
- <catalog>
- <book>
- <title>XML Guide</title>
- <author>
- <name>John Doe</name>
- <bio>
- John is a <emphasis>renowned</emphasis> XML expert with over 20 years of experience.
- </bio>
- </author>
- <author>
- <name>Jane Smith</name>
- </author>
- <publisher>
- <name>Tech Press</name>
- <address>
- <street>123 Tech Street</street>
- <city>Techville</city>
- <country>Techland</country>
- </address>
- </publisher>
- <price>29.99</price>
- </book>
- <book>
- <title>Advanced XML</title>
- <author>
- <name>Alice Johnson</name>
- </author>
- <price>39.99</price>
- </book>
- </catalog>
复制代码
5.3 嵌套规则解析
让我们分析一下上述DTD中定义的一些关键嵌套规则:
1. catalog元素必须包含至少一个book元素(book+)。
2. 每个book元素必须包含一个title、至少一个author、可选的publisher和一个price。
3. 每个author元素必须包含一个name,还可以包含一个可选的bio。
4. bio元素可以包含文本和emphasis元素的混合内容。
5. publisher元素如果出现,必须包含name和address。
6. address元素必须按顺序包含street、city和country。
这些规则确保了XML文档的结构符合预期的数据模型,从而提高了数据的结构化质量。
6. 属性声明与嵌套规则的关系
除了元素嵌套规则外,DTD还可以定义元素的属性。属性声明使用<!ATTLIST>,其基本语法是:
- <!ATTLIST 元素名 属性名 属性类型 默认值>
复制代码
属性类型包括:
• CDATA:字符数据
• ID:唯一标识符
• IDREF:引用另一个元素的ID
• IDREFS:引用多个元素的ID,用空格分隔
• NMTOKEN:名称标记
• NMTOKENS:多个名称标记,用空格分隔
• ENTITY:实体
• ENTITIES:多个实体,用空格分隔
• NOTATION:符号
• 枚举值:(值1 | 值2 | …):从预定义的值列表中选择一个
默认值可以是:
• #REQUIRED:属性必须出现
• #IMPLIED:属性可选
• #FIXED 值:属性有固定值
• 默认值:如果属性未指定,则使用此默认值
6.1 属性声明示例
让我们扩展前面的图书目录DTD,添加一些属性声明:
- <!ATTLIST book
- id ID #REQUIRED
- category (fiction | non-fiction | technical) "technical"
- lang CDATA "en"
- >
- <!ATTLIST author
- id ID #REQUIRED
- contact-email CDATA #IMPLIED
- >
- <!ATTLIST price
- currency CDATA "USD"
- >
复制代码
6.2 带属性的XML示例
- <book id="b001" category="technical" lang="en">
- <title>XML Guide</title>
- <author id="a001" contact-email="john@example.com">
- <name>John Doe</name>
- <bio>
- John is a <emphasis>renowned</emphasis> XML expert with over 20 years of experience.
- </bio>
- </author>
- <publisher>
- <name>Tech Press</name>
- <address>
- <street>123 Tech Street</street>
- <city>Techville</city>
- <country>Techland</country>
- </address>
- </publisher>
- <price currency="USD">29.99</price>
- </book>
复制代码
6.3 ID和IDREF的使用
ID和IDREF属性可以用来建立元素之间的关系。例如,我们可以为作者和图书建立引用关系:
- <!ATTLIST book
- id ID #REQUIRED
- author-ids IDREFS #REQUIRED
- ...其他属性...
- >
- <!ATTLIST author
- id ID #REQUIRED
- ...其他属性...
- >
复制代码
对应的XML示例:
- <catalog>
- <author id="a001">
- <name>John Doe</name>
- </author>
- <author id="a002">
- <name>Jane Smith</name>
- </author>
-
- <book id="b001" author-ids="a001 a002">
- <title>XML Guide</title>
- ...其他内容...
- </book>
- </catalog>
复制代码
在这个例子中,book元素的author-ids属性引用了两个author元素的ID,建立了图书和作者之间的关系。
7. 实体声明与重用
实体(Entity)是DTD中的一个重要概念,它允许我们定义可重用的内容片段。实体声明使用<!ENTITY>,其基本语法是:
7.1 内部实体
内部实体在DTD内部定义,引用时使用&实体名;的格式。
例如:
- <!ENTITY company "Tech Corporation">
- <!ENTITY copyright "Copyright &company; 2023">
复制代码
在XML中使用:
- <footer>©right;</footer>
复制代码
解析后的结果:
- <footer>Copyright Tech Corporation 2023</footer>
复制代码
7.2 外部实体
外部实体引用外部文件的内容,可以是系统标识符(SYSTEM)或公共标识符(PUBLIC)。
例如:
- <!ENTITY header SYSTEM "header.xml">
- <!ENTITY logo PUBLIC "-//Tech Corp//Logo//EN" "logo.png">
复制代码
在XML中使用:
- <document>
- &header;
- <content>...</content>
- </document>
复制代码
7.3 参数实体
参数实体只能在DTD内部使用,以%开头,主要用于DTD的模块化和重用。
例如:
- <!ENTITY % common-elements "title | author | publisher">
- <!ELEMENT book (%common-elements;, price)>
- <!ELEMENT magazine (%common-elements;, issue)>
复制代码
8. 常见错误和解决方法
在实现DTD元素嵌套规则时,可能会遇到一些常见错误。下面列出了一些常见错误及其解决方法。
8.1 无效的元素嵌套
错误示例:
- <book>
- <price>29.99</price>
- <title>XML Guide</title> <!-- title元素应该在price之前 -->
- </book>
复制代码
解决方法:确保子元素的顺序符合DTD中定义的序列规则。如果顺序不重要,可以使用选择(|)而不是序列(,)。
8.2 缺少必需的元素
错误示例:
- <book>
- <title>XML Guide</title>
- <!-- 缺少必需的author元素 -->
- <price>29.99</price>
- </book>
复制代码
解决方法:确保所有必需的元素(没有?或*修饰符的元素)都包含在XML中。
8.3 无效的元素重复
错误示例:
- <book>
- <title>XML Guide</title>
- <author>John Doe</author>
- <title>Advanced XML</title> <!-- title元素不能重复 -->
- <price>29.99</price>
- </book>
复制代码
解决方法:检查DTD中元素的出现次数规则。如果元素需要重复出现,确保在DTD中使用了+或*修饰符。
8.4 混合内容模型错误
错误示例:
- <!ELEMENT description (#PCDATA, emphasis)*> <!-- 错误的混合内容语法 -->
复制代码
解决方法:在混合内容模型中,子元素必须用竖线(|)分隔,并且整个列表后面必须跟一个星号(*)。
正确的语法:
- <!ELEMENT description (#PCDATA | emphasis)*>
复制代码
8.5 递归元素定义
错误示例:
- <!ELEMENT section (title, section)> <!-- 直接递归,会导致无限循环 -->
复制代码
解决方法:避免直接递归。如果需要递归结构,可以使用可选或可重复的修饰符:
- <!ELEMENT section (title, section*)> <!-- 允许零个或多个嵌套的section -->
复制代码
9. DTD验证工具
为了确保XML文档符合DTD定义的规则,可以使用各种验证工具:
9.1 在线验证工具
• XML Validation by W3Schools
• FreeFormatter XML Validator
9.2 命令行工具
• xmllint(来自libxml2库)
- xmllint --dtdvalid book.dtd book.xml
复制代码
• java org.apache.xerces.parsers.SAXParser -v book.xml
9.3 编程语言库
大多数编程语言都提供了XML验证的库,例如:
Java:
- import javax.xml.XMLConstants;
- import javax.xml.parsers.DocumentBuilder;
- import javax.xml.parsers.DocumentBuilderFactory;
- import javax.xml.validation.Schema;
- import javax.xml.validation.SchemaFactory;
- import org.xml.sax.SAXException;
- import java.io.File;
- import java.io.IOException;
- public class XMLValidator {
- public static void main(String[] args) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setValidating(true);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- // 设置错误处理器
- builder.setErrorHandler(new org.xml.sax.ErrorHandler() {
- public void warning(org.xml.sax.SAXParseException e) throws SAXException {
- System.out.println("Warning: " + e.getMessage());
- }
-
- public void error(org.xml.sax.SAXParseException e) throws SAXException {
- System.out.println("Error: " + e.getMessage());
- }
-
- public void fatalError(org.xml.sax.SAXParseException e) throws SAXException {
- System.out.println("Fatal error: " + e.getMessage());
- }
- });
-
- // 解析并验证XML文档
- builder.parse(new File("book.xml"));
- System.out.println("XML is valid.");
- } catch (Exception e) {
- System.out.println("XML is not valid because " + e.getMessage());
- }
- }
- }
复制代码
Python:
- from lxml import etree
- def validate_xml(xml_file, dtd_file):
- try:
- dtd = etree.DTD(dtd_file)
- xml = etree.parse(xml_file)
- result = dtd.validate(xml)
- if result:
- print("XML is valid.")
- else:
- print("XML is not valid:")
- for error in dtd.error_log:
- print(f"Line {error.line}: {error.message}")
- except Exception as e:
- print(f"Validation error: {str(e)}")
- # 使用示例
- validate_xml("book.xml", "book.dtd")
复制代码
10. DTD与其他XML模式定义语言的比较
虽然DTD是XML文档结构验证的早期标准,但现在还有其他几种更现代的XML模式定义语言。下面将DTD与其他几种主要的XML模式定义语言进行比较。
10.1 DTD vs. XML Schema (XSD)
示例比较:
DTD示例:
- <!ELEMENT book (title, author, price)>
- <!ELEMENT title (#PCDATA)>
- <!ELEMENT author (#PCDATA)>
- <!ELEMENT price (#PCDATA)>
- <!ATTLIST book
- id ID #REQUIRED
- lang CDATA "en"
- >
复制代码
等效的XSD示例:
- <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:element name="book">
- <xs:complexType>
- <xs:sequence>
- <xs:element name="title" type="xs:string"/>
- <xs:element name="author" type="xs:string"/>
- <xs:element name="price" type="xs:decimal"/>
- </xs:sequence>
- <xs:attribute name="id" type="xs:ID" use="required"/>
- <xs:attribute name="lang" type="xs:string" default="en"/>
- </xs:complexType>
- </xs:element>
- </xs:schema>
复制代码
10.2 DTD vs. RELAX NG
RELAX NG示例(XML语法):
- <grammar xmlns="http://relaxng.org/ns/structure/1.0">
- <start>
- <element name="book">
- <attribute name="id">
- <text/>
- </attribute>
- <optional>
- <attribute name="lang">
- <text/>
- </attribute>
- </optional>
- <element name="title">
- <text/>
- </element>
- <element name="author">
- <text/>
- </element>
- <element name="price">
- <text/>
- </element>
- </element>
- </start>
- </grammar>
复制代码
RELAX NG示例(紧凑语法):
- element book {
- attribute id { text },
- attribute lang { text }?,
- element title { text },
- element author { text },
- element price { text }
- }
复制代码
10.3 选择建议
• DTD:适合简单的XML结构,需要广泛的兼容性,或者处理遗留系统。
• XML Schema (XSD):适合复杂的XML结构,需要丰富的数据类型和约束,或者与Web服务技术(如SOAP)一起使用。
• RELAX NG:适合需要高度可读性和灵活性的场景,特别是文档密集型的XML应用。
11. 最佳实践和高级技巧
为了充分利用DTD并提高XML文档的结构化数据质量,以下是一些最佳实践和高级技巧。
11.1 模块化DTD
对于大型DTD,使用参数实体将其模块化,提高可维护性。
- <!-- 定义公共参数实体 -->
- <!ENTITY % common-elements "title | author | publisher">
- <!ENTITY % common-attributes "id ID #REQUIRED">
- <!-- 定义元素组 -->
- <!ENTITY % book-content "(title, author+, publisher?, price)">
- <!-- 使用参数实体 -->
- <!ELEMENT book (%book-content;)>
- <!ATTLIST book %common-attributes;>
复制代码
11.2 使用条件段
条件段允许根据特定条件包含或排除DTD的一部分,这对于创建可配置的DTD很有用。
- <![INCLUDE[
- <!-- 这部分将被包含 -->
- <!ELEMENT book (title, author, price)>
- ]]>
- <![IGNORE[
- <!-- 这部分将被忽略 -->
- <!ELEMENT book (title, author, publisher, price)>
- ]]>
复制代码
11.3 命名约定
采用一致的命名约定可以提高DTD的可读性和可维护性:
• 元素名使用小写字母,单词之间用连字符分隔(如book-title)
• 属性名使用小写字母,单词之间用连字符分隔(如book-id)
• 参数实体名以百分号(%)开头,后面跟描述性名称(如%common-elements;)
• 通用实体名使用描述性名称(如&company-name;)
11.4 文档化DTD
使用注释来文档化DTD,解释元素和属性的用途。
- <!-- =============================================================== -->
- <!-- 图书目录DTD -->
- <!-- 版本: 1.0 -->
- <!-- 最后更新: 2023-10-01 -->
- <!-- =============================================================== -->
- <!-- 根元素:catalog,包含一个或多个book元素 -->
- <!ELEMENT catalog (book+)>
- <!-- book元素表示一本图书,包含title、一个或多个author、可选的publisher和price -->
- <!ELEMENT book (title, author+, publisher?, price)>
复制代码
11.5 避免过度约束
虽然严格的约束可以确保数据质量,但过度约束可能会使DTD难以使用和维护。找到适当的平衡点:
• 只对真正必要的元素和属性使用#REQUIRED
• 考虑使用ANY内容模型处理可能变化的结构
• 对于可选的元素,使用?修饰符而不是将其完全排除
11.6 版本控制
随着需求的变化,DTD可能需要更新。使用版本控制来管理DTD的演变:
- <!-- DTD版本信息 -->
- <!ENTITY % version "1.1">
- <!ENTITY % version-date "2023-10-01">
- <!-- 版本特定的声明 -->
- <![%version;[
- <!ELEMENT book (title, author+, publisher?, price, isbn?)>
- ]]>
复制代码
12. 总结
DTD是XML文档结构验证的重要工具,通过正确实现元素嵌套规则,可以显著提高结构化数据的质量。本文详细介绍了DTD的基本语法、元素声明、嵌套规则、属性声明、实体使用等方面的内容,并提供了丰富的示例和最佳实践。
正确使用DTD可以带来以下好处:
1. 数据一致性:确保XML文档遵循预定义的结构,提高数据的一致性。
2. 数据完整性:通过必需元素和属性的声明,确保关键数据不会缺失。
3. 错误减少:在数据创建和交换过程中及早发现结构错误。
4. 文档化:DTD本身可以作为数据结构的文档,帮助理解数据模型。
5. 互操作性:标准化的数据结构有助于不同系统之间的数据交换。
虽然现在有更现代的XML模式定义语言(如XML Schema和RELAX NG),但DTD因其简洁性和广泛兼容性仍然是一个有价值的选择,特别是在处理遗留系统或需要简单验证的场景中。
通过遵循本文提供的指南和最佳实践,您可以有效地使用DTD来定义XML文档的元素嵌套规则,从而提升结构化数据的质量,为数据交换和处理奠定坚实的基础。 |
|