使用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