Search code examples
xmlxquerymarklogic

How to wrap several nodes within a XML document to a new node using XQuery?


I want to Wrap multiple nodes (specific nodes) into a new single node within my xml document and then want to insert it.

Example XML Document-

<root>
  <value1>somevalue</value1>
  <value2>somevalue</value2>
  <value3>somevalue</value3>
  <value4>somevalue</value4>
  <value5>Australia</value5>
  <value6>India</value6>
  <value7>USA</value7>
  <value8>somevalue</value8>
  <value9>somevalue</value9>
  <value10>somevalue</value10>
</root>

Since my value5 to value7 are name of the countries, i want to put them at the same father node. The output need to look like this:

Output-

<root>
  <value1>somevalue</value1>
  <value2>somevalue</value2>
  <value3>somevalue</value3>
  <value4>somevalue</value4>
  <Country>
    <value5>Australia</value5>
    <value6>India</value6>
    <value7>USA</value7>
  </Country>
  <value8>somevalue</value8>
  <value9>somevalue</value9>
  <value10>somevalue</value10>
</root>

Similarly if my other values belong to some other fields/properties then i want to WRAP them in a new single node.

Any Suggestions?


Solution

  • You could achieve what you are trying to do in XSLT, using xsl:for-each-group.

    If you want to group them when the value is not equal to "somevalue", then you could use group-adjacent to test whether the element value is equal to "somevalue" or not, and then wrap those that are not in the <country> element.

    You can execute an XSLT within your XQuery module in MarkLogic, like this:

    xquery version "1.0-ml";
    declare variable $doc := document {
    <root>
      <value1>somevalue</value1>
      <value2>somevalue</value2>
      <value3>somevalue</value3>
      <value4>somevalue</value4>
      <value5>Australia</value5>
      <value6>India</value6>
      <value7>USA</value7>
      <value8>somevalue</value8>
      <value9>somevalue</value9>
      <value10>somevalue</value10>
    </root>
    };
    
    declare variable $grouping-xslt :=
      <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output omit-xml-declaration="yes" indent="yes" />
    
        <xsl:template match="root">
            <xsl:copy>
                <xsl:for-each-group select="*" group-adjacent=". = 'somevalue'">
                    <xsl:choose>
                        <xsl:when test="current-grouping-key()">
                            <xsl:copy-of select="current-group()"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <country>
                                <xsl:copy-of select="current-group()"/>
                            </country>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each-group>        
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>;
    
    xdmp:xslt-eval($grouping-xslt, $doc)
    

    If you have a known sequence of country names that you would want to group by, then you could do that with group-by and test whether the value matches any of the country names:

    xquery version "1.0-ml";
    declare variable $doc := document {
    <root>
      <value1>somevalue</value1>
      <value2>somevalue</value2>
      <value3>somevalue</value3>
      <value4>somevalue</value4>
      <value5>Australia</value5>
      <value6>India</value6>
      <value7>USA</value7>
      <value8>somevalue</value8>
      <value9>somevalue</value9>
      <value10>somevalue</value10>
    </root>
    };
    
    declare variable $grouping-xslt :=
      <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output omit-xml-declaration="yes" indent="yes" />
        <xsl:param name="countries" />
        <xsl:template match="root">
            <xsl:copy>
                <xsl:for-each-group select="*" group-by=". = $countries">
                    <xsl:choose>
                        <xsl:when test="current-grouping-key()">
                            <country>
                                <xsl:copy-of select="current-group()"/>
                            </country>
                        </xsl:when>
                        <xsl:otherwise>
                             <xsl:copy-of select="current-group()"/>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each-group>        
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>;
    
    declare variable $params := map:new(map:entry("countries", ("Australia", "India", "USA")));
    
    xdmp:xslt-eval($grouping-xslt, $doc, $params)