Name function in XPATH - xslt

I have the following elements in my xmi file:
<element1 id= 3 >
<element2 id= 3>
I want to transform them into something like:
<element1 id= 3 name =element2>
<element2 id= 3>
I am using xslt to transform:
<xsl:if test="#id = //*[#id]/#id">
<xsl:sequence
select="fn:createAtt('name',X)" />
</xsl:if>
I want to compare the id of two elements and in case they match then i want to save the name of second element (element2) into the name attribute of first element.
The comparison works ok. The problem is how to read the name of the second element ? I tried to use the name() function but am not able to read exactly that name that matches the comparison.

I would do it like this: first define a key as
<xsl:key name="el-by-id" match="*" use="#id"/>
then I would write a template
<xsl:template match="*[#id]">
<xsl:variable name="same-id" use="key('el-by-id', #id) except ."/>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="$same-id">
<xsl:attribute name="name" select="node-name($same-id[1])"/>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
That way you can efficiently reference the elements using a key, then if one element of the same id is found the attribute named name is created. I used the XSLT/XPath 2.0 node-name function, depending on your exact requirements you might want to use <xsl:attribute name="name" select="name($same-id[1])"/> instead.

Related

How to check for atomic item in XPATH?

i would like to check a sequence of Items and report the item type:
If it is an atomic value (maybe an empty string) report String "A"
If it is an Element report its local Name
Otherwise report String "X"
My Question is: How can i check an item to be an atomic value? The only Solution that i found is to use the xs:string(.) function, which takes an anyAtomic as argument. See the following XSLT 3.0 fragment
<xsl:template match="/*">
<xsl:variable name="content" as="item()*">
<xsl:apply-templates select="node()"/>
</xsl:variable>
<result>
<xsl:for-each select="$content">
<xsl:choose>
<!-- I`d like to check for any atomic value here -->
<xsl:when test="xs:string(.)">A</xsl:when>
<xsl:when test="self::*"><xsl:value-of select="lower-case(local-name())"/></xsl:when>
<xsl:otherwise>X</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()" as="xs:string">
<!-- What if i change to normalize-space(.) ? -->
<xsl:sequence select="."/>
</xsl:template>
Problem is, that i get an error when i use the normalize-space() function in the template matching text-Node, because in this case the effective boolean value of xs:string() is false for some text Nodes. The following check for the self Axis fails with the error message XPTY0020: The required item type of the context item for the self axis is node(); the supplied value "" is an atomic value.
I have tried with expressions like ". is anyAtomic", but the IS Operator expects Nodes.
I am pretty sure that xs:string(.) is not the correct way to check for atomic items, but what is it?
Thanks,
Frank Steimke
XPath has an instance of operator you can use with sequence types so I think you are looking for . instance of xs:anyAtomicType or e.g. . instance of xs:string.

how to do a dynamic boolean checking in xslt

From my root template for unique account value, call goes to trans template were in the input xml I will have multiple nodes, my requirement is that once the call goes from root template to trans template, if a match of accountId found in between multiple elements, account details template is called only once, irrespective of other match found. I need a solution for above requirement.
input sample :
<elements><accountId>1</accountId></elements>
<elements><accountId>1</accountId></elements>
<elements><accountId>2</accountId></elements>
<elements><accountId>2</accountId></elements>
<elements><accountId>3</accountId></elements>
The below line should be wrapped under some code so its called only once
<xsl:call-template name="Account_details" />
Below is my complete code for xsl
<xsl:template match="/">
<xsl:variable name="unique-accounts" select="//*/*/*/accountId/text()[generate-id()=generate-id(key('account-by-id', .)[1])]"/>
<xsl:for-each select="$unique-accounts">
<xsl:variable name="currentValue" select="current()"/>
<xsl:apply-templates select="//trans">
<xsl:with-param name="passCurrentValue" select="$currentValue"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="trans">
<xsl:param name="passCurrentValue" />
<xsl:variable name="booleanValue" select="true()"/>
<xsl:for-each select="elements">
<xsl:if test="$passCurrentValue=/*/*/accountId">
<xsl:if test="$booleanValue">
<xsl:call-template name="Account_details" />
<xsl:variable name="booleanValue" select="false()"></xsl:variable>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="Account_details">
.............
</xsl:template>
With the code
<xsl:template match="/">
<xsl:variable name="booleanCheck" select="true"></xsl:variable>
the variable booleanCheck is a node-set (XSLT 1.0) or sequence of elements named true below the document node /. So unless your XML input has a root element named true the value is an empty node-set respectively empty sequence. If you want to select a boolean value then use <xsl:variable name="booleanValue" select="true()"/>. See http://www.w3.org/TR/xpath/#function-true. Then you can test <xsl:if test="$booleanValue">. That should explain how to use boolean values, whether that fits into your context I am not sure.

XSLT - How to refer to a current node value using xsl:choose?

I try to create a variable, which I can use in a later template:
<xsl:variable name="fc">
<xsl:choose>
<xsl:when test="self::node()='element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Unfortunately it does not work.
<xsl:template match="element1">
<h1><font color="{$fc}"><xsl:value-of select="self::node()"/></font></h1>
</xsl:template>
What am I doing wrong?
Here is the extensive code:
XML:
<root
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.test.com scheme.xsd" xmlns="http://www.test.com" xmlns:tst="http://www.test.com">
<elementA>
<elementB tst:name="name">
<elementC tst:name="name">
<element1> Test1 </element1>
<element2> Test2 </element2>
</elementC >
</elementB>
</elementA>
</root>
All the elements are qualified and part of the namespace "http://www.test.com".
XSLT:
<xsl:template match="/">
<html>
<body><xsl:apply-templates select="tst:root/tst:elementA/tst:elementB/tst:elementC/tst:element1"/>
</body>
</html>
</xsl:template>
<xsl:variable name="var_fc">
<xsl:choose>
<xsl:when test="local-name(.)='tst:element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="tst:element1">
<h2><font color="{$var_fc}"><xsl:value-of select="self::node()"/></font></h2>
</xsl:template>
As a result, element1 should turn gray, but it always turn red.
You can't use a variable for this, as the content of an xsl:variable is evaluated just once at definition time, whereas you want to evaluate some logic every time the variable is referenced, in the current context at the point of reference.
Instead you need a template, either a named one:
<xsl:template name="fc">
<xsl:choose>
<xsl:when test="local-name()='element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:template>
or (better) a pair of matching templates with a mode, to let the template matcher do the work:
<!-- match any node whose local name is "element1" -->
<xsl:template mode="fc" match="node()[local-name() = 'element1']">gray</xsl:template>
<!-- match any other node -->
<xsl:template mode="fc" match="node()">red</xsl:template>
When you want to use this logic:
<h1>
<font>
<xsl:attribute name="color">
<xsl:apply-templates select="." mode="fc" />
</xsl:attribute>
Seeing as you have the tst prefix mapped in your stylesheet you could check the name directly instead of using the local-name() predicate:
<xsl:template mode="fc" match="tst:element1">gray</xsl:template>
<xsl:template mode="fc" match="node()">red</xsl:template>
XSLT variables are designed not to be changeable. Actually they could be named constants. If your variable fc is created global, it will use the root element for choose. You have to use choose in the actual template to be tested against the current element. If you want to have "red" and "gray" defined only once, create two variables with just that text content and use these instead the plain text in the choose.
Maybe it is a typo:
<xsl:when test=self::node()='element1'">gray</xsl:when>
should be:
<xsl:when test="self::node()='element1'">gray</xsl:when>
there is a missing quote.
I think instead of test="self::node()='element1'" you want test="self::element1" or test="local-name(.) = 'element1'".
A couple of other errors in your code:
(1) self::node() = 'element1'
tests whether the content of the element is "element1", not whether its name is "element1"
(2) local-name(.)='tst:element1'
will never be true because the local name of a node never contains a colon.
Experienced users would often write this code using template rules:
<xsl:template mode="var_fc" match="tst:element1">gray</xsl:template>
<xsl:template mode="var_fc" match="*">red</xsl:template>
and then
<xsl:apply-templates select="." mode="var_fc"/>

How should I pass a parameter from one XSLT template to another?

I'm having trouble passing a parameter to a template.
<!-- // Product / Instances -->
<xsl:template match="/data/products/instances">
<ul>
<xsl:apply-templates select="item">
<xsl:with-param name="idp" select="#id"/>
</xsl:apply-templates>
</ul>
</xsl:template>
<!-- // Product / Instances / Instance -->
<xsl:template match="/data/products/instances/item">
<xsl:param name="idp"/>
<p>$idp: <xsl:value-of select="$idp"/></p> <!-- $idp is empty -->
<xsl:for-each select="/data/instances/entry">
<xsl:if test="#id = $idp">
<p><xsl:value-of select="code"/></p>
</xsl:if>
</xsl:for-each>
</xsl:template>
/data/products/instances/item has an attribute named id, which has a value of an integer.
Although the second template and its for-each loop are being processed (I've tested them by outputting dummy output from within them), the value of the $idp parameter is not being passed to the second template.
Thanks.
The problem is that when you do the apply-templates, your current context is on the instances element, and so the attribute #id refers to the attribute id of the instances element, and not the attribute on the item elements you are going to select (which have not yet been selected at that point).
In this sample given, there is no actually need to pass in a parameter. Simply use a variable in the matching template instead. Insteaf of the xsl:param, do the following:
<xsl:variable name="idp" select="#id"/>
This will get the value of the id attribute for you, as you are positioned on the item element at that point.
You would need to show enough details to allow us to reproduce the issue, otherwise it is hard to tell what goes wrong.
I think you don't need any parameter, and you should use a key
<xsl:key name="k1" match="data/instances/entry" use="#id"/>
<!-- // Product / Instances -->
<xsl:template match="/data/products/instances">
<ul>
<xsl:apply-templates select="item"/>
</ul>
</xsl:template>
<!-- // Product / Instances / Instance -->
<xsl:template match="/data/products/instances/item">
<xsl:for-each select="key('k1', #id)">
<p><xsl:value-of select="code"/></p>
</xsl:for-each>
</xsl:template>

Match conditionally upon current node value

Given the following XML:
<current>
<login_name>jd</login_name>
</current>
<people>
<person>
<first>John</first>
<last>Doe</last>
<login_name>jd</login_name>
</preson>
<person>
<first>Pierre</first>
<last>Spring</last>
<login_name>ps</login_name>
</preson>
</people>
How can I get "John Doe" from within the current/login matcher?
I tried the following:
<xsl:template match="current/login_name">
<xsl:value-of select="../people/first[login_name = .]"/>
<xsl:text> </xsl:text>
<xsl:value-of select="../people/last[login_name = .]"/>
</xsl:template>
I'd define a key to index the people:
<xsl:key name="people" match="person" use="login_name" />
Using a key here simply keeps the code clean, but you might also find it helpful for efficiency if you're often having to retrieve the <person> elements based on their <login_name> child.
I'd have a template that returned the formatted name of a given <person>:
<xsl:template match="person" mode="name">
<xsl:value-of select="concat(first, ' ', last)" />
</xsl:template>
And then I'd do:
<xsl:template match="current/login_name">
<xsl:apply-templates select="key('people', .)" mode="name" />
</xsl:template>
You want current() function
<xsl:template match="current/login_name">
<xsl:value-of select="../../people/person[login_name = current()]/first"/>
<xsl:text> </xsl:text>
<xsl:value-of select="../../people/person[login_name = current()]/last"/>
</xsl:template>
or a bit more cleaner:
<xsl:template match="current/login_name">
<xsl:for-each select="../../people/person[login_name = current()]">
<xsl:value-of select="first"/>
<xsl:text> </xsl:text>
<xsl:value-of select="last"/>
</xsl:for-each>
</xsl:template>
If you need to access multiple users, then JeniT's <xsl:key /> approach is ideal.
Here is my alternative take on it:
<xsl:template match="current/login_name">
<xsl:variable name="person" select="//people/person[login_name = .]" />
<xsl:value-of select="concat($person/first, ' ', $person/last)" />
</xsl:template>
We assign the selected <person> node to a variable, then we use the concat() function to output the first/last names.
There is also an error in your example XML. The <person> node incorrectly ends with </preson> (typo)
A better solution could be given if we knew the overall structure of the XML document (with root nodes, etc.)
I think what he actually wanted was the replacement in the match for the "current" node, not a match in the person node:
<xsl:variable name="login" select="//current/login_name/text()"/>
<xsl:template match="current/login_name">
<xsl:value-of select='concat(../../people/person[login_name=$login]/first," ", ../../people/person[login_name=$login]/last)'/>
</xsl:template>
Just to add my thoughts to the stack
<xsl:template match="login_name[parent::current]">
<xsl:variable name="login" select="text()"/>
<xsl:value-of select='concat(ancestor::people/child::person[login_name=$login]/child::first/text()," ",ancestor::people/child::person[login_name=$login]/child::last/text())'/>
</xsl:template>
I always prefer to use the axes explicitly in my XPath, more verbose but clearer IMHO.
Depending on how the rest of the XML documents looks (assuming this is just a fragment) you might need to constrain the reference to "ancestor::people" for example using "ancestor::people[1]" to constrain to the first people ancestor.