XML教程

5章 属性、空标记和XSL(1)

使用XML对一组给定的数据进行编码,有很多种方法。但是没有哪一种方法是唯一正确的,只是一些方法相较而言更可取,在特定的应用中更合适。本章采用前面章节中所用的棒球示例,仔细探讨使用XML创建棒球统计的不同方法。文中会特别强调使用属性存储信息和使用空标记定义元素位置。另外,鉴于CSS(级联样式单)对缺乏内容的XML元素执行起来并不顺利,我们将检验另一种功能更强大的样式单语言--XSL

 

      本章内容包括:

      * 属性

      * 属性与元素的对比

      * 空标记

      * XSL

      5.1 属性

      在上一章中,所有的数据可分为标记名或者元素的内容两类。这种方法直接易懂,但不是唯一的。XML元素与HTML中的元素一样,有自己的属性。元素的每个属性是一个名称-数值对,名称和数值分别为一个字符串,一个元素不能有两个同名的属性。

 

      大家都熟悉HTML的属性句法,请看下面的<IMG>标记实例:

      <IMG SRC=cup.gif WIDTH=89 HEIGHT=67 ALT="Cup of coffee">

      该标记有4个属性,SRC属性的值是cup.gifWIDTH属性的值是89HEIGHT属性的值是67ALT属性的值是Cup of

      coffee。然而,与HTML不同,XML中属性的值必须加引号,并且必须有与起始标记匹配的终止标记。上述标记实例用XML表示为:

 

<IMG SRC="IMAGE\cup.gif" WIDTH="89" HEIGHT="67" ALT="Cup of coffee">

</IMG>

 

 

      HTMLXML的另一个不同点是:XML没有赋予IMG标记及其属性任何特殊意义。特别是不能保证XML浏览器会把该标记翻译成装载并显示cup.gif文件中的图像的指令。

 

      可以很容易将属性句法应用到棒球示例中,这样会使标记显得简洁明了。例如,我们可以用SEASON元素中的一个YEAR属性代替一个YEAR子元素:

 

<SEASON YEAR="1998">

</SEASON>

 

 

      另一方面,LEAGUE应当是SEASON个子元素而不是一个属性。因为在一个赛季中可能有两个联赛,而且子元素在任何时候都有可能指代不同的事物。但是,一个元素的属性名是不能重复的。因此,不能像下面的示例那样编写SEASON元素。

 

 

<SEASON YEAR="1998" LEAGUE="National" League="American">

</SEASON>

 

 

      LEAGUE确实是一个子元素而不是一个属性的另一个原因是,它含有子结构,可进一步分成多个DIVISION元素,其属性值是无格式文本。XML元素可对结构方便地加以编码,而属性值却不能。

 

      联赛名称是无结构的普通文本,每一个联赛只有一个名称,因此,LEAGUE元素含有一个NAME属性,而不是一个LEAGUE_NAME子元素:

 

<LEAGUE NAME="National League">

</LEAGUE>

 

 

      由于属性与元素的联系比子元素更加紧密,上述的属性名应使用NAME,而不是LEAGUE_NAME,不会出错。各分部和球队这些子元素同样有NAME属性,不必担心与联赛名混淆。一个标记可以有多个属性,只要这些属性同名即可。我们可以将各队所在的城市看作一个属性,如下所示:

 

 

<LEAGUE NAME="American League">

<DIVISION NAME="East">

<TEAM NAME="Orioles" CITY="Baltimore"></TEAM>

<TEAM NAME="Red Sox" CITY="Boston"></TEAM>

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

<TEAM NAME="Devil Rays" CITY="Tampa Bay"></TEAM>

<TEAM NAME="Blue Jays" CITY="Toronto"></TEAM>

</DIVISION>

</LEAGUE>

 

 

      如果把每一项统计选作一个属性,一个队员将包括许多属性。下面的示例是用属性表示的Joe Girardi1998年的统计数据。

 

<PLAYER GIVEN_NAME="Joe" SURNAME="Girardi"

GAMES="78" AT_BATS="254" RUNS="31" HITS="70"

