XML系列——原来XML Schema并不难
1.前言
大家都知道XML Schema是对XML文件的数据结构的定义规范,它完全可以取代DTD。在没有仔细地去了解XML Schema之前,一直都觉得它挺难的。在看了W3School教程之后,发现也并没有想像的那么难,可能W3School教程并没有很全面地介绍XML Schema,但个人觉得已经很实用了。下面是对XML Schema的一个学习总结。Notic:文章中有些XSD元素或属性没有解释说明的,请到W3School查看。
在学习一样新东西的时候,概念往往是一切知识的基础,有了清晰明了的概念后,在深入探讨的时候往往能事半功倍。OK,那我们就从概念开始吧!
2.Schema中的一些概念
注:带*号的概念是自己总结出来的概念名称,所以这里说的一些概念没看懂没关系,有了印象后带着疑问继续往下看吧。2.1.XML Schema
XML Schema定义了XML文件的数据结构,与XSD(XML Schema Define)是同一个概念,而事实上,XML Schema文件的后缀名是以xsd结尾的。XSD只是一个标准的XML文件。
2.2.XSD中的数据类型
与平时编程说到的数据类型非常相似,只是在XSD中,有它自己的一套数据类型罢了。比如:string代表字符串类型,date代表了日期类型等等,具体可以查看W3School中关于XSD数据类型的说明。
2.3.元素的结构类型*
在XSD中,每一个元素的定义都必须提供它的结构类型,XSD中一共有两种结构类型:复杂类型和简单类型。它们分别对应了XSD中的<complexType>和<simpleType>。
2.3.1.复杂类型——complexType
所有带属性的元素或含有子元素的元素都属于复杂类型。复杂类型由子元素定义和属性定义组成。
2.3.2.简单类型——simpleType
所有只包含文本节点的元素或所有属性都属于简单类型。对于简单类型,可以理解成是XSD数据类型的一个扩展,它也是一种数据类型。
2.4.元素的内容*
元素的内容指的是复杂类型元素的开始标签与结束标签之间的内容(注意,元素内容不适用于简单类型的元素,也就是只包含文本节点的元素)。XSD中有两种类型的元素内容:复杂内容和简单内容。它们分别对应了XSD中的两个标签<complexContent>和<simpleContent>。这两个标签在XSD中主要是为了扩展或修改复杂类型complexType的限制,除此外,这两个标签没有其他作用。
2.4.1.复杂内容——complexContent
元素的内容不是简单的文本节点都可以看作是复杂内容。一般用于扩展复杂类型的元素
2.4.2.简单内容——simpleContent
元素的内容只有简单的文本节点可以看作是简单内容。个人觉得simpleContent只在一种情况下有用:一个带属性的元素,而且它内部只有文本节点。
3.XML实例场景
为了更好地说明事物,我们现在定义一个这样的一个XML文件:
<?xml version="1.0" encoding="utf-8" ?> <school xmlns="http://www.sin90lzc.com/school" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sin90lzc.com/school school.xsd"> <teachers> <teacher head="true"> <name>Tim</name> <subject main="true">Math</subject> </teacher> <teacher> <name>Rain</name> <subject>英语</subject> </teacher> <teacher> <name>Petter</name> <subject>语文</subject> </teacher> </teachers> <students> <student> <name>Sally</name> <teacher ref="Tim" /> </student> <student> <name>Edison</name> <teacher ref="Petter" /> </student> </students> </school>
关于school.xml的中文表述:
一个学校有多个老师和多个学生。在老师中,只有一名是校长(head="true"),每个老师的名字不能相同而且首字母必须大写,每个老师都只能教一门课程,课程的重要性通过属性main="true"指示(这样的一个XML结构是有问题的,但在这里我们的目的是为了表达XSD中的一些功能),课程的名称可以用英文表示,也可以用中文表示,而且只包含语文、数学、英语三个课程。在学生中,学生的名字也不能相同而且首字母必须大写,每个学生都有他们的直属指导老师,这里的老师引用必须是<teachers>中定义的老师的其中一名。
下面终于可以动手写一份XSD来定义school.xml的数据结构。
4.元素定义
4.1.XSD中的根元素<schema>
<?xml version="1.0" encoding="utf-8" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.sin90lzc.com/school" xmlns:s="http://www.sin90lzc.com/school" xmlns="http://www.sin90lzc.com/school" elementFormDefault="qualified"> </xs:schema>
- xmlns:xs="http://www.w3.org/2001/XMLSchema":定义了XSD文档的命名空间为xs
- targetNamespace="http://www.sin90lzc.com/school":目标命名空间,即需要较验的文档的命名空间
-
xmlns:s="http://www.sin90lzc.com/school" xmlns="http://www.sin90lzc.com/school":定义了默认的命令空间与s命令空间是相同的命名空间,这样做的目的是因为文档中会用到XPath,而XPath不能识别默认的命令空间,在XPath中需要使用s命名空间。
- elementFormDefault="qualified":指示使用该XSD的xml文件必须使用目标命名空间
4.2.元素定义公式
XSD有三种定义元素的方式:
- 全定义方式
<xs:element name="元素名称"> <元素的结构类型定义></元素的结构类型定义> </xs:element>
- 结构类型/数据类型引用方式
<xs:element name="元素名称" type="元素的结构类型名称|数据类型名称" />
- 元素引用方式
<xs:element ref="引用元素定义的ID" />
为了更直觉地展示XSD,首先采用全定义方式来组织XSD文件,在最后会使用第二,第三种方式来使我们的XSD文件更清晰,更易读。
4.3.<school>元素定义
由于<school>元素内包含两个子元素<teachers>和<students>,因此它属于复杂类型,应使用<complexType>来定义结构类型。
<xs:schema ...> <xs:element name="school"> <xs:complexType> </xs:complexType> </xs:element> </xs:schema>
如概念中说到的,复杂类型由子元素定义和属性定义组成。而所有的子元素的定义都必须使用<xs:sequence>或<xs:all>或<xs:choice>包裹,指示子元素的一些特征。
<xs:element name="school"> <xs:complexType> <xs:sequence> <xs:element name="teachers"></xs:element> <xs:element name="students"></xs:element> </xs:sequence> </xs:complexType> </xs:element>
4.4.<teachers>元素定义
现在我们把焦点放在<teachers>元素的定义上,<teachers>元素内包含了多个<teacher>的子元素,因此,它属于复杂类型。
<xs:element name="teachers"> <xs:complexType> <xs:sequence> <xs:element name="teacher" minOccurs="1" maxOccurs="unbounded"></xs:element> </xs:sequence> </xs:complexType> </xs:element>
minOccurs和maxOccurs属性分别用于限制元素最少或最多可以重复使用的次数,unbounded表示没有限制次数。
4.5.<teacher>元素定义
<teacher>带有属性head和子元素<name> <subject>,因此它也是属于复杂类型。
<xs:element name="teacher" minOccurs="1" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="name"></xs:element> <xs:element name="subject"></xs:element> </xs:sequence> <xs:attribute name="head" type="xs:boolean"></xs:attribute> </xs:complexType> </xs:element>
4.6.<name>元素定义
<name>元素只包含有文本节点,因此它属于简单类型simpleType。而且对name元素中的文本要求是首字母大写,这个要求可以使用<xs:restriction>元素来添加对值的约束。
<xs:element name="name"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="[A-Z](\w*)"></xs:pattern> </xs:restriction> </xs:simpleType> </xs:element>
4.7.<subject>元素定义
<subject>元素带有属性main,因此它属于复杂类型。还应注意到<subject>的内容是简单内容simpleContent,因为只包含文本节点。对于<subject>的值,只能取Chinese、Math、English、语文、数学、英语这六个值之一。因为XSD没有内置的Chinese、Math、English、语文、数学、英语枚举数据类型,因此我们必须在<schema>子元素中定义一个新的数据类型(简单类型)。
<xs:simpleType name="subject3"> <xs:union> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Math"></xs:enumeration> <xs:enumeration value="Chinese"></xs:enumeration> <xs:enumeration value="English"></xs:enumeration> </xs:restriction> </xs:simpleType> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="数学|语文|英语"></xs:pattern> </xs:restriction> </xs:simpleType> </xs:union> </xs:simpleType>
在这里有必要说明一下<xs:union>的用法,它指示了一个简单类型的值可以是多个不同的简单类型。<xs:union>可能应用最多的是定义Color的值,它既可以是字符串(“red”),也可以是16进制表示法(#ff0000)。在上面的"subject3"的定义中也说明了一件事,枚举值即可以使用<xs:enumeration>定义,也可以使用正则表达式<xs:pattern>来定义。
下面在<subject>元素中使用新的数据类型subject3
<xs:element name="subject"> <xs:complexType> <xs:simpleContent><!--simpleContent仅仅是用于扩展复杂结构类型--> <xs:extension base="subject3"> <xs:attribute name="main" default="false" type="xs:boolean"></xs:attribute> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:element>
<xs:extension>元素扩展了简单结构类型subject3,使subject3还包含有一个main属性。
4.8.<students>及其子元素的定义
根据4.1-4.7的说明,很容易举一反三得出<students>及其子元素的定义
5.唯一约束和键引用约束
第4节已经完成了所有的元素定义,但是回头看一下第3节的实例场景,发现我们还有几点要求没有完成:
- 老师和学生的名字都不能相同。
- 老师中只有一位是校长
- 学生的直属指导老师应该引用<teachers>下的老师名字。
5.1.老师和学生的名字都不能相同
在<teachers>下添加unique约束
<xs:element name="teachers"> <xs:complexType> ... </xs:complexType> <xs:unique name="uni_tname"> <xs:selector xpath="./s:teacher"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:unique> </xs:element>
这里使用了XPath语法,而且XPath中的元素必须使用命名空间。上面这段代码的意思是在<teachers>元素下的所有<teacher>元素的<name>元素值是唯一的。
同理,在<students>下添加unique约束。
<xs:element name="students"> <xs:complexType> ... </xs:complexType> <xs:unique name="uni_sname"> <xs:selector xpath="./s:student"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:unique> </xs:element>
5.2.老师中只有一位是校长
跟5.1类似,只是XPath这个时候引用的是属性节点。
<xs:element name="teachers"> <xs:complexType> ... </xs:complexType> <xs:unique name="uni_tname"> <xs:selector xpath="./s:teacher"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:unique> <xs:unique name="uni_head"> <xs:selector xpath="./s:teacher"></xs:selector> <xs:field xpath="./@head"></xs:field> </xs:unique> </xs:element>
5.3. 学生的直属指导老师应该引用<teachers>下的老师名字
这里要用到键引用约束,键引用约束与数据库中的主键和外键的概念非常相似。在这个场景中,所有的<teachers><teacher><name>的值为主键,而<students><teacher ref="主键值">中的ref为外键引用主键的值。
首先,我们得声明主键
<xs:element name="school"> <xs:complexType> ... </xs:complexType> <xs:key name="key_tname"><!--school元素下的所有teachers元素下的所有teacher元素的name元素值作为键--> <xs:selector xpath="./s:teachers/s:teacher"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:key> </xs:element>
接着,引用主键
<xs:element name="school"> <xs:complexType> </xs:complexType> <xs:key name="key_tname"> <xs:selector xpath="./s:teachers/s:teacher"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:key> <xs:keyref name="ref_tname" refer="key_tname"> <xs:selector xpath="./s:students/s:student"></xs:selector> <xs:field xpath="./s:teacher/@ref"></xs:field> </xs:keyref> </xs:element>
5.4.完整的XSD文档的最终版
<?xml version="1.0" encoding="utf-8" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.sin90lzc.com/school" xmlns:s="http://www.sin90lzc.com/school" xmlns="http://www.sin90lzc.com/school" elementFormDefault="qualified"> <xs:simpleType name="subject3"> <xs:union> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Math"></xs:enumeration> <xs:enumeration value="Chinese"></xs:enumeration> <xs:enumeration value="English"></xs:enumeration> </xs:restriction> </xs:simpleType> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="数学|语文|英语"></xs:pattern> </xs:restriction> </xs:simpleType> </xs:union> </xs:simpleType> <xs:element name="school"> <xs:complexType> <xs:sequence> <xs:element name="teachers"> <xs:complexType> <xs:sequence> <xs:element name="teacher" minOccurs="1" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="name"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="[A-Z](\w*)"></xs:pattern> </xs:restriction> </xs:simpleType> </xs:element> <xs:element name="subject"> <xs:complexType> <xs:simpleContent> <xs:extension base="subject3"> <xs:attribute name="main" default="false" type="xs:boolean"></xs:attribute> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="head" type="xs:boolean"></xs:attribute> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> <xs:unique name="uni_tname"> <xs:selector xpath="./s:teacher"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:unique> <xs:unique name="uni_head"> <xs:selector xpath="./s:teacher"></xs:selector> <xs:field xpath="./@head"></xs:field> </xs:unique> </xs:element> <xs:element name="students"> <xs:complexType> <xs:sequence> <xs:element name="student" maxOccurs="unbounded" minOccurs="1"> <xs:complexType> <xs:sequence> <xs:element name="name"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="[A-Z](\w*)"></xs:pattern> </xs:restriction> </xs:simpleType> </xs:element> <xs:element name="teacher"> <xs:complexType> <xs:attribute name="ref" type="xs:string"> </xs:attribute> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> <xs:unique name="uni_sname"> <xs:selector xpath="./s:student"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:unique> </xs:element> </xs:sequence> </xs:complexType> <xs:key name="key_tname"> <xs:selector xpath="./s:teachers/s:teacher"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:key> <xs:keyref name="ref_tname" refer="key_tname"> <xs:selector xpath="./s:students/s:student"></xs:selector> <xs:field xpath="./s:teacher/@ref"></xs:field> </xs:keyref> </xs:element> </xs:schema>
6.优化XSD文档
这里直接给出优化后的XSD文档,相信大家的智慧可以轻易看出如何优化XSD文档。
<?xml version="1.0" encoding="utf-8" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.sin90lzc.com/school" xmlns:s="http://www.sin90lzc.com/school" xmlns="http://www.sin90lzc.com/school" elementFormDefault="qualified"> <xs:simpleType name="subject3"> <xs:union> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Math"></xs:enumeration> <xs:enumeration value="Chinese"></xs:enumeration> <xs:enumeration value="English"></xs:enumeration> </xs:restriction> </xs:simpleType> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="数学|语文|英语"></xs:pattern> </xs:restriction> </xs:simpleType> </xs:union> </xs:simpleType> <xs:complexType name="subjectType"> <xs:simpleContent> <xs:extension base="subject3"> <xs:attribute name="main" default="false" type="xs:boolean"></xs:attribute> </xs:extension> </xs:simpleContent> </xs:complexType> <xs:simpleType name="nameType"> <xs:restriction base="xs:string"> <xs:pattern value="[A-Z](\w*)"></xs:pattern> </xs:restriction> </xs:simpleType> <xs:element name="name" type="nameType"></xs:element> <xs:complexType name="teacherType"> <xs:sequence> <xs:element ref="name"></xs:element> <xs:element name="subject" type="subjectType"></xs:element> </xs:sequence> <xs:attribute name="head" type="xs:boolean"></xs:attribute> </xs:complexType> <xs:complexType name="teachersType"> <xs:sequence> <xs:element name="teacher" minOccurs="1" maxOccurs="unbounded" type="teacherType"></xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="refTeacherType"> <xs:attribute name="ref" type="xs:string"></xs:attribute> </xs:complexType> <xs:complexType name="studentType"> <xs:sequence> <xs:element ref="name"></xs:element> <xs:element name="teacher" type="refTeacherType"></xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="studentsType"> <xs:sequence> <xs:element name="student" maxOccurs="unbounded" minOccurs="1" type="studentType"> </xs:element> </xs:sequence> </xs:complexType> <xs:complexType name="schoolType"> <xs:sequence> <xs:element name="teachers" type="teachersType"> <xs:unique name="uni_tname"> <xs:selector xpath="./s:teacher"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:unique> <xs:unique name="uni_head"> <xs:selector xpath="./s:teacher"></xs:selector> <xs:field xpath="./@head"></xs:field> </xs:unique> </xs:element> <xs:element name="students" type="studentsType"> <xs:unique name="uni_sname"> <xs:selector xpath="./s:student"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:unique> </xs:element> </xs:sequence> </xs:complexType> <xs:element name="school" type="schoolType"> <xs:key name="key_tname"> <xs:selector xpath="./s:teachers/s:teacher"></xs:selector> <xs:field xpath="./s:name"></xs:field> </xs:key> <xs:keyref name="ref_tname" refer="key_tname"> <xs:selector xpath="./s:students/s:student"></xs:selector> <xs:field xpath="./s:teacher/@ref"></xs:field> </xs:keyref> </xs:element> </xs:schema>
当然,如果想要school.xml中看到结果,还需要稍微改一下school.xml
<school xmlns="http://www.sin90lzc.com/school" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sin90lzc.com/school school_opt.xsd"> </school>