简体   繁体   中英

Use XSLT to transform XML with conditionals upon node count

I am trying to remove nodes from an XML file. Using just one XSLT for each of my XML I need to make decisions in the XSLT based upon the number of children of the document element.

<root>
  <branch>
    <foo>bar</foo>
  </branch>
<root>

should transform into

  <branch>
  </branch>

but

<root>
  <branch>
    <foo>bar</foo>
  </branch>
  <branch>
    <foo>baz</foo>
<root>

into

<root>
  <branch>
  </branch>
  <branch>
  </branch>
<root>

That is, the root element should be removed if its (only) child can act as the new document root of the result XML after applying XSLT. The <foo> nodes have to be removed in every occurence.

Is there a way to perform this operation with a single XSL?

Try

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

<xsl:template match="root[*[2]]">
  <xsl:copy>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>

<xsl:template match="root[* and not(*[2])]">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="branch/foo"/>

A simpler, shorter and more generic (no element names hardcoded) solution :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

 <xsl:template match="/*[not(*[2])]">
  <xsl:apply-templates/>
 </xsl:template>

 <xsl:template match="/*/*/node()"/>
</xsl:stylesheet>

when this transformation is applied on the first provided XML document (corrected to be made well-formed):

<root>
    <branch>
        <foo>bar</foo>
    </branch>
</root>

the wanted, correct result is produced :

<branch></branch>

When the same transformation is applied on the second provided XML document (again needed to be corrected for well-formedness):

<root>
    <branch>
        <foo>bar</foo>
    </branch>
    <branch>
        <foo>baz</foo>
    </branch>
</root>

again the wanted, correct output is produced :

<root>
    <branch></branch>
    <branch></branch>
</root>

Explanation :

  1. The identity rule copies every node "as-is".

  2. There are two templates that override the identity template for specific nodes and process these nodes in different ways.

  3. The first overriding template matches a top element that doesn't have a second element child. It doesn't copy the element itself, but processes its children.

  4. The second overriding template matches any element that is a grand-child of the top element. This template doesn't have a body, which means that all such matched element are ignored and not included in the output (in other words -- "deleted")

Do note :

This transformation can be applied to any XML document, regardless of the element names in it, and still produces the wanted, correct result.

For example , when applied on this XML document:

<t>
    <b>
        <f>brrr</f>
    </b>
    <b>
        <f>bzzz</f>
    </b>
</t>

the wanted, correct result is produced :

<t>
    <b></b>
    <b></b>
</t>

Contrast this to the result produced by the currently - accepted answer :

<t>
    <b>
        <f>brrr</f>
    </b>
    <b>
        <f>bzzz</f>
    </b>
</t>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM