简体   繁体   中英

How to calculate max string-length of a node-set?

I am trying to use XSLT to turn an XML document into plain text tables for human consumption. I am using xsltproc , which only implements XSLT 1.0 (so max is from EXSLT actually).

I tried the below, but the commented-out definition fails because string-length returns only a single value (the length of the first node's string value), not a node-set like max wants.

Transformation:

<?xml version="1.0" encoding="utf-8"?>
<!-- vim: set sts=2 sw=2: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:math="http://exslt.org/math" xmlns:str="http://exslt.org/strings">
  <xsl:output method="text"/>
  <xsl:template match="/root">
    <!-- <xsl:variable name="max_a_width" select="math:max(string-length(data/@a))"/> -->
    <xsl:variable name="max_a_width" select="string-length(data/@a)"/>
    <xsl:text>+-</xsl:text><xsl:value-of select="str:padding($max_a_width, '-')"/><xsl:text>-+&#10;</xsl:text>
    <xsl:for-each select="data">
      <xsl:text>| </xsl:text><xsl:value-of select="@a"/><xsl:value-of select="str:padding($max_a_width - string-length(@a), ' ')"/><xsl:text> |&#10;</xsl:text>
    </xsl:for-each>
    <xsl:text>+-</xsl:text><xsl:value-of select="str:padding($max_a_width, '-')"/><xsl:text>-+&#10;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Input:

<?xml version="1.0" encoding="utf-8"?>
<!-- vim: set sts=2 sw=2: -->
<root>
  <data a="aa"/>
  <data a="aaa"/>
  <data a="a"/>
</root>

Output:

+----+
| aa |
| aaa |
| a  |
+----+

In order to make the right border line up, I need to have the actual maximum value in the variable. (In my real example I will have column headers and multiple columns, but they are not necessary to reproduce the problem).

If it makes it easier to find a solution, I can guarantee that the data values will never contain spaces.

<xsl:variable name="max_a_width">
  <xsl:for-each select="data">
    <xsl:sort select="string-length(@a)" data-type="number" />
    <xsl:if test="position() = last()">
      <xsl:value-of select="string-length(@a)" />
    </xsl:if>
  </xsl:for-each>
</xsl:variable>

This is the general method of picking from an ordered list of derived values in XSLT 1.0.

If you want to pick the minimum/maximum from actual (natively sortable) values, you can take a short-cut:

<xsl:variable name="max_a" select="//a[not(. &lt; //a)][1]" />

Here's a simple recursive template. Run it with the same XSLT file as input to test it. This is of course easier with XSLT 2 or XQuery...

 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template name="maxlen"> <xsl:param name="input" /> <xsl:param name="max-so-far" select="0" /> <xsl:choose> <xsl:when test="not($input)"> <xsl:value-of select="$max-so-far" /> </xsl:when> <xsl:when test="string-length($input[1]) &gt; $max-so-far"> <xsl:call-template name="maxlen"> <xsl:with-param name="input" select="$input[position() &gt; 1]" /> <xsl:with-param name="max-so-far" select="string-length($input[1])" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="maxlen"> <xsl:with-param name="input" select="$input[position() &gt; 1]" /> <xsl:with-param name="max-so-far" select="$max-so-far" /> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:variable name="input"> <test> <string>123456789012345678901234</string> <string>12345678901234</string> <string>1234567890</string> <string>12345678901234567890123456</string> </test> </xsl:variable> <xsl:template match="/"> <output> <xsl:call-template name="maxlen"> <xsl:with-param name="input" select="//test/string" /> </xsl:call-template> </output> </xsl:template> </xsl:stylesheet> 

This is how to do this using EXSLT math:max() :

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:math="http://exslt.org/math" 
xmlns:str="http://exslt.org/strings">
<xsl:output method="text" encoding="UTF-8"/>

<xsl:template match="/root">
    <xsl:variable name="lengths">
        <xsl:for-each select="data">
            <length><xsl:value-of select="string-length(@a)"/></length>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="max_a_width" select="math:max(exsl:node-set($lengths)/length)"/>
    <!-- the rest -->
</xsl:template>

</xsl:stylesheet>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM