0

I have an XML document named employees.xml:

<employees>
    <row>
        <emp_no>10001</emp_no>
        <first_name>Georgi</first_name>
        <last_name>Facello</last_name>
    </row>
    <row>
        <emp_no>10002</emp_no>
        <first_name>Bezalel</first_name>
        <last_name>Simmel</last_name>
    </row>
</employees>

I want to write a function named my-remove-elements that will remove non-selected attributes. For example, only keep first_name and last_name in the XML document:

  <employees>
        <row>
            <first_name>Georgi</first_name>
            <last_name>Facello</last_name>
        </row>
        <row>
            <first_name>Bezalel</first_name>
            <last_name>Simmel</last_name>
        </row>
    </employees>

The definition of my function is:

declare function local:my-remove-elements($input as element(), $remove-names as xs:string*) as element() {
   element {node-name($input) }
      {$input/@*,
       for $child in $input/node()[name(.)=$remove-names]
          return
             if ($child instance of element())
                then local:my-remove-elements($child, $remove-names)
                else $child
      }
};

This is the way I call it:

  let $doc := doc("employees.xml")
    return 
         local:my-remove-elements($doc, ('first_name', 'last_name'))

It throws me "err:XPTY0004 ... is not a sub-type of element()..." I have changed the code:

let $rows:= doc("employees.xml")//row
        return 
             local:my-remove-elements($rows, ('first_name', 'last_name'))

this time is still: "err:XPTY0004: The actual cardinality for parameter 1 does not match the cardinality in the function's signature ...". Do you know how to fix this and make it work?

Judah Flynn
  • 544
  • 1
  • 8
  • 20

2 Answers2

0

Here’s one possible solution:

declare function local:remove-elements(
  $node as node(),
  $keep-names as xs:string*
) as node()? {
  if($node instance of element()) then (
    let $name := name($node)
    where $name = $keep-names
    return element { $name } {
      $node/@*,
      for $child in $node/node()
      return local:remove-elements($child, $keep-names)
    }
  ) else (
    $node
  )
};

local:remove-elements(
  doc("employees.xml")/employees,
  ('employees', 'row', 'first_name', 'last_name')
)

Here’s another one, which uses XQuery Update:

declare function local:remove-elements(
  $node as node(),
  $keep-names as xs:string*
) as node()? {
  copy $c := $node
  modify delete node $c//*[not(name() = $keep-names)]
  return $c
};

local:remove-elements(
  doc("employees.xml")/employees,
  ('employees', 'row', 'first_name', 'last_name')
)
Christian Grün
  • 6,012
  • 18
  • 34
0

The error err:XPTY0004 is because you are invoking the function passing a document-node() as the first parameter, but the first parameter needs to be an element().

Change how you are invoking it, passing in the document element (i.e. $doc/*) instead of the document-node()

Then, you need to adjust the logic in your function. If you were only to select $child nodes that are equal to the $remove-names (which is confusing, consider renaming to something like $keep-leaf-names), then the employees elements would be excluded. Move that filter down, and add logic to process if the element has children or it's name() is equal to any of the $remove-names.

declare function local:my-remove-elements($input as element(), $remove-names as xs:string*) as element() {
   element {node-name($input) }
      {$input/@*,
       for $child in $input/node()
          return
             if ($child instance of element()) then 
                if ($child/* or name($child)=$remove-names) then 
                  local:my-remove-elements($child, $remove-names) 
                else ()
             else $child
      }
};
Mads Hansen
  • 63,927
  • 12
  • 112
  • 147