DTD约束简介

文档类型声明

文档类型声明就是DOCTYPE,它告诉解析器,XML文档必须遵循DTD定义。同时,他也告诉解析器,到哪里找到文档定义的其余内容。在前边的例子里DOCTYPE很简单:

<!DOCTYPE students[]>

文档类型声明以<!DOCTYPE开始,然后是根标签的名字,我们这里根标签是students所以写的是students,注意<!DOCTYPE和students之间有一个空格,这个空格必须有。这里声明以后就意味着文档中根标签必须是students。

在根元素的后边有几种不同的情况,上边的例子里,紧跟在跟标签的是一对[]我们所有DTD都是在[]中间编写的。

还有一种情况DTD是单独保存到一个外部文件中的,我们需要在声明中引入外部的DTD文件。引入外部的DTD文件主要有两种方式:系统标识符、公共标识符。

系统标识符

利用系统标识符我们可以声明一个外部DTD文件的位置,它由两部分组成:关键字SYSTEM和指向文档位置的URI引用。URI可以是硬盘上的一个文件,也可以是网络中的一个文件。

<!DOCTYPE students SYSTEM "students.dtd" []>

 

<!DOCTYPE students SYSTEM "file:///c:/students.dtd" []>

 

<!DOCTYPE students SYSTEM "http://www.xxx.com/xx.dtd" >

上边三种都是使用系统标识符的例子,SYSTEM关键字后边可以根的是一个硬盘上文件的地址,也可以是一个网络中的地址,最后一个例子没有[]这个也是完全可以的。

公共标识符

公共标识符是定义外部DTD的第二种方法:

<!DOCTYPE students PUBLIC "-//ATGUIGU//DTD students//EN" "students.dtd">

和系统标识符类似,公共标识符以PUBLIC开始,其后紧跟一个专用标识符,但是公用标识符不是用来表示文件的引用,而是表示目录中的一个记录。根据XML规范公共标识符可以采用任意格式,但是经常采用的一种格式是正式公共标识符(Formal Public Identifier,FPI)。

FPI语法结构:-//所有人//文件描述//语言//版本。

但是大多数的解析器是不能解析公共标识符的,所以我们一般还会在公共标识符后边加一个系统标识符,如上公共标识符"-//ATGUIGU//DTD students//EN"后边我们加了一个空格,空格后边的"students.dtd"就是一个系统标识符。

这一段的意思就是解析器先会尝试通过公共标识符寻找DTD文件,如果找不到在通过系统标识符获取。

外部DTD文件

接下来我们尝试创建一个外部DTD约束文件,并将它引入到当前文档中。利用外部的DTD约束文件可以很方便的将常用的约束信息和他人共享,而不再需要重复定义。

这里我们尝试将刚才的students的约束定义为一个外部的DTD约束,首先我们创建一个纯文本文件,名称保存为students.dtd。内容如下:

<!ELEMENT students (student)*>

<!ELEMENT student (name , age , gender , address)>

<!ATTLIST student id CDATA #REQUIRED>

在创建一个新的xml文件,并通过公共标识符的方式将以上约束引入:

<!DOCTYPE students PUBLIC "//ATGUIGU//DTD students//EN" "students.dtd">

重复上边的步骤对编写后的文档进行验证,我们会发现当文档内容不符合我们定义的规则时,同样会报错,并且如果使用的带有自动提示功能的编辑器时(如Eclipse)在输入XML标签时还会弹出提示。

2.3.2 DTD声明

一个完整的DTD声明由三个基本部分组成:元素声明、属性声明、实体声明。

元素声明

当我们用DTD定义一个XML文档的内容是,必须用DTD定义文档里的每一个元素。读者将会明白,DTD也可以对可选元素进行声明。可选元素是指在XML文档中可能出现,也可能不出现的元素。

<!ELEMENT students (student)*>

元素声明的基本语法:<!ELEMENT 元素名 元素内容模型>,使用!ELEMENT声明一个元素,接下类是元素名也就是标签名,元素内容模型跟在元素名的后边。一个元素的内容模型定义了可允许的元素内容。一个元素可能包含一个子元素、一段文本或子元素域文本的组合,也允许元素内容为空。这正是DTD的核心,这样就可以定义文档的结构。就XML推荐标准而言,有四类内容文档,它们是:元素内容、混合内容、空内容、任意内容。

在XML中元素中可以有子元素,我们可以通过DTD来定义某个元素中可以包含哪些子元素,为了顶一个元素中可以包含哪些子元素,我们只需将子元素名写在父元素后边的()中。例如如果一个students标签中只允许有一个student元素,则定义如下:

<!ELEMENT students (student)>

事实上在students元素中是可以出现多个student的,只需要在()后边多加一个*,如下:

<!ELEMENT students (student)*>

还可以使用其他符合?表示一次或零次,+表示一次或多次,*表示零次或多次。

在student标签中可以有name、age、gender、address,则可以在()中编写都给元素名,使用,分割。如下:

<!ELEMENT student (name , age , gender , address)>

使用,分割代表子元素必须按照这样的顺序出现,即age必须在name后,gender必须在age后,address必须在gender后。如果不要求子元素的顺序可以使用|分割子标签:

<!ELEMENT student (name|age|gender|address)>

如果元素中的内容是纯文本的内容,使用#PCDATA定义:

<!ELEMENT name #PCDATA>

如果元素仅仅是一个空元素,也称为自结束标签我们可以使用EMPTY来定义:

<!ELEMENT br EMPTY>

ANY表示在元素中可以定义任意内容:

<!ELEMENT test ANY>

属性声明

使用ATTLIST关键字声明元素中的属性

<!ATTLIST student id CDATA #REQUIRED>

上边这个例子为student元素声明一个id属性。

ATTLIST声明包括三部分的基本内容:ATTLIST关键字、相应的元素名、属性列表。一个ATTLIST可以定义任意个属性,每个属性由三部分组成属性名、属性类型、属性值声明。我们来看一个下我们声明的id属性:

id CDATA #REQUIRED

这个属性声明里,属性名是id,关键字CDATA是属性类型,它表示id属性的值是字符数据,最后的#REQUIRED关键字,表示属性时必须的。

属性名这里没有什么好说的,就是要注意属性名要符合XML的规范。主要说一下属性的类型,在声明属性时,我们必须说明处理器如何处理属性值中的字符数据。属性主要有如下几个类型:

类型

描述

CDATA

字符数据。

ID

唯一确定的属性值

IDREF

属性值是一个ID的引用,引用一个标识的唯一元素

IDREFS

属性值为一个用空格分隔的IDREF列表

ENTITY

属性值是一个外部非解析实体(图片、mp3)

ENTITIES

属性值是一个用空格分隔的ENTITY列表

NMTOKEN

属性值是一个名称标记

NMTOKENS

属性值是一个有空格分隔的NMTOKEN列表

Enumerated List

枚举类型

在声明属性时,需要说明属性值的取值方式。通常,我们希望在声明属性时,指定一个默认值;有时,我们希望在文档中给属性赋值;而在其他时候,我们要求属性值为某个固定值。声明属性时必须说明这些特性。根据XML推荐标准,属性值可以有4种,默认值、固定值、必须值、隐含值(或可选值)。

默认值就是元素没有设置属性,或者某个属性没有设置值时,也希望该属性有某个值,这个值就是默认值。但是如果一旦为属性设置了一个值则默认值不再有效。设置默认值非常简单,只需在属性类型后插入一个引号表示的值即可:

<!ATTLIST student duty (班长|组长|学生) "学生">

这里我们为student标签增加了一个duty的必须属性,这个属性有三个可选值,班长、组长、学生。当我们没有指定该属性时,默认认为duty为学生。

注意不能为ID类型的属性指定默认值。

在某些情形中,属性值是固定不变的。如果某个属性值固定不变,那么是使用#FIXED关键字,之后紧跟着这个固定值。固定值在许多方面与默认值相似。当解析器正则解析文档时,遇到一个固定值属性时,就把这个固定值插入到这个属性里。

