XSL Attribute expression notation in text nodes - xslt

value-of is horrible. When I need to insert a large number of variable values into a text node, it really pollutes the XSL file.
Is there a way to be able to use attribute expression notation, i.e. text text {$variable}, on the inside of an output text node? Or at least something more concise than value-of?

Not in XSLT 1.0. However, in XSLT 3.0 you can use TVTs (text value templates). They work the same as AVTs (attribute value templates).
To use a TVT, add the standard attribute xsl:expand-text="yes" to the element. This will cause the processor to treat descendant text nodes of that element as a TVT.
Example:
XSLT 3.0
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="who" select="'Dan'"/>
<xsl:variable name="what" select="'BAM!'"/>
<result xsl:expand-text="yes">This is {$who}'s result: {$what}</result>
</xsl:template>
</xsl:stylesheet>
Output (using any well-formed XML as input)
<result>This is Dan's result: BAM!</result>
Note: Tested using Saxon-PE 9.5.
Here's a better example showing the "descendant" text nodes being evaluated...
XML Input
<test>
<v1>one</v1>
<v2>two</v2>
<v3>three</v3>
</test>
XSLT 3.0
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/*">
<result xsl:expand-text="yes">
<value>Value of v1: {v1}</value>
<value>Value of v2: {v2}</value>
<value>Value of v3: {v3}</value>
</result>
</xsl:template>
</xsl:stylesheet>
Output
<result>
<value>Value of v1: one</value>
<value>Value of v2: two</value>
<value>Value of v3: three</value>
</result>

You can use concat.
<xsl:value-of select="concat(
'text text ',
$variable,
'text text ',
$variable,
'text text'
)" />

Related

Split element within xslt file

I have the following input
UK/006/10
US/004/12
And wanted to get the following output.
Country: UK
Code:006
Line: 10
Country: US
Code:004
Line:12
I tried to use following, but I need something simple, like split function. Can someone help on this?
<xsl:value-of select="substring-before(substring-after($User_def_type_4, '/'), '/')" />
Using Invisible XML, you could define a grammar for your text data to map it to XML, then an extension function library like the CoffeeSacks library to Saxon Java can be used in XSLT to parse and post-process the text so that with e.g. the XML input being
<data>UK/006/10
US/004/12</data>
and the XSLT being
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:cs="http://nineml.com/ns/coffeesacks"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:template match="data">
<xsl:apply-templates select="cs:parse-string(cs:grammar-string($grammar), .)/node()"/>
</xsl:template>
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:next-match/>
<xsl:comment xmlns:saxon="http://saxon.sf.net/">Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
<xsl:param name="grammar" as="xs:string" expand-text="no">Countries = Country*.
Country = Name, -'/', Code, -'/', Line, #A?.
Name = ['A'-'Z'],['A'-'Z'].
Code = ['0'-'9'],['0'-'9'],['0'-'9'].
Line = ['0'-'9'],['0'-'9'].</xsl:param>
</xsl:stylesheet>
you get e.g.
<?xml version="1.0" encoding="UTF-8"?>
<Countries>
<Country>
<Name>UK</Name>
<Code>006</Code>
<Line>10</Line>
</Country>
<Country>
<Name>US</Name>
<Code>004</Code>
<Line>12</Line>
</Country>
</Countries>
<!--Run with SAXON HE 11.3 -->
Online sample using Saxon HE 11 Java and the named CoffeeSacks library.

XSLT - Get String between commas

How can I get the value 'four' in XSLT?
<root>
<entry>(one,two,three,four,five,six)</entry>
</root>
Thanks in advance.
You didn't specify the XSLT version, so I assume version 2.0.
I also assume that word four is only a "marker", stating from which place
take the result string (between the 3rd and 4th comma).
To get the fragment you want, you can:
Use tokenize function to "cut" the whole content of entry
into pieces, using a comma as the cutting pattern.
Take the fourth element of the result array.
This expression can be used e.g. in a template matching entry.
So the example script can look like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="entry">
<xsl:copy>
<xsl:value-of select="tokenize(., ',')[4]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
For your input XML it gives:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<entry>four</entry>
</root>

XSL query inner xml

Say this is my xml :
<History>
<University>TSU</University>
<Payload>
<Attrib Order="0">OVERSEA</Attrib>
<Attrib Order="1">GRADE2</Attrib>
<Attrib Order="2"><Person><ID>TQR344</ID></Person></Attrib>
<Attrib Order="3">3566644</Attrib>
</Payload>
</History>
And I want to query the inner XML inside Order=2 tag and read ID of the person.
I have created this so far :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="no" />
<xsl:template match="/History">
<xsl:apply-templates select="/History" />
</xsl:template>
<xsl:template name="Person" match="//History">
<Event>
<Uni><xsl:value-of select="University" /></Uni>
<ID><xsl:value-of select="Payload/Attrib[#Order='2']/Person/ID" disable-output-escaping="yes" /></ID>
</Event>
</xsl:template>
</xsl:stylesheet>
But as you can see it is not working.
Also I assigned the inner XML into a variable and tried to query that variable and It didn't work too.
Is it possible to do that via xsl ?
Limitations : I cannot change xml format. But maybe I was able to move from xsl ver 1 to new versions.
I want to query the inner XML inside Order=2 tag
The tag in question does not contain any XML; its content is a string and needs to be manipulated using string functions. Try:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/History">
<Event>
<Uni>
<xsl:value-of select="University" />
</Uni>
<ID>
<xsl:value-of select="substring-before(substring-after(Payload/Attrib[#Order='2'], '<ID>'),'</ID><')"/>
</ID>
</Event>
</xsl:template>
</xsl:stylesheet>
Note:
1. This:
<xsl:template match="/History">
<xsl:apply-templates select="/History" />
</xsl:template>
creates an infinite loop and will crash your processor.
2. Alternatively, you could serialize the string back into XML and process the result as XML; in XSLT 1.0, this can be done only by outputting the string with the escaping disabled, saving the result as a new document, then processing the new document with another XSLT stylesheet. Using XSLT 3.0 (or a processor that supports serializing as an extension) this can be all done during the same transformation.

Find node containing the most recent date when date needs converted to valid date format

I need an xPath to be used in a global variable which will select the 'Policy' node with the most recent dateTime (2014-12-02-04:00). Unfortanately the Time delimeter is a dash instead of 'T' so I can't use max() straight away. If I try to use substring or translate to remove the dashes and colon to simply compare numbers I get the error which states that there cannot be more that one sequence in those functions.
Is there a way to evaluate PolicyEffectiveDate from the root node when it is in 2014-12-02-04:00 format?
/Policies/PolicySummary/Policy[2]/PolicyEffectiveDate
XSLT 2.0 is OK. Also, note that I don't have control over the XML format. Thanks.
Given sample XML of:
<?xml version="1.0" encoding="UTF-8"?>
<Policies>
<PolicySummary>
<Policy>
<PolicyNumber>123</PolicyNumber>
<PolicyEffectiveDate>2014-06-01-04:00</PolicyEffectiveDate>
</Policy>
<Policy>
<PolicyNumber>1234</PolicyNumber>
<PolicyEffectiveDate>2014-12-02-04:00</PolicyEffectiveDate>
</Policy>
<Policy>
<PolicyNumber>12345</PolicyNumber>
<PolicyEffectiveDate>2014-08-02-04:00</PolicyEffectiveDate>
</Policy>
</PolicySummary>
</Policies>
You can simply sort the policies by their "dates" as text. For example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:for-each select="Policies/PolicySummary/Policy">
<xsl:sort select="PolicyEffectiveDate" data-type="text" order="descending"/>
<xsl:if test="position()=1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="utf-8"?>
<Policy>
<PolicyNumber>1234</PolicyNumber>
<PolicyEffectiveDate>2014-12-02-04:00</PolicyEffectiveDate>
</Policy>
in your example.

XSL: How to concatenate nodes with conditions?

I have the following code (eg):
<response>
<parameter>
<cottage>
<cot>
<res>
<hab desc="Lakeside">
<reg cod="OB" prr="600.84>
<lwz>TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#</lwz>
<lwz>TR#2#AB#200.26#0#QB#OK#20120830#20120831#EU#3-0#</lwz>
<lwz>TR#2#AB#200.26#0#QB#OK#20120831#20120901#EU#3-0#</lwz>
I need to create a concatenated string that includes the whole of the first 'lwz' line and then the price (200.26, but it can be different in each line) for each corresponding line.
So the output, separating each line with | would be:
TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#|200.26|200.26
Thanks
This XSLT 1.0 transformation:
<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="lwz[1]">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="lwz[position() >1]">
<xsl:value-of select=
"concat('
',
substring-before(substring-after(substring-after(substring-after(.,'#'),'#'),'#'),'#')
)
"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided text (converted to a well-formed XML document !!!):
<response>
<parameter>
<cottage>
<cot>
<res>
<hab desc="Lakeside">
<reg cod="OB" prr="600.84">
<lwz>TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#</lwz>
<lwz>TR#2#AB#200.26#0#QB#OK#20120830#20120831#EU#3-0#</lwz>
<lwz>TR#2#AB#200.26#0#QB#OK#20120831#20120901#EU#3-0#</lwz>
</reg>
</hab>
</res>
</cot>
</cottage>
</parameter>
</response>
produces the wanted, correct result:
TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#
200.26
200.26
II XSLT 2.0 solution:
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="lwz[1]">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="lwz[position() >1]">
<xsl:value-of select=
"concat('
', tokenize(.,'#')[4])"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the above XML document, again produces the wanted, correct result. Note the use of the standard XPath 2.0 function tokenize():
TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#
200.26
200.26
You can use the XPath substring function to select substrings from your lwz node data. You don't really give much more detail about your problem, if you want a more detailed answer, perhaps provide the full XML document and your best-guess XSLT