您的位置:寻梦网首页编程乐园XML 编程《XML实用大全》

第5章 属性、空标记和XSL

使用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.gif,WIDTH属性的值是89,HEIGHT属性的值是67,ALT属性的值是Cup of coffee。然而,与HTML不同,XML中属性的值必须加引号,并且必须有与起始标记匹配的终止标记。上述标记实例用XML表示为:

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

</IMG>

HTML与XML的另一个不同点是: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 Girardi在1998年的统计数据。

<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 &amp;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>

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

YEAR {display:inline}

MONTH {display:none}

DAY {display:none}

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

另外,属性句法显得模糊不清,"10/11/1999"究竟表示10月11日还是11月10日?不同国家的读者对它的理解是不同的。即使语法分析程序能够识别某种格式,但不能保证其他人能够正确输入日期。作此对照用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>

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

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中的HEIGHT和WIDTH属性,尽管这些属性值随图片的改变而改变,但是无法想象属性中的数据除了一个很短的字符串外还能是什么。HEIGHT和WIDTH分别是一维的数,因此作为属性执行起来很顺利。

此外,属性也适用于与文档有关而与文档内容无关的简单信息。例如,给每一个元素指定一个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属性使链接文档中的特定元素成为可能。这样它们就有与HTML中A元素的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格式化将在第15章XSL格式化对象中讨论。

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

5.4.3 标题

图5-4很明显丢失了数据。尽管清单5-2中的样式单显示了一些内容(与图5-3所示的CSS样式单不同),但是它没有显示XML文档中的任何数据。要添加这些数据需要使用XSL指令元素把XML源文档中的数据复制到XSL模板中。清单5-3增加了必要的XSL指令,从SEASON元素中抽取YEAR属性并把它插入到结果文档的TITLE和H1标头之间。图5-5显示了处理后的文档。

清单5-3:一个含有抽取SEASON元素和YEAR属性指令的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>

<xsl:for-each select="SEASON">

<xsl:value-of select="@YEAR"/>

</xsl:for-each>

Major League Baseball Statistics

</TITLE>

</HEAD>

<BODY>

<xsl:for-each select="SEASON">

<H1>

<xsl:value-of select="@YEAR"/>

Major League Baseball Statistics

</H1>

</xsl:for-each>

<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指令能够从SEASON元素中抽取YEAR属性。

<xsl:for-each select="SEASON">

<xsl:value-of select="@YEAR"/>

</xsl:for-each>

图5-5 清单5-1采用清单5-3所示的XSL样式单后的显示结果

这些指令出现两次是因为我们希望年份在输出结果中出现两次,一次在H1主题中,一次在TITLE中。这些指令每次出现都执行同样的功能。<xsl:for-each select="SEASON">寻出全部SEASON元素。<xsl:value-of select="@YEAR"/>插入SEASON元素中的YEAR属性值——这就是由<xsl:for-each select="SEASON">找到的字符串“1998”。

这非常重要,重述如下:xsl:for-each选出源文档(例如清单5-1)中的某一特定的XML元素,数据就从此元素中读取。Xsl:value-of把所选取元素的某一特定部分复制到输出文档中。因此,必须使用两个XSL指令。使用任何一个都是无效的。

XSL指令不同于输出的HTML和H1元素是因为这些指令都处于XSL的命名域内。也就是说所有的XSL元素名称都以xsl:开头。命名域由样式单根元素中的xmlns:xsl属性辨别。本书中的清单5-2,5-3和所有其他示例中xmlns:xsl属性的值都是http://www.w3.org/tr/wd-xsl。

命名域将在第18章中详细阐述。

5.4.4 联赛、分部和球队

下面通过添加一些XSL指令取出前面出现过的两个LEAGUE元素,并把这两个元素映射到H2标题中,如清单5-4所示。图5-6显示了使用该样式单后的文档。

清单5-4:一个带有提取LEAGUE元素指令的XSL样式单

<?xml version="1.0"?>

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

<xsl:template match="/">

<HTML>

<HEAD>

<TITLE>

<xsl:for-each select="SEASON">

<xsl:value-of select="@YEAR"/>

</xsl:for-each>

Major League Baseball Statistics

</TITLE>

</HEAD>

<BODY>

<xsl:for-each select="SEASON">

<H1>

<xsl:value-of select="@YEAR"/>

Major League Baseball Statistics

</H1>

<xsl:for-each select="LEAGUE">

<H2 ALIGN="CENTER">

<xsl:value-of select="@NAME"/>

</H2>

</xsl:for-each>

</xsl:for-each>

<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>

图5-6 当采用清单5-4中的样式单之后,联赛名称显示为H2标题样式

关键的新要素是嵌套的xsl:for-each指令:

<xsl:for-each select="SEASON">

<H1>

<xsl:value-of select="@YEAR"/>

Major League Baseball Statistics

</H1>

<xsl:for-each select="LEAGUE">

<H2 ALIGN="CENTER">

<xsl:value-of select="@NAME"/>

</H2>

</xsl:for-each>

</xsl:for-each>

最外层的指令用于选取SEASON元素,只有找到该元素才能找到它的YEAR属性,并把它的值与另外的文本Major League Baseball Statistics一起放到<H1>与</H1>之间。下一步浏览器会循环选取SEASON元素的每一个LEAGUE子元素,并把它的NAME属性值放到<H2 ALIGN ="CENTER"> 与</H2>之间。尽管只有一个xsl:for-each与LEAGUE元素相配,但是它会对SEASON元素内所有直接的LEAGUE子元素进行循环。因此,该模板在没有联赛和联赛数目不定的情况下都能工作。

同样的技巧可以用于设计表示小组的H3标题和表示各球队的H4标题。清单5-5演示了该程序,图5-7显示了使用这一样式单后的文档。各小组名称和各球队名称是从XML数据中读取的。

清单5-5:一个带有提取DIVISION和TEAM元素指令的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>

<xsl:for-each select="SEASON">

<xsl:value-of select="@YEAR"/>

</xsl:for-each>

Major League Baseball Statistics

</TITLE>

</HEAD>

<BODY>

<xsl:for-each select="SEASON">

<H1>

<xsl:value-of select="@YEAR"/>

Major League Baseball Statistics

</H1>

<xsl:for-each select="LEAGUE">

<H2 ALIGN="CENTER">

<xsl:value-of select="@NAME"/>

</H2>

<xsl:for-each select="DIVISION">

<H3 ALIGN="CENTER">

<xsl:value-of select="@NAME"/>

</H3>

<xsl:for-each select="TEAM">

<H4 ALIGN="CENTER">

<xsl:value-of select="@CITY"/>

<xsl:value-of select="@NAME"/>

</H4>

</xsl:for-each>

</xsl:for-each>

</xsl:for-each>

</xsl:for-each>

<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>

图5-7 采用清单5-5所示的样式单后显示的小组名和队名

对于TEAM元素,它的CITY和NAME属性值是H4标题的内容。同时,请注意嵌套的选取赛季、小组和各队的xsl:for-each元素,它反映了文档自身的分支结构。这并不是一种巧合。其他方案可能不要求与文档的体系相匹配,但这一方案是最简单的,尤其适用于像清单5-1所示的棒球统计这样的高度结构化的数据。


5.4.5 球员

下一个步骤是为各队的每一个队员添加统计数字,最基本的方法是用一个表格表示。清单5-6展示的XSL样式单把队员以及他们的统计数据安排在一个表格中。其中没有引入新的XSL元素。相同的xsl:for-each和xsl:value-of元素被用于PLAYER元素和它的属性中。输出的是标准的HTML表格标记。图5-8显示了结果。

清单5-6:一个将球员及其统计数据放入表格的XSL样式单

<?xml version="1.0"?>

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

<xsl:template match="/">

<HTML>

<HEAD>

<TITLE>

<xsl:for-each select="SEASON">

<xsl:value-of select="@YEAR"/>

</xsl:for-each>

Major League Baseball Statistics

</TITLE>

</HEAD>

<BODY>

<xsl:for-each select="SEASON">

<H1>

<xsl:value-of select="@YEAR"/>

Major League Baseball Statistics

</H1>

<xsl:for-each select="LEAGUE">

<H2 ALIGN="CENTER">

<xsl:value-of select="@NAME"/>

</H2>

<xsl:for-each select="DIVISION">

<H3 ALIGN="CENTER">

<xsl:value-of select="@NAME"/>

</H3>

<xsl:for-each select="TEAM">

<H4 ALIGN="CENTER">

<xsl:value-of select="@CITY"/>

<xsl:value-of select="@NAME"/>

</H4>

<TABLE>

<THEAD>

<TR>

<TH>Player</TH><TH>P</TH><TH>G</TH>

<TH>GS</TH><TH>AB</TH><TH>R</TH><TH>H</TH>

<TH>D</TH><TH>T</TH><TH>HR</TH><TH>RBI</TH>

<TH>S</TH><TH>CS</TH><TH>SH</TH><TH>SF</TH>

<TH>E</TH><TH>BB</TH><TH>SO</TH><TH>HBP</TH>

</TR>

</THEAD>

<TBODY>

<xsl:for-each select="PLAYER">

<TR>

<TD>

<xsl:value-of select="@GIVEN_NAME"/>

<xsl:value-of select="@SURNAME"/>

</TD>

<TD><xsl:value-of select="@POSITION"/></TD>

<TD><xsl:value-of select="@GAMES"/></TD>

<TD>

<xsl:value-of select="@GAMES_STARTED"/>

</TD>

<TD><xsl:value-of select="@AT_BATS"/></TD>

<TD><xsl:value-of select="@RUNS"/></TD>

<TD><xsl:value-of select="@HITS"/></TD>

<TD><xsl:value-of select="@DOUBLES"/></TD>

<TD><xsl:value-of select="@TRIPLES"/></TD>

<TD><xsl:value-of select="@HOME_RUNS"/></TD>

<TD><xsl:value-of select="@RBI"/></TD>

<TD><xsl:value-of select="@STEALS"/></TD>

<TD>

<xsl:value-of select="@CAUGHT_STEALING"/>

</TD>

<TD>

<xsl:value-of select="@SACRIFICE_HITS"/>

</TD>

<TD>

<xsl:value-of select="@SACRIFICE_FLIES"/>

</TD>

<TD><xsl:value-of select="@ERRORS"/></TD>

<TD><xsl:value-of select="@WALKS"/></TD>

<TD>

<xsl:value-of select="@STRUCK_OUT"/>

</TD>

<TD>

<xsl:value-of select="@HIT_BY_PITCH"/>

</TD>

</TR>

</xsl:for-each>

</TBODY>

</TABLE>

</xsl:for-each>

</xsl:for-each>

</xsl:for-each>

</xsl:for-each>

<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>

5.4.6 区分投手与击球手

可以注意到图5-8中的一个缺点是没有正确处理投手。贯穿本章和第4章,投手是用完全不同的统计数字集表示的,无论他们的统计数据是存储在元素内容中还是属性中。因此,投手确实需要一个表格以区别于其他队员。在把队员放入表格之前必须查看他是不是投手。如果队员的POSITION属性包含“pitcher”就忽略他。然后在只包含投手队员元素的第二个表格中反转上面的过程,投手的PLAYER元素中POSITION属性值是字符串“pitcher”。

要完成这些还必须给xsl:for-each增加另外的选择队员的代码。我们不需要选择所有队员,相反只需要选择那些POSITION属性不是投手的队员,句法如下:

<xsl:for-each select="PLAYER [(@POSITION != Pitcher )">

因为XML文档对首发投手和替补投手做了区分,正确的答案应该检查这两种情况:

<xsl:for-each select="PLAYER [(@POSITION != StartingPitcher )
$and$(@POSITION != Relief Pitcher )]">

图5-8 采用清单5-6的XSL样式单后队员统计的显示情况

关于投手的清单,只需要把Staring Pitcher或者Relief Pitcher前的不等号变为等号。(仅仅把不等号改为等号是不能满足的,同时必须把and改为or。)句法如下:

<xsl:for-each select="PLAYER[(@POSITION= Starting Pitcher )
$or$(@POSITION= Relief Pitcher )]">

与C或JAVA语言不同,这里比较相等只用一个等号而不是双等号,因为XSL中没有赋值操作。

清单5-7显示的XSL样式单把投手和击球手区分在两个不同的表格中,投手表格为所有投手添加了常规统计项目。清单5-1将这些项目编码在以下属性中:wins(投中)、losses(失球)、saves(救球)、shutouts(被封杀)等等。相应省略了一些列标签以保持表格预定的宽度。图5-9显示了最后的结果。

图5-9 采用清单5-7的XSL样式单能够区分投手和击球手

清单5-7:区分投手和击球手的样式单

<?xml version="1.0"?>

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

<xsl:template match="/">

<HTML>

<HEAD>

<TITLE>

<xsl:for-each select="SEASON">

<xsl:value-of select="@YEAR"/>

</xsl:for-each>

Major League Baseball Statistics

</TITLE>

</HEAD>

<BODY>

<xsl:for-each select="SEASON">

<H1>

<xsl:value-of select="@YEAR"/>

Major League Baseball Statistics

</H1>

<xsl:for-each select="LEAGUE">

<H2 ALIGN="CENTER">

<xsl:value-of select="@NAME"/>

</H2>

<xsl:for-each select="DIVISION">

<H3 ALIGN="CENTER">

<xsl:value-of select="@NAME"/>

</H3>

<xsl:for-each select="TEAM">

<H4 ALIGN="CENTER">

<xsl:value-of select="@CITY"/>

<xsl:value-of select="@NAME"/>

</H4>

<TABLE>

<CAPTION><B>Batters</B></CAPTION>

<THEAD>

<TR>

<TH>Player</TH><TH>P</TH><TH>G</TH>

<TH>GS</TH><TH>AB</TH><TH>R</TH><TH>H</TH>

<TH>D</TH><TH>T</TH><TH>HR</TH><TH>RBI</TH>

<TH>S</TH><TH>CS</TH><TH>SH</TH><TH>SF</TH>

<TH>E</TH><TH>BB</TH><TH>SO</TH>

<TH>HBP</TH>

</TR>

</THEAD>

<TBODY>

<xsl:for-each select="PLAYER [(@POSITION
!= Starting Pitcher )

$and$(@POSITION != Relief Pitcher )]">

<TR>

<TD>

<xsl:value-of select="@GIVEN_NAME"/>

<xsl:value-of select="@SURNAME"/>

</TD>

<TD><xsl:value-of select="@POSITION"/></TD>

<TD><xsl:value-of select="@GAMES"/></TD>

<TD>

<xsl:value-of select="@GAMES_STARTED"/>

</TD>

<TD><xsl:value-of select="@AT_BATS"/></TD>

<TD><xsl:value-of select="@RUNS"/></TD>

<TD><xsl:value-of select="@HITS"/></TD>

<TD><xsl:value-of select="@DOUBLES"/></TD>

<TD><xsl:value-of select="@TRIPLES"/></TD>

<TD>

<xsl:value-of select="@HOME_RUNS"/>

</TD>

<TD><xsl:value-of select="@RBI"/></TD>

<TD><xsl:value-of select="@STEALS"/></TD>

