John
John

Reputation: 2852

XQuery to delete node from parent and insert it as sibling

Following is the XML Structure -

<Docs>
 <Doc>
   <P>blah blah blah<pg>1</pg>blah blah</P>
   <P>blah blah blah<pg>2</pg>blah blah</P>
 </Doc>
 <Doc>
   <P>blah blah blah<pg>3</pg>blah blah</P>
   <P>blah blah blah<pg>4</pg>blah blah</P>
 </Doc>
</Docs>

I want to delete the pg node within the P node and insert it as a sibling of P node. Like this -

<Docs>
 <Doc>
   <P>blah blah blah</P>
   <pg>1</pg>
   <P>blah blah</P>
   <P>blah blah blah</P>
   <pg>2</pg>
   <P>blah blah</P>
 </Doc>
 <Doc>
   <P>blah blah blah</P>
   <pg>3</pg>
   <P>blah blah</P>
   <P>blah blah blah</P>
   <pg>4</pg>
   <P>blah blah</P>
 </Doc>
</Docs>

How to get it done ?

Upvotes: 0

Views: 717

Answers (4)

grtjn
grtjn

Reputation: 20414

XQuery 3.0 on the other hand, provides the new tumbling window feature that helps to mimic the for-each-group behavior described in my other answer. Here is how it looks like:

xquery version "3.0";

let $xml :=
  <Docs>
    <Doc>
        <P>blah blah blah<pg>1</pg>blah blah</P>
        <P>blah blah blah<pg>2</pg>blah blah</P>
    </Doc>
    <Doc>
        <P>blah blah blah<pg>3</pg>blah blah</P>
        <P>blah blah blah<pg>4</pg>blah blah</P>
    </Doc>
  </Docs>
return
  <Docs>{
    for $doc in $xml/Doc
    return
        <Doc>{
            for $P in $doc/P
            return
                for tumbling window $w in $P/node()
                start when true()
                end next $e
                    when $e instance of element(pg)
                return (
                    $w[self::pg],
                    <P>{
                        $w[not(self::pg)]
                    }</P>
                )
        }</Doc>
  }</Docs>

It does require an XQuery processor with 3.0 support, including these tumbling windows. Zorba is an excellent example, you can test this code online at http://try.zorba.io

HTH!

Upvotes: 1

grtjn
grtjn

Reputation: 20414

XSLT looks more suited for this task indeed. XSLT 2.0 provides the xsl:for-each-group feature that can be very usefull here. It is more robust than matching just on text() nodes inside the P. That will certainly help if the P element contains other inline elements beside the pg markers.

Here the 2.0 solution, a slight alteration of the solution by Daniel Haley:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="P">
        <xsl:variable name="P" select="."/>
        <xsl:for-each-group select="node()" group-starting-with="pg">
            <xsl:apply-templates select="self::pg"/>
            <xsl:element name="{node-name($P)}">
                <xsl:apply-templates select="$P/@*|current-group()[not(self::pg)]"/>
            </xsl:element>
        </xsl:for-each-group>
    </xsl:template>

</xsl:stylesheet>

HTH!

Upvotes: 2

Jens Erat
Jens Erat

Reputation: 38682

This is an XQuery solution if you've got support for XQuery Update.

for $p in $c//P                       (: for each paragraph tag :)
return (
  for $node in $p/(text(), pg)        (: find all subnodes :)
  return (
    let $node :=
      if ($node/self::text())
      then element P { $node }        (: wrap text nodes in new paragraph tags :)
      else $node
    return insert node $node after $p (: insert the node after the old paragraph tag :)
  ),
  delete node $p                      (: drop the old paragraph tag :)
)

Just realized a version without XQuery Update (only returning the results) is even shorter:

element Docs {
  element Doc {
    for $node in //P/(text(), pg)
    return
      if ($node/self::text())
      then element P { $node }
      else $node
  }
}

Upvotes: 2

Daniel Haley
Daniel Haley

Reputation: 52858

Here's an XSLT option...

XML Input

<Docs>
    <Doc>
        <P>blah blah blah<pg>1</pg>blah blah</P>
        <P>blah blah blah<pg>2</pg>blah blah</P>
    </Doc>
    <Doc>
        <P>blah blah blah<pg>3</pg>blah blah</P>
        <P>blah blah blah<pg>4</pg>blah blah</P>
    </Doc>
</Docs>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="P">
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="P/text()">
        <P><xsl:value-of select="."/></P>
    </xsl:template>

</xsl:stylesheet>

XML Output

<Docs>
   <Doc>
      <P>blah blah blah</P>
      <pg>1</pg>
      <P>blah blah</P>
      <P>blah blah blah</P>
      <pg>2</pg>
      <P>blah blah</P>
   </Doc>
   <Doc>
      <P>blah blah blah</P>
      <pg>3</pg>
      <P>blah blah</P>
      <P>blah blah blah</P>
      <pg>4</pg>
      <P>blah blah</P>
   </Doc>
</Docs>

Upvotes: 3

Related Questions