Define xsl variable to for terminate attribute of <xsl:message> does not work:
<xsl:variable name="TERMINATE_ON_ERROR" select="'no'" />
<xsl:message terminate="$TERMINATE_ON_ERROR">
<xsl:text>foo</xsl:text>
</xsl:message>
<!-- ... -->
<xsl:message terminate="$TERMINATE_ON_ERROR">
<xsl:text>bar</xsl:text>
</xsl:message>
This forces me to use terminate="no" for all cases:
<xsl:message terminate="no">
<xsl:text>foo</xsl:text>
</xsl:message>
<!-- ... -->
<xsl:message terminate="no">
<xsl:text>bar</xsl:text>
</xsl:message>
and then replace all of them if I change my mind instead of changing just single variable.
I prefer solution for XSLT 1.0 (using xsltproc).
In XSLT 2 (https://www.w3.org/TR/xslt20/#message) and 3 (https://www.w3.org/TR/xslt-30/#element-message) the terminate attribute allows an attribute value template (e.g. terminate="{$TERMINATE_ON_ERROR}") but XSLT 1 https://www.w3.org/TR/xslt-10/#message doesn't seem to allow it.
So the main XSLT like approach in that case of XSLT 1 would be to write one stylesheet to create a second stylesheet, for that you have to use xsl:namespace-alias, as shown in https://www.w3.org/TR/xslt-10/#section-Creating-Elements-and-Attributes.
Related
I have an XSLT template that passes a chunk of HTML into parameter. I need to pass that parameter to another template where it will be transformed one of two ways, depending upon the ID attribute of the top level HTML element contained in the parameter. Is it possible to test the content of a parameter with Xpath in XSLT 1.0? Here's my HTML:
<div id="someID">There could be many child nodes here</div>
Here's my XSLT test:
<xsl:when test="$content/#id = 'someID'">...</xsl:when>
Saxon 6.5.3 fails on this and tells me to switch to use exsl or specify version 1.1 (which has no impact), but I'd love to know if there's a clever way to achieve this using the tools I have in place.
The parameter is declared in the template as follows: <xsl:param
name="content"><xsl:apply-imports/></xsl:param>.
Strictly speaking, that's a variable, not a parameter. And its content is a result tree fragment. As the error message says, you must convert it to a node-set before you can process it further (in XSLT 1.0).
So do something like:
<xsl:variable name="content-rtf">
<xsl:apply-imports/>
</xsl:variable>
<xsl:variable name="content" select="exsl:node-set($content-rtf)" />
<xsl:choose>
<xsl:when test="$content/div/#id = 'someID'">
<!-- some result -->
</xsl:when>
<xsl:otherwise>
<!-- another result -->
</xsl:otherwise>
</xsl:choose>
after adding:
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
to your opening <xsl:stylesheet> tag.
Untested because a reproducible example was not provided.
<xsl:when test="$content/div[#id = 'someID']">...</xsl:when>
I have xml like defined below . The node EducationDetails can repeat (unbounded).
<PersonalDetailsResponse>
<FirstName></FirstName>
<LastName></LastName>
<EducationDetails>
<Degree></Degree>
<Institution></Institution>
<Year></Year>
</EducationDetails>
<EducationDetails>
<Degree></Degree>
<Institution></Institution>
<Year></Year>
</EducationDetails>
</PersonalDetailsResponse>
I want to create another xml from the above one using xslt.
My requirement is, if there is no data in any of the EducationDetails child nodes , then the resulting xml has to get data from another source.
My problem is , I am not able to check if all the EducationDetails child nodes are empty.
Since variable value cannot be changed in xslt , I tried using saxon with below code.
xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"
<xsl:variable name="emptyNode" saxon:assignable="yes" select="0" />
<xsl:when test="count(ss:education) > 0">
<xsl:for-each select="ss:education">
<xsl:if test="not(*[.=''])">
<saxon:assign name="emptyNode">
<xsl:value-of select="1" />
</saxon:assign>
</xsl:if>
</xsl:for-each>
<xsl:if test="$emptyNode = 0">
<!-- Do logic if all educationdetails node is empty-->
</xsl:if>
</xsl:when>
But it throwing exception "net.sf.saxon.trans.XPathException: Unknown extension instruction " .
It looks like saxon 9 jar is required for it ,which I am not able to get from my repository.
Is there a simpler way to check if all the child nodes of are empty.
By empty I mean, child nodes might be present, but no value in them.
Well, if you use <xsl:template match="PersonalDetailsResponse[EducationDetails[*[normalize-space()]]">...</xsl:template> then you match only on PersonalDetailsResponse element having at least one EducationDetails element having at least one child element with non whitespace data. As you seem to use an XSLT 2.0 processor you can also use the perhaps clearer <xsl:template match="PersonalDetailsResponse[some $ed in EducationDetails/* satisfies normalize-space($ed)]">...</xsl:template>.
Or perhaps if you want a variable inside the template use
<xsl:template match="PersonalDetailsResponse">
<xsl:variable name="empty-details" select="not(EducationDetails/*[normalize-space()])"/>
<xsl:if test="$empty-details">...</xsl:if>
</xsl:template>
With XSLT 2.0 the use of some or every satisfies might be easier to understand e.g.
<xsl:template match="PersonalDetailsResponse">
<xsl:variable name="empty-details" select="every $dt in EducationDetails/* satisfies not(normalize-space($dt))"/>
<xsl:if test="$empty-details">...</xsl:if>
</xsl:template>
But usually writing templates with appropriate match conditions saves you from using xsl:if or xsl:choose inside of a template.
I am trying to use XSL choose condition. here I am trying to acheive is, when client sends below xml to Dp System. My XSLT should look for values matching Pv(a) or Pv(b) or Pv(c), if any of these are mactched then send to the backend url which is mentioned in the xsl
else
invoke another rule which is called "Do not call rule" (which is nothing but, picks the local file called error.xml
Thanks for the help
Input xml
<DownloadProfileChannels>
<DownloadProfileChannel>
<IntervalLength>60</IntervalLength>
<PulseMultiplier>0.025</PulseMultiplier>
<Category>Pv(a)</Category> <!-- for every Pv(a) or Pv(b) or Pv(c) -->
<TimeDataEnd>2014-02-20T08:00:00Z</TimeDataEnd>
<MedianValues>
<MedianValue>
<ChannelValue>9112</ChannelValue>
<ProfileStatuses i:nil="true" />
</MedianValue>
<MedianValue>
<ChannelValue>9096</ChannelValue>
<ProfileStatuses i:nil="true" />
</MedianValue>
<MedianValue>
<ChannelValue>9188</ChannelValue>
<ProfileStatuses i:nil="true" />
</MedianValue>
</MedianValue>
</MedianValues>
</DownloadProfileChannel>
</DownloadProfileChannels>
My XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<xsl:message dp:priority="debug"> Entered the XSL File </xsl:message>
</xsl:message>
<xsl:choose>
<xsl:when test="contains($Quantity,'Pv(a) or Pv(b)')">
<xsl:variable name="destURL"
select="http://backendurl.com"/>
<dp:set-variable name="'var://service/routing-url'"
value="$destURL"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="destURL"
select="local:///clienterror.xml"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This line:
<xsl:when test="contains($Quantity,'Pv(a) or Pv(b)')">
Is checking if $Quantity contains the literal string 'Pv(a) or Pv(b)'. You need to separate these out into two check like so:
<xsl:when test="contains($Quantity,'Pv(a)') or contains($Quantity,'Pv(b)')">
Firstly, as #Lego Stormtroopr says, you need to separate out the two conditions. But also, I don't think you really want a "contains" test here, you want an "=" test. The contains function would match Pv(a)(b)(c) - anything that has Pv(a) as a substring, whereas I think you want to match the whole node. So it becomes
<xsl:when test="$Quantity = 'Pv(a)' or $Quantity ='Pv(b)'">
which, if you are using XSLT 2.0, can be further abbreviated to
<xsl:when test="$Quantity = ('Pv(a)', 'Pv(b)')">
Alternatively, in XSLT 2.0 you can use regular expression matching:
<xsl:when test="matches($Quantity, 'Pv([ab])')">
In a rather complex xslt file some elements are to be processed twice. This is done by a template with a paramater.
<xsl:template macht="x">
<xsl:param name="modus"/>
<!-- comon things to do for both cases -->
<xsl:choose>
<xsl:when test="$modus='case1'"> <!-- things to do in case 1 --> </xsl:when>
<xsl:when test="$modus='case2'"> <!-- things to do in case 2 --> </xsl:when>
</xsl:choose>
</xsl:template>
The problem is: I cannot simply apply or call this template directly. The element x (for which this template with these two cases is for) is often at a quite low level of the xml input. Almost all ancestor elements have to be processed (in both cases) before it actually comes to x.
The call for the two cases is at the allmost top level.
In html it would be like this
<body>
<h1>Case 1</h1>
<xsl:apply-templates><xsl:with-parameter name="modus" select="case1"/>
<h1>Case 2</h1>
<xsl:apply-templates><xsl:with-parameter name="modus" select="case2"/>
</body>
So. How can I make sure, that the parameter reaches the template for x?
Of course, I could replace all
<xsl:apply-templates/>
calls within the templates for every single ancestor element of x by
<xsl:param name="modus">
<!-- What ever content here -->
<xsl:apply-templates><xsl:with-parameter name="modus" select="$modus"/></apply-templates>
But that would mean a lot of effort. Is there a better way to do this?
XSLT 2.0 has tunnel parameters e.g. with
<xsl:apply-templates>
<xsl:with-param name="modus" tunnel="yes" select="'foo'"/>
</xsl:apply-templates>
and
<xsl:template match="bar">
<xsl:param name="modus" tunnel="yes"/>
...
</xsl:template>
you don't have to pass on the parameter explicitly in the templates for ancestors of bar. So using an XSLT 2.0 processor like Saxon 9 you can do that.
I need to be able to store a node set in variable and then perform more filting/sorting on it afterward. All the examples I've seen of this involve either using XSL2 or extensions neither of which are really an option.
I've a list of hotels in my XML doc that can be sorted/filtered and then paged through 5 at a time. I'm finding though I'm repeating alot of the logic as currently I've not found a good way to store node-sets in xsl variable and then use xpath on them for further filtering/sorting.
This is the sort of thing I'm after (excuse the code written of the top of my head so might not be 100%):
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="3_star_or_less" select="/results/hotels[number(rating) <= 3]" />
<xsl:for-each select="3_star_or_less">
<xsl:sort select="rating" />
</xsl:for-each>
Has anyone got an example of how best to do this sort of thing?
Try this example:
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="three_star_or_less"
select="$hotels[number(rating) <= 3]" />
<xsl:for-each select="$three_star_or_less">
<xsl:sort select="rating" />
<xsl:value-of select="rating" />
</xsl:for-each>
There is no problem storing a node-set in a variable in XSLT 1.0, and no extensions are needed. If you just use an XPath expression in select attribute of xsl:variable, you'll end up doing just that.
The problem is only when you want to store the nodes that you yourself had generated in a variable, and even then only if you want to query over them later. The problem here is that nodes you output don't have type "node-set" - instead, they're what is called a "result tree fragment". You can store that to a variable, and you can use that variable to insert the fragment into output (or another variable) later on, but you cannot use XPath to query over it. That's when you need either EXSLT node-set() function (which converts a result tree fragment to a node-set), or XSLT 2.0 (in which there are no result tree fragments, only sequences of nodes, regardless of where they come from).
For your example as given, this doesn't seem to be a problem. Rubens' answer gives the exact syntax.
Another note, if you want to be able to use the variable as part of an XPath statement, you need to select into the variable with <xsl:copy-of select="."/> instead of <xsl:value-of select="."/>
value-of will only take the text of the node and you wont be able to use the node-set function to return anything meaningful.
<xsl:variable name="myStringVar">
<xsl:value-of select="."/>
</xsl:variable>
<!-- This won't work: -->
<Output>
<xsl:value-of select="node-set($myStringVar)/SubNode" />
</Output>
<xsl:variable name="myNodeSetVar">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- This will work: -->
<Output>
<xsl:value-of select="node-set($myNodeSetVar)/SubNode" />
</Output>