DOUBLES="11" TRIPLES="4" HOME_RUNS="3"

RUNS_BATTED_IN="31" WALKS="14" STRUCK_OUT="38"

STOLEN_BASES="2" CAUGHT_STEALING="4"

SACRIFICE_FLY="1" SACRIFICE_HIT="8"

HIT_BY_PITCH="2">

</PLAYER>

 

 

      清单5-1应用这种新的属性样式展示了一个完整的XML文档,文档是1998年重要棒球联赛的统计。展示的信息与上一章清单4-1中的一样(包括2个联赛、6个分部、30个球队和9名运动员),只是标记的方式不同。图5-1显示了装入到Internet

      Explorer 5.0中的没有任何样式单的文档。

      5-1 1998年主要棒球联赛统计,使用属性表示信息

清单5-1:使用属性存储棒球统计的完整的XML文档

 

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

<SEASON YEAR="1998">

<LEAGUE NAME="National League">

<DIVISION NAME="East">

<TEAM CITY="Atlanta" 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>

<PLAYER GIVEN_NAME="Ozzie" SURNAME="Guillen"

POSITION="Shortstop" GAMES="83" GAMES_STARTED="59"

AT_BATS="264" RUNS="35" HITS="73" DOUBLES="15"

TRIPLES="1" HOME_RUNS="1" RBI="22" STEALS="1"

CAUGHT_STEALING="4" SACRIFICE_HITS="4"

SACRIFICE_FLIES="2" ERRORS="6" WALKS="24"

STRUCK_OUT="25" HIT_BY_PITCH="1">

</PLAYER>

<PLAYER GIVEN_NAME="Danny" SURNAME="Bautista"

POSITION="Outfield" GAMES="82" GAMES_STARTED="27"

AT_BATS="144" RUNS="17" HITS="36" DOUBLES="11"

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

CAUGHT_STEALING="0" SACRIFICE_HITS="3"

SACRIFICE_FLIES="2" ERRORS="2" WALKS="7"

STRUCK_OUT="21" HIT_BY_PITCH="0">

</PLAYER>

<PLAYER GIVEN_NAME="Gerald" SURNAME="Williams"

POSITION="Outfield" GAMES="129" GAMES_STARTED="51"

AT_BATS="266" RUNS="46" HITS="81" DOUBLES="18"

TRIPLES="3" HOME_RUNS="10" RBI="44" STEALS="11"

CAUGHT_STEALING="5" SACRIFICE_HITS="2"

SACRIFICE_FLIES="1" ERRORS="5" WALKS="17"

STRUCK_OUT="48" HIT_BY_PITCH="3">

</PLAYER>

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

POSITION="Starting Pitcher" GAMES="33"

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

COMPLETE_GAMES="4" SHUT_OUTS="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">

</PLAYER>

<PLAYER GIVEN_NAME="Javier" SURNAME="Lopez"

POSITION="Catcher" GAMES="133" GAMES_STARTED="124"

AT_BATS="489" RUNS="73" HITS="139" DOUBLES="21"

TRIPLES="1" HOME_RUNS="34" RBI="106" STEALS="5"

CAUGHT_STEALING="3" SACRIFICE_HITS="1"

SACRIFICE_FLIES="8" ERRORS="5" WALKS="30"

STRUCK_OUT="85" HIT_BY_PITCH="6">

</PLAYER>

<PLAYER GIVEN_NAME="Ryan" SURNAME="Klesko"

POSITION="Outfield" GAMES="129" GAMES_STARTED="124"

AT_BATS="427" RUNS="69" HITS="117" DOUBLES="29"

TRIPLES="1" HOME_RUNS="18" RBI="70" STEALS="5"

CAUGHT_STEALING="3" SACRIFICE_HITS="0"

SACRIFICE_FLIES="4" ERRORS="2" WALKS="56"

STRUCK_OUT="66" HIT_BY_PITCH="3">

</PLAYER>

<PLAYER GIVEN_NAME="Andres" SURNAME="Galarraga"

POSITION="First Base" GAMES="153" GAMES_STARTED="151"

AT_BATS="555" RUNS="103" HITS="169" DOUBLES="27"

TRIPLES="1" HOME_RUNS="44" RBI="121" STEALS="7"

CAUGHT_STEALING="6" SACRIFICE_HITS="0"

SACRIFICE_FLIES="5" ERRORS="11" WALKS="63"

STRUCK_OUT="146" HIT_BY_PITCH="25">

</PLAYER>

<PLAYER GIVEN_NAME="Wes" SURNAME="Helms"

POSITION="Third Base" GAMES="7" GAMES_STARTED="2"

AT_BATS="13" RUNS="2" HITS="4" DOUBLES="1"

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

CAUGHT_STEALING="0" SACRIFICE_HITS="0"

SACRIFICE_FLIES="0" ERRORS="1" WALKS="0"

STRUCK_OUT="4" HIT_BY_PITCH="0">

</PLAYER>

</TEAM>

<TEAM CITY="Florida" NAME="Marlins">

</TEAM>

<TEAM CITY="Montreal" NAME="Expos">

</TEAM>

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

</TEAM>

<TEAM CITY="Philadelphia" NAME="Phillies">

</TEAM>

</DIVISION>

<DIVISION NAME="Central">

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

</TEAM>

<TEAM CITY="Cincinnati" NAME="Reds">

</TEAM>

<TEAM CITY="Houston" NAME="Astros">

</TEAM>

<TEAM CITY="Milwaukee" NAME="Brewers">

</TEAM>

<TEAM CITY="Pittsburgh" NAME="Pirates">

</TEAM>

<TEAM CITY="St.Louis" NAME="Cardinals">

</TEAM>

</DIVISION>

<DIVISION NAME="West">

<TEAM CITY="Arizona" NAME="Diamondbacks">

</TEAM>

<TEAM CITY="Colorado" NAME="Rockies">

</TEAM>

<TEAM CITY="Los Angeles" NAME="Dodgers">

</TEAM>

<TEAM CITY="San Diego" NAME="Padres">

</TEAM>

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

</TEAM>

</DIVISION>

</LEAGUE>

<LEAGUE NAME="American League">

<DIVISION NAME="East">

<TEAM CITY="Baltimore" NAME="Orioles">

</TEAM>

<TEAM CITY="Boston" NAME="Red Sox">

</TEAM>

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

</TEAM>

<TEAM CITY="Tampa Bay" NAME="Devil Rays">

</TEAM>

<TEAM CITY="Toronto" NAME="Blue Jays">

</TEAM>

</DIVISION>

<DIVISION NAME="Central">

<TEAM CITY="Chicago" NAME="White Sox">

</TEAM>

<TEAM CITY="Kansas City" NAME="Royals">

</TEAM>

<TEAM CITY="Detroit" NAME="Tigers">

</TEAM>

<TEAM CITY="Cleveland" NAME="Indians">

</TEAM>

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

</TEAM>

</DIVISION>

<DIVISION NAME="West">

<TEAM CITY="Anaheim" NAME="Angels">

</TEAM>

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

</TEAM>

<TEAM CITY="Seattle" NAME="Mariners">

</TEAM>

<TEAM CITY="Texas" NAME="Rangers">

</TEAM>

</DIVISION>

</LEAGUE>

</SEASON>

在清单5-1中,队员的信息是用属性表示的,清单4-1是用元素内容表示的。当然,也有合二为一的表示方法。例如,队员的名字作为元素内容,而其他部分作为属性,如下所示:

 

 

<P>

On Tuesday<PLAYER GAMES="78" AT_BATS="254" RUNS="31"

HITS="70" DOUBLES="11" TRIPLES="4" HOME_RUNS="3"

RUNS_BATTED_IN="31" WALKS="14" STRIKE_OUTS="38"

STOLEN_BASES="2" CAUGHT_STEALING="4"

SACRIFICE_FLY="1" SACRIFICE_HIT="8"

HIT_BY_PITCH="2">Joe Girardi</PLAYER>struck out twice

and...

</P>

 

 

      这样处理后,Joe

      Girardi的名字会以一个超链接文本的脚注或者工具提示包含在页面文本中,同时能保证希望深究它的读者得到该统计。一组相同的数据可以用多种方法进行编码,具体选择哪一种编码方法取决于用户特定应用的需要。

 

      5.2 属性与元素的对比

      何时使用子元素或属性没有严格的规则可循,通常要看哪一种更适合自己应用的需要。随着经验的增长就会有一种感觉,知道在何时使用属性比子元素更简单,反之亦然。一个好的经验规则是数据本身应当存储在元素中,而有关数据的信息(元数据)应当存储在属性中。不知道怎么做时,就把信息放在元素中。

 

      为区分数据与元数据,首先要问自己是否会有一些读者希望看到一条特别的信息。如果答案是肯定的,该信息应当包含在个子元素中。相反,则应包含在一个属性中。如果从该文档中删除所有标记与属性,文档的基本信息应当还存在。属性是放置ID号、URL、参考资料及其他与读者不直接相关的信息的好地方。但是,把元数据作为属性存储的基本规则还有许多例外。这些例外包括:

 

      * 属性不能很好地保持原文的结构。

      * 元素允许包括元元数据(有关信息的更深层次的信息)。

      * 每个人对元数据和非元数据的理解是不一样的。

      * 面对以后的变化,元素更具扩展性。

5.2.1 结构化的元数据

      需要特别记住的是元素可以有子结构而属性没有。这使元素更加灵活,更方便我们将元数据编译成子元素。例如,设想我们在写一篇论文,而且希望其中包含某件事情的出处,结果可能是这样:

 

 

<FACT SOURCE="The Biographical History of Baseball,

Donald Dewey and Nicholas Acocella (New York:Carroll &amp;

Graf Publishers,Inc.1995)p.169">

Josh Gibson is the only person in the history of baseball to

hit a pitch out of Yankee Stadium.

</FACT>

 

 

      很明显,信息“The Biographical History of Baseball, Donald Dewey and Nicholas

      Acocella(New York:Carroll &Graf Publishers,Inc.

      1995)p.169"是元数据。它不是事情本身而更像事情的有关信息。SOURCE属性暗含了许多子结构。按照下文的方法组织上面的信息可能更有效:

 

<SOURCE>

<AUTHOR>Donald Dewey</AUTHOR>

<AUTHOR>Nicholas Acocella</AUTHOR>

<BOOK>

<TITLE>The Biographical History of Baseball</TITLE>

<PAGES>169</PAGES>

<YEAR>1995</YEAR>

</BOOK>

</SOURCE>

 

 

      此外,使用元素代替属性包含附加的信息更容易、直接,例如作者的e-mail地址,可找到文档的电子副本的URL,日报特刊的标题或主题以及其他看似重要的信息等。

 

      日期是另外一个常见的例子。与学术论文有关的一个常用的元数据是第一次收到论文的日期,它对建立发明创造的优先权很重要。在ARTICLE标记中很容易包含一个DATE属性,如下所示:

 

 

<ARTICLE DATE="06/28/1969">

Polymerase Reactions in Organic Compounds

</ARTICLE>

 

 

      DATE属性中含有用/表示的子结构,如果要从属性值中获得该结构要比读取DATE元素的子元素困难得多,如下所示:

 

<DATE>

<YEAR>1969</YEAR>

<MONTH>06</MONTH>

<DAY>28</DAY>

</DATE>

 

 

      例如,使用CSSXSL很容易将日期或月份格式化为看不见的形式,因此只会出现年份。请看下面使用CSS的例子:

      YEAR {display:inline}

      MONTH {display:none}

      DAY {display:none}

      如果DATE是作为属性存储的,几乎没有简单的办法可以访问其中任何一部分。我们只有用一种类似ECMAScriptJava的编程语言写一个单独的程序,才能分析其日期格式。使用标准的XML工具和子元素做起来就比较容易。

 

      另外,属性句法显得模糊不清,"10/11/1999"究竟表示1011日还是1110日?不同国家的读者对它的理解是不同的。即使语法分析程序能够识别某种格式,但不能保证其他人能够正确输入日期。作此对照用XML表示就不会摸棱两可。

 

      最后,使用DATE子元素允许一个元素有多个日期。例如,学术论文通常要交还作者修改。在此情况下,记录再次收到修改过的论文的日期也很重要。例如:

 

