Jelmervdl
Jelmervdl

Reputation: 31

How to select attribute or fallback value if attribute does not exist?

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

Answers (4)

Gunther
Gunther

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

Dimitre Novatchev
Dimitre Novatchev

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

gioele
gioele

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

FailedDev
FailedDev

Reputation: 26930

What about this ?

//node[@rel='su' and not(@root)]

This should match the nodes without the root attribute.

Upvotes: 0

Related Questions