I want to get the nodes from a nodeset which are not included in a different nodeset.
first nodeset
<suppressingEvent event="x">
<suppressedEvent event="a"/>
<suppressedEvent event="b"/>
<suppressedEvent event="c"/>
</suppressingEvent>
second nodeset
<suppressingEvent event="y">
<suppressedEvent event="a"/>
<suppressedEvent event="b"/>
<suppressedEvent event="c"/>
<suppressedEvent event="d"/>
</suppressingEvent>
Thus I want to get <suppressedEvent event="d"/>
First I filter the second nodeset for the suppressedEvent nodes of the first nodeset with a key and store these in the variable sameSuppressedEvents.
<xsl:variable name="sameSuppressedEvents" select="key('suppressedEventsFromSuppressingEvent', $suppressingEvent/suppressedEvent/#event, .)"/>
Then I want to store the remaining <suppressedEvent event="d"/> in the variable otherSuppressedEvents. But the predicate [not($sameSuppressedEvents)] does not work.
<xsl:variable name="otherSuppressedEvents" select="./suppressedEvent[not($sameSuppressedEvents)]"/>
But when I use directly the key inside the predicate I get the desired output.
<xsl:variable name="otherSuppressedEvents" select="./suppressedEvent[not(key('suppressedEventsFromSuppressingEvent', $suppressingEvent/suppressedEvent/#event, .))]"/>
Why does it behave like this?
And what is <xsl:variable name="otherSuppressedEvents" select="./suppressedEvent[not($sameSuppressedEvents)]"/> actually doing? Obviously not returning the unequal suppressedEvent nodes.
Is there a general way to compare two variables which are holding a nodeset and returning just the nodes which are unique in the nodeset which is under investigation?
Full stylesheet
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" media-type="application/xml" encoding="UTF-8" standalone="no"/>
<xsl:template match="/">
<FIDandFIC>
<xsl:apply-templates select="FiMrelations/suppressingEvent"/>
</FIDandFIC>
</xsl:template>
<xsl:template match="suppressingEvent">
<FIC>
<xsl:attribute name="event">
<xsl:value-of select="./#event"/>
</xsl:attribute>
<xsl:attribute name="fatal">
<xsl:value-of select="./#fatal"/>
</xsl:attribute>
<!-- Take all other suppressingEvent nodes to compare the underlying suppressedEvent Nodes with the ones from this current suppressingEvent -->
<!-- If the underlying suppressed Events are the same then you can combine these suppressing Events to one FID -->
<xsl:apply-templates select="../suppressingEvent" mode="equalFID">
<xsl:with-param name="suppressingEvent" select="."/>
</xsl:apply-templates>
</FIC>
</xsl:template>
<xsl:key name="suppressedEventsFromSuppressingEvent" match="suppressedEvent" use="./#event"/>
<xsl:template match="suppressingEvent" mode="equalFID">
<xsl:param name="suppressingEvent"/>
<xsl:if test="count($suppressingEvent/suppressedEvent) = count(./suppressedEvent)">
<xsl:variable name="sameSuppressedEvents" select="key('suppressedEventsFromSuppressingEvent', $suppressingEvent/suppressedEvent/#event, .)"/>
<xsl:variable name="otherSuppressedEvents" select="./suppressedEvent[not(key('suppressedEventsFromSuppressingEvent', $suppressingEvent/suppressedEvent/#event, .))]"/>
<!-- this does not work -->
<!-- <xsl:variable name="otherSuppressedEvents" select="./suppressedEvent[not($sameSuppressedEvents)]"/> -->
<xsl:if test="not($otherSuppressedEvents)">
<equalFID>
<xsl:attribute name="suppressingEvent">
<xsl:value-of select="./#event"/>
</xsl:attribute>
<xsl:attribute name="OBD-Relevant">
<xsl:value-of select="./#OBD-Relevant"/>
</xsl:attribute>
<xsl:attribute name="fatal">
<xsl:value-of select="./#fatal"/>
</xsl:attribute>
</equalFID>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The effect of the key() function with "." as the third argument is going to depend on the value of ".", which is different inside a predicate, so it's not surprising that the two cases are different.
In the first case you haven't told us what the context node is when you call key(). In the second case it's clear that it's a suppressedEvent element, which makes the call very dubious since key() will only search within the subtree rooted at the node supplied in the 3rd argument.
Related
My question is about xsl:variable and the syntax for a predicate in an Xpath. I've boiled down my question to the point where this short XML can help me demonstrate:
<root>
<tabular>
<col halign="left"/>
<col halign="right"/>
<row>
<cell>Some content</cell>
<cell>Some content</cell>
</row>
</tabular>
</root>
In my application, when I am applying a template on a cell, I need to access the #halign of the corresponding col. In doing so, I have encountered a discrepancy between Xpath expressions that I thought should be equivalent. I would like to understand why this happens. To demonstrate, I apply the XSL at the end of this post using XSLT 1.0.
The cell template in my XSLT here is silly but it lays out the discrepancy I don't understand. Basically it repeatedly tries to print the #halign value corresponding to the second cell. First, using the $col variable that has value 2. Then using [position()=$col]. Then using [number($col)]. Then simply using [2], hard coded. Lastly, using a separate $colsel variable that was defined using a #select attribute.
I expect to see:
ancestor::tabular/col[...]/#halign
[2] makes right
[position()=2] makes right
[number(2)] makes right
(hard 2) [2] makes right
(var #select) [2] makes right
but instead I see:
ancestor::tabular/col[...]/#halign
[2] makes left
[position()=2] makes right
[number(2)] makes right
(hard 2) [2] makes right
(var #select) [2] makes right
Is anyone able to offer an explanation for why using [$col] behaves differently?
Here is the XSL:
<xsl:template match="/">
<xsl:apply-templates select="root/tabular"/>
</xsl:template>
<xsl:template match="tabular">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="cell"/>
</xsl:template>
<?xml version='1.0'?> <!-- As XML file -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="root/tabular"/>
</xsl:template>
<xsl:template match="tabular">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="cell[2]"/>
</xsl:template>
<xsl:template match="cell[2]">
<xsl:variable name="col">
<xsl:value-of select="2"/>
</xsl:variable>
<xsl:variable name="colsel" select="2"/>
<xsl:text>ancestor::tabular/col[...]/#halign</xsl:text>
<xsl:text>
</xsl:text>
<xsl:text> [</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[$col]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text> [position()=</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[position()=$col]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text> [number(</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>)] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[number($col)]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>(hard 2) [2] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[2]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>(var #select) [</xsl:text>
<xsl:value-of select="$colsel"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[$colsel]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Let us use a more convenient example:
XML
<root>
<item>first</item>
<item>second</item>
</root>
XSLT 1.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="num" select="2"/>
<xsl:variable name="str" select="string(2)"/>
<xsl:variable name="rtf">2</xsl:variable>
<xsl:template match="/root">
<results>
<num>
<xsl:copy-of select="item[$num]"/>
</num>
<str>
<xsl:copy-of select="item[$str]"/>
</str>
<rtf>
<xsl:copy-of select="item[$rtf]"/>
</rtf>
</results>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<results>
<num>
<item>second</item>
</num>
<str>
<item>first</item>
<item>second</item>
</str>
<rtf>
<item>first</item>
<item>second</item>
</rtf>
</results>
Now you ask why the difference in the results. The answer can be found in the XPath specification that prescribes how a predicate is to be evaluated:
A PredicateExpr is evaluated by evaluating the Expr and converting the
result to a boolean. If the result is a number, the result will be
converted to true if the number is equal to the context position and
will be converted to false otherwise; if the result is not a number,
then the result will be converted as if by a call to the boolean
function.
In the first instance the value of the $num variable is the number 2. Therefore the result of evaluating the expression within the predicate is a number, and the predicate will be true when the number is equal to the context position - which is only true for the item in the second position.
In the second instance, the value of the $str variable is the string "2". Therefore the expression within the predicate does not evaluate to a number and will be converted to boolean by doing:
boolean("2")
which returns true() for all items, regardless of their position.
In the third instance, the value of the $rtf variable is a result tree fragment that contains a text node that consists of the character "2". When placed in a predicate, the outcome will be similar to the previous instance: the result of evaluating the expression is not a number, and converting it to a boolean will produce a value of true(). Note that your:
<xsl:variable name="col">
<xsl:value-of select="2"/>
</xsl:variable>
does exactly the same thing.
Note also that in XSLT 1.0 the xsl:value-of instruction returns the value of the first node in the selected node-set. Therefore, if we change our template to:
<xsl:template match="/root">
<results>
<num>
<xsl:value-of select="item[$num]"/>
</num>
<str>
<xsl:value-of select="item[$str]"/>
</str>
<rtf>
<xsl:value-of select="item[$rtf]"/>
</rtf>
</results>
</xsl:template>
the result will be:
<results>
<num>second</num>
<str>first</str>
<rtf>first</rtf>
</results>
but still both items are selected by item[$str] and by item[$rtf].
Change the variable declaration to:
<xsl:variable name="col" select="2"/>
and it will behave as you expect and select the second col.
You had declared the variable using xsl:value-of: <xsl:value-of select="2"/>, which creates a computed text() node.
When you use that $col variable by itself in a predicate, that string value "2" it is evaluated as true() in the predicate test, rather than if it were a number() and would then be interpreted as short-hand for position() = 2.
I am trying to create a function that takes one pram and then queries the input XML in several ways based on the input. My problem is that when I try to query the input xml and store a value in a variable while in the function I get the error:
'/' cannot select the root node of the tree containing the context item: the context item is absent
How can I query the XML from within the function? Below is the XSLT
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lang="info:lc/xmlns/codelist-v1"
xmlns:foo="http://whatever">
<xsl:output indent="yes" />
<xsl:function name="foo:get-prefered">
<xsl:param name="field-name"/>
<xsl:variable name="var1" select="sources/source[#type='A']/name" />
</xsl:function>
<xsl:template match="/">
<xsl:value-of select="foo:get-prefered(10)"></xsl:value-of>
</xsl:template>
</xsl:stylesheet>
This is as per the W3C specification where it states...
Within the body of a stylesheet function, the focus is initially
undefined; this means that any attempt to reference the context item,
context position, or context size is a non-recoverable dynamic error.
The solution is to pass in a node (such as the root node) as a parameter
<xsl:function name="foo:get-prefered">
<xsl:param name="root"/>
<xsl:param name="field-name"/>
<xsl:variable name="var1" select="$root/sources/source[#type='A']/name"></xsl:variable>
<xsl:value-of select="$var1" />
</xsl:function>
<xsl:template match="/">
<xsl:value-of select="foo:get-prefered(/, 10)"></xsl:value-of>
</xsl:template>
Alternatively, you could consider using a named-template instead of a function, perhaps:
<xsl:template name="get-prefered">
<xsl:param name="field-name"/>
<xsl:variable name="var1" select="sources/source[#type='A']/name"></xsl:variable>
<xsl:value-of select="$var1" />
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="get-prefered">
<xsl:with-param name="field-name">10</xsl:with-param>
</xsl:call-template>
</xsl:template>
This is my XSLT file:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:for-each select="//child_4331">
<xsl:value-of select="*"/>
<xsl:value-of select="#value" />
<xsl:attribute name="onclick">
<xsl:call-template name="GetOnClickJavaScript" />
</xsl:attribute>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
How can I set the click event on child_4331 value?
You didn't say, but I'm assuming you want to copy the child_4331 element and add the onclick attribute.
I would get rid of the template matching '/' and create one to match 'child_4331'. Use xsl:copy to create a copy of the element and add the attribute inside it. If the child_4331 element has attributes or child elements you will want to use xsl:apply-templates to pick them up.
Here is a sample snippet. Your solution may vary depending on your desired output. I can't give you more without knowing what your source XML looks like and what you expect to see in the result.
<xsl:template match="child_4331">
<xsl:copy>
<xsl:attribute name="onclick">
<xsl:call-template name="GetOnClickJavaScript" />
</xsl:attribute>
</xsl:copy>
</xsl:template>
I am trying to merge the elements from two separate web.xml files using XSLT. For example, if web-1.xml and web-2.xml are being merged, and I'm processing web-1.xml, I want all elements in web-2.xml to be added into the result, except any that already exist in web-1.xml.
In the XSLT sheet, I have loaded the document whose servlet's are to be merged into the other document using:
<xsl:variable name="jandy" select="document('web-2.xml')"/>
I then have the following rule:
<xsl:template match="webapp:web-app">
<xsl:copy>
<!-- Copy all of the existing content from the document being processed -->
<xsl:apply-templates/>
<!-- Merge any <servlet> elements that don't already exist into the result -->
<xsl:for-each select="$jandy/webapp:web-app/webapp:servlet">
<xsl:variable name="servlet-name"><xsl:value-of select="webapp:servlet-name"/></xsl:variable>
<xsl:if test="not(/webapp:web-app/webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
The problem I'm having is getting the test in the if correct. With the above code, the test always evaluates to false, whether a servlet-name element with the given node exists or not. I have tried all kinds of different tests but with no luck.
The relevant files are available at http://www.cs.hope.edu/~mcfall/stackoverflow/web-1.xml, and http://www.cs.hope.edu/~mcfall/stackoverflow/transform.xslt (the second web-2.xml is there as well, but StackOverflow won't let me post three links).
provide an anchor for the first document, just before the for-each loop:
<xsl:variable name="var" select="."/>
then, use it in your if:
<xsl:if test="not($var/webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
Your template matches XPATH webapp:webapp from web-1.xml and
you are refencing absolute XPATH if your xsl:if condition: /webapp:web-app/webapp:servlet/webapp:servlet-name[text() = $servlet-name]. Try to do it using relative XPATH:
<xsl:if test="not(webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
<xsl:copy-of select="."/>
</xsl:if>
I haven't checked it, so you have to give it a try.
Also, it would be easier if you could provide web-1.xml and web-2.xml files.
EDIT
The following XSLT merges two files - the only problem appears when there are sections of the same type (like listener) in two places of the input XML.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:webapp="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xpath-default-namespace="http://java.sun.com/xml/ns/javaee">
<xsl:output indent="yes"/>
<xsl:variable name="jandy" select="document('web-2.xml')"/>
<xsl:template match="/">
<xsl:element name="web-app">
<xsl:for-each select="webapp:web-app/*[(name() != preceding-sibling::node()[1]/name()) or (position() = 1)]">
<xsl:variable name="nodeName" select="./name()"/>
<xsl:variable name="web1" as="node()*">
<xsl:sequence select="/webapp:web-app/*[name()=$nodeName]"/>
</xsl:variable>
<xsl:variable name="web2" as="node()*">
<xsl:sequence select="$jandy/webapp:web-app/*[name() = $nodeName]"/>
</xsl:variable>
<xsl:copy-of select="$web1" copy-namespaces="no"/>
<xsl:for-each select="$web2">
<xsl:variable name="text" select="./*[1]/text()"/>
<xsl:if test="count($web1[*[1]/text() = $text]) = 0">
<xsl:copy-of select="." copy-namespaces="no"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
If have a big 'xsl:choose' chunk in which I need to set a number of defined sets of attributes on different elements.
I really do not like to repeat the definition of sets of attributes inside every branch of the 'choose'.
So I would like to work with a variable that contains those attributes.
A lot easier to maintain and less room for error...
So far I have not been able to call the attribute node out?
I thought they are just a node-set, so copy-of would do the trick.
But that gives me nothing on output.
Is this because attribute nodes are not really children?
But XSLT 1.O does not allow me to address them directly...<xsl:copy-of select="$attributes_body/#*/> returns an error
Here is the stylesheet fragment (reduced from original)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="list">
<xsl:for-each select="figure">
<xsl:variable name="attributes_body">
<xsl:attribute name="chapter"><xsl:value-of select="#chapter"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="#id"/></xsl:attribute>
</xsl:variable>
<xsl:variable name="attributes_other">
<xsl:attribute name="chapter"><xsl:value-of select="#book"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="#id"/></xsl:attribute>
</xsl:variable>
<xsl:choose>
<xsl:when test="ancestor::body">
<xsl:element name="entry">
<xsl:copy-of select="$attributes_body"/>
<xsl:text>Body fig</xsl:text>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="entry">
<xsl:copy-of select="$attributes_other"/>
<xsl:text>other fig</xsl:text>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
If this can not be done in XLST 1.0 would 2.0 be able to do this?
<xsl:variable name="attributes_body">
<xsl:attribute name="chapter"><xsl:value-of select="#chapter"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="#id"/></xsl:attribute>
</xsl:variable>
You need to select the wanted attributes -- not to copy their contents in the body of the variable.
Remember: Whenever possible, try always to specify an XPath expression in the select attribute of xsl:variable -- avoid copying content in its body.
Solution:
Just use:
<xsl:variable name="attributes_body" select="#chapter | #id">
Here is a complete example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="x/a">
<xsl:variable name="vAttribs" select="#m | #n"/>
<newEntry>
<xsl:copy-of select="$vAttribs"/>
<xsl:value-of select="."/>
</newEntry>
</xsl:template>
</xsl:stylesheet>
when applied on this:
<x>
<a m="1" n="2" p="3">zzz</a>
</x>
produces:
<newEntry m="1" n="2">zzz</newEntry>
in my case, I was trying to store a tag attribute into a variable
to do so, use this syntax tag-name/#attribute-inside-tag-name
here is an example
<xsl:variable name="articleLanguage" select="/article/#language"/><!--the tricky part -->
//<!--now you can use this this varialbe as you like -->
<xsl:apply-templates select="front/article-meta/kwd-group[#language=$articleLanguage]"/>
and the xml was
<article article-type="research-article" language="es" explicit-lang="es" dtd-version="1.0">
.....
hope this help you