固定值常见应用是指定版本号。DTD的作者经常为某个专用的DTD文件指定一个固定的版本号:

<!ATTLIST students version CDATA #FIXED "1.0">

XML文档中有一些属性时必须有的这些属性就叫做必须值,我们使用#REQUIRED关键字将一个属性设置为必须值:

<!ATTLIST student id CDATA #REQUIRED>

如上我们的id属性就是一个必须值,如果student标签中没有id属性则解析器会报错。如果一个属性已经设置为必须值了,则不能再为这个属性指定默认值。

还有一些属性既不是必需值,也没有默认值或固定值。这种情况下该属性可能会出现在元素里,也可能不出现在元素里。我们通常把这些元素称为可选值。使用#IMPLIED声明一个可选属性:

<!ATTLIST student class CDATA #IMPLIED>

在同一个ATTLIST中可以创建多个属性的声明也很简单,如下:

<!ATTLIST student id CDATA #REQUIRED

                  duty (班长|组长|学生) "学生">

同样也可以使用多个ATTLIST标签设置:

<!ATTLIST student id CDATA #REQUIRED>

<!ATTLIST student duty (班长|组长|学生) "学生">

实体声明

如果要把某个特殊字符插入到XML文档里,要使用转义字符或实体应用。XML内置了5个实体,用于在XML文档里插入特殊字符。除了这个5个内置实体外,我们还可以利用字符引用表示一些难以输入的字符,例如:我们可以使用'代表单引号,使用 ;代表©等。事实上,在XML文档里实体不限于这些简单字符的引用。在文档里,实体可以是一段要替换的文本,可以是其他的XML标记,甚至可以是外部文件。我们把实体分为4大类,每一类都可以插入到XML文档里:内置实体、字符实体、一般实体、参数实体。

内置实体,在XML中默认可以使用5个实体:  

实体

字符

&amp;

&

<

<

>

>

'

"

这5个是内置实体,这是因为根据XML标准推荐,所有的XML解析器默认时都必须支持这5个实体。它们不需要再DTD里定义,我们马上就要看到,其他类型的实体,在插入到文档之前必须现在DTD中定义。

字符实体和内置实体很像,不需要再DTD中声明。它们可以直接用在元素内容和属性内容里。字符实体引用常用来表示不容易出入的字符,或非ASCII字符。

与内置实体一样,要在文档中使用字符实体,必须在文档里插入字符实体的引用。引用字符实体的语法与引用5个内置实体的语法非常相似。如 ;代表©。实际上字符实体和内置实体最大的区别就是字符实体没有实体名。第一个个字符时&,然后是一个#,而不是是提名,紧跟其后的是一个数字,本例是169,踏实©字符的Unicode码。结尾是一个;。

与内置的实体引用一样,凡是运行使用普通文本的地方,如元素的内容和属性值,都可以使用字符实体。在DTD定义里也可以使用字符实体。与内置实体一样,字符实体不能代替实际的XML标记,也不能作为元素名或属性名的一部分。

一般实体的作用与5个内置实体的作用非常相似,但是前者必须在XML文档中使用之前,先在DTD中定义。最常见的情形是,XML开发人员利用普通实体,建立可以重用的替换文本段。

在XML中声明普通实体有两种方式:可以直接声明实体的值,或者指向一个外部文件。首先我们来看在DTD文件中直接定义实体的值:

<!ENTITY wirter "ATGUIGU lilichao">

语法:<!ENTITY 实体名 "引用内容">,声明完这个实体我们就可以通过&实体名;在文档中使用实体,比如使用&writer;就代表ATGUIGUI lilichao这句话。

这里还可以将实体的值作为一个外部文件引入,和通过DOCTYPE引如外部的DTD文档很像:

<!ENTITY wirter SYSTEM "test.txt">

或者也可以使用公共标识符:

<!ENTITY wirter PUBLIC "-//ATGUIGU//DTD students//EN" "test.txt">

这样在使用声明时,就是代表的引用的外部文件中的内容。