使用XSLT 1.0将字符串限制为列入白名单的字符
问题
使用XSLT 1.0,给定具有任意字符的字符串,如何获得符合以下规则的字符串。
- 首字符必须是以下字符之一:a-z,A-Z,冒号或者下划线
- 所有其他字符必须是0-9以上的任何字符,句点或者连字符
- 如果有任何字符不符合上述规则,请用下划线替换
背景
在XSLT中,我将某些属性转换为元素,但是我需要确保该属性不包含任何不能在元素名称中使用的值。只要可以预测地将其转换为名称,我就不太在乎该属性的完整性。我也不需要补偿元素名称中的每个有效字符(有很多)。
我遇到的问题是带有空格的属性,translate函数可以轻松将其转换为下划线:
translate(@name,' ','_')
但是不久之后,我发现一些使用斜杠的属性,所以我现在也必须添加它。这将很快失去控制。我希望能够定义允许的字符的白名单,并用下划线替换所有不允许的字符,但翻译的工作原理是通过从黑名单进行替换来实现。
解决方案
据我所知,XSLT 1.0对此没有内置功能。 XSLT 2.0允许我们使用正则表达式,尽管我肯定我们也知道这一点。
如果我们偶然使用MS解析器,则可以编写可以在XSLT中利用的.NET扩展库,而我几个月前就在这里写过。
如果我们使用的是Saxon之类的软件,我可以肯定地说,它们还提供了编码自己的扩展的方法,它们可能确实已经拥有自己的扩展,但是我不熟悉该引擎。
希望这可以帮助。
我们可以编写一个递归模板来做到这一点,一个接一个地处理字符串中的字符,测试它们,并在必要时进行更改。就像是:
<xsl:template name="normalizeName">
<xsl:param name="name" />
<xsl:param name="isFirst" select="true()" />
<xsl:if test="$name != ''">
<xsl:variable name="first" select="substring($name, 1, 1)" />
<xsl:variable name="rest" select="substring($name, 2)" />
<xsl:choose>
<xsl:when test="contains('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:_', $first) or
(not($first) and contains('0123456789.-', $first))">
<xsl:value-of select="$first" />
</xsl:when>
<xsl:otherwise>
<xsl:text>_</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="normalizeName">
<xsl:with-param name="name" select="$rest" />
<xsl:with-param name="isFirst" select="false()" />
</xsl:call-template>
</xsl:if>
</xsl:template>
但是,如果我们准备好接受一些骇客的攻击,则可以使用更短的方法。首先声明一些变量:
<xsl:variable name="underscores" select="'_______________________________________________________'" /> <xsl:variable name="initialNameChars" select="'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:_'" /> <xsl:variable name="nameChars" select="concat($initialNameChars, '0123456789.-')" />
现在,该技术是采用名称,并通过将名称中所有合法的字符全部替换为空来识别不合法的字符。你可以使用translate()函数来做到这一点。一旦在字符串中出现了一组非法字符,就可以再次使用translate()函数将其替换为下划线。这是模板:
<xsl:template name="normalizeName">
<xsl:param name="name" />
<xsl:variable name="first" select="substring($name, 1, 1)" />
<xsl:variable name="rest" select="substring($name, 2)" />
<xsl:variable name="illegalFirst"
select="translate($first, $initialNameChars, '')" />
<xsl:variable name="illegalRest"
select="translate($rest, $nameChars, '')" />
<xsl:value-of select="concat(translate($first, $illegalFirst, $underscores),
translate($rest, $illegalRest, $underscores))" />
</xsl:template>
我们唯一需要注意的是,下划线字符串必须足够长,以覆盖单个名称中可能出现的所有非法字符。使它与我们可能会遇到的最长名称的长度相同将解决这个问题(尽管我们可能会因为它的名称更短而逃脱了)。
作为另一种选择,XSLT标准库中可能有一个适合字符串函数。 http://xsltsl.sourceforge.net/string.html#template.str:string-match

