0
votes

With XQuery I want to select a special value from every article within a product.

What I currently have:

Input XML (extract):

<product type="product" id="2246091">
<product type="article">
  <attribute identifier="EXAMPLE1" type="BOOLEAN">0</attribute>
  <attribute identifier="EXAMPLE2" type="BOOLEAN">1</attribute>
</product>
<product type="article">
  <attribute identifier="EXAMPLE1" type="BOOLEAN">1</attribute>
  <attribute identifier="EXAMPLE2" type="BOOLEAN">1</attribute>
</product>
<product type="article">
  <attribute identifier="EXAMPLE1" type="BOOLEAN">0</attribute>
  <attribute identifier="EXAMPLE2" type="BOOLEAN">1</attribute>
</product>
</product>

XQuery:

 for $i in //product
                 [@type = 'product' 
                 and @id = '2246091']
                 //attribute
                 [@type='BOOLEAN' 
                 and @identifier= ('EXAMPLE1', 'EXAMPLE2') ]
 where $i = '1'
 return $i

This returns me every attribute element from every article under a product where the content is '1' and its identifier is EXAMPLE1 or EXAMPLE2. It could be, that in article 1 there is the same attribute identifier (e.g. EXAMPLE1) as in article 2.

What I get:

<?xml version="1.0" encoding="UTF-8"?>
<attribute identifier="EXAMPLE2" type="BOOLEAN">1</attribute>
<attribute identifier="EXAMPLE1" type="BOOLEAN">1</attribute>
<attribute identifier="EXAMPLE2" type="BOOLEAN">1</attribute>
<attribute identifier="EXAMPLE2" type="BOOLEAN">1</attribute>

I tried to add a distinct-values around my for loop, but this will return me only '1'.

What I would like is to get every attribute only once:

<attribute identifier="EXAMPLE2" type="BOOLEAN">1</attribute>
<attribute identifier="EXAMPLE1" type="BOOLEAN">1</attribute>
1
What does this have to do with lua? What does the input file look like? Does it have these elements duplicated?Etan Reisner
Yes these elements are existing for every article. For Example 'EXAMPLE1' can be on article 1 and article 2 and set '1' under the same product.frgtv10
And you need the attribute elements from all articles because they might have different names and you want all the names?Etan Reisner
Updated the question with correct code snippets!frgtv10

1 Answers

2
votes

It sounds as if what you want is to see one attribute element for each distinct value of the identifier attribute found among the attribute elements whose content is 1. (Or, slightly more challengingly, one attribute element for each set of equivalent attribute elements, where equivalence is defined by deep-equals().)

The distinct-values() function isn't helping you here, because it coerces any input nodes into simple values (here, 1).

If matching on the identifier attribute suffices

If the identifier attribute suffices to establish equivalence among the elements, then something like the following should suffice (not tested):

let $ones := //product[@type = 'product' 
                       and @id = '2246091']
             //attribute[@type='BOOLEAN' 
                         and @identifier = 
                         ('EXAMPLE1', 'EXAMPLE2') ],
    $ids := distinct-values($ones/@identifier)
for $id in $ids
return ($ones[@identifier = $id])[1]

If a more general equivalence test is needed

If @identifier does not suffice to establish equivalence for your purposes, you will have to do something more complicated; in the general case one way to do it would be to write a function of two arguments (I'll call it local:equivalent()) which returns true iff the two arguments are equivalent for your purposes. Then write a second function to accept a sequence of items and remove duplicates from the sequence (where 'being a duplicate' means 'returning true on local:equivalent()). Something like this might work as a first approximation (not tested):

(: dedup#1:  remove duplicates from a sequence :)
declare function local:dedup(
  $items as item()*
) as xs:boolean {
  local:dedup($items, ())
};

(: dedup#2: work through the input sequence one
   by one, removing duplicates and accumulating
   non-duplicates.  Cost is n^2 / 2. :)
declare function local:dedup(
  $in as item()*,
  $out as item()*
) as xs:boolean {
  if (empty($in)) 
  then $out
  else let $car := head($in)
       return if (some $i in $in
                  satisfies
                  local:equivalent($i, $car))
              then local:dedup(tail($in), $out)
              else local:dedup(tail($in), ($car, $out))
};

(: equivalent#2:  true iff arguments are equivalent :)
declare function local:equivalent(
  $x, $y : item()
) as xs:boolean {
  // determine application-specific equivalence
  // however you like ...
  deep-equal($x, $y)
};

(: Now do the work :)
let $ones := //product[@type = 'product' 
                       and @id = '2246091']
             //attribute[@type='BOOLEAN' 
                         and @identifier = 
                         ('EXAMPLE1', 'EXAMPLE2') ]
return local:dedup($ones)

Those comfortable with higher-order functions will want to go a step further and remove the dependency on having a function named local:equivalent by allowing both local:dedup functions to accept an additional argument providing the equivalence function.