This question already has answers here:
Is there a way to use mode in an xsl:if test?
(2 answers)
Closed 2 years ago.
Suppose an XSLT template matches an element in two modes, is it possible to figure out dynamically which mode triggered the match?
In pseudo xslt:
template match="elementname" mode="a b"
if %mode% = 'a' then do something
if %mode% = 'b' then do something
(this would be useful in situations where 90% of the template is identical for both modes, but in one case an additional attribute needs to be created)
(this question was already asked Is there a way to use mode in an xsl:if test? My mistake for not spotting that. Thanks for the replies anyway)
You could always just use a parameter:
<xsl:apply-templates select="*" mode="ab">
<xsl:with-param name="sw">a</xsl:with-param>
</xsl:apply-templates>
<xsl:template select="elementname" mode="a b">
<xsl:param name="sw" />
<xsl:if test="$sw='a'>do this</xsl:if>
</xsl:template>
Related
Part of some XSLT I am working on is this very simple template to show up an unresolved reference type of error.
<!-- a basic check when matching on copying index elements - are they referring to a defined item element -->
<xsl:template match="index" mode="expand">
<xsl:variable name="index_name_xml"><xsl:value-of select="#name"/></xsl:variable>
<xsl:if test="not(//item[#name=$index_name_xml])">
<xsl:message terminate="yes"><xsl:value-of select="concat('FAIL : cannot find "',$index_name_xml,'" in items')"/></xsl:message>
</xsl:if>
</xsl:template>
When this element
<index name="User X Ordinate"/>
is matched in input doc the above template is called, the templates xpath SHOULD find this node (in input doc)
<item name="User X Ordinate" address="UserXOrd_s" usage="realtime" type="uint16_t" unit="unit_ordinate_q8" />
but it doesn't and I get my fail message
FAIL : cannot find "User X Ordinate" in dbitems Error at char 7 in xsl:value-of/#select on line 253 column 130 of db_expander.xsl:
XTMM9000: Processing terminated by xsl:message at line 253 in db_expander.xsl
and I am scratching my head as there are dozens of cases in my transformation where the template does what I want, and TWO cases when it doesn't (a clue I cant figure out yet). I cant see any spelling errors and the two slashes in the xpath should mean ALL 'item' elements at any level in the document are checked. I cant see how is doesn't work.
EDIT :: Apologies for this amateurish post. I kind of got lost trying to recreate a simple version of the problem where I could post the whole source. My partial understanding is that the problem may be related to how the XSL is passing the node /context into the template -- its slightly out of my depth at the moment but -- result tree fragment / context in the source XML?
However, if I add a 'root' variable into the template (shown below) the template does what I want -- the problems are gone -- so the problem seems to be relating to the context being passed. I tried but failed to make a small stand alone example that fails to post here -- my tests kept working...so I am obviously still not grasping a finer point(s) yet.
<xsl:variable name="root" select="/"/>
<xsl:template match="index" mode="expand">
<xsl:variable name="index_name" > <xsl:value-of select="#name"/></xsl:variable>
<xsl:choose>
<xsl:when test="$root//dbgroup//item[#name=$index_name]">
<!--xsl:message terminate="no">
<xsl:value-of select="concat('item found for : ',$index_name, ' (parent is ',parent::node()/#name,')')"/>
</xsl:message-->
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="no">
<xsl:value-of select="concat('item NOT found for : ',$index_name, ' (parent is ',parent::node()/#name,')')"/>
</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I need to do some more reading as I dont know a good way to debug this other than xsl:messages....
If a template is supplied with an RTF (result tree fragment) then unfortunately '//*' doesn't refer to ANY element in the hierarchy of the source document anymore, but rather ANY element in the RTF hierarchy which does not contain all the elements (in my case / most cases) of the source document.
Hence why I needed to use the $root variable inside the template in my 'EDIT' above in order to get access to elements not in the RTF.
The trick is knowing that you will end up with an RTF when you apply templates within a variable declaration in order to populate it. And so when this is passed to a template, you will need another way to get back to the context of your source document.
This question already has answers here:
Check type of node in XSL template
(3 answers)
Closed 4 years ago.
I'm trying to write a XSLT1.0 template that accepts a node as parameter. Inside that template, I need to test if the node passed as parameter is of particular type, in my case a text node. I can check the type of the current node via self::text() and similar constructs, but how do I do that when the node in question is given by a variable?
Here's a piece of code that actually does what I need, but I think there must be a more straightforward way to achieve that. This $node/../text() does not seem right to me, to say the least.
<xsl:template name="renderCommand">
<xsl:param name="node"/>
<xsl:variable name="nodeName">
<xsl:choose>
<xsl:when test="$node/../text()">
<xsl:value-of select="name($node)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('.', name($node))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
</xsl:template>
If you have a variable representing a node and want to check it is a text node then using when="$node/self::text()" suffices.
I have an XSL template that gives different results when used in two different contexts.
The template manifesting the defect is:
<xsl:template match="*" mode="blah">
<!-- snip irrelevant stuff -->
<xsl:if test="see">
<xsl:message>Contains a cross-ref. <xsl:value-of select="."/></xsl:message>
</xsl:if>
<xsl:apply-templates select="."/>
</xsl:template>
Given:
<el>This is a '<see cref="foo"/>' cross-referenced element.</el>
In one situation, I get the desired result:
Contains a cross-ref. This is a ' ' cross-referenced element.
(the <see/> is being dealt with as an XML element and is ultimately matched by another template.)
But in another situation, the xsl:if doesn't trigger and if I output the contents with <xsl:message><xsl:value-of select="."/>, I get:
This is a '<see cref="foo"/>' cross-referenced element.
It seems to me that in the latter improperly-behaving scenario, it's acting like it's been output-escaped. Does that make sense? Am I barking up the wrong tree? This is a typically complex XSL situation and trying to trace the call-stack is difficult; is there a particular XSLT processing command I should be looking for?
I've been trying to wrap my head around using XPath and XQuery for this with the help of some previous posts to no avail. Right now I have null child nodes which should just default to ordering at the end of a sort but unfortunately, the sort does not occur at all on these null nodes. As a result I have been trying to find a way to set them to zero during the sorting section. Here is a sample below:
<xsl:for-each select="MyItems/Item">
<xsl:sort select="Order/obj/Number" order="ascending">
I want to do something similar to an inline if statement as part of the sort like in C# below:
foreach(item in MyItems.OrderBy(Order/obj/Exists != false ? Order/obj/Number : 0)
I was using these links: dynamic xpath expression and XSLT transfom with inline if statements to try and understand but I'm still not getting it. Any help is appreciated. I need the solution in XSLT.
Your situation is unclear as you say nothing about the contents of your XML or the nature of your XSLT transform. But it sounds something like you have Item elements with no Order/obj/Number elements to sort on?
I would code that something like this
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="MyItems/Item[Order/obj/Number]">
<xsl:sort select="Order/obj/Number" />
</xsl:apply-templatesh>
<xsl:apply-templates select="MyItems/Item[not(Order/obj/Number)]" />
</xsl:copy>
</xsl:template>
<xsl:template select="MyItems/Item">
<xsl:copy-of select="current()" />
</xsl:template>
Talking about "null nodes" isn't helpful. It's not a well-defined term. Show us your XML, your desired results and your actual results, and we can help you.
What should happen is that if the select expression in xsl:sort returns an empty sequence/node-set, the effective sort key is a zero-length string, so these items sort before any others (assuming ascending order).
I'm working with xslt for the first time. I have 2.0, but that's about the only advantage I have access to with the c# transform library we have. I'm trying to count a number of child nodes in the XML document which contain a date more than 12 years ago and have a certain type attribute.
Sample xml structure:
<xml version=\"1.0\" encoding=\"utf-8\"?>
<... />
<Dependents>
<Dependent><DOB>1964-04-01</DOB><DependentType>Spouse</DependentType></Dependent>
<Dependent><DOB>2000-01-01</DOB><DependentType>Child</DependentType></Dependent>
<Dependent><DOB>2012-01-01</DOB><DependentType>Child</DependentType></Dependent>
</Dependents>
<... />
where <... /> signifies some extra unrelated stuff.
So essentially, I want the number of children younger than 12 years old. (I have the count of dependenttype = child of all ages working, it's just the under 12 that's giving me trouble). The approach that was suggested to me was to construct a variable that stands for 12 years ago from today and use that as a basis for comparison in the count() function. That sounds reasonable, but I'm stuck constructing the date without the use of 3rd party libraries (e.g. exslt) that are so often linked in questions like this for the easy, handy-dandy answers.
The xslt I've gotten so far for this is:
<xsl:variable name="today" select="current-dateTime()" as="xs:dateTime" />
<xsl:variable name="twelveyearsago" select="xs:dateTime(concat(year-from-dateTime($today) - 12, '-', month-from-dateTime($today), '-', day-from-dateTime($today)))" />
<xsl:text>12yearsago=</xsl:text><xsl:value-of select="$twelveyearsago" />
And it's not working because the month-from-dateTime (and presumable day-from-dateTime) don't add leading zeros. For today, March 21 2012 I'm getting back: Saxon.Api.DynamicError : Invalid dateTime value "2000-3-21" (Month must be two digits) (The W3Schools xpath function reference implies that they should, but they don't.)
What I would like to output is:
<xsl:text>&numberofchildren=</xsl:text><xsl:value-of select="count(//InsuranceRequest/HealthInsurance/Dependents/Dependent/DependentType[text() = 'Child'])" />
<xsl:text>&childrenunder12=</xsl:text><xsl:value-of select="children under twelve" />
The more I pound my head against this, the more I feel like there's a simpler approach that I'm just not seeing.
Edit: I cleaned up the xslt syntax so it's valid and not a doubly-quoted c# string.
You can simply substract a duration of 12 years as in <xsl:variable name="twelveyearsago" select="$today - xs:yearMonthDuration('P12Y')"/>, then use e.g. //Dependent[DependentType = 'Child' and xs:date(DOB) >= $twelveyearsago].
[edit]
Here is a template that compiles and executes fine for with Saxon 9.4:
<xsl:template match="/">
<xsl:variable name="today" select="current-date()"/>
<xsl:variable name="twelve-years-ago" select="$today - xs:yearMonthDuration('P12Y')"/>
<xsl:value-of select="count(//Dependent[DependentType = 'Child' and xs:date(DOB) >= $twelve-years-ago])"/>
</xsl:template>