Reputation: 31
I'm trying to count the occurrences of every value for a specific attribute (e.g. @root) of every element that matches a query. Some of these matching elements do not have the attribute, but I want to count those as well (i.e. also list the amount of matched elements that did not have the attribute)
Currently I use this, but this does not match the nodes that dont have a root
attribute. The counting is done by the application, which allows me to show intermediate results.
//node[@rel='su']/@root
I want something like
//node[@rel='su']/string(if .[@root] then @root else 'fallback-value')
The queries are run against Berkeley DB XML so XPath2 and XQuery might also be used to solve this.
edit: To clarify, I am looking for a query which treats the non-existence of the @root attribute as a special case; i.e. as if the attribute did exist and had 'fallback-value' as value.
Upvotes: 2
Views: 3170
Reputation: 5256
This can easily be done using XQuery. First, collect the matching values while enforcing your fallback policy:
let $matches := //node[@rel='su']/(data(@root), '')[1]
The above is a slighty corrected reformulation of the expression shown in your question. Note however that it will treat root attributes with a zero-length value the same as a non existing root attribute.
Then group by distinct value, and count the number of occurrences for each group:
for $value in distinct-values($matches)
let $count := count($matches[. = $value])
return <value count="{$count}">{$value}</value>
When applied to this input,
<x>
<node rel="su" root="A"/>
<node rel="su" root="A"/>
<node rel="su" root="B"/>
<node rel="su" root=""/>
<node rel="su"/>
<node rel="su"/>
</x>
the result could be
<value count="2">A</value>
<value count="1">B</value>
<value count="3"/>
For enforcing a specific order of the result, add an order by
clause:
for $value in distinct-values($matches)
let $count := count($matches[. = $value])
order by $value
return <value count="{$count}">{$value}</value>
Upvotes: 1
Reputation: 243499
Use:
concat('Having @root: ', count(//node[@rel='su']/@root ),
', Not having @root: ', count(//node[@rel='su'][not(@root)]),
', Having @root occur ',
floor(count(//node[@rel='su']/@root ) div count(//node[@rel='su']) * 100),
'% of the time.'
)
XSLT - based verification:
<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:value-of select=
"concat('Having @root: ', count(//node[@rel='su']/@root ),
', Not having @root: ', count(//node[@rel='su'][not(@root)]),
', Having @root occur ',
floor(count(//node[@rel='su']/@root ) div count(//node[@rel='su']) * 100),
'% of the time.'
)
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<node rel="su" root="2"/>
<node rel="su" root="1"/>
<node rel="su" />
<node rel="su" root="4"/>
<node rel="su" root="5"/>
<node rel="su" />
<node rel="su" root="7"/>
<node rel="su" root="8"/>
<node rel="su" />
<node rel="su" />
</t>
the wanted, correct result is produced:
Having @root: 6, Not having @root: 4, Having @root occur 60% of the time.
Upvotes: 0
Reputation: 10205
Try this:
//node[@rel='su']/(@root/string(), 'no-value')[1]
Follows the original answer to the misunderstood question
Why don't you just use
//node[@rel='su']
then? It will match both node
elements that have a @root
attribute and those who do not.
Given that a node
can have at most one @root
attributes, counting the nodes is enough.
Upvotes: 1
Reputation: 26930
What about this ?
//node[@rel='su' and not(@root)]
This should match the nodes without the root
attribute.
Upvotes: 0