I have the following XML:
<parent>
<pet>
<data>
<birthday/>
</data>
</pet>
<pet>
<data>
<birthday/>
</data>
</pet>
</parent>
And now I want to select the first birthday element via parent//birthday[1] but this returns both birthday elements because bothof them are the first child of their parents.
How can I only select the first birthday element of the entire document no matter where it is located. I've tried parent//birthday[position()=1] but that doesn't work either.
You mean (note the parentheses!)
(/parent/pet/data/birthday)[1]
or, a shorter, but less specific variation:
(/*/*/*/birthday)[1]
(//birthday)[1]
or, more semantic, the "birthday of the first pet":
/parent/pet[1]/data/birthday
or, if not all pets have birthday entries, the "birthday of the first pet that for which a birthday is set":
/parent/pet[data/birthday][1]/data/birthday
If you work from a context node, you can abbreviate the expression by making it relative to that context node.
Explanation:
/parent/pet/data/birthday[1] selects all <birthday> nodes that are the first in their respective parents (the <data> nodes), throughout the document
(/parent/pet/data/birthday)[1] selects all <birthday> nodes, and of those (that's what the parentheses do, they create an intermediary node-set), it takes the first one
FYI: you can visualize the results of the various Xpath queries with the (free) XPathVisualizer tool. Works on Windows only.
Ok, I admit this is horrendous and there must be a better way, but it appears to work.
/*/*[descendant::birthday and not(preceding-sibling::*[descendant::birthday])]
I look for all elements at the second level in the tree that have a descendant element called birthday that do not have a preceding sibling element that has a birthday element as a descendant.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="birthdays" select="//birthday"/>
<xsl:value-of select="$birthdays[1]"/>
</xsl:template>
</xsl:stylesheet>
try
//birthday[position()=1]
// finds nodes no matter where there are in the hierarchy
you could also do
pet[position()=1]/data/birthday
Related
I am learning XSLT and I came to an aspect of XSLT/XPath which is not clear to me.
I want to check if there is at least one element namePart with a value inside.
There can be something like this in XML:
<mods:name type="personal">
<mods:namePart type="family">Salamonis</mods:namePart>
<mods:namePart type="given"/>
</mods:name>
But also this due to any reason:
<mods:name type="personal">
<mods:namePart/>
</mods:name>
I think I have found out the solution for my problem. Actually I found two similar solutions but I do not understand the difference:
first:
<xsl:for-each select="mods:name">
<xsl:if test="mods:namePart/text() != ''"> ..... </xsl:if>
<xsl:for-each>
second:
<xsl:for-each select="mods:name">
<xsl:if test="mods:namePart[text() != '']"> ..... </xsl:if>
<xsl:for-each>
Apparently, both of them are working fine. But I am still thinking what is better to use or if there are some minor differences.
My solution is taken from this comment: https://stackoverflow.com/a/7660915/14163073
So are both of these solution working in accordance with the comment? (operator returns true if at least one item on the left side "matches" one item on the right side)
Thanks for any explanation!
Well, take a tour through an XPath tutorial, I would say. Preferences are often a personal choice and style. For the last test I would simply use e.g. <xsl:if test="mods:namePart = 'foo'"> as that works for any contents of the mods:namePart elements. It doesn't really matter for your simple example but in the end you might end up using XPath against e.g. some mixed contents HTML paragraph element and want to check its whole content (e.g. test="p = 'This is an example text.'") and the p could be anything from a simple <p>This is an example text.</p> to <p>This is an <b>example</b> text.</p>.
I am trying to do some cleanups using XSLT. I want to do some changes on text fragments and leave all the other nodes in peace. However my current implementation runs very slow and consumes a lot of memory. The removal of a small template changes the run time from a minute to a fraction of a second.
This is the XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:import href="../common/identity.xsl"/>
<xsl:template match="text()" priority="100">
<xsl:variable name="pass1" select="replace(., '(_|~)', ' ')"/>
<xsl:variable name="pass2" select="replace($pass1, ' , ', ', ')"/>
<xsl:variable name="final" select="$pass2"/>
<xsl:value-of select="$final"/>
</xsl:template>
<xsl:template match="body/text()[1][. = ' '] | body/text()[last()][. = ' ']"
priority="200"/>
</xsl:stylesheet>
The first template replaces some characters, the second template removes the first and last text fragments, but only if they contain exactly one space (sadly normalize-space does not fit my needs).
This XSLT runs very slow and consumes a lot of memory. If I remove the last templates, the same XSLT runs fast and using a normal amount of memory.
The XSLT is run using Saxon-(HE|EE) 9.5.1.3 inside oXygen 15.2.
What is causing this big loss of performance? Is it the use of text fragments in general? The use of priorities? The use of [1] and [last()]?
using not(following-sibling::text()) instead of last() fixed it. Could you explain why or give some pointers to the problems of last()?
There are two ways of evaluating patterns: left-to-right, and right-to-left, corresponding to the "formal" and "informal" semantics given in section 5.5.3 of the specification. The right-to-left method is much more efficient, but it cannot be used for all patterns; in particular, patterns that use positional predicates are tricky. Saxon will handle a number of cases efficiently, including match="para[last()]", but for some others, including match="para[last()-1]" and (it seems) match="section/para[last()]", it takes the slow-but-methodical route. I'll take a look at the code and see if this can be improved.
How to find only nodes with at least a similar/equal sibling node using Xpath?
For example:
<root>
<parent>
<node>...</node>
<node_unique>...</node_unique>
<node>...</node>
<another_one>...</another_one>
<another_one>...</another_one>
</parent>
</root>
In the example the xpath shold select only <node> and <another_one> because they are appearing more than once.
I was trying to find a solution for this for hours without success (now I think is not possible with XPath...).
These are impossible to select with a single XPath 1.0 expression (due to lack of range variables in XPath 1.0).
One possible solution is to select all /*/*/* elements, then to get the name of each element, using name() off that element, then to evaluate /*/*/*[name() = $currentName][2] (where $currentName should be substituted with the name just obtained. If the last expression selects an element, then the currentName is a name that occurs at least twice -- therefore you keep that element. Do so with all elements and their names. As an auxhiliarry step, one might dedup the names (and selected elements) by placing them in a hash-table.
In Xpath 2.0 it is trivial to select with a single XPath expression all children of a given parent, that have at least one other sibling with the same name:
/*/*/*
[name() = following-sibling::*/name()
and
not(name() = preceding-sibling::*/name())
]
A much more compact expression:
/*/*/*[index-of(/*/*/*/name(), name())[2]]
XSLT 2.0 - based verification:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select=
"/*/*/*[index-of(/*/*/*/name(), name())[2]]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<parent>
<node>...</node>
<node_unique>...</node_unique>
<node>...</node>
<another_one>...</another_one>
<another_one>...</another_one>
</parent>
</root>
the above XPath expression is evaluated and the selected from this evaluation elements are copied to the output:
<node>...</node>
<another_one>...</another_one>
Note: For a related question/answer, see this.
I have the following XML:
<ArrayOfStationStatus xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autopagerMatchedRules="1">
<StationStatus ID="20" StatusDetails="To the platform due to planned maintenance work.">
<Station ID="20" Name="Bermondsey"/>
<Status ID="NS" CssClass="Closed" Description="No Step Free Access" IsActive="true">
<StatusType ID="2" Description="Station"/>
</Status>
</StationStatus>
</ArrayOfStationStatus>
And would like to select StationStatus nodes that contain a particular phrase in the Name attribute. It's important that I select SationStatus nodes.
This is the xpath I have come up with but it's not correct:
/ArrayOfStationStatus/StationStatus[contains(lower-case(child::Station/#Name),lower-case('phrase'))]
EDIT::::::::
I just solved it! This is the code I needed:
/ArrayOfStationStatus/StationStatus[child::Station[contains(lower-case(attribute::Name),lower-case("Ac"))]]
Well I managed to solve it people! Here is the solution, in this case I'm looking for the phrase 'Ac' as you can see
/ArrayOfStationStatus/StationStatus[child::Station[contains(lower-case(attribute::Name),lower-case("Ac"))]]
Also remember
lower-case(
is only available in xpath 2.0 (Dimitre Novatchev)
I am now using xpath to test a node's parent node's immediate following sibling(uncle or ant) node.
My xml looks like
<MyParent>
<A>
<B>
<C>
</MyParent>
<Uncle>
..
</Uncle>
Now I am in the template match for child node B, and I want to test if the immeididate following-sibling of my parent is called "Uncle",
I tried the following two xpaths:
<xsl:if test="parent::MyParent/following-sibling::*[1][self::Uncle]">
<xsl:text>we have it</xsl:text>
</xsl:if>
and
<xsl:if test="parent::MyParent[following-sibling::*[1][self::Uncle]]">
<xsl:text>we have it</xsl:text>
</xsl:if>
neither of them will work, could experts help debug where I made mistakes? Thanks :).
Try this.
../following-sibling::*[position()=1][name()='Uncle']