1.5 XML语法

编写XML文档必须遵循一些语法规则,但在介绍这些规则前,首先要了解标记、元素和属性这3个术语。

1.5.1 XML的标记、元素和属性

XML最主要的术语有标记、元素和属性3个。

1.标记(起始标记,结束标记)

在上面的XML文档中,已经看到有这样一些特征相同的字符串:<title>、<price>、<quantity>、</books>等。它们都是由小于号“<”开始,由大于号“>”结束,在XML规范里,将其称为XML标记。标记又有起始标记和结束标记之分。起始标记由“<”开始,由“>”结束。比如<title>、<price>、<quantity>。结束标记由“</”开始,由“>”结束。比如</title>、</price>、</quantity>。

2.元素

XML元素是指从一个起始标记到它的结束标记间的一段内容。比如<title>The book thief</title>就是一个元素。元素是XML文档的基本单位,一个XML文档可以由一个或者多个元素构成。XML元素可以扩展,以携带更多的信息。假设有如下的XML文档。

<?xml version="1.0" encoding="UTF-8"?>
<note>
  <to>Frank</to>
  <from>John</from>
  <body>Don't forget the meeting!</body>
</note>

假设已经创建了一个应用程序,可将<to>、<from>以及<body>元素从文档中提取出来,并输出以下信息。

MESSAGE
To: Frank
From: John
Don't forget the meeting!

如果这个XML文档作者又向这个文档中添加了一些信息,此时的文档内容如下。

<note>
 <date>2008-08-08</date>
 <to>Frank</to>
 <from>John</from>
 <heading>Reminder</heading>
 <body>Don't forget the meeting!</body>
 <phone>12345678</phone>
</note>

那么这个应用程序会中断或崩溃吗?不会。这个应用程序仍然可以找到XML文档中的<to>、<from>以及<body>元素,并产生同样的输出。XML的优势之一,就是可以经常在不中断应用程序的情况进行扩展。

3.属性(attribute)

一个元素可以带有属性,属性写在起始标签里,位于元素名称的后面。比如:

<books ISBN ="9787544238212">

其中ISBN ="9787544238212"就是books元素的一个属性。其中ISBN是属性的名称,9787544238212是属性的值,在语句中属性值必须加引号。

1.5.2 XML的语法规则

XML主要的语法规则如下。

1.每个起始标签必须有对应的结束标记

例如前面的例子中,起始标记<price>必须有相应的结束标记</price>。

2.一个XML文档只能有一个根元素

XML文档是树状结构的,像一棵节点树。比如上面例子中,<books>就是根元素,而<title>、<price>、<quantity>则是<books>元素的子节点。

如果一个文档中有两个<books>根元素,就会出错,比如下面的文档运行时就会出错。

<?xml version="1.0" encoding="UTF-8"?>
<books ISBN ="9787544238212">
 <title>偷书贼</title>
 <price>25</price>
 <quantity>10</quantity>
</books>
<books ISBN ="978758225">
 <title>香水</title>
 <price>100</price>
 <quantity>12</quantity>
</books>

3.所有的XML元素必须正确嵌套

正确的嵌套如下。

<books><title>香水</title></books>

错误的嵌套如下。

<books><title>香水</books></title>

4.标记区分大小写

XML标记是区分大小写的,起始标记与相应的结束标记的大小写必须一致。

下面的两行代码中,第1行是错误的,第2行是正确的。

<title>我的故事</Title>
<title>我的故事</title>

XML元素是XML文档的基本单位。一个XML文档由一个或者多个XML元素构成。比如<site>woyouxian.net</site>就是一个XML元素,也可以是一个最简单的XML文档。一个XML元素从一个起始标记开始,到对应的结束标记结束。起始标记和结束标记之间的内容,称为XML元素的内容,比如,woyouxian.net就是元素<site>woyouxian.net</site>的内容。

如果一个XML元素没有内容,比如,<site></site>则称其为空元素。空元素有一种特殊的写法,即以“<”开始,然后是元素名称,在以“/>”结束,比如<site />。

XML语法中标记是区分大小写的。比如<SITE>和<site>就表示两个不同的元素。这一点在编写XML文档时要特别注意。

5.XML元素间有父子、同级关系

XML文档是树状结构的,它只有一个根元素,其他元素都是根元素的后代。比如下面的例子:

<?xml version="1.0" encoding="UTF-8"?>
<father>Tom Smith
 <son>John Smith
  <grandson>Hans Smith</grandson>
 </son>
 <daughter>Jane Smith</daughter>
</father>

根元素是<father>,<father>下面有2个子节点,即son和daughter元素,而son元素下面又有一个子节点,即<grandson>元素。

XML元素之间的关系,主要有:

·子节点(child)

·父节点(parent)

·并列节点又称兄弟姐妹关系(sibling)

以上面的例子来解释这些关系。<son>元素和<daughter>元素是<father>元素的子节点。<father>元素是<son>元素和<daughter>元素的父节点。<daughter>元素和<son>元素的关系是并列节点关系。

6.属性的值必须加引号

XML元素可以带有属性。作为XML元素的附加信息,属性写在起始标记里,位于元素名称的后面。比如<book ISBN ="9787544238212">就是一个带有属性的XML元素。XML属性是以名称和值的形式配对出现的,XML属性名称是区分大小写的,比如Name和name就表示两个不同的属性。XML属性的值应用引号围起来,可以用双引号,也可以用单引号。以下两种写法都是正确的,不过通常来说,多采用双引号。

<book ISBN ="9787544238212">
<book ISBN ='9787544238212'>

如果属性的值里包含双引号,就用单引号包围属性值,比如下面的例子。

<site info ='wo"you"xian.net'>

如果属性值里包含单引号,就用双引号包含属性值,比如下面的例子。

<site info ='wo"you"xian.net'>

一个XML元素可以有一个或者多个属性,每个属性都以空格分开。比如下面的例子。

<site name="woyouxian.net" author="me">

7.一个XML元素不能有相同的属性

下面的写法是错误的,因为一个XML元素不能有两个相同的属性名称,即使属性值不同也不允许。

<books ISBN ="9787544238212" ISBN ="97875442dr">

不过,如果将其中一个ISBN改为小写就是对的,比如下面的写法就是正确的。

<books ISBN ="9787544238212" isbn ="97875442dr">

这是因为XML是区分大小写的,ISBN和isbn表示两个不同的属性。

关于属性的应用,还需要详细说明一下。

在描述数据时应该使用XML元素还是属性呢?没有硬性规定说哪些数据应该使用元素,哪些数据应该使用属性,比如以下这两种写法都是对的。

第一种写法,使用属性:

<site name="woyouxian.net">

第二种写法,使用元素:

<site>
 <name>woyouxian.net</name>
</site>

通常来说,表达描述元数据(metadata)时,应使用属性,而描述数据本身时应当使用元素。元数据意为描述数据的数据。比如一篇文章,文章关键词就是元数据,而文章的内容就是数据本身,示例如下。

<article keywords="XML,属性" >

<content>XML可以带有属性,作为XML元素的附加信息。XML属性是以名称和值的形式配对出现的。XML属性应写在起始标签里面,位于起始标记的名称之后。

</content>
</article>

另外,ID索引大都使用属性,比如:

<employee ID="6699">

在XML中,使用属性值通常能够简化文档的书写,但不要太频繁地应用属性值,毕竟XML是用来储存和传送数据信息的,它的可扩展性更为重要,这是因为,用户可能随时需要向XML文件中添加数据,虽然使用属性值可以方便地为元素添加额外的信息说明,但是这样做非常不利于日后的维护和更新。对比下面的两个例子。

示例1:使用属性值

<?xml version="1.0"encoding="UTF-8"?>
<Me Name="jsper"Gender="male" Job="No" Email="jsper@371.net">
</Me>

示例2:不使用属性值

<?xml version="1.0" encoding="UTF-8"?>
<Me>
 <Name>jsper</Name>
 <Gender>male</Gender>
 <Job>No</Job>
 <Email>jsper@371.net</Email>
</Me>

显然,示例2比示例1更易于扩展、维护和更新。此外,使用属性值还有以下一些缺点:

·属性值不可以包含多重数值。

·属性值难于扩展。

·属性值不能够用于描述结构内容(子元素则可以)。

·属性值很难通过DTD来进行测试。