<TD>

<xsl:value-of select="@CAUGHT_STEALING"/>

</TD>

<TD>

<xsl:value-of select="@SACRIFICE_HITS"/>

</TD>

<TD>

<xsl:value-of select="@SACRIFICE_FLIES"/>

</TD>

<TD><xsl:value-of select="@ERRORS"/></TD>

<TD><xsl:value-of select="@WALKS"/></TD>

<TD>

<xsl:value-of select="@STRUCK_OUT"/>

</TD>

<TD>

<xsl:value-of select="@HIT_BY_PITCH"/>

</TD>

</TR>

</xsl:for-each><!— PLAYER —>

</TBODY>

</TABLE>

<TABLE>

<CAPTION><B>Pitchers</B></CAPTION>

<THEAD>

<TR>

<TH>Player</TH><TH>P</TH><TH>G</TH>

<TH>GS</TH><TH>W</TH><TH>L</TH><TH>S</TH>

<TH>CG</TH><TH>SO</TH><TH>ERA</TH>

<TH>IP</TH><TH>HR</TH><TH>R</TH><TH>ER</TH>

<TH>HB</TH><TH>WP</TH><TH>B</TH><TH>BB</TH>

<TH>K</TH>

</TR>

</THEAD>

<TBODY>

<xsl:for-each select="PLAYER[(@POSITION= Starting Pitcher )

$or$(@POSITION= Relief Pitcher )]">

<TR>

<TD>

<xsl:value-of select="@GIVEN_NAME"/>

<xsl:value-of select="@SURNAME"/>

</TD>

<TD><xsl:value-of select="@POSITION"/></TD>

<TD><xsl:value-of select="@GAMES"/></TD>

<TD>

<xsl:value-of select="@GAMES_STARTED"/>

</TD>

<TD><xsl:value-of select="@WINS"/></TD>

<TD><xsl:value-of select="@LOSSES"/></TD>

<TD><xsl:value-of select="@SAVES"/></TD>

<TD>

<xsl:value-of select="@COMPLETE_GAMES"/>

</TD>

<TD>

<xsl:value-of select="@SHUT_OUTS"/>

</TD>

<TD><xsl:value-of select="@ERA"/></TD>

<TD><xsl:value-of select="@INNINGS"/></TD>

<TD>

<xsl:value-of select="@HOME_RUNS_AGAINST"/>

</TD>

<TD>

<xsl:value-of select="@RUNS_AGAINST"/>

</TD>

<TD>

<xsl:value-of select="@EARNED_RUNS"/>

</TD>

<TD>

<xsl:value-of select="@HIT_BATTER"/>

</TD>

<TD>

<xsl:value-of select="@WILD_PITCH"/>

</TD>

<TD><xsl:value-of select="@BALK"/></TD>

<TD>

<xsl:value-of select="@WALKED_BATTER"/>

</TD>

<TD>

<xsl:value-of select="@STRUCK_OUT_BATTER"/>

</TD>

</TR>

</xsl:for-each><!— PLAYER —>

</TBODY>

</TABLE>

</xsl:for-each><!— TEAM —>

</xsl:for-each><!— DIVISION —>

</xsl:for-each><!— LEAGUE —>

</xsl:for-each><!— SEASON —>

<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>

5.4.7 元素内容与select属性

本章集中讨论了使用XSL样式单格式化存储在一个元素属性中的数据,因为使用CSS无法访问属性。如果想要包含一个元素的字符数据而不是属性,XSL同样做得很好。只要简单地把元素名称当作xsl:value-of元素的select属性值就能表明一个元素的文本将被复制到输出文档中。请看清单5-8:

清单5-8:greeting.xml

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

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

<GREETING>

Hello XML!

</GREETING>

假如要向标题H1中复制致词“Hello XML!”首先,使用xsl:for-each选择GREETING元素:

<xsl:for-each select="GREETING">

<H1>

</H1>

</xsl:for-each>

只用这一段语句足以把两个H1标记复制到输出中。使用没有select属性的xsl:value-of在两个H1标记之间放置GREETING元素的文本,当前元素(GREETING)的内容就会被默认选中。清单5-9显示了完整的样式单。

清单5-9:greeting.xsl

<?xml version="1.0" ?>

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

<xsl:template match="/">

<HTML>

<BODY>

<xsl:for-each select="GREETING">

<H1>

<xsl:value-of/>

</H1>

</xsl:for-each>

</BODY>

</HTML>

</xsl:template>

</xsl:stylesheet>

使用select同样可以选择一个子元素中的内容,只需把该子元素的名称当作xsl:value-of的select属性值。例如,在上一章的棒球示例中,队员统计被存储在子元素而不是属性中。假定文档的结构是这样(事实上这种结构比本章中的基于属性的结构更常见),表示击球员表格的XSL如下所示:

<TABLE>

<CAPTION><B>Batters</B></CAPTION>

<THEAD>

<TR>

<TH>Player</TH><TH>P</TH><TH>G</TH>

<TH>GS</TH><TH>AB</TH><TH>R</TH><TH>H</TH>

<TH>D</TH><TH>T</TH><TH>HR</TH><TH>RBI</TH>

<TH>S</TH><TH>CS</TH><TH>SH</TH><TH>SF</TH>

<TH>E</TH><TH>BB</TH><TH>SO</TH><TH>HBP</TH>

</TR>

</THEAD>

<TBODY>

<xsl:for-each select="PLAYER [(POSITION

!= Starting Pitcher ) $and$(POSITION != Relief Pitcher )]">

<TR>

<TD>

<xsl:value-of select="GIVEN_NAME"/>

<xsl:value-of select="SURNAME"/>

</TD>

<TD><xsl:value-of select="POSITION"/></TD>

<TD><xsl:value-of select="GAMES"/></TD>

<TD>

<xsl:value-of select="GAMES_STARTED"/>

</TD>

<TD><xsl:value-of select="AT_BATS"/></TD>

<TD><xsl:value-of select="RUNS"/></TD>

<TD><xsl:value-of select="HITS"/></TD>

<TD><xsl:value-of select="DOUBLES"/></TD>

<TD><xsl:value-of select="TRIPLES"/></TD>

<TD><xsl:value-of select="HOME_RUNS"/></TD>

<TD><xsl:value-of select="RBI"/></TD>

<TD><xsl:value-of select="STEALS"/></TD>

<TD>

<xsl:value-of select="CAUGHT_STEALING"/>

</TD>

<TD>

<xsl:value-of select="SACRIFICE_HITS"/>

</TD>

<TD>

<xsl:value-of select="SACRIFICE_FLIES"/>

</TD>

<TD><xsl:value-of select="ERRORS"/></TD>

<TD><xsl:value-of select="WALKS"/></TD>

<TD>

<xsl:value-of select="STRUCK_OUT"/>

</TD>

<TD>

<xsl:value-of select="HIT_BY_PITCH"/>

</TD>

</TR>

</xsl:for-each><!— PLAYER —>

</TBODY>

</TABLE>

在这种情况下,在每个PLAYER元素的子元素中,该元素的GIVEN_NAME、SURNAME、POSITION、GAMES、GAMES_STARTED、 AT_BATS、RUNS、HITS、DOUBLES、TRIPLES、HOME_RUNS、RBI、STEALS、CAUGHT_STEALING、SACRIFICE_HITS、SACRIFICE_FLIES、ERRORS、WALKS、STRUCK_OUT和HIT_BY_PITCH子元素的内容被抽取出来并被复制到输出文档中。因为本章使用了与上一章PLAYER子元素名称相同的属性名,该示例与清单5-7几乎是一致的。主要差别是@符号没有了。它表明这是一个属性而不是一个元素。

