8.7 创建结果树

前面介绍的XSLT转换都是将XML转换成普通的HTML文档,实际上XSLT转换的结果还可以是另一份XML文档,因此XSLT还提供了多种元素来创建元素、属性、文本、处理指令和注释等元素,通过这些元素来创建结果树,从而得到另一份XML文档。

8.7.1 创建元素和属性

使用<template…/>元素定义模板规则时,该元素里包含的任何静态内容(除了XSLT元素外)都将作为模板内容直接输出,这些模板内容既可以是普通文本内容,也可以是HTML元素。

如果希望模板内容里的元素名也是可变的,可以随XML文档改变,那就只能使用下面的语法了。

8.7.1.1 使用element创建元素

XSLT提供了一个<element…/>元素,用于在结果树中动态地创建元素,所创建的元素不仅内容可变,甚至元素名也是可变的。

使用<element…/>元素时可指定如下几个属性:

alt name:指定所创建的元素名,该属性是必填属性。其值也可以是带命名空间限定的元素名。

alt namespace:指定所创建元素的命名空间URI。如果name属性值指定的元素名包含命名空间的限定前缀,该前缀将被绑定到namespace属性指定的命名空间上。

alt use-attribute-sets:可指定多个命名属性集的名字,多个名字之间以空白隔开。这是使用命名属性集的一种方式。关于命名属性集请参阅8.7.1.4小节的介绍。

假设有如下XML文档:

程序清单:codes\08\8.7\element\element.xml

alt

如果希望将上面的XML文档转换为另一份XML文档,转换后的XML文档以<name…/>元素的内容(疯狂Java讲义)作为元素名,以<price…/>元素的内容作为元素值,可以使用如下XSLT样式单文档:

程序清单:codes\08\8.7\element\element.xslt

alt

经过该XSLT转换后得到的结果文档不是HTML文档,它依旧是一份XML文档,其代码如下(不包含注释内容):

alt

备注:上面的结果文档中的换行和缩进是笔者另外添加的,直接转换得到的结果文本中没有这些换行和缩进。

细心的读者可能已经发现:上面的element.xslt文档中有一个{name},这种用法的作用和<value-of…/>元素类似,都用于输出指定表达式的值。区别在于<value-of…/>元素将根据表达式值创建文本节点,然后再输出该文本节点;而{expression}则用于将表达式的值赋给指定属性,这种用法称为属性值模板。

8.7.1.2 属性值模板

许多时候,我们需要根据XML文档的内容来动态设置结果文档里的属性值,这时使用<value-of…/>元素已无法满足要求,因为它是将指定表达式的值创建成文本节点然后输出——虽然我们看到的转化结果是指定表达式的值,但实际情况是该表达式的值被创建成了文本节点。

使用属性值模板{expression}就可以解决这个问题,其中的expression可以是任意XPath表达式。XSLT处理器会把{expression}当成属性值模板处理,计算expression表达值的值,并将计算得到的属性值赋给结果文档中的指定属性。

例如对于如下XML文档:

程序清单:codes\08\8.7\attribute_template\book.xml

alt

如果对上面的XML文档进行转换时需要动态生成一个超级链接,其href属性值根据<website…/>元素内容产生,则可定义如下XSLT文档:

程序清单:codes\08\8.7\attribute_template\attribute_template.xslt

alt

上面的XSLT文档中的粗体字代码就是一个属性值模板,它会将当前节点集下的website节点的内容输出成href属性的值。从上面的代码可以看出,属性值中除花括号以及里面的表达式外,其他内容都会照原样输出。经过上面的XSLT转换后的结果文档如图8.16所示。

alt

图8.16 使用属性值模板输出属性值

使用属性值模板也有如下限制:

alt 属性值模板不支持嵌套,也就是说不允许在{}里再嵌套{}。

alt 属性值模板不能作为select和match属性的值。

alt 不要使用属性值模板直接输出表达式的值。如果需要直接输出表达式的值,应该使用<value-of…/>元素来完成。

alt 属性值模板不能作为xmlns:xxxPrefix属性的值。

8.7.1.3 使用attribute创建属性

与<element…/>用于创建元素类似,<attribute…/>用于创建属性。如果需要创建的属性名是静态的(无须根据源XML文档改变),通常没必要使用<attribute…/>元素。但如果需要创建的属性名需要根据源XML文档动态地改变,那就需要使用<attribute…/>元素了。

使用<attribute…/>元素可以指定如下属性:

alt name:指定所创建的属性名,该属性是必填属性。其值也可以是带命名空间限定的属性名。

alt namespace:指定所创建属性的命名空间URI。如果name属性值指定的属性名包含命名空间的限定前缀,该前缀将被绑定到namespace属性指定的命名空间上。

