Само решение принадлежит Джени Теннисон (мои лишь комментарии). Каждая лексема возвращается в виде узла, состоящего из элемента token, содержащего текст. Если строка разделителей пуста, то по умолчанию исходная строка разбивается на отдельные символы.
<xsl:templatename="tokenize"><xsl:paramname="string"select="''"/><xsl:paramname="delimiters"select="' 	
'"/><xsl:choose><!-- Ничего не делать, если строка пуста --><xsl:whentest="not($string)"/><!-- Если разделителей нет, строка разбивается на отдельные символы. --><xsl:whentest="not($delimiters)"><xsl:call-templatename="_tokenize-characters"><xsl:with-paramname="string"select="$string"/></xsl:call-template></xsl:when><xsl:otherwise><xsl:call-templatename="_tokenize-delimiters"><xsl:with-paramname="string"select="$string"/><xsl:with-paramname="delimiters"select="$delimiters"/></xsl:call-template></xsl:otherwise></xsl:choose></xsl:template><xsl:templatename="_tokenize-characters"><xsl:paramname="string"/><xsl:iftest="$string"><token><xsl:value-ofselect="substring($string, 1, 1)"/></token><xsl:call-templatename="_tokenize-characters"><xsl:with-paramname="string"select="substring($string, 2)"/></xsl:call-template></xsl:if></xsl:template><xsl:templatename="_tokenize-delimiters"><xsl:paramname="string"/><xsl:paramname="delimiters"/><xsl:paramname="last-delimit"/><!-- Извлечь разделитель --><xsl:variablename="delimiter"select="substring($delimiters, 1, 1)"/><xsl:choose><!-- Если строка разделителей пуста, имеем лексему --><xsl:whentest="not($delimiter)"><token><xsl:value-ofselect="$string"/></token></xsl:when><!-- Если строка содержит хотя бы один разделитель, мы должны разбить ее --><xsl:whentest="contains($string, $delimiter)"><!-- Если строка начинается с разделителя, обрабатывать предшествующую подстроку не нужно --><xsl:iftest="not(starts-with($string, $delimiter))"><!-- Обрабатываем часть, предшествующую текущему разделителю, --><!-- пробуя следующий разделитель. Если следующего нет, то первая проверка в этом шаблоне выделяет лексему --><xsl:call-templatename="_tokenize-delimiters"><xsl:with-paramname="string"select="substring-before($string, $delimiter)"/><xsl:with-paramname="delimiters"select="substring($delimiters, 2)"/></xsl:call-template></xsl:if><!-- Обрабатываем часть, следующую за разделителем, применяя текущий разделитель --><xsl:call-templatename="_tokenize-delimiters"><xsl:with-paramname="string"select="substring-after($string, $delimiter)"/><xsl:with-paramname="delimiters"select="$delimiters"/></xsl:call-template></xsl:when><xsl:otherwise><!-- Текущий разделитель не встречается, поэтому переходим к следующему --><xsl:call-templatename="_tokenize-delimiters"><xsl:with-paramname="string"select="$string"/><xsl:with-paramname="delimiters"select="substring($delimiters, 2)"/></xsl:call-template></xsl:otherwise></xsl:choose></xsl:template>
Выделение лексем – типичная задача обработки текста. В языках, где есть развитый аппарат регулярных выражений, она решается тривиально. В этом отношении такие языки, как Perl, Python, JavaScript и Tcl пока превосходят XSLT. Однако, как показано в этом рецепте, разбиение на лексемы можно выполнить, даже не выходя за пределы чистого XSLT. Если вы готовы прибегнуть к расширениям, то можете реализовать низкоуровневые операции со строками на каком-нибудь другом языке.
Если же вам больше нравится подход XSLT, но ваш процессор не оптимизирует хвостовую рекурсию, то в шаблоне _tokenize-characters можно воспользоваться алгоритмом «разделяй и властвуй»:
<xsl:templatename="_tokenize-characters"><xsl:paramname="string"/><xsl:paramname="len"select="string-length($string)"/><xsl:choose><xsl:whentest="$len = 1"><token><xsl:value-ofselect="$string"/></token></xsl:when><xsl:otherwise><xsl:call-templatename="_tokenize-characters"><xsl:with-paramname="string"select="substring($string, 1, floor($len div 2))"/><xsl:with-paramname="len"select="floor($len div 2)"/></xsl:call-template><xsl:call-templatename="_tokenize-characters"><xsl:with-paramname="string"select="substring($string, floor($len div 2) + 1)"/><xsl:with-paramname="len"select="ceiling($len div 2)"/></xsl:call-template></xsl:otherwise></xsl:choose></xsl:template>
В языке Java также имеется готовый класс для разбиения строки на лексемы (java.util.StringTokenizer).