一般的用户无需自己编写 DTD 文件和掌握 DTD 的完整语法,只要建立对 DTD 文件的直观认识和概念就可以了。因此,本章内容了解即可。
1.5.1 元素定义
在 DTD 文件中,如何定义 XML 文档中允许使用的元素呢?每一条 <!ELEMENT ...> 语句定义一个元素。
<!ELEMENT ...> 语句的通用语法格式为:<!ELEMENT 元素名称 使用规则>
例如:<!ELEMENT 书架 (书名,作者,售价)>
元素的使用规则定义了元素中包含的组成成分,以及每一种组成成分出现的次数、次序,还可以是某些成分进行某种关系组合后出现的次数、次序。DTD 通过定义元素的使用规则来描述整个 XML 文件的文档结构,并对文档结构中的每一个细节进行说明。DTD 不但定义了 XML 文档中可能存在的元素,而且定义了元素的具体类型。
下面,详细介绍一下 DTD 元素的使用规则可以有的形式:
- (#PCDATA) 表示元素中嵌套的内容是普通文本字符串。其中,关键字 PCDATD 是 Parsed Character Data (被解析的字符数据)的简写,因此,在元素内容中出现的特殊字符需要在相应的预定义实体表示。
- 用一对圆括号 () 将元素中要嵌套的子元素括起来。如,(书名,作者,售价) 表示元素中要嵌套书名、作者、售价等子元素。
- 元素中也可以嵌套文本字符串和其它子元素的混合。如,(书名,#PCDATA)。
- 关键字 EMPTY 表示元素中不包含任何子元素和普通文本字符串,这种情况用于定义 XML 文档中的空元素。如,<!ELEMENT HR EMPTY> 语句在 XML 文档中所定义的元素形式为 <HR/>。
- 关键字 ANY 表示元素中可以有任何类型的子元素和普通文本字符串,以及它们的混合,还可以不包含任何内容。如,<!ELEMENT 联系人列表 ANY> 语句表示在 XML文件中定义的 <联系人列表> 元素中可以嵌套任何形式的内容。除了根元素之外,在定义其他元素时使用关键字 ANY 都是不好的习惯,这样就失去了 DTD 对 XML 文档的约束效果。
对于 DTD 文件中的元素定义,还有以下补充说明:
- XML DTD 使用与 XML 文档同样的注释方式,即 <!-- 注释内容 -->。
- 每条元素定义语句的顺序是无关紧要的。
- 在 XML 文档结构中具有不同用途的元素不能使用相同的元素名,这样会引起文件中各个元素的混淆。
- 一个元素的各个组成成分可以以任意顺序出现,也可以强制遵循一定的顺序:当使用规则中的各个组成成分用空白字符分隔,那么他们的出现顺序没有严格要求;如果元素的使用规则中的各个组成成分用逗号(,)分隔,那么他们在 XML 文档中出现的顺序必须与它们的排列顺序一致。
- 如果一个元素的各个组成成分以竖杠(|)分隔,那么XML 文档中只能出现它们之中的任何一个,也就是说竖杠字符(|)表示各个组成成分之间的 “或者” 关系。
- 在元素的使用规则中可以通过正则表达式来定义子元素出现的次数。元字符 “+” 用于表示它修饰的成分必须出现一次或多次;元字符 “?” 表示它修饰的成分只能出现一次或不出现;元字符 “*” 表示它修饰的成分可以不出现,也可以出现一次或多次;不使用任何元字符表示该成分必须且只能出现一次。
- 一对圆括号 () 可用于将括在其中的内容组合成一个可统一操作的分组,分组中可以嵌套更小的分组。如:
<!ELEMENT MYFILE((TITLE*,AUTHOR?,EMALE)* | COMMENT)>
在 XML 文档中出现 MYFILE 元素要么包含 COMMENT 元素,要么包含零个、一个或多个 (TITLE*,AUTHOR?,EMALE) 所表示的分组。分组里包含零个、一个或多个 TITLE 子元素,接着是零个或一个 AUTHOR 子元素,再接着是一个 EMALE 子元素。
1.5.2 属性定义
在 DTD 文档中如何给 XML 文档中的元素定义属性呢?语法格式如下:
<!ATTLIST 元素名
属性名1 属性类型 设置说明
属性名2 属性类型 设置说明
......
>
ATTLIST 用于定义一个元素的所有属性设置信息,可以定义一个或多个属性。
例如,有一个 <商品> 的元素,在 XML 文档中的定义如下:
<商品 类别="服装" 颜色="黄色">...</商品>
显然,<商品> 元素有两个属性,即 “类别” 和 “颜色”。在 DTD 中,怎么定义<商品> 元素的属性信息呢?
<!ATTLIST 商品
类别 CDATA #REQUIRED
颜色 CDATA #IMPLIED
>
其中,CDATA 是一种属性类型,表示属性值为普通文本字符串;#REQUIRED 和 #IMPLIED 是属性的设置说明,#REQUIRED 表示必须设置该属性,#IMPLIED 表示该属性可有可无。
在 DTD 中定义元素的属性,有以下设置说明:
- #REQUIRED 表示必须设置该属性。
- #IMPLIED 表示可以设置,也可以不设置该属性。
- #FIXED 表示该属性的取值固定位一个默认值,在 XML 文件中不能将该属性设置为其他值。使用 #FIXED 关键字时,还需为该属性提供一个默认值。
- 直接使用默认值:如果在属性设置说明部分没有使用上面任何一种关键字,而是直接指定一个默认值的话,那么在 XML 文件中可以设置,也可以不设置该属性。如果 XML 文件中没有设置该属性,该属性将被自动设置,设置值为 DTD 中定义的默认值;如果 XML 文件中设置了该属性,可以使用新的属性值来覆盖 DTD 中定义的默认值。
对于一个属性究竟使用哪种设置说明,需要根据实际情况来决定。下面是一个综合了上面几种属性设置说明的例子:
<!ATTLIST 页面作者
姓名 CDATA #IMPLIED
年龄 CDATA #IMPLIED
联系信息 CDATA #REQUIRED
网站职务 CDATA #FIXED "页面作者"
个人爱好 CDATA "上网"
>
在 DTD 中定义元素的属性时,可以将属性设置成下面10种类型。
(1)CDATA
属性类型为 CDATA,表示属性值为普通文本字符串。当然,在属性设置值中出现的特殊字符,也需要使用其转义字符序列来表示。如,用 & 表示字符 &,用 < 表示字符 <,用 " 表示字符 "。
(2)ENUMERATED(枚举类型)
属性的类型是可以是一组取值的列表,在 XML 文档中设置的属性值只能是这个列表中的某个值,这种特殊的属性类型被称之为 ENUMERATED(枚举类型)。需要注意的是,在 DTD 定义中并不会出现关键字 ENUMERATED。
例:enum.xml
<?xml version="1.0" encoding="GB2312" standalone="yes"?>
<!DOCTYPE 购物篮 [
<!ELEMENT 购物篮 ANY>
<!ELEMENT 肉 EMPTY>
<!ATTLIST 肉 品种 (鸡肉 | 牛肉 | 鱼肉 | 猪肉) "鸡肉">
]>
<购物篮>
<肉 品种="鱼肉" />
<肉 品种="牛肉" />
<肉/>
</购物篮>
在上面的例子中,<购物篮> 元素中的第三个子元素实际也具有 “品种” 这个属性,且该属性取值为 “鸡肉”。
(3)ID
使用关键字 ID 作为属性类型时,表示属性的设置值将用于唯一标识一个 XML 文件中的某个元素,这种 ID 类型的属性通常由 XML 文件的解析处理程序或脚本语言使用。
ID 属性的值必须符合一定的名称规范,即只能由字母、数字或下划线开始,其中不能出现空白字符(例如,空格和制表符)。不要给 ID 类型的属性指定默认值,容易造成不同的元素具有相同标识名的情况,更不能使用 #FIXED 作为 ID 类型的属性的设置说明。ID 类型的属性通常使用 #REQUIRED 作为设置说明。如果并不需要要求每个元素都有自己的标识时,也可以使用 #IMPLIED 作为属性的设置说明。
例:id.xml
<?xml version="1.0" encoding="GB2312" standalone="yes"?>
<!DOCTYPE 联系人列表 [
<!ELEMENT 联系人列表 ANY>
<!ELEMENT 联系人 (姓名,EMAIL)>
<!ELEMENT 姓名 (#PCDATA)>
<!ELEMENT EMAIL (#PCDATA)>
<!ATTLIST 联系人 编号 ID #REQUIRED>
]>
<联系人列表>
<联系人 编号="1">
<姓名>张三</姓名>
<EMAIL>zhangsan@it315.org</EMAIL>
</联系人>
<联系人 编号="2">
<姓名>李四</姓名>
<EMAIL>lisi@it315.org</EMAIL>
</联系人>
</联系人列表>
(4)IDREF 和 IDREFS
使用关键字 IDREF 作为属性类型时,一个元素的 IDREF 类型的属性设置值将是同一个 XML 文件中的另一个元素的 ID 类型的属性的设置值。
例:idref.xml
<?xml version="1.0" encoding="GB2312" standalone="yes"?>
<!DOCTYPE 联系人列表 [
<!ELEMENT 联系人列表 ANY>
<!ELEMENT 联系人 (姓名,EMAIL)>
<!ELEMENT 姓名 (#PCDATA)>
<!ELEMENT EMAIL (#PCDATA)>
<!ATTLIST 联系人 编号 ID #REQUIRED>
<!ATTLIST 联系人 上司 IDREF #IMPLIED>
]>
<联系人列表>
<联系人 编号="2">
<姓名>张三</姓名>
<EMAIL>zhangsan@it315.org</EMAIL>
</联系人>
<联系人 编号="1" 上司="2">
<姓名>李四</姓名>
<EMAIL>lisi@it315.org</EMAIL>
</联系人>
</联系人列表>
IDREFS 关键字用于表示 IDREF 的列表类型,一个元素的 IDREFS 类型的属性设置值可以是同一个 XML 文件中的另外多个元素的 ID 类型的属性的设置值,每个 ID 属性值之间用空格分隔。
(5)NMTOKEN 和 NMTOKENS
NMTOKEN 是 Name Token 的简写,意思是名称记号,它表示由一个或多个字母、数字、句点(.)、连字号(-)或下划线(_)所组成的一个名称。除了第一个字符位置外,NMTOKEN 属性的设置值中也可以包含冒号(:)。NMTOKENS 关键字用于表示一种列表类型,一个元素的 NMTOKENS 类型的属性设置值可以是同一个 XML 文件中的另外多个元素的 NMTOKEN 类型的属性的设置值。每个 NMTOKEN 属性值之间用空格分隔。
例如,对于下面的 DTD 定义语句:
<!ELEMENT 用户 EMPTY>
<!ATTLIST 用户 姓名 NMTOKEN #REQUIRED>
<!ELEMENT 数据 (#PCDATA)>
<!ATTLIST 数据 授权用户 NMTOKENS #IMPLIED>
与这段 DTD 定义语句对应的 XML 数据片段如下:
<用户 姓名="zhangsan" />
<用户 姓名="lisi" />
<数据 授权用户="zhangsan lisi">
这里是一些授权访问的数据
</数据>
(6)NOTATION
现实世界中存在着很多无法或不易用 XML 格式组织的数据,如图像、声音、影像,等等。对于这些数据,XML 应用程序常常并不提供直接的应用支持,但可以通过设置 NOTATION 类型的属性来让一个外部应用程序处理。
NOTATION 类型的属性值为在 DTD 中使用 <!NOTATION ...> 语句定义的一个 notation(符号)。notation 定义语句分为两种情况:
<!NOTATION 符号名 SYSTEM "MIME类型">
<!NOTATION 符号名 SYSTEM "URL路径名">
第一种指定数据的 MIME 类型,第二种指定处理程序的 URL 路径。
例:notation.xml
<?xml version="1.0" encoding="GB2312" standalone="yes" ?>
<!DOCTYPE 文件 [
<!NOTATION mp SYSTEM "movPlayer.exe">
<!NOTATION gif SYSTEM "Image/gif">
<!ELEMENT 文件 ANY>
<!ELEMENT 电影 EMPTY>
<!ATTLIST 电影 演示设备 NOTATION (mp | gif) #REQUIRED>
]>
<文件>
<电影 演示设备="mp">
</文件>
(7)ENTITY 和 ENTITYS
ENTITY 即 “实体” 的意思。关于实体定义的细节,下节介绍。使用 ENTITY 作为属性的类型时,表明其属性值必须为在 DTD 中使用 <!ENTITY ...> 语句定义的一个实体(entity)的引用。例如,下面的 DTD 定义语句:
<!ENTITY it315 "IT资讯交流网,www.it315.org">
<!ELEMENT 电影 EMPTY>
<!ATTLIST 电影 来源 ENTITY #REQUIRED>
与这段 DTD 定义语句对应的 XML 数据片段如下:
<电影 来源="&it315;">
只有引用实体才可以作为 ENTITY 类型的属性的设置值,参数实体不能用做 ENTITY 类型的属性的设置值。ENTITYS 关键字用于表示一种列表类型,一个元素的 ENTITYS 类型的属性设置值可以是多个实体的引用,每个实体的引用之间用空格分隔。
1.5.3 实体定义
在 DTD 中可以预先定义一个或多个实体,实体的根本作用是为一段文本内容创建一个别名,以后在 XML 文档中就可以多次引用这个别名,XML 解析器程序将把 XML 文档书写中出现的别名引用转变成其所对应的的文本内容。
在 DTD 定义中,一条 <!ENTITY ...> 语句用于定义一个实体。实体可分为两种类型:引用实体和参数实体。
1. 引用实体
引用实体两种语法定义格式:
<!ENTITY 实体名称 "实体内容">
<!ENTITY 实体名称 SYSTEM "外部 XML 文档的URL">
引用实体主要在 XML 文档中被引用,其引用方式为:
&实体名称;
如果实体名称是通过第一种语法格式定义的,它将被直接转变成实体内容。如果实体名称是通过第二种语法格式定义的,它将被替换成外部 XML 文档中的内容,即在当前 XML 文档中的实体名称引用处插入外部 XML 文档中的内容。例如,在一个 XML 文档中要引入另外一个包含版权信息的 XML 文档,可以采用如下语法:
<!DOCTYPE copyright [
<!ENTITY copyright SYSTEM "http://www.it315.org/copyright.xml">
]>
......
©right;
例子:book2.dtd
<!ENTITY author "www.it315.org">
<!ELEMENT 书架 (书+)>
<!ELEMENT 书 (书名,作者,售价)>
<!ELEMENT 书名 (#PCDATA)>
<!ELEMENT 作者 (#PCDATA)>
<!ELEMENT 售价 (#PCDATA)>
book2.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE 书架 SYSTEM "book2.dtd">
<书架>
<书>
<书名>Java 就业培训教程</书名>
<作者>&author;</作者>
<售价>39.00 元</售价>
</书>
</书架>
注意,在 book2.xml 中引用了 book2.dtd 文件中定义的实体,显然 book2.xml 不能独立使用,standalone 属性值应为 no。
2. 参数实体
参数实体被 DTD we年自身使用,它的定义语法格式为:
<!ENTITY % 实体名称 "实体内容">
对参数实体进行引用的方式为:
%实体名称;
下面是一个使用参数实体的 DTD 定义:
<!ENTITY % TAG_NAMES "姓名 | EMAIL | 电话 | 地址">
<!ENTITY 个人信息 (%TAG_NAMES; | 生日)">
<!ENTITY 客户信息 (%TAG_NAMES; | 公司名)">
对参数实体的引用实际上是一个较严格的文本替换过程。在上面的 DTD 定义中,对于“个人信息”和“客户信息”这两个元素的定义规则中都包含“姓名 | EMAIL | 电话 | 地址”这一相同的部分,所以将这部分内容定义成一个名称为 TAG_NAMES 的参数实体,然后将“个人信息”和“客户信息”这两个元素定义规则中的“姓名 | EMAIL | 电话 | 地址”部分替换成对 TAG_NAMES 这个参数实体的引用。
最后需要提醒读者,无论人们还是计算机程序软件,要想理解 DTD 文档中各个元素或属性的语义,仅仅能够看懂 DTD 文档的语法还不够,还必须掌握相关领域知识。因为 DTD 定义了 XML 文档必须遵循什么样的结构,规定了 XML 文件中可出现的元素名、属性名,以及元素之间的嵌套关系,但是 DTD 并没有表达元素的语义,即元素所代表的具体意义和作用。元素的语义存在于文档之外,它存在于文档作者和读者心中或者存在于生成和读取文档的计算机程序软件中。例如,对于下面的 DTD 定义语句:
<!ELEMENT filter-mapping (filter-name, (url=pattrrn | servlet-name))>
你很容易看懂这条语句的 DTD 含义,它表示在 XML 文档中可以出现名称为 filter-mapping 的元素,该元素中必须嵌套两个子元素,第一个元素必须是 filter-name,第二个元素必须是 url-pattern 和 servlet-name 中的一个。但是,你知道 filter-name 元素代表的是什么东西吗?url-pattern 和 servlet-name 元素代表的又是什么东西呢?显然,只有那些具备相关领域知识的人们参看该 DTD 文档制定者提供的说明书后,才能明白。用于处理 XML 文档的程序软件也需要知道每个元素所代表的意义,才能知道在程序中该如何使用所读取到的各个元素。
====== 以上内容来源于 张孝祥老师的《深入体验Java Web开发内幕 》,记录下来,只作为个人读书笔记。===========