 | 级别: 高级 Doug Tidwell (dtidwell@us.ibm.com), 技术宣传人员, IBM
2007 年 12 月 10 日 XSLT 2.0 的一项重要改进是更好的国际化支持,特别是排序和比较文本。这个看起来很简单的任务在某些语言中非常复杂,比如,上下文的不同决定着是否要考虑重音字符。
Á
、
À
和 A 是同一个字符吗?有时候必须当作一个字符,尽管实际上是三个不同的代码点。多数语言(包括 XSLT 1.0)中的简单字符串比较函数不足以承担这项任务。本文通过例子说明如何编写自定义的排序函数,然后从 XSLT 2.0 样式表中调用它。
自定义排序
本文将用到 XSLT 2.0 和 XPath 2.0 的一些新特性。XSLT 2.0 有很多函数和元素允许指定排序。排序是任何排序算法的核心。排序函数 比较两个项返回三个值中的一个。如果前项在后项之前,函数返回一个小于零的值。如果两个项相同,函数返回零。当然,如果前项在后项之后,返回值就大于零。
本文中的例子采用基于 Java 的 Saxon XSLT 2.0 处理程序。Saxon 实现了 XSLT 2.0 规范(其作者 Michael Kay 是 XSLT 2.0 规范的编辑),包括自定义排序。要在 Saxon 中使用自定义排序,需要指定实现排序函数的 Java 类名。
我们将举三个例子:
- 排序一组西班牙文单词
- 比较德语词汇
- 排序一组乐队和音乐家,忽略乐队名称开始位置的文本 “
The
”
排序西班牙语词汇
 |
