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属性不是投手的队员,句法如下:
因为XML文档对首发投手和替补投手做了区分,正确的答案应该检查这两种情况:
图5-8 采用清单5-6的XSL样式单后队员统计的显示情况
关于投手的清单,只需要把Staring Pitcher或者Relief Pitcher前的不等号变为等号。(仅仅把不等号改为等号是不能满足的,同时必须把and改为or。)句法如下:
与C或JAVA语言不同,这里比较相等只用一个等号而不是双等号,因为XSL中没有赋值操作。
清单5-7显示的XSL样式单把投手和击球手区分在两个不同的表格中,投手表格为所有投手添加了常规统计项目。清单5-1将这些项目编码在以下属性中:wins(投中)、losses(失球)、saves(救球)、shutouts(被封杀)等等。相应省略了一些列标签以保持表格预定的宽度。图5-9显示了最后的结果。
图5-9 采用清单5-7的XSL样式单能够区分投手和击球手
清单5-7:区分投手和击球手的样式单
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文档中嵌入信息如注释和处理命令的方法。