在XML规范中,空白包括空格、制表符和空行。在编辑XML文档时,常常使用空白来分隔标记,以获得较好的可读性。XML的空白字符有两类:有效空白字符和无效空白字符。有效空白字符是文档的一部分,应当保留。有效空白字符是指,在编辑XML文档时,为了提高可读性而添加的字符。在XML文档中,可以在元素中使用一个特殊的属性xml:space,来通知应用程序保留此元素中的空白。在有效的文档中,这个属性和其他任何属性一样,在使用时必须声明。xml:space属性必须被声明为Enumerated(枚举)类型,它的值必须是“default”和“preserve”两者之一,也可两个都取。

“default”允许应用程序根据需要处理空白。如果不包含xml:space属性,结果与使用“default”值相同。“preserve”指示应用程序按原样保留空白,暗示空白可能有含义。xml:space属性的值应用于包含该属性的元素的所有子代,但由一个子元素覆盖时除外。

例如,下列文档指定的是相同的空白行为。

<?xml version="1.0" encoding="UTF-8"?>
<poem xml:space="default">
  <author>
    <givenName>Alix</givenName>
    <familyName>Krakowski</familyName>
  </author>
  <verse xml:space="preserve">
    <line>Roses   are  red,</line>
    <line>Violets  are  blue.</line>
    <signature xml:space="default">-Alix</signature>
  </verse>
</poem>

<?xml version="1.0" encoding="UTF-8"?>
<poem xml:space="default">
  <author xml:space="default">
    <givenName xml:space="default">Alix</givenName>
    <familyName xml:space="default">Krakowski</familyName>
  </author>
  <verse xml:space="preserve">
    <line xml:space="preserve">Roses   are  red,</line>
    <line xml:space="preserve">Violets  are  blue.</line>
    <signature xml:space="default">-Alix</signature>
  </verse>
</poem>

在以上两个示例中,均通知应用程序必须保留诗行中的所有空白,但是文档中其他部分的空白可以根据需要处理。仔细观察二者的不同之处。

1.5.3 XML名称命名规则

XML名称可以包含英文字母、数字,以及其他字符(比如下划线),但不能以数字或者标点符号开头,不能以xml开头(或者xml的其他大小写形式,因为这是XML相关标准的保留词)。XML名称不能包含空格,虽然XML名称可以包含非英语的字符,比如¥、ö和ç等,但是考虑到读取XML文档的软件未必支持多语言,为保证兼容性,建议还是使用英文字母比较稳妥。

虽然XML名称支持下划线(_),连字符(-),句号(.)和冒号(:),但XML名称开头不能用连字符(-)和句号(.)冒号(:),因为它们是XML Namespace要用到的保留符号,这在以后章节中会详述。所以,为保险起见,用户只使用下划线(_)就行了,其他的标点符号不要用。

正确的XML名称示例如下。

<name>

<first_name>

<one_4_all>

错误的XML名称示例如下。

<mom's pie>

<mon/day/year>

<4-u>

<first name>

当为XML名称命名时,建议采用如下的命名习惯。

·使名称具有描述性。可使用带下划线的名称来描述特征。

·名称应当比较简短。比如,使用<book_title>,而不使用<the_title_of_the_book>。

·避免使用“-”字符。如果按照“first-name”的方式进行命名,一些软件会认为用户需要提取第一个单词。

·避免“.”字符。如果按照“first.name”的方式进行命名,一些软件会认为“name”是对象“firste”的属性。

·避免使用“:”字符。冒号会被转换为命名空间来使用(稍后介绍)。

·XML文档经常有一个对应的数据库,其中的字段会对应XML文档中的元素,一个实用的经验是,使用数据库的名称规则来命名XML文档中的元素。

1.5.4 XML实体引用

在XML中,一些字符拥有特殊的意义。比如在XML文档里,除了表示一个标记的开始之外,不允许有其他小于号“<”出现,因为“<”总是被XML解析器解释为一个标记的开始。例如:

<person>if age < 10 </person>

这种写法会导致XML文档解释错误。为避免这样的错误,而用户又需要在XML文档内容里使用小于号,则可以使用小于号的实体引用(entity reference),即“<”来替换小于号。因此上面的例子正确的写法如下。

<person>if age < 10 </person>

XML文档内容里也不能用“&”这个字符,因为“&”被解析为某个实体引用的开始。所以,用户必须使用“&”的实体引用“&amp;”来替代“&”。比如:

