Search code examples
xquerymarklogicmarklogic-dhf

How to Replace multiple nodes of XML in one go ? DATA HUB FRAMEWORK


I am having several documents in STAGING DB.

Based on a condition i need to check if the document with same ID exist in FINAL.

If it does then i need to replace the multiple nodes of document from STAGING to FINAL and then insert it.

DOC from STAGING-
<root>
 <ID>1</ID>
 <value1>India</value1>
 <value2>USA</value2>
 <value3>Russia</value3>
 <value4>Srilanka</value4>
 <value5>Europe</value5>
 <value6>Antartica</value6>
 <value7>Spain</value7>
</root>

DOC from FINAL-
<root>
 <ID>1</ID>
 <value1></value1>
 <value2></value2>
 <value3></value3>
 <value4></value4>
 <value5>Europe</value5>
 <value6>Antartica</value6>
</root>

OUTPUT i am expecting in FINAL-

<root>
 <ID>1</ID>
 <value1>India</value1>
 <value2>USA</value2>
 <value3>Russia</value3>
 <value4>Srilanka</value4>
 <value5>Europe</value5>
 <value6>Antartica</value6>
 <value7>Spain</value7>
</root>

From the STAGING i just need to focus on (value1,value2,value,value4,value7) and replace it. For other values i am having some different conditions so i have to ignore them.

Logic i have written in WRITER.xqy-

  let $boolean := fn:false()
  let $var := if((......)
             then
                (
                    let $docs :=
                                   cts:search(doc(),cts:and-query((
                                                                        cts:element-value-query(xs:QName("ID"),$id),
                                                                        cts:collection-query(("MyCollection"))
                                                                   ))) 
                    let $temp := 
                                    if((fn:exists($result) eq fn:true())) then xdmp:set($boolean,fn:true()) else ()
                    return $docs


                ) 
            else ()

let $envelope := if($boolean) 
                    then
                        (
                        let $nodes := ("value1,value2,value,value4,value7")
                        let $tokenize := fn:tokenize($nodes,",")
                        let $values := for $i in $tokenize
                                       let $final :=  xdmp:value(fn:concat("$var//*:root/*:",$i))
                                       let $staging :=  xdmp:value(fn:concat("<",$i,">","{$envelope//*:root/*:",$i,"/text()}","</",$i,">"))
                                       let $envelope := mem:node-replace($final,$staging) 
                                       return $envelope
                        return $values
                        ) 
               else $envelope
return
xdmp:document-insert($id,$envelope, xdmp:default-permissions(), map:get($options, "entity"))

This gives me

ERROR- ARG2 for xdmp:document-insert is NOT a NODE.

I do understand it as my $envelope is iterating for all the NODES and returning multiple envelopes.

Any Suggestions to resolve this ?


Solution

  • When you use the in-mem-update function, it returns the result of the modification.

    If you are going to be making a sequence of changes to the document, you need to use the product of the previous mem:* method calls as the input for the next. You can achieve that with a recursive function that either calls itself with one less element name, or returns the final result when there are no more names.

    Below is an example of how it could be done. I also simplified some of the logic to use XPath to select the desired elements with a predicate filter on the local-name(), instead of generating strings and evaluating with xdmp:value(). I think that it's more straightforward and easier to read.

    import module namespace mem    = "http://xqdev.com/in-mem-update" 
        at '/MarkLogic/appservices/utils/in-mem-update.xqy';
    
    declare function local:replace-elements($final-doc, $staging-doc, $element-names) {
      if (fn:empty($element-names)) then 
        $final-doc
      else
        let $name := fn:head($element-names)
        let $final :=  $final-doc//*:root/*[local-name() = $name]
        let $staging :=  $staging-doc//*:root/*[local-name() = $name]
        let $final-updated :=
          if ($final) then 
            mem:node-replace($final, $staging)
          else (: the element doesn't exist in the final doc :)
            (: insert the staging element as a child of the root element :)
            mem:node-insert-child($final-doc//*:root, $staging) 
            (: Otherwise, if you don't want to add the staging element, return $final-doc instead of inserting :)
        return
          local:replace-elements(document{$final-updated}, $staging-doc, fn:tail($element-names)) 
    };
    
    let $boolean := fn:false()
    let $var := 
      if ((......) then
        (
          let $docs :=
            cts:search(doc(), cts:and-query((
              cts:element-value-query(xs:QName("ID"), $id),
              cts:collection-query("MyCollection")
            ))) 
          let $temp := 
            if ((fn:exists($result) eq fn:true())) then 
              xdmp:set($boolean,fn:true()) 
            else ()
          return $docs
        ) 
      else ()
    
    let $envelope := 
      if ($boolean) then
        let $nodes := ("value1,value2,value3,value4,value7")
        let $element-names := fn:tokenize($nodes,",")
        return
          local:replace-elements($var, $envelope, $element-names)
      else $envelope
    
    return
        xdmp:document-insert($id,$envelope, xdmp:default-permissions(), map:get($options, "entity"))