Toya
Toya

Reputation: 37

XSL Transform - Conditional Removing of Empty Tags

I have xsl to remove all empty tags, but I only want to remove empty tags if all tags for that section are empty. Take a look at my input file for example:

<NEWORDER>
<ID>1</ID>
<HEADER>
   <NMP>NAME PLATE</NMP>
   <ORDER>
     <USER_ID>USER ID</USER_ID>
     <PARTNER_ID>PARTNER ID</PARTNER_ID>
     <USER_REFERENCE>5555-55555555-5555</USER_REFERENCE>
     <PO_HEADER>
        <PO_NUMBER>5555-55555555-5555</PO_NUMBER>
        <PO_DATE>20170322</PO_DATE>
        <PO_TYPE>BT</PO_TYPE>
        <RELEASE_NBR></RELEASE_NBR>
        <CUST_ORDER_NBR>5555555555</CUST_ORDER_NBR>
        <CONTACT_NAME></CONTACT_NAME>
        <CONTACT_PHONE></CONTACT_PHONE>
        <TRANS_METHOD></TRANS_METHOD>
        <SHIP_COMP>SC</SHIP_COMP>
        <CURR_CODE>USD</CURR_CODE>
        <INCO_TERMS>FOB</INCO_TERMS>
        <NAMED_PLACE></NAMED_PLACE>
        <PAYMENT_METHOD>BT</PAYMENT_METHOD>
        <TERM_TYPE>555</TERM_TYPE>
        <TERM_DESC>NET 10 DAYS</TERM_DESC>
        <DEST_BRANCH>5555</DEST_BRANCH>
    </PO_HEADER>
    <PO_HEADER_NOTES>
      <TEXT1_QUAL></TEXT1_QUAL>
      <TEXT1_MSG></TEXT1_MSG>
    </PO_HEADER_NOTES>
  </ORDER>
 </HEADER>
</NEWORDER>

In this example I would want the output to only delete the "PO_HEADER_NOTES" section as all inner tags are empty. Example of expected output:

<NEWORDER>
<ID>1</ID>
<HEADER>
   <NMP>NAME PLATE</NMP>
   <ORDER>
     <USER_ID>USER ID</USER_ID>
     <PARTNER_ID>PARTNER ID</PARTNER_ID>
     <USER_REFERENCE>5555-55555555-5555</USER_REFERENCE>
     <PO_HEADER>
        <PO_NUMBER>5555-55555555-5555</PO_NUMBER>
        <PO_DATE>20170322</PO_DATE>
        <PO_TYPE>BT</PO_TYPE>
        <RELEASE_NBR></RELEASE_NBR>
        <CUST_ORDER_NBR>5555555555</CUST_ORDER_NBR>
        <CONTACT_NAME></CONTACT_NAME>
        <CONTACT_PHONE></CONTACT_PHONE>
        <TRANS_METHOD></TRANS_METHOD>
        <SHIP_COMP>SC</SHIP_COMP>
        <CURR_CODE>USD</CURR_CODE>
        <INCO_TERMS>FOB</INCO_TERMS>
        <NAMED_PLACE></NAMED_PLACE>
        <PAYMENT_METHOD>BT</PAYMENT_METHOD>
        <TERM_TYPE>555</TERM_TYPE>
        <TERM_DESC>NET 10 DAYS</TERM_DESC>
        <DEST_BRANCH>5555</DEST_BRANCH>
    </PO_HEADER>
  </ORDER>
 </HEADER>
</NEWORDER>

The xsl that I am using to delete all empty nodes is below:

<?xml version="1.0" encoding="UTF-8"?>

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

    <xsl:template match="*">
    <xsl:if test=". != '' or ./@* != ''">
        <xsl:element name="{local-name()}">
            <xsl:apply-templates select="@* | node()" />
        </xsl:element>
    </xsl:if>
    </xsl:template>

   <xsl:template match="@*">
        <xsl:attribute name="{local-name()}">
            <xsl:value-of select="." />
        </xsl:attribute>
   </xsl:template>
       <xsl:template match="text() | comment() | processing-instruction()">
       <xsl:copy />
    </xsl:template>
</xsl:stylesheet>

Please advise. Any help is appreciated.

Upvotes: 0

Views: 706

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 117165

I only want to remove empty tags if all tags for that section are empty.

That's not a very clear requirement. Looking at the given example, it looks like it should be restated as: remove any element that (a) has children, but (b) does not contain any text nodes - either as direct children, or children of one of its descendants. This would be implemented as:

XSLT 1.0

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

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

<xsl:template match="*[* and not(descendant::text())]"/>

</xsl:stylesheet>

Result

<?xml version="1.0" encoding="UTF-8"?>
<NEWORDER>
  <ID>1</ID>
  <HEADER>
    <NMP>NAME PLATE</NMP>
    <ORDER>
      <USER_ID>USER ID</USER_ID>
      <PARTNER_ID>PARTNER ID</PARTNER_ID>
      <USER_REFERENCE>5555-55555555-5555</USER_REFERENCE>
      <PO_HEADER>
        <PO_NUMBER>5555-55555555-5555</PO_NUMBER>
        <PO_DATE>20170322</PO_DATE>
        <PO_TYPE>BT</PO_TYPE>
        <RELEASE_NBR/>
        <CUST_ORDER_NBR>5555555555</CUST_ORDER_NBR>
        <CONTACT_NAME/>
        <CONTACT_PHONE/>
        <TRANS_METHOD/>
        <SHIP_COMP>SC</SHIP_COMP>
        <CURR_CODE>USD</CURR_CODE>
        <INCO_TERMS>FOB</INCO_TERMS>
        <NAMED_PLACE/>
        <PAYMENT_METHOD>BT</PAYMENT_METHOD>
        <TERM_TYPE>555</TERM_TYPE>
        <TERM_DESC>NET 10 DAYS</TERM_DESC>
        <DEST_BRANCH>5555</DEST_BRANCH>
      </PO_HEADER>
    </ORDER>
  </HEADER>
</NEWORDER>

Upvotes: 1

Eir&#237;kr &#218;tlendi
Eir&#237;kr &#218;tlendi

Reputation: 1190

Given your requirements, the following single template seems to fit the bill:

  • Copies elements with text or attributes.
  • For elements with no text or attributes:
    • Copies them if they have no children of their own.
    • Deletes them if they have children, but the children also have no text or attributes.

Note that this is slightly different from your sample XSLT -- for attributes, that only checked if an element's attributes evaluated to '', the empty string. In my own experience, I have (rarely) run into cases where an element might have an empty-string attribute, and where that attribute needs to be maintained, so the code below accounts for that by checking for the existence of attributes, rather than just the value of attributes. Tweak to match your requirements.

<xsl:template match="*">
    <xsl:choose>
        <!-- Strip only if:
            1) Element has children.
            2) Nothing in the tree starting here contains any text or attributes. -->
        <xsl:when test="./* and not(descendant-or-self::*[text() or @*])"/>
        <!-- In all other cases, just copy over, and process children. -->
        <xsl:otherwise>
            <!-- Copies the element itself. -->
            <xsl:copy>
                <!-- Copies all attributes, if there are any. -->
                <xsl:copy-of select="@*"/>
                <!-- Sends any children along for further processing. -->
                <xsl:apply-templates/>
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Upvotes: 0

Related Questions