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?
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...
Related
I am processing an xml file using xslt.
<ns1:declarationStatements>
<ns1:parameterisedEntity>
<ns2:code>NUTSUPSTATE20</ns2:code>
<ns2:localeData>
<ns1:description>
<![CDATA[** When {s} according to instructions {m}g typically weighs {m}g.]]>
</ns1:description>
<ns1:id>20253</ns1:id>
</ns2:localeData>
<ns2:specType>FOOD</ns2:specType>
<ns2:id>6653</ns2:id>
</ns1:parameterisedEntity>
<ns1:textParameters>
<ns1:value>228</ns1:value>
<ns1:id>68225</ns1:id>
<ns1:sequence>2</ns1:sequence>
</ns1:textParameters>
<ns1:textParameters>
<ns1:value>cooked</ns1:value>
<ns1:id>68233</ns1:id>
<ns1:sequence>0</ns1:sequence>
</ns1:textParameters>
<ns1:textParameters>
<ns1:value>255</ns1:value>
<ns1:id>68229</ns1:id>
<ns1:sequence>1</ns1:sequence>
</ns1:textParameters>
<ns1:id>133421</ns1:id>
</ns1:declarationStatements>
I want to get the text inside <ns1:description> i.e.-
**When {s} according to instructions {m}g typically weighs {m}g
But I want {s}, {m} and {m} to be replaced by the values in <ns1:textParameters>/<ns1:value>. It should look like -
**When cooked according to instructions 255g typically weighs 228g.
I tried doing that by using <xsl:value-of select="ns0:declarationStatements"> and the manipulating string but it is becoming very tedious and complex.
The number of such braces may also vary. So do we have anything like List or Array in XSLT?
Is there any other way or trick I can use to solve this problem?
Thanks
Assuming the parameters are meant to be inserted in order of their ns1:sequence value, I would start by defining a key as:
<xsl:key name="text-param" match="ns1:textParameters" use="ns1:sequence" />
then call the following recursive template with ns1:description as the string param:
<xsl:template name="merge-params">
<xsl:param name="string"/>
<xsl:param name="i" select="0"/>
<xsl:choose>
<xsl:when test="contains($string, '{') and contains(substring-after($string, '{'), '}')">
<xsl:value-of select="substring-before($string, '{')" />
<xsl:value-of select="key('text-param', $i)/ns1:value" />
<!-- recursive call -->
<xsl:call-template name="merge-params">
<xsl:with-param name="string" select="substring-after($string, '}')" />
<xsl:with-param name="i" select="$i + 1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I've got a string that could have line breaks and apostrophes. I need to do a replace for both. I have the following xsl code:
<xsl:call-template name="replaceapostrophes">
<xsl:with-param name="string">
<xsl:call-template name="replacelinefeeds">
<xsl:with-param name="string" select="hl7:text/hl7:paragraph"/>
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
<!-- template for replacing line feeds with <br> tags for page display -->
<xsl:template name="replacelinefeeds">
<xsl:param name="string"/>
<xsl:choose>
<xsl:when test="contains($string,'
')">
<xsl:value-of select="substring-before($string,'
')"/>
<br/>
<xsl:call-template name="replacelinefeeds">
<xsl:with-param name="string" select="substring-after($string,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template for replacing html encoded apostrophes for page display -->
<xsl:template name="replaceapostrophes">
<xsl:param name="string"/>
<xsl:choose>
<xsl:when test="contains($string, ''')">
<xsl:value-of select="substring-before($string,''')"/>'<xsl:call-template name="replaceapostrophes">
<xsl:with-param name="string" select="substring-after($string,''')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This is the xml code:
<text>
<paragraph>Adding apostrophe to the patient's instructions
and checking for a second line</paragraph>
</text>
However, when this runs, I'm getting the apostrophe accounted for, but not the line breaks.
Adding apostrophe to the patient's instructions and checking for a second line
rather than
Adding apostrophe to the patient's instructions
and checking for a second line
It does work fine if there's one or the other but not both in the same string.
Is there a different way I need to do these?
Thanks
Try doing it the other way around (replace apostrophes first, then line breaks).
Basically you are putting an HTML <br/> element into your variable, and then taking its text value to replace the apostrophes , which removes the line break again.
Use the templates the other way round, i.e. first replace the apostrophes, then the line feeds. And make sure for the result of the replacement of line feeds you use xsl:copy-of and not xsl:value-of when you output it, as otherwise the br elements will be lost. So in case you have <xsl:variable name="text"><xsl:call-template name="replacelinefeeds">..</xsl:call-template></xsl:variable>, make sure you use <xsl:copy-of select="$text"/>.
In one of my xml message I have a particular tag in which some (unknown) of last positions are filled with a particular character(/). But during XSLT I want to remove this character and generate another string free from that characters in last positions.
Your question is very vague, but you would create an identity transform that copied every element recursively, with a special template for the elements you want to modify, like this
<xsl:template match="particular-tag">
<xsl:copy>
<xsl:value-of select="substring-before(., '/')"/>
</xsl:copy>
</xsl:template>
This will remove all characters from the first slash onwards from every <particular-tag> element.
If I understand the question correctly, you have a string which may end with zero or more slashes; you want a function or a template to strip the trailing slashes -- but not other slashes -- from the string. In XSLT 2.0, the simplest thing to do is write a function to do this. There are many ways to write the function; a straightforward one is this one (which generalizes the problem to stripping any given character off the end of a string, not just slash):
<xsl:function name="my:strip-trailing-char">
<xsl:param name="s" required="yes"/>
<xsl:param name="c" required="yes"/>
<xsl:choose>
<xsl:when test="ends-with($s,$c)">
<xsl:value-of select="my:strip-trailing-char(
substring($s,1,string-length($s) - 1),
$c
)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$s"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
In XSLT 1.0, you can do the same thing with a named template:
<xsl:template name="my:strip-trailing-char">
<xsl:param name="s" required="yes"/>
<xsl:param name="c" required="yes"/>
<xsl:choose>
<xsl:when test="ends-with($s,$c)">
<xsl:call-template name="my:strip-trailing-char">
<xsl:with-param name="s"
select="substring($s,1,string-length($s) - 1)"/>
<xsl:with-param name="c" select="$c"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$s"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
is any way to create function create wanted spaces in the output, because i was keep hot coding were ever i required space <xsl:text>
</xsl:text >, kindly suggest a function to generate a space based on the passed parameter,please advice
You may be looking at recursive template. Something like this:
<xsl:template name="WriteSpaces">
<xsl:param name="count" />
<xsl:if test="$count > 0">
<xsl:text> </xsl:text>
<xsl:call-template name="WriteSpaces">
<xsl:with-param name="count" select="$count - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Notice that invoking this would actually pollute your code even more than just putting spaces directly. For static content I would probably just use text. For dynamic content (i.e. if you want <xsl:text> </xsl:text><xsl:value-of select="." /><xsl:text> </xsl:text>) I would use concat() function as it does not limit the number of parameters - so you could code above just as <xsl:value-of select="concat(' ', ., ' ')" />.
I want to implement carriage return within xslt.
The problem is I have a varible:
Step 1 = Value 1 breaktag Step 2 = Value 2 as a string and would like to appear as
Step 1 = Value 1
Step 2 = Value 2
in the HTML form but I am getting the br tag on the page.Any good ways of implementing a line feed/carriage return in xsl would be appreciated
As an alternative to
<xsl:text>
</xsl:text>
you could use
<xsl:text>
</xsl:text> <!-- newline character -->
or
<xsl:text>
</xsl:text> <!-- carriage return character -->
in case you don't want to mess up your indentation
This works for me, as carriage-return + life feed.
<xsl:text>
</xsl:text>
The "
" string does not work.
The cleanest way I've found is to insert !ENTITY declarations at the top of the stylesheet for newline, tab, and other common text constructs. When having to insert a slew of formatting elements into your output this makes the transform sheet look much cleaner.
For example:
<?xml version="1.0"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY nl "<xsl:text>
</xsl:text>">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="step">
&nl;&nl;
<xsl:apply-templates />
</xsl:template>
...
</xsl:stylesheet>
use a simple carriage return in a xsl:text element
<xsl:text>
</xsl:text>
Try this at the end of the line where you want the carriage return. It worked for me.
<xsl:text><![CDATA[<br />]]></xsl:text>
I was looking for a nice solution to this, as many would prefer, without embedding escape sequences directly in the expressions, or having weird line breaks inside of a variable. I found a hybrid of both this approaches actually works, by embedding a text node inside a variable like this:
<xsl:variable name="newline"><xsl:text>
</xsl:text></xsl:variable>
<xsl:value select="concat(some_element, $newline)" />
Another nice side-affect of this is that you can pass in whatever newline you want, be it just LF, CR, or both CRLF.
--Daniel
Here is an approach that uses a recursive template, which looks for
in the string from the database and then outputs the substring before.
If there is a substring after
remaining, then the template calls itself until there is nothing left.
In case
is not present then the text is simply output.
Here is the template call (just replace #ActivityExtDescription with your database field):
<xsl:call-template name="MultilineTextOutput">
<xsl:with-param name="text" select="#ActivityExtDescription" />
</xsl:call-template>
and here is the code for the template itself:
<xsl:template name="MultilineTextOutput">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<xsl:variable name="text-before-first-break">
<xsl:value-of select="substring-before($text, '
')" />
</xsl:variable>
<xsl:variable name="text-after-first-break">
<xsl:value-of select="substring-after($text, '
')" />
</xsl:variable>
<xsl:if test="not($text-before-first-break = '')">
<xsl:value-of select="$text-before-first-break" /><br />
</xsl:if>
<xsl:if test="not($text-after-first-break = '')">
<xsl:call-template name="MultilineTextOutput">
<xsl:with-param name="text" select="$text-after-first-break" />
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" /><br />
</xsl:otherwise>
</xsl:choose>
Works like a charm!!!
I believe that you can use the xsl:text tag for this, as in
<xsl:text>
</xsl:text>
Chances are that by putting the closing tag on a line of its own, the newline is part of the literal text and outputted as such.
I separated the values by Environment.NewLine and then used a pre tag in html to emulate the effect I was looking for
This is the only solution that worked for me. Except I was replacing
with \r\n