How can I use XSLT 1.0 to right justify plain text

2019-07-07 09:05发布

I'm working with an XML file that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="align-test.xsl"?>
<alignTest>
    <item name="Some Name" number="3"></item>
    <item name="Another Name" number="10"></item>
    <item name="A Third One" number="43"></item>
    <item name="A Really Long Name" number="100"></item>
</alignTest>

My goal is to output a plain text file with a nicely formatted table in it. I've figured out how to align and pad text columns and a separater using this stylesheet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text"/>
    <xsl:template match="/">

        <xsl:for-each select="alignTest/item">
            <!-- Scroll right. The next line keeps going, but might not look like it due to formatting -->
            <xsl:value-of select="substring(concat(@name, '                         '), 1, 22)"/>
            <xsl:text> | </xsl:text>
            <xsl:value-of select="@number"/>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each>

    </xsl:template>
</xsl:stylesheet>

Which outputs:

Some Name              | 3
Another Name           | 10
A Third One            | 43
A Really Long Name     | 100

I'd also like the numeric values to be right justified. Like so:

Some Name              |   3
Another Name           |  10
A Third One            |  43
A Really Long Name     | 100

How can I use XSLT 1.0 to right justify plain-text in that way?

标签: xslt
2条回答
放荡不羁爱自由
2楼-- · 2019-07-07 09:18

I found a few answers on this page.

The simply way described is to do something like:

<xsl:value-of select="substring(concat('    ', @number), string-length(@number) + 1, 4)"/>

The page also lists a couple of templates for padding both on the left and the right. They call themselves recursively and pad the appropriate amount. (Note that they will also truncate if the requested length is less than that of the string being worked on.) The templates also off a feature of changing the character that is used for padding.

They take more code to implement, but might be easier to maintain. Here's a version of the original stylesheet updated with the two templates to show their usage:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text"/>

    <xsl:template match="/">
        <xsl:for-each select="alignTest/item">

            <xsl:call-template name="padRightSide">
                <xsl:with-param name="stringToPad" select="@name"></xsl:with-param>
                <xsl:with-param name="totalLength" select="22"></xsl:with-param>
                <xsl:with-param name="padCharacter" select="' '"></xsl:with-param>
            </xsl:call-template>

            <xsl:text>|</xsl:text>

            <xsl:call-template name="padLeftSide">
                <xsl:with-param name="stringToPad" select="@number"/>
                <xsl:with-param name="totalLength" select="5"/>
                <xsl:with-param name="padCharacteracter" select="' '"/>
            </xsl:call-template>

            <xsl:text>&#10;</xsl:text>

        </xsl:for-each>
    </xsl:template>

    <!-- template to pad the left side of strings (and right justificy) -->
    <xsl:template name="padLeftSide">
        <xsl:param name="stringToPad"/>
        <xsl:param name="totalLength"/>
        <xsl:param name="padCharacteracter"/>

        <xsl:choose>
            <xsl:when test="string-length($stringToPad) &lt; $totalLength">
                <xsl:call-template name="padLeftSide">
                    <xsl:with-param name="stringToPad" select="concat($padCharacteracter,$stringToPad)"/>
                    <xsl:with-param name="totalLength" select="$totalLength"/>
                    <xsl:with-param name="padCharacteracter" select="$padCharacteracter"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="substring($stringToPad,string-length($stringToPad) - $totalLength + 1)"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!-- template to pad the right side of strings -->
    <xsl:template name="padRightSide">
        <xsl:param name="padCharacter"/> 
        <xsl:param name="stringToPad"/>
        <xsl:param name="totalLength"/>
        <xsl:choose>
            <xsl:when test="string-length($stringToPad) &lt; $totalLength">
                <xsl:call-template name="padRightSide">
                    <xsl:with-param name="padCharacter" select="$padCharacter"/>
                    <xsl:with-param name="stringToPad" select="concat($stringToPad,$padCharacter)"/>
                    <xsl:with-param name="totalLength" select="$totalLength"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="substring($stringToPad,1,$totalLength)"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

Of course, you could reduce the size of their footprint a little by hard coding the padding character.

查看更多
女痞
3楼-- · 2019-07-07 09:19

Here is a more readable and more efficient version of your answer:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="item">
        <xsl:call-template name="padRightSide">
            <xsl:with-param name="stringToPad" select="@name"/>
            <xsl:with-param name="totalLength" select="22"/>
        </xsl:call-template>

        <xsl:text>|</xsl:text>

        <xsl:call-template name="padLeftSide">
            <xsl:with-param name="stringToPad" select="@number"/>
            <xsl:with-param name="totalLength" select="5"/>
        </xsl:call-template>

        <xsl:text>&#10;</xsl:text>
 </xsl:template>

 <!-- template to pad the left side of strings (and right justificy) -->
 <xsl:template name="padLeftSide">
    <xsl:param name="stringToPad"/>
    <xsl:param name="totalLength"/>
    <xsl:param name="padChar" select="' '"/>
    <xsl:param name="padBuffer" select=
    "concat($padChar,$padChar,$padChar,$padChar,$padChar,
            $padChar,$padChar,$padChar,$padChar,$padChar
            )"/>
    <xsl:variable name="vNewString" select=
                       "concat($padBuffer, $stringToPad)"/>

    <xsl:choose>
        <xsl:when test="not(string-length($vNewString) >= $totalLength)">
            <xsl:call-template name="padLeftSide">
                <xsl:with-param name="stringToPad" select="$vNewString"/>
                <xsl:with-param name="totalLength" select="$totalLength"/>
                <xsl:with-param name="padChar" select="$padChar"/>
                <xsl:with-param name="padBuffer" select="$padBuffer"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select=
            "substring($vNewString, 
                       string-length($vNewString) - $totalLength + 1)"/>
        </xsl:otherwise>
    </xsl:choose>
 </xsl:template>

 <!-- template to pad the right side of strings -->
 <xsl:template name="padRightSide">
    <xsl:param name="totalLength"/>
    <xsl:param name="padChar" select="' '"/>
    <xsl:param name="stringToPad"/>
    <xsl:param name="padBuffer" select=
    "concat($padChar,$padChar,$padChar,$padChar,$padChar,
            $padChar,$padChar,$padChar,$padChar,$padChar
            )"/>
    <xsl:variable name="vNewString" select=
                       "concat($stringToPad, $padBuffer)"/>
    <xsl:choose>
        <xsl:when test="not(string-length($vNewString) >= $totalLength)">
            <xsl:call-template name="padRightSide">
                <xsl:with-param name="stringToPad" select="$vNewString"/>
                <xsl:with-param name="totalLength" select="$totalLength"/>
                <xsl:with-param name="padChar" select="$padChar"/>
                <xsl:with-param name="padBuffer" select="$padBuffer"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="substring($vNewString,1,$totalLength)"/>
        </xsl:otherwise>
    </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

Another improvement is to dynamically calculate the maximum string-length and not to have to count it manually:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:variable name="vNamesMaxLen">
  <xsl:call-template name="maxLength">
   <xsl:with-param name="pNodes"
                   select="/*/item/@name"/>
  </xsl:call-template>
 </xsl:variable>


 <xsl:variable name="vNumsMaxLen">
  <xsl:call-template name="maxLength">
   <xsl:with-param name="pNodes"
                   select="/*/item/@number"/>
  </xsl:call-template>
 </xsl:variable>

 <xsl:template match="item">
        <xsl:call-template name="padRightSide">
            <xsl:with-param name="stringToPad"
                            select="@name"/>
            <xsl:with-param name="totalLength"
                            select="$vNamesMaxLen+1"/>
        </xsl:call-template>

        <xsl:text>|</xsl:text>

        <xsl:call-template name="padLeftSide">
            <xsl:with-param name="stringToPad"
                            select="@number"/>
            <xsl:with-param name="totalLength"
                            select="$vNumsMaxLen+1"/>
        </xsl:call-template>

        <xsl:text>&#10;</xsl:text>
 </xsl:template>

 <xsl:template name="maxLength">
  <xsl:param name="pNodes" select="/.."/>

  <xsl:for-each select="$pNodes">
   <xsl:sort select="string-length()" data-type="number"
             order="descending"/>
    <xsl:if test="position() = 1">
     <xsl:value-of select="string-length()"/>
    </xsl:if>
  </xsl:for-each>
 </xsl:template>

 <!-- template to pad the left side of strings (and right justificy) -->
 <xsl:template name="padLeftSide">
    <xsl:param name="stringToPad"/>
    <xsl:param name="totalLength"/>
    <xsl:param name="padChar" select="' '"/>
    <xsl:param name="padBuffer" select=
    "concat($padChar,$padChar,$padChar,$padChar,$padChar,
            $padChar,$padChar,$padChar,$padChar,$padChar
            )"/>
    <xsl:variable name="vNewString" select=
                    "concat($padBuffer, $stringToPad)"/>

    <xsl:choose>
        <xsl:when test="not(string-length($vNewString) >= $totalLength)">
            <xsl:call-template name="padLeftSide">
                <xsl:with-param name="stringToPad" select="$vNewString"/>
                <xsl:with-param name="totalLength" select="$totalLength"/>
                <xsl:with-param name="padChar" select="$padChar"/>
                <xsl:with-param name="padBuffer" select="$padBuffer"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select=
            "substring($vNewString, 
                       string-length($vNewString) - $totalLength + 1)"/>
        </xsl:otherwise>
    </xsl:choose>
 </xsl:template>

 <!-- template to pad the right side of strings -->
 <xsl:template name="padRightSide">
    <xsl:param name="totalLength"/>
    <xsl:param name="padChar" select="' '"/>
    <xsl:param name="stringToPad"/>
    <xsl:param name="padBuffer" select=
    "concat($padChar,$padChar,$padChar,$padChar,$padChar,
            $padChar,$padChar,$padChar,$padChar,$padChar
            )"/>
    <xsl:variable name="vNewString" select=
                    "concat($stringToPad, $padBuffer)"/>
    <xsl:choose>
        <xsl:when test="not(string-length($vNewString) >= $totalLength)">
            <xsl:call-template name="padRightSide">
                <xsl:with-param name="stringToPad" select="$vNewString"/>
                <xsl:with-param name="totalLength" select="$totalLength"/>
                <xsl:with-param name="padChar" select="$padChar"/>
                <xsl:with-param name="padBuffer" select="$padBuffer"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="substring($vNewString,1,$totalLength)"/>
        </xsl:otherwise>
    </xsl:choose>
 </xsl:template>
</xsl:stylesheet>
查看更多
登录 后发表回答