Xpath 1.0 select first node back to ancestor - xpath

My XML as below :
<Query>
<Comp>
<Pers>
<Emp>
<Job>
<Code>Not selected</Code>
</Job>
</Emp>
<Emp>
<Job>
<Code>selected</Code>
</Job>
</Emp>
</Pers>
</Comp>
</Query>
I have an XPath : /Query/Comp/Pers/Emp/Job[Code='selected']/../../../..
The result should only have one < Emp > that meet condition
<Query>
<Comp>
<Pers>
<Emp>
<Job>
<Code>selected</Code>
</Job>
</Emp>
</Pers>
</Comp>
</Query>
How could I get the result?
The system doesn't work with ancestor::*. I have to use '/..' to populate the ancestor.

You shouldn't have to use ancestor here to get the <emp> tag, the following expath should select any <emp> tag that meets your criteria:
/Query/Comp/Pers/Emp[Job[Code='selected']]
Note: You say your result should be one, which will be correct in this case but this expression will return all nodes that match your criteria
Edit:
You've stated you're using XSLT and you've given me a bit of a snippet below, but I'm still not 100% sure of your actual structure. You can use the XPath to identify all the nodes that are not equal to selected, and then use XSLT to copy everything except those.
// Copy's all nodes in the input to the output
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
// Matches specifically the Emp records that are not equal to selected and
// applies no action to them to they do not appear in the output
<xsl:template match="/Query/Comp/Pers/Emp[Job[Code!='selected']]" />
The two templates above would transform your input to your desired output!

Related

XSLT Function Return Type

Originally: **How to apply XPath query to a XML variable typed as element()* **
I wish to apply XPath queries to a variable passed to a function in XSLT 2.0.
Saxon returns this error:
Type error at char 6 in xsl:value-of/#select on line 13 column 50 of stackoverflow_test.xslt:
XTTE0780: Required item type of result of call to f:test is element(); supplied value has item type text()
This skeleton of a program is simplified but, by the end of its development, it is meant to pass an element tree to multiple XSLT functions. Each function will extract certain statistics and create reports from the tree.
When I say apply XPath queries, I mean I wish to have the query consider the base element in the variable... if you please... as if I could write {count(doc("My XSLT tree/element variable")/a[1])}.
Using Saxon HE 9.7.0.5.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="f:f">
<xsl:template match="/root">
<xsl:variable name="first" as="element()*">
<xsl:copy-of select="(./a[1])" />
</xsl:variable>
<html>
<xsl:copy-of select="f:test($first)" />
</html>
</xsl:template>
<xsl:function name="f:test" as="element()*">
<xsl:param name="frstElem" as="element()*" />
<xsl:value-of select="count($frstElem/a)" />
<!-- or any XPath expression -->
</xsl:function>
</xsl:stylesheet>
Some example data
<root>
<a>
<b>
<c>hi</c>
</b>
</a>
<a>
<b>
<c>hi</c>
</b>
</a>
</root>
Possibly related question: How to apply xpath in xsl:param on xml passed as input to xml
What you are doing is perfectly correct, except that you have passed an a element to the function, and the function is looking for an a child of this element, and with your sample data this will return an empty sequence.
If you want f:test() to return the number of a elements in the sequence that is the value of $frstElem, you can use something like
<xsl:value-of select="count($frstElem/self::a)" />
instead of using the (implicit) child:: axis.

XMLStarlet: selecting nodes using less than / greater than

Does XMLStarlet let you use a less-than/greater-than operator to filter on an attribute value? For example, consider a document like this:
<xml>
<list>
<node name="a" val="x" />
<node name="b" val="y" />
<node name="c" val="z" />
etc.
</list>
{code}
Is there a way to select nodes whose value is greater than "x"? This XPath does not seem to work with XMLStarlet 1.5.0:
//node[#val > 'x']
Nor does this:
//node[#value gt 'x']
Comparing Characters like they were numbers (ASCII values/UniCode codepoints) is (unfortunately) impossible in XPath 1.0, look at this SO question if interested in more details.
So if your #val attributes are sorted in the XML, you can achieve this with a simple XPath expression selecting all nodes after an 'equal' match:
//node[#val='x']/following-sibling::node
If not, you'd have to use an XSLT-Stylesheet. Luckily, XMLStarlet has the ability to apply XSL-Stylesheets. I cite from their overview:
Apply XSLT stylesheets to XML documents (including EXSLT support, and passing parameters to stylesheets)
So you have the possibility to apply an xsl:stylesheet to achieve the desired result using xsl:sort, which is capable of sorting by characters.
<xsl:template match="/list">
<xsl:for-each select="//node"> <!-- all nodes sorted by 'val' attribute' -->
<xsl:sort select="#val" data-type="text" order="ascending" case-order="upper-first"/>
<xsl:value-of select="#name" /> <!-- or whatever output you desire -->
</xsl:for-each>
</xsl:template>

XPath selection excluding element from a child node

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>

