8.13 XSLT的内置函数

XSLT 1.0提供了少量内置函数,这些内置函数可以完成一些特定的功能。

8.13.1 使用system-property函数

system-property(string)用于根据指定属性名获取XSLT转换器的系统属性值,使用时需要传入属性名作为参数,其string参数可以是如下几种:

alt xsl:version:返回该XSLT处理器所实现的XSLT 版本号。


alt注意

实际上返回的并不是XSLT处理器所实现的XSLT版本,而是该XSLT样式单所遵循的XSLT版本。如果XSLT样式单版本是1.0,则即使该XSLT转换器实现了XSLT 2.0,执行该函数时也依然返回1。


alt xsl:vendor:返回该XSLT处理器的开发厂商的名称,例如Altova GmbH(对于XMLSpy的转换器)、Transformiix(对于Firefox的转换器)。

alt xsl:vendor-url:返回该XSLT处理器的开发厂商的网址。

例如如下XSLT样式单文件:

程序清单:codes\08\8.13\system-property.xslt

alt

对于上面的XSLT,直接使用XMLSpy进行转换处理(在XML编辑界面下按F10键即可),将看到使用该XSLT进行转换的结果文档如图8.28所示。

alt

图8.28 使用system-property函数

8.13.2 使用current函数返回当前节点集

使用current()函数无须任何参数,它的作用是返回当前节点集。对于独立使用的表达式,即没有嵌套在其他表达式中的表达式,current()与点号(.)总是相同的。

例如如下代码:

alt

完成等同于:

alt

在方括号中使用时,current()与点号是不同的,例如:

alt

上面的代码表示处理所有具有<list…/>父元素且name 属性值等于当前节点的ref 属性值的<item…/>元素。

如果将上面的current()改为点号,如下所示:

alt

上面的代码和如下代码的效果相同:

alt

它们表示处理所有具有<list…/>父元素且name属性和ref属性的值相同的<item…/>元素。

由于current()独立使用的效果和点号相同,因此在这种情况下很少使用current(),直接使用点号即可;如果需要在方括号里访问当前节点集,那就只能选择使用current()了。

8.13.3 使用element-available和function-available 函数

这两个函数的用法也很简单,它们用于判断指定的XSLT元素和XSLT函数是否可用,使用时分别以XSLT元素名和XSLT函数名作为参数即可。

例如如下XSLT代码:

程序清单:codes\08\8.13\element-available.xslt

alt

上面的XSLT只是简单地判断了xsl:value-of和table两个元素,以及current和abs两个函数是否可用。从上面的代码可以看出:判断某个元素是否可用时应添加xsl前缀,但判断某个函数是否可用则无须添加该前缀。使用上面的XSLT转换后的结果如图8.29所示。

alt

图8.29 判断XSLT元素、函数是否可用

8.13.4 使用unparsed-entity-uri函数

unparsed-entity-uri函数用于返回未解析实体的URI。下面的XML文档使用内部DTD定义了几个未解析实体:

程序清单:codes\08\8.13\entity.xml

alt

下面的XSLT样式单中使用了unparsed-entity-uri函数来返回未解析实体的URI:

程序清单:codes\08\8.13\unparsed-entity-uri.xslt

alt

使用XMLSpy提供的转换器进行转换后可看到图8.30所示结果文档。

alt

图8.30 返回未解析实体的URI

8.13.5 使用document 函数处理多个源XML文档

document函数提供了从多份源XML文档获取数据的方式,其语法格式如下:

alt

从上面的语法可以看出,document()函数的返回值是node-set,即节点集。也就是说,document可以直接将另外的源XML文档导入,导入的源XML文档整体将作为节点集使用。

关于document()的参数的说明如下:

alt 如果只提供一个参数,并且该参数为字符串,则document()将把该字符串当成URI处理,自动加载该URI对应的XML文档,并将该XML文档转换为节点集返回。

alt 如果只提供一个参数,并且该参数为节点集,则该节点集中的每个节点都将被作为URI对待,函数会加载所有URI对应的XML文档,并将所有节点集联合在一起返回。

alt 如果提供了两个参数,则第一个参数可以是字符串或节点集,而第二个参数必须是节点集。第二个参数将作为前一个参数的基URI。

alt 如果将空字符串传递给document(),则该函数将加载当前XSLT样式单自身,并将该XSLT样式单(也是XML文档)转换为节点集返回。

下面先看一种简单的情况,假设本应用有如下两份XML文档:

程序清单:codes\08\8.13\book.xml

alt

程序清单:codes\08\8.13\price.xml

alt

上面两份XML文档中分别定义了图书名和图书价格,这两份XML文档必须结合使用才有意义,下面的XSLT文档中将使用document()来处理这两份XML文档:

