1. XML语法
在说明DTD约束前,先介绍XML的基本语法:
文档声明
例:<?xml version="1.0" encoding="utf-8" standalone="yes"?>
元素定义:
由开始标记、属性、元素内容、结束标记构成
例:<城市>清远</城市>
属性定义:
一个个元素中可以有多个属性
例:<售价 单位="元">68</售价>
注释:<!-- 这里写注释内容 -->
DTD约束
DTD约束是早期出现的一种XML约束模式语言,根据它的语法创建出来的文件称为DTD文件。
DTD的引入方式:
外部式:
- <!DOCTYPE 根元素名称 SYSTEM “外部DTD文件的URL”>
- <!DOCTYPE 根元素名称 PUBLIC “DTD名称” “外部DTD文件的URL”>
用一个例子来解释如何使用外部式引入DTD
book.dtd文件
<!ELEMENT 书架 (书+)>
<!ELEMENT 书 (书名,作者,售价)>
<!ELEMENT 书名 (#PCDATA)>
<!ELEMENT 作者 (#PCDATA)>
<!ELEMENT 售价 (#PCDATA)>
book.xml文件
<?xml version="1.0" encoding="utf-8"?>
<!-- 这里引入book.dtd对book.xml进行约束 -->
<!DOCTYPE 书架 SYSTEM "book.dtd">
<书架>
<书>
<书名>***</书名>
<作者>***</作者>
<售价>10</售价>
</书>
<书>
<书名>****</书名>
<作者>****</作者>
<售价>100</售价>
</书>
</书架>
内嵌式:直接将DTD约束写在XML文档中
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE 书架 [
<!ELEMENT 书架 (书+)>
<!ELEMENT 书 (书名,作者,售价)>
<!ELEMENT 书名 (#PCDATA)>
<!ELEMENT 作者 (#PCDATA)>
<!ELEMENT 售价 (#PCDATA)>
]>
<书架>
<书>
<书名>***</书名>
<作者>***</作者>
<售价>10</售价>
</书>
<书>
<书名>****</书名>
<作者>****</作者>
<售价>100</售价>
</书>
</书架>
DTD语法
DTD的结构一般由元素类型定义、属性定义、实体定义、记号定义等构成
- 元素定义:每一条<!ELEMENT ...>语句定义一个元素
<!ELEMENT 元素名称 元素内容>
在上面定义格式中,元素名称是自定义的;元素内容是对元素包含内容的声明,包括数据类型和符号两部分,它共有5中形式:
- #PCDATA:表示元素中嵌套的内容是普通字符串,其中,关键字PCDATA是ParsedCharacter Data的简写。例如,<!ELEMENT 书名 (#PCDATA)> 表示书名所嵌套的内容是字符串类型。
- 子元素:说明元素包含的元素。例如:<!ELEMENT 书 (书名,作者,售价)>表示书中要嵌套书名、作者、售价等子元素
- 混合内容:表示元素既可以包含字符数据,也可以包含子元素。混合数据必须被定义零个或多个,例如:<!ELEMENT 书 (#PCDATA书名) *>表示书中嵌套的子元素书名包含零个或多个,并且书名是字符串文本格式。
- EMPTY:该元素是一个空元素。使用场景:元素在文档中已经表明了明确含义,就可以在DTD中用关键字 EMPTY 表明空元素。例如:<!ELEMENT br EMPTY>,br是一个没有内容的空元素。
- ANY:表示元素可以包含任何的字符数据和子元素。例如:<!ELEMENT 联系人 ANY>表示联系人可以包含任何形式的内容。但是在实际开发中,应该尽量避免使用ANY,因为除了根元素外,其他使用ANY的元素都将失去DTD对XML文档的约束效果。
在定义元素时,元素内容中可以包含一些符号,不同的符号具有不同的作用。具体如下:
- 问号[?]:表示该对象可以出现0次或1次
- 星号[*]:表示该对象可以出现0次或多次
- 加号[+]:表示该对象可以出现1次或多次
- 竖线[|]:表示在列出的对象中选择1个
- 逗号[,]:表示对象必须按照指定的顺序出现
- 括号[()]:用于给元素进行分组
- 属性定义:基本语法格式如下
<!ATTLIST 元素名
属性名1 属性类型 设置说明
属性名2 属性类型 设置说明
......
>
在上面属性定义的语法格式中,”元素名“是属性所属元素的名字,”属性名“是属性的名称,”属性类型“则用来指定该属性属于那种类型,”设置说明“用来说明该属性是否必须出现。关于”属性类型“和”设置说明“的相关讲解如下:
2.1. 设置说明:定义元素的属性时,有4中设置说明可以选择
设置说明 | 含义 |
---|---|
#REQUIRED | 表示元素的该属性是必须的,例如,当定义联系人信息的DTD时,我们希望每一个联系人都有一个联系电话属性,这时,可以在属性声明时使用REQUIRED |
#IMPLED | 表示元素可以包含该属性,也可以不包含该属性。例如,当定义一本书的信息时,发现书的页数属性对读者无关紧要,这时,在属性声明时可以使用IMPLIED |
#FIXED | 表示一个固定的属性默认值,在XML文档中不能将该属性设置为其他值。使用#FIXED关键字时,还需要为该属性提供一个默认值。当XML文档中没有定义该属性时,其值将被自动设置为DTD中定义的默认值 |
默认值 | 和FIXED一样,如果元素不包含该属性,该属性将被自动设置为DTD中定义的默认值。不同的是,该属性的值是可以改变的,如果XML文件中设置了该属性,新的属性值会覆盖DTD中定义的默认值 |
2.2. 属性类型:在DTD中定义元素的属性时,有10种属性类型可以选择,常见的几种属性类型如下
- CDATA
这是最常用的一种属性类型,表明属性类型是字符数据,与元素内容说明种的#PCDATA相同。当然,在属性设置值中出现的特殊字符,也需要使用转义字符序列来表示,例如,用”&“表示字符”&“,用”<“表示字符”<“等。 - Enumerated(枚举类型)
在声明属性时,可以限制属性的取值只能从一个列表中选择,这类属性属于Enumerated(枚举类型)。需要注意的是,在DTD定义中并不会出现关键字Enumerated。案例enum.xml展示如何定义Enumerated类型的属性。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- 内嵌式DTD约束 -->
<!DOCTYPE 购物篮 [
<!ELEMENT 购物篮 ANY>
<!-- EMPTY表示肉及不包含字符数据,也不包含子元素,是一个空元素 -->
<!ELEMENT 肉 EMPTY>
<!ELEMENT 肉 品种 (鸡肉|牛肉|猪肉|鱼肉) "鸡肉">
]>
<购物篮>
<肉 品种="鱼肉"/>
<肉 品种="牛肉"/>
<肉/>
</购物篮>
在enum.xml中,“品种”属性的类型是Enumerated,其值只能是“鸡肉、牛肉、猪肉、鱼肉”,而不能使用其他值。“品种”属性的默认值是“鸡肉”,所以,即使<购物篮>元素中的第三个子元素没有显示定义“品种”这个属性,但它实际上也具有“品种”这个属性,且属性的取值为“鸡肉”。
- ID
一个ID类型的属性用于唯一标识XML文档中的一个元素。其属性值必须遵守XML名称定义的规则。一个元素只能有一个ID类型的属性,而且ID类型的属性必须设置为#IMPLIED或#REQUIRED。因为ID类型属性的每一个取值都是用来标识一个特定元素,所以,为ID类型的属性提供默认值,特别是固定的默认值是毫无意义的。
<!-- 例:id.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!ELEMENT 联系人列表 [
<!ELEMENT 联系人列表 ANY>
<!ELEMENT 联系人 (姓名,EMAIL)>
<!ELEMENT 姓名 (#PCDATA)>
<!ELEMENT EMAIL (#PCDATA)>
<!ELEMENT 联系人 编号 ID #REQUIRED>
]>
<联系人列表>
<联系人 编号="id1">
<姓名>张三</姓名>
<EMAIL>[email protected]</EMAIL>
</联系人>
<联系人 编号="id2">
<姓名>李四</姓名>
<EMAIL>[email protected]</EMAIL>
</联系人>
</联系人列表>
在 id.xml 中,将元素为<联系人>的编号属性设置为#REQUIRED,说明每个联系人都有一个编号,同时,属性编号的类型为ID,说明编号是唯一的。这样一来,通过编号就可以找到唯一对应的联系人了。
- IDREF和IDREFS
在上面的 id.xml 中,虽然张三和李四两个联系人的ID编号是唯一的,但是这两个ID类型的属性没有发挥作用,这时可以使用IDREF类型,使这两个联系人之间建立一种一对一的关系。案例 Idref.xml 展示IDREF的使用:
<!-- 例:Idref.xml 演示如何使用IDREF -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE 联系人列表 [
<!ELEMENT 联系人列表 ANY>
<!ELEMENT 联系人 (姓名,EMAIL)>
<!ELEMENT 姓名 (#PCDATA)>
<!ELEMENT EMAIL (#PCDATA)>
<!ELEMENT 联系人
编号 ID #REQUIRED
上司 IDREF #IMPLIED
>
]>
<联系人列表>
<联系人 编号="id1">
<姓名>张三</姓名>
<EMAIL>[email protected]</EMAIL>
</联系人>
<联系人 编号="id2" 上司="id1">
<姓名>李四</姓名>
<EMAIL>[email protected]</EMAIL>
</联系人>
</联系人列表>
在 Idref.xml 中,为元素<联系人列表>的子元素<联系人>增加一个名称为上司的属性,并且将该属性的类型设置为IDREF,IDREF类型属性的值必须为一个已经存在的ID类型的属性值。在第二个<联系人>元素中,将“上司”属性设置为第一个联系人的编号的属性值,如此一来,就可以形成两个联系人元素之间的对应关系,即李四的上司是张三。
IDREF可以是两个元素之间形成一对一的关系,而IDREFS可以是元素之间形成一对多的关系。例如:学生借书(Library.xml)
<!-- Library.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library [
<!ELEMENT library (books,records)>
<!ELEMENT books (book+)>
<!ELEMENT book (title)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT records (item+)>
<!ELEMENT item (data,person)>
<!ELEMENT data (#PCDATA)>
<!ELEMENT person EMPTY>
<!ATTLIST book bookid ID #REQUIRED>
<!ATTLIST person name CDATA #REQUIRED>
<!ATTLIST person borrowed IDREFS #REQUIRED>
]>
<library>
<books>
<book bookid="b0101">
<title>xml基础</title>
</book>
<book bookid="b0102">
<title>xml进阶</title>
</book>
<book bookid="b0103">
<title>xml再进阶</title>
</book>
</books>
<records>
<item>
<data>2019-12-26</data>
<person name="李四" borrowed="b0101 b0102"/>
</item>
<item>
<data>2019-12-27</data>
<person name="李四" borrowed="b0101 b0102 b0103"/>
</item>
</records>
</library>
除了上述的几种属性类型外,DTD约束中还有NMTOKEN、NMTOKENS、NOTITION、ENTITY和ENTITYS几种属性类型。