Reputation: 3219
Is there an easy way to indent whole blocks of text in XSL? I'd like to indent blocks of text to roughly match the nested levels of the XML.
The answer here, XSL: output of "nested" structures proposes an indent parameter which is increased as you recurse down.
This is a good start, but requires a long <value-of> tag at the beginning of every line, which of course can't be put inside of the <xsl:text> block.
So, is there a way to use that to indent a multi-line <xsl:text> block? I don't want to have to wrap every single line separately into <xsl:text> blocks, just so that I can add a variable to the beginning of each line.
Example template:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="drv">
<xsl:param name="pIndent"/>
<xsl:text>
Column {
anchors { left: parent.left; right: parent.right }
</xsl:text>
<xsl:apply-templates select="snc|kap">
<xsl:with-param name="pIndent" select="concat($pIndent, ' ')"/>
</xsl:apply-templates>
<xsl:text>
}</xsl:text>
</xsl:template>
</xsl:stylesheet>
So, I want to be able to output the text at the appriate indentation level. So, is there a way to dynamically indent the whole <xsl:text> block (or blocks in this case)? This will allow the output code to be indented correctly, making it much easier for me to debug.
Output like this, but indented to the correct level in the surrounding code:
Column {
anchors { left: parent.left; right: parent.right }
[Code from some other template
kept at this indent level]
}
I'll try and summarise the question in a couple of different ways, as people are struggling to understand it:
Given a level of indentation known at runtime, how can you indent the contents of an entire <xsl:text> block to that level?
Or alternatively: Given a value known at runtime, how can you prepend the value to the beginning of every line in an <xsl:text> block?
Upvotes: 0
Views: 1671
Reputation: 122364
You essentially want a "function" that can take a string and prepend a particular prefix to the start of the string and also immediately after every newline character that the string contains. In XSLT 2.0 this would be very simple using the regular expression replace
function, but unfortunately lxml only supports XSLT 1.0.
In XSLT 1.0 I'd approach this using a tail-recursive named template:
<xsl:template name="printIndented">
<xsl:param name="text" />
<xsl:param name="indent" />
<xsl:if test="$text">
<xsl:value-of select="$indent" />
<xsl:variable name="thisLine" select="substring-before($text, ' ')" />
<xsl:choose>
<xsl:when test="$thisLine"><!-- $text contains at least one newline -->
<!-- print this line -->
<xsl:value-of select="concat($thisLine, ' ')" />
<!-- and recurse to process the rest -->
<xsl:call-template name="printIndented">
<xsl:with-param name="text" select="substring-after($text, ' ')" />
<xsl:with-param name="indent" select="$indent" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
To use the "function" you'd have to do something like
<xsl:call-template name="printIndented">
<xsl:with-param name="indent" select="$pIndent" />
<xsl:with-param name="text"
>Column {
anchors { left: parent.left; right: parent.right }</xsl:with-param>
</xsl:call-template>
<xsl:apply-templates select="snc|kap">
<xsl:with-param name="pIndent" select="concat($pIndent, ' ')"/>
</xsl:apply-templates>
<xsl:call-template name="printIndented">
<xsl:with-param name="indent" select="$pIndent" />
<xsl:with-param name="text">}</xsl:with-param>
</xsl:call-template>
Of course, you then have to be extremely careful not to use an auto-formatter on the XSLT source code itself, as that would throw all your carefully indented code out of the window...
Upvotes: 2