xsltsymphony-cms

XSL: Is there an easy way to prevent widows?


I was hoping to call ..

<xsl:call-template name="widow-fix">
  <with-param name="text" select="text"></with-param>
</xsl:call-template>

And then it would look for the last space in the text and replace it with #160; when finalized.


Should be able to support

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

and

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

Please use a different character, like # to answer/prove, so the result when I test would be

Lorem ipsum dolor sit amet, consectetur adipiscing#elit.

and

<p>Lorem ipsum dolor sit amet, consectetur adipiscing#elit.</p>

Solution

  • All that you need is to replace the last space in every text node:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    
    <xsl:template match="p | text()">
        <xsl:apply-templates select="." mode="widow-fix"/>
    </xsl:template>
    
    
    <xsl:template match="* | @*">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    
    
    <xsl:template match="* | @*" mode="widow-fix">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" mode="widow-fix"/>
        </xsl:copy>
    </xsl:template>
    
    
    <xsl:template match="text()[contains(., ' ')]" mode="widow-fix">
        <xsl:call-template name="text">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
    </xsl:template>
    
    
    <xsl:template name="text">
        <xsl:param name="text"/>
    
        <xsl:variable name="substring-before" select="substring-before($text, ' ')"/>
        <xsl:variable name="substring-after"  select="substring-after($text, ' ')"/>
    
        <xsl:choose>
            <xsl:when test="contains($substring-after, ' ')">
                <xsl:value-of select="$substring-before"/>
                <xsl:text> </xsl:text>
    
                <xsl:call-template name="text">
                    <xsl:with-param name="text" select="$substring-after"/>
                </xsl:call-template>
            </xsl:when>
    
            <xsl:otherwise>
                <xsl:value-of select="$substring-before"/>
                <!--<xsl:text>&#160;</xsl:text>-->
                <xsl:text>#</xsl:text>
                <xsl:value-of select="$substring-after"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
    
    </xsl:stylesheet>
    

    The mode="widow-fix" can process both text nodes and paragraphs preserving the enclosing tag.

    I used such document as a test source

    <book>
    <p>Highly random content</p>
    in this book
    </book>
    

    which converts to the following

    <book>
    <p>Highly random#content</p>
    in this#book
    </book>