<ARTICLE>

<TITLE>

Maximum Projectile Velocity in an Augmented Railgun

</TITLE>

<AUTHOR>Elliotte Harold</AUTHOR>

<AUTHOR>Bruce Bukiet</AUTHOR>

<AUTHOR>William Peter</AUTHOR>

<DATE>

<YEAR>1992</YEAR>

<MONTH>10</MONTH>

<DAY>29</DAY>

</DATE>

<DATE>

<YEAR>1993</YEAR>

<MONTH>10</MONTH>

<DAY>26</DAY>

</DATE>

</ARTICLE>

 

 

      再比如,在HTML中,IMG标记的ALT属性被限定为一个单独的文本字符串。虽然一幅图片比成千的单词更能说明问题,但还是应该用已标记的文本来代替一个IMG标记。例如,考虑图5-2中的饼形图。

 

      5-2 主要棒球联赛中各位置球员的分布情况

      使用ALT属性对该图的最好描述如下:

 

<IMG SRC="IMAGE\05021.gif"

ALT="Pie Chart of Positions in Major League Baseball"

WIDTH="819" HEIGHT="623">

</IMG>

 

 

      如果对上图使用一个ALT子元素描述,会更具灵活性,因为我们可以在其中嵌入标记。例如,使用一个写有相关数字的一览表去替代饼形图:

 

<IMG SRC="IMAGE\05021.gif" WIDTH="819" HEIGHT="623">

<ALT>

<TABLE>

<TR>

<TD>Starting Pitcher</TD><TD>242</TD><TD>20%</TD>

</TR>

<TR>

<TD>Relief Pitcher</TD><TD>336</TD><TD>27%</TD>

</TR>

<TR>

<TD>Catcher</TD><TD>104</TD><TD>9%</TD>

</TR>

<TR>

<TD>Outfield</TD><TD>235</TD><TD>19%</TD>

</TR>

<TR>

<TD>First Base</TD><TD>67</TD><TD>6%</TD>

</TR>

<TR>

<TD>Shortstop</TD><TD>67</TD><TD>6%</TD>

</TR>

<TR>

<TD>Second Base</TD><TD>88</TD><TD>7%</TD>

</TR>

<TR>

<TD>Third Base</TD><TD>67</TD><TD>6%</TD>

</TR>

</TABLE>

</ALT>

</IMG>

 

 

      在得不到位图图片的情况下,甚至可以使用实际的PostscriptSVGVML代码来形成该图片。

5.2.2 元元数据

      元素可用于元数据,同样也可用于元元数据,或者信息的深层相关信息。例如,一首诗的作者是这首诗的元数据,书写作者姓名所用的语言就是这首诗的元元数据。特别是对于明显的非罗马语言,这并非是无关紧要的。例如,Odyssey的作者是Homer还是Ωμηοδ?如果使用元素就可以很容易写出:

 

 

<POET LANGUAGE="English">Homer</POET>

<POET LANGUAGE="Greek">Ωμηοδ</POET>

 

 

      但是,如果POET是一个属性而不是一个元素,如下所示的这种不易操作的结构会让人感到纠缠不清:

 

<POEM POET="Homer" POET_LANGUAGE="English"

POEM_LANGUAGE="English">

Tell me,O Muse,of the cunning man...

</POEM>

 

 

      而且如果想要同时提供诗人的英文名与希腊名的时候,这种表示方法会更显得重要:

 

<POEM POET_NAME_1="Homer" POET_LANGUAGE_1="English"

POET_NAME_2=" Ωμηοδ" POET_LANGUAGE_2="Greek"

POEM_LANGUAGE="English">

Tell me,O Muse,of the cunning man...

</POEM>

 

 

      5.2.3 有关元数据的说明

      判断元数据的决定权掌握在读者手中,不同的读者和他们的阅读目的决定哪些是元数据,哪些是数据。例如,阅读一份学报上的文章,作者的名字与文章的内容相比就显得无足轻重。但是,如果作为晋升委员会的委员浏览学报来确定发表与未发表文章的人员,作者的名字与所发表文章的数量比其内容更重要。

 

      事实上,人们也许会改变对数据和元数据的看法。今天看似无关紧要的东西,下周可能会变得很有用。你可以使用样式单隐藏今天看似不重要的元素,在以后可改变样式单将其显示出来。但是,显示一个原先存储在属性中的信息很困难。通常在此情况下需要重写整个文档,而不是简单地修改样式单。

 

      5.2.4 元素更具扩展性

      在只需要传达一两个字的非结构性信息时,使用属性是很方便的。在此情况下,显然不需要个子元素。但是这并不排除日后需要它。

      例如,目前可能只需要存储一篇文章的作者名而不必区分名和姓。但将来可能会需要存储姓名、e-mail地址、机构、邮政通信处、URL以及更多的东西。如果把文章的作者保存为一个元素,在其中添加子元素包含这些附加的信息会很容易。

 

      尽管上述任何改动都需要重新修改文档、样式单和相关的程序,但是把一个简单的元素修改为元素树比把一个属性修改为元素树简单得多。而且使用了属性就只好继续使用下去。扩展属性句法使之超越最初的设计范围也很困难。

5.2.5 使用属性的最佳时机

      在前面已经详尽阐述了应当使用子元素代替属性的原因,然而,必须指出的是,有时候使用属性是有意义的。首先,同前面提到的一样,属性非常适用于那些读者未必想看见的没有子结构的简单数据。例如,IMG中的HEIGHTWIDTH属性,尽管这些属性值随图片的改变而改变,但是无法想象属性中的数据除了一个很短的字符串外还能是什么。HEIGHTWIDTH分别是一维的数,因此作为属性执行起来很顺利。

 

      此外,属性也适用于与文档有关而与文档内容无关的简单信息。例如,给每一个元素指定一个ID属性常常是有用的,这是文档中仅隶属于元素的唯一字符串。该字符串可用于不同的目的,包括链接到文档中的特殊元素。甚至在文档发生改变时,这些元素会随之移动。例如:

 

 

<SOURCE ID="S1">

<AUTHOR ID="A1">Donald Dewey</AUTHOR>

<AUTHOR ID="A2">Nicholas Acocella</AUTHOR>

<BOOK ID="B1">

<TITLE ID="B2">

The Biographical History of Baseball

</TITLE>

<PAGES ID="B3">169</PAGES>

<YEAR ID="B4">1995</YEAR>

</BOOK>

</SOURCE>

 

 

      利用ID属性使链接文档中的特定元素成为可能。这样它们就有与HTMLA元素的NAME属性一样的功能。其他与链接有关的数据--HREF属性指明的链接目标,SRC属性指定的图像和二进制数据等等--作为属性都很合适。

 

      在第16"Xlink"和第17"XPointer"中讨论XLL--可扩展链接语言时,会看到更多的这种例子。

      属性也常用于存储文档的特定样式信息。例如,TITLE元素一般是以粗体出现,但是如果使一个TITLE元素有粗体和斜体两种字体,可以这样描述:

<TITLE style="font-style:italic">Significant Others</TITLE>

 

      这样做可以在不改变文档树状结构的情况下嵌入样式信息。虽然最理想的方法是使用一个单独的元素,但当不能在处理的标记集里添加元素时,这个方案会给文档作者更多的控制权。例如,一个站点的管理员需要使用某一特定的DTD,而且不希望任何人修改DTD。除此之外,还要允许他人对个别的页面做微小的校正。使用这种方案时要有所节制,否则很快会发现自己又陷入了HTML"地狱"中,使用XML的本意是要避免这一"地狱"的。

 

      使用属性的最后一个原因是为了保持与HTML的兼容性。甚至扩展到使用的标记,对于诸如<IMG><P><TD>看起来与HTML相似的标记还是使用标准的HTML属性为好。这样做有双重好处,至少使传统的浏览器能够部分地分析和显示你的文档,而且对于文档的作者来说更熟悉这种方式。

 

      5.3 空标记

      上一章中没有属性的方式是一种极端的情况,由此可能会想到另一个极端——将所有的信息全部存储在属性中,而不是存储在内容中。通常不推荐使用这种方式。把信息全部存储在元素内容中同样也是极端的,只是实际处理起来更容易。这一节考虑仅使用属性来说明的可能性。

 

      只要元素中没有内容,就可以使用空标记来简化。可以只包含一个空标记而不是一个起始标记和一个终止标记。空标记与起始标记的区别在于结束标记使用“/>”而不是简单的“>”。例如,不是<PLAYER></PLAYER>而是<PLAYER/>

 

      空标记可以包含属性。例如,下面是关于Joe Girardi的一个空标记,含有7个属性:

 

<PLAYER GIVEN_NAME="Joe" SURNAME="Girardi"

GAMES="78" AT_BATS="254" RUNS="31" HITS="70"

DOUBLES="11" TRIPLES="4" HOME_RUNS="3"

RUNS_BATTED_IN="31" WALKS="14" STRUCK_OUT="38"

STOLEN_BASES="2" CAUGHT_STEALING="4"

SACRIFICE_FLY="1" SACRIFICE_HIT="8"

HIT_BY_PITCH="2"/>

 

 

      XML句法分析器对空标记的处理与非空标记是一样的。下面的PLAYER元素与前面的空标记元素PLAYER精确地说是等价的(尽管不是完全一致):

 

<PLAYER GIVEN_NAME="Joe" SURNAME="Girardi"

GAMES="78" AT_BATS="254" RUNS="31" HITS="70"

DOUBLES="11" TRIPLES="4" HOME_RUNS="3"

RUNS_BATTED_IN="31" WALKS="14" STRUCK_OUT="38"

STOLEN_BASES="2" CAUGHT_STEALING="4"

SACRIFICE_FLY="1" SACRIFICE_HIT="8"

HIT_BY_PITCH="2"></PLAYER>

 

 

      <PLAYER/><PLAYER></PLAYER>之间的不同只是句法表面的不同,而没有别的不同。如果不喜欢空标记句法或者阅读起来感到困难,就不要使用它。

5.4 XSL

      如图5-1所示,属性在文档的XML源视图中是可见的。但是一旦把CSS样式单施加其上,属性就会消失。图5-3显示了清单5-1使用前面章节中棒球统计样式单后的样子。它看起来是一个空白文档,因为CSS样式单仅适用于元素内容,而不适用于属性。在使用CSS时,希望显示给读者的任何数据应当是元素内容的一部分,而不是它的属性。

 

      5-3 CSS施加于一个元素中不含任何字符数据的XML文档时显示的空白文档

      但是,仍然有一种可选择的样式单语言能够访问并显示属性数据。这就是Extensible Style Language (XSL)Internet

      Explorer 5.0至少部分支持它。XSL分为两部分:变换部分和格式化部分。

      XSL替换部分能够将一个标记替换为另一个标记。通过定义替换规则,使用标准的HTML标记代替XML标记或者使用HTML标记与CSS属性来替换XML标记。同时还可以在文档中重新安排元素和在XML文档中添加没有出现过的附加内容。

 

      XSL格式化部分把功能强大的文档视图定义为页面。XSL格式化功能能够指定页面的外观和编排,包括多个专栏、围绕主题的字数、行间距、相配的字体属性等等。它的功能非常强大,足可以为网络和打印自动处理来自于相同源文档的编排任务。例如,XSL格式化允许包含有show

      times(在线播放)和广告的XML文档生成本地报纸上电视节目单的打印及在线版本。但是IE

      5.0和大多数其他工具还不支持XSL格式化。因此,本节重点介绍XSL变换。

      XSL格式化将在第15XSL格式化对象中讨论。

      5.4.1 XSL样式单模板

      每个XSL样式单包括一些模板,XML文档中的数据会注入其中。例如,某一模板如下所示:

 

<HTML>

<HEAD>