select属性的功能很多。可选择元素:按元素位置(例如第一、第二、最后、第十七个元素等等);按特定的内容;按特殊的属性值;或者按照元素的父或子元素含有一定的内容或属性值进行选择。甚至可以使用全部布尔逻辑运算符来组合各种不同的选择条件。在14章的XSL中将要探讨使用select属性的更多可能。

5.4.8 CSS还是XSL

CSS与XSL在某种程度上是重复的。XSL的功能确实比CSS更强大,但是XSL的功能与其复杂性是分不开的。这一章仅仅涉及了XSL最基本的用途。实际上XSL更复杂,而且比CSS更难学习和使用,同时也带来了一个问题:“什么时候应该使用CSS,什么时候应该使用XSL?”

CSS比XSL得到更广泛的支持。部分CSS Level 1被Netscape 4和Internet Exploer 4支持作为HTML元素(尽管存在一些令人头疼的区别);此外,Internet Exploer 5.0和Mozilla 5.0能很好支持可以同时用于XML和HTML的大部分CSS Level 1的内容和一些CSS Level 2的内容。因此,选择CSS会与更广泛的浏览器相互兼容。

另外,CSS更成熟一些,CSS Level 1(包含目前为止我们已经看到的大部分CSS内容)和CSS Level 2是W3C的推荐规范。XSL仍然是一个早期的工作草案,而且直到本书出版后也不会最终定型。早期的XSL采纳者曾经接受过考验,而且将在形式统一的标准之前接受再一次的考验。选择CSS意味着无须为了追随软件和标准的发展不停地重写自己的样式单。但是,XSL将最终形成一个可用的标准。

因为XSL是一种新事物,不同的软件实现方式不同,实现的是草案标准的不同的子集。在写作本书的1999年春天至少有三种主要不同形式的XSL在广泛应用,到本书出版前将会有更多。如果当前浏览器中不完善的CSS操作已经让人头疼的话,那么众多的XSL变种就会使人发疯。

但是,XSL的功能很明显比CSS强大。CSS仅允许格式化元素内容,不允许改变或重新安排这些内容,必须根据元素的内容或属性为元素选择不同的格式化方式或者增添诸如署名之类简单、额外的文本。XSL非常适用于XML文档仅包含最少的数据,并且数据周围没有HTML装饰的情况。

使用XSL能够从页面上分离出关键数据,如刊头、向导栏和署名等。使用CSS不得不在数据文档中包含全部这些项目。XML+XSL允许数据文档与Web页面文档分离单独存在,从而使得XML+XSL文档更容易维护和处理。

XSL终将成为现实世界和大量数据应用的最佳选择,CSS更适合于简单的页面,如祖母用于向她们孙子寄送图片的页面。但对于这些用途,HTML已经足够。如果使用HTML行不通,XML+CSS不会有多大的帮助。相较而言,XML+XSL能够解决更多HTML不能解决的困难。对于传统的浏览器来说,仍然需要CSS,但长远看来使用XSL才是发展方向。


5.5 本章小结

在本章中,读者看到了从头创建的XML文档的示例。特别是学到如下内容:

·          信息可以保存在元素的属性中。

  • 属性是包含在元素起始标记中的一个名字-数值对。
  • 属性主要用来保存关于元素的元信息,而不是元素的数据。
  • 属性比元素内容更不便处理。
  • 对于非常简单并且不随文档改变其形式的信息,使用属性较好。特别是样式信息和链接信息,作为属性执行起来很顺利。
  • 空标记给没有内容的元素提供了句法修饰。
  • XSL是一种功能强大的样式单语言,使我们能够访问和显示属性数据和转换文档。

下一章将详细介绍结构完整的XML文档必须严格遵循的规则。我们还将研究另外一些在XML文档中嵌入信息如注释和处理命令的方法。



[上页] [目录] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [18] [下页]