第3章文档类型定义
下载
第3章文档类型定义
上一章介绍了如何编写格式正规的X M L文档.然而,当你开发符合XML 1.0的文档结构时,
出现了这样一个有趣的问题:你如何与其他人交流你设计的结构 主流的浏览器已经支持或者
正在准备支持X M L,但是这仅限于显示X M L的内容.如果你开发的程序不仅用到X M L,而且创
建了新的X M L词汇表,即:将你的设计意图隐藏在代码中.那么,为了使XML 1.0的其他用户
能够理解符合你创建的词汇表的文档的结构,作为X M L词汇表的设计者,你必须通过某种通用
的方式说明词汇表的语法规则.为此,XML 1.0提供了一种机制――文档类型定义(D o c u m e n t
Type Definition,D T D),并将其作为规范的一部分.D T D使用正式的语法定义X M L文档的结构
和允许值.你在上一章看到的X M L是格式正规的X M L.它符合X M L的基本语法规则,并且没有
其他任何语法约定.在本章,我们将创建有效的X M L:它不仅遵循X M L的语法规则,而且受到
你所创建的词汇表规则的约束.
D T D将带来以下优越性.首先,通过创建D T D,能够正式而精确地定义词汇表.所有词汇
表规则都包含在D T D中.凡是未在D T D中出现的规则都不属于词汇表的一部分.许多解析器可
以利用D T D验证文档实例的有效性.只要在文档实例中写入一条简单的声明语句,解析器就能
够获取D T D,并将其中的内容与文档实例进行比较.另外,X M L创作工具也可以通过类似的方
式使用D T D.一旦选择了D T D,创作工具就能够实施D T D中的规则,它根据D T D中说明的结构,
仅允许用户在文档中添加D T D允许的元素或属性.
XML 1.0推荐标准专门描述了如何构建D T D,以及如何将它与根据其中规则编写的文档相关
联.它还定义了解析器应该对D T D执行的处理.在本章中,我们将讨论使用D T D的原因.除此
之外,我们还将介绍XML 1.0 DTD的语法规则,以及如何在文档实例与D T D之间建立关联.利
用以上知识,我们将为有关图书目录的例子创建D T D.
3.1 为何需要正式的结构
当你编写的代码要对以特殊词汇表为依据的文档进行操作时,实际上你是在创建文件资料.
你的源代码中溶入了词汇表规则.代码必须遵循某种结构;当结构改变时,必须修改代码.通
常情况下,这是可以接受的.设计者可以将他的设计意图传达给一小组程序员,应用程序中的
所有代码都将遵照这些假设进行编写.毕竟,编写完全数据驱动的代码是非常困难的.
然而,如果没有显式的文件资料,就无法可靠地捕获文档中的错误.唯一的错误检测机制
就是运行代码.如果你的代码执行通过,或者文档以一种出乎意料的方式偏离了设计者的初衷,
就很难检测出错误.最终,你的应用程序将无法实现预期的目标.
为了解决上述问题,需要依靠清晰,准确的语法规则文档,它应该包含词汇表允许的所有
规则.如果配备了这样的文档,程序员就不必为了确认对词汇表的理解程度与词汇表的设计者
进行面对面的交流.如果文档本身也是用一种正式的(具有严格精确的格式)语法书写的,解
析器就能够阅读这些规则.由此形成了一种可靠错误检测机制.解析器能够指出任何检测到的
词汇表错误,你可以先修改这些错误,然后再着眼于应用程序的逻辑.
3.1.1 文档域
X M L文档可以看作是程序中数据结构的快照.它们用于程序之间的信息交流.这些信息都
属于某个应用领域―你要解决的问题空间.如果你的X M L词汇表所构建的模型非常适合于要
解决的问题,就能够简化应用程序的编写和维护.为了设计出有效的X M L词汇表,你必须深入
分析应用程序要解决的问题.如果你的X M L仅仅符合格式正规约束,可能很难明确地反映出商
业过程.你不能理所当然地认为你的X M L例子能够覆盖每种可能出现的情况.即使真的如你所
愿,它们也不能以有效的方式传递你掌握的知识.
相反,D T D能够通过定义记录词汇表中的所有信息.你在设计词汇表时考虑到的所有问题
都必须写入D T D.从而,其他人可以通过D T D了解你对问题的理解(或者至少可以知道你针对
这个问题所记录下来的内容).D T D具有以下两个作用:将你掌握的知识提供给程序,同时获得
了文件资料.
3.1.2 验证文档的有效性
如果格式正规的文档是遵循一些隐式规则编写的,解析器无法根据这些规则检查其中的错
误.整个系统的完整性取决于创建和使用X M L的应用程序的完整性.代码中的错误可能很难被
发现.它们还可能引起其他程序的中断,或者导致错误的数据进入系统.然而,XML 1.0推荐标
准规定了验证有效性的解析器应该具有的功能.如果某个X M L文档引用了D T D,验证有效性的
解析器应该读取D T D,并确保文档符合D T D中描述的语法.如果你需要完善的错误检测机制,
只需使用D T D和验证有效性的解析器.文档语法,词汇表以及指定值中的任何错误都逃不过解
析器的眼睛.如果文档顺利通过解析器的有效性验证,你就可以放心大胆地考虑程序逻辑,不
必再纠缠于语法问题.当然,有效性验证并不能避免应用程序逻辑方面的失误,但是它能够过
滤出代码中的无效数据.
对于I n t e r n e t应用程序来说,这一点尤为重要.你不能假设你要处理的应用程序经历了与
你的代码同样严格的质量控制.为另一个企业服务的编程小组可能针对特定的业务或领域实现
了公共的X M L词汇表.他们对词汇表的解释可能与你的想法不同.他们的测试自然也与你的
不尽相同.利用D T D和验证有效性的解析器,就能够立即对文档的完整性进行可靠的检查.
当然,有效性检查的程度取决于D T D.了解了以上概念,下面我们开始介绍如何编写有效的
D T D.
3.2 编写DTD:通用原则
简单来说,X M L文档由元素和相应的属性组成.虽然我们还可以定义其他项,但元素和属
性是文档支持的两个主要概念.此外,元素的内容是通过其他元素或X M L标准中规定的基本类
型进行定义的.D T D必须能够定义文档中的所有元素,元素可以设置的属性,以及元素之间的
关系.
52使用XML高级编程
下载
3.2.1 将DTD与XML文档相关联
D T D是与文档相关的.通常,文档中包含一条用于与D T D建立关联的指令,当验证有效性
的解析器读到该指令时,它获取D T D,并根据其中定义的规则对文档进行检验.下面我们将讨
论如何在D T D与文档实例之间建立关联.
1. DOCTYPE标记
我们在第2章曾经简要讨论过这个标记.为了将D T D声明与文档实例相关联,XML 1.0提供
了特殊的D O C T Y P E声明.D O C T Y P E声明必须位于X M L声明之后,且在任何文档元素之前.但
是,X M L声明和D O C T Y P E声明之间可以插入注释和处理指令.
D O C T Y P E声明包含关键字D O C T Y P E,文档根元素的名称,以及内容声明结构.在详细阐
述多少有些晦涩的语句之前,我们先通过一个例子看一下D O C T Y P E声明在文档实例中的位置.
以下是某个X M L文档的前三行:
第一行的X M L声明表示该文档符合XML 1.0的语法,第二行说明该文档使用C a t a l o g词汇表―
文档类型"C a t a l o g".更确切地说,文档的第一个元素或称根元素最好是C a t a l o g,否则解析器会产
生错误.在本例中,根元素恰好是C a t a l o g.
程序段中的省略号隐藏了D O C T Y P E声明的其余部分.真正的声明到底在哪里呢 X M L规范
定义了两种提供声明的方法.你可以在独立的D T D文件中提供外部子集声明,或者在D O C T Y P E
声明体中包含内部子集,或者同时采用上述两种方式.在上例中(内部D T D与外部D T D相混合
的情况),内部D T D可以添加新的声明,或者覆盖外部D T D中的声明.(根据X M L规范的定义,
解析器首先读取内部子集,其中的声明具有较高的优先权.)
在我们讨论如何提供声明之前,还有一个问题要考虑.正如我们在第2章所看到的,X M L声
明可以有s t a n d a l o n e属性.该属性可以取以下两个值:y e s或n o.如果属性值为y e s,说明文档实
例没有会影响到传递给应用程序的文档信息的外部声明.如果属性值为n o,说明文档有外部声
明,且声明中包含的值是正确定义文档内容所必需的――例如,特殊的缺省值.
下面的代码是前一例子的变种,它表示我们需要的所有声明都包含在文档中:
在实际应用中,可选的s t a n d a l o n e属性很少出现.属性值y e s并不能保证文档没有任何类型
的外部依赖,而仅仅意味着即使在处理过程中不考虑外部声明,在作为接收方的应用程
序关注的范围内,文档不会产生错误(即使文档可能是格式正规的X M L).因此,它的主
要用途是作为解析器和其他应用程序的标志,表示是否需要获取外部内容.
现在,让我们清除前两个程序段中的省略号.D O C T Y P E声明由以下部分组成:关键字,文
档的根元素名称(在本例中是C a t a l o g),可选的外部标识符,以及可选的标记声明块.外部标识
符用于外部D T D(外部子集)的命名和定位,标记声明块是由标记声明(内部子集)构成的.
首先让我们来讨论标记声明块.
第3章文档类型定义使用53
下载
2. 内部D T D子集
如果必要的话,我们可以将所需的全部声明都包含在内部子集中,正如你在上一章所看到
的例子.D O C T Y P E标记中的标记声明块由以下几部分构成:左方括号,声明列表和右方括号.
下面是一个简单的例子:
内部D T D非常有用.你永远也不会为找不到D T D而头疼.然而,即便是最简单的X M L词汇
表,内部D T D也会令文档的长度剧增.另外,无论文档是由人编写的,还是由程序生成的,每
个文档实例中都必须包含相同的内部D T D.即使文档的用户不打算验证文档的有效性,这些声
明也必须随文档一起传输.我们不推荐频繁地使用内部D T D,但是对于简单的词汇表―特别
是测试标记原型时,它们还是值得考虑的.
在某些情况下,设计人员可能希望同时使用内部D T D和外部D T D.内部D T D用于添加声明.
当内部D T D与外部D T D声明的项目重复时,内部声明将取代外部声明.这一特征使得设计者能
够根据特殊文档的需求调整声明,但是使用该特征时应该谨慎.如果我们过度频繁地覆盖外部
D T D,就不太合适了―这充分证明了初始设计的失败.
3. 外部D T D
从某种角度讲,外部D T D更加灵活.上一章曾经介绍过如何引用D T D;现在我们将进行更
加详细的阐述.在本例中,D O C T Y P E声明不仅包含常规的关键字和根元素名称,而且包含指示
外部D T D源的关键字和D T D的位置.X M L规范定义了几种声明源的方法.声明中可以使用关键
字S Y S T E M或P U B L I C.如果使用S Y S T E M关键字,解析器将仅根据给出的U R L寻找D T D―
D T D通过U R L显式地直接定位.在我们的例子中,位于"S Y S T E M"关键字之后的是用于命名
D T D文件的U R L.
用于定位D T D的U R L不应该包含段标识符(字符#加名称).XML 1.0建议指出,如果U R L
中包含该标识符,解析器将产生错误指示.
下面是两个例子:
和:
在第一个例子中,对于包含的D O C T Y P E声明的文档,验证其有效性所需的所有声明都位于
文件P u b C a t a l o g . d t d中.在第二个例子中,D T D文件位于虚构的世界图书馆组织的We b服务器上.
在以上两例中,P u b C a t a l o g . d t d文件应该包含C a t a l o g元素的声明.
然而,如果D T D源关键字为P U B L I C,情况就略微复杂一些.P U B L I C关键字用于声明众所
周知的词汇表.例如,假设出版界已经对图书种类D T D达成了大量共识.需要根据该词汇表解
析文档的应用程序可能会通过某种策略来定位D T D.如果这个D T D非常普及,应用程序或许有
本地拷贝.直接使用本地拷贝可能比从We b服务器上远程下载更可取.D T D可能存放在数据库
中,或者可以通过其他与应用程序相关的技术获得.如果使用P U B L I C关键字和U R I,应用程序
就有机会利用自己的算法定位D T D.
54使用XML高级编程
下载
统一资源标识符(URI)可以是URL,也可以是一个单独的名字.
例如:
如果U R I"u n i v e r s a l / P u b l i s h i n g / B o o k"对于处理这类文档的应用程序来说是已知的,应用程
序可以通过某种有效的方式自行寻找D T D.或许我们恰好有适合该领域的解析器.它可能有
D T D的本地拷贝,或者它可以访问由本地数据库服务器维护的D T D.最关键的是,寻找D T D的
方法主要是由负责处理D O C T Y P E声明的应用程序确定的.
当然,"众所周知"通常是相对的.因此,XML 1.0允许P U B L I C声明同时有公共U R I和系统
标识符.如果使用文档的应用程序或解析器不能从P U B L I C关键字提供的U R I定位D T D,它必须
使用系统标识符.
在本例中,文档的设计者允许作为接收方的应用程序根据公共的U R I自行寻找D T D.如果该
过程失败,对我们的出版领域不熟悉的通用解析器一般属于这种情况,应用程序可以从地址为
w w w. u n i v e r s a l l i b r a r y. o rg的We b服务器请求指定的文件.
3.2.2 基本标记声明
D T D通过四种标记声明定义X M L文档中允许出现的内容.表3 - 1显示了与这些声明相关的关
键字及其含义.前两个声明与X M L文档中的信息有关―元素和属性.
表3 - 1
D T D关键字含义
E L E M E N TX M L元素类型声明
AT T L I S T特定元素类型可设置的属性及这些属性的允许值声明
E N T I T Y可重用的内容声明
N O TAT I O N不需要解析的外部内容(例如:二进制数据)的格式声明,以及用于
处理这些内容的外部应用程序
后两种声明起辅助作用.特别是实体(E N T I T Y)用于简化X M L词汇表的设计.它所包含的
内容通常会在D T D或文档中反复出现,因此需要创建特殊的声明.该声明的作用类似于C / C + +
中的i n c l u d e语句,它以特定的名称作为内容的替代符.
表示法(N O TAT I O N)用于处理非X M L内容.表示法用于声明特殊的数据类,并将之与外
部程序相关联.这个外部程序就成为所声明的数据类的处理器.举例来说,如果你的文档与
J P E G图像有关,你可能需要相关的显示程序来接收和展示J P E G二进制数据.当然,你的文档依
赖于接收系统能够提供的处理器.有些设计者为了获得可移植性,宁愿放弃处理器引用.在那
种情况下,表示法将退化为一种输入机制.
我们将在下一节详细讨论表示法和实体.
3.3 正式的DTD结构
现在,你已经对D T D有了一定程度的了解,并学会了如何将它与文档相关联.我们希望前
第3章文档类型定义使用55
下载
面介绍的例子能够激发起你进一步学习如何声明文档结构的兴趣.除了前面提到的四个标记声
明,D T D还将用到其他结构.然而,我们首先来关注一下实体.
下面将要介绍的所有语法都在XML 1.0推荐标准(h t t p : / / w w w. w 3 . o rg / T R / R E C - x m l /)中有
明确的定义.有时,建议中的内容可能会令你感到迷惑,那么不妨看看由Tim Bray编写的
X M L规范解读(Annotated XML Specification),Tim Bray是X M L推荐标准的作者之一.
该文档位于h t t p : / / w w w. x m l . c o m / a x m l / t e s t a x m l . h t m.它是用X M L创建的,因此也不失为一
个有价值的XML应用实例.
3.3.1 实体
X M L提供了声明内容块的方法,你可以根据需要多次引用这些内容块,它不仅能够节省空
间,而且能够减少文档创作者的代码输入量.为了在D T D中声明实体,需要定义实体的名称及
它引用的内容.当你需要使用它时,采用特殊的语法通过名称进行引用,这种特殊的语法能够
说明你所提供的名称是实体引用.它类似于C / C + +中的d e f i n e指令,或其他形式的可替换的样板
文本或内容.文档内容中使用的实体称为通用实体(general entity).我们可以根据是否解析实
体的内容将定义进一步细化.解析实体(parsed entity)是X M L内容.实体的值称为置换文本.
相反,未解析(unparsed entity)可以是非文本内容.即使它是文本,并不一定要求是X M L.这
就是"未解析"一词的来历.如果你知道用于替换的内容不是X M L,或者甚至不是文本,那么
解析器就没有必要对它进行处理.另一方面,解析实体是要粘贴到文档内容中的X M L,因此,
解析器就必须将它传递到文档中.
下面我们将详细地讨论实体的分类,再次重申并扩展上一章介绍的内容.
1. 预定义实体
X M L必须保留某些字符用于本身格式的定义,
例如:尖括号.另外,有些字符是不可打印的.鉴
于此,X M L提供了一些预定义的实体,用户可以利
用这些实体在文档中使用上述字符,并保证不产生
冲突.因此在元素的文本内容中,可以用实体表示
一些特殊字符,以免它们在解析时与文档的标记混
淆.
任何字符都可以表示为数字引用.具体方法是在符号"& #"之后加上字符的数字值和分号
(它们之间没有空格).例如,大于号可以表示为& # 6 2 ;.对于使用频率极高的字符,X M L提供
了预定义的实体(参见表3 - 2).
例如:
2. 通用实体
通用实体是最简单的实体形式.它能够声明与某个名称相关联的可解析的文本块,我们将
通过该名称引用相应的文本.这类实体声明包含关键字E N T I T Y,实体名称和替换值.例如:
56使用XML高级编程
下载
表3 - 2
字符实体引用
& g t ;
&& a m p ;
'(单引号)& a p o s ;
"(双引号)& q u o t ;
利用这个声明,我们只需引用名称"c o p y r i g h t",就能够在文档内容的任何位置插入版权信
息.当然,进行实体引用时,我们需要通过某种方式告诉解析器这是实体引用,以免它将实体
名称与标记文本混淆.为此,我们在名称之前增加符号&,在其后增加分号.名称与定界符(&
和;)之间不含空格.例如:
值得注意的是,由于字符&是X M L的保留字符,因此如果我们需要在文档中使用它,必须
借助(前面介绍的)预定义实体.
当实体引用在解析过程中被替换为置换文本时,其结果必须是格式正规的XML.
通用实体也有外部形式,即:将置换文本存放在外部文件中.其声明形式如下:
关键字S Y S T E M用于指示外部源,后面的U R L表示文件的位置.你也可以使用P U B L I C关键
字,U R I标识符和后备的U R L组合.对于文档中的外部实体,X M L建议有一定的限制:属性值
中不能引用可解析的外部实体,以避免实体的字符编码与主文档的编码形式不同.
X M L推荐标准不要求不验证有效性的解析器读取并插入外部实体的内容.
最后,实体不能包含直接或间接的对自身的引用.因此以下声明是不合法的:
现在,让我们来讨论另一种实体:参数实体.
3. 参数实体
仅仅在D T D中使用的解析实体称为参数实体.它使我们能够简便地引用或修改D T D中常用
的结构,我们只需维护一处代码.与逐一修改D T D中出现每个结构相比,这种方法简单得多,
但是当我们打算扩展结构时,仍然需要编辑D T D.参数实体声明由以下几部分组成:E N T I T Y关
键字,百分号,名称和替换值.例如:
关键字CDATA代表字符数据;我们将在属性一节详细讨论它.
上面的置换文本是属性列表声明的一部分,它包含三个普通属性.当解析器处理上述语句
时,它会用这三个属性取代参数实体名称.如果我们需要在D T D输入该属性集合,只需引用实
体p e o p l e P a r a m e t e r s即可.
在D T D中,所有参数实体必须在引用之前进行声明.这意味着D T D内部子集不能引用在外
部子集中声明的参数实体,因为解析器首先读取内部子集―所以会导致引用出现在声明之前.
引用参数实体时,需要在实体名称之前增加百分号,在其后增加分号.定界符与名称之间
没有空格.下面的代码显示了如何引用上面定义的参数实体:
第3章文档类型定义使用57
下载
以上代码声明I n s u r e d P e r s o n元素包含4个属性:其中c a r r i e r是显式声明的,其余三个参数
(a g e,w e i g h t和h e i g h t)包含在参数实体中,当解析器用置换文本取代实体引用时,会出现元素
的完整列表.因此,上面的例子等价于以下代码段:
程序清单3 - 1
这种类型的替换形式―在声明中进行替换―只能用于D T D外部子集.在内部子集中,
参数实体引用只能位于其他声明之间;因此这类实体引用的置换文本必须是一个完整的声明,
否则将影响D T D格式的正规性.
一个格式正规的文档应该遵循的规则都可以应用于参数实体.用置换文本取代实体引用后,
仍然必须保证文档格式的正规性.当你构建参数实体时,一定要谨记这条规则.通常,在参数
实体的置换文本中使用标记时要格外谨慎.下面的例子就破坏了格式正规约束:
% m y P a r m ;的置换文本是不完整的声明,它缺少结束标记>,因此当解析器替换% m y P a r m ;时,
D T D就不再是格式正规的了.
与通用实体类似,参数实体的置换文本也可以位于外部文件中.例如:
从以上讨论可以看出,对于定义X M L文档词汇表来说,实体是一种非常有价值的工具.下
面让我们看看如何定义词汇表中的元素类型.
3.3.2 元素
元素是X M L的核心与灵魂.在D T D中,元素类型是通过E L E M E N T标记声明的.除了关键
字,标记还提供所声明类型的名称和内容规范.正如第2章所述,元素类型名要遵守X M L对名称
的限制.名称可以是字母,数字,也可以使用标点符号,如:冒号(:),下划线(_),连字符
(-)和句点(.).然而,名称不能以数字开头.它的第一个字符只能是字母,下划线或冒号.
虽然名称中可以使用冒号,但是在第7章介绍名称空间时,你会看到有关冒号的保留用法.
鉴于这方面的原因,最好避免在元素名称中使用冒号.
元素内容可以分为以下四种类型:空,元素,复合及任意.空元素中既不包含文本,也不
含子元素.但是它可以有属性.它用关键字E M P T Y来表示.元素(更确切地说是纯元素)内容
是指元素中只包含子元素,而不含文本.顾名思义,复合内容是元素和可解析字符数据
(# P C D ATA)或文本的组合.对于两种类型,我们可以通过结构表达所需的内容.复合内容和
元素内容是采用内容模型(content model)表示的.内容模型是一种规范,它定义了元素内容
的内部结构.如果你希望元素具有任意形式的内容,同时不破坏X M L的格式正规语法,应该使
58使用XML高级编程
下载
用关键字A N Y进行声明.
元素类型S o m e D a t a不含任何内容.下面是该类型的实例:
通常,在以下情况你可能会使用空元素.在文档中写入元素本身足以起到标识的作用.例
如,H T M L中的元素.你可以利用这种方法通知应用程序改变处理模式.如果你希望在文
档中插入一组相关的参数,而专门为它们建立结构又不太值得,此时你可以使用空元素,利用
它来表达参数之间的关系.稍后讨论属性时,你会对此有进一步认识,X M L定义的某些属性类
型可以用来表示一对一或一对多关系.如果你要说明的仅仅是关系本身,空元素是最适合不过
的.
A n y O l d T h i n g声明为A N Y内容,因此我们可以使
用元素和文本的任意组合.一般而言,使用A N Y内
容模型时要格外谨慎,因为解析器基本上不能提供
有效性验证.
内容模型即元素结构的声明.它是由圆括号包含的若干子元素名称,运算符和# P C D ATA关键
字的组合.运算符用于说明元素包含的元组,以及元素和字符数据之间的组合方式(参见表3 - 3).
以逗号分隔的列表表示顺序排列的元素.下面的代码声明了P e r s o n N a m e元素:
在元素实例中,F i r s t,M i d d l e和L a s t必须按指定的顺序出现.如果你希望为文档的创作者提
供选择的余地,可以参考下面的F r u i t B a s k e t元素类型声明,它可以包含A p p l e或O r a n g e,但是两
者不能同时出现:
内容模型可以嵌套.下面的例子是修改后的F r u i t B a s k e t,它包含的第一个参数是C h e r r y,第
二个参数可以从A p p l e或O r a n g e中选择其一:
根据以上声明产生的实例必须包含两个元素:C h e r r y以及A p p l e或O r a n g e,且它们必须按照
指定的顺序出现.根据声明,元素实例只可能有以下两种形式:
程序清单3 - 2
程序清单3 - 3
第3章文档类型定义使用59
下载
表3 - 3
顺序运算符含义
,(逗号)表示严格顺序
|(管道符号)表示选择
除了我们前面介绍的顺序运算符,还有一种非
常重要的运算符――元组运算符.特定的元素类型
允许多少实例 表3 - 4列出了元组运算符.
如果没有元组运算符,说明元组数为一.元组
运算符可以用于元素或内容模型,它能够产生许多
非常复杂的结构.让我们进一步修改F r u i t B a s k e t元
素类型声明:
以上内容模型组表示F r u i t B a s k e t可以有一个或多个元素类型C h e r r y的实例,以及零个或多个
A p p l e或O r a n g e的实例.而且所有C h e r r y元素必须连续出现.下面是一个正确的F r u i t B a s k e t实例:
程序清单3 - 4
如果你希望表示复合内容,需要在内容模型中包含# P C D ATA.内容模型中的元素必须以|运
算符分隔,而且整个组声明为可出现"零次或多次":
提示根据XML 1.0推荐标准中规定的语法,使用复合内容模型时,#PCDATA关键字必须
是模型中的第一个选项.
以上代码表示可以从I t e m A,I t e m B和# P C D ATA中选择零个或多个选项.它可以有以下实
例:
程序清单3 - 5
考虑以下内容模型,看看它们各自表达什么含义:
元素f o o包含两个子元素,第一个永远是元素A.第二个是B或C.
在上例中,f o o包含两个或三个按顺序排列的子元素.其中B是可选的.
60使用XML高级编程
下载
表3 - 4
元组运算符含义
可选的;可有可无
*零个或多个
+一个或多个
现在,f o o元素变得更加复杂了.它的第一个子元素可以是A,B或D.根据选择不同,它可
以有一至四个子元素.其中A是可选的,然后是B和C或D,E也是可选的.
在上例中,元素f o o可以有一个或两个子元素.可能是顺序排列的A和B,也可能是C或者D.
让我们再稍微修改一下这个模型:
在上例中,元素f o o可以包含重复的A,B对列表,或者一个单独的C或D.借助内容模型,
可以产生变化多样的子内容实例.例如:
根据上述定义,元素f o o可以包含一个A,零个或多个B,C对,以及至少一个D.
我们希望通过上述例子能够激发起你尝试更复杂模型的兴趣.内容模型的规则虽然简单,
但是它能够产生灵活多样的结构.为了测试以上定义,你可以在D T D中插入其中一段代码,然
后编写符合定义的文档,并在能够验证有效性的解析器上运行.
提示I n t e r n e t上提供了几个可以通过We b页面访问的解析器.我常用的是
http://www.stg.brown.edu/service/xmlvalid/,我经常用它来检查DTD结构.
现在让我们来看一看属性.
3.3.3 属性
属性是对元素的补充和修饰,它能够将一些简单的特性与元素相关联.通过属性,我们可
以给元素绑定大量信息.例如,在H T M L标记I M G中,S R C就是一个属性.属性在XML DTD中
是使用AT T L I S T标记声明的.对于含属性的元素,至少要通过一个AT T L I S T标记声明其属性列
表.AT T L I S T声明由以下部分构成:AT T L I S T关键字,属性修饰的元素名称,以及零个或多个
属性定义.为了增强可读性,每个属性定义通常占据单独的一行.
属性定义包含属性名称,类型和缺省声明.
在以上代码中,我们声明了一个名为A t t r i b u t e N a m e的属性,它必须在m y E l e m e n t元素实例的
起始标记中出现(# R E Q U I R E D―这是缺省设置),属性的值是字符串(C D ATA).
属性声明可以有几种不同的缺省设置,它定义了属性在文档中出现的方式.在研究属性类
型之前,我们先来看看属性声明的缺省设置.
1. 缺省值
属性声明可以有四种缺省设置,如表3 - 5所示.
表3 - 5
属性缺省设置含义
# R E Q U I R E D元素的每个实例必须包含该属性
# I M P L I E D元素实例可以选择是否包含该属性
# F I X E D加上缺省值属性的值永远固定为缺省值;如果元素中不包含该属性,解析器
第3章文档类型定义使用61
下载
(续)
属性缺省设置含义
将缺省值作为属性值
只有缺省值如果元素中不包含该属性,解析器将缺省值作为属性值.否则,
该属性可以有其他值
如果AT T L I S T声明中设置了缺省的属性值,即使文档中的某些元素实例忽略了该属性,
X M L解析器仍然会认为该属性已经被赋予了缺省值.因此,对于下面显示的属性声明,这两个
元素实例是等价的:
程序清单3 - 6
从上例可以看出,c o l o r属性声明有缺省值:b l u e.在第一个元素实例中,我们显式声明了这
个属性,而在第二个实例中,我们省略了属性.对于解析器来说,这两个实例是相同的――都
有值为b l u e的属性c o l o r.
在下面的例子中,B o o k元素包含一个名为l e v e l的属性.如果我们将l e v e l属性的缺省值设为
P r o f e s s i o n a l,考虑一下会出现什么情况.
如果文档中的B o o k元素不含l e v e l属性,任何处理该元素的应用程序都会认为元素设置了
l e v e l属性,且值为P r o f e s s i o n a l.如果缺省值出现的几率非常高,不妨采用这种方式.在这种情
况下,我们可以声明缺省值,当元素实例的属性值与缺省值相同时,可以省略该属性.
然而,这种技术也可能给应用程序带来麻烦.你必须确保所选择的缺省值对于应用程序的
处理来说是可靠的.元素的属性很容易被遗忘.在这种情况下,应用程序将使用D T D中声明的
缺省值.如果你编写的代码极其依赖于属性值的正确设置,应该使用# R E Q U I R E D关键字(或枚
举值,我们稍后会讨论有关内容),以确保属性值的显式设置.
下面显示了元素B o o k的属性列表,你不必对各部分的含义过于计较.
程序清单3 - 7
在属性列表中,首先要指定元素名称B o o k,然后是属性名称,类型,以及元素是否必须包
62使用XML高级编程
下载
含该属性.可选的属性用关键字# I M P L I E D表示.用关键字# R E Q U I R E D修饰的属性必须出现在
每个B o o k元素实例中.
表3 - 6列出了X M L定义的属性类型.
表3 - 6
属性类型含义
C D ATA字符数据(字符串)
I D特定文档中唯一的名称
I D R E F对某些具有I D属性的元素的引用,这些元素的I D属性值必须与
I D R E F属性的值相同
I D R E F S若干以空格分隔的I D R E F
E N T I T Y已定义的外部实体的名称
E N T I T I E S若干以空格分隔的E N T I T Y名称
N M TO K E N名称
N M TO K E N S若干以空格分隔的N M TO K E N
N O TAT I O N接受一个在D T D中声明为用于指示表示法类型的名称
[枚举值]接受用户显式定义的属性可选值中的一个值
下面让我们依次讨论这些属性类型.
2. CDATA
所有的内容最终都会变成文本.当属性值为纯文本时,你可以将该属性声明为C D ATA类型.
例如:
该属性的值可以是任意长度的字符串.唯一的限制是它不能包含标记.上述声明可以有以
下实例:
只要属性值是纯文本,解析器都会将它视作有效.
3. ID,I D R E F,I D R E F S:文档中的关系表示
毫无疑问,对于I D类型的属性,其值必然是具有唯一标识功能的名称.而且它们必须遵守
X M L名称定义的规则.特定元素的I D属性值在整个文档中必须是唯一的.它可以作为元素的唯
一标识符.每个元素至多有一个I D类型的属性.最后需要说明的是,I D类型的属性必须设置为
# I M P L I E D或# R E Q U I R E D,不能是# F I X E D或缺省的.可想而知,为I D提供缺省值,特别是固定
的缺省值是毫无意义的.这会破坏I D的唯一性.例如,在下面的声明中,社会保障号(S S N)
作为个人的唯一标识符,并与文件中的个人信息相关联:
如何使I D类型的属性发挥作用呢 当然是通过引用.我们可以利用它在两个对象之间建立
一对一的关系.I D R E F类型可以用于在文档中创建链接和交叉引用.I D R E F属性的值必须受到
与I D类型同样的约束.它们必须与文档中的某个I D属性具有相同的值.I D R E F值不能指向文档
中不存在的I D(但是除此之外还有其他方法,我们将在第8章介绍).在应用程序中,我们通过
第3章文档类型定义使用63
下载
I D和I D R E F实现交叉引用,而不必多次重复整个元素.如果文档中包含上述声明,可以在D T D
中写入以下声明:
我们很容易想到,i d属性是指P e r s o n中的S S N属性.则文档中可以写入以下代码:
程序清单3 - 8
通过交叉引用,C u s t o m e r可以写作具有I D R E F属性的空元素,而不必包含整个P e r s o n元素.
当我们需要P e r s o n信息时,由于P e r s o n元素的S S N属性与C u s t o m e r的i d属性具有相同的值,因此
应用程序能够通过i d找到P e r s o n元素.
有时,我们希望将一个元素与其他多个元素相关联.这就要依靠I D R E F S类型.它能够建立
一对多的关系.这类属性的值是一系列以空格分隔的I D值.其中每个I D必须满足对I D类型的约
束,当然它们必须与文档中的I D属性值相匹配.
以上代码段声明了一个空元素,它定义了项目组与成员之间的一对多包含关系.M e m b e r s属
性通过引用P e r s o n元素列举出项目组成员的标识,P e r s o n元素是在D T D的其他位置声明的,它具
有I D类型的属性.例如:
以上代码表示的项目组由三个人构成,他们的社会保障号分别是:111 - 2 2 - 3 3 3 3,2 2 2 - 11 -
4 4 4 4和1 2 3 - 4 5 - 6 7 8 9.
利用I D,I D R E F和I D R E F S,我们可以表示关系数据库中常见的关系.如果你将X M L作为本
地数据库与专用数据模式之间的转换工具,你会深刻体会到这几种类型的价值.
4. ENTITY,E N T I T I E S:可替换的内容
实体可以用于属性声明中,它能够重用公共的结构,提高代码效率.对于一个可能多次出
现的结构,你可以声明代表该结构的实体,然后通过引用实体实现对结构的调用.另外,实体
中可以包含未解析内容,并作为有效的属性值.通过这种方式,文档创作者可以引用各种类型
的数据,而不仅仅是X M L标记.如果你有一个图形文件,并希望将它作为图解,可以借助实体
将它插入文档.为此,首先将属性类型声明为E N T I T Y:
在D T D中,还要声明实体:
64使用XML高级编程
下载
N D ATA(表示法数据)关键字说明实体的数据有相应的n o t a t i o n类型(参见后面对
NOTATION类型的讨论).
而后,在X M L文档中,我们可以在属性中引用图像:
以上代码将G I F文件s a l e s _ c h a r t . g i f与S a l e s R e s u l t元素相关联.
对于经常要重用的实体,这种方法非常值得推崇.例如,在我们所举的例子中,每月只
需修改s a l e s _ c h a r t . g i f文件,就可以重用它.但是,假如实体的值需要频繁修改,这种方法
就不可取了.
为了将E N T I T Y作为属性类型,你需要执行四个步骤.前三个步骤都是在D T D(外部D T D或
内部子集)中进行声明.第四个步骤涉及特定的文档实例.我们将这四个步骤总结如下:
声明一个表示法(我们很快就会介绍有关内容)
声明一个或多个实体,以便在属性中使用
为元素声明类型为E N T I T Y的属性
在文档中创建元素类型实例,将实体名称作为属性值
正如我们能够将多个I D R E F值作为单一的属性值(I D R E F S),实体也可以有类似的特性.
这就是E N T I T I E S类型,它与I D R E F S具有类似的效果.属性值中的每个名称必须符合E N T I T Y
类型的规则,实体名称之间以空格分隔.因此,我们有以下代码(我们暂时省略表示法声
明):
程序清单3 - 9
我们关于事故现场的报告有一个A c c i d e n t S c e n e元素,其中包含现场,汽车和受害者的照片.
5. NMTO K E N,N M TO K E N S:名称记号
某些情况下,你可能希望将属性值作为离散的记号,而不是文本.为此我们可以使用枚举
类型(稍后即将讨论该类型),但是,假如我们希望值列表能够无限扩展呢 这就需要依靠X M L
中称为名称记号(name token)的类型.它在D T D中缩写为N M TO K E N.N M TO K E N类型必须
遵守元素名称的命名规则,但是其中一项限制除外.它们只能包含字母,数字,冒号,句点和
连字符.然而,与元素和属性名称不同的是,N M TO K E N的第一个字符可以是任意字符.下面
的代码说明了如何声明N M TO K E N属性:
第3章文档类型定义使用65
下载
程序清单3 - 1 0
上述代码表示元素E m p l o y e e有一个名为s e c u r i t y _ l e v e l的属性,其值符合X M L名称记号的规
则.我们可以用它来控制对机密文档的访问.由于定义属性列表时使用了N M TO K E N,而不是
枚举类型,文档创作者只需创建新的值,就能够适应新的安全级别要求,而不必每次都编辑
D T D.只要符合我们前面介绍的有效的N M TO K E N值应该遵守的规则,任何值都可以作为这种
属性的值.
显然,N M T O K E N类型使得应用程序必须承担验证值有效性的任务.然而对于枚举类型,
解析器能够提供有效性检查.
与I D R E F S和E N T I T I E S类似,你可以声明属性类型N M TO K E N S,它的值由多个名称记号构
成.每个名称必须是有效的名称记号,它们之间以空格分隔.
程序清单3 - 11
这个职员能够访问名为r e d,g r e e n,m e g a和u l t r a的安全区域.就类型而言,这些都是有效的
N M TO K E N值.与枚举类型不同,解析器不检查这些值的有效性.文档的作者必须确保自己使
用了适当的名称.
6. NOTAT I O N:非X M L数据
当我们讨论实体类型的属性时,曾经提及表示法.通过将实体名称作为属性值,可以将G I F
和J P E G图形文件与元素相关联.然而,X M L解析器不能处理二进制格式.那么,解析器的作用
是什么呢 我们可以使用表示法标识要链接到X M L文档的外部数据项的格式.表示法声明能够
说明格式的名称,以及相关的外部处理器.解析器可以根据声明将自己不能识别的数据交给外
部处理器处理.处理器声明类似于用于定位D T D文件的D O C T Y P E声明.它可以是P U B L I C或
S Y S T E M的,而且必须包含外部处理器的名称:
现在我们知道,当j p g作为表示法名称时,与之相关的数据将发送给j p g v i e w e r. e x e处理.利
用表示法,X M L文档可以容纳多种不同的数据类型.这对于报表,病历,法律文书,学术报告,
以及任何丰富多彩的多媒体演示来说都是非常有用的.但是,X M L仅仅是一个最基本的工具集.
为了提供正确的表示语义,应用程序还有大量工作要做.
通过使用关键字N O TAT I O N,可以将属性定义为表示法名称类型的.例如:
66使用XML高级编程
下载
在以上声明中,I m a g e元素可以有一个名为t y p e的属性,它是表示法类型的.该属性可选的
值有g i f和j p g.如果元素实例没有定义t y p e属性,解析器会假设该属性设置为缺省值g i f.然而,
在上述实例中,值j p g覆盖了缺省值.
7. 枚举类型:选择
名称记号的长度是不受限的.虽然N M TO K E N和N M TO K E N S属性值的格式必须符合命名规
则,但是它所允许的值是可以自由设置的.在许多情况下,我们只希望允许一小部分字符串值,
例如:y e s和n o是表示决策的枚举值;r e d,y e l l o w和g r e e n是信号灯的颜色,等等.在这些情况下,
我们要采用枚举属性.
为了声明枚举属性,在通常出现类型关键字的位置应该放置一组值.这些可选值包含在圆
括号中,并以管道符号(|)分隔.声明中的可选值不需要带引号,但是与X M L中的名称一样,
它是大小写敏感的.文档中的属性实例必须包含唯一的一个可选值,且这个值必须是在属性声
明中列举的.与其他属性值类似,枚举值必须包含在引号中.下面是两个简单的例子:
程序清单3 - 1 2
在第一个例子中,属性值只能是y e s或n o;Y E S,N O和m a y b e都是无效的.设置属性值时,
不仅必须使用枚举类型声明中提供的值,而且要注意大小写.如果你构建的枚举类型的值可能
由用户手工输入,应该考虑因大小写产生的各种变体.
现在让我们讨论D T D中使用的另一种技术.
3.3.4 条件部分
许多程序员都习惯于在程序中指定要解析的信息,仅当满足特定的条件时,编译器才解析
指定的内容.D T D提供了类似的功能,虽然它比通常的编程语言有更多的限制――运行时不能
执行条件表达式.D T D可以包含条件部分,它用于向解析器说明包含或忽略声明部分.它们能
够用来控制D T D中的相关声明块.然而,D T D内部子集不支持条件部分.
条件部分包括:惊叹号,左方括号,关键字,以及由方括号包含的声明块.如果关键字为
I N C L U D E,其中的声明被认为是D T D的一部分.如果关键字为I G N O R E,处理器虽然读取其中
的声明,但是在处理时忽略它.
程序清单3 - 1 3
第3章文档类型定义使用67
下载
在上例中,A u d i t E n t r y及其属性将成为D T D的一部分,D e b u g E n t r y及其属性则不会对D T D产
生任何影响.根据该D T D创建的文档可以使用A u d i t E n t r y,但是文档中的D e b u g E n t r y元素会被认
为是无效的.
这一特征乍看起来没有什么价值.如果你不需要某些声明,为什么还要将它们添加到D T D
中呢 如果声明包含在D T D中,为什么要使用I N C L U D E呢 实际上,条件部分应该与参数实体
配合使用.让我们更改一下以上实例,并就条件部分的用法作进一步说明.假设创建文档时,
我们在文档实例的D O C T Y P E声明中写入以下实体声明:
程序清单3 - 1 4
假设文档在创建时需要包含调试信息或计费信息.如果D T D外部子集包含以下代码:
程序清单3 - 1 5
然后,假设文档实例是要交给财务部门的:
程序清单3 - 1 6
68使用XML高级编程
下载
这样,文档就能够正确地验证有效性.
由于内部D T D子集中的声明是先读的,因此实际上,参数实体声明是出现在外部D T D子
集中的参数实体引用之前的.
在以上例子中,我们需要的是在生产环境中用于审核的元素,但是在某些情况,出于测试
或错误检测的需要,我们可能希望在文档中包含用于调试的元素.与A u d i t E n t r y相关的声明将包
含在D T D中,而与D e b u g E n t r y相关的声明将被忽略.为了将D e b u g E n t r y作为文档的有效元素,
只需交换参数实体的关键字,这一操作是相当简单的.如果使用恰当,条件部分能够提供大量
功能,并改善代码的可重用性.
3.4 DTD的缺点
D T D能够有效地推动X M L的发展.然而,它也受到一些因素的限制.首先,它使用自己的
一套语法,与文档实例的语法截然不同.更重要的是,如果X M L解析器能够使应用程序简便地
访问它们所处理的D T D中的声明,就会使D T D成为一种非常有用的工具.遗憾的是,目前几乎
没有解析器能够做到这一点.这一现状妨碍了我们利用D T D验证文档的有效性,以及将相应领
域的信息传达给编程人员.我们的应用程序无法了解D T D中的声明及其结构.
类似地,我们不能使用解析器动态创建D T D.这似乎不是一个重要的限制.毕竟,D T D被
认为是恒定不变的定义,而不像动态建立的文档那样需要进行有效性验证.即使如此,仍然有
一些需要根据条件做出选择的情况,例如:根据某些值改变词汇表规则.我们可以从数据库读
取当前值,并据此构建D T D.该D T D可以用来创建一系列文档,这些文档在当前状态下是有效
的.然后,D T D将随文档一起传送,以便接收者根据文档创建时的环境状态验证文档的有效性.
D T D提供的条件选择结构不能实现上述功能,因此不得不动态创建D T D.如果缺乏解析器的支
持,我们只能手工创建D T D.
D T D是一种封闭的结构.X M L词汇表的规则完全包含在D T D中.如果你不需要从其他D T D
借用声明或结构,或许感受不到这方面的局限性.由于实体处在一个很低的层次,因此试图改
善D T D扩展性的工作往往徒劳无功.事实上,没有简明的方法能够改善D T D的扩展性.对于可
扩展的标记工具,D T D也显得极不合作.我们无法根据概念和对象的相关性将声明分为若干段.
假如能够做到这一点,就可以创建许多描述各个商业领域的D T D,然后通过引用将它们粘合在
一起,以满足真正的应用需求.当我们在第7章讨论命名空间和模式时,会分析各种借用信息的
可能性.它涉及的内容超出了D T D的范围.
D T D在数据类型信息方面也存在一定的缺陷.它所提供的唯一工具就是表示法.我们无法
根据现有的类型定义自己的新类型.表示法能够为一种未解析文本标记一个名称,但是这与强
大的类型定义机制有着本质区别.我们希望将某些值表示为简单类型,例如:数字,而不是文
本,并对这些值执行恰当的操作.
在W 3 C提出弥补D T D不足的解决方案之前,已经出现了其他一些模式机制,我们将在第7章
介绍有关内容.然而,暂时不考虑D T D的缺陷,D T D是目前唯一一种声明X M L词汇表结构和内
容的正式方法.D T D是非常基础性的概念,它有助于理解其他模式,以及如何利用X M L通过标
准的方式交换文档.
第3章文档类型定义使用69
下载
3.5 用于图书目录问题的DTD
现在,让我们利用本章所学的内容为图书出版领域定义一个X M L词汇表.更明确地说,在
下面的例子中,我们将要定义描述图书目录的语法.从最通用的角度讲,我们的词汇表将涉及
一类图书.从最精确的角度讲,我们可以利用词汇表从整体上描述图书.我们将允许文档创作
者包含图书本身的目录.这似乎超出了我们的讨论范围.如果需要的话,我们能够提供某种形
式的图书文档链接,但是在进行图书分类时,确实没必要包含其目录.
3.5.1 图书目录问题的正式定义
我们首先来描述一下要建模的业务流程.目前,你还不必担心D T D.一旦理解了要处理的
业务流程,你就能够建立漂亮的模型,急于创建规则往往会影响你对问题本身的理解.我们要
做的第一件事情是设计问题空间的主要对象及其相互关系.
1. 问题模型
首先必须明确的是,我们要讨论的是一个目录.我们要描述的所有内容都包含在这个目录
中.我们不会涉及各个目录之间的关系,而仅考虑目录本身的内部关系.这有助于我们集中注
意力,我相信这是编写优秀的X M L词汇表的关键.
目录中包含一本或多本图书―这就是整个词汇表.如果我们停留在这样的认识上,会遗
漏什么问题吗 图书是由出版社出版的,因此我们应该在目录中包含出版社.一个出版社就够
了吗 当然.这就意味着一家出版社至少需要一个Book Catalog文档.虽然它也可以有多个文档.
出版社有时会涉猎多个领域.这对我们的模型有影响吗 它们是如何组织的 通常,目录是根
据主题组织的.而主题是出版社出版的内容.如果一家出版社拥有大量书籍,它会根据某些主
题或知识领域划分出更细的目录.
就问题本身而言,并不需要包含多家出版社.但是既然谈到这个因素,你可能会考虑谁还
有可能使用该模型.图书馆或图书收集者会有同样的需求,他们需要根据主题来描述图书.图
书销售商也有类似的需求.这三类用户需要在他们的类别中包含多个出版社.通过这方面的修
改,为该模型创造了更多的应用机会.这种修改通常意味着你遇到了问题.你可能转移了问题
的方向.然而,在本例中却不是这种情况.我们仍然能够满足单一出版社的需求.无论有多少
出版社,在出版社信息之后,文档包含的依然是一系列图书摘要.从"有且仅有一个"出版社
变为"一个或多个"出版社使得我们的模型更加灵活,同时并不影响我们要描述的内容.
主题是什么 它是用于组织知识或讨论的线索.它类似于新闻组中的线索.主题或线索通
常可以从书名或书的目录获得,但是我不希望采取这种方式.线索应该有自己的描述性信息.
到目前为止,我们得到了图3 - 1所示的组织图.
在我们的模型中,一个目录包含一家或多家出版社,零个或多个线索,以及一本或多本图
书.现在,我要讨论两个问题.它们都是关于我对问题空间所做的假设.第一个问题是元组的
数目.为什么我会认为图书和出版社可以是"一个或多个",而线索甚至可以不出现 不包含任
何书籍的图书目录是一种极端情况,只有数学家会对此感兴趣.我们基本上不会对空集有兴趣
――如果目录为空,有什么必要讨论它吗 这是个具有实际用途的词汇表,而不是理论意义上
70使用XML高级编程
下载
的.然而,词汇表中可以没有用于组织图书的出版社线索.
图3-1
谈到出版社,又出现了一些不太明确的问题.有些情况下,我们只对目录中的书感兴趣.
为此,我们是否应该将出版社的元组数设置为"零个或多个" 你的想法也许与我的不同―
对于建立D T D来说,没有永恒的规则―但是每本书确实与一家出版社有关系.即使相当简单
的情况―只有一家出版社―我们也希望锁定这种关系.我们可以忽略出版社信息,但是电
子商务应用中需要该信息.因此,在我们的例子中,我们将出版社的数目设定为"一个或多
个".
另一个问题是如何将线索与图书相关联.刚才我们曾经将图书线索与新闻组线索进行比较,
鉴于此,我们可以将图书作为线索的子元素.然而,这会产生一定的限制.根据目录中线索的
特征以及图书涉及的范围,一本书往往会与多个线索相关联.有些用户可能对于按照线索组织
信息不感兴趣.许多程序都属于这种情况.例如一个简单的清单程序.它注重的是按照字母顺
序或者根据I S B N排列所有图书.如果根据线索组织图书,这些程序不得不通过线索寻找图书.
在本例中,我们会将线索作为一个独立的结构.当然,为了在图书与线索之间建立联系,必须
通过某种机制定义它们之间的链接.
在正式编写D T D之前,首先让我们看一下有关对象的结构.由于这是一个关于图书的目录
―因此出版社和线索的重要性仅仅体现在它们与图书的关系―我们将以此为出发点.
2. 图书
在此,我并不打算提供元素组成图,我只希望通过图3 - 2说明元素的包含关系.我们将确定
图书元素的子元素.(从图书项的角度考虑,)图书元素应
该包含哪些内容
当然,书总是要有书名的.我还加入了摘要.这是从
学术论文得到的启发,它是图书内容的简短描述,通常只
有一段.零售目录也有图书简介,虽然其内容一般比学术
摘要少.无论如何,简短的描述是非常有用的.实际上,
由于它是图书内容的总结,因此可以作为主要的搜索目标.
当你根据关键字进行搜索时,相信你宁愿将查找的范围限
定在摘要中.因为即使书的正文中包含你要找的关键字,
书的内容也有可能与你所关心的问题毫不相干.然而,摘
要体现的是书的主题.无论出于什么目的,图书的摘要都
第3章文档类型定义使用71
下载
目录线索*出版商图书
图3-2图书书名摘要
价格(可选)
是目录的主要组成部分.
许多出版的商业图书的背面都有三个推荐的主题域.它们是由出版社提供的,目的是帮助
图书经销商给图书进行适当的分类.作为商业用途的目录文档也应该包含该信息.
事实上,目录上出现的每本书都是为了销售的.然而也有一些例外.当我们讨论出版社的
数目时,曾经提到该D T D也可以供图书收集者使用,而他们对于图书的价格并不感兴趣.另外,
博物馆或图书馆也不关心书的价格;书籍本身是有价值的,但是给它们做目录并不是为了销售.
而且出版社在给图书确定零售价之前可能已经将它归入某个目录.鉴于以上原因,我们将价格
作为可选的.
至此,我们已经有了四个子项.记住,我们早就将书的目录排除在词汇表之外.当你需要
更深入地了解书的内容时,你会需要它的目录.书的目录由章节标题构成.现在让我们暂停对
图书对象的讨论,转向另一个重要对象―出版社.
3. 出版商
图3 - 3是出版商对象的包含关系图.
出版商是一个法人实体.它是负责图书编译,出版和发
行的公司.我们的出版商模型将反映它的商业特征.显然,
公司的名称是最基本的信息.由于这是一家公司,它常常会
有许多分公司.例如,一家大型出版商可能在每个洲都有分
公司.I n t e r n e t或许会改变这种情况,但是许多出版商都需要
提供位置列表―即:若干地址.出版社通常有多个印记.
它类似于目录中的线索,或者说市场中的品牌.印记通常由
名称和徽标构成.描述出版社必须列出其所有印记.
我最后要提到的问题可能有些争议.书是由作者编写的.我们会在书中包含作者的信息.
然而,一位作者常常会写多本书.在图书之外单独描述作者,并在两者之间建立联系似乎是一
种更恰当的方式.这意味着需要某种链接机制.当我们讨论将图书置于线索之外时,曾经提到
过这种需求.我们在此最好加以强调.作者是否不属于出版商的范围 虽然某些作者会从一家
出版商跳到另一家出版商,但这只是一种例外,而不是常规.作者群体是出版商的重要智力资
产,因此我认为应该在出版商对象中包含作者信息.
4. 线索
现在我们来讨论线索.它是一个有些难以捉摸的概念.在现实世界中找不到合适的类比.
但是,我们确实需要它,这一点在前面已经有过论述.实际上,很难为线索定义一种适用于所
有目录的正式结构.我相信简短的文本内容―# P C D ATA―就足以描述线索.
Wrox 出版社的We b站点(h t t p : / / w w w. w r o x . c o m)是根据八条线索组织的.其他出版社也
有类似的概念,有时称为系列,或者根据目标读者划分类别.在计算机图书领域,图书
通常是根据特定的技术,语言或产品族来组织的,因此线索的概念是相关的.
5. 出版目录D T D
既然已经了解了要描述的内容,让我们开始定义图书目录文档用到的标记.在X M L中,对
象是通过元素模型化的.它们所包含的信息通常可以转化为子元素,某些简单的特性也可以通
72使用XML高级编程
下载
图3-3
出版商
公司名
地址
出版印记
作者
过属性来描述.
(1) 目录
显然,目录元素应该是目录文档的根.根据前面的组织结构图,我们可以将目录描述为:
内容模型相当简单.C a t a l o g元素包含顺序排列的子元素:出版社,线索和图书.每个子元
素的元组数目如下:一个或多个P u b l i s h e r,零个或多个T h r e a d,一个或多个B o o k.这说明
T h r e a d虽然很有用,但并不是十分重要.由于线索的数量定义,出版社列表后面有可能紧跟着
图书列表.
目录元素不包含任何属性.它在文档中只有一个,因此没有必要提供标识符.我们将对目
录的讨论分散到它所包含的其他对象中.
(2) 出版社
现在,让我们来看看P u b l i s h e r元素的定义:
上述定义对应于我们在前面看到的示意图.P u b l i s h e r元素包含一个公司名称,一个或多个地
址,若干印记,以及零个或多个作者.有些出版社为了简单起见有时宁愿不要作者,但是在我
们的目录中,无作者代表该书是匿名作者编写的,或者作者过多,例如:会议的专题文集.
有一点需要特别注意,I m p r i n t s没有设定元组数.这并不是疏忽.其他属性都是在同一级列
出的,对于I m p r i n t s,我希望改变一下风格,通过元素集合来说明.P u b l i s h e r元素将包含由
I m p r i n t元素集构成的子元素.这种方式并不会影响元素的含义,它有利于应用程序的程序员理
解D T D.如果你希望忽略该元素,只需直接跳至下一元素.如果你对该元素感兴趣,你可以利
用解析器检查属于I m p r i n t s元素的子元素的数量,由此得到印记数.
另外,P u b l i s h e r元素还有一个属性i s b n:
P u b l i s h e r元素需要一个唯一的指示符,而公司名称不能作为指示符.图书的I S B N包含分配
给出版商的唯一数字.例如,Wr o x出版社的所有书的I S B N都包含序号1 8 6 1.我希望文档的创作
者将整个I S B N中的这个片断作为该属性的值.
下面我们来介绍构成P u b l i s h e r元素的子元素:
程序清单3 - 1 7
以上代码应该与你想象的差不多.大多数元素都是# P C D ATA类型的.
本节最后给出了图书目录DTD的完整代码.
I m p r i n t s元素是由一个或多个I m p r i n t元素构成的,如前所述,I m p r i n t元素是# P C D ATA类型
的.A u t h o r元素看上去略微混乱一些,但是在分析其内容模型之前,我们还要进一步说明一下
第3章文档类型定义使用73
下载
A d d r e s s和I m p r i n t元素的属性:
由于出版社可能有多处办公地点,因此要将总部和分公司区分开.在此,我使用简单的枚
举类型,而且该属性是可选的.关键字# I M P L I E D表示文档的作者可以忽略该属性,同时不会影
响文档的有效性.如果元素包含该属性,它的值必须是y e s或n o.
现在让我们来研究I m p r i n t元素的属性列表.由于该元素的内容为# P C D ATA,因此I m p r i n t的
名称的长度不受限制.我需要将图书与印记相关联,所以我希望有个较短的关键字.这就是
s h o r t I m p r i n t N a m e属性的作用.此后,Our Incredibly Wonderful Children's Book Division可以缩
写为I n c r e d C h i l d.我们同样没有限制该属性的长度.实际上,D T D语法并未提供控制字符串长
度的设置.
与字符串长度问题相比,如何使用I D类型的属性更加重要.如果文档中出现该属性,它就
作为唯一的关键字,可以被文档中的其他元素引用.在我们的例子中,我们知道该属性将被
B o o k元素引用,但是D T D并不提供显式的声明机制.
让我们回到A u t h o r元素,以下是A u t h o r及其子元素的声明:
程序清单3 - 1 8
A u t h o r的子元素相对来说比较通俗,它包含的文本信息你可想而知.我们提供了名称和个人
简历信息.该元素的关键在于它的属性.A u t h o r元素的a u t h o r C i t e I D属性是元素的简短引用,它
的作用与I m p r i n t元素的s h o r t I m p r i n t N a m e属性类似.它是一个I D类型的属性,用于将作者与一本
或多本书相关联.
P o r t r a i t元素用于将作者的照片与作者的文字信息相关联.由于X M L标记中不能包含二进制
数据,因此需要借助链接机制.我们不能使用实体取代链接,因为在一份很长的目录清单中,
会出现许多作者和相应的图像文件,当创建文档时它们大多数都是动态命名的.X M L委员会针
对链接提出了许多方法,但是都未形成正式的建议规范,而且几乎未得到解析器厂商的支持.
为了简单起见,我们还是暂时不提X L i n k或X P o i n t e r的语法为好,我将图像文件的U R L作为
p i c L i n k属性的值.然而,我们的应用程序无论如何都需要支持某种链接机制.如果读取目录文
档的应用程序使用浏览器,我们可以为浏览器生成包含I M G标记的H T M L文件.在这类应用程序
中,p i c L i n k的值将成为I M G标记的S R C属性的值.如果使用文档的应用程序不具备可视化的界
面,它只需忽略该属性.
74使用XML高级编程
下载
你是否曾经考虑过使用表示法 由于我不希望限制应用程序使用D T D的行为,因此放弃
了这种方法.如果没有提供针对特殊类型的处理程序,表示法只是给数据类型定义了一
个名称.如果我指定了处理程序,就必须保证任何使用目录文档的应用程序有特定的行
为,例如:显示链接.而我的设计目标是无论应用程序是否有可视化的界面,D T D都应
该发挥同样的作用.
(3) 线索
我们已经完整而详细地分析了P u b l i s h e r元素的内容.在C a t a l o g内容模型中,下一个对象是
T h r e a d.以下是该元素的声明:
元素的内容是用于描述线索的文本.它可能是关键字列表(例如:对于Wr o x出版社来说,
元素的内容可能是A S P,D a t a b a s e,X M L,S c r i p t i n g等),或者更长的描述.属性t h r e a d I D用于在
图书与线索之间建立关联.
(4) 图书
下面我们将讨论最后一个元素――B o o k.它是D T D的核心.我们介绍问题模型时曾经给出
了图书对象的示意图,元素的声明完全是按照该图产生的:
Ti t l e元素和A b s t r a c t元素是# P C D ATA类型的.R e c S u b j C a t e g o r i e s元素的声明如下:
C a t e g o r y是# P C D ATA类型的.我
立刻下载此文档