使用XSLT查找文本模式的索引

时间:2013-04-25 18:50:27

标签: xslt xpath

我需要一个转换来查找节点中文本模式的索引。例如,在下面的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>

3 个答案:

答案 0 :(得分: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>

答案 1 :(得分: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 s。

  • xsl:param s。

  • xsl:if

  • 没有其他名称空间声明。

  • substring-after()

  • 没有RegExes。

  • replace()

  • tokenize()

  • 没有regex-group()`s。

  • string-join()

  • count()

答案 2 :(得分: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>

=&GT;

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