<company>John & Hans</company>

应该写成

<company>John & Hans</company>

XML有5个预定的实体引用,如表1-1所示。

表1-1 XML的预定实体引用

当XML解析器解析含有上述实体引用的XML文档时,会将这些实体引用转换成相应的字符。只有“<”和“&”字符在XML是非法的,另外几个是合法的。但是“>”容易被看成是一个标记的结束符号,而“'”和“"”这两个符号,又经常作为XML属性的开始符号和结束符号,所以为了避免XML语句的书写错误,建议将这3个符号也用其实体引用来表示。

1.5.5 XML的CDATA区

上一小节讲到了XML的实体引用,为了避免XML文档的解析错误,应该使用“<”来替代“<”,使用“&amp;”替代“&”。但是,假设用户需要在XML文档里写一段内容,里面包含了很多的“<”或者“&”,要将所有“<”或者“&”转换成实体引用会是件很烦琐的事情。这时,用户可以使用CDATA区(CDATA section)来定义这段内容。术语CDATA是指不应由XML解析器进行解析的文本数据。在CDATA区里,用户可以不必使用实体引用,因为XML解析器不会解析CDATA区中的内容。

CDATA区以“<![CDATA[”开始,以“]]>”结束,示例如下。

<mycode> This is a html page
<![CDATA[
        <html>
        <head>
        <title>woyouxian</title>
        </head>
        <body>
        I like woyouxian.net
        </body>
        </html>
        ]]>
</mycode>

注意:在CDATA区内,不能出现字符串“]]>”,也不允许嵌套CDATA区。标记CDATA部分结束的“]]>”不能包含空格或折行。如果在CDATA区的内容中需要包含字符串“]]>”,则需要用“]]>”来替换“]]>”。

在XHTML(HTML的XML版本)中经常会使用到CDATA。用户往往需要在XHTML页面中嵌入一些JavaScript代码,由于代码中包含大量“<”或“&”字符,为了避免错误,可以将脚本代码定义为CDATA。这时CDATA部分中的所有内容都会被解析器忽略。

例如下面的例子,用来测试客户是否试图从账户上转移比他实际拥有的更多的钱。

<script type=text/javascript>
//<![CDATA[
function validateTransfer(currentBalance, transferAmount)
{
 if (currentBalance > 0 && transferAmount < currentBalance)
  {
  return true;
  }
 alert("Insufficient funds to transfer the requested amount.");
 return false;
}
//]]>
</script>

因为文本已经包裹在CDATA区块中,所以JavaScript代码可以写成它的标准形式,否则,if语句中的“&&”和“<”标志就需要用XML实体引用替换,这时的代码如下。

if (currentBalance > 0 && transferAmount < currentBalance)

显然,以上代码对于人和脚本解析器来说都是较难理解的。

1.5.6 XML的注释

开发人员可以在XML文档里写注释,以帮助他人或者自己日后理解。XML的注释以“<!--”开始,以“-->”结束,示例如下。

<!-- This is a comment. -->

注意:在XML注释里面,除了结束符“-->”,不能有连续的两个连字符“--”。在结束XML注释时,“--->”也是非法的。XML的注释可以放在XML元素的内容里,但是不能放在标记里。

1.5.7 XML声明

XML文档应当以XML声明(declaration)开始,不过不是必须的。下面是一个简单的XML文档示例,第一行就是XML声明。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<site>
   woyouxian.net
</site>

如果一个XML文档含有声明,必须放在XML文档的第一行。

XML文档声明主要有以下3个参数。

·版本(version)

版本表示遵循的是W3C的XML 1.0的标准。

·字符编码(encoding)

字符编码表示该XML文档使用的字符编码。一些英文的XML文档,使用的是单字节的ISO-8859-1编码。不过对于中文文档来说,应该使用支持双字节的UTF-8或者Unicode编码。此外,如果是含有非英语字符的XML文档,也需要将XML文档保存存成UTF-8或者Unicode格式。

·独立(standalone)

如果XML的standalone属性值是no,表示它需要DTD。不需要DTD的XML文档,standalone属性值应该写yes。

1.5.8 格式正确的XML文档

综前所述,格式正确的XML文档必须符合以下规则。

·每个起始标签必须有结束标记呼应。

·XML文档只有一个根元素。

·XML元素必须正确嵌套。

·XML元素不能有相同名称的属性。

·XML属性值必须加引号。

·XML注释不能写在标记里。

·XML文档内容里不能出现“<”和“&”,如需要输入则必须用实体引用“<”来替代“<”,用“&amp;”替代“&”。

1.5.9 XML的命名空间

制定XML命名空间标准的初衷是为了解决XML文档中命名的冲突问题。由于XML中的元素名是预定义的,当两个不同的文档使用相同的元素名时,就会发生命名冲突。比如在一个文档中,元素<table>wood table</table>中的<table>表示桌子,而在另一个文档中,元素<table>namelist</table>中的<table>表示表格。如果同时处理这两个文档,就会发生名字冲突。为了解决这个问题,引进了命名空间(namespaces)这个概念。命名空间通过给标识名称加一个唯一资源标示符(URI)定位的方法,来区别这些名称相同的标识。XML命名空间将XML文档中的元素和属性名称与自定义和预定义的URI关联起来。为命名空间URI定义的前缀用来限定XML数据中的元素和属性的名称以实现此关联。命名空间可防止元素和属性名称冲突,并允许以不同的方式处理和验证同名的元素和属性。

命名空间使用xmlns:属性在元素中声明,此属性的值就是标识该命名空间的URI。命名空间声明的语法是xmlns:<name>=<"uri">,其中<name>是命名空间前缀的名称,<"uri">是说明命名空间URI的字符串。一旦声明后,前缀就可以用来限定XML文档中的元素和属性,并将它们与命名空间URI关联。因为命名空间的前缀会在整个文档中使用,所以它的长度应较短。

下例定义了两个BOOK元素。这两个BOOK元素不完全相同,每个元素分别与不同的命名空间关联。第一个BOOK元素由命名空间前缀mybook限定,而第二个BOOK元素由前缀bb限定。通过对每个BOOK元素使用命名空间声明,每个命名空间前缀都与不同的命名空间URI相关联。

<mybook:BOOK xmlns:mybook="http://www.contoso.com/books.dtd">
<bb:BOOK xmlns:bb="urn:blueyonderairlines">

若要表明元素是特定命名空间的一部分,应事先在其前面添加命名空间前缀,从而使其成为一个完全限定的元素名称。例如,如果文档中存在<Publisher>元素,并且已经为该元素声明了命名空间,则<Publisher>元素需要用冒号将命名空间别名添加到该元素前面。如果<Publisher>元素属于mybook命名空间,则将其声明为<mybook:Publisher>。因此,<Publisher>元素此时是被完全限定的。

若要声明并使用默认命名空间,应从元素的声明中省略别名和分号,如此,BOOK元素为:

<BOOK xmlns="http://www.contoso.com/books.dtd">

任何没有用命名空间前缀完全限定的元素均属于默认命名空间。当在XML文档中使用多个命名空间时,将其中一个命名空间定义为默认命名空间可以使文档更加简洁。只有来自非默认命名空间的元素需要完全限定。默认命名空间只适用于元素,不适用于属性。命名空间声明有范围。这意味着命名空间可以出现在文档中的任何位置,但它又像编程变量一样有作用范围,只在相应的范围内有效。命名空间有两种范围:默认范围和限定范围。

默认范围命名空间是在根元素中声明的命名空间,它应用于文档中所有未限定的元素。限定范围命名空间是在一个更具体的命名空间,在文档中某一位置重写时声明的。

尽管命名空间必须声明后才能使用,但这并不意味着它必须出现在XML文档的开头。例如,下面的示例显示一个在数据中间声明的限定范围命名空间,它是在<BOOK>元素级别声明的,该命名空间只应用于它的子代。

<Author>Joe Smith</Author>
<BOOK xmlns:book="http://www.contoso.com">
    <title>My Wonderful Day</title>
    <price>$3.95</price>
</BOOK>
<Publisher>
    <Name>MSPress</Name>
</Publisher>

在<BOOK>元素定义的命名空间不应用于<BOOK>元素以外的元素,如<Publisher>元素。当命名空间在文档中出现时,意味着所声明的命名空间从它的声明位置直到元素的结尾(命名空间的声明范围)都有效。如果已为<Publisher>元素声明了命名空间,则需要通过一个冒号将该命名空间添加到元素之前,以完全限定元素。假定<Publisher>元素属于mybook命名空间,该元素将声明为<mybook:Publisher>。

