第3章 XML名称空间
本章要点
• 掌握名称空间声明的两种形式
• 掌握名称空间在元素中的运用
• 掌握名称空间在属性中的运用
• 理解名称空间的应用会导致文档有效性出现问题
一个XML文档中可以包含许多元素与属性,当我们使用其他人的XML文档,或者在文档中使用多个DTD文件时,就有可能碰到名称相同的元素,而这些名称相同的元素可能代表了完全不同的含义。比如:title可以用于表示标题,也可以用于表示某个人的头衔;table可以用于表示表格,也可以用于表示桌子。当这些具有相同名称不同含义的元素混合到一个文档中时,就会造成理解和处理上的混乱。为了解决这个问题,就要用到W3C发布的另一个推荐标准——XML名称空间,读者可以在http://www.w3.org/TR/REC-xml-names/上查看该规范的详细内容。
那么什么是名称空间呢?比方说,在北京,有两个地方叫做小营,我们坐出租车的时候,说去小营,有时候司机就会走错地方,所以,我们在说去小营的时候,总是说“去亚运村的小营”或者“去清河的小营”,当我们加上一个名称空间的时候(亚运村或清河),就构成了一个完整的限定名,这样司机就不会走错了。XML名称空间的概念与此类似,也是通过给元素或属性加上一个名称空间,来唯一标识一个元素或属性。实际上名称空间的概念在很多程序设计语言中早已存在,在Java中也有类似的概念,我们可以把Java中的包看成是它里面所有类和接口的名称空间,类是类中所有变量和方法的名称空间。
3.1 声明名称空间
名称空间通过使用一系列的保留属性来声明,这种属性的名字必须是以“xmlns”或以“xmlns:”作为前缀。与其他任何XML属性一样,这些属性可以直接或以默认的方式给出。
有以下两种形式的名称空间声明。
(1)第一种形式:<元素名xmlns:prefixname="URI">
元素名是指你在哪一个元素上声明名称空间,在这个元素上声明的名称空间适用于声明它的元素和属性,以及该元素内容中的所有元素及其属性。xmlns:prefixname作为该元素的属性名,属性的值是一个URI引用,是标识该名称空间的名称空间名字。其中prefixname给出名称空间前缀的名字,该前缀用于将元素及属性的名字与URI关联在一起。需要注意的是,在这样的声明中,名称空间的名字不能为空("")。如果有两个URI,其组成字符完全相同,就可以认为它们标识了同一个名称空间。
来自于XML名称空间的名字可以作为限定名(qualified names)出现,限定名包含了一个以冒号(:)分隔的名称空间前缀和一个本地部分(local part)。映射到URI引用的名称空间前缀选择了一个名称空间。
我们看一个例子:
<hr xmlns:hr="http://www.sunxin.org/hr">
这个例子声明了一个名称空间,它的名字就是http://www.sunxin.org/hr,因为URI可以包含一些在XML名称中不允许的字符,而且太长,所以需要有一个简短的名称空间前缀,在本例中,就是hr,它和名称空间名字(http://www.sunxin.org/hr)相关联。这样,就可以直接使用这个前缀,作为元素或属性限定名的一部分,从而标识这个元素或属性属于哪一个名称空间。例如:<hr:employee>,hr是限定名的名称空间前缀部分,employee是限定名的本地部分,表示这个元素属于hr所关联的名称空间。当XML处理器解析文档时,将前缀替换成所关联的URI。
提示
名称空间推荐标准中指出,在名称空间声明中,使用相对URI引用已经被废弃了(不赞成使用)。
名称空间前缀可以是不包含冒号的任何合法的XML名称。
在声明名称空间时,有两个前缀是不允许使用的,它们是xml和xmlns。xml前缀只能用于XML 1.0规范中定义的xml:space和xml:lang属性,前缀xml被定义为与名称空间名字http://www.w3.org/XML/1998/namespace绑定。前缀xmlns仅仅用于声明名称空间的绑定,它被定义为与名称空间名字http://www.w3.org/2000/xmlns/绑定。
(2)第二种形式:<元素名xmlns="URI">
这种声明形式没有给出名称空间的前缀名,URI所标识的是默认的名称空间。在这样的默认声明中,属性值可以为空("")。我们看一个例子:
<hr xmlns="http://www.sunxin.org/hr">
表示声明了一个默认的名称空间,hr元素及其内容中所有的没有前缀的元素都属于http://www. sunxin.org/hr所标识的名称空间,除非被hr元素内容中其他的默认名称空间声明所覆盖。
注意
在声明名称空间时,选择的URI不需要指向实际的内容,在URI所标识的位置上,可以不存在任何东西。在名称空间声明中的URI,只是形式上的标识符,其唯一的目的是提供一个唯一的名字。
3.2 名称空间在元素和属性中的运用
下面我们通过几个例子来看一下名称空间在元素和属性中的运用。
3.2.1 名称空间在元素中的运用
例3-1是一个关于图书的格式良好的XML文档,但是它有一个问题,title元素既用于表示书名,又用于表示作者的头衔,两个同名的元素却具有不同的含义。为了解决这个问题,我们决定采用名称空间,如例3-2所示。
例3-1
<?xml version="1.0" encoding="GB2312"?> <books> <book> <title>JSP深入编程</title> <author> <name>张三</name> <title>作家</title> </author> </book> <book> <title>XML从入门到精通</title> <author> <name>李四</name> <title>教师</title> </author> </book> </books>
例3-2
<?xml version="1.0" encoding="GB2312"?> <books xmlns:people="http://www.sunxin.org/people"> <book> <title>JSP深入编程</title> <author> <people:name>张三</people:name> <people:title>作家</people:title> </author> </book> <book> <title>XML从入门到精通</title> <author> <people:name>李四</people:name> <people:title>教师</people:title> </author> </book> </books>
我们声明了一个名字为http://www.sunxin.org/people的名称空间,将前缀people与名称空间名字相关联,然后给元素author下的子元素name和title都附加了前缀。现在我们很容易就能区分书名和作者的头衔,前者以title元素表示,后者以people:title元素表示。
注意
在例3-2中,名称空间http://www.sunxin.org/people的声明范围适用于<books>元素和它的属性,以及该元素内容中的所有元素及其属性,但是只有具有名称空间前缀people的元素和属性才存在于声明的名称空间中。也就是说,元素books并不存在于名称空间中。
一个元素也可以有多个名称空间前缀的声明作为属性,如例3-3所示。
例3-3
<?xml version="1.0" encoding="GB2312"?> <books:books xmlns:people="http://www.sunxin.org/people" xmlns:books="http://www.sunxin.org/books"> <books:book> <books:title>JSP深入编程</books:title> <books:author> <people:name>张三</people:name> <people:title>作家</people:title> </books:author> </books:book> <books:book> <books:title>XML从入门到精通</books:title> <books:author> <people:name>李四</people:name> <people:title>教师</people:title> </books:author> </books:book> </books:books>
要注意的是,在名称空间的声明中,重要的是URI而不是前缀。前缀可以改变,只要URI不变,文档的含义就不变。在下面的例子(例3-4)中使用前缀p和bks代替people和books,但是该文档和例3-3的效果是一样的。
例3-4
<?xml version="1.0" encoding="GB2312"?> <bks:books xmlns:p="http://www.sunxin.org/p" xmlns:bks="http://www.sunxin.org/bks"> <bks:book> <bks:title>JSP深入编程</bks:title> <bks:author> <p:name>张三</p:name> <p:title>作家</p:title> </bks:author> </bks:book> <bks:book> <bks:title>XML从入门到精通</bks:title> <bks:author> <p:name>李四</p:name> <p:title>教师</p:title> </bks:author> </bks:book> </bks:books>
提示
名称空间的声明不一定要放在根元素上,可以根据需要在任何元素上声明名称空间。
3.2.2 默认名称空间
在具有很多元素(这些元素都在同一个名称空间中)的文档中,给每个元素名称都添加一个前缀将是一件繁琐的事情。为此,我们可以使用没有前缀名的xmlns属性将默认的名称空间附加给元素及其子元素,元素本身及其子元素都被认为是在默认的名称空间中,除非它们有明确的前缀。下面我们看一个例子,如例3-5所示。
例3-5
<?xml version="1.0" encoding="GB2312"?> <books xmlns:p="http://www.sunxin.org/p" xmlns="http://www.sunxin.org/bks"> <book> <title>JSP深入编程</title> <author> <p:name>张三</p:name> <p:title>作家</p:title> </author> </book> <book> <title>XML从入门到精通</title> <author> <p:name>李四</p:name> <p:title>教师</p:title> </author> </book> </books>
books元素本身(没有前缀)和它的内容中所有没有前缀的元素(book、title、author)都属于默认名称空间http://www.sunxin.org/bks,而author元素的子元素name和title则属于http://www.sunxin. org/p名称空间。要注意的是,如果元素books有前缀,那么就只有它的内容中所包含的没有前缀的元素属于默认名称空间。
默认名称空间声明中的URI可以设为空字符串,这样的话,在它的声明范围内,没有前缀的元素将被认为不存在于任何的名称空间中,这和没有声明默认名称空间是一样的。我们看一个例子,如例3-6所示。
例3-6
<?xml version="1.0" encoding="GB2312"?> <books xmlns="http://www.sunxin.org/bks"> <book> <title>JSP深入编程</title> <auhtor xmlns=""> <name>张三</name> <title>作家</title> </auhtor> </book> </books>
author元素本身和它内部的name和title子元素不存在于默认名称空间http://www.sunxin.org/bks中,也不存在于任何其他的名称空间中。
3.2.3 名称空间在属性中的运用
因为属性是属于某个元素的,我们很容易就能把它与其他元素具有相同名称的属性区分开来,所以给属性添加名称空间不如给元素添加名称空间那么重要。但如果有必要,仍然可以给属性添加名称空间。
在例3-7中,auhtor元素的属性id_card存在于http://www.sunxin.org/people名称空间中。
例3-7
<?xml version="1.0" encoding="GB2312"?> <books xmlns:bks="http://www.sunxin.org/bks" xmlns:p="http://www.sunxin.org/people"> <bks:book> <bks:title>JSP深入编程</bks:title> <bks:author p:id_card="CH-100-200"> <p:name>张三</p:name> <p:title>作家</p:title> </bks:author> </bks:book> </books>
要注意的是,一个属性要想在某个名称空间中,必须给该属性显式地加上名称空间的前缀,没有前缀的属性不在任何的名称空间中(包括默认的名称空间)。即使拥有属性的元素在某个名称空间中,没有前缀的属性仍然不在该名称空间或任何其他的名称空间中。
在XML文档中,没有任何一个标签可以包含两个相同的属性。所谓相同有两种情况:一种是属性的名字完全相同;另一种是属性限定名中的本地部分完全相同,而不同的前缀绑定到了相同的名称空间名字。下面我们通过两个例子(例3-8和例3-9)来说明名称空间在属性中运用时要注意的问题。
例3-8
<?xml version="1.0" encoding="GB2312"?> <x xmlns:n1="http://www.w3.org" xmlns:n2="http://www.w3.org" > <!--错误,两个属性的名字相同--> <bad a="1" a="2" /> <!--错误,前缀n1和n2绑定的是同一个名称空间名字,而本地部分也完全相同--> <bad n1:a="1" n2:a="2" /> ① </x>
① 曾经有读者问我,“对于①处的XML代码在使用IE浏览时,为什么没有报错呢?而你的书上说前缀n1和n2绑定的是同一个名称空间名字,而本地部分也完全相同,IE应该会报错啊”。
实际上,即使你使用XMLSpy浏览①处的代码,XMLSpy也不会报错。这是因为这两个软件内置的XML解析器默认没有打开对名称空间的支持。如果我们在解析XML文档时,打开了对名称空间的支持,那么①处的代码将会引发错误。
例3-9
<?xml version="1.0" encoding="GB2312"?> <x xmlns:n1="http://www.w3.org" xmlns="http://www.w3.org" > <!--正确,属性名不相同--> <good a="1" b="2" /> <!--正确,没有前缀的属性a不在任何的名称空间中,即使是默认的名称空间也不例外--> <good a="1" n1:a="2" /> </x>
3.3 名称空间和DTD
在名称空间这一节的例子中,我们没有用到DTD,这是因为当XML文档应用了名称空间后,其有效性就有可能出现问题。
这是因为DTD和名称空间并不相关,当XML处理器在验证文档的有效性时,根本不管元素前缀的含义,只是按照DTD的规范对文档进行有效性验证。我们在声明名称空间时,使用了xmlns或xmlns:prefixname属性,而在验证时,会发现在DTD中没有声明该属性,当然验证就会失败。另外,如果在DTD中声明的是book元素,而在文档中使用的是bks:book元素,则文档也是无效的。所以,为了让使用名称空间的文档有效,我们必须在DTD中像声明其他属性一样声明xmlns或xmlns: prefixname属性,此外,还需要重写所有带有前缀的元素和属性的声明。例如,以前有个文档没有使用名称空间,如例3-10所示。
例3-10
<?xml version="1.0" encoding="GB2312"?> <!DOCTYPE book [ <!ELEMENT book (title, author)> <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> ]> <book> <title> JSP深入编程</title> <author>张三</author> </book>
现在为文档中的元素应用了名称空间,相应地就要重写DTD,如例3-11所示。
例3-11
<?xml version="1.0" encoding="GB2312"?> <!DOCTYPE book [ <!ELEMENT book (bk:title, bk:author)> <!ATTLIST book xmlns:bk CDATA #REQUIRED> <!ELEMENT bk:title (#PCDATA)> <!ELEMENT bk:author (#PCDATA)> ]> <book xmlns:bk="http://www.sunxin.org/bk"> <bk:title> JSP深入编程</bk:title> <bk:author>张三</bk:author> </book>
如果读者对于使用了名称空间的文档要重写DTD还有什么疑问的话,最简单的方法就是忘掉名称空间,将文档看成标准的XML文档,只不过这个文档有一些包含了冒号的元素和属性名称,然后根据这个文档,重新编写它的DTD。
读者也许会问,如果我的文档使用了名称空间就要重写DTD,那岂不是非常麻烦。实际上,这也是名称空间饱受争议的地方。因为名称空间声明中的URI只是用于提供一个唯一的名字,并不是标识用于验证的DTD文件,何况它还可以指向其他类型的资源。为了验证文档的有效性,仍然是通过查找在文档类型声明中的内部或外部的DTD进行验证。当然,为了最大限度地减少对DTD的修改,我们可以使用默认的名称空间,如例3-12所示。
例3-12
<?xml version="1.0" encoding="GB2312"?> <!DOCTYPE book [ <!ELEMENT book (title, author)> <!ATTLIST book xmlns CDATA #REQUIRED> <!ELEMENT title (#PCDATA)> <!ELEMENT author (#PCDATA)> ]> <book xmlns="http://www.sunxin.org/bk"> <title></title> <author></author> </book>
这样,我们只需要在DTD中,为元素book添加一个xmlns属性的声明就可以了。但是默认的名称空间并不能完全解决冲突,如果另一个DTD将book元素定义为包含#PCDATA,那么XML处理器在进行有效性验证的时候,就会看到两个一样的元素book却有两种定义,于是判定文档是无效的,在这种情况下,就只能通过在文档和DTD中添加前缀以区分两个不同的book元素。
3.4 小结
本章主要介绍了XML名称空间,主要内容包括:
● 为了解决多个XML文档命名冲突的问题,可以利用W3C发布的另一个推荐标准——XML名称空间。
• 在声明名称空间时,选择的URI不需要指向实际的内容,在URI所标识的位置上,可以不存在任何东西。在名称空间声明中的URI,只是形式上的标识符,其唯一的目的是提供一个唯一的名字。在名称空间声明时,不能使用相对URI,而要使用绝对URI。
• 来自于XML名称空间的名字可以作为限定名(qualified name)出现,限定名包含了一个以冒号(:)分隔的名称空间前缀和一个本地部分。要注意,没有冒号的名字也可以是限定名(在默认名称空间的情况下)。
• 默认名称空间声明中的URI可以设为空字符串,在声明范围内,没有前缀的元素将被认为不存在于任何的名称空间中。
一个属性要想在某个名称空间中,必须给该属性加上名称空间前缀,没有前缀的属性不在任何的名称空间中(包括默认的名称空间)。即使拥有属性的元素在某个名称空间中,没有前缀的属性仍然不在该名称空间或任何其他的名称空间中。