<TITLE>

XSL Instructions to get the title

</TITLE>

</HEAD>

<H1>XSL Instructions to get the title </H1>

<BODY>

XSL Instructions to get the statistics

</BODY>

</HTML>

 

 

      斜体部分将由特定的XSL元素取代,这些元素把基本的XML文档中的数据复制到该模板中。该模板可用于许多不同的数据集。例如,模板设计用于处理棒球示例,那么相同的样式单能够显示不同赛季的统计。

 

      这令人想起了用于HTML的某种服务器端嵌入方案。事实上,这与服务器端嵌入方案极其类似。但是,XML源文档与XSL样式单的实际变换发生在客户端,而不是服务器端。而且输出的文档可以是任何一种结构完整的XML文档,不必是HTML文档。

 

      XSL指令能够提取存储于XML文档中的任何数据。包括元素内容、元素名称和对我们的示例很重要的元素属性。特定的元素由一种模式选定,该模式会考虑元素的名称和值、元素的属性名和值以及XML文档树状结构中的绝对和相对位置等等。数据一经从一个元素中取出,就可以移动、复制和经过其他多种处理。在这个简要的介绍中描述了使用XML变换部分所能做的事情。读者将学到使用XSL编写一些能够立即在网上看到的令人吃惊的文档。

 

      在第14章的"XSL变换"中对XSL的变换作了彻底的阐述。

5.4.2 文档的主体

      请看下面的简单例子,并把它应用于清单5-1所示的棒球统计的XML文档中,清单5-2是一个XSL样式单。它提供XML数据将要注入的HTML"模子"

      清单5-2:一个XSL样式单

 

<?xml version="1.0"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">

<xsl:template match="/">

<HTML xmlns:xsl="http://www.w3.org/TR/WD-xsl">

<HEAD>

<TITLE>

Major League Baseball Statistics

</TITLE>

</HEAD>

<BODY>

<H1>Major League Baseball Statistics</H1>

<HR></HR>

Copyright 1999

<A HREF="http://www.macfaq.com/personal.html">

Elliotte Rusty Harold

</A>

<BR />

<A HREF="mailto:elharo@metalab.unc.edu">

elharo@metalab.unc.edu

</A>

</BODY>

</HTML>

</xsl:template>

</xsl:stylesheet>

 

 

      该清单像一个包含在XSL:template元素中的HTML文件,也就是说它的结构更像是这样:

 

<?xml version="1.0"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">

<xsl:template match="/">

HTML file goes here

</xsl:template>

</xsl:stylesheet>

 

 

      清单5-2不仅是一个XSL样式单,同样是一个结构完整的HTML文档。它以一个XML名称开始,文档的根元素是xsl:stylesheet。该样式单包含唯一的模板,把XML数据编码为一个xsl:template元素。xsl:template元素有一个match属性,其值为/,内容是一个结构完整的HTML文档。输出的HTML结构完整不是一种巧合。因为HTML首先必须是一个XSL样式单的一部分,并且XSL样式单是结构完整的XML文档,因此在一个XSL样式单中的所有HTML一定结构完整。

 

      Web浏览器尽量使XML文档各部分与每个xsltemplate元素相匹配。/模板与文档的根即整个文档本身相匹配。浏览器读取模板并将来自XML中的数据插入XSL指令指明的位置。但是该特定模板不包含XSL指令。因此它的内容只是被逐字逐句地复制到Web浏览器中,产生如图5-4所示的输出结果。请注意该图不显示XML文档的任何数据,只显示XSL模板中的数据。把清单5-2中的XSL样式单与清单5-1中的XML文档连接起来很方便,只需增加一个处理指令,该指令位于XML声明和根元素之间,含有一个值为text/xsltype属性和一个指向样式单的href属性。例如:

 

 

<?xml version="1.0"?>

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

<SEASON YEAR="1998">

...

 

      这与在文档上连接CSS样式单的方法一样,唯一不同的是type属性的值为text/xsl而不是text/css

      5-4 采用清单5-2XSL样式单后,XML文档中的数据而不是XSL模板中的数据消失了