3.1 XML语法基础

为了便于讨论,先给出一个XML文档的样本,通过分析,逐渐展开。

例3.1 一个XML文档示例。

这是一个描述图书信息的XML文档。根元素是booklist,下面有若干个子元素book,在book中有表示具体数据的子元素name、author、press、pubdate、price五个元素。另外,book元素还包含一个附属于它的属性isbn。

3.1.1 结构良好性

结构良好性(well-formedness)是XML规范中特别强调的重要概念,XML文档的书写必须满足“结构良好(well-formed)”的原则。所谓结构良好的必要条件是:

①它总体是一个XML文档。

②它满足所有在XML 1.0规范中提出的关于结构良好性的约束。

③在XML文档中被直接或间接引用的每一个解析实体(Parsed Entity)也是结构良好的。

其中关于XML文档的定义如下:

①它包含至少一个元素。

②有一个叫做根或文档(document)的元素,它不能作为任何其他元素的内容出现。对于所有其他元素,如果开始标签出现在另一个元素的内容中,则结束标签也要在同一个元素中。简言之,由开始标签和结束标签界定的元素应完好地相互嵌套。

是什么原因使W3C对结构良好性受到如此重视,以至于在XML 1.0规范的开篇之处就讨论结构良好的问题?这是因为,过去几年来,WWW在Internet上取得了巨大成就,但是,用于描写WWW的基本编程语言是HTML。相当数量的人在书写HTML时的不规范性和随意性,使得HTML文件的可读性大大降低,甚至出现混乱的局面,如写标签时不写结束标签、写属性值时不用引号("或')、不区分大小写等。而浏览器非常宽宏大量,几乎所有的不规范都可以勉强通过解析并显示出来,使得一部分编程人员可以继续这种不规范行为和随意性,客观上更加剧了这种不规范和随意性的泛滥。W3C的专家们期望XML规范是下一代WWW的主要编程语言和标准,他们不希望几年后,XML的使用也像HTML一样出现任何问题。所以,把结构良好性写在了XML 1.0规范的重要位置,用于强调XML文档中的所有词素和语法必须是定义明确的、结构良好的。

3.1.2 XML声明

例3.1 中的第一行出现的<?xml version="1.0"?>是XML规范规定的所有XML文件必须具有的一行,称为XML声明,并规定,这个声明必须写在每一个XML文档的第一行,以声明和标注这是一个XML文档,否则它就不是一个XML文档,并依此来区别于其他类型的文档。

1.XML声明

XML声明的格式为:

<?xml version="1.0" encoding="" standalone=""?>

一般情况下,上面的格式中可以省去encoding和standalone,但不能省略version(版本)声明。

例3.2 不带encoding和standalone的XML文档。

<?xml version="1.0"?>

<hello>Good morning!</hello>

XML的解析程序可以正确地得到上面文本的正确结果,因为使用的字符集是ASCII码,对于其他的字符集合,必须事先声明。

2.XML的版本号

尽管XML文档的版本号可以取"1.x",其中x可以是0~9的数字,但XML 1.0文档只用"1.0"作为版本声明的数字。当XML 1.0处理器遇到"1.x"而不是"1.0"的版本数字时,它将把其当作"1.0"文档处理。所以不管XML 1.0出到第几个版本(现在是第五版本),均约定version声明为"1.0"(即version="1.0")。

3.XML的字符选择

XML文档的默认字符系统是UTF-8,与传统的ASCII字符集一致,如果处理的是英文字符,这已经满足了需要。此时XML文档可以使用英文字符。

如果使用其他国家的代码,如拉丁文字、中文、朝鲜文、日文,则必须使用Unicode字符集来定义XML文档将要使用的特定字符。

为了解决不同语言标准互不兼容的问题,Unicode为每一个字符提供唯一的编号。Unicode用16位二进制代码对字符进行编码,这样可以产生65536个可能的不同字符。ASCII字符集在Unicode中只占很小的一部分。目前常见的字符集有UTF-8、UTF-16、ISO-10646-UCS-2、ISO-10646-UCS-4、ISO-8859-n。下面是用几种不同的字符集对例3.2 XML文档进行定义和改写后的代码。

例3.3 字符集为ISO-8859-1的XML文档。

<?xml version="1.0" encoding="ISO-8859-1"?>

<hello>Good morning!</hello>

例3.4 字符集为ISO-8859-1的XML文档。

<?xml version="1.0" encoding="ISO-8859-1"?>

<hello>早上好!</hello>

要想使用其他字符集,可以如法炮制。读者可以编辑这些程序,然后用IE浏览器浏览,查看其结果如何,以理解XML的代码集合的正确使用。如果要使用中文字符集,必须有encoding="GB2312"声明。因为,GB2312中列出了基本的汉字交换标准。

例3.5 字符集为GB2312的XML文档。

<?xml version="1.0" encoding="GB2312"?>

<hello>你好,世界!</hello>

请读者用浏览器浏览,看看结果怎样。如果删除encoding="GB2312",情况又会怎样?

4.standalone属性

独立文档声明(standalone)以XML声明的成分出现,告诉文档是否存在一个外部的文档实体或参数实体。即在XML声明中的standalone属性用来定义是否存在外部的标记声明。如果不存在外部标记声明(该声明影响信息从XML处理器传输到应用程序),standalone取值为“yes”。如果可能存在这类外部标记声明,则取值为“no”。如果不存在外部标记声明,standalone声明没有意义。如果存在外部标记声明而又没有写明standalone的取值,则系统假定该值为“no”。

注意:外部标记声明仅只表示外部声明的存在。在一个文档中,存在对外部实体的引用,而这些实体又在文档内部进行了声明,此时文档的standalone状态不会改变。

任何有standalone="no"声明的XML文档,可以在算法上转变成独立文档,这种文档对某些网络发布应用可能是需要的。

下面是standalone声明的示例:

<?xml version="1.0" standalone="yes"?>

3.1.3 XML元素

元素(element)是XML中最为重要的组成部分。如果一个XML文档中没有元素,则该文档就不是一个XML文档。按照XML的结构良好性要求,一个XML文档至少有一个叫根元素的元素存在。在XML文档的根元素下可以有若干级子元素,各级子元素形成树状结构。关于XML文档的树状结构,在XML DOM一章中还要讨论。

1.元素标签与元素名

元素标签(tag)由两个定界符“<”和“>”括起来的有限制字符串组成,如上例中的<book>、<name>,在“<”和“>”中的有限制字符串叫做元素名。所谓“有限制”,是指元素名要满足一定的命名规则。XML的元素标签与HTML中的元素标签形式一样,所不同的是HTML的元素标签由HTML规范规定,XML的元素标签由用户自己规定。元素标签简称标签(tag)。

在XML中,元素标签分为开始标签和结束标签。开始标签由左角括弧“<”和右角括弧“>”把元素名括起来。结束标签是在开始标签的左角括弧“<”后紧跟符号“/”。如上例中的<book>、<name>是开始标签,它们的结束标签分别是</book>、</name>。

在开始标签和结束标签中包含的任意字符串称为元素值。除了包含元素值以外,在开始标签和结束标签还可以包含下一级子元素。如<name>数据通信与计算机网络</name>定义了一个图书书名的元素,元素值是“数据通信与计算机网络”。而<book>…</book>包含的就是多个下一级子元素。

元素名的一般命名规则要求具有确切含义,建议用户在给元素命名时使用自然语言中有一定意义的单词或者单词的组合作为元素名。例如booklist定义了图书列表,author定义了作者元素,press定义了出版社元素等。除此之外,还有如下规则:

①首字符是英文、汉字字符或其他字符,后跟数字或其他符号;首字符不能用数字、语音符号、英文句号“.”和连字符“-”开头,但可以使用英文冒号“:”和下划线“_”。

②严格区分英文字母的大小写。

③不能使用“X”“M”“L”三个字母的任意大小写搭配的字符串:XML、xml、xMl、Xml、…等作为元素名称,或作为元素名的开头。

④不能独立使用数字作为元素名。

⑤元素名中可以使用英文“_”“.”“:”“-”等符号,但避免使用空格符,除用于名称空间描述外,最好不用冒号作为元素名,因为XML名称空间赋予冒号“:”特殊的用途。

⑥不能使用英文“<、>、?、/、&、+、*”等符号作为元素名。

为了便于理解上述定义,下面是一些非法的标记名示例。

合法的元素标签:

使用汉字作为XML元素标签是合法的,如最后两个标记。

另外还要注意,因为要用来作为XML名称的定界符,ASCII码中(除英文字母和数字外)的符号和标点符号,还有Unicode中相当大的一组符号在XML的名称中不能使用,使用这组字符不能保证给出的上下文是XML名称的组成部分。分号“;”会标准化为逗号“,”,有可能改变实体引用的意义,也不用于XML名称。

2.元素嵌套

每个XML文档必须有一个且只能有一个称为“根”(root)的元素,如例3.1中的<booklist>。其他元素必须写在一对根元素<booklist>和</booklist>之间。

元素可以包含值,可以为空值,还可以有下层子元素。

某元素的子元素写在该元素的开始标签之后和结束标签之前,如每一个<book>元素下的所有子元素写在<book>之后和</book>之前,这种结构称为元素嵌套。元素嵌套子元素,子元素可以嵌套下一级子元素。这种嵌套必须严格。图3.1是几种嵌套情况,在图3.1(a)中的<a>元素有两个平级子元素<b>、<c>,<b>、<c>元素书写正确。图3.1(b)中<a>元素有两级子元素<b>、<c>,其中<c>又是<b>的子元素,<c>元素书写正确。图3.1(c)中<a>元素有<b>、<c>,但<b>、<c>元素交叉嵌套,因此错误。图3.1(d)中<a>和<b>是同级元素,但交叉嵌套,因此错误。

图3.1 元素嵌套

3.空元素

当元素标签之间没有元素值时,这样的元素叫空元素。如图3.1(a)中的元素<b>、<c>是空元素,但是<a>不是空元素,因为它包含了两个子元素,尽管其子元素为空。

当元素为空值时,其表示可以简化成用“<”和“/>”包括元素名。如<name></name>简化成<name/>,<phone></phone>可以简写成<phone/>。

对于在XML转换文档中出现的某些无结束元素的HTML元素,如<img>、<br>、<hr>,为了满足结构良好性,在XML转换程序中使用时应该写成<img/>、<br/>、<hr/>。这样既可以满足XML的结构良好性要求,又可以在XML应用中使用HMTL中的既定元素。

3.1.4 XML元素的属性

与HTML一样,XML元素也有属性(Attribute)。为了便于理解XML中的属性,先看HTML中几个元素的属性,如<font color="" face="" size="">、<img src="" width=""height="" alt="">

对于熟悉HTML语法的读者,很容易理解上面两个标签的含义和用法。前者是定义字符的标签,其中color、face、size都是属性,分别定义颜色、字体、字大小。后者定义图形,其中src、width、height、alt也是属性。这说明在HTML中大量使用了属性。

1.属性的定义

在XML文档中,属性是用来与元素联系起来的一对“名字-值”,属性的定义不能出现在元素的开始标签和空元素标签之外。元素的属性可以是一个,也可以是多个。当出现多个属性时,每个属性之间用空格分隔。多个属性又叫属性列表,对于一个给定的元素,属性列表是附属于这个元素的属性集合,可以确定这些属性的类型约束,可以为属性提供默认的属性值。

属性和属性值之间用“=”号连接,属性值必须用英文的引号("或')括起来。在例3.1中,每个<book>元素中定义了一个isbn的属性,其取值是每本书的ISBN号,其形式是isbn="字符串"。如第一个<book>元素中isbn="7-04-008653-0",第二个<book>元素中isbn="7-113-07019-9"等。

例3.6 是关于服装中衬衫的XML文档,在该文档中为元素<price>定义了currency和unit两个属性,currency定义了衬衫价格的货币种类是人民币(RMB),unit定义了衬衫价格的货币单位是元(Yuan)。这样的定义容易使人把price元素与货币种类currency和货币单位unit这两个属性关联起来,使之成为一个整体。

例3.6 关于服装的XML文档(文件名:ch3-6.xml)。

在XML文档中,属性和元素之间没有本质差别,例3.6中价格的属性可以分别设计成独立的元素,这并不违反XML的语法规则。如例3.7所示。此时currency和unit原来是price元素的属性,现在成为<shirt>的子元素,与price元素形成平级关系,这并不违反XML的语法规则。但是这样做,消除了currency和unit这两个元素跟price元素之间的直接关联关系,可以把它们理解成是与其他元素平级的元素,这与原来的设计意图是矛盾的。另外作为独立元素的unit到底是想表示价格的单位还是衬衫的单位,意义不明确。所以在设计元素和元素属性时,要事先仔细考虑。

例3.7 把例3.6中price元素的属性写成元素。

2.属性类型

XML属性类型有三种:字符串类型、标记类型和枚举类型。字符串类型可以取任意的文字串作为值。标记类型有不同的词法和语义约束。有关属性的定义、使用和说明将在第4章文档类型定义和第5章XML Schema中介绍。

3.1.5 标记与字符数据

1.标记

标记(Makeup)包含下列内容:开始标签(tag)、结束标签、空元素标签、实体引用、字符参考、注释、CDATA节定界符、文档类型声明、处理指令、XML声明、文本声明。

2.字符数据

所有不是标记的文本构成了文档的字符数据。所谓的文本,则是指包含字符数据、标记以及任意位于文档实体顶层(即文档元素之外且不在任何其他标记内)的空白符的混合。

英文and、符号“&”和左角括弧“<”不能以其自身的字符形式出现,除非用于标记的定界符,或者出现在注释、处理指令、CDTAT节中。如果确实需要使用它们,必须以转义字符串“&”和“<”的形式来表示。当需要使用串" ]]> "而不是标记CDATA节的结束符时,右角括弧“>”必须表示成" > "。

在元素内容中,字符数据是任意的字符串,它不包含任何标记开始定界符和不包含CDATA节结束定界符" ]]> "。在CDATA节中,字符数据是不包含CDATA节结束定界符" ]]> "的任意字符串。

当属性值中需要使用单引号“'”和双引号“"”作为值内容时,需用转义字符"'"和"""。

3.1.6 实体

1.实体概念

XML文档可以包含一个以上的存储单元,这些存储单元叫实体(entity),它们全部都有内容且由实体名称来标识(除文档实体和外部DTD子集外)。每个XML文档有一个叫文档实体的实体,用来作为XML处理器的起始位置,并可以包含整个XML文档。

实体可以是解析的和不可解析的。所谓解析实体是:包含一个字符序列的文本,它可以用来表示标记和字符数据。所谓不可解析实体的是:它的内容可以是或者不是文本,如果是文本,那么不是XML。每一个不可解析的实体与一个表示法联系,由名称标识。除了要求XML处理器把这些用于实体和表示法的标识符应用于应用程序外,XML对不可解析实体的内容没有约束。

解析实体用实体引用的名称调用,不可解析实体用给定ENTITY或者ENTITIES属性值来调用。关于ENTITY或者ENTITIES的讨论,请参考4.3.2节。

2.引入实体的原因

在XML文档中,由于规定使用“<、>、'、"、&”等符号作为XML文本的标记和内容声明的组成部分,我们通常把这些符号叫做XML的保留字。当需要在XML文档数据中写入上述这些字符时,如果不进行转换,解析器将会把它们理解成XML规范定义的含义,而无法实现把这些符号作为元素数据的目的。如:

在XML文档中表示上述表达式时,由于使用了XML规范规定的保留字符“<、>、&”,在系统解析时就会产生错误,得到错误的信息和字符。为了解决这个问题,XML提供了五种预定义实体,在文档中需要表示这些字符数据时,使用这些预定义实体对这几个保留字进行转义,如表3.1所示。

表3.1 XML规范的预定义实体

需要实体的另外一个原因是,可以通过实体引用调用事先定义好的内部或者外部实体,使多个XML文档可以访问和调用同一个实体。

3.实体声明

实体类别可以按照引用方式和所处位置这两种方式来分类。按引用方式可以分为普通实体和参数实体,按实体所处的位置可以分为内部实体和外部实体。内部实体和外部实体的具体内容由普通实体和参数实体定义。

无论是哪类实体,在使用之前都必须先声明,没有声明的实体的引用被认为是错误的。如果在XML文档中多次声明了同一个实体,则XML处理器可能发布一个警告。

实体的声明因为其类型不同,方法各有差异,下面分别予以介绍。

(1)预定义实体

XML处理器必须识别预定义实体,无论它们是否被声明。由于互操作性的原因,一个合法的XML文档与其他实体一样,在使用前应该声明它们。声明时,实体“<”(lt)和“&”(amp)用双转义字符,实体“>”(gt)、“'”(apos)、“"”(quot)用单转义字符,声明如下:

其中,#34、#38、#39、#60和#62分别是quot、amp、apos、lt、gt符号的ASCII码。

(2)普通实体

普通实体是那些用在XML文档内容中的实体。普通实体的声明格式为:

<!ENTITY实体名实体定义>

其中,前面的“!”和关键词“ENTITY”不能缺少,实体名是将要被定义的实体名称,实体定义是指实体所取的值,通常是一个字符串,例如:

<!ENTITY qhpress "清华大学出版社">

<!ENTITY hepress "高等教育出版社">

上面的两行实体声明中,分别声明了qhpress和hepress的实体,它们的值是"清华大学出版社"和"高等教育出版社"。实体值用一对双引号“"”来界定,且实体值本身不包含双引号,此处的双引号是英文符号(不能使用汉字中的双引号),否则系统在解析时将报错。

注意:实体值中避免使用“<”符号,因为任何对显式包含“<”符号的实体的引用将导致结构良好性错误,如<! ENTITY exampleEntity "<">。

(3)参数实体

参数实体是在DTD内使用的解析实体。参数实体的声明格式为:

<!ENTITY %实体名参数实体>

其中,“!”和关键词“ENTITY”意义同前。参数实体定义必须使用“%”。参数实体是指实体定义可以被引用的一种实体类型,例如:

<!ENTITY % datatype "(#PCDATA)">

这个实体定义了datatype为参数实体,取值为“#PCDATA”。也可以为实体定义多个可以选择的值。例如下面的参数实体type可以取三种数据类型STRING、INTEGER和DATE。

<!ENTITY % type "(STRING|INTEGER|DATE)">

下面的举例,定义了一个外部实体文件的URL,名为X的参数实体。

<!--声明外部参数实体-->

<!ENTITY % X SYSTEM "http://www.kmu.edu.cn/xml/x.dtd" >

注意:普通实体和参数实体使用不同的方式引用,被识别为不同的上下文。更进一步说,它们占据不同的名称空间。同名的普通实体和参数实体是两个不同的实体。

(4)内部实体

在XML文档中定义的实体称为内部实体。普通实体和参数实体均可以作为内部实体或外部实体的内容使用,所以,内部实体也分为普通实体和参数实体。内部实体的定义方法就是普通实体和参数实体的定义方法。例如:

例3.8 内部实体定义。

上面的程序中声明了四个实体hepress、tdpress、ydpress和qhpress,因为,它们位于XML文档的内部,所以是内部实体。程序中出现的DOCTYPE声明是用于引用外部或内部的文件或实体的专用命令,其含义和用法将在DTD中详细讨论。

(5)外部实体

在XML文档外部也可以定义实体。这个实体是与XML文档分离的独立文件,这种实体定义就是外部实体。其定义格式是:

<!ENTITY实体名SYSTEM/ PUBLIC外部实体文件URI NDATA类型名>

其中,标识符SYSTEM和PUBLIC是不能缺少的,它们是定义外部实体不可缺少的部分;外部实体文件URI是指外部实体的参考源,其内容是实体的替换文本,它可能与文档实体、包含外部DTD子集的实体、或某些其他的外部参数实体有关系。如果在外部实体定义中出现NDATA部分,则表示的“实体名”是一个不可解析的实体。例如:

<!ENTITY fact SYSTEM "http://www.kmu.edu.cn/xml/fact.xml">

<!ENTITY source SYSTEM "Conoff.gif" NDATA GIF>

在最后一个实体定义中,由于出现了NDATA部分,表明source是一个不可解析的实体。这里是把图形文件作为不可解析实体进行处理的。

4.实体引用

实体引用(reference)是XML规范中正规使用的词汇,通俗地讲,所谓的实体引用,就是实体的引用或实体的调用,这与程序设计语言中的函数和过程调用十分类似。

实体引用的方法根据实体定义的不同而不同。

对于普通实体,其引用方法为:

&实体名;

对于参数实体,其引用方法为:

%实体名;

例3.9 实体引用示例。

除自定义的普通实体hepress、qhpress、tdpress和ydpress的引用外,上述程序中还使用了符号实体“&”的引用“&”。上面程序的显示效果如图3.2所示。

图3.2 实体引用示例

关于参数实体的定义和引用,可以从下面的DTD定义中得到说明:

在元素name和author中定义了它的类型是PCDATA(解析字符数据),但是这个类型的定义通过引用参数实体datatype来实现。有关PCDATA的详细讲解将在有关DTD章节进行。

参数实体的多值引用是实体引用中常见的引用,下面讨论多值参数实体的定义和引用。对于下面这种情况的元素定义,可以使用多值参数实体来实现:

这样的元素类型定义可以使用参数实体来实现:

5.注意事项

实体声明、定义和引用使XML文档变得结构良好,容易阅读和理解。但使用实体应该注意:

①在引用实体之前必须先定义实体。

②内部实体位于XML文档内部,外部实体独立于XML文档,是一个结构良好的XML格式文件。

③实体引用不能出现递归引用,即不能出现A引用B,B又引用A的情形。

④实体引用时,引用符“&”和“%”与实体名之间不能有空格,最后不能缺少英文符号的“;”。

3.1.7 处理指令

处理指令(Processing Instruction)允许XML文档包含用于应用程序的指令,简称PI。其格式如下:

<?指令名属性="属性值"?>

处理指令用“<?”和“?>”包括起来。从形式上说,<?xml version="1.0"?>就是处理指令,它的作用就是对XML文档进行标识。如下面的样式表转换引用的说明也是处理指令:

<?xml-stylesheet type="text/css" href="book.css"?>

<?xml-stylesheet type="text/xsl" href="clothes.xsl"?>

前者是在样式表转换中引入了CSS文档对XML文档进行转换,后者是用XSL对XML文档进行格式转换。

XML的表示法(Notation)机制可用于PI的正式声明中,在处理指令中不识别参数实体引用。

3.1.8 CDATA节

CDATA节可以出现在字符数据允许出现的地方。

在XML中出现的字符分为可解析的字符数据和不可解析的字符数据。如下面的程序片段是一段C程序,其功能用来实现在给定字符串的指定位置插入字符:

其中出现了XML规范不允许出现的字符实体,如“<、"、>”等符号,按照XML规范,这类数据必须经过实体引用转换才能正确表示出来。如果不使用实体引用,在系统解析时将出现错误。为了解决这类数据在XML中的表示问题,在XML规范中引入了CDATA类型的数据,用来处理这种表示。

CDATA节的定义格式为:

<![CDATA[字符数据]]>

在CDATA节中,只有“]]>”被识别为标记,所以“<”“>”和“&”可以出现在CDATA的文字中,它们不需要使用“<”“>”和“&”来换码。

这样在XML中表示上述程序作为一个代码段,可以写成下面的形式:

如果要在文本中表示XML文档的片段,也可以用这个方法来实现。如要表示例3.1的某个部分,可以表示成:

这样,在系统解析时,那些包含<、>符号的成分不会作为元素识别,而是作为CDATA数据原样显示出来,如图3.3和图3.4所示。

图3.3 CDATA示例1

图3.4 CDATA示例2

把CDATA写进XML文档时,要为其设计一个包含CDATA节内容的根元素,否则解析器将报告错误。

另外,CDATA节中不允许嵌套。即CDATA节中不允许出现在CDATA节中,换句话说,CDATA的定义只能是单层结构。

注意:在IE 9.0后的版本中,CDATA的显示效果不是图3.3和图3.4的样子。

3.1.9 注释

与其他所有的程序设计语言一样,XML文档中也可以使用注释。系统在解析注释时,不把它当作可解析数据处理,并默认其正确性。注释的格式为:

<!--注释文字-->

注释的作用是对XML文档的某些部分进行说明,使文档具有较好的可读性。

注释的书写要注意下面的规则:

①注释可以出现在其他标记外部的任何地方,但不能出现在XML文档的第一行。因为XML规范要求,XML的声明必须位于XML文档的第一行。

②注释可以出现在元素标签(“<”与“>”)外的任何地方。

③为了保持兼容性,注释中不能出现连续两个连字符“--”。

④注释不能嵌套注释。

⑤在注释中不识别参数实体引用。

例如,正确的注释如下:

错误的注释:

<!--This is a example of embedded comment<!--embeded comment-- >-- >

因为使用嵌套,这是一个错误的注释:

因为注释出现在元素标签内部,这个注释是错误的。

注意:不允许使用串“--->”作为注释的结束符。