如下的两个XML文档示例说明了如何使用命名空间。

示例1:下面的XML文档在<table>元素中携带了信息。

<h:table xmlns:h="http://www.w3.org/TR/html4/">
 <h:tr>
  <h:td>Apples</h:td>
 <h:td>Bananas</h:td>
</h:tr>
</h:table>

示例2:下面的XML文档携带了家具table的信息。

<f:table xmlns:f="http://www.w3schools.com/furniture">
 <f:name>African Coffee Table</f:name>
 <f:width>80</f:width>
 <f:length>120</f:length>
</f:table>

上面两个例子中,除了使用前缀外,两个<table>元素都使用了xmlns属性,使元素和不同的命名空间关联到一起。

1.5.10 错误处理

在创建XML文档时,无论多么严格地遵守XML文档的格式规则,如匹配标记。为属性值添加单、双引号,使用实体引用字符等等,都难免会出现错误。当XML解析器发现XML文档中的错误时,它会根据文档错误的类型——一般性错误或致命性错误,而采取相应的处理方式。如果发生一般性错误,解析器能够恢复错误,并继续解析XML文档;如果发生致命性错误,则解析器停止处理文档并报告相关的错误信息。任何破坏XML文档格式规则的错误都被认为是致命性错误。现在大多数浏览器都提供错误报告工具,可以帮助用户查找文档中不易觉察的错误。这些错误报告工具通常是非常严格的,一旦发现文档中的错误,将立即终止对文档的处理,即使该错误没有被定义为致命的错误,这是种简单但却十分有效的处理方式。下面的示例演示了在浏览器中对XML文档中的错误的处理过程。有如下的XML文档:

<?xml version="1.0" encoding="utf-8"?>
<pangrams createdOn="2015-01-04T10:19:45'>
<!-- This file is designed to show
how errors are reported in a browser -->
 <pangram>The quick brown fox jumps over the lazy dog.</pangram>
 <pangram>Pack my box with five dozen liquor jugs.</pangram>
 <pangram>Glib jocks quiz nymph to vex dwarf.</pangram>
 <pangram>The five boxing wizards jump quickly.</Pangram>
 <pangram>What you write deserves better than a jiggling, shaky,
inexact & questionably fuzzy approximation of blur</pangram>
</pangrams>

这个文档包含了3个错误,是否能发现错误取决于对XML熟悉和一般校对的能力。当用浏览器(在此用Google Chrome浏览器)打开该文档时,浏览器报告了第1个错误,如图1-2所示。

图1-2 第1个错误

根据错误报告的定位和仔细地观察,可发现createdOn属性值的引号不匹配(一边用了双引号,一边用了单引号),修改该错误(将单引号改为双引号)并保存文档。用浏览器再次打开此文档,浏览器会报告第2个错误,如图1-3所示。

图1-3 第2个错误

这一次,浏览器报告不匹配的标记名称错误。第4个<pangram>元素以<pangram>为起始标签,但以</Pangram>为结束标记,开始和结束标记的大小写必须相匹配。在文档中修改后保存,然后重新在浏览器中打开文件,浏览器报告了最后一个错误,如图1-4所示。

图1-4 第3个错误

错误报告称文档中含有无名称的实体引用。这个可能会有点难以理解。这是因为,在XML中,符号“&”用于一个实体引用的开始,其后应该具有名称和分号。如果想在文档内容中使用“&”符号,就必须应用它的实体引用形式“&amp;”,否则浏览器就认为是一个错误。将示例文档中的“&”用“&amp;”替换并保存后,再用浏览器打开文档,得到如图1-5所示的结果。

图1-5 正确显示

XML解析器通常以连续的工作方式检查错误。它们从头开始读取文件,一旦遇到错误就停止对文件的进一步解析,并立即报告错误。这就解释了为何浏览器每一次只显示一个错误。待修复该错误后,解析器就可进一步解析文件,遇到错误再次停止对文件的解析,并立即报告错误。如此反复,直至所有错误均被修复,浏览器就可以正确地显示整个XML文件了。