Thursday, March 17, 2016

Finding longest lines in XSLT

I needed to scale some code listings so that they fit the width of the page. DocBook doesn't have a particular automation for that, so I needed to find the length of the longest line in a listing and do scaling according to that.
So here it is:
<!-- Finds the length of the longest line in the given text -->
  <xsl:function name="vaadin:longestLineLength">
    <xsl:param name="listing">

    <xsl:variable name="firstLineLength">
      <xsl:choose>
        <xsl:when test="contains($listing, '&#xa;')">
          <xsl:value-of select="string-length(substring-before($listing, '&#xa;'))">
        </xsl:value-of></xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="string-length($listing)">
        </xsl:value-of></xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:variable name="longestInRest">
      <xsl:choose>
        <xsl:when test="contains($listing, '&#xa;')">
          <xsl:value-of select="vaadin:longestLineLength(substring-after($listing, '&#xa;'))">
        </xsl:value-of></xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="number(0)">
        </xsl:value-of></xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:choose>
      <!-- Must use number() here or otherwise it'll use string comparison. -->
      <xsl:when test="number($longestInRest) &gt; number($firstLineLength)">
        <xsl:value-of select="$longestInRest">
      </xsl:value-of></xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$firstLineLength">
      </xsl:value-of></xsl:otherwise>
    </xsl:choose>
  </xsl:param></xsl:function>

It can be called for example as follows:
<xsl:template match="programlisting | screen">
    <!-- Extract text content by eliminating formatting tags -->
    <xsl:variable name="content">
      <xsl:apply-templates select="* | text()" mode="text-listing"/>
    </xsl:variable>

    <xsl:variable name="longestLineLength">
      <xsl:value-of select="vaadin:longestLineLength($content)"/>
    </xsl:variable>

    <xsl:copy>
      <xsl:if test="$longestLineLength > 50">
        <xsl:attribute name="dbscaling">
          <!-- This should give 65% scaling for lines 63 chars long -->
          <xsl:value-of select="(50.0 div $longestLineLength) * 95"/>
        </xsl:attribute>
      </xsl:if>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

Note how you need to give the listing as pure text to the method, and node as an input node structure.