级别: 初级 Kevin Manley (kmanley@cs.washington.edu), 顾问
2002 年 1 月 01 日 XSLT 的函数型编程风格会使一些看来简单的任务变得有点更复杂。字符串处理就是这样的一个示例。本文中,Kevin 向您展示了如何实现一个可重用的断行算法,在从 XML 生成纯文本时,该算法允许对输出进行更精细的控制。
XSLT 已经广泛使用在把一个 XML 结构转换成另一个 XML 结构或将 XML 转换为 HTML 以在浏览器中显示。但是,将 XML 转换成旧的纯文本,XSLT 也是极为有用的。例如,在一个订单分派系统中,您可能希望能够通过电子邮件发送纯文本、以传真方式或将文本打印到一个预先印制的表格上来分派(以 XML 表示的)订单。
在从 XML 生成文本时会发生的问题之一是:您可能需要将文本分成定长的数行。这个问题在生成 XML 或 HTML 时并不会真正出现。XML-到-XML 转换通常是个中间步骤,后面常跟着进一步的处理。而且对于 HTML,格式化通常用表格实现,所以文本会按照封闭单元格的宽度自动断行。然而在生成纯文本时,对如何输出文本有时需要更多的控制。
XSLT 和函数型编程
那些对于 XSLT 刚入门的人可能会觉得奇怪:为什么这样简单的问题值得详细描述。这是一个微不足道的算法,不是吗?啊,既对又不对。在许多开发人员使用的命令式编程语言(例如:C、C++、Java、Perl 和 Python)中,将一个长字符串分成小于或等于 N 个字符的数行是一个简单的练习。 这些语言之一中的解决方案可能是对源文本进行循环,并维护最后一个可见的断开字符的位置。当当前行的长度超过 N 时,这一行的输出到最后一个断开字符为止,然后将起始位置设置为断开字符后面的字符,并继续循环。
但是,使用 XSLT 时,必须重新考虑这种方法。XSLT 使用函数型编程样式,它更接近于 Lisp 或 Haskell 而不是 C 那样的命令式语言。同样地,XSLT 中没有任何变量赋值 ― 只有初始化。没有变量赋值是有意义的,它用来从程序中消除数据流的相关性。没有这些相关性, 在如何对程序求值时,执行环境会有更多灵活性,因为它不必按任何特定次序执行步骤。这为将来功能更强大的 XSLT 处理器设置了舞台,那些处理器可以随着它们的输入变化而不断递进地执行转换,而无需重新计算整个转换。
递归解决方案
针对断行问题的函数型编程模型的实际含意是:我们不能对输入字符串进行迭代操作。XSLT 不具有任何常规的循环结构(只有对输入节点集操作的
<xsl:for-each/> )。这是有道理的 ― 毕竟,在没有变量赋值的情况下,无法更新循环计数器!使用函数型语言实现迭代任务的常见 方法是将迭代变成递归,而那恰好是如何解决断行问题的方法。以下是在第 N 个字符处断行的递归算法。首先,查找一个左子字符串,其长度小于等于 N 个字符并且以一个断开字符结束。输出这个子字符串,后跟一个回车符。然后,对剩余的右子字符串递归调用这一算法。
清单 1 中显示的模板函数
breakText 实现了这个算法。通过调用帮助函数
maxSubstringEndingWithBreak 来找到左子字符串,然后使用返回值初始化变量
strFirst 。使用
<xsl:value-of/> 元素输出这个值和行结束字符。然后,将变量
strRest 初始化为剩余的右子字符串,并对该字符串递归调用
breakText 。
清单 1. 用 XSLT 编写的递归断行算法(br.xslt)
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<!-- global constants -->
<xsl:variable name="EOL" select="'
'"/>
<xsl:variable name="BREAK" select="' '"/>
<!--
Breaks msg into one or more lines.
Lines are broken at the numChars-th character, so each line will
have no more than (numChars-1) characters each.
-->
<xsl:template name="breakText">
<xsl:param name="strMsg"/>
<xsl:param name="numChars"/>
<xsl:choose>
<xsl:when test="string-length($strMsg) < $numChars">
<xsl:value-of select="$strMsg"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="strFirst">
<xsl:call-template
name="maxSubstringEndingWithBreak">
<xsl:with-param name="strFragment"
select="substring($strMsg,1,$numChars)"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="strRest"
select="substring-after($strMsg,$strFirst)"/>
<xsl:value-of select="$strFirst"/>
<xsl:value-of select="$EOL"/>
<xsl:call-template name="breakText">
<xsl:with-param name="strMsg" select="$strRest"/>
<xsl:with-param name="numChars" select="$numChars"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Given parameter s, this function returns the maximal left
substring of s ending in a break character. If there is no
break character, then the entire string is returned.
-->
<xsl:template name="maxSubstringEndingWithBreak">
<xsl:param name="strFragment"/>
<xsl:variable name="len" select="string-length($strFragment)"/>
<xsl:choose>
<xsl:when test="len <= 1 or substring($strFragment,$len)=$BREAK
or contains($strFragment,$BREAK)=false">
<xsl:value-of select="$strFragment"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="maxSubstringEndingWithBreak">
<xsl:with-param name="strFragment"
select="substring($strFragment, 1, $len - 1)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
|
maxSubstringEndingWithBreak 模板得到单个字符串参数
strFragment ,并返回字符串中以断开字符结束的最大左子字符串。该模板也使用递归。首先,检查传入的字符串是否已结束于一个断开字符。如果是的话,就完成了。否则,执行递归调用,并传入
strFragment 中最右端字符外的所有字符。如果
strFragment 只有一个字符,或者不包含任何断开字符,则没有结束于断开字符的子字符串。在这种情况下,能够做的只有返回已有的内容。这符合不正常的情况。在不正常的情况中,调用方希望在字符 X 处断行 ,但 是这一行包含了一个或多个长度超过 X 个字符的词。
用法示例
清单 2 中的 XML 文件示例显示了一个元素,它带有某些长文本内容。清单 3 中显示的 XSL 文件输出该文本,并将其分成每行不超过 15 个字符的几行。当然,将属性文本或任何计算的字符串值传递到
breakText 也很容易。只要使用相应的 XPath 将数据选入
strMsg 中。
清单 2. XML 示例(test.xml)
<?xml version="1.0"?>
<example>
<sometext>This is some element text that we'd like to have broken
into multiple lines of no more than X characters each.</sometext>
</example>
|
清单 3. XSLT 示例(test.xslt)
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:import href="br.xslt"/>
<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:template match="/example/sometext">
<xsl:call-template name="breakText">
<xsl:with-param name="strMsg" select="text()"/>
<xsl:with-param name="numChars" select="15"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
|
将代码合并到您自己的样式表中
通过将断行算法放在自己的文件中并在需要时导入它,该断行算法彻底地与不相关的 XSLT 代码分开。在上面的 test.xslt 中,我们使用
<xsl:import> 元素导入断行功能。将您自己的帮助函数分开成类似这样的独立模块是个好主意,因为这使得在不同的样式表之间重用代码更方便。
结束语
在本文中,我已经讨论了使用 XSLT 生成纯文本,以及生成文本时会出现的断行问题。接着,我考虑了 XSLT 的函数型编程模型如何需要递归方法来实现断行算法。最后,我展示了一些可包含在任何样式表中以提供断 行能力的可重用代码。
参考资料
关于作者  | |  | Kevin Manley 是一位独立软件开发人员和顾问,在美国华盛顿州西雅图工作。他拥有 Worcester Polytechnic Institute 电子工程专业学士学位,目前正在华盛顿大学攻读计算机科学专业硕士学位。可以通 过
kmanley@cs.washington.edu与他联系。
|
对本文的评价
|