版权声明: 本文可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息。
原文出处: http://www.aiview.com/notes/xml_xsl_manual.htm
作者: 张洋 Alex_doesAThotmail.com
最后更新: 2001-12-15
目录
- 用户的需求
- 对应的技术实现
- 定义DTD
- 如何准备手册内容
- 最关键的部分: 开发XSL
- 将各部分粘合起来
- 参考资源
本文将介绍利用XML与XSL技术内容与表示相分离的特点,创建一个易于扩充,结构灵活的用户手册文档。当然,本文的重点在于阐述一种利用XML的方法,并不仅限于用户手册文档,您可以利用在任何您认为需要的地方。
本文介绍的内容基于XML、XSL与DTD技术规范,您可以使用IE5.0及其以上版本进行测试。
用户的需求
从用户的视角,我们要实现的用户手册包含以下几个部分:
- 在页面左侧的导航栏,利用树状显示每一个级别的标题,每个标题前按顺序标有标题序号,形如2.1或2.1.8,对于包含子标题的标题要与不包含子标题的标题,即叶子标题区分显示,前面用不同的图片区分。
- 页面右侧是内容栏,除包含各级标题外,还包含各级标题下的具体内容,内容类型有文字和图片。不同级别的标题要使用不同的现实方式,每个标题前也加有标题序号,与左侧导航栏一致。
- 对于含有图片的内容部分,图片显示为缩略图,通过点击可以在正常大小与缩略图之间切换。
- 点击左侧导航栏的标题,可以定位到右侧的相关内容。
- 对于手册的标题和内容,应该可以方便添加与删除,修改了一个标题的级别,比如从一级标题换到三级标题的位置,其显示风格可以自动更改。理论上,标题的级别可能会有无限多层。
- 无论是左侧的导航栏,还是右侧的内容区域,用户只对标题的相对位置和级别负责,标题前面的序号要求自动生成。
对应的技术实现
我们打算开发如下几个部分来实现这个用户手册:
- 一个DTD定义文件,即Document Type Defination(文档类型定义)。定义这个文件需要根据客户对于手册的需求来做,这也是我们需要首先着手去做的部分。
- 一个XML文档,包含了用户手册的真实的、具体的内容,这个文档当然应该是良构的,需要遵循我们在上面DTD文件中定义的规则。这个文档的内容由手册的编写者来提供,他无需了解其他的技术细节,只需遵照一个简单的规则即可,如果能够使用像XMLSPY这样的工具编辑会更省力。
- 两个XSL文件,这是我们需要开发的主体部分,也就是决定内容将如何呈现给用户,共有两个XSL文件,分别针对左侧的导航栏和右侧的内容主体。
- 一个超文本文件,构成用户手册的框架,实现导航栏与内容主体的分割。
定义DTD
通过前面的需求陈述,可以抽象出如下页面元素:
- 标题,会有不同的级别
- 内容,包括文本和图片
为此我们定义如下DTD元素:
分别表示图片、段落文本和标题。为了表示标题的嵌套关系,我们又定义一个Item元素,把它作为一个块,不同的Item之间可以是平行关系,也可以是嵌套关系,所有的基本元素(图片、段落文本和标题)必须从属于一个Item。具体的关系描述如下:
- 一个Item中只能包含、并且必须包含一个标题(Name)
- 一个Item中可以同时包含多段文字或者多张图片,或者只包含其中一种
- 一个Item中可以嵌套一个或多个Item,也可以不嵌套任何Item,嵌套的Item具有相同的属性
通过以上描述,我们可以定义元素Item:
> >
元素名称后面的'*' 代表此元素可以为0个或者多个。
'(Para | Image)' 与 'Para, Image*' 同义。
作为DTD,还必须定义一个根元素,我们起名为'PMS_Help',它将包含Item元素,进而间接的包含了所有的元素。
> >
上面表示为,使用了这个DTD的XML文档中可以定义0个或者多个Item元素。
此外,我们还需要为图片的缩略图定义:
> >
至此,DTD文件我们就已经定义完了,我们将其保存为PMS_Help.dtd,这里 下载 完整的DTD文件。
如何准备手册内容
接下来我们要根据上面定义的DTD文件,整理含有实际内容的XML文件,文件内容示例如下:
> >
1<pms_help>
2> <item>
3> <name>第一级别标题</name>
4> <item>
5>
6> <name>第二级别标题</name>
7> <item>
8> <name>第三级别标题</name>
9> <para>段落文本1</para>
10>
11> <image small="image1_small.gif"/>image1.gif
12> <para>段落文本2</para>
13> <para>段落文本3</para>
14> </item>
15>
16> <item>
17> ...
18> </item>
19> </item>
20> <item>
21> ...
22> </item>
23>
24> </item>
25> <item>
26> ...
27> </item>
28> </pms_help>
在第三级标题下面,当然还可以继续嵌套Item元素,有更多级别的标题,只要你在下面的XSL样式文件中对应说明了这个级别标题如何显示即可。
在Para标记之间的文本在显示时会被格式化为一个段落,如果你有多段文本,应该分别用Para进行标记。
Image标记中有一个属性字段small,其值应是一个图片文件名,这个图片用作默认的缩略图显示,当点击这个图片,会显示出Image标记之间的图片文件,当然您也可以对着两个图片使用同一个文件名。图片文件的路径不需要在这里指定,路径是写在下面XSL文件中的。
当我们把包含手册内容的XML文件准备好之后,我们还需要做一个额外的工作,就是将此XML文件复制一份,两个文件分别取名为:PMS_Help.xml, PMS_Help_Lf.xml,两个文件分别绑定不同的XSL样式文件,以取得不同的显示效果,除此以外,两个XML文件是完全相同的。下载两个XML文件: 导航栏XML文件 , 内容栏XML文件
最关键的部分: 开发XSL
1. 首先实现相对简单一点的对应左侧导航栏的XSL文件。
XML文件以一定的结构描述了我们要展现的内容,而在XSL当中,决定了这些内容将以何种方式展现,实际上是XSL是将XML文档格式化为浏览器可以理解的HTML格式文本,在XSL中首先需要定义根级的模板,实际对应了HTML文档的Head以及Body部分,代码如下:
1<xsl:template match="PMS_Help">
2<html>
3<head>
4<title>Index</title>
5<style media="screen">
6 <xsl:comment><![CDATA[
7 body {font-fimaly:'宋体',Arial; font-size:9pt; color:#000000;}
8 ]]></xsl:comment>
9
10 </style>
11</head>
12<body>
13<h2>Index</h2>
14<p></p>
15<xsl:for-each select="Item">
16<xsl:apply-templates select="."></xsl:apply-templates>
17</xsl:for-each>
18</body>
19</html>
20</xsl:template>
接下来实现对应处理Item部分的模板:
1<xsl:variable name="allParentNode" select="ancestor::Item/Name"></xsl:variable>
1<xsl:variable name="allChildNode" select="child::Item/Name"></xsl:variable>
1<xsl:variable name="numOfAllParentNode" select="count($allParentNode) + 1"></xsl:variable>
1<xsl:variable name="numOfAllChildNode" select="count($allChildNode)"></xsl:variable>
1<xsl:element name="div">
2<xsl:attribute name="onclick">
3 window.open('PMS_Help.xml#<xsl:number count="Item" format="01-01-01" level="multiple"></xsl:number>','main')
4 <!-- <xsl:value-of select=""alert(this.id)"" /> --></xsl:attribute>
5<xsl:attribute name="style"><xsl:value-of select="concat('text-indent:',(($numOfAllParentNode - 1) * 30),';white-space: nowrap')"></xsl:value-of></xsl:attribute>
6<xsl:attribute name="id"><xsl:number count="Item" format="01-01-01" level="multiple"></xsl:number></xsl:attribute>
7<xsl:attribute name="onmouseover"><xsl:value-of select="" this.style.color='red';this.style.cursor='hand' ""></xsl:value-of></xsl:attribute>
8<xsl:attribute name="onmouseout"><xsl:value-of select="" this.style.color='black' ""></xsl:value-of></xsl:attribute>
9<xsl:choose>
10<xsl:when test="$numOfAllParentNode = 0">
11<img alt="" border="0" src="images/page/minus.gif"/>
12</xsl:when>
13<xsl:when test="$numOfAllParentNode != 0 and $numOfAllChildNode = 0">
14<img alt="" border="0" src="images/page/passage.gif"/>
15</xsl:when>
16<xsl:otherwise>
17<img alt="" border="0" src="images/page/minus.gif"/>
18</xsl:otherwise>
19</xsl:choose>
20<xsl:number count="Item" level="multiple"></xsl:number>_
21 <xsl:value-of select="Name"></xsl:value-of>
22</xsl:element>
1<xsl:if test="count(Item)>0">
2<xsl:apply-templates select="Item"></xsl:apply-templates>
3</xsl:if>
在上面的代码中,我们通过如下的方式定义变量:
1<xsl:variable name="allParentNode" select="ancestor::Item/Name"></xsl:variable>
其中,allParentNode是我们为变量取的名字,后面使用select关键字为其赋值,ancestor关键字用于取出后面给定节点的所有父节点,Item/Name是我们在DTD中定义的节点名称。之后我们可以通过 $allParentNode 来引用这个变量。接下来我们使用:
1<xsl:variable name="numOfAllParentNode" select="count($allParentNode) + 1"></xsl:variable>
取出当前节点所有父节点的数量。使用以下代码能够在XSL当中实现一个类似case语句的功能,用来判断是否是叶子节点,以显示不同的图片。
1<xsl:choose>
2<xsl:when test="$numOfAllParentNode = 0">
3<img alt="" border="0" src="images/page/minus.gif"/>
4</xsl:when>
5<xsl:when test="$numOfAllParentNode != 0 and $numOfAllChildNode = 0">
6<img alt="" border="0" src="images/page/passage.gif"/>
7</xsl:when>
8<xsl:otherwise>
9<img alt="" border="0" src="images/page/minus.gif"/>
10</xsl:otherwise>
11</xsl:choose>
由于我们的DTD中不限制Item定义的级别,因此,我们需要递归的处理Item的显示,使用如下语句:
1<xsl:if test="count(Item)>0">
2<xsl:apply-templates select="Item"></xsl:apply-templates>
3</xsl:if>
我们将此XSL文件保存为PMS_Help_Lf.xsl, 下载 此文件。
2. 接下来实现对应右侧具体内容展示的XSL。
同样,也需要定义Head和body部分,不同的是,这里定义了更加复杂的样式单(CSS),并增加了一段Javascript脚本:
1<xsl:template match="PMS_Help">
2<html>
3<head>
4<title>Content</title>
5<style media="screen, print">
6 <xsl:comment><![CDATA[
7 body {font-fimaly:'宋体',Arial; font-size:9pt; color:#000000;}
8 .Title1 {font-fimaly:'宋体'; font-size:17.2pt; color:#333333; font-weight:bold; margin-left:0pt; background-color:#eeeeee; white-space: nowrap}
9 .Title2 {font-fimaly:'宋体'; font-size:14pt; color:#000000; font-weight:bold; margin-left:0pt; white-space: nowrap}
10 .Title3 {font-fimaly:'宋体'; font-size:12pt; color:#333333; font-weight:bold; margin-left:90pt; background-color:#eeeeee; white-space: nowrap}
11 .Title4 {font-fimaly:'宋体'; font-size:12pt; color:#000000; font-weight:bold; margin-left:90pt; white-space: nowrap}
12 .Title5 {font-fimaly:'宋体'; font-size:10pt; color:#333333; font-weight:bold; margin-left:160pt; background-color:#eeeeee; white-space: nowrap}
13 .Title6 {font-fimaly:'宋体'; font-size:10pt; color:#000000; font-weight:bold; margin-left:160pt; white-space: nowrap}
14 .Para1 {font-fimaly:'宋体'; font-size:9pt; color:#333333; margin-left:90pt}
15 .Para2 {font-fimaly:'宋体'; font-size:9pt; color:#333333; margin-left:90pt}
16 .Para3 {font-fimaly:'宋体'; font-size:9pt; color:#333333; margin-left:90pt}
17 .Para4 {font-fimaly:'宋体'; font-size:9pt; color:#333333; margin-left:90pt}
18 .Para5 {font-fimaly:'宋体'; font-size:9pt; color:#333333; margin-left:160pt}
19 .Para6 {font-fimaly:'宋体'; font-size:9pt; color:#333333; margin-left:160pt}
20 .image {cursor:hand}
21 ]]></xsl:comment>
22 </style>
23<script>
24 <xsl:comment><![CDATA[
25 /* 当用户点击图片时,交替显示缩略图和全图 */
26 function swapImg(which, small, normal){
27 var s = "/";
28 //alert(which + '|' + small + '|' + normal);
29 //alert(which.src.substring(which.src.lastIndexOf(s)+1));
30 if (which.src.substring(which.src.lastIndexOf(s)+1) == small){
31 which.src = 'images/' + normal;
32 which.alt = '===> 缩小 <===';
33 }
34 else{
35 which.src = 'images/' + small;
36 which.alt = '<=== 放大 ===>';
37 }
38 }
39 ]]></xsl:comment>
40 </script>
41</head>
42<body>
43<a name="top"></a>
44<h2>Content</h2>
45<p align="right">
46<a href="mailto:[email protected]">EMail to us</a><xsl:text> </xsl:text><a href="javascript:print()">打印本手册</a>
47</p>
48<xsl:for-each select="Item">
49<xsl:apply-templates select="."></xsl:apply-templates>
50</xsl:for-each>
51</body>
52</html>
53</xsl:template>
接下来是处理Item的部分:
1<xsl:template match="Item">
2<xsl:variable name="allParentNode" select="ancestor::Item/Name"></xsl:variable>
3<xsl:variable name="allChildNode" select="child::Item/Name"></xsl:variable>
4<xsl:variable name="numOfAllParentNode" select="count($allParentNode) + 1"></xsl:variable>
5<xsl:variable name="numOfAllChildNode" select="count($allChildNode)"></xsl:variable>
6<!-- output the content of help -->
7<!-- 显示各级标题 -->
8<xsl:element name="a">
9<xsl:attribute name="name"><xsl:number count="Item" format="01-01-01" level="multiple"></xsl:number></xsl:attribute>
10</xsl:element>
11<xsl:element name="div">
12<xsl:attribute name="class"><xsl:value-of select="concat('Title', $numOfAllParentNode)"></xsl:value-of></xsl:attribute>
13<xsl:number count="Item" level="multiple"></xsl:number><xsl:text> </xsl:text>
14<xsl:value-of select="Name"></xsl:value-of>
15<!-- 级别为奇数的标题下加横线 -->
16<xsl:if test="$numOfAllParentNode = 1 or ($numOfAllParentNode mod 2) != 0">
17 ::<a href="#top" target="_self">top</a>
18<hr noshade="noshade" size="1"/>
19</xsl:if>
20</xsl:element>
21<p></p>
22<!-- 如果还存在下一级Item,则递归调用此模板 -->
23<xsl:if test="count(Item)>0">
24<xsl:apply-templates select="Item"></xsl:apply-templates>
25</xsl:if>
26<!-- 如果不存在下一级标题,显示段落和图片内容 -->
27<xsl:if test="count(Item)=0">
28<xsl:apply-templates select="Para | Image"></xsl:apply-templates>
29</xsl:if>
30</xsl:template>
为了以不同的样式显示不同级别的标题我们做了如下处理,根据级别的不同,选择不同的样式单(CSS)定义:
1<xsl:attribute name="class"><xsl:value-of select="concat('Title', $numOfAllParentNode)"></xsl:value-of></xsl:attribute>
额外的,我们在级别为奇数的标题下画上横线:
1<xsl:if test="$numOfAllParentNode = 1 or ($numOfAllParentNode mod 2) != 0">
2
3 ::<a href="#top" target="_self">top</a>
4<hr noshade="noshade" size="1"/>
5</xsl:if>
同样的,我们也需要递归调用此模板进行处理:
1<xsl:if test="count(Item)>0">
2<xsl:apply-templates select="Item"></xsl:apply-templates>
3</xsl:if>
与上一个XSL不同,我们在这里还需要处理Para和Image标记,这些内容在导航栏是不需要显示的。我们定义了模板:
1<xsl:template match="Para | Image">
2<xsl:variable name="allParentNode" select="ancestor::Item/Name"></xsl:variable>
3<xsl:variable name="numOfAllParentNode" select="count($allParentNode) + 1"></xsl:variable>
4<!-- 如果节点是Para,显示段落 -->
5<xsl:if test="self::Para">
6<xsl:element name="div">
7<xsl:attribute name="class"><xsl:value-of select="concat('Para', ($numOfAllParentNode) - 1)"></xsl:value-of></xsl:attribute>
8<p>
9<xsl:value-of select="."></xsl:value-of>
10</p>
11</xsl:element>
12</xsl:if>
13<!-- 如果节点是Image,显示图片 -->
14<xsl:if test="self::Image">
15<xsl:variable name="small" select="normalize-space(@small)"></xsl:variable>
16<xsl:variable name="normal" select="normalize-space(.)"></xsl:variable>
17<xsl:element name="div">
18<xsl:attribute name="class"><xsl:value-of select="concat('Para', ($numOfAllParentNode) - 1)"></xsl:value-of></xsl:attribute>
19<xsl:element name="img">
20<xsl:choose>
21<xsl:when test="boolean($small)">
22<xsl:attribute name="src">images/<xsl:value-of select="$small"></xsl:value-of></xsl:attribute>
23<xsl:attribute name="alt"><=== 放大 ===></xsl:attribute>
24<xsl:attribute name="onclick"><xsl:value-of select="concat('swapImg(this, "',$small,'","',$normal,'")')"></xsl:value-of></xsl:attribute>
25</xsl:when>
26<xsl:when test="not(boolean($small))">
27<xsl:attribute name="src">images/<xsl:value-of select="$normal"></xsl:value-of></xsl:attribute>
28</xsl:when>
29</xsl:choose>
30<xsl:attribute name="class">image</xsl:attribute>
31<xsl:attribute name="border">0</xsl:attribute>
32</xsl:element>
33<p></p>
34</xsl:element>
35</xsl:if>
36</xsl:template>
通过onclick属性,我们为图片定义了onclick事件,以处理缩略图与正常大小图片之间的转换:
1<xsl:attribute name="onclick"><xsl:value-of select="concat('swapImg(this, "',$small,'","',$normal,'")')"></xsl:value-of></xsl:attribute>
这个XSL文件我们保存为PMS_Help.xsl, 下载 此XSL文件。
将各部分粘合起来
最后写一个HTML文件,定义两个框架,将两个XML文件粘合起来, 点此查看 此用户手册的效果。
回顾一下,此用户手册包含:
- 一个DTD文件,用于定义XML文档的结构。此文件在XML文件中被引用,但并不是强制性的,换句话说,你可以根本就不生成这个文件,但实际上你必须遵循一定的规则来编写XML文件以及实现你的XSL样式文件。
- 两个XML文件,包含了手册的实际内容。这两个文件除了链接的XSL样式文件不同外,其他完全相同。
- 两个XSL样式文件,在以上的两个XML文件中被引用。一个用于格式化左侧导航栏显示,一个用于格式化右侧具体内容显示。
- 一个超文本文件,用于粘合两个XML文件。
参考资源
- 下载此用户手册全部代码的 打包文件
- 下载电子教程: XML初学进阶 ,来自XML中国论坛-http://www.xml.net.cn (域名已失效!)