程序清单:codes\08\8.13\document.xslt

alt

上面的XSLT中使用document()加载了当前节点的price属性所对应的XML文档(即price.xml文档),并使用<apply-templates…/>来处理price.xml文档的所有<price…/>元素。使用上面的XSLT转换得到的结果文档如图8.31所示。

alt

图8.31 使用document()加载其他XML文档

上面的转换结果虽然得到了多份XML文档的数据,但并没有将图书和对应的价格关联起来。为了将图书和价格关联起来,可使用如下XSLT进行转换:

程序清单:codes\08\8.13\document2.xslt

alt

上面的XSLT样式单将price.xml文档内包含的所有<price…/>元素定义成prices变量,然后通过<name…/>元素和<price…/>元素的id属性建立了图书和价格之间的关联。使用上面的XSLT转换XML文档的结果如图8.32所示。

alt

图8.32 使用document()加载其他XML文档

对于上面两份XSLT样式单而言,第一份样式单中使用document()时传入的参数是XML节点;第二份样式单中使用document()时传入的参数是普通字符串。其实质并没有区别,XSLT都把它们当成XML文档的URI处理,根据它们来加载XML文档内容。

8.13.6 使用format-number函数

format-number函数用于将数值按指定格式转换成字符串返回,其语法格式如下:

alt

关于该函数的参数说明如下:

alt 第一个参数用于指定需要格式化的数值。

alt 第二个参数用于格式化字符串——该格式化字符串中必然有许多占位符,通常情况下,直接使用这些占位符即可。如果开发者需要自定义第二个参数(格式化字符串)中的占位符,则需要使用第三个参数。

alt 第三个参数是一个<decimal-format…/>元素的name属性值。

<decimal-format…/>元素的作用非常简单,开发者可通过该元素改变格式化字符串中的占位符,例如小数点默认的占位符是点号(.),通过该元素可将其改为竖线(|)或星号(*)等任意字符。

<decimal-format…/>元素只能作为<stylesheet…/>和<transform…/>两个元素的子元素使用,该元素不能包含任何子元素,但可以分配多个属性,这些属性都用于改变格式化字符串中的占位符。该元素可使用的属性如下:

alt name:可选属性,用于为该<decimal-format…/>指定名字。如果没有指定name属性值,则该<decimal-format…/>将作为默认的数值格式,对所有没有传入第三个参数的format-number()起作用。

alt decimal-separator:可选属性,用于指定整数部分和小数部分的分隔符。默认值为点号(.)。

alt grouping-separator:可选属性,用于指定千位分隔符。默认值为逗号(,)。

alt infinity:可选属性,用于指定代表无穷大的字符串。默认值为字符串"Infinity"。

alt minus-sign:可选属性,用于指定代表减号的字符。默认值为中画线(-)。

alt NaN:可选属性,用于指定代表非数的字符串。默认值为字符串"NaN"。

alt percent:可选属性,用于指定代表百分号的字符。默认值为百分号(%)。

alt per-mille:可选属性,用于指定代表千分号的字符。默认值为千分号(#x2030)。

alt zero-digit:可选属性,用于指定代表数字零的字符。默认值为数字零(0)。

alt digit:可选属性,用于指定代表数字的字符。默认值为"#"。

alt pattern-separator:可选属性,用于指定分隔正子样式和负子样式的分隔符。默认值为分号(;)。


alt注意

在某些情况下,format-number()的第二个参数可以指定2个格式化字符串——为所有正数指定一个格式化字符串,为所有负数指定一个格式化字符串,这两个字符串需要一个分隔符来隔开,默认的分隔符是分号(;),开发者也可通过<decimal-format…/>元素的pattern-separator属性来改变该分隔符。


通过上面的介绍可以看出,如果不使用<decimal-format…/>元素,格式化字符串应该由默认的占位符组成。默认的占位符有:“.”、“,”、“#”、“%”、“0”和Unicode千分号字符(#x2030)。

如果format-number()的第一个参数无法转换为数值,则format-number()将返回NaN。如果第二个参数不是有效的格式化字符串,format-number()将直接返回输入值,不作任何更改。

如下XSLT示范了<decimal-format…/>元素和format-number()的作用:

程序清单:codes\08\8.13\format-number.xslt

alt

上面的XSLT中使用<decimal-format…/>元素改变了格式字符串中可用的占位符,并4次调用format-number()函数对数值进行转化,使用该XSLT转换root.xml的结果如图8.33所示。

alt

图8.33 format-number函数的作用

8.13.7 使用key函数

key()函数是一个用于查找XML节点的方法。任何查找操作都至少需要指定如下3方面内容:

alt 查找什么东西?

alt 根据什么属性查找?

alt 查找的目标值是什么?

比如一个最简单的查找——查找名字为“孙悟空”的人,这个查找操作表明了要查找的对象是人,根据名字属性查找,查找的目标值是“孙悟空”。

key()函数的功能也只是进行一次简单的查找,其语法格式如下:

alt

看到上面的语法格式,可能有读者感到疑惑了:刚才不是说一次简单的查找需要3方面内容吗?现在怎么才2个参数呢?这只是XSLT的一个设计问题。key()函数的第2个参数value就代表要查找的目标值;至于第1个参数name,它必须是一个<key…/>元素的name属性值。

也就是说,key()函数必须和<key…/>元素结合使用,<key…/>元素必须作为<stylesheet…/>或<transform…/>元素的子元素使用,使用时可指定如下3个属性:

alt name:为该<key…/>元素指定一个名字,该名字将作为第一个参数传入key()函数。

alt match:用于指定一个节点模式——该节点模式可匹配一批节点。

alt use:用于指定一个XPath表达式。key()将根据该XPath表达式来确定被查找节点。


alt提示

key()函数的第一个参数和<key…/>元素的name属性保持相等,这就建立了key()函数和<key…/>元素之间的关联关系,此时还剩下3个值:

● <key…/>元素的match属性:用于指定本次查找什么东西(XML文档中的一批节点)。

● <key…/>元素的use属性:用于指定本次根据什么属性查找,该属性值既可以是子元素名,也可以是属性名。

● key()函数的第二个参数:用于指定本次查找的目标值。

上面3个值正好满足了一次查找操作所需指定的3个方面。


很多地方喜欢把key()函数称为根据“键”查询,把<key…/>元素称为定义“键”(包括笔者写书过程中参考的W3C和MSDN的文档),但这种说法具有极大的误导性:因为习惯上所说的“键”都指能唯一标识某个实体的属性——也就是当某个“键”值被确定后,该“键”所对应的实体是唯一的。而XSLT中的<key…/>元素中的use属性所指定的XPath表达式并不能唯一地表示match属性所对应的节点,这一点从key()函数的返回值就可以看出:key()函数的返回值是节点集,而不是单个的节点。

在笔者看来,<key…/>元素的作用只是指定需要查找哪一批节点(由match属性指定),以及根据这批节点的什么子元素和什么属性进行查找(由use属性指定)。

假设有如下XML文档:

程序清单:codes\08\8.13\booklist.xml

alt

上面的XML文档中每个<book…/>元素都定义了<isbn…/>子元素,如果希望key()函数可以根据<isbn…/>子元素来查找<book…/>元素,就需要使用<key…/>元素进行定义了。上面的XML文档中每个<book…/>元素都包含一个<pre…/>子元素,用以说明阅读此书之前应先阅读哪本图书。也就是说,该<pre…/>子元素的值会引用另一个<book…/>元素的<isbn…/>子元素的值。

假设需要找出阅读某本图书之前应先阅读哪本图书,则可以使用如下XSLT样式单进行转换:

程序清单:codes\08\8.13\key.xslt

alt

上面的XSLT样式单的第一行粗体字代码定义了根据<isbn…/>子元素的值来查询<book…/>元素的值,第二行粗体字代码利用key()函数进行了查找,找到了<isbn…/>子元素的值等于<pre…/>子元素的值的<book…/>元素,经过该XSLT样式单的转换,可看到图8.34所示结果。

alt

图8.34 使用key()函数进行查找

由于<key…/>元素只是定义根据怎样的属性查找哪批节点,因此XSLT完全允许为一批节点定义多个查找属性,例如如下代码:

alt

8.13.8 使用generate-id函数

generate-id()函数用于为指定节点生成唯一标识名,其语法格式如下:

alt

该函数将为node-set里的第一个节点生成唯一标识名,如果没有指定node-set参数,则为当前上下文节点生成唯一标识名。


alt注意

generate-id()函数生成的标识名由ASCII字母和数字组成,且必定以字母开头,因此该标识名是合法的XML 名称。如果node-set所包含的节点数为0,则返回空字符串。


对于generate-id()函数而言,当它生成唯一标识时具有一定的随机性:两次转换中,generate-id()函数对同一节点生成的唯一标识名往往是不同的,但在同一次转换中,generate-id()函数对同一节点生成的唯一标识名总是相同的。

下面的XSLT样式单示范了generate-id函数的用法:

程序清单:codes\08\8.13\generate-id.xslt

alt

上面的样式单文件中使用generate-id()函数为当前节点(未传入node-set参数)生成了唯一标识,使用该样式单对上面的booklist.xml文件进行转换,结果如图8.35所示。

alt

图8.35 使用generate-id()函数