关于排序西班牙语和德语的说明
作者不会说西班牙语或德语,因此如果关于这两种语言本身的说法不当,还请谅解。这里的目的是说明如何创建实现自定义排序的扩展,然后在样式表中使用这些扩展排序和比较文本。
本文将实现的传统西班牙语排序,分别将 ch、ll 和 ? 视作分别排在 c、l 和 n 之后的独立字符。但是,大部分说西班牙语的人士现在使用 Association of Spanish Language Academies (La Asociación de Academias de Lingua Espa?ola) 定义的新排序。现代西班牙语排序认为 ch、ll 不属于特殊字符;而是按照英语中的办法排序。字符 ? 仍然排在 n 之后。
德语的排序有三种排序可供选择:DIN-1、DIN-2 和 Austrian。(DIN
标准由标准化组织 Deutsches Institut
Für Normung 定义。)不同的国家采用不同的排序。DIN-1 通常用于排序单词,但是瑞士也用这种顺序排序姓名。本文将实现的 DIN-2 排序算法在德国用于排序姓名。奥地利采用 Austrian 排序,虽然这种方法面临着被 DIN-2 替代的危险。这里要实现的 DIN-2
算法,主要难度在于 ? 等同于 ae、? 等同于 oe、? 等同于 ss、ü 等同于 ue。代码必须能够识别出一个词中的两个字符相当于另一个词中的一个字符。
|
|
第一个自定义排序器用于排序西班牙语单词。西班牙语的字母表包含 30 个字符,除了西欧语言的 26 个基本字母外,ch (che)、ll (elle)、
ñ
(eñe) 和 rr (erre) 也被看作是单独的字符。按照传统的西班牙语排序法,以 ch 开始的单词都排在以 cz 开始的单词之后,以 ll 开始的单词都排在以 lz 开始的单词之后,以
ñ
开始的单词都排在以 n 开始的单词之后。字符 rr 没有固定的排序顺序。下面的西班牙语自定义排序实现了这些规则。
下面是一组西班牙语词汇:
清单 1. 西班牙语词汇
<?xml version="1.0"?>
<!-- spanish-words.xml -->
<wordlist>
<word>campo</word>
<word>luna</word>
<word>ciudad</word>
<word>llaves</word>
<word>chihuahua</word>
<word>arroz</word>
<word>limonada</word>
</wordlist>
|
实现一种自定义排序算法似乎很难。所幸的是,Java 定义了类 RuleBasedCollator。只需要定义说明字符排序顺序的规则字符串,剩下的工作交给 Java 就行了。创建扩展 RuleBasedCollator 的 Java 类,必须编写规则字符串和构造函数。代码如下,其中包括规则字符串:
清单 2. 西班牙语排序器源代码
package com.oreilly.xslt;
import java.text.ParseException;
import java.text.RuleBasedCollator;
public class SpanishCollation extends RuleBasedCollator
{
public SpanishCollation() throws ParseException
{
super(traditionalSpanishRules);
}
private static String smallnTilde = new String("\u00F1");
private static String capitalNTilde = new String("\u00D1");
private static String traditionalSpanishRules =
("< a,A < b,B < c,C < ch, cH, Ch, CH " +
"< d,D < e,E < f,F < g,G < h,H < i,I " +
"< j,J < k,K < l,L < ll, lL, Ll, LL " +
"< m,M < n,N " +
"< " + smallnTilde + "," + capitalNTilde + " " +
"< o,O < p,P < q,Q < r,R < s,S < t,T " +
"< u,U < v,V < w,W < x,X < y,Y < z,Z");
}
|
使用规则字符串定义字符排序的顺序。要注意 清单 2 中很多成对的小写和大写字符。中间的小于号表示 a 和 A 出现在 b 和 B 之前。和字符组一起定义的还有 che 和 elle,尽管它们是两个字符而不是一个。当 Java 运行时排序的时候,这些规则就告诉它 ll 应作为 l 和 m. 之间的单独字符处理。
另两个特殊规则表明 ch 的所有大小写组合都排在 c 和 d 之间,
ñ
和
Ñ
排在 n 和 o 之间。
定义了排序西班牙语单词的规则之后,现在可以在 XSLT 2.0 样式表中使用它了。XSLT 2.0 允许在样式表的很多地方调用自定义排序。比如,<xsl:sort> 元素新增的 collation 属性。但 XSLT 2.0 规范没有 定义如何利用该属性的内容。Saxon 处理程序要求该属性采用如下格式:
collation="http://saxon.sf.net/collation?class=com.oreilly.xslt.SpanishCollation;"
|
Saxon 要求自定义排序类的名称以 http://saxon.sf.net/collation? 开始,再加上关键字 class= 和完全限定的类名。这个类在运行时加载,必须能够在 Java 类路径中访问。如果其他的 XSLT 2.0 处理程序也支持自定义排序(据我所知,到 2007 年 10 月还没有),collation 属性的格式可能不同。
调用 com.oreilly.xslt.SpanishCollation 类的样式表如下:
清单 3. XSLT 2.0 样式表调用排序类
<?xml version="1.0"?>
<!-- custom-collation-spanish.xsl -->
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="html"/>
<xsl:variable name="words" as="xs:string*"
select="wordlist/word"/>
<xsl:variable name="normally_sorted_words" as="xs:string*">
<xsl:perform-sort select="$words">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<xsl:variable name="usefully_sorted_words" as="xs:string*">
<xsl:perform-sort select="$words">
<xsl:sort select="."
collation="http://saxon.sf.net/collation?class=com.oreilly.xslt.SpanishCollation;"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:template match="/">
<html>
<head>
<title>Sorting with a custom collation</title>
</head>
<body style="font-family: sans-serif; font-size: 12pt;">
<h1>Sorting with a custom collation</h1>
<p>Here is a table that uses a <i>custom collation</i>
to sort words according to the traditional rules of
Spanish.</p>
<table cellpadding="5" width="50%"
style="font-weight: bold;">
<tr style="font-size: 120%; font-style: italic;
text-align: center;">
<td>Original words</td>
<td>Normally sorted words</td>
<td>Spanish sorted words</td>
</tr>
<xsl:for-each select="1 to count($words)">
<tr style="background: {if (. mod 2 = 1)
then 'gray' else 'white'};
color: {if (. mod 2 = 1)
then 'white' else 'black'};">
<td style="border: solid white 6px;">
<xsl:value-of
select="., '. ', subsequence($words, ., 1)"
separator=""/>
</td>
<td style="border: solid white 6px;">
<xsl:value-of
select="index-of($words,
subsequence($normally_sorted_words, ., 1))"/>
<xsl:text>. </xsl:text>
<xsl:value-of
select="subsequence($normally_sorted_words, ., 1)"/>
</td>
<td style="border: solid white 6px;">
<xsl:value-of
select="index-of($words,
subsequence($usefully_sorted_words, ., 1))"/>
<xsl:text>. </xsl:text>
<xsl:value-of
select="subsequence($usefully_sorted_words, ., 1)"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
|
用下面的命令调用样式表引擎:
java net.sf.saxon.Transform spanish-words.xml custom-collation-spanish.xsl
|
该命令将结果输出到标准输出。如果希望把结果保存到文件中,可使用 -o 选项。命令 java net.sf.saxon.Transform -o results.html words.xml custom-collation-spanish.xsl 把结果保存到文件 results.html 中。
样式表创建三个字符串序列。第一个是 XML 输入文件中的单词列表。第二个是根据默认排序算法排序的词汇列表。第三个使用自定义的西班牙语排序函数。<xsl:for-each> 元素将单词列表写入 HTML 表格。得到的 HTML 文档如图 1 所示:
图 1. 用两种不同方式排序的词汇
这里要注意几个 XSLT 2.0 特点。首先,使用 <xsl:perform-sort> 元素对序列变量中的项排序。它告诉 XSLT 2.0 处理程序对序列中的值执行 <xsl:sort> 元素。使用 <xsl:sort> 调用自定义排序类。
<xsl:for-each> 元素从 1 开始,遍历单词列表中的每个词。表格中每隔一行用灰色背景白色文本。要创建这种代码,在属性值模板(放在花括号 { } 中的代码)中使用上下文项的值。每个表行的定义如下:
<tr style="background: {if (. mod 2 = 1)
then 'gray' else 'white'};
color: {if (. mod 2 = 1)
then 'white' else 'black'};">
|
如果上下文项除 2 余 1,样式表就生成 CSS 代码 background: gray; color: white;。否则,当前行的单元格显示白底黑字。mod 运算符特别适合循环遍历一组值。
最后,还要注意第一栏中的数字表示单词在 XML 源文档中出现的次序。另外两栏中,无论单词出现在什么位置,但这个数字是不变的。比如,chihuahua 是原来列表中的第五个单词,因此旁边的数字都是 5。单词 chihuahua 在第 2 栏中出现在第三行,第 3 栏中出现在第四行,但显示的数字都是 5。实现方法如下:
<td style="border: solid white 6px;">
<xsl:value-of
select="index-of($words,
subsequence($normally_sorted_words, ., 1))"/>
<xsl:text>. </xsl:text>
<xsl:value-of
select="subsequence($normally_sorted_words, ., 1)"/>
</td>
|
每个单元格都包括三部分内容:数字、句点和当前的单词。生成数字比较麻烦。对于第 2 和第 3 栏(下面的代码是第 2 栏),必须确定单词在原来列表中的位置(保存在变量 $words 中)。为此使用了 index-of() 和 subsequence() 函数。使用 subsequence() 从排序后的序列中检索当前的单词。subsequence() 的第二个参数是开始位置,点号表示上下文项。第三个参数是返回的项的个数。对于所针对的单词,index-of() 返回单词在原来序列中的位置。
用德语排序比较字符串
下一个例子使用自定义排序器比较德语词汇。这里的困难在于四个德语字符的表示不只一种(注意大小写):
| 单字符 | 等价的双字符 |
|---|
ä
(元音变音的 a)或
Ä
(元音变音的 A) |
ae 或
AE
|
ö
(元音变音的 o)或
Ö
(元音变音的 O) |
oe 或
OE
|
ß
(不发音的 s) |
ss
|
ü
(元音变音的 u)或
Ü
(元音变音的 U) |
ue 或
UE
|
有时候需要把这些字符看成是一样的。换句话说,Strasse(街道)和 Straße 是一样的。显然使用逐字符比较的标准排序法肯定会说两者不同,因此需要使用自定义的排序法。
和西班牙语排序法一样,只要定义比较字符的规则即可:
清单 4. 德语自定义排序法的源代码
package com.oreilly.xslt;
import java.text.ParseException;
import java.text.RuleBasedCollator;
public class GermanCollation extends RuleBasedCollator
{
public GermanCollation() throws ParseException
{
super(traditionalGermanRules);
}
private static String sharpS = new String("\u00DF");
private static String uppercaseUmlautA = new String("\u00C4");
private static String lowercaseUmlautA = new String("\u00E4");
private static String uppercaseUmlautO = new String("\u00D6");
private static String lowercaseUmlautO = new String("\u00F6");
private static String uppercaseUmlautU = new String("\u00DC");
private static String lowercaseUmlautU = new String("\u00FC");
private static String traditionalGermanRules =
("< a,A " +
"<" + lowercaseUmlautA + "=ae " +
"<" + uppercaseUmlautA + "=AE " +
"< b,B < c,C < d,D < e,E < f,F " +
"< g,G < h,H < i,I < j,J < k,K " +
"< l,L < m,M < n,N < o,O " +
"<" + lowercaseUmlautO + "=oe " +
"<" + uppercaseUmlautO + "=OE " +
"< p,P < q,Q < r,R < s,S " +
"< ss=" + sharpS +
"< t,T < u,U " +
"<" + lowercaseUmlautU + "=ue " +
"<" + uppercaseUmlautU + "=UE " +
"< v,V < w,W < x,X < y,Y < z,Z");
}
|
清单 4 中的代码使用等号表示某些字符和字符组是等价的。(西班牙语排序器使用了小于号。)规则字符串包含 ...s,S < ss=ß < t, T ... 之类的项。它告诉排序器双字符 ss 等价于单字符
ß
。(代表特殊字符的字符串使得代码看起来更清楚。)
样式表如下:
清单 5. 使用德语排序函数的样式表
<?xml version="1.0"?>
<!-- custom-collation-german.xsl -->
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="html"/>
<xsl:variable name="wordgroup1" as="xs:string*"
select="wordlist/wordgroup/word[1]"/>
<xsl:variable name="wordgroup2" as="xs:string*"
select="wordlist/wordgroup/word[2]"/>
<xsl:template match="/">
<html>
<head>
<title>Comparing words with a custom collation</title>
</head>
<body style="font-family: sans-serif; font-size: 12pt;">
<h1>Comparing words with a custom collation</h1>
<p>This table illustrates what happens when you use
a <i>custom collation</i> to compare German words:</p>
<table cellpadding="5" width="50%"
style="font-weight: bold; text-align: center;">
<tr style="font-size: 120%; font-style: italic;
vertical-align: bottom;">
<td>First word</td>
<td>Second word</td>
<td>Compared normally</td>
<td>Compared with <br/>German (DIN-2) rules</td>
</tr>
<xsl:for-each select="1 to count($wordgroup1)">
<xsl:variable name="word1"
select="subsequence($wordgroup1, ., 1)"/>
<xsl:variable name="word2"
select="subsequence($wordgroup2, ., 1)"/>
<tr style="background: {if (. mod 2 = 1)
then 'gray' else 'white'};
color: {if (. mod 2 = 1)
then 'white' else 'black'};">
<td style="border: solid white 6px;">
<xsl:value-of select="$word1"/>
</td>
<td style="border: solid white 6px;">
<xsl:value-of select="$word2"/>
</td>
<td style="border: solid white 6px;">
<xsl:value-of
select="if (compare($word1, $word2) = 0)
then 'Equal'
else 'Not equal'"/>
</td>
<td style="border: solid white 6px;">
<xsl:value-of
select="if (compare($word1, $word2,
'http://saxon.sf.net/collation?class=com.oreilly.xslt.GermanCollation;')
= 0)
then 'Equal'
else 'Not equal'"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
|
样式表和西班牙语排序的例子很相似。主要的区别在于直接使用了 compare() 函数。列出的德语单词如下:
清单 6. 进行比较的德语单词
<?xml version="1.0"?>
<!-- german-words.xml -->
<wordlist>
<wordgroup>
<word>Berlin</word>
<word>Stuttgart</word>
</wordgroup>
<wordgroup>
<word>Stra?e</word>
<word>Strasse</word>
</wordgroup>
<wordgroup>
<word>B?blingen</word>
<word>Boeblingen</word>
</wordgroup>
<wordgroup>
<word>München</word>
<word>Muenchen</word>
</wordgroup>
</wordlist>
|
每个 <wordgroup> 包含两个单词。前两组单词不同,但其他各组按照德语排序法是相同的。样式表生成两个序列。第一个序列($wordgroup1)包含全部第一个 <word> 元素,第二个序列($wordgroup2)包含全部第二个单词。在 <xsl:for-each> 元素中,样式表迭代遍历这两个单词序列。对于表中的每一行,两个单词分别存储在 $word1 和 $word2 变量中。
调用样式表的命令如下:
java net.sf.saxon.Transform german-words.xml custom-collation-german.xsl
|
结果如图 2 所示:
图 2. 用德语规则比较单词
自定义字符串排序器
最后一个例子对一些乐队和音乐家排序。需要自定义排序器在排序的时候忽略文本 “The
”。换句话说,把 The
Beatles 当作 Beatles 来排序。无法通过一组规则实现这个目的,因为无法在希望按照特殊方式排序的字符中包含空格。也不能删除 The 来排序,因为它可能成为乐队名称的一部分,如 Them 或 They Might Be Giants。另一个困难来自 The The 这样的乐队名称。排序的时候需要无视第一个 “The
”。
为了进一步增加难度,我们要求对艺术家姓名排序时忽略大小写。如果乐队叫 the
e.e. cummingses,需要按名称中包含大写 E 的艺术家的姓名来排序。
还要保证只有出现在字符串开始位置的字符才能忽略。换句话说,处理 Toad
the Wet Sprocket 的时候不能删除字符 “the
”。虽然只有当错误地赋予 Toad the Wet Sprocket 某种音乐特点的时候才会出现这种问题,但代码应该非常健壮,仅当出现在字符串的开始时,才能忽略字符 “The
”。
需要忽略的字符串是 “The
” 这三个字符。不能使用 RuleBasedCollator,因为需要忽略这些字符,而不是定义其在排序中的位置。好消息是可以利用 Java Comparator 接口。更妙的是,需要实现的方法只有一个,即 compare()。自定义排序扩展函数如下:
清单 7. 自定义排序器的源代码
package com.ibm.dw;
import java.util.Comparator;
public class TheTheCollation implements Comparator<String>
{
public int compare(String stringOne, String stringTwo)
{
return stringOne
.replaceFirst("^(T|t)(H|h)(E|e) ", "")
.compareToIgnoreCase(stringTwo.replaceFirst("^(T|t)(H|h)(E|e) ", ""));
}
}
|
代码非常简单,只需要覆盖 compare() 函数。给定两个字符串,如果需要删除 “The
”,那么调用原来的 Java 比较函数。由于需要忽略大小写,使用了 compareToIgnoreCase() 而不是更基本的 compare()。
String.replaceFirst() 函数删除了字符串开始位置的 “The
”。(如您所见,类名中也包含有 The。)关键在于 replaceFirst() 函数的第一个参数是一个正则表达式。正则表达式 "^(T|t)(H|h)(E|e) " 仅和出现在字符串开始位置的 “The
” 匹配,正则表达式中的 ^ 符号指向字符串的开头。括号中的成对字符表示大小写的任意组合。这是避免组合使用 String.startsWith() 和 String.substring() 这些函数的一种漂亮的解决方法。
乐队和音乐家的清单如下:
清单 8. 乐队和音乐家列表
<?xml version="1.0"?>
<!-- artists.xml -->
<artistlist>
<artist>The Clash</artist>
<artist>They Might Be Giants</artist>
<artist>Eminem</artist>
<artist>The Whigs</artist>
<artist>X</artist>
<artist>Talking Heads</artist>
<artist>The Rutles</artist>
<artist>Them</artist>
<artist>The Yardbirds</artist>
<artist>the e.e. cummingses</artist>
<artist>Romeo Void</artist>
<artist>The B-52's</artist>
<artist>B. B. King</artist>
<artist>The The</artist>
<artist>Beastie Boys</artist>
<artist>The Beatles</artist>
</artistlist>
|
其中八位艺术家属于名称以 “The” 开始的乐队。在唱片店里寻找 The Beatles 乐队的作品时,不应该到 “T” 下寻找。需要一种自定义排序函数,它认为 The Beatles 应该出现在 Eminem 之前。
样式表基本上和第一个相同,主要的区别是使用了不同的自定义排序器。调用 Java 类的样式表片段如下:
<xsl:variable name="usefully_sorted_artists" as="xs:string*">
<xsl:perform-sort select="$artists">
<xsl:sort select="."
collation="http://saxon.sf.net/collation?class=com.ibm.dw.TheTheCollation;"/>
</xsl:perform-sort>
</xsl:variable>
|
用下面的命令调用样式表引擎:
java net.sf.saxon.Transform artists.xml custom-collation-thethe.xsl
|
结果如图 3 所示:
图 3. 按两种方法排序的艺术家
最后一点改进:增加鼠标效果
在每一栏中写出各项的最初位置是说明排序方式不同的好办法。作为最后一个例子,我们看如何改进样式表,当鼠标移到一栏中某位艺术家的姓名上时突出显示其他两栏中该艺术家的姓名。在生成的 HTML 页面中实现这项功能需要三步:
- 按照命名惯例为每个单元格增加一个 ID。
- 为每个单元格定义
id、onmouseover 和 onmouseout 属性。
- 定义当鼠标移动到单元格中突出显示和移出单元格取消突出显示的 JavaScript 函数。
需要靠单元格的 ID 确定适当的单元格。ID 采用 col1-1、col2-1 和 col3-1 的格式。ID
为 col1-1 的单元格是第一列中的第一项。ID 为 col2-1 和 col3-1 的单元格分别是第 2 和第 3 列中具有相同文本的单元格。就是说第 2 和第 3 列中每个单元格的 ID 的最后一个数字都和为该单元格本身生成的数字相同。该例中,The Clash 是 XML 文件中的第一位艺术家,因此所有 The Clash 的编号都是 1。包含 The Clash 的单元格的 ID 是 col1-1、col2-1 和 col3-1。
现在知道了 ID 的组成,可以编写 onmouseover 和 onmouseout 属性来使用 ID 的最后一部分。创建两个 JavaScript 函数,highlightCells() 和 unhighlightCells()。给定数字 3,highlightCells() 将突出显示 ID 为 col1-3、col2-3 和 col3-3 的三个元素。生成的 HTML 文档中的表行如下:
<td id="col1-1"
onmouseover="highlightCells('1');"
onmouseout="unhighlightCells('1');"
style="border: solid white 6px;">1. The Clash</td>
|
当然还需要编写 JavaScript 函数。如下所示:
清单 9. 突出显示单元格的 JavaScript 函数
<title>Sorting with a custom collation</title><script language="JavaScript">
<!--
function highlightCells(rowNum)
{
el = document.getElementById('col1-' + rowNum);
el.style.border='solid #E15119 6px';
el = document.getElementById('col2-' + rowNum);
el.style.border='solid #E15119 6px';
el = document.getElementById('col3-' + rowNum);
el.style.border='solid #E15119 6px';
}
function unhighlightCells(rowNum)
{
el = document.getElementById('col1-' + rowNum);
el.style.border='solid white 6px';
el = document.getElementById('col2-' + rowNum);
el.style.border='solid white 6px';
el = document.getElementById('col3-' + rowNum);
el.style.border='solid white 6px';
}
--></script></head>
|
JavaScript 代码使用 getElementById()
函数查找给定 ID 的元素。突出显示的单元格的边框颜色设成 developerWorks 采用的橙色阴影。取消突出显示的时候将单元格边框颜色设置为白色。图 4 显示了把鼠标放在包含 The Clash 的单元格上时的效果:
图 4. 突出显示不同列中艺术家的 JavaScript 效果
完整的样式表如下所示:
清单 10. 生成鼠标特效的 XSLT 样式表
<?xml version="1.0"?>
<!-- custom-collation-advanced.xsl -->
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="html"/>
<xsl:variable name="artists" as="xs:string*"
select="artistlist/artist"/>
<xsl:variable name="normally_sorted_artists" as="xs:string*">
<xsl:perform-sort select="$artists">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<xsl:variable name="usefully_sorted_artists" as="xs:string*">
<xsl:perform-sort select="$artists">
<xsl:sort select="."
collation="http://saxon.sf.net/collation?class=com.ibm.dw.TheTheCollation;"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:template match="/">
<html>
<head>
<title>Sorting with a custom collation</title>
<script language="JavaScript"><xsl:comment>
function highlightCells(rowNum)
{
el = document.getElementById('col1-' + rowNum);
el.style.border='solid #E15119 6px';
el = document.getElementById('col2-' + rowNum);
el.style.border='solid #E15119 6px';
el = document.getElementById('col3-' + rowNum);
el.style.border='solid #E15119 6px';
}
function unhighlightCells(rowNum)
{
el = document.getElementById('col1-' + rowNum);
el.style.border='solid white 6px';
el = document.getElementById('col2-' + rowNum);
el.style.border='solid white 6px';
el = document.getElementById('col3-' + rowNum);
el.style.border='solid white 6px';
}
</xsl:comment></script>
</head>
<body style="font-family: sans-serif; font-size: 12pt;">
<h1>Sorting with a custom collation</h1>
<p>Here is a table that uses a <i>custom collation</i>
to sort data ignoring the characters
<span style="font-family: monospace;">The </span>
at the start of the data:</p>
<table cellpadding="5" width="50%"
style="font-weight: bold;">
<tr style="font-size: 120%; font-style: italic;
text-align: center;">
<td>Original data</td>
<td>Normally-sorted data</td>
<td>Usefully-sorted data</td>
</tr>
<xsl:for-each select="1 to count($artists)">
<tr style="background: {if (. mod 2 = 1)
then 'gray' else 'white'};
color: {if (. mod 2 = 1)
then 'white' else 'black'};">
<xsl:variable name="col2Index"
select="index-of($artists,
subsequence($normally_sorted_artists, ., 1))"/>
<xsl:variable name="col3Index"
select="index-of($artists,
subsequence($usefully_sorted_artists, ., 1))"/>
<td id="col1-{.}"
onmouseover="highlightCells('{.}');"
onmouseout="unhighlightCells('{.}');"
style="border: solid white 6px;">
<xsl:value-of
select="., '. ', subsequence($artists, ., 1)"
separator=""/>
</td>
<td id="col2-{$col2Index}"
onmouseover="highlightCells('{$col2Index}');"
onmouseout="unhighlightCells('{$col2Index}');"
style="border: solid white 6px;">
<xsl:value-of select="$col2Index"/>
<xsl:text>. </xsl:text>
<xsl:value-of
select="subsequence($normally_sorted_artists, ., 1)"/>
</td>
<td id="col3-{$col3Index}"
onmouseover="highlightCells('{$col3Index}');"
onmouseout="unhighlightCells('{$col3Index}');"
style="border: solid white 6px;">
<xsl:value-of select="$col3Index"/>
<xsl:text>. </xsl:text>
<xsl:value-of
select="subsequence($usefully_sorted_artists, ., 1)"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
|
用下面的命令调用样式表引擎:
java net.sf.saxon.Transform artists.xml custom-collation-advanced.xsl
|
请注意,JavaScript 代码是用 <xsl:comment> 元素生成的。样式表输出 <script> 元素,紧跟着输出注释内容。注释的最后再加上 <script> 元素。过去,一些浏览器把注释结束标记(-->)解释成自减运算符而发生间断性错误。这样编写样式表可以避免这种错误。
结束语
Java 提供了非常强大的类,很容易改变排序的方式。本文编写了支持西班牙语、德语和特定领域排序类型的三个 Java 类。这三个类基本上只需要一行代码。XSLT 2.0 允许从样式表直接调用这些类,因而可以方便地为各种语言或其他需求添加自定义的排序函数。这种简单的技术可以大大丰富 XML 处理工具箱。
鸣谢
感谢 O'Reilly 的 Simon St. Laurent 和 Associates,允许本文使用前两个代码例子和关于不同排序算法的说明文字。这些材料取自 XSLT (ISBN 0596527217) 第二版。该书的第二版介绍了几个 XSLT 扩展,包括用 Java、C#、Python、Ruby 和 JavaScript 编写的代码。可以在 amazon.com 预定该书。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 本文的 XML、XSLT、HTML 和 Java 示例 | x-xsltsort-samples.zip | 16KB | HTTP |
|---|
参考资料 学习
获得产品和技术
-
Saxon XSLT 2.0 处理程序:从 SourceForge 下载 Michael
Kay 编写的这个工具。
-
Altova 网站上的 AltovaXML 页面:进一步了解 Altova(XML Spy 和其他产品的开发商)开发的 XSLT 处理程序,可免费下载。该处理程序支持 XSLT 1.0、XSLT 2.0 和 XQuery 1.0。虽然是开放源代码的产品,但许可证不允许在用户产品中内嵌这种 XSLT 引擎。
-
IBM 试用软件:用这些试用软件开发您的下一个项目,可直接从 developerWorks 下载。
讨论
关于作者  | 
|  | Doug Tidwell 是 IBM Software Group 的战略师。作为一名技术宣传人员,他的工作主要是关注 SCA、SDO 和 XForms 这些新技术,帮助人们使用明天的技术解决今天的问题。他是 O'Reilly 版 XSLT 一书的作者,预计第二版将于 2008 年的情人节上市(ISBN 0596527217)。作为 1997 年第一届 XML 大会上的发言者之一,他具有近十年的 XSLT 工作经验,包括一些早期的 XML 转换方法。他目前在撰写一本关于水果派的发明者、荷兰柑橘商人 Julius of Orange 的一本书。Doug 和他的妻子 烹饪图书作家 Sheri Castle、女儿 Lily 以及他们的狗 Domino、Supine Canine 居住在北加利福尼亚的 Chapel Hill。 |
对本文的评价
|  |