In XSLT 1, is not(self::DEFINEDTERM|self::TEXT) equivalent to not(self::DEFINEDTERM or self::TEXT)?
Which is preferred?
This is an example of XSL where this XPath is used:
<xsl:template name="DEFINITION" match="DEFINITION">
<xsl:element name="body">
<xsl:attribute name="break">before</xsl:attribute>
<xsl:element name="defn">
<xsl:attribute name="id" />
<xsl:attribute name="scope" />
<xsl:value-of select="DEFINEDTERM" />
</xsl:element>
<xsl:apply-templates select="TEXT" />
</xsl:element>
<xsl:apply-templates select="*[not(self::DEFINEDTERM|self::TEXT)]" />
</xsl:template>
"or" compares two booleans, whereas "|" forms the union of two node-sets.
Using a node-set where a boolean is expected gives you an implicit test on whether the node-set is empty. In effect it tests exists(N) where exists() returns true if N is non-empty.
So if M and N are node-sets, then
not(M or N) means not(exists(M) or exists(N))
not(M | N) means not(exists(M|N))
and since the union of two node-sets is empty only if both node-sets are empty, the two expressions always produce the same result.
I would think it likely that any decent XPath processor is going to evaluate both expressions in exactly the same way, so there's no reason to prefer one over the other.
Note that this equivalence only applies if both operands are node-sets. (If they aren't, the union operator will fail, so you need to use "or").
Related
I'm new to xslt 2.0, I would like to set the value to a variable in for-each loop only once (means if the value set, I want to come out of the loop).
For now it keep iterating for all the users. I just want to come out of the loop once the value set (immediately after my first attemp). I'm not sure how to break if the value set once.
Can you please help me on the below code ?
XSLT Code:
<xsl:variable name="v_first_name">
<xsl:for-each select="$emailList/emails/child::*">
<xsl:variable name="mailid" select="id" />
<xsl:for-each select="$userList/users/child::*">
<xsl:if test="emailid = $mailid">
<xsl:if test="firstname eq 'Antony'">
<xsl:value-of select="firstname" />
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$v_first_name != ''">
<first_name>
<xsl:value-of select="$v_first_name" />
</first_name>
</xsl:if>
XML O/p:
<first_name>AntonyAntonyAntonyAntony</first_name>
Expected XML O/P:
<first_name>Antony</first_name>
Note1: Please note that I'm using xslt 2.0 and my lists can have duplicates (So Antony can come twice, but I want only once (or unique)).
Note2: I also tried with position(), but couldn't find it work as the condition () can match at any position.
Thanks in advance.
Start with XPath and simply select the nodes you are looking for instead of considering for-each a "loop". If you select e.g. $userList/users/*[emailid = $emailList/emails/*/id] you select child elements from users which have a matching emailid in $emailList/emails/*.
I am not sure which sense it makes to hard code a first name value and then output it but of course you can select e.g. $userList/users/*[emailid = $emailList/emails/*/id and firstname = 'Antony']/lastname. That gives you a sequence of element nodes, if you want the first use a positional predicate e.g. depending on the structure of your input $userList/users/*[emailid = $emailList/emails/*/id and firstname = 'Antony'][1]/lastname or, of all selected elements ($userList/users/*[emailid = $emailList/emails/*/id and firstname = 'Antony']/lastname)[1].
I need to select all <next> nodes, but excluding <element4> from each and add a new element in its place (it would be a replace). I'm working with php.
<root>
<next>
<node>
<element1>text</element1>
<element2>text</element1>
<element3>text</element1>
<element4>text</element1>
</node>
<node>
<element1>text</element1>
<element2>text</element1>
<element3>text</element1>
<element4>text</element1>
</node>
</next>
</root>
so it should look like this:
<next>
<node>
<element1>text</element1>
<element2>text</element1>
<element3>text</element1>
<new>text</new>
</node>
<node>
<element1>text</element1>
<element2>text</element1>
<element3>text</element1>
<new><int xmlns="foo.bar">0</int></new>
</node>
</next>
Any tips? Thank you!
XPath is a selection language: it selects nodes or atomic items from an input sequence, it is the language of choice to make a selection over XML or hierarchical data, as SQL is (usually) the language of choice with relational databases.
As such, you can exclude elements from the selection, but you cannot update or change the original sequence. It is possible to do a limited transformation (i.e., turn a string in an integer), but this will change what is selected, it will not change the source. While XPath (namely version 2.0 and up) can "create" atomic values on the fly, it cannot create new elements.
This is possible and will return numeric values in XPath 2.0:
/next/node/number(.)
But this is not possible:
/next/node/(if (element4) then create-element(.) else .)
However, in XSLT 2.0 and up you can create a function that creates elements. As said above, XPath selects, and if you want to change the document, you can create a new document using XSLT (the T standing for Transformation).
Something like the following (partial XSLT 2.0, you need add headers):
<xsl:function name="f:create">
<xsl:param name="node" />
<xsl:param name="name" />
<xsl:choose>
<xsl:when test="name($node) = $name">
<xsl:element name="{if(number($node)) then 'int' else 'new'}">
<xsl:value-of select="$node" />
</xsl:element>
</xsl:when>
<xsl:otherwise><xsl:copy-of select="$node" /></xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="node">
<!-- now XPath, with help of XSLT function, can conditionally create nodes -->
<xsl:copy-of select="child::*/create(., 'element4')" />
</xsl:template>
<!-- boilerplate code, typically used to recursively copy non-matched nodes -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
Note that, while this shows how you can create a different element using XPath and an XSLT function, it does not change the source, it changes the output. Also, it is not a recommended practice, as in XSLT the same pattern is more easily done by simply doing:
<!-- the more specific match -->
<xsl:template match="element4[number(.)]">
<new>
<int xmlns="foo.bar">
<xsl:value-of select="number(.)" />
</int>
</new>
<xsl:template>
<!-- XSLT will automatically fallback to this one if the former fails -->
<xsl:template match="element4">
<new><xsl:copy-of select="node()" /></new>
</xsl:template>
<!-- or this one, if both the former fail -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
I am trying to extract using the following
<xsl:template match="//alarms:alarmRaisedTime">
<xsl:variable name="secondsSince1970" select="(xs:dateTime(.) - xs:dateTime('1970-01-01T00:00:00')) div xs:dayTimeDuration('PT1S')" />
<xsl:element name="alarmRaisedTime" namespace="MY NAME SPACE">
<xsl:value-of select="$secondsSince1970"/>
</xsl:element>
</xsl:template>
Output I get is
<alarmRaisedTime>1367855105.001</alarmRaisedTime>
I would like to get the below given output (Note Milli Seconds is removed)
<alarmRaisedTime>1367855105</alarmRaisedTime>
I tried the following:
<xsl:value-of select="fn:substring-before($secondsSince1970,.)"/>
but it did not work.
Even simpler:
floor($secondsSince1970)
By definition:
The floor function returns the largest (closest to positive infinity) number that is not greater than the argument and that is an integer.
Try
<xsl:value-of select="fn:substring-before($secondsSince1970,'.')"/>
The . in your expression refers to the context item alarms:alarmsRaisedTime
Being relatively new to XSLT I have what I hope is a simple question. I have some flat XML files, which can be pretty big (eg. 7MB) that I need to make 'more hierarchical'. For example, the flat XML might look like this:
<D0011>
<b/>
<c/>
<d/>
<e/>
<b/>
....
....
</D0011>
and it should end up looking like this:
<D0011>
<b>
<c/>
<d/>
<e/>
</b>
<b>
....
....
</D0011>
I have a working XSLT for this, and it essentially gets a nodeset of all the b elements and then uses the 'following-sibling' axis to get a nodeset of the nodes following the current b node (ie. following-sibling::*[position()=$nodePos]). Then recursion is used to add the siblings into the result tree until another b element is found (I have parameterised it of course, to make it more generic).
I also have a solution that just sends the position in the XML of the next b node and selects the nodes after that one after the other (using recursion) via a *[position() = $nodePos] selection.
The problem is that the time to execute the transformation increases unacceptably with the size of the XML file. Looking into it with XML Spy it seems that it is the 'following-sibling' and 'position()=' that take the time in the two respective methods.
What I really need is a way of restricting the number of nodes in the above selections, so fewer comparisons are performed: every time the position is tested, every node in the nodeset is tested to see if its position is the right one. Is there a way to do that ? Any other suggestions ?
Thanks,
Mike
Yes there is a way to do it much more efficiently: See Muenchian grouping. If having looked at this you need more help with the details, let us know. The key you'll need is something like:
<xsl:key name="elements-by-group" match="*[not(self::b)]"
use="generate-id(preceding-sibling::b[1])" />
Then you can iterate over the <b> elements, and for each one, use key('elements-by-group', generate-id()) to get the elements that immediately follow that <b>.
The task of "making the XML more hierarchical" is sometimes called up-conversion, and your scenario is a classic case for it. As you may know, XSLT 2.0 has very useful grouping features that are easier to use than the Muenchian method.
In your case it sounds like you would use <xsl:for-each-group group-starting-with="b" /> or, to parameterize the element name, <xsl:for-each-group group-starting-with="*[local-name() = 'b']" />. But maybe you already considered that and can't use XSLT 2.0 in your environment.
Update:
In response to the request for parameterization, here's a way to do it without a key.
Note though that it may be much slower, depending on your XSLT processor.
<xsl:template match="D0011">
<xsl:for-each select="*[local-name() = $sep]">
<xsl:copy>
<xsl:copy-of select="following-sibling::*[not(local-name() = $sep)
and generate-id(preceding-sibling::*[local-name() = $sep][1]) =
generate-id(current())]" />
</xsl:copy>
</xsl:for-each>
</xsl:template>
As noted in the comment, you can keep the performance benefit of keys by defining several different keys, one for each possible value of the parameter. You then select which key to use by using an <xsl:choose>.
Update 2:
To make the group-starting element be defined based on /*/*[2], instead of based on a parameter, use
<xsl:key name="elements-by-group"
match="*[not(local-name(.) = local-name(/*/*[2]))]"
use="generate-id(preceding-sibling::*
[local-name(.) = local-name(/*/*[2])][1])" />
<xsl:template match="D0011">
<xsl:for-each select="*[local-name(.) = local-name(../*[2])]">
<xsl:copy>
<xsl:copy-of select="key('elements-by-group', generate-id())"/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
<xsl:key name="k1" match="D0011/*[not(self::b)]" use="generate-id(preceding-sibling::b[1])"/>
<xsl:template match="D0011">
<xsl:copy>
<xsl:apply-templates select="b"/>
</xsl:copy>
</xsl:template>
<xsl:template match="D0011/b">
<xsl:copy>
<xsl:copy-of select="key('k1', generate-id())"/>
</xsl:copy>
</xsl:template>
This is the fine grained trasversal pattern:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="b[1]" name="group">
<xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::b[1]" mode="group"/>
</xsl:template>
<xsl:template match="b[position()!=1]"/>
<xsl:template match="b" mode="group">
<xsl:call-template name="group"/>
</xsl:template>
</xsl:stylesheet>
Output:
<D0011>
<b>
<c></c>
<d></d>
<e></e>
</b>
<b>
....
....
</b>
</D0011>
It seems with all the rich amount of function in xpath that you could do an "if" . However , my engine keeps insisting "there is no such function" , and I hardly find any documentation on the web (I found some dubious sources , but the syntax they had didn't work)
I need to remove ':' from the end of a string (if exist), so I wanted to do this:
if (fn:ends-with(//div [#id='head']/text(),': '))
then (fn:substring-before(//div [#id='head']/text(),': ') )
else (//div [#id='head']/text())
Any advice?
Yes, there is a way to do it in XPath 1.0:
concat(
substring($s1, 1, number($condition) * string-length($s1)),
substring($s2, 1, number(not($condition)) * string-length($s2))
)
This relies on the concatenation of two mutually exclusive strings, the first one being empty if the condition is false (0 * string-length(...)), the second one being empty if the condition is true. This is called "Becker's method", attributed to Oliver Becker (original link is now dead, the web archive has a copy).
In your case:
concat(
substring(
substring-before(//div[#id='head']/text(), ': '),
1,
number(
ends-with(//div[#id='head']/text(), ': ')
)
* string-length(substring-before(//div [#id='head']/text(), ': '))
),
substring(
//div[#id='head']/text(),
1,
number(not(
ends-with(//div[#id='head']/text(), ': ')
))
* string-length(//div[#id='head']/text())
)
)
Though I would try to get rid of all the "//" before.
Also, there is the possibility that //div[#id='head'] returns more than one node.
Just be aware of that — using //div[#id='head'][1] is more defensive.
The official language specification for XPath 2.0 on W3.org details that the language does indeed support if statements. See Section 3.8 Conditional Expressions, in particular. Along with the syntax format and explanation, it gives the following example:
if ($widget1/unit-cost < $widget2/unit-cost)
then $widget1
else $widget2
This would suggest that you shouldn't have brackets surrounding your expressions (otherwise the syntax looks correct). I'm not wholly confident, but it's surely worth a try. So you'll want to change your query to look like this:
if (fn:ends-with(//div [#id='head']/text(),': '))
then fn:substring-before(//div [#id='head']/text(),': ')
else //div [#id='head']/text()
I do strongly suspect this may fix it however, as the fact that your XPath engine seems to be trying to interpret if as a function, where it is in fact a special construct of the language.
Finally, to point out the obvious, insure that your XPath engine does in fact support XPath 2.0 (as opposed to an earlier version)! I don't believe conditional expressions are part of previous versions of XPath.
How about using fn:replace(string,pattern,replace) instead?
XPATH is very often used in XSLTs and if you are in that situation and does not have XPATH 2.0 you could use:
<xsl:choose>
<xsl:when test="condition1">
condition1-statements
</xsl:when>
<xsl:when test="condition2">
condition2-statements
</xsl:when>
<xsl:otherwise>
otherwise-statements
</xsl:otherwise>
</xsl:choose>
according to pkarat's, law you can achieve conditional XPath in version 1.0.
For your case, follow the concept:
concat(substring-before(your-xpath[contains(.,':')],':'),your-xpath[not(contains(.,':'))])
This will definitely work. See how it works. Give two inputs
praba:
karan
For 1st input: it contains : so condition true, string before : will be the output, say praba is your output. 2nd condition will be false so no problems.
For 2nd input: it does not contain : so condition fails, coming to 2nd condition the string doesn't contain : so condition true... therefore output karan will be thrown.
Finally your output would be praba,karan.
Personally, I would use XSLT to transform the XML and remove the trailing colons. For example, suppose I have this input:
<?xml version="1.0" encoding="UTF-8"?>
<Document>
<Paragraph>This paragraph ends in a period.</Paragraph>
<Paragraph>This one ends in a colon:</Paragraph>
<Paragraph>This one has a : in the middle.</Paragraph>
</Document>
If I wanted to strip out trailing colons in my paragraphs, I would use this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
version="2.0">
<!-- identity -->
<xsl:template match="/|#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- strip out colons at the end of paragraphs -->
<xsl:template match="Paragraph">
<xsl:choose>
<!-- if it ends with a : -->
<xsl:when test="fn:ends-with(.,':')">
<xsl:copy>
<!-- copy everything but the last character -->
<xsl:value-of select="substring(., 1, string-length(.)-1)"></xsl:value-of>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Unfortunately the previous answers were no option for me so i researched for a while and found this solution:
http://blog.alessio.marchetti.name/post/2011/02/12/the-Oliver-Becker-s-XPath-method
I use it to output text if a certain Node exists. 4 is the length of the text foo. So i guess a more elegant solution would be the use of a variable.
substring('foo',number(not(normalize-space(/elements/the/element/)))*4)
Somewhat simpler XPath 1.0 solution, adapted from Tomalek's (posted here) and Dimitre's (here):
concat(substring($s1, 1 div number($cond)), substring($s2, 1 div number(not($cond))))
Note: I found an explicit number() was required to convert the bool to an int otherwise some XPath evaluators threw a type mismatch error. Depending on how strict your XPath processor is type-matching you may not need it.