Pablo Lionel Arturi
Pablo Lionel Arturi

Reputation: 65

XPath selection excluding element from a child node

I need to select all <next> nodes, but excluding <element4> from each and add a new element in its place (it would be a replace). I'm working with php.

<root>
<next>
  <node>
    <element1>text</element1>
    <element2>text</element1>
    <element3>text</element1>
    <element4>text</element1>
  </node>   
  <node>
    <element1>text</element1>
    <element2>text</element1>
    <element3>text</element1>
    <element4>text</element1>
  </node>   
</next> 
</root>

so it should look like this:

<next>
  <node>
    <element1>text</element1>
    <element2>text</element1>
    <element3>text</element1>
    <new>text</new>
  </node>   
  <node>
    <element1>text</element1>
    <element2>text</element1>
    <element3>text</element1>
    <new><int xmlns="foo.bar">0</int></new>
  </node>   
</next> 

Any tips? Thank you!

Upvotes: 0

Views: 580

Answers (1)

Abel
Abel

Reputation: 57149

XPath is a selection language: it selects nodes or atomic items from an input sequence, it is the language of choice to make a selection over XML or hierarchical data, as SQL is (usually) the language of choice with relational databases.

As such, you can exclude elements from the selection, but you cannot update or change the original sequence. It is possible to do a limited transformation (i.e., turn a string in an integer), but this will change what is selected, it will not change the source. While XPath (namely version 2.0 and up) can "create" atomic values on the fly, it cannot create new elements.

This is possible and will return numeric values in XPath 2.0:

/next/node/number(.)

But this is not possible:

/next/node/(if (element4) then create-element(.) else .)

However, in XSLT 2.0 and up you can create a function that creates elements. As said above, XPath selects, and if you want to change the document, you can create a new document using XSLT (the T standing for Transformation).

Something like the following (partial XSLT 2.0, you need add headers):

<xsl:function name="f:create">
    <xsl:param name="node" />
    <xsl:param name="name" />
    <xsl:choose>
        <xsl:when test="name($node) = $name">
            <xsl:element name="{if(number($node)) then 'int' else 'new'}">
                <xsl:value-of select="$node" />
            </xsl:element>
        </xsl:when>
        <xsl:otherwise><xsl:copy-of select="$node" /></xsl:otherwise>
    </xsl:choose>
</xsl:function>

<xsl:template match="node">
    <!-- now XPath, with help of XSLT function, can conditionally create nodes -->
    <xsl:copy-of select="child::*/create(., 'element4')" />
</xsl:template>

<!-- boilerplate code, typically used to recursively copy non-matched nodes -->
<xsl:template match="node() | @*">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
    </xsl:copy>
</xsl:template>

Note that, while this shows how you can create a different element using XPath and an XSLT function, it does not change the source, it changes the output. Also, it is not a recommended practice, as in XSLT the same pattern is more easily done by simply doing:

<!-- the more specific match -->
<xsl:template match="element4[number(.)]">
    <new>
        <int  xmlns="foo.bar">
            <xsl:value-of select="number(.)" />
        </int>
    </new>
<xsl:template>

<!-- XSLT will automatically fallback to this one if the former fails -->
<xsl:template match="element4">
    <new><xsl:copy-of select="node()" /></new>
</xsl:template>

<!-- or this one, if both the former fail -->
<xsl:template match="node() | @*">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
    </xsl:copy>
</xsl:template>

Upvotes: 1

Related Questions