Determining which disjunct in an xsl:template match matched - xslt

Suppose I have XML like this:
<xsl:template match="assessment | section ">
.
.
.
</xsl:template>
I do this because I mostly want to treat assessment and section nodes the same. However, I do want to treat them a little differently. How can I tell whether a match was for assessment or for section?

Do something like this:
<xsl:if test="name()='section'">
</xsl:if>

You may test for:
self::assessment
which is slightly more efficient than using the name() function.
However in cases like this I will put the common code in another template (either named or in a named mode) and will instantiate the common processing like this:
<xsl:apply-templates select="." mode="common"/>

Related

How to use xsl:number count as condition in xsl:if test "condition"?

Within an xsl:for-each select loop, I have <xsl:number count="//headline"/> that correctly gives me the node #; Now I want to use that number in an xsl:if test block, but I cannot get the test expression right, msxml4.dll keeps kicking back errors. Am using xsl 1.0 (and stuck with it for now)
So, in <xsl:if test="expression">...output if the expression is true..</xsl:if>
I want the test expression to essentially be like this (so I can do something specific for Node #4, in this example):
<xsl:number count="//headline"/> = 4
This is what I have that does not work:
<xsl:if test="<xsl:number count="//headline"/> = 4">
Thanks in advance for any insights,
George
As #michael.hor257k explains, the general approach is to put the xsl:number call inside an xsl:variable (or in XSLT 2.0, inside an xsl:function). Sometimes though it's more convenient to abandon xsl:number:
<xsl:if test="count(preceding::headline) = 3">...</xsl:if>
If it's a big document then both xsl:number and preceding::headline are potentially expensive when executed inside a loop, and if this is a concern then you should compare them under your chosen XSLT processor: one may be optimized better than the other.
Come to think of it, your use of xsl:number looks odd to me. The count attribute is a pattern, and the pattern //headline matches exactly the same nodes as the pattern headline. As a result I misread your call on xsl:number as counting all the headlines in the document, whereas it actually only counts the preceding-sibling headlines. I wonder if that is what you intended?
If (!) I understand correctly, you want to do something like:
<xsl:variable name="n">
<xsl:number count="headline"/>
</xsl:variable>
<xsl:value-of select="$n"/>
<xsl:if test="$n = 4">
<!-- do something -->
</xsl:if>

xslt variable xpath expressions

Can someone help me out for the following solution:
<xsl:variable name="filterByNameA" select="*/person[firstName=$fname] "/>
I want to do this if possible
<xsl:variable name="filterByNameA" select="*/person[firstName=$fname] and */person[secondName=$fname]"/>
In other words, I need to have two conditions in one variable in order to get me all of the person elements that have the same firstName and secondName.
Put the two tests inside the same predicate:
<xsl:variable name="filterByNameA" select="*/person[firstName=$fname and secondName=$fname]"/>
Or equivalently, use two predicates:
<xsl:variable name="filterByNameA" select="*/person[firstName=$fname][secondName=$fname]"/>
When you have consecutive predicates like that, each one filters the list that resulted from the one before, so they are effectively anded together

How to easily generate unique strings in XSLT?

I have a question!
I have an XML document that has sections and subsections. I am generating a Doxygen page out of it using XSLTProc and now I have a problem. When I generate a section name like this:
<xsl:template match="SECTION/SUBSECTION">
#subsection <xsl:value-of select="#title"/>
<xsl:apply-templates/>
</xsl:template>
Then the first word of the title does not show up, because Doxygen expects the declaration in this way:
#subsection <subsectionname> <subsectiontitle>
So, the first word is automatically treated as the subsection name. Putting a randomly generated string there does not seem like a very simple task. I tried to put unique number instead, by using <xsl:value-of select="count(preceding-sibling::*[#col]) + 1", which worked as expected, but as it turns out, Doxygen does not accept numbers as subsection names. I also tried to strip white spaces of "#title" and use that as the subsection name, but XSLTProc complains that it was not an immediate child of <xslt:stylesheet>. How can I easily put some unique string there? It does not have to be meaningful text.
Thanks in advance!
Use the generate-id() function.
<xsl:value-of select="generate-id(#title)"/>
If you want the generated string to be more "readable", here is one way to do this:
<xsl:value-of select="concat(#title, generate-id(#title))"/>

xsl: how to use parameter inside "match"?

My xsl has a parameter
<xsl:param name="halfPath" select="'halfPath'"/>
I want to use it inside match
<xsl:template match="Element[#at1='value1' and not(#at2='{$halfPath}/another/half/of/the/path')]"/>
But this doesn't work. I guess a can not use parameters inside ''. How to fix/workaround that?
The XSLT 1.0 W3C Specification forbids referencing variables/parameters inside a match pattern.:
"It is an error for the value of the
match attribute to contain a
VariableReference"
There is no such limitation in XSLT 2.0, so use XSLT 2.0.
If due to unsurmountable reasons using XSLT2.0 isn't possible, put the complete body of the <xsl:template> instruction inside an <xsl:if> where the test in conjunction with the match pattern is equivalent to the XSLT 2.0 match pattern that contains the variable/parameter reference(s).
In a more complicated case where you have more than one template matching the same kind of node but with different predicates that reference variables/parameters, then a wrapping <xsl:choose> will need to be used instead of a wrapping <xsl:if>.
Well, you could use a conditional instruction inside the template:
<xsl:template match="Element[#at1='value1']">
<xsl:if test="not(#at2=concat($halfPath,'/another/half/of/the/path'))">
.. do something
</xsl:if>
</xsl:template>
You just need to be aware that this template will handle all elements that satisfy the first condition. If you have a different template that handles elements that match the first, but not the second, then use an <xsl:choose>, and put the other template's body in the <xsl:otherwise> block.
Or, XSLT2 can handle it as is if you can switch to an XSLT2 processor.
This topic had the answer to my question, but the proposed solution by Flynn1179 was not quite correct for me (YMMV). So try it the way it is suggested by people more expert than me, but if it doesn't work for you, consider how I solved it. I am using xsltproc that only handles XSL version 1.0.
I needed to match <leadTime hour="0024">, but use a param: <xsl:param name="hour">0024</xsl:param>. I found that:
<xsl:if test="#hour='{$hour}'"> did not work, despite statements here and elsewhere that this is the required syntax for XSL v.1.0.
Instead, the simpler <xsl:if test="#hour=$hour"> did the job.
One other point: it is suggested above by Dimitre that you put template inside if statement. xsltproc complained about this: instead I put the if statement inside the template:
<xsl:template match="leadTime">
<xsl:if test="#hour=$leadhour">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:if>
</xsl:template>
In XSLT 2.0 you can refer to global variables within a match pattern, but the syntax is simpler than your guess:
<xsl:template match="Element[#at1='value1' and
not(#at2=$halfPath/another/half/of/the/path)]"/>
rather than
<xsl:template match="Element[#at1='value1' and
not(#at2='{$halfPath}/another/half/of/the/path')]"/>
Also, the semantics are not what you appear to be expecting: a variable referenced on the lhs of "/" must contain a node-set, not a fragment of an XPath expression.

XSLT: Turn <element></element> into <element />

What do I need to do to this:
<xsl:template match="xs:simpleType">
<xsl:copy>
<xsl:copy-of select="node()[not(self::xs:annotation or self::xs:restriction)]|#*"/>
</xsl:copy>
</xsl:template>
Currently, this turns out:
<xs:simpleType xmlns:core="urn:org:pesc:core:CoreMain:v1.4.0" name="SINIDType">
</xs:simpleType>
I'd prefer it to look like:
<xs:simpleType name="SINIDType" />
with those blank lines in there, it looks like your select statement is (correctly) selecting your whitespace nodes as well as your elements. try using
select="*[not(self::xs:annotation or self::xs:restriction)]|#*"
which will only match element nodes, not text nodes.
The serializer is responsible for whether an empty element is emitted as <abc></abc> or <abc/>, and they are exactly equivalent. Some serializers give no option, and will always produce one or the other.
But it might be that you are emitting whitespace between them; in that case you'll have to change the xsl:copy to something else that doesn't include the insignificant whitespace, like by adding or text() = '' to your predicate.
As far as eliminating the xmlns:core namespace declaration, that depends on your context. It will be generated always if there is an element that requires it inside of your type, or if you're using XSLT and haven't excluded the namespace with the #exclude-result-prefixes attribute on the <xsl:stylesheet> root element. And even then, depending on your processor environment, the serializer may "decide" that it wants that namespace output needlessly, since it was in scope in the input.
Also, it's kind of odd to see <xsl:copy> ... <xsl:copy-of .../> ... </xsl:copy>. You really shouldn't wrap the copy-of inside of the copys. Just make it copy-of.