How to apply-templates to all templates except a spefic one - xslt

In the code bellow I'm applying all the templates to the element chapter (in the end of the code) but I would like to know how it is possible to apply all the templates to this element except a spefic one. In this case it's the element title because I'm already selecting it in the line before and it appears repeated in the html file. Someone?
<xsl:template match="chapter">
<h3>
<a name="{#id}"><xsl:value-of select="title"/></a>
</h3>
<xsl:apply-templates/>
</xsl:template>
Output:
<h3>Title</h3>
Title<br>
Text.

A plain <xsl:apply-templates/> is equivalent to <xsl:apply-templates select="node()" />, i.e. all child nodes. You can exclude certain nodes by using the XPath 2.0 except operator, e.g.
<xsl:apply-templates select="node() except title" />
This would select all child nodes except those that are elements with the name title. If you are only interested in child elements (not text nodes etc.) then you could use * except title instead.
The except operator essentially implements set difference - you're not limited to simple element names on the right, you can use any expression that returns a sequence of nodes, e.g.
node() except (title | div[#class = 'heading'])
X except Y selects all nodes that are in the sequence selected by X and not also in the sequence selected by Y

Related

xsl 3.0: How to process certain child elements first in xsl:apply-templates, then the remainder (overriding document order)

Assume my xml input is a MFMATR element with a few child elements, such as: TRLIST, INTRO, and SBLIST -- in that document order. I am converting to HTML.
I have a template that matches on the MFMATR element, and wants to run xsl:apply-templates on the 3 child elements, but I want INTRO to be processed first (listed first in the HTML). The other two (TRLIST and SBLIST) should keep their relative document order, as long as INTRO comes before both of them.
So I'd like to run <xsl:apply-templates select="INTRO, *"> but not have INTRO matched twice. (Using this syntax with xsl 3.0 causes dupes for me.) I also don't want to explicitly list every tag in the select expression, so unknown tags will still be processed.
A 2nd real life example is this: <xsl:apply-templates select="TITLE, CHGDESC, *"/>. Again, right now that is causing dupes I don't want.
I am using Saxon.
So I'd like to run <xsl:apply-templates select="INTRO, *"> but not have INTRO matched twice
Try:
<xsl:apply-templates select="INTRO, * except INTRO">
This seems to work. If someone has a better answer, let me know and I will change it.
There is no DRY violation here -- no repeated element names or variable names. I want it to look clean at all the call sites I will have.
It seems idiomatic to me since the function was pulled from w3's own website!
<xsl:template match="MFMATR">
<!-- Process INTRO first, no matter where it appears -->
<xsl:variable name="nodes" select="INTRO, *"/>
<xsl:apply-templates select="kp:distinct_nodes_stable($nodes)"/>
</xsl:template>
<xsl:template match="INTRO">
<xsl:variable name="nodes" select="TITLE, CHGDESC, *"/>
<xsl:apply-templates select="kp:distinct_nodes_stable($nodes)"/>
</xsl:template>
<!-- Discard duplicate elements in $seq, but keep their ordering -->
<!-- Adapted from https://www.w3.org/TR/xpath-functions/#func-distinct-nodes-stable -->
<xsl:function name="kp:distinct_nodes_stable" as="node()*">
<xsl:param name="seq" as="node()*"/>
<xsl:sequence select="fold-left($seq, (),
function($foundSoFar as node()*, $this as node()) as node()* {
if ($foundSoFar intersect $this)
then $foundSoFar
else ($foundSoFar, $this)
}) "/>
</xsl:function>

XSLT 1.0 - change the order of lines

I want to change the order of line cac:InvoiceLine depending on this node:
cac:AdditionalItemProperty/cbc:Value
All InvoiceLines that have Item type=RC must be gruop at the end of lines, and all that have CU must be on the top.
If the mentioned values are the only ones you are concerned about, then it seems like you could just sort alphabetically by that value; see xsl:sort. You could just put this inside the xsl:for-each or xsl:apply-templates where you process your invoice lines:
<xsl:sort select="cac:AdditionalItemProperty/cbc:Value" />
On the other hand, if you only want to output only the line items with the mentioned values, you could select them separately. For example, assuming you have a template which matches your invoice lines, you'd first apply it to the 'CU' ones and then to the 'RC' ones:
<xsl:apply-templates select="cac:InvoiceLine[cac:AdditionalItemProperty/cbc:Value='CU']" />
<xsl:apply-templates select="cac:InvoiceLine[cac:AdditionalItemProperty/cbc:Value='RC']" />

Selecting multiple elements including current

I have data structured like this:
<url title="Stack Overflow">http://stackoverflow.com/</url>
<url>http://example.com/</url>
The title attribute is optional. I have an XSLT stylesheet including this:
<xsl:template match="url">
<xsl:value-of select="(#title | .)[1]"/>
</xsl:template>
I want to display the title if there is one and otherwise display the url. However for some reason the title is never displayed. (In every case the url is displayed.)
What am I doing wrong?
Thanks!
Try either:
<xsl:value-of select="(#title | text())[1]"/>
or:
<xsl:value-of select="(#title | .)[last()]"/>
Explanation:
The . abbreviation in your expression represents the url element itself, which precedes its attributes in document order.

What does this do? <xsl:apply-templates select="."/> and <xsl:apply-templates select="*|#*"/>

I am very new to XSL and I am confused about what the select inside the following pieces of code will select.
<xsl:apply-templates select="."/>
<xsl:apply-templates select="*|#*"/>
Any ideas?
Thanks
Check out the Abbreviated Syntax section of XPath 2.0.
In the <xsl:apply-templates select="."/> example, . evaluates to the context item. In most cases, this is the same as the node currently being processed. So this example will select the context node.
In the <xsl:apply-templates select="*|#*"/> example, * will select all the child elements of the context node. #* will select all the attributes of the context node. | is the union operator. So this example will select all the child elements of the context node and also all the attributes of the context node.
<xsl:apply-templates select="."/> is frequently used to apply further processing to the context node.
<xsl:apply-templates select="*|#*"/> is frequently used to process all the child elements of the current node and its attributes. It’s often used when you’re done handling an element and want to hand off its child elements/attributes to any other templates that apply.
<xsl:apply-templates select="."/>
processes the content of the current node! the dot . indicates content.. if the current node doesn't have childnodes but has data instead (ex: <foo>Sample Data</foo>) then the parser processes the data Sample Data
<xsl:apply-templates select="#*|*"/>
processes the attribute and child nodes or the data under the current node.. difference is .. this one takes care of all the attribute of the context node..
I use the word process instead of copy, because .. apply-template unlike copy-of and value-of evaluates other templates, for example along with above code if I have one more template like below:
<xsl:template match="text()[.='Sample Data']"/>
then it will drop the text from your output XML. Where as copy-of select="node_name" and value-of select="node-name" copy the data despite of being this template in our XSL file..

XSL: Calling specific Element from Multiple Instances of one Schema

I created a component named "BusLocationLinks" it stores the name of the business as well as the coordinates for a map I created.
I have nearly 50 business locations with that same schema (BusLocationsLinks) and only want to list the element "Business Name" of all components components of that name. I've tried everything but can not make them all display. ANY ADVICE?
Here's my current code:
<xsl:template name="BusLocationLinks">
<xsl:for-each select="BusLocationLinks/BusinessName">
<li class="active">
<xsl:value-of select="BusinessName" />
</li>
</xsl:for-each>
</xsl:template>
my xml code looks similar to such:
<BusLocationLinks>
<BusinessName>Star Property</BusinessName>
</BusLocationLinks>
Without seeing your XML it is difficult to diagnose the problem. However, it is likely that you have the following structure:
<BusLocationLinks>
<BusinessName>name1</BusinessName>
<BusinessName>name2</BusinessName>
<BusinessName>name3</BusinessName>
</BusLocationLinks>
If that is the case, then you should adjust your XSLT like this:
<xsl:template name="BusLocationLinks">
<xsl:for-each select="BusinessName">
<li class="active">
<xsl:value-of select="." />
</li>
</xsl:for-each>
</xsl:template>
The body of an xsl:for-each instruction resets the context node to be one of the nodes in the selected node set (a different one each time the body of the for-each is evaluated).
In your example, that means that within the body of the for-each, the current node is one of the BusLocationLinks/BusinessName elements you selected. Your loop creates a list-item element for each of them (check your output, I expect you'll see them there) containing the value of the BusinessName child of the context node. The context node matches the expression BusLocationLinks/BusinessName, so you are looking for the values of nodes which match BusLocationLinks / BusinessName / BusinessName. If you don't have any nodes that match the expression BusLocationLinks / BusinessName / BusinessName, you'll be getting empty li elements.
Try <xsl:value-of select="."/>.