XML教程

10章 DTDs中的属性声明

一些XML元素具有属性。属性包含应用程序使用的信息。属性仅在程序对元素进行读、写操作时,提供元素的额外信息(如ID号等),对于人类读、写元素来说是毫无意义的。在本章中学习各种属性类型和如何在DTD中声明属性。

本章内容如下:

* 什么是属性?

* 如何在DTD中声明属性

* 如何声明多个属性

* 如何指定属性的缺省值

* 属性类型

* 预定义属性

* 基于属性的棒球比赛统计数据的DTD

10.1 什么是属性?

在第3章曾经讨论过开始标记和空标记可包含由等号"="分割开的成对的属性名和属性值。例如:

<GREETING LANGUAGE= "English">

Hello XML!

<MOVIE SOURCE= "WavingHand.mov" />

</GREETING>

上述例子中,GREETING元素具有LANGUAGE属性,其属性值为ENGLISHMOVIE元素具有SOURCE属性,其属性值为WavingHand.movGREETING元素内容为Hello XML!。书写内容的语言对内容本身来说是一个有用的信息,可是语言不是内容的一部分。

与此相似,MOVIE元素内容为保存在WavingHand.mov文件中的二进制数据。尽管文件名告诉我们到何处可找到元素内容,但它本身不是元素内容。再次强调,属性包含有关元素内容信息,而不是元素内容本身。

元素可具有多个属性,例如:

<RECTANGLE WIDTH= "30" HEIGHT= "45" />

<SCRIPT LANGUAGE= "javascript" ENCODING= "8859_1" >...</SCRIPT>

上例中,SCRIPT元素属性LANGUAGE的值为javascriptSCRIPT元素属性ENCODING的值为8859_1RECTANGLE元素属性WIDTH的值为30RECT元素属性HEIGHT的值为45。这些属性值均为字符串数据,不是数字型数据。

结束标记不能带属性,下例视为非法:

<SCRIPT>...</SCRIPT LANGUAGE= "javascript" ENCODING= "8859_1" >

10.2 DTD中声明属性

与元素和实体相似,为保持文档的合法性,需要在文档的DTD中声明属性。<!ATTLIST>标记用于声明属性,其形式如下:

<!ATTLIST Element_name Attribute_name Type Default_value>

Element_name为拥有该属性的元素名。Attribute_name为属性名,Type为表10-1列出的10种有效属性类型的一种。最常用的属性类型为CDATA。最后,若未规定属性值,则属性值为Default_value

例如,研究下列元素:

<GREETING LANGUAGE= "Spanish">

Hola!

</GREETING>

DTD中,可按如下格式声明该元素:

<!ELEMENT GREETING (#PCDATA)>

<!ATTLIST GREETING LANGUAGE CDATA "English">

<!ELEMENT>标记简单地说明greeting元素包含可析字符数据,这里没什么新内容。<!ATTLIST>标记表明GREETING元素拥有LANGUAGE属性,其值为CDATA类型,本质上与元素内容的#PCDATA相同。若所看见的GREETING标记中没有LANGUAGE属性,则LANGUAGE属性值为缺省指定的English

10-1 属性类型

CDATA

字符数据不是标记的文本

Enumerated

可能取值的列表,可从中选出正确的值

ID

不能被文档中其他任何ID类型属性共享的数字,具有唯一性

IDREF

文档中元素的ID类型属性的值

IDREFS

由空格分开的若干个ID

ENTITY

DTD中声明的实体名

ENTITIES

DTD中声明的若干个实体的名字,彼此间由空格分开

NMTOKEN

XML名称

NOTATION

DTD中声明的注释名

NMTOKENS

由空格分开的多个XML名称

在各自的标记中分别声明各自的属性列表。属性所属元素的名字包含在<!ATTLIST>标记中,如上例中的属性声明仅用于GREETING元素。如果其余元素也具有LANGUAGE属性,就需要各自独立的<!ATTLIST> 声明。

对大部分声明而言,属性声明在文档中出现的顺序并无严格要求,可位于与其相连的元素声明之前或之后。实际上,甚至可以对同一属性进行多次声明。这时,第一个声明首先执行。

尽管非同寻常,甚至可以为并不存在的标记声明属性。在最初编辑DTD时,可以声明一些并不存在的属性,计划在以后再返回这里继续这些工作,为这些属性声明元素。

10.3 声明多个属性

元素通常具有多个属性。HTMLIMG元素可有HEIGHTWIDTHALTBORDERALIGN和其他几个属性 。实际上,大部分HTML标记都具有多个属性,XML标记也是如此。例如,很自然的RECTANGLE元素需要LENGTHWIDTH属性:

<RECTANGLE LENGTH= "70px" WIDTH="85px"/>

也可用几个属性声明来声明这些属性,一个属性声明对应一个属性。例如:

<!ELEMENT RECTANGLE EMPTY>

<!ATTLIST RECTANGLE LENGTH CDATA "0px">

<!ATTLIST RECTANGLE WIDTH CDATA "0px">

上例说明,RECTANGLE元素具有LENGTHWIDTH属性,它们的缺省值均为0px

可按如下方式,组合两个<!ATTLIST>标记为一个单一声明:

<!ATTLIST RECTANGLE LENGTH CDATA "0px"

WIDTH CDATA "0px">

该声明声明了LENGTHWIDTH属性,两个属性类型均为CDATA,缺省值为0px。若各属性的类型或缺省值不同,也可用这种语法结构进行声明。如下所示:

<!ATTLIST RECTANGLE LENGTH CDATA "15px"

WIDTH CDATA "34pt">

从个人角度来说,我不喜欢这种风格。看起来很混乱;且为易于辨认,过于依赖于额外的空格在其中的正确放置(尽管这些空格对标签的实际意义而言并不重要)。可是你一定会遇到其他人书写的、这种风格的DTD,所以必须掌握这种 书写方法。

10.4 指定属性的缺省值

若不采用明确指定一个缺省属性值(如0px)的方式,属性声明可以要求作者提供属性值,或者完全忽略该属性值,甚至总是使用缺省值。这三种类型分别由三个关键词#REQUIRED#IMPLIED#FIXED加以指定。

10.4.1 #REQUIRED

有时要选一个恰当的缺省属性值并不容易。例如,在为创建一个用于内部网的DTD时,可能要求所有的文档都至少有一个空的<AUTHOR1>标记;这些标记通常情况下并不显示,但可用来识别创建文档的作者。标记中拥有NAMEEMAILEXTENSION属性,以便与作者联系。例如:

<AUTHOR NAME="Elliotte Rusty Harold"

EMAIL= elharo@metalab.unc.edu EXTENSION= "3459"/>

假设要强制要求在内部网上张贴文档人的表明身份,就不采取为这些属性提供缺省值的方法。然而XML无法阻止任何人把作者身份定为"Luke Skywalker"(洛克天行者),但至少可通过使用#REQUIRED的缺省值方式,要求指定作者身份为某个人。例如:

<!ELEMENT AUTHOR EMPTY>

<!ATTLIST AUTHOR NAME CDATA #REQUIRED>

<!ATTLIST AUTHOR EMAIL CDATA #REQUIRED>

<!ATTLIST AUTHOR EXTENSION CDATA #REQUIRED>

如果语法分析器遇到一个<AUTHOR/>标记,该标记没有包含这些属性中的一个或几个时,将返回一个错误。

也可使用#REQUIRED强迫作者提交IMG元素的WIDTHHEIGHTALT属性。例如:

<!ELEMENT IMG EMPTY>

<!ATTLIST IMG ALT CDATA #REQUIRED>

<!ATTLIST IMG WIDTH CDATA #REQUIRED>

<!ATTLIST IMG HEIGHT CDATA #REQUIRED>

任何试图忽略这些属性的行为(这样的Web页面太多了)都将产生一个不合法文档。XML处理器将注意到这种错误,并且将通知缺少这些属性的作者。

10.4.2 #IMPLIED

有时可能找到一个好的属性缺省值,但也不想要求文档作者包含这属性值。例如,假设在内部网上张贴文档的一些人拥有电子邮件地址,但它们没有电话分机号;为此,不想要求它们在<AUTHOR/>标记中包含EXTENSION(分机号)属性部分。例如:

<AUTHOR NAME="Elliotte Rusty Harold"

EMAIL="elharo@metalab.unc.edu "/>

如果依然不想为EXTENSION(分机号)提供缺省属性值,但是想提供作者引入这种类似属性的能力。在这种情况下,就可使用#IMPLIED的缺省值。如下所示:

<!ELEMENT AUTHOR EMPTY>

<!ATTLIST AUTHOR NAME CDATA #REQUIRED>

<!ATTLIST AUTHOR EMAIL CDATA #REQUIRED>

<!ATTLIST AUTHOR EXTENSION CDATA #IMPLIED>

如果XML处理器遇到没有EXTENSION属性的<AUTHOR/>标记,就不向XML应用程序提供有用的属性值。应用程序按收到的通知进行相应的选择。例如,应用程序把元素送入SQL数据库中,属性映射为字段,应用程序或许在数据库相应字段中插入空的数据。

10.4.3 #FIXED

最后,可能想提供一个不允许作者更改的属性缺省值。例如,希望为在内部网上张贴文档的人员的AUTHOR元素指定一个同等的COMPANY标识属性。方法如下:

<AUTHOR NAME= "Elliotte Rusty Harold" COMPANY="TIC"

EMAIL= "elharo@metalab.unc.edu" EXTENSION="3459"/>

可通过指定缺省值为#FIXED,其后跟随实际的缺省值,来要求所有的人员对COMPANY属性使用该缺省值。例如:

<!ELEMENT AUTHOR EMPTY>

<!ATTLIST AUTHOR NAME CDATA #REQUIRED>

<!ATTLIST AUTHOR EMAIL CDATA #REQUIRED>

<!ATTLIST AUTHOR EXTENSION CDATA #IMPLIED>

<!ATTLIST AUTHOR COMPANY CDATA #FIXED "TIC">

文档作者不需要在它们各自的标记中真正地引用固定的属性。如果它们没有包括固定属性,则使用缺省值;如果包括了固定的属性,无论如何它们使用的属性值必须一致,否则语法分析器将返回一个错误信号。

10.5 属性类型

前面的所有例子都具有CDATA类型的属性。CDATA是最通用的类型,但此外还允许使用其他九种属性类型。所有十种类型如下:

* CDATA

* Enumerated(枚举)

* NMTOKEN

* NMTOKENS

* ID

* IDREF

* IDREFS

* ENTITY

* ENTITIES

* NOTATION

上述属性类型中的九种在类型字段中的值为常数,而Enumerated是一种特殊的类型,表示属性值必须为一可能取值列表中的一个。下面分别对各类型进行深入的研究。

10.5.1 CDATA属性类型

最通用的属性类型CDATA,表明属性值为不包括小于号(<)和引号(")的任意文本字符串。可通过普通的实体引用(&lt;和&quot;)的方式或由字符引用Unicode值的方式插入小于号和引号字符。原始的和号(&&#0;&#0;不是字符或实际引用开始的和号,必须使用换码符&amp;

实际上,即使属性值中不可避免的要包含双引号(")的情况下,也不可直接使用;替代的方法是用单引号把双引号括起来。如下例所示:

<RECTANGLE LENGTH= 7" WIDTH= 8.5" >

如果属性值中包含单引号和双引号,而且并不用作界定属性值的用途;它们必须替换为实体引用&apos(单引号)&quot(双引号)。例如:

<RECTANGLE LENGTH= 8& pos;7" WIDTH="10 6&quot;"/>

10.5.2 Enumerated属性类型

Enumerated类型不是XML的关键词,而是由竖线分隔的可能的属性值列表。任一值均需为有效的XML名称。文档作者可选取列表中的一个成员为属性的值,缺省值必须为列表中的一个值。

例如,假设希望某个元素具有可见和不可见属性。希望该元素具有一个VISIBLE属性,其属性值为TRUEFALSE。如果该元素为简单的P元素,那么<!attlist>声明可如下所示:

<!ATTLIST P VISIBLE (TRUE | FALSE) "TRUE">

上述声明表示P元素的VISIBLE属性可有可无,若拥有VISIBLE属性,则属性值必须为TRUEFALSE;如果没有VISIBLE属性,则假定该值为TRUE。例如:

<P VISIBLE= "FALSE">You can t see me! Nyah! Nyah!</P>

<P VISIBLE= "TRUE">You can see me.</P>

<P>You can see me too.</P>

就其自身而言,这声明并不是一个提供隐藏文本能力的魔术般的咒语。这种能力依然依靠应用程序去理解不应该显示不可见元素。为决定元素的显示或隐藏,可以通过对元素应用VISIBLE属性的样式单规则来进行设置。例如:

<xsl:template match= "P[@VISIBLE= FALSE ]" >

</xsl:template>

<xsl:template match= "P[@VISIBLE= TRUE ]" >

<xsl: apply-templates/>

</xsl:template>

10.5.3 NMTOKEN属性类型

NMTOKEN属性类型限定属性值为有效的XML名称。如第6章所述,XML名称必须以字母或下划线开头。名字中后面的字符可以为字母、数字、下划线、连字符和句号。但不可包括空格(下划线通常作为空格的替代品)。从技术上说,名字中可包含冒号(:)但不应该使用冒号,因为冒号被保留为与命名域(namespace)一起使用。

当使用编程语言处理XML数据时,证明了NMTOKEN的价值。这并不是一种偶然,除了允许使用冒号以外,上述规则与JAVAJavaScript和其他程序语言标识符规则一致。例如,可在元素中使用NMTOKEN属性访问特别的JAVA类。那么就应用JAVAAPI映射把数据传送到专有类的特有方法中。

当需要从大量名字中选取不是XML的规定部分但与XML命名要求相符的名字时,就能体现NMTOKEN的用途。这些要求的最重要部分就是对空格的限制。例如,NMTOKEN可以用于下述属性,其值必须映射为8.3DOS文件名,另一方面该属性也能用于UNIXMacintoshWindows NT文件名,而这些文件名中通常包含空格。

例如,假如要求<ADDRESS/>标记中的州(state)属性为两个字母缩写;不能用DTD强制这些特性的执行,但可应用如下<!ATTLIST>声明防止人们输入类似“New York""Puerto Rico"的值:

<!ATTLIST ADDRESS STATE NMTOKEN #REQUIRED>

无论何种情况,像“California""Nevada"和其他一个单词的州名依然为合法值。当然,可以利用具有几十个两个字母的代码的枚举列表的简单方法;但是这种方法将导致巨大的工作量,比人们想象的大得多。举个例子,想一想,如果用两个字母代码代表美国50个州、所有的领土和属地、所有的国外的军事基地和加拿大所有的省份会是一个什么样的情况?另一方面,如果曾经在DTD文件中的参数实体引用定义了这样的列表,就可重复多次使用这个文件。

10.5.4 NMTOKENS属性类型

NMTOKENS属性类型几乎就是NMTOKEN的复数形式。这种类型的属性可以使如下情况合法--属性由若干XML名称字组成,彼此间由空格分隔。通常可为使用NMTOKEN属性相同的理由而使用NMTOKENS属性,但仅仅在需要多个名字的时候。

例如,如果state元素的属性值需要多个两个字母州代码时,就可应用下例所示的方法:

<!ATTLIST ADDRESS STATES NMTOKENS #REQUIRED>

然后,就可编写如下所示的标记:

<ADDRESS STATES="MI NY LA CA">

不幸的是,如果应用这种技术,就不能再排除类似“New York"这样的州名,因为州名中每一独立的部分都为一个合格的NMTOKEN。如下所示:

<ADDRESS STATES="MI New York LA CA">

10.5.5 ID属性类型

一个ID类型的属性标识文档中唯一的元素,编辑工具和其余应用程序通常使用ID列举文档中的元素,并不关心元素的实际意义和各元素彼此之间的关系。

一个ID类型属性值必须为有效的XML名称,该名称以字母开头,由字母数字混排的字符和下划线组成,并且其中不带空格。一个特定的名字不能用作多个标记的ID属性。若在一个文档中两次使用同一ID将导致语法分析器返回一个错误信息;另外,一个元素不能具有超过一个的ID类型的属性。

一般来说,ID属性的存在只是为了处理数据的程序方便。在许多情况下,除了ID属性值外,多个元素可能会是一样的,如果以可以预见的方式来选取ID的话,程序就可以列举出文档中所有不同类型的元素或同一类型的不同元素。

ID类型属性与#FIXED类型的属性不兼容。ID类型属性不能同时具有#FIXED类型的属性,因为#FIXED类型的属性仅能拥有一个单一的值,而每个ID类型属性都具有不同的值。大部分ID属性使用#REQUIRED值。如清单10-1例所示:

清单10-1required ID属性类型

<?xml version="1.0" standa lone="yes"?>

<!DOCTYPE DOCUMENT [

<!ELEMENT DOCUMENT (P*)>

<!ELEMENT P (#PCDATA)>

<!ATTLIST P PNUMBER ID #REQUIRED>

]>

<DOCUMENT>

<P PNUMBER="p1">The quick brown fox</P>

<P PNUMBER="p2" >The quick brown fox</P>

</DOCUMENT>

10.5.6 IDREF属性类型

IDREF类型的属性值为文档中另一个元素的ID。例如,清单10-2表明IDREFID属性用于子元素和父元素之间的连结。

清单10-2family.xml

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

<!DOCTYPE DOCUMENT [

<!ELEMENT DOCUMENT (PERSON*)>

<!ELEMENT PERSON (#PCDATA)>

<!ATTLIST PERSON PNUMBER ID #REQUIRED>

<!ATTLIST PERSON FATHER IDREF #IMPLIED>

<!ATTLIST PERSON MOTHER IDREF #IMPLIED>

]>

<DOCUMENT>

<PERSON PNUMBER= "a1" >Susan</PERSON>

<PERSON PNUMBER= "a2" >Jack</PERSON>

<PERSON PNUMBER= "a3" MOTHER= "a1" FATHER= "a2" >Chelsea</PERSON>

<PERSON PNUMBER= "a4" MOTHER= "a1" FATHER= "a2" >David</PERSON>

</DOCUMENT>

当在文档结构树上并不冲突的两个元素之间需要建立连结时,通常使用这种并不普遍但很重要的类型。在清单10-2中,每个子元素都有FATHERMOTHER属性给出,这两个属性包含的是对应的ID属性。

在清单10-2中,无法简洁地使用IDREF建立父元素到子元素之间的链接,这是因为每个父元素都具有不确定的子元素数目。解决的方法就是,可以把所有同一父元素的子元素组成FAMILY元素,然后链接到FAMILY上。即使使用这种方法,当面对半同属(共享唯一一个父元素)的元素时,也不好用。简而言之,IDREF适用于多对一的关系,不适合一对多的关系。

10.5.7 ENTITY属性类型

ENTITY类型的属性提供把外部二进制数据和外部不可析实体链接到文档中的能力。ENTITY属性值为DTD中声明的不可析通用实体名,该实体名链接到外部实际数据。

经典的ENTITY属性的例子就是图像。图像由另一URL处可用的二进制数据组成。假如XML浏览器支持ENTITY类型属性,在DTD中按如下方式声明,就可在XML文档中包括一幅枷瘢?/p>

<!ELEMENT IMAGE EMPTY>

<!ATTLIST IMAGE SOURCE ENTITY #REQUIRED>

<!ENTITY LOGO SYSTEM "logo.gif">

然后在期望图像出现在文档中的位置处,就可插入如下的IMAG标记:

<IMAGE SOURCE="LOGO"/>

所有XML浏览器自动识别的过程并不是在变魔术,这仅仅是一种简单的技术,浏览器和其余应用程序可能采用也可能不采用这种技术在文档中嵌入非XML数据。

这种技术在第11 "嵌入非XML数据" 中有更深入的探讨。

10.5.8 ENTITIES 属性类型

ENTITIES属性类型几乎就是ENTITY的复数形式。若干由空格分隔的不可析实体名组成ENTITIES类型属性的值。每一实体名指向一个外部非XML数据资源。这种类型属性的用途之一为:使不同图片之间的切换变得光滑平顺,如下例所示:

<!ELEMENT SLIDESHOW EMPTY>

<!ATTLIST SLIDESHOW SOURCES ENTITIES #REQUIRED>

<!ENTITY PIC1 SYSTEM "cat.gif">

<!ENTITY PIC2 SYSTEM "dog.gif">

<!ENTITY PIC3 SYSTEM "cow.gif">

然后在文档中希望显示图片的位置上插入如下标记:

<SLIDESHOW SOURCES="PIC1 PIC2 PIC3">

再一次声明,这不是所有(或任意)XML浏览器可以自动识别的通用格式;仅仅是某些浏览器和其余的应用程序可能采用也可能不采用的在文档中嵌入非XML数据的方法而已。

10.5.9 NOTATION属性类型

NOTATION属性类型指定属性值为DTD中声明的记号名。这一属性的缺省值也必须为DTD中声明的记号名。在下一章中介绍记号的详细内容。简单地说,记号可标识非XML数据的格式;例如为不可析实体指定一帮助程序。

11嵌入非XML格式数据"讲述了这方面的内容。

例如,SOUND元素的PLAYER属性具有NOTATION类型和缺省值MP,从而标识非XML数据的格式,记号MP表示一个特殊类型的声音文件:

<!ATTLIST SOUND PLAYER NOTATION (MP) #REQUIRED>

<!NOTATION MP SYSTEM "mplay32.exe">

也可提供不同记号的选择。这样做的用法之一是为不同的平台指定不同的帮助应用程序。浏览器可从中选取一可用的值。这种情况下,NOTATION关键词后紧跟一对圆括号,括号内包含由竖直线分隔的、许可的记号名列表。例如:

<!NOTATION MP SYSTEM "mplay32.exe">

<!NOTATION ST SYSTEM "soundtool">

<!NOTATION SM SYSTEM "Sound Machine">

<!ATTLIST SOUND PLAYER NOTATION (MP | SM | ST) #REQUIRED>

这表明SOUND元素的PLAYER属性值可设置为MPSTSM。下一章对此再作进一步的研究。

乍看上去,这种处理方法与其余列表属性(如ENTITIESNMTOKENS)处理方法好像不一致;但其实这两种方法截然不同。ENTITIESNMTOKENS在文档的实际元素中具有一个属性列表,但在DTD的属性声明中仅有一个值。可是文档中实际元素的NOTATION属性值仅有一个。可取值的列表位于DTD

的属性声明中。

10.6 预定义属性

在某种程度上说,可以在XML中预定义两个属性。必须在DTD中为将应用的每一元素声明这两个属性,但是仅仅可以为原定目标而使用这些声明的属性。通过在属性名前加xml:来标识这类属性。

这两类属性分别为xmlspacexmllangXmlspace属性描述如何对待元素中的空格;xmllang属性描述书写元素的语言(以及可选的方言和国别)。

10.6.1 xmlspace

HTML中,空格并不重要。尽管一个空格和没有空格之间的差别是非常重要的,但是一个空格和两个空格、一个空格和一个回车符、一个空格三个回车符和12个制表符之间的差别并不重要。对某些文本来说,空格非常重要,如计算机源代码、某些大型机数据库报告或者e.e.cumming的诗文,可使用PRE元素指定等宽的字体和保留空格。

不过,XML 的缺省方式为保留空格。XML处理器毫不改变地传送全部空格字符给应用程序。应用程序通常忽略额外的空格。可是XML处理器可通知应用程序某些特定的元素包含需保留的、意义重大的空格。作者可使用xmlspace属性,为应用程序说明这些元素。

如果元素包含重要的空格,DTD将为xmlspace属性提供一个<!ATTLIST>标记。这个属性具有枚举类型,其值为defaultpreserve。如清单10-3所示。

清单10-3:用XML编码的具有重要空格的Java源代码

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

<!DOCTYPE PROGRAM [

<!ELEMENT PROGRAM (#PCDATA)>

<!ATTLIST PROGRAM xml:space (default|preserve) preserve >

]>

<PROGRAM xml:space= "preserve">public class AsciiTable {

public static void m in (String[] args) {

for (int i = 0; i &lt; 128; i++) {

System.out.println(i + " "+ (char) i);

}

}

}

</PROGRAM>

不管xmlspace值为defaultpreserve,全部空格都传送给应用程序。可是,若值为default,应用程序按正常方式处理额外的空格;若值为preserve,则按有特殊意义的态度对待这些额外的空格。

空格的特殊意义在某种角度来说依赖于数据的最终目标。例如,Java源代码中的额外空格仅与源代码的编辑器有关,但与编译器无关。

已定义xmlspace属性元素的子元素,除非子元素定义了与原值相矛盾的xmlspace属性,否则将表现出与其父元素相似的行为(保留或不保留空格)。

10.6.2 xml:lang

xml:lang属性标识书写元素内容的语言。属性值可以为CDATA、NMTOKEN或者枚举列表类型。理想的情况下,每一个属性值均为原始ISO-639标准定义的两个字母语言代码。代码列表可在下述Web地址处找到:

http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt。

例如,研究下面两个例子,其中的句子取自Petronius Satiricon,分别用拉丁文(Latin)和英文(English)书写。一个SENTENCE标记包装了这两个句子,但是第一个句子标记使用的xml:lang属性值为"Latin",而第二个句子中其值为"English"。

拉丁文(Latin):

<SENTENCE xml:lang="la">

Veniebamus in forum deficiente now die, in quo notavimus frequentiam rerum venalium, non quidem pretiosarum sed tamen quarum fidem male mbulantem obscuritas temporis facillime tegeret.

</SENTENCE>

英文(English):

<SENTENCE xml:lang= "en">

We have come to the marketplace now when the day is failing, where we have seen many things for s le, not for the valuable goods but rather that the darkness of the time may most easily conceal their shoddiness.

</SENTENCE>

说英语的读者很容易区分哪个为原稿,哪个为译 本;计算机可根据xml:lang属性提供的线索进行区分。这个差别可确定拼写检查器决定如何检查特定的元素和决定使用哪个词典。搜索引擎可以检查这些语言属性,然后确定如何检索页面内容和返回基于用户优先选择的匹配结果。

语言种类过多,而代码不足

XML在使用语言代码问题上依然有点落后。原始的ISO-639标准语言代码由两个不分大小写的ASCII字母字符构成;这标准允许的代码数目不能超过676种不同的代码(26*26),可是现在地球上使用的语言远远不止676种(即使不包括类似Etruscan这种已经消亡的语言)。实际上,合理的代码还少于676种,因为语言的缩写必须与语言的名字具有一定的关系。

ISO-639标准的第二部分使用三个字母的代码,可处理地球上使用的所有语言。可是XML标准规范要求使用两个字母代码。

语言(language)属性应用在元素及其所有的子元素上,除非其中某个子元素声明了不同的语言。前面的SENTENCE元素可按如下方式进行声明:

<!ELEMENT SENTENCE (#PCDATA)>

<!ATTLIST SENTENCE xml:lang NMTOKEN "en">

假如没有适当的ISO代码可利用,也可利用在IANA注册的代码之一,虽然现在IANA仅增加了四种附加代码(如表10-2所示)。最新的列表可在下面的地址处找到:

http://www.isi.edu/iana/assignments/language/tags

10-2 IANA语言代码

代 码

语 言

no-bok

Norwegian "Book language"(挪威的书面语言)

no-nyn

Norwegian "New Norwegian"(新挪威语言)

i-navajo

Navajo(印第安语)

i-mingo

Mingo

例如:

<P xml:lang="no-nyn">

如果需要使用的语言代码(或许是Klingon)既不包含在ISO代码中也不包含在IANA代码中,就可定义新的语言代码。这些"x-codes"必须以字符串x-或者X-开始,标识为用户自定义、私人使用的代码。例如:

<P xml:lang="x-klingon">

xml:lang属性值可包含附加的子代码部分,用连字符"-"把子代码与主要的语言代码区分开。最常见的情况是第一个子代码为ISO-3166规定的两个字母的国家代码。最新的国家代码列表可在下面的地址中找到:

http://www.isi.edu/in-notes/iana/assignment/country-codes

例如:

<P xml:lang="en-US">Put the body in the trunk of the car.</P>

<P xml:lang="en-GB">Put the body in the boot of the car.</P>

如果第一个子代码不是ISO规定的两个字母国家代码,就必须是设置IANA注册的语言的字符集子代码,如csDECMCS、roman8、mac、cp037或ebcdic-cp-ca。当前使用的代码列表可以在下述地址中找到:

ftp://ftp.isi.edu/in-notes/iana/assignments/character-sets

示例如下:

<P xml:lang= "en-mac">

最终的结果可能是第一个子代码,另一个以x-或X-开头的x-code。例如:

<P xml:lang= "en-x-tic">

根据惯例,语言代码写为小写格式,国家代码为大写格式。可是这仅仅是一个惯例。这是XML少数对大小写敏感部分中的一个,因为它继承了ISO对大小写不敏感的特性。

DTD中使用的其他所有属性相同,为保持文档的合法性。必须明确地声明xml:lang属性,必需直接用于它所施加的元素(对于指定xml:lang属性的元素的子元素是间接施用)。

或许不希望xml:lang取任意值。其允许值也应为合法的XML名称字,所以通常赋予属性NMTOKEN类型。这种类型可限制属性值为合法的XML名称字。例如:

<!ELEMENT P (#PCDATA)>

<!ATTLIST P xml:lang NMTOKEN #IMPLIED "en">

另外,如果仅允许很少的几个语言或方言,就可以应用枚举类型。例如,下述DTD说明P元素可以为English或Latin。

<!ELEMENT P (#PCDATA)>

<!ATTLIST P xml:lang (en | la) "en">

也可以使用CDATA类型,但是没有什么理由要这样做。使用NMTOKEN或者枚举类型有助于发现某些潜在的错误。

10.7 基于属性的棒球统计数据的DTD

在第5章中,为1998 Major League Season创建了一个结构完整的XML文档,文档中应用属性来保存赛季(SEASON)的年份(YEAR)、联盟名(NAME)、分部名、球队名、球队比赛的城市名(CITY)和每个球员的详细统计资料。下面的清单10-4是清单5-1的一个缩略版本,完整的XML文档中包括两个联盟、六个分部、六个球队和两个球员的数据,以便于去理解元素的位置所在和元素具有什么属性。

清单10-4:完整的XML文档

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

<SEASON YEAR= "1998" >

<LEAGUE NAME= "Nation l League" >

<DIVISION NAME="East">

<TEAM CITY=" Atlant NAME=" Braves">

<PLAYER GIVEN_NAME=" Marty SURNAME=" Malloy"

POSITION=" Second Base" GAMES=" 11" GAMES_STARTED=" 8"

AT_BATS=" 28" RUNS=" 3" HITS=" 5" DOUBLES=" 1"

TRIPLES=" 0" HOME_RUNS=" 1" RBI=" 1" STEALS=" 0"

CAUGHT_STEALING=" 0" SACRIFICE_HITS=" 0 "

SACRIFICE_FLIES=" 0" ERRORS=" 0" WALKS=" 2"

STRUCK_OUT=" 2" HIT_BY_PITCH=" 0" />

<PLAYER GIVEN_NAME=" Tom" SURNAME=" Glavine"

POSITION=" Starting Pitcher" GAMES=" 33"

GAMES_STARTED=" 33" WINS=" 20" LOSSES=" 6" SAVES=" 0"

COMPLETE_GAMES=" 4 SHUTOUTS=" 3 ERA=" 2.47"

INNINGS=" 229.1" HOME_RUNS_AGAINST=" 13"

RUNS_AGAINST=" 67" EARNED_RUNS=" 63" HIT_BATTER=" 2"

WILD_PITCHES=" 3" BALK=" 0" WALKED_BATTER=" 74"

STRUCK_OUT_BATTER=" 157" />

</TEAM>

</DIVISION>

<DIVISION NAME=" Central" >

<TEAM CITY=" Chicago NAME=" Cubs" >

</TEAM>

</DIVISION>

<DIVISION NAME=" West >

<TEAM CITY=" San Francisco" NAME=" Giants" >

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE NAME=" American League" >

<DIVISION NAME=" East" >

<TEAM CITY=" New York NAME=" Yankees" >

</TEAM>

</DIVISION>

<DIVISION NAME=" Central" >

<TEAM CITY=" Minnesota" NAME=" Twins" >

</TEAM>

</DIVISION>

<DIVISION NAME=" West" >

<TEAM CITY=" Oakland" NAME=" Athletics" >

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

为了此文档的合法性和结构的完整性,就需要提供DTD。该DTD中必须声明清单10-4中使用的所有元素和属性。元素的声明与旧版类似,只是由于大部分信息被转移到属性中的缘故,显得更为简短:

<!ELEMENT SEASON (LEAGUE, LEAGUE)>

<!ELEMENT LEAGUE (DIVISION, DIVISION, DIVISION)>

<!ELEMENT DIVISION (TEAM+)>

<!ELEMENT TEAM (PLAYER*)>

<!ELEMENT PLAYER EMPTY>

10.7.1 DTD中声明SEASON的属性

SEASON拥有单一的属性YEAR。尽管有些语义上的限定,规定什么是,而什么不是一个年份(1998是年份,March 31就不是);DTD不进行这种限定。因此,最好的方法就是声明YEAR属性具有最通用的属性类型CDATA;另外,希望每个赛季都具有年份值(year),就可使YEAR属性为REQUIRED类型。

<!ATTLIST SEASON YEAR CDATA #REQUIRED>

尽管确实无法限制作者输入YEAR属性文本的格式,但是至少可以提供一个记号,表明可以接受何种格式的文本。例如,规定年份(year)需要四位数的格式就不失为一个好主意。

<!ATTLIST SEASON YEAR CDATA #REQUIRED> <!--e.g. 1998 -->

<!--DO NOT USE TWO DIGIT YEARS like 98, 99, 00!! -->

10.7.2 DTD中声明DIVISIONLEAGUE属性

接下来考虑DIVISIONLEAGUE元素。它们都具有单一的NAME属性。此外自然具有CDATAREQUIRED属性类型。因为两个不同的元素具有两个相互独立的NAME属性,所以需要两个独立的<!ATTLIST>声明。

<!ATTLIST LEAGUE NAME CDATA #REQUIRED>

<!ATTLIST DIVISION NAME CDATA #REQUIRED>

在这里添加注释可以有助于表明作者期望的格式;例如是否包括单词LeagueDivision作为名字的一部分。

<!ATTLIST LEAGUE NAME CDATA #REQUIRED>

<!--e.g. "Nation l League" -->

<!ATTLIST DIVISION NAME CDATA #REQUIRED>

<!--e.g. "East" -->

10.7.3 DTD中声明TEAM属性

TEAM元素具有NAMECITY属性,两个属性均为CDATAREQUIRED类型。

<!ATTLIST TEAM NAME CDATA #REQUIRED>

<!ATTLIST TEAM CITY CDATA #REQUIRED>

添加注释有助于建立某些并不明显的东西,例如,在一些情况下,CITY属性实际上可以是某个州名:

<!ATTLIST TEAM CITY CDATA #REQUIRED>

<!--e.g. "San Diego" as in "San Diego Padres"

or "Texas" as in "Texas Rangers" -->

换一种方式,可以在一个简单的<!ATTLIST>中声明这两个属性。

<!ATTLIST TEAM NAME CDATA #REQUIRED

CITY CDATA #REQUIRED>

10.7.4 DTD中声明PLAYER的属性

PLAYER元素可以说是包含了大部分属性类型。首先是GIVEN_NAMESURNAME两个属性,均为简单的CDATAREQUIRED类型。

<!ATTLIST PLAYER GIVEN_NAME CDATA #REQUIRED>

<!ATTLIST PLAYER SURNAME CDATA #REQUIRED>

下一个PLAYER属性是POSITION。因为棒球场上球员的位置是一个相当基本的数据,在这里可使用枚举属性类型。可是"First Base""Second Base""Third Base""Starting Pitcher""Relief Pitcher"都包含有空格,因此它们都不是有效的XML名称字。因此仅能使用CDATA属性类型。实际情况下没有任何理由为POSITION选定缺省值,所以该属性应为REQUIRED类型。

<!ATTLIST PLAYER POSITION CDATA #REQUIRED>

接下来的是各种各样的统计数据:GAMESGAMES_STARTEDAT_BATRUNSHITSWINSLOSSESSAVESSHUTOUTS等等。上述每一个属性本应为数字类型,但是因为XML不提供数字类型机制,所以把它们简单地声明为CDATA类型。因为不是每一个球员都具有如上的每一个统计数据值,所以把上述各属性声明为IMPLIED类型,而不是REQUIRED类型。

<!ATTLIST PLAYER GAMES CDATA #IMPLIED>

<!ATTLIST PLAYER GAMES_STARTED CDATA #IMPLIED>

<!-Batting Statistics ->

<!ATTLIST PLAYER AT_BATS CDATA #IMPLIED>

<!ATTLIST PLAYER RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER HITS CDATA #IMPLIED>

<!ATTLIST PLAYER DOUBLES CDATA #IMPLIED>

<!ATTLIST PLAYER TRIPLES CDATA #IMPLIED>

<!ATTLIST PLAYER HOME_RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER RBI CDATA #IMPLIED>

<!ATTLIST PLAYER STEALS CDATA #IMPLIED>

<!ATTLIST PLAYER CAUGHT_STEALING CDATA #IMPLIED>

<!ATTLIST PLAYER SACRIFICE_HITS CDATA #IMPLIED>

<!ATTLIST PLAYER SACRIFICE_FLIES CDATA #IMPLIED>

<!ATTLIST PLAYER ERRORS CDATA #IMPLIED>

<!ATTLIST PLAYER WALKS CDATA #IMPLIED>

<!ATTLIST PLAYER STRUCK_OUT CDATA #IMPLIED>

<!ATTLIST PLAYER HIT_BY_PITCH CDATA #IMPLIED>

<!-Pitching Statistics ->

<!ATTLIST PLAYER WINS CDATA #IMPLIED>

<!ATTLIST PLAYER LOSSES CDATA #IMPLIED>

<!ATTLIST PLAYER SAVES CDATA #IMPLIED>

<!ATTLIST PLAYER COMPLETE_GAMES CDATA #IMPLIED>

<!ATTLIST PLAYER SHUTOUTS CDATA #IMPLIED>

<!ATTLIST PLAYER ERA CDATA #IMPLIED>

<!ATTLIST PLAYER INNINGS CDATA #IMPLIED>

<!ATTLIST PLAYER HOME_RUNS_AGAINST CDATA #IMPLIED>

<!ATTLIST PLAYER RUNS_AGAINST CDATA #IMPLIED>

<!ATTLIST PLAYER EARNED_RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER HIT_BATTER CDATA #IMPLIED>

<!ATTLIST PLAYER WILD_PITCHES CDATA #IMPLIED>

<!ATTLIST PLAYER BALK CDATA #IMPLIED>

<!ATTLIST PLAYER WALKED_BATTER CDATA #IMPLIED>

<!ATTLIST PLAYER STRUCK_OUT_BATTER CDATA #IMPLIED>

只要你愿意,可以把PLAYER可能使用的所有属性组合为一个外形庞大的<!ATTLIST>声明:

<!ATTLIST PLAYER

GIVEN_NAME CDATA #REQUIRED

SURNAME CDATA #REQUIRED

POSITION CDATA #REQUIRED

GAMES CDATA #IMPLIED

GAMES_STARTED CDATA #IMPLIED

AT_BATS CDATA #IMPLIED

RUNS CDATA #IMPLIED

HITS CDATA #IMPLIED

DOUBLES CDATA #IMPLIED

TRIPLES CDATA #IMPLIED

HOME_RUNS CDATA #IMPLIED

RBI CDATA #IMPLIED

STEALS CDATA #IMPLIED

CAUGHT_STEALING CDATA #IMPLIED

SACRIFICE_HITS CDATA #IMPLIED

SACRIFICE_FLIES CDATA #IMPLIED

ERRORS CDATA #IMPLIED

WALKS CDATA #IMPLIED

STRUCK_OUT CDATA #IMPLIED

HIT_BY_PITCH CDATA #IMPLIED

WINS CDATA #IMPLIED

LOSSES CDATA #IMPLIED

SAVES CDATA #IMPLIED

COMPLETE_GAMES CDATA #IMPLIED

SHUTOUTS CDATA #IMPLIED

ERA CDATA #IMPLIED

INNINGS CDATA #IMPLIED

HOME_RUNS_AGAINST CDATA #IMPLIED

RUNS_AGAINST CDATA #IMPLIED

EARNED_RUNS CDATA #IMPLIED

HIT_BATTER CDATA #IMPLIED

WILD_PITCHES CDATA #IMPLIED

BALK CDATA #IMPLIED

WALKED_BATTER CDATA #IMPLIED

STRUCK_OUT_BATTER CDATA #IMPLIED>

这种方法不会有什么好处,应用这种方法就无法在个别属性后加上简单的注释。

10.7.5 棒球比赛统计数据示例的完整DTD

清单10-5显示的是棒球比赛统计数据基本属性的完整DTD

清单10-5:利用属性包含大部分信息的棒球比赛统计数据的完整DTD

<!ELEMENT SEASON (LEAGUE, LEAGUE)>

<!ELEMENT LEAGUE (DIVISION, DIVISION, DIVISION)>

<!ELEMENT DIVISION (TEAM+)>

<!ELEMENT TEAM (PLAYER*)>

<!ELEMENT PLAYER EMPTY>

<!ATTLIST SEASON YEAR CDATA #REQUIRED>

<!ATTLIST LEAGUE NAME CDATA #REQUIRED>

<!ATTLIST DIVISION NAME CDATA #REQUIRED>

<!ATTLIST TEAM NAME CDATA #REQUIRED

CITY CDATA #REQUIRED>

<!ATTLIST PLAYER GIVEN_NAME CDATA #REQUIRED>

<!ATTLIST PLAYER SURNAME CDATA #REQUIRED>

<!ATTLIST PLAYER POSITION CDATA #REQUIRED>

<!ATTLIST PLAYER GAMES CDATA #REQUIRED>

<!ATTLIST PLAYER GAMES_STARTED CDATA #REQUIRED>

<!-Batting Statistics ->

<!ATTLIST PLAYER AT_BATS CDATA #IMPLIED>

<!ATTLIST PLAYER RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER HITS CDATA #IMPLIED>

<!ATTLIST PLAYER DOUBLES CDATA #IMPLIED>

<!ATTLIST PLAYER TRIPLES CDATA #IMPLIED>

<!ATTLIST PLAYER HOME_RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER RBI CDATA #IMPLIED>

<!ATTLIST PLAYER STEALS CDATA #IMPLIED>

<!ATTLIST PLAYER CAUGHT_STEALING CDATA #IMPLIED>

<!ATTLIST PLAYER SACRIFICE_HITS CDATA #IMPLIED>

<!ATTLIST PLAYER SACRIFICE_FLIES CDATA #IMPLIED>

<!ATTLIST PLAYER ERRORS CDATA #IMPLIED>

<!ATTLIST PLAYER WALKS CDATA #IMPLIED>

<!ATTLIST PLAYER STRUCK_OUT CDATA #IMPLIED>

<!ATTLIST PLAYER HIT_BY_PITCH CDATA #IMPLIED>

<!-Pitching Statistics ->

<!ATTLIST PLAYER WINS CDATA #IMPLIED>

<!ATTLIST PLAYER LOSSES CDATA #IMPLIED>

<!ATTLIST PLAYER SAVES CDATA #IMPLIED>

<!ATTLIST PLAYER COMPLETE_GAMES CDATA #IMPLIED>

<!ATTLIST PLAYER SHUTOUTS CDATA #IMPLIED>

<!ATTLIST PLAYER ERA CDATA #IMPLIED>

<!ATTLIST PLAYER INNINGS CDATA #IMPLIED>

<!ATTLIST PLAYER HOME_RUNS_AGAINST CDATA #IMPLIED>

<!ATTLIST PLAYER RUNS_AGAINST CDATA #IMPLIED>

<!ATTLIST PLAYER EARNED_RUNS CDATA #IMPLIED>

<!ATTLIST PLAYER HIT_BATTER CDATA #IMPLIED>

<!ATTLIST PLAYER WILD_PITCHES CDATA #IMPLIED>

<!ATTLIST PLAYER BALK CDATA #IMPLIED>

<!ATTLIST PLAYER WALKED_BATTER CDATA #IMPLIED>

<!ATTLIST PLAYER STRUCK_OUT_BATTER CDATA #IMPLIED>

使用如下的序进程把清单10-5的内容引入到清单10-4中,当然我们假设清单10-5保存为一个文件名为"baseballattribtes.dtd"的文件中。

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

<!DOCTYPE SEASON SYSTEM "baseball attributes.dtd" >

10.8 本章小结

本章中学习了如何在DTD中声明元素的属性。具体地说,学习了下述内容:

* DTD<!ATTLIST>标记中声明属性。

* 一个<!ATTLIST>标记可以声明一个元素任意数目的属性。

* 属性通常具有缺省值,但是可以通过使用关键词#REQUIRED#IMPLIEDU#FIXED改变这种状态。

* DTD中,可以声明十种属性类型: CDATA、枚举类型、NMTOKENNMTOKENSIDIDREFIDREFSENTITYENTITIESNOTATION

* 预定义的xml:space属性确定元素中的空格是否有意义。

* 预定义的xml:lang属性规定书写元素内容的语言。

下一章将学习到如何利用记号、处理指令和不可析实体在XML文档中嵌入非XML数据。