If you want to count characters from the start of the span, but maintain the element structure of the original XML, then I think you'll have to approach it using tail-recursive templates to pass the "number of remaining characters" from one sibling node to the next. At each point check whether you've yet output the required number of characters, and if not then continue processing the next sibling node (element or text node) with the updated count:
<!-- text nodes are output, truncated if necessary -->
<xsl:template match="text()" mode="substr" priority="10">
<xsl:param name="chars" as="xs:integer" tunnel="yes" />
<xsl:value-of select="substring(., 1, $chars)" />
<xsl:next-match/><!-- call the common node() template -->
</xsl:template>
<!-- for element nodes we copy the start tag and then we continue processing
with the first child node (which may be a text node or element) -->
<xsl:template match="*" mode="substr" priority="10">
<xsl:copy>
<xsl:sequence select="@*" />
<xsl:apply-templates select="node()[1]" mode="substr"/>
</xsl:copy>
<xsl:next-match/><!-- call the common node() template -->
</xsl:template>
<!-- Ignore comments and PIs, or you might want to copy them instead -->
<xsl:template match="comment()|processing-instruction()"
mode="substr" priority="10">
<xsl:apply-templates select="following-sibling::node()[1]" mode="substr" />
</xsl:template>
<!-- check the continuation criteria and go to next sibling if appropriate -->
<xsl:template match="node()" mode="substr" priority="5">
<xsl:param name="chars" as="xs:integer" tunnel="yes"/>
<xsl:if test="$chars gt string-length(.)">
<xsl:apply-templates select="following-sibling::node()[1]" mode="substr">
<xsl:with-param name="chars" select="$chars - string-length(.)"
tunnel="yes"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
To start the process you then apply the "substr" mode templates to the first child node of your span
and pass the necessary character count as a parameter
<xsl:template match="span">
<xsl:copy>
<!-- <xsl:sequence select="@*" /> if the span might have attributes -->
<xsl:apply-templates select="node()[1]" mode="substr">
<xsl:with-param name="chars" select="38" tunnel="yes" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
Given your example input, a chars
value of 38 would produce
<span>Hello this is user874774 writing. <strong>Here</strong></span>
as there are 34 characters in the first text node (between <span>
and <strong>
) and four from the <strong>
up to the length limit. A chars
value of 33 would produce just
<span>Hello this is user874774 writing.</span>
truncating the first text node after 33 characters and not including the strong
at all.