<attribute…/>元素通常需要放在其他父元素里使用,表示为其父元素增加指定的属性。通常有如下语法格式:

alt

上面的<attribute…/>元素将为<abc…/>元素增加一个属性,其属性名将根据expression表达式计算得到,其属性值由<attribute…/>元素里的内容指定。

例如对于如下XML文档:

程序清单:codes\08\8.7\attribute\attribute.xml

alt

如果希望结果文档里包含根据该源文档动态改变的属性,那就必须使用<attribute…/>元素,如下所示:

程序清单:codes\08\8.7\attribute\attribute.xslt

alt

上面的粗体字代码中的<attribute…/>元素将为它的父元素(动态创建的<疯狂Java讲义…/>元素)增加一个属性,其属性名为“{name}的价格”——此处有一个属性值模板的用法,属性值为price元素的值,经过该XSLT转换后的结果文档如下所示:

alt

备注:上面的结果文档中的换行和缩进是笔者另外添加的,直接转换得到的结果文本中没有这些换行和缩进。

8.7.1.4 命名属性集

在某些情况下,有多个属性经常放在一起重复使用,这时可将这多个属性定义成一个命名属性集,然后整体添加到指定元素中,这样该元素就可以一次性地拥有该命名属性集里包含的所有属性。

使用命名属性集至少有2个优势:

alt 使用命名属性集能提高代码可复用性。

alt 使用命名属性集能提高代码的可维护性——如果有一天需要改变某个属性定义,只需要修改命名属性集定义即可,无须逐个元素地修改属性定义。

在XSLT中使用<attribute-set…/>元素定义命名属性集,该元素只能作为<stylesheet…/>或<transform…/>的子元素,它可以包含多个<attribute…/>元素,每个<attribute…/>元素定义一个属性,而它则将多个属性组合成一个命名属性集。

使用<attribute-set…/>元素时可指定如下两个属性:

alt name:必填属性,用于指定该命名属性集的名称。

alt use-attribute-sets:可指定多个命名属性集的名字,多个名字之间以空白隔开。该属性用于让当前命名属性集包含另外多个命名属性集里的属性。

使用<attribute-set…/>定义一个命名属性集之后,就可用use-attribute-sets属性将该命名属性集所包含的全部属性一次性添加到其他元素里,该属性可指定多个命名属性集的名字,多个名字之间以空白隔开。

不仅<element…/>、<copy…/>和<attribute-set…/>等XSLT元素可以指定use-attribute-sets属性,XSLT文档中任何普通元素(非XSLT元素)也都可以指定use-attribute-sets属性,只需要添加xsl前缀即可。

下面的XSLT文档示范了命名属性集的用法:

程序清单:codes\08\8.7\attribute-set\attribute-set.xslt

alt

8.7.2 创建文本

XSLT提供了一个专门用于创建文本节点的<text…/>元素,使用这个元素时可指定如下可选的属性:

alt disable-output-escaping:该属性指定是否禁用特殊字符(例如<或&)转义,其默认值是no,也就是启用特殊字符转义,即开发者输出如<或&等特殊字符时,该元素会自动将这些特殊字符转义成对应的实体引用。如果将该属性设为yes,可能导致生成的XML文档不是格式良好的文档。

<text…/>元素的语法格式如下:

alt

<text…/>元素可出现在<comment…/>、<copy…/>、<element…/>、<fallback…/>、<for-each…/>、<if…/>、<otherwise…/>、<message…/>、<param…/>、<processing-instruction…/>和<template…/>等元素中,用于生成文本节点。

大多数情况下,完全没有必要使用这个元素来创建文本节点,直接在模板定义里书写静态内容即可输出成文本节点,而如果需要根据源XML文档的内容动态地生成文本节点,则可以考虑使用<value-of…/>元素。

使用<text…/>元素可以对样式表创建的空白进行一定的控制。例如,为了使样式表更容易阅读,可能需要在模板上每行编写一个元素,并在某些行缩进,这时就可以利用<text…/>元素的功能了,它创建的文本会原封不动地保留每个空白字符。

例如希望由8.7.1.1节里的XML文档转换得到的文档本身就具有良好的换行和缩进,就需要使用<text…/>元素了,如下所示:

程序清单:codes\08\8.7\text\text.xslt

alt

上面的<text…/>元素仅仅用于创建一些换行和空白,通过这些创建的换行和空白,就可让转换得到的结果文档具有良好的格式和缩进。

8.7.3 创建处理指令

XSLT提供了<processing-instruction…/>元素用于创建处理指令,使用该元素时可指定如下属性:

alt name:指定处理指令的指令名。

使用<processing-instruction…/>元素的语法格式如下:

alt

<processing-instruction…/>元素的name属性负责创建处理指令的指令名,其元素内容负责提供处理指令的其他部分。

例如下列代码可在结果文档中创建一条处理指令:

程序清单:codes\08\8.7\pi\pi.xslt

alt

上面的XSLT定义将在结果文档中创建如下处理指令:

alt

如上所示,处理指令名是crazyit,由<processing-instruction…/>元素的name属性指定,处理指令的内容正是<processing-instruction…/>元素所包含的内容。

需要指出的是:处理指令的结束标记是问号和大于号(?>),因此不要在<processing-instruction…/>元素的内容里包含问号和大于号。

8.7.4 创建注释

如果需要在结果文档里创建注释,可以使用<comment…/>元素,该元素无须任何属性,用法非常简单,下面是<comment…/>元素的语法格式:

alt

如下XSLT片段可创建一条注释:

alt

上面的<comment…/>元素生成的注释既有静态内容,也有随源XML文档改变的动态部分——总之,放在<comment…/>元素内的内容都将变成注释。

上面的<comment…/>元素转换后可得到如下注释:

alt

由此可见,XSLT转换器会自动在<comment…/>所包含的内容之前添加<!—,之后添加—>,从而生成结果文档中的注释。需要指出的是,由于XML文档中的注释有如下限制:

alt 注释中不能出现双中画线(—)。

alt XML注释不能以—->结尾。

因此定义<comment…/>元素内容时也要注意如下两点:

alt <comment…/>元素内容中不要出现双中画线(—)。

alt <comment…/>元素内容不要以-结尾。


alt注意

虽然有些XSLT处理器可以自动“智能”地处理上面两种情形,但也只是部分XSLT转换器的“个别行为”,因此实际开发中应该尽量避免出现以上两种情况。


8.7.5 复制

XSLT还允许将目标文档中的节点直接复制到结果文档中,XSLT为这种复制操作提供了如下两个元素:

alt <copy…/>:将源文档中的当前节点复制到结果文档中。

alt <copy-of…/>:将源文档里选中的节点集复制到结果文档中。

由于<copy…/>元素只是将源文档里的当前节点复制到目标文档中,因此该元素的用法比较简单,使用时只有一个可选的属性:

alt use-attribute-sets:可指定多个命名属性集的名字,多个名字之间以空白隔开。

使用<copy…/>元素的语法格式如下:

alt

使用<copy…/>元素复制源文档中的当前节点时必须注意:<copy…/>元素仅仅复制当前元素本身(包括该元素所对应的命名空间属性),而它所包含的其他属性、子元素和文本内容都不会复制到结果文档中。

例如对于如下XML文档:

程序清单:codes\08\8.7\copy\copy.xml

alt

下面定义一个XSLT文档将上面的<book…/>元素复制到结果文档中,复制<book…/>元素时既不会复制其中的isbn属性,也不会复制它所包含的任何内容。下面是进行转换的XSLT文档:

程序清单:codes\08\8.7\copy\copy.xslt

alt

如上所示,XSLT定义复制源文档中的当前节点(<book…/>元素)时,还额外指定了一个new-isbn属性和节点内容,否则结果文档中的<book…/>元素将是一个不包含任何属性的空元素。上面的XML文档经过该XSLT转换后的结果如下:

alt

备注:上面的结果文档中的格式和缩进是笔者另外添加的,实际转换得到的结果文档并没有这么规范的格式。

<copy-of…/>元素比<copy…/>元素的功能强得多,它需要指定一个必选的select属性,该属性的值是一个XPath表达式,<copy-of…/>会将select属性匹配的每个节点复制到结果文档中。<copy-of…/>不仅会复制select属性匹配的每个元素,还会复制该元素所包含的全部属性和子元素。

假设有如下XML文档:

程序清单:codes\08\8.7\copy-of\copy-of.xml

alt

下面的XSLT文档将使用<copy-of…/>元素复制上面的XML文档中的<name…/>元素,<copy-of…/>不仅会复制<name…/>元素本身,还会复制它所包含的isbn属性和<content…/>子元素:

程序清单:codes\08\8.7\copy-of\copy-of.xslt

alt

上面的XSLT文档中的<copy-of…/>元素会将源XML文档里的3个<name…/>元素复制到结果文档内,转换后得到的结果文档如下:

alt

8.7.6 输出格式化数值

XSLT提供了<number…/>元素用于输出格式化数值,该元素除了可以将指定数值格式化输出之外,还有一个更常用的功能是进行统计计数。<number…/>元素的用法有点复杂,因为它支持的属性非常多。下面是<number…/>元素的语法格式:

alt

<number…/>元素中的各属性的意义如下:

alt value:指定一个表达式,<number…/>元素将输出该表达式所对应的值。<number…/>元素会自动将该值舍入到整数,然后转换为字符串并插入结果树。

alt format:指定序列格式。该属性支持表8.1所示的值:

表8.1 序列格式以及生成的序列值

alt

alt lang:指定数值序列所使用的语言。如果不指定lang值,将根据系统环境确定语言。

alt letter-value:指定使用不同的字符序列来消除歧义。

alt grouping-separator:指定十进制数中的分组(例如千位分隔)分隔符,例如grouping-separator=","且grouping-size="3",则生成的数字格式将为1,000,000。

alt grouping-size:指定十进制数里的分组长度(通常为3,作为千位分隔符使用)。


alt注意

grouping-separator 和grouping-size 两个属性必须同时指定才有效,如果只指定其中一个,XSLT将忽略该设定。


alt level:指定对源XML文档中的元素以怎样的方式生成数值序列。默认值为single。

alt count:指定将不同类型的元素放在一起生成数值序列。

alt from:指定从哪个元素起重新开始生成数值序列。

假设有如下XML文档:

程序清单:codes\08\8.7\number\number.xml

alt

上面的XML文档中的<list…/>元素里包含了3个<book…/>元素,下面先使用<number…/>元素来格式化该文档中的<price…/>元素,代码如下:

程序清单:codes\08\8.7\number\number1.xslt

alt

上面的XSLT中使用<number…/>元素时仅指定了一个value属性,在这种用法下<number…/>元素的作用和<value-of…/>相似,都是输出指定表达式所代表的数值。应用该XSLT转换上面的XML文档,用浏览器浏览可看到图8.17所示结果:

alt

图8.17 使用number进行简单格式化

下面再增加一个不指定value属性的<number…/>元素,并结合grouping-separator和grouping-size两个属性为数值增加分隔符。代码如下:

程序清单:codes\08\8.7\number\number2.xslt

alt

上面的XSLT文档中两处使用了<number…/>元素来输出格式化数值。第1个<number…/>元素没有指定value属性值,因此该元素将直接生成一个数值序列。第2个<number…/>元素指定了value属性值,并指定了grouping-separator和grouping-size两个属性值,因而可以在输出格式化数值时每3个数字使用一个分隔符进行分隔。使用浏览器浏览上面的XML文档,可看到图8.18所示结果:

alt

图8.18 输出格式化数值

为了讲解level、count和from这3个属性的用法,下面先定义如下XML文档:

程序清单:codes\08\8.7\number\list.xml

alt

在上面的XML文档中,<item…/>元素里又嵌套了<item…/>元素,这样才可以很好地体现level属性的作用。

假设使用如下XSLT进行转换:

程序清单:codes\08\8.7\number\number3.xslt

alt

上面的程序中的粗体字代码定义了一条输出格式化数值的语句,这条语句将输出一个简单的数值,因为这条输出格式化数值的语句会对所有<item…/>元素生效,所以使用该XSLT转换后将得到如下结果:

从图8.19中可以看出,<number…/>元素可以对所有<item…/>元素进行统计,并输出对应的数值序列,而且它可以“智能”地只将同一级的<item…/>元素放在一起统计——所以我们看到每个<item…/>元素下的多个<item…/>子元素都拥有形如1、2、3的数值序列。这种效果就是由level属性控制的,level属性的默认值是single,也就是说XSLT会单独统计每个<item…/>元素,并为它们生成单独的数值序列。

alt

图8.19 level="single"的结果

如果想让<number…/>元素把所有被统计元素放在一起统计,只生成一个连续的数值序列,可以指定level为any,此时可看到图8.20所示结果。

alt

图8.20 level="any"的结果

如果想让<number…/>元素进行统计时更加“智能”,不仅可以对所有被统计元素进行“分级单独统计”,还可以让父元素的数值序列自动添加到子元素的数值序列之前,可以指定level="multiple",此时可看到图8.21所示结果。

alt

图8.21 level="multiple"的结果

默认情况下,<number…/>元素只统计当前元素——例如在<item…/>元素对应的模板里定义<number…/>元素,那它将只对<item…/>元素起作用。如果希望XSLT将更多元素放在一起统计,可以使用count属性,该属性可指定多个需要被统计的元素名,多个元素名之间以竖线(|)分隔。例如将上面的<number…/>元素改为如下形式:

程序清单:codes\08\8.7\number\number4.xslt

alt

使用该XSLT转换前面的XML文档,在浏览器中浏览将看到图8.22所示结果。

alt

图8.22 count="item | type | list"的结果

from则用于指定<number…/>每次遇到某个元素就开始重新统计——也就是原有的统计清零,例如将上面的XSLT中的<number…/>改为如下形式:

程序清单:codes\08\8.7\number\number5.xslt

alt

使用浏览器浏览,可看到图8.23所示结果。

alt

图8.23 指定from="type"的结果