XSLT: How to exclude empty elements from my result?

前端 未结 4 517
感动是毒
感动是毒 2020-12-05 21:06

I have a rather complicated xslt sheet transforming one xml format to another using templates. However, in the resulting xml, I need to have all the empty elements excluded.

相关标签:
4条回答
  • 2020-12-05 21:31

    The provided (partial) XSLT code illustrates well an XSLT antipattern. Try almost always to avoid the use of <xsl:for-each>.

    Below there is a sample XML document and a transformation which copies all nodes with the exception of the "empty" elements. Here by "empty" we mean either childless, or with one child whitespace-only child node.

    XML Document:

    <a>
     <b>
       <c>  </c>
       <d/>
       <e>1</e>
     </b>
    </a>
    

    Transformation:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
     <xsl:strip-space elements="*"/>
    
     <xsl:template match="node()|@*">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
     </xsl:template>
    
     <xsl:template match=
      "*[not(node())]
      |
       *[not(node()[2])
       and
         node()/self::text()
       and
         not(normalize-space())
         ]
      "/>
    </xsl:stylesheet>
    

    Result:

    <a>
       <b>
          <e>1</e>
       </b>
    </a>
    

    Do note:

    1. The use of the Identity Rule.

    2. How we override the Identity Rule with a template that only matches "empty" elements. As this template does nothing (has no body at all), this doesn't copy ("deletes") the "empty" elements.

    Using and overriding the Identity Rule is the most important XSLT design pattern.

    0 讨论(0)
  • 2020-12-05 21:39

    There are some tricky cases where Dimitre's answer (which is certainly the right approach) might behave unexpectedly. For instance, if you've refactored your XSLT to use the identity pattern (which you should), and you have created a template like this:

    <xsl:template match="Vehicle/TransportShiftNumber[. != '123']">
       <EdiActivevehicle>
          <xsl:value-of select="."/>
       </EdiActivevehicle> 
    </xsl:template>
    

    the transform may still create empty EdiActivevehicle elements if TransportShiftNumber is empty.

    Ordinarily, if multiple templates match a node, the one that's more specific will be selected. "More specific" typically means that patterns that have a predicate will beat out patterns that don't. (The actual conflict-resolution rules are more involved; see section 5.5 of the XSLT recommendation.) In this case, both the above template and the empty-element template use predicates, and thus both have the same priority.

    So the XSLT processor will do one of two things: it will report an error (that's allowed, though I've never seen an XSLT processor that unfriendly), or it will select the template that appears latest in the stylesheet.

    There are two ways to fix this. Either put the empty-element-filtering template at the bottom of the stylesheet, or explicitly assign it a priority that's higher then 0.5 (which is the default value for most patterns that have predicates):

    I'd probably do the latter, because I generally structure stylesheets with the expectation that the ordering of templates is not significant and I don't want any nasty surprises if I start moving things around. But I'd sure put a comment in there explaining myself: I've never seen anyone actually use an explicit priority on a template.

    0 讨论(0)
  • 2020-12-05 21:43

    I started with Dimitre's solution above (thanks!) but I still had output or null elements with null children like so:

                     <a>
                        <b>
                           <c/>
                           <d/>           
                        </b>             
                     </a>
    

    This seems to work... still testing.

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
      xmlns:date="http://exslt.org/dates-and-times" 
      xmlns:exsl="http://exslt.org/common" 
      xmlns:func="http://exslt.org/common"  
      xmlns:random="http://exslt.org/random"
      xmlns:regexp="http://exslt.org/regular-expressions" 
      xmlns:set="http://exslt.org/sets" 
      xmlns:str="http://exslt.org/strings" 
      version="1.0" 
      extension-element-prefixes="date exsl func random regexp set str">
    
      <xsl:output 
        method="xml" 
        encoding="utf-8" 
        omit-xml-declaration="no" 
        indent="yes"/>
    
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="node()|@*">
        <xsl:copy>
          <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match=
        "*[not(node())]
        |
        *[not(string())]
        "/>
    </xsl:stylesheet>
    
    0 讨论(0)
  • 2020-12-05 21:52

    This is probably the simplest way:

    <xsl:for-each select="Nodes/Node[text() != '']">
    
    </xsl:for-each>
    

    If you have control of the XML generation then don't add the root node if there is no children. Regardless of which way you choose XSL is quite verbose.

    0 讨论(0)
提交回复
热议问题