Search code examples
xquerymarklogic

How to Replace multiple nodes of XML efficiently?


I am trying to replace around 500 Nodes in a single go for a single document and i am having 5000+ document in my database.

The code which i am using is related to this SO question i asked previously- Link

Any suggestions to make my code efficient ?


Solution

  • The in-mem-update library should generally be avoided, especially when making numerous method calls for the same document. Since each method walks the entire node tree and generates a brand new document, it can be slow and expensive if you run against large documents and/or make a bunch of mem:* method calls on those docs.

    A better replacement would be Ryan Dew's XQuery XML Memory Operations library, or to use XSLT.

    Below is an example of how you could perform this sort of "merge" with XSLT that should perform way better than the in-mem-update methods:

    declare variable $STAGING := document{
    <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>
    };
    
    declare variable $FINAL := document{
    <root>
     <ID>1</ID>
     <value1></value1>
     <value2></value2>
     <value3></value3>
     <value4></value4>
     <value5>Europe</value5>
     <value6>Antartica</value6>
    </root>
    };
    
    declare variable $XSLT := 
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    
      <xsl:param name="staging-doc"/>
      <xsl:param name="element-names"/>
    
      <xsl:variable name="final-doc" select="/"/>
    
      <xsl:key name="staging-elements" match="root/*[local-name() = $element-names]" use="local-name()"/>
    
      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="root/*[local-name() = $element-names]">
        <!--if there is an element in the staging-elements doc with this name, use it. Otherwise, use the matched element from this doc -->
        <xsl:copy-of select="(key('staging-elements', local-name(.), $staging-doc), .)[1]"/>
      </xsl:template>
    
      <xsl:template match="root">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
          <!-- also copy any other elements with the specified names from the staging document that were not already in the final -->
          <xsl:apply-templates select="$staging-doc/root/*[local-name() = $element-names and not(key('staging-elements', local-name(), $final-doc))]"/>
        </xsl:copy>
      </xsl:template>
    
    </xsl:stylesheet>;
    
    declare variable $PARAMS := map:new((
        map:entry("staging-doc", $STAGING), 
        map:entry("element-names", tokenize(("value1,value2,value3,value4,value7"), ",") )
      ));
    
    xdmp:xslt-eval($XSLT, $FINAL, $PARAMS)