Is this an optimal xpath query? [duplicate] - bash

For example:
//person[#id='abc123']/#haircolor|/#weight"
PS. there are lots of "person" records

Try this:
//person[#id='abc123']/#*[name()='weight' or name()='haircolor']
If you're using an XPath 2.0 processor, you may also use a prettier option:
//person[#id='abc123']/(#haircolor|#weight)`

Are you wanting to search for person nodes based on the value of multiple attributes. If that's the question then you can just use ands e.g.
//person[#id='abc123' and #haircolor='blue' and #weight='...']
If you want to search on a single attribute, but return the values of the other attributes, I would do something like this:
<xsl:template match="person[#id='abc123']">
<xsl:value-of select="#haircolor"/>
<xsl:value-of select="#weight"/>
</xsl:template>

If you are trying to get the values of the specified attributes I would suggest introducing a variable for the requested person.
<xsl:variable name="person" select="//person[#id = 'abc123']" />
After that you can get any attribute from the requested person by using the specified variable.
<xsl:value-of select="$person/#haircolor" />
<xsl:value-of select="$person/#weight" />

Sample XML:
<X>
<Y ATTRIB1=attrib1_value ATTRIB2=attrib2_value/>
</X>
string xPath="/" + X + "/" + Y +
"[#" + ATTRIB1 + "='" + attrib1_value + "']" +
"[#" + ATTRIB2 + "='" + attrib2_value + "']"
XPath Testbed:
http://www.whitebeam.org/library/guide/TechNotes/xpathtestbed.rhtm

Related

Breaking for-each loop on a conditional base

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].

Sorting by one of two fields

I am trying to sort a XML nodeset by one of two fields, conditional on the value in another field.
<xsl:for-each select="CampusCourseDeliveryItem">
<xsl:sort select="OffCampus"/>
<xsl:sort select="OrganisationName" />
<xsl:sort select="OffCampusLocation"/>
<!-- code to display node goes here -->
What I want is, if OffCampus='Y', use OffCampusLocation as the sort key, otherwise use OrganisationName.
Example data:
<CampusCourseDelivery>
<CampusCourseDeliveryItem>
<OrganisationName>Chicago</OrganisationName>
<OffCampus>N</OffCampus>
<OffCampusLocation></OffCampusLocation>
</CampusCourseDeliveryItem>
<CampusCourseDeliveryItem>
<OrganisationName>London</OrganisationName>
<OffCampus>Y</OffCampus>
<OffCampusLocation>Detroit</OffCampusLocation>
</CampusCourseDeliveryItem>
<CampusCourseDeliveryItem>
<OrganisationName>Seattle</OrganisationName>
<OffCampus>Y</OffCampus>
<OffCampusLocation>Berlin</OffCampusLocation>
</CampusCourseDeliveryItem>
<CampusCourseDeliveryItem>
<OrganisationName>Adelaide</OrganisationName>
<OffCampus>N</OffCampus>
<OffCampusLocation>Ignore this value</OffCampusLocation>
</CampusCourseDeliveryItem>
</CampusCourseDelivery>
Expected sort order:
Adelaide
Berlin
Chicago
Detroit
Sort by the following expression:
concat(
substring(OffCampusLocation, 1, string-length(OffCampusLocation) * (OffCampus='Y')),
substring(OrganisationName, 1, string-length(OrganisationName) * (OffCampus='N'))
)

How to select two attributes from the same node with one expression in XPath?

For example:
//person[#id='abc123']/#haircolor|/#weight"
PS. there are lots of "person" records
Try this:
//person[#id='abc123']/#*[name()='weight' or name()='haircolor']
If you're using an XPath 2.0 processor, you may also use a prettier option:
//person[#id='abc123']/(#haircolor|#weight)`
Are you wanting to search for person nodes based on the value of multiple attributes. If that's the question then you can just use ands e.g.
//person[#id='abc123' and #haircolor='blue' and #weight='...']
If you want to search on a single attribute, but return the values of the other attributes, I would do something like this:
<xsl:template match="person[#id='abc123']">
<xsl:value-of select="#haircolor"/>
<xsl:value-of select="#weight"/>
</xsl:template>
If you are trying to get the values of the specified attributes I would suggest introducing a variable for the requested person.
<xsl:variable name="person" select="//person[#id = 'abc123']" />
After that you can get any attribute from the requested person by using the specified variable.
<xsl:value-of select="$person/#haircolor" />
<xsl:value-of select="$person/#weight" />
Sample XML:
<X>
<Y ATTRIB1=attrib1_value ATTRIB2=attrib2_value/>
</X>
string xPath="/" + X + "/" + Y +
"[#" + ATTRIB1 + "='" + attrib1_value + "']" +
"[#" + ATTRIB2 + "='" + attrib2_value + "']"
XPath Testbed:
http://www.whitebeam.org/library/guide/TechNotes/xpathtestbed.rhtm

How to write an XPath query to match two attributes?

Following Question:
<div id="id-74385" class="guest clearfix" style="z-index: 999;">
Given above,
If I want a XPath expression with checks both id and class, can we do it w/ 'and' condition LIKE:
//div[#id='id-74385'] and div[#class='guest clearfix']
Is this correct way? My execution fails here... Please help!
//div[#id='..' and #class='...]
should do the trick. That's selecting the div operators that have both attributes of the required value.
It's worth using one of the online XPath testbeds to try stuff out.
or //div[#id='id-74385'][#class='guest clearfix']
Adding to Brian Agnew's answer.
You can also do //div[#id='..' or #class='...] and you can have parenthesized expressions inside //div[#id='..' and (#class='a' or #class='b')].
Sample XML:
<X>
<Y ATTRIB1=attrib1_value ATTRIB2=attrib2_value/>
</X>
string xPath="/" + X + "/" + Y +
"[#" + ATTRIB1 + "='" + attrib1_value + "']" +
"[#" + ATTRIB2 + "='" + attrib2_value + "']"
XPath Testbed:
http://www.whitebeam.org/library/guide/TechNotes/xpathtestbed.rhtm

Is there an "if -then - else " statement in XPath?

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.

Resources