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个实体:
实体 |
字符 |
& |
& |
< |
< |
> |
> |
' |
‘ |
" |
“ |
这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"> |
这样在使用声明时,就是代表的引用的外部文件中的内容。