Select top 10 events from wevtutil using xpath

I am currently working on a project that uses the Windows event log. I am using wevtutil to get the results from the event logs. I know that wevtutil supports xpath queries, but since I'm new to xpath I don't know that I can achieve what I'm trying to do.
In SQL, what I would be doing is something like this:
SELECT log.*, COUNT(1) numHits
FROM Application log
GROUP BY Source, Task, Level, Description
ORDER BY numHits DESC
LIMIT 10
Is it possible to do such a thing using xpath?
Edit: Here is a sample Event:
<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'>
<System>
<Provider Name='MSSQL$SQLEXPRESS' />
<EventID Qualifiers='16384'>17403</EventID>
<Level>4</Level>
<Task>2</Task>
<Keywords>0x80000000000000</Keywords>
<TimeCreated SystemTime='2010-10-20T20:06:18.000Z' />
<EventRecordID>9448</EventRecordID>
<Channel>Application</Channel>
<Computer>SHAZTOP</Computer>
<Security />
</System>
<EventData>
<Data>73094</Data>
<Binary>
FB4300000A000000130000005300480041005A0054004F0050005C00530051004C004500580050005200450053005300000000000000</Binary>
</EventData>
</Event>
XPath 1.0 has four data types: string, number, boolean and node set.
The only XPath ordering criteria is document order (in the given axis direction). That is how you can limit any result node set as #Dimitre and #Welbog have sugested with fn:position().
But, there is no specification that an XPath engine must provide a node set result in any given order. So, you can't sort nor grouping in XPath 1.0. You can select the firsts of each group, but not efficiently. As example:
//Event[not(System/Level = preceding::Level) or
not(System/Task = preceding::Task)]
XPath 2.0 has the sequence data type. A sequence has the exclicit order of construction. So, you can group. As example:
for $event (//Event)[index-of(//Event/System/concat(Level,'++',Task),
System/concat(Level,'++',Task))[1]]
result //Event[System/Level = $event/System/Level]
[System/Task = $event/System/Task]
But, because XPath 2.0 has not built-in sorting nor recursion mechanism (you could provide an extension function...) you can't sort.
For that you need a language with built-in sorting or a way to express its algorithm. Both XSLT (1.0 or 2.0) and XQuery have these features.
In SQL, what I would be doing is
something like this:
SELECT log.*, COUNT(1) numHits
FROM Application log
GROUP BY Source, Task, Level, Description
ORDER BY numHits DESC
LIMIT 10
Is it possible to do such a thing
using xpath?
In case no sorting is necessary, one can get the first $n nodes selected by any XPath expression by:
(ExpressionSelectingNodeSet)[not(position() > $n)]
where $n can be substituted by a specific number
If there is a requirement that the nodes be sorted on one or more sort-keys, then this is not possible pure XPath, but one can easily perform such tasks with XSLT, using the <xsl:sort> instruction and the XPath position() function:
<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="/*">
<nums>
<xsl:for-each select="num">
<xsl:sort data-type="number" order="descending"/>
<xsl:if test="not(position() > 5)">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</nums>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>010</num>
</nums>
the correct result, containing only the top 5 numbers is produced:
<nums>
<num>010</num>
<num>09</num>
<num>08</num>
<num>07</num>
<num>06</num>
</nums>
You can use the position() function to limit the results you're getting:
/root/element[position()<=10]
For example, that would select the first ten element elements which are children of the root.
If your structure is more complicated, you can use the position element in different places. For example, if the element element can exist in more than one parent, but you want the first ten of them regardless of parent, you can do it this way:
(/root/parent1/element | /root/parent2/element)[position()<=10]

how to for every parent node select every not first child node in a tree with multiple parent nodes

His,
I think I've got a tricky questions for XPath experts. There is a node structure like this:
A(1)-|
|-B(1)
|-B(2)
|-B(3)
A(2)-|
|-B(2.1)
|-B(2.2)
|-B(2.3)
...
How to, with a single XPath-expression, extract only the following nodes
A(1)-|
|-B(2)
|-B(3)
A(2)-|
|-B(2.2)
|-B(2.3)
...
That is for every parent node its first child element should be excluded.
I tried A/B[position() != 1] but this would filter out only B(1.1) and select B(2.1).
Thanks
This XPath expression (no preceding-sibling:: axis used):
/*/a/*[not(position()=1)]
when applied on this XML document:
<t>
<a>
<b11/>
<b12/>
<b13/>
</a>
<a>
<b21/>
<b22/>
<b23/>
</a>
</t>
selects the wanted nodes:
<b12 />
<b13 />
<b22 />
<b23 />
This can be verified with this XSLT transformation, producing the above result:
<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="/">
<xsl:copy-of select="/*/a/*[not(position()=1)]"/>
</xsl:template>
</xsl:stylesheet>
Tricky. You could select nodes that have preceding siblings:
A/B[preceding-sibling::*]
This will fail for the first element and succeed for the rest.

Resources