This XSLT 2.0 transformation:
<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>
when applied on the provided XML document:
<root> <info find="ain"> <txt>The rain in Spain falls mainly in the plain.</txt> </info> </root>
produces the correct result:
6 15 26 41
Here is the equally short transformation that uses this to produce the wanted XML result:
<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>
When this transformation is applied on the same provided XML document (above), the wanted result is produced:
<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>
Alternatively, one can use:
<xsl:for-each select= "(1 to string-length(txt) -string-length($vPat) +1) [starts-with(substring($vTxt, .), $vPat)] ">
And the complete transformation is:
<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>
Do note the simplicity and directness of this solution: