xPath to get parent node position using count - xpath

My xml looks like the following. The focus is at the third level: <name>,<address> etc. I'd like to find the position of parent <customer>.
I use "count(../preceding-sibling::*) + 1" but it doesn't return a count when the node is empty. How do I fix this? Thanks.
<record>
<customer>
<name>Sue A</name> -- return 1
<address>123 Main St</address> -- return 1
<phone></phone> -- missing
<status>A</status> -- return 1
</customer>
<customer>
<name>John B</name> -- return 2
<address></address> -- missing
<phone>123-456-7890</phone> -- return 2
<status></status> -- missing
</customer>
…
</record>

While the question is lacking a lot of information, using the following xquery It looks like your logic is correct, however you are probably using the text nodes as current nodes instead of the element node.
This query returns the same wrong results, because some text nodes are missing:
let $x :=
for $grandchild in //customer/*/text()
return count($grandchild/../../preceding-sibling::*) + 1
return $x
This query returns correct results, because every element exists:
let $x :=
for $child in //customer/*
return count($child/../preceding-sibling::*) + 1
return $x
Selecting the element node as current node will probably fix any issue.

Related

i want to get every <path> where <id_wert> = 1 from a xml file

i want to get every <path> where <id_wert> = 1 from a xml file
my query in sql would be "select path from xml where id = 1
<xml_export>
<id>1<\id>
<path>\DATEN\00000001.003</path>
</xml_export>
<xml_export>
<id>2</id>
<path>\DATEN\00000001.004</path>
</xml_export>
<xml_export>
<id>1</id>
<path>\DATEN\00000001.005</path>
</xml_export>
You can locate xml_export parent nodes by child id with desired value and then to get their child path nodes.
As following:
"//xml_export[.//id='1']//path"
In case you want to make it more strict, to search for only cases where id and path are direct children of xml_export the former expression can be changed to be
"//xml_export[./id='1']/path"

XPath get element by name with highest number value

I have this xml source:
<element1>
<element2>
<element3>156</element3>
<element4>Test</element4>
<element5>descriptionxxxx</element5>
</element2>
<element2>
<element3>25</element3>
<element4>Top</element4>
<element5>descriptionyyyy</element5>
</element2>
<element2>
<element3>852</element3>
<element4>Test</element4>
<element5>descriptionzzzz</element5>
</element2>
</element1>
I would need to find all element2 where element4 = "Test", and return element5 for the max of element3.(in this case, It would return "descriptionzzzz")
I have tried:
To get all element2 with element4 = "Test" -> doc("xxx.xml")//element1/element2[element4="Test"]
But now I would need to get the element2 with the highest element3 of the previous list, and return element5 of that element2.
Thanks!
I have done something like this for my xml and it has worked for me: //element2[element4="Test"][element3=max(//element2[element4="Test"]/element3)]/element5
If you're able to use Saxon (PE or higher) there's an XPath extension function that does exactly what you need:
saxon:highest(//element2, function($e){number($e/element3)})
saxon:highest(S, F) finds the item(s) from S that have the highest value for the result of computing function F.

XQuery - How can I count and save informations in a map

I want to safe IDs in a map. If the ID occurs again, I want to set the count ($value) from 1 to 2 and so on.
Following you find my code:
declare namespace functx = "http://www.functx.com";
declare variable $idMap := map{};
declare function functx:uniqueID ($entityID as xs:string) as xs:integer {
let $idMap := map:merge(($idMap, if(not(map:contains($idMap, $entityID))) then map:entry($entityID, 1) else map:entry(entityID, map:get($idMap, $entityID)+1)))
return map:get($idMap, $entityID)
};
declare variable $map := map:merge((
map:entry("Sheff", "85246525"),
map:entry("Peter", "85246454"),
map:entry("Marcel", "85246525"),
map:entry("Lion", "85244565"),
map:entry("Klaus", "85241234")
));
map:for-each($map,
function($key, $value) {
functx:uniqueID($value)
}
)
Result:
1
1
1
1
1
Expected Result
1
1
2 (: Because it is the second time, that 85246525 occurs. :)
1
1
Edited 23.03.2020 - 17:45:
I have a complex xquery, which functions. But the target system need unique IDs per line. I have a map, which hold my information like the upper one. I need to add something behind the IDs like (001, 002, 003) to have different IDs.
Best practice would be, that only douplicate IDs get a added number.
Do you understand or what do you need more from me?
One way to construct a new map with an "index" added to duplicated values is to use grouping:
map:merge(
for $key in map:keys($map)
group by $value := $map($key)
for $group-key at $pos in $key
return map:entry($group-key, $value || '-' || format-integer($pos, '000'))
)
At https://xqueryfiddle.liberty-development.net/6qVSgeT that gives
{
"Peter": "85246454-001",
"Marcel": "85246525-001",
"Sheff": "85246525-002",
"Lion": "85244565-001",
"Klaus": "85241234-001"
}
You've written your code as if $idMap is mutable, and as if calling functx:uniqueID() has the side-effect of modifying the map. That isn't going to work in a functional language.
You need a completely different approach; and to help you with that, we need to look at the problem you are trying to solve, not at your existing approach to a solution.

Xquery - return only distinct attributes from if/then

I've been trying to write a query to get distinct attribute values after using if/then to determine whether I'll use the element in the first place. Here's my example xml and the query i've written so far:
<donors>
<donor donor_id="x21" cn_id="x12">
<homeless>$1201</homeless>
<conservation>$300</conservation>
<cancerResearch>$250</cancerResearch>
</donor>
<donor donor_id="x23" cn_id="x13">
<homeless>$121</homeless>
<conservation>$30</conservation>
<cancerResearch>$50</cancerResearch>
</donor>
<donor donor_id="x24" cn_id="x14">
<homeless>$1201</homeless>
<cancerResearch>$250</cancerResearch>
</donor>
<donor donor_id="x25" cn_id="x12">
<homeless>$1201</homeless>
<conservation>$300</conservation>
<cancerResearch>$250</cancerResearch>
</donor>
</donors>
I want to first get all donors who have a child "conservation". I've done the following for that:
<conservationists>
{
for $x in //donor
return
if(exists($x/conservation))
then <conservationist cn_id="{$x/#cn_id}/>
else ()
}
</conservationists>
I tried wrapping the whole thing in distinct-values but that just gave nothing, and every where else I tried doing something to that effect I just ended up with an end tag.
This is one possible way :
<conservationists>
{
for $x in distinct-values(//donor[conservation]/#cn_id)
return
<conservationist cn_id="{$x}"/>
}
</conservationists>
xpathtester demo
The expression distinct-values(//donor[conservation]/#cn_id) returns distinct values of cn_id attribute from donor elements that have at least one conservation child element.

Does xpath query has Limit option like mysql

I want to limit number of result I receive from xpath query.
For example:-
$info = $xml->xpath("//*[firstname='Sheila'] **LIMIT 0,100**");
You see that LIMIT 0,100.
You should be able to use "//*[firstname='Sheila' and position() <= 100]"
Edit:
Given the following XML:
<root>
<country.php desc="country.php" language="fr|pt|en|in" editable="Yes">
<en/>
<in>
<cityList desc="cityList" language="in" editable="Yes" type="Array" index="No">
<element0>Abu</element0>
<element1>Agartala</element1>
<element2>Agra</element2>
<element3>Ahmedabad</element3>
<element4> Ahmednagar</element4>
<element5>Aizwal</element5>
<element150>abcd</element150>
</cityList>
</in>
</country.php>
</root>
You can use the following XPath to get the first three cities:
//cityList/*[position()<=3]
Results:
Node element0 Abu
Node element1 Agartala
Node element2 Agra
If you want to limit this to nodes that start with element:
//cityList/*[substring(name(), 1, 7) = 'element' and position()<=3]
Note that this latter example works because you're selecting all the child nodes of cityList, so in this case Position() works to limit the results as expected. If there was a mix of other node names under the cityList node, you'd get undesirable results.
For example, changing the XML as follows:
<root>
<country.php desc="country.php" language="fr|pt|en|in" editable="Yes">
<en/>
<in>
<cityList desc="cityList" language="in" editable="Yes" type="Array" index="No">
<element0>Abu</element0>
<dog>Agartala</dog>
<cat>Agra</cat>
<element3>Ahmedabad</element3>
<element4> Ahmednagar</element4>
<element5>Aizwal</element5>
<element150>abcd</element150>
</cityList>
</in>
</country.php>
</root>
and using the above XPath expression, we now get
Node element0 Abu
Note that we're losing the second and third results, because the position() function is evaluating at a higher order of precedence - the same as requesting "give me the first three nodes, now out of those give me all the nodes that start with 'element'".
Ran into the same issue myself and had some issue with Geoffs answer as it, as he clearly describes, limits the number of elements returned before it performs the other parts of the query due to precedence.
My solution is to add the position() < 10 as an additional conditional after my other conditions have been applied e.g.:
//ElementsIWant[./ChildElementToFilterOn='ValueToSearchFor'][position() <= 10]/.
Notice that I'm using two separate conditional blocks.
This will first filter out elements that live up to my condition and secondly only take 10 of those.

Resources