XPath matching the element with maximum value of an attribute - xpath

<?xml version="1.0" encoding="ISO-8859-1"?>
<users>
<user number="0775547857">
<step stepnumber="1">complete</step>
<step stepnumber="2">complete</step>
<step stepnumber="3">complete</step>
</user>
<user number="0775543754">
<step stepnumber="1">complete</step>
<step stepnumber="2">complete</step>
</user>
<user number="0777743059">
<step stepnumber="1">complete</step>
</user>
</users>
Given a number, I want to find the maximum stepnumber in the list of steps.
What i've got so far is //user[#number='0775547857']/step[#stepnumber]
And i think I have to use the fn:max function but I am having trouble on how to use the max function passing the list of step numbers.
Example : If i give number 0775547857, the maximum step number is 3 and for 0775543754 it is 2 and so on.
Thanx a lot in advance.
Is this correct?
//user[#number='0772243950']/step[fn:max((#stepnumber))]

The max() function is defined only in XPath 2.0 and above.
An XPath 2.0 expression that finds the maximum stepnumber of the step children of a user that has a number attribute with value $pNum is:
max(/*/user[#number=$pNum]/step/#stepnumber/xs:integer(.))
Substituting $pnum with 0775547857 and evaluating this XPath 2.0 expression on the following XML document:
<users>
<user number="0775547857">
<step stepnumber="1">complete</step>
<step stepnumber="11">complete</step>
<step stepnumber="2">complete</step>
<step stepnumber="3">complete</step>
</user>
<user number="0775543754">
<step stepnumber="1">complete</step>
<step stepnumber="2">complete</step>
</user>
<user number="0777743059">
<step stepnumber="1">complete</step>
</user>
</users>
produces the wanted, correct result:
11
Do note: using the xs:integer(.) above is necessary if we want to find the maximum of values as integers. Without it the maximum will be found on the values as strings and 3 will be bigger than 11.
In XPath 1.0 the following XPath expression returns the wanted maximum value:
/*/user[#number=$pNum]/step
[not(#stepnumber
<
../step/#stepnumber
)
]
/#stepnumber

Try this:
fn:max(//user[#number='0775547857']/step[#stepnumber])

Related

XPath 1.0 fetch the child record

Below is the XML
<on-error-continue type="APIKIT:BAD_REQUEST" enableNotifications="true" logException="true">
<set-variable value="200" doc:name="httpStatus" variableName="httpStatus" />
<set-variable value="Bad request" doc:name="logDescription" variableName="logDescription" />
<flow-ref doc:name="global-prepare-error-response-sub-flow" name="global-prepare-error-response-sub-flow"/>
</on-error-continue>
<on-error-continue type="APIKIT:TOO_MANY_REQUEST" enableNotifications="true" logException="true">
<set-variable value="200" doc:name="httpStatus" variableName="httpStatus" />
<set-variable value="Many request" doc:name="logDescription" variableName="logDescription" />
<flow-ref doc:name="global-prepare-error-response-sub-flow" name="global-prepare-error-response-sub-flow"/>
</on-error-continue>
Wanted to get the single record
"set-variable value="200" doc:name="httpStatus" variableName="httpStatus"
using xPath 1.0 expression: Parent is -->on-error-continue type="APIKIT:BAD_REQUEST" and child is -->set-variable value = "200".
Have tried below expression. It is working fine with Xpath2.0 but not working with 1.0
//*[local-name()='on-error-continue'][#*[local-name()='type' and .='APIKIT:BAD_REQUEST']]/set-variable[#value='200' and #variableName='httpStatus']
Using this handy website, I took the xml and put it in a root element, <root>YOUR XML</root>.
With this XPath:
//root/on-error-continue[#type='APIKIT:TOO_MANY_REQUEST']/set-variable[#value='200' and #variableName='httpStatus']
I was able to extract the matching record. Try it yourself and replace the root with * in the above XPath. You should see the records that you're seeking.
The wildcard operator can be used like any element in the path.

Xpath 1.0 nodelist based on node names

I don't like to ask for help, but this time I'm getting totally stuck with a xpath query.
Please have a look at this XML:
<doc>
<car>
<property id="color">
<attribute id="black" />
<attribute id="white" />
<attribute id="green" />
</property>
<property id="size">
<attribute id="small" />
<attribute id="medium" />
<attribute id="large" />
</property>
</car>
<attributes>
<color>white</color>
<size>small</size>
</attributes>
</doc>
The car/properties should be output according to the attributes nodenames. The desired output is:
<property id="color"><attribute id="white" /></property>
<property id="size"><attribute id="small" /></property>
The xpath
/doc/car/property[#id=name(/doc/attributes/*)]/attribute[#id=/doc/attributes/*/text()]
results only the first node, because the name() function returns only the name of the first element.
Who can help me to find out a working xpath (XSLT 1.0)? Many thanks for your help in advance!
You can achieve this with XSLT-1.0, but not only with XPath-1.0, because in XPath-1.0 you can only return the first item. This is not a problem in XSLT-1.0, because you can use an xsl:for-each loop, like the following:
<xsl:for-each select="/doc/attributes/*">
<property id="{/doc/car/property[#id=current()/local-name()]/#id}"><attribute id="{/doc/car/property[#id=current()/local-name()]/attribute[#id=current()/.]/#id}" /></property>
</xsl:for-each>
This code emits the following XML:
<property id="color"><attribute id="white"/></property>
<property id="size"><attribute id="small"/></property>
As seen, your requirements seem to be a little bit redundant, but I guess that your greater scenario justify the means.
What about these options (it's still unclear to me why you're using name() since I don't see any namespace in your sample data) :
//property|//attribute[#id=//attributes/*]
//attribute[#id=//attributes/*]|//attribute[#id=//attributes/*]/parent::property
//property|//attribute[#id=substring-before(normalize-space(//attributes)," ") or #id=substring-after(normalize-space(//attributes)," ")]
Third option should work even if you have to deal with a namespace for the #id inside the attributes node.
Output :
My working solution:
<xsl:stylesheet version="1.0">
<xsl:template match="/">
<xsl:for-each select="/doc/car/property">
<property id="{#id}">
<xsl:variable name="id" select="#id" />
<xsl:copy-of select="attribute[#id=/doc/attributes/*[name()=$id]/text()]" />
</property>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Another solution without using a loop:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="doc/car/property"/>
</xsl:template>
<xsl:template match="property">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="attribute[#id = /doc/attributes/*[name() = current()/#id]]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For each property it copies the element node and its attributes. Then it copies its attribute children having an id matching the respective element below /doc/attributes.

XPath 2 expressions interfering with each other

I have a XML like this:
<Values>
<Value AttributeID="asset.extension">pdf</Value>
<Value AttributeID="asset.size">10326</Value>
<Value AttributeID="ATTR_AssetPush_Webshop">1</Value>
<Value AttributeID="asset.format">PDF (Portable Document Format application)</Value>
<Value AttributeID="asset.mime-type">application/pdf</Value>
<Value AttributeID="asset.filename">filename.pdf</Value>
<Value AttributeID="asset.uploaded">2018-01-10 17:05:39</Value>
<Value AttributeID="ATTR_Verwendungsort" Derived="true">WebShop,</Value>
</Values>
I have 2 (or more) XPath-expressions like this:
<xsl:template match="/STEP-ProductInformation/Assets/Asset/Values/Value[not(#AttributeID='asset.mime-type')]" />
<xsl:template match="/STEP-ProductInformation/Assets/Asset/Values/Value[not(#AttributeID='asset.size')]" />
For some reason though, If I have 2 of them together, all information are being stripped. If I use only 1 expressoin, I get my desired output. Can't I use 2 expressions like this?
I also tried combining them like this:
<xsl:template match="/STEP-ProductInformation/Assets/Asset/Values/Value[not(#AttributeID='asset.mime-type') and (#AttributeID='asset.size')]" />
But that didn't do it, either.
The desired output would be like this:
<Values>
<Value AttributeID="asset.size">10326</Value>
<Value AttributeID="asset.mime-type">application/pdf</Value>
</Values>
I think in XSLT 2/3 you could express it as
<xsl:template match="Values/Value[not(#AttributeID = ('asset.mime-type', 'asset.size'))]"/>
In XSLT/XPath 1.0 you would need Values/Value[not(#AttributeID = 'asset.mime-type' or #AttributeID = 'asset.size')].
<xsl:template match="/STEP-ProductInformation/Assets/Asset/Values
/Value[not(#AttributeID='asset.mime-type')]" />
<xsl:template match="/STEP-ProductInformation/Assets/Asset/Values
/Value[not(#AttributeID='asset.size')]" />
This is a logical error -- has nothing to do with XPath.
It is like saying:
From all days of the week I will work only on Mondays
From the days selected above I will work only on Tuesdays
The first statement above selects only Mondays. The 2nd statement selects all Tuesdays from these Mondays -- that is the empty set.
A correct statement:
From all days of the week I will work only on Mondays or Tuesdays

XPATH get default value when node is empty or not present

I have 3 types of data
<results>
<place>
<key>place</key>
<value>1</value>
</place>
</results>
OR
<results>
<place>
<key>place</key> // notice the missing value
</place>
</results>
OR
<results>
</results>
So my sample data will be like
<event>
<results>
<place>
<key>place</key>
<value>1</value>
</place>
<some additional data here>
</results>
</event>
<event>
<results>
<place>
<key>place</key>
</place>
<some additional data here>
</results>
</event>
<event>
<results>
<some additional data here>
</results>
</event>
I need an XPath expression that can give me a default value when <value> of <place> is present, null or missing. <place> can be missing as well in some cases as mentioned in my third sample data.
Output that I expect here is 1, <default-value>, <default-value>.
XPATH 2.0 solution will work as well. I have tried scourging stackoverflow and google but couldnt find anything.
Use:
//results/concat(place/value, for $r in . return 'default-value'[not($r/place/value)])
XSLT - based verification:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"//results/concat(place/value, for $r in . return 'default-value'[not($r/place/value)])"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided (and completed) XML document:
<t>
<event>
<results>
<place>
<key>place</key>
<value>1</value>
</place>
<x/>
</results>
</event>
<event>
<results>
<place>
<key>place</key>
</place>
<y/>
</results>
</event>
<event>
<results>
<z/>
</results>
</event>
</t>
the XPath expression is evaluated and its results are copied to the output:
1 default-value default-value
I did it finally after a lot of trial and error.
{xpath::/events/event/(results//(place|rank)/value/string(), '')[1]}
the trick was to go one level up i.e. <results> in my case and then use the (if value present, default-value) XPATH notation.
Earlier, I was trying this unsuccessfully.
{xpath::/events/event/results//((place|rank)/value/string(), '')[1]}

Summary table with xForms

I have an xml like the following:
<table1>
<row>
<person>person1</person>
<value>10</value>
</row>
<row>
<person>person2</person>
<value>20</value>
</row>
<row>
<person>person1</person>
<value>5</value>
</row>
</table1>
<summaryTable>
<row>
<person>person1</person>
<value_total/>
</row>
<row>
<person>person2</person>
<value_total/>
</row>
</summaryTable>
With XForms 1 (there is no option to switch to XForms 2), using framework betterform, I want to calculate the values in the summary table, by doing the SUM of the rows in 'table1' that have the same person name. To do that I have the following binds:
<xf:bind id="bind_table1"
nodeset="table1" repeatableElement="row">
<xf:bind id="bind_head_table1" nodeset="head" />
<xf:bind id="bind_row_table1" nodeset="row">
<xf:bind id="bind_person" nodeset="person" type="xf:string" />
<xf:bind id="bind_value" nodeset="value" type="xf:integer" />
</xf:bind>
</xf:bind>
<xf:bind id="bind_summaryTable"
nodeset="summaryTable"
repeatableElement="row">
<xf:bind id="bind_head_summaryTable" nodeset="head" />
<xf:bind id="bind_row_summaryTable" nodeset="row">
<xf:bind id="bind_person_name" nodeset="person_name" type="xf:string" readonly="true"/>
<xf:bind id="bind_value_total" nodeset="value_total" type="xf:integer" readonly="true" calculate="SUM(//table1/row[person/text() = ../person_name/text()]/value)"/>
</xf:bind>
</xf:bind>
What I want to have at the end is the value_total for person1 = 15 and value_total for person2 = 20, but using this 'calculate' expression I'm getting 'NaN'. If I replace the calculate expression to compare with a literal String like:
<xf:bind id="bind_value_total" nodeset="value_total" type="xf:integer" readonly="true" calculate="SUM(//table1/row[person/text() = 'person1']/value)"/>
then I get as value_total 15 (the sum is correctly done). So it seems that the error is in the comparison expression person/text() = ../person_name/text() . Does someone have an idea about how should be the correct expression?
Thanks
Try the context() function in the calculate attribute to refer to the current node, like this:
<xf:bind nodeset="summaryTable/row/value_total" calculate="sum(//table1/row[person/text() = context()/../person/text()]/value)"/>
The context function gives you the current context node. If your bind references a nodeset with multiple nodes, it will be evaluated one time for every node, and that node is what context() returns.
It works for me with XSLTForms, maybe your version of betterForm supports it.

Resources