使用XSLT 1.0将字符串限制为列入白名单的字符

时间:2020-03-06 14:42:18  来源:igfitidea点击:

问题

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