Search code examples
xmlxquerymarklogic

How to add attribute to all particular children of a node


I have the following node in which I want to add attribute to all add nodes.

<test>
  <add>x1</add>
  <c><add>x2</add></c>
  <b att1="x">x</b>
</test>

I tried

functx:add-attributes($test, xs:QName('att1'), 1)

It can add the attribute to the test node. But

When I tried

functx:add-attributes($test/add, xs:QName('att1'), 1)

It added the attribute to the first add node but returns only add node with added attribute. Then when I tried with $test//add it throws error.

When I tried

for $add in $test//add 
   return functx:add-attributes($add, xs:QName('att1'), 1)

It returns two add nodes individually. Now, how to restructure the original node to add the attributes to only the specified nodes.


Solution

  • First, let me point out that there is a difference in how this is done for just in-memory use versus updating the content of the database. For the latter, you could do:

    for $add in $test//add
    return
      xdmp:node-insert-child(
        $add, 
        attribute atta1 { 1 }
      )
    

    To change it in memory, which is what functx does, you'll be making a copy of the original, making changes in the copy as you build it. This is called recursive descent and is a pretty common pattern. I wrote a blog post a while ago that shows how to implement recursive descent, but essentially you'll do a typeswitch that, when it encounters an "add" element, creates the new attribute. You can use the functx function for that. Something along these lines (untested):

    declare function local:change($node) 
    { 
      typeswitch($node) 
        case element(add) return 
          functx:add-attributes($node, xs:QName('att1'), 1)
        case element() return 
          element { fn:node-name($node) } { 
            $node/@*, 
            $node/node() ! local:change(.)
          } 
        default return $node 
    };
    

    This code assumes that an add element won't have add elements inside of it; if you will, then you'd want to do something like the second case for the first.