1

我需要一个转换来找到节点内文本模式的索引。例如在下面的 XML 中。如果我的节点文本模式<txt>是“ain”,则答案将是 6、15、26 和 41。

<root>
  <info find="ain">
    <txt>The rain in Spain falls mainly in the plain.</txt>
  </info>
</root>

转变为...

<find>
  <txt>The rain in Spain falls mainly in the plain.</txt>
  <hit ndx="6"/>
  <hit ndx="15"/>
  <hit ndx="26"/>
  <hit ndx="41"/>
</find>
4

3 回答 3

3

这个 XSLT 2.0 转换

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:template match="/*/info">
  <xsl:variable name="vSeq" select="string-to-codepoints(txt)"/>
  <xsl:variable name="vPatSeq" select="string-to-codepoints(@find)"/>

  <xsl:sequence select=
   "for $vPat in string(@find),
        $vPatLength in string-length(@find)
     return
        index-of($vSeq, $vPatSeq[1])
                [$vPat eq codepoints-to-string(subsequence($vSeq, ., $vPatLength))]
   "/>
 </xsl:template>
</xsl:stylesheet>

应用于提供的 XML 文档时:

<root>
  <info find="ain">
    <txt>The rain in Spain falls mainly in the plain.</txt>
  </info>
</root>

产生正确的结果

  6 15 26 41

下面是同样简短的转换,它使用它来生成所需的 XML 结果:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*/info">
  <xsl:variable name="vSeq" select="string-to-codepoints(txt)"/>
  <xsl:variable name="vPatSeq" select="string-to-codepoints(@find)"/>

  <find>
   <xsl:copy-of select="txt"/>

   <xsl:for-each select=
   "for $vPat in string(@find),
        $vPatLength in string-length(@find)
     return
        index-of($vSeq, $vPatSeq[1])
                [$vPat eq codepoints-to-string(subsequence($vSeq, ., $vPatLength))]">

    <hit ndx="{.}"/>
   </xsl:for-each>
  </find>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于同一个提供的 XML 文档(如上)时,会产生所需的结果

<find>
   <txt>The rain in Spain falls mainly in the plain.</txt>
   <hit ndx="6"/>
   <hit ndx="15"/>
   <hit ndx="26"/>
   <hit ndx="41"/>
</find>

或者,可以使用

<xsl:for-each select=
  "(1 to string-length(txt) -string-length($vPat) +1)
    [starts-with(substring($vTxt, .), $vPat)]
">

完整的转换是

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>

     <xsl:template match="/*/info">
      <xsl:variable name="vTxt" select="txt"/>
      <xsl:variable name="vPat" select="string(@find)"/>

      <find>
       <xsl:copy-of select="txt"/>

           <xsl:for-each select=
            "(1 to string-length(txt) -string-length($vPat) +1)
                 [starts-with(substring($vTxt, .), $vPat)]
            ">

            <hit ndx="{.}"/>
           </xsl:for-each>
      </find>
     </xsl:template>
</xsl:stylesheet>

请注意此解决方案的简单性和直接性

  • 没有递归。

  • 没有命名模板。

  • 没有xsl:function

  • 没有xsl:param

  • 没有xsl:if

  • 没有额外的命名空间声明。

  • 没有substring-after()

  • 没有正则表达式。

  • 没有replace()

  • 没有tokenize()

  • 没有 regex-group()` s。

  • 没有string-join()

  • 没有count()

于 2013-04-26T04:08:24.730 回答
3

编辑:这是一个 XSLT 2.0 解决方案:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*" />

  <xsl:template match="info[@find]">
    <find>
      <xsl:copy-of select="txt[1]" />

      <xsl:variable name="pattern" 
                    select="replace(@find, '[-/\\^$*+?.()|\[\]{}]', '\\$0')" />
      <xsl:variable name="parts" select="tokenize(txt, $pattern)" />

      <xsl:for-each select="1 to count($parts) - 1">
        <xsl:variable name="soFar"
                      select="string-join($parts[position() &lt;= current()], 
                                          $pattern)" />
        <hit ndx="{1 + string-length($soFar)}" />
      </xsl:for-each>
    </find>
  </xsl:template>
</xsl:stylesheet>

而且因为我已经对此进行了研究,所以这里有一个 XSLT 1.0 方法。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*" />

  <xsl:template match="info[@find]">
    <find>
      <xsl:copy-of select="txt[1]" />
      <xsl:call-template name="Matches">
        <xsl:with-param name="text" select="txt[1]" />
        <xsl:with-param name="pattern" select="@find" />
      </xsl:call-template>
    </find>
  </xsl:template>

  <xsl:template name="Matches">
    <xsl:param name="text" />
    <xsl:param name="pattern" />
    <xsl:param name="offset" select="1" />

    <xsl:variable name="found" select="substring-before($text, $pattern)" />
    <xsl:if test="$found">
      <hit ndx="{$offset + string-length($found)}" />

      <xsl:call-template name="Matches">
        <xsl:with-param name="text" select="substring-after($text, $pattern)" />
        <xsl:with-param name="pattern" select="$pattern" />
        <xsl:with-param name="offset" 
                        select="$offset + 
                                string-length($found) + 
                                string-length($pattern)" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

在您的示例输入上运行任何一个时,结果是:

<find>
  <txt>The rain in Spain falls mainly in the plain.</txt>
  <hit ndx="6" />
  <hit ndx="15" />
  <hit ndx="26" />
  <hit ndx="41" />
</find>
于 2013-04-25T20:42:05.313 回答
1

使用递归。如果您使用的是 XSLT2,那么最简单的方法是创建一个函数:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:s="http://string-functions"
    version="2.0">

    <xsl:function name="s:indexes" as="element(find)">
        <xsl:param name="str1"/>
        <xsl:param name="str2"/>
        <find value="{$str2}">
            <txt><xsl:value-of select="$str1"/></txt>
            <xsl:sequence select="s:indexes($str1, $str2, 0)"/>
        </find>
    </xsl:function>

    <xsl:function name="s:indexes" as="element(hit)*">
        <xsl:param name="str1"/>
        <xsl:param name="str2"/>
        <xsl:param name="offset"/>                
        <xsl:variable name="sub-before" select="substring-before($str1, $str2)"/>
        <xsl:if  test="$sub-before ne ''">
            <xsl:variable name="position" select="$offset + string-length($sub-before) + 1"/>
            <xsl:variable name="rest" select="substring(substring-after($str1, $sub-before), string-length($str2))"/>
            <xsl:variable name="new-offset" select="$offset + string-length($str1) - string-length($rest)"/>
            <hit test="{$position}"/>
            <xsl:sequence select="s:indexes($rest, $str2, $new-offset)"/>
        </xsl:if>
    </xsl:function>

    <xsl:template match="*">
        <xsl:sequence select="s:indexes('The rain in Spain falls mainly in the plain', 'ain')"/>
    </xsl:template>

</xsl:stylesheet>

=>

<find value="ain">
    <txt>The rain in Spain falls mainly in the plain</txt>
    <hit test="6"/>
    <hit test="15"/>
    <hit test="26"/>
    <hit test="41"/>
</find>
于 2013-04-25T19:26:19.190 回答