XML empty element, XSLT

Empty Element

1. How to replace an empty element
2. to test if the value of a certain node is empty
3. Testing for an empty element
4. I want to match any node with no data.
5. How to handle empty tags
6. Matching empty tags
7. Testing for Empty elements
8. Empty elements
9. How to delete empty element tag from output XML
10. Testing for empty elements
11. Check for null value and Check if child tag exists
12. Empyt node additional offering
13. More on empty
14. Output a default value for an empty node
15. Test for empty elements

1.

How to replace an empty element

DaveP

how to replace a singleton tag(e.g. <eos/>) with another tag(e.g. <BR/>)?

<xsl:template match="eos">
  <BR />
</xsl:template>

JT offers a longer explanation

>Does anyone have the idea of how to replace a singleton tag(e.g. <eos/>)
>with another tag(e.g. <BR/>)?

These are called 'empty elements' in XML-speak.

><xsl:template match="eos">
>??? What should go here???
></xsl:template>

Well, when you come across an 'eos' element, you want to generate an empty 'BR' element, so:

<xsl:template match="eos">
  <xsl:element name="BR" />
</xsl:template>

Or, shorter:

<xsl:template match="eos">
  <BR />
</xsl:template>

2.

to test if the value of a certain node is empty

Michael Kay




Depends what you mean by empty.

Contains no child nodes: not(node())

Contains no text content: not(string(.))

Contains no text other than whitespace: not(normalize-space(.))

Contains nothing except comments: not(node()[not(self::comment())])

3.

Testing for an empty element

Jeni Tennison

To test here whether the VENDOR_ITEM_NUM element is an empty element or not - in other words does it have any child nodes.

You can form a node set that holds the child nodes of the current node (the VENDOR_ITEM_NUM element using the XPath:

  child::node()

Note however, that this will get *any* node in the content of the element: elements, text, processing instructions and even comments. Probably you want to limit this to only elements and text nodes that consist of more than just whitespace:

  child::text()[normalize-space(.)] | child::*

or, shorter:

  text()[normalize-space(.)] | *

These XPaths actually tell you whether the element has any content. In fact, VENDOR_ITEM_NUM probably only takes text children anyway, and it's only their value that you care about. If you are interested in whether the VENDOR_ITEM_NUM element has some textual content that isn't just whitespace you can get the nodes that make up that text using just:

  text()[normalize-space(.)]

If you just run a test on that node set, then you will get 'true' if there are any nodes in the node set, and 'false' if there aren't. But in a test like this, then you can alternatively test whether the current node has a 'string value' (which is formed by concatenating together the string values of all the text and element child nodes), so you can use:

  normalize-space(.)

So try:

<xsl:template match="VENDOR_ITEM_NUM">	
  <tr>
    <td></td>
    <td>
      <font size="2">
        <xsl:if test="normalize-space(.)">
          <xsl:value-of select="concat('Vendor Item number: ',.)"/>
        </xsl:if>
      </font>
    </td>
  </tr>
</xsl:template>

If you want to use a variable to hold an empty string to test against, you should probably define it as:

    <xsl:variable name="empty_string"/>

as this creates an empty *string* rather than an empty 'result tree fragment'.

You should then be able to test with it as in:

<xsl:template match="VENDOR_ITEM_NUM">	
  <tr>
    <td></td>
    <td>
      <font size="2">
        <xsl:if test="normalize-space(.) != $empty_string">
          <xsl:value-of select="concat('Vendor Item number: ',.)"/>
        </xsl:if>
      </font>
    </td>
  </tr>
</xsl:template>

but don't forget to normalize-space() the content of the element, just to get rid of any whitespace that might be lurking around.

4.

I want to match any node with no data.

Michael Kay

    example:
 <field1>data</field1>
 <field2></field2>

How do I match field2's null.

This should match any element that has no immediate text content (but I can't speak for LotusXSL). Note that it will also match elements that have whitespace text content if the whitespace is stripped.

If you want the element to have "no data" in the sense that it has no *descendant* text nodes you could try:

<xsl:template match="*[.='']">

5.

How to handle empty tags

Ben Robb

 
E.g. 
<street_1>xxxx Drive</street_1>
     <street_2><street_2>



<xsl:if test="street_2[.!='']">
<P>Street 2 :
         <xsl:value-of select="street_2"/></P>
 </xsl:if>
            

6.

Matching empty tags

merle

For <RESUME></RESUME>

I just do for example:
  <xsl:choose>
   <xsl:when test='RESUME[.!=""]'>
    resume: <I><xsl:value-of select="RESUME/text()"/></I>
   </xsl:when>
  </xsl:choose>
            

7.

Testing for Empty elements

Mike Kay

To test if the element exists:

<xsl:template match="book">
<xsl:if test="author">

To test if the element exists and is non-empty:

<xsl:template match="book">
<xsl:if test="string(author)">

To test that the element exists and has text and/or element content:

<xsl:if test="author/* or author/text()">

David Carlisle adds

<xsl:when test="* | text()">

and even more easily but not quite equivalent as it also tests for comments and processing instructions:

<xsl:when test="node()">

8.

Empty elements

David Pawson

Playing with Mike Kays definition of empty elements, Redhat archive

<doc>
<node id="@id00"/>
<node id="id1"> </node>
<node id="id02"><!--comment--></node>
<node id="id03"><x/></node>
<node id="id04"><x> </x></node>
 </doc>

<xsl:template match="node">
  <xsl:call-template name="mt"/>
</xsl:template>

<xsl:template name="mt">
  <xsl:choose>
  <xsl:when test="not(string(.))">
    Node: <xsl:value-of select="@id"/> is  empty using string(.)&nl;
  </xsl:when>
  <xsl:when test="string(.)">
    Node: <xsl:value-of select="@id"/> is not empty using string(.)&nl;
  </xsl:when>
<xsl:otherwise>
  Otherwise 1 reached on  <xsl:value-of select="@id"/> .&nl;
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
  <xsl:when test="not(node())">
    Node: <xsl:value-of select="@id"/> has no children &nl;
  </xsl:when>
  <xsl:when test="node()">
    Node: <xsl:value-of select="@id"/> has children &nl;
  </xsl:when>
<xsl:otherwise>
  Otherwise 2 reached on  <xsl:value-of select="@id"/> .&nl;
</xsl:otherwise>
</xsl:choose>
</xsl:template>

gives

  Node: @id00 is  empty using string(.)
  Node: @id00 has no children 
 
  Node: id1 is not empty using string(.)
  Node: id1 has children 
 
  Node: id02 is  empty using string(.)
  Node: id02 has children 
 
  Node: id03 is  empty using string(.)
  Node: id03 has children 

  Node: id04 is not empty using string(.)
  Node: id04 has children

David Carlisle notes that if I had used <xsl:strip-space elements="node x"/> then all would show as empty using this stylesheet.

Mike Kay notes:

<a> <!--comment-->A</a>

     will output <a> A</a>
     rather than <a>A</a>

9.

How to delete empty element tag from output XML

Kay Michael


> I use XSLT to convert XML to XML and HTML. I need to delete 
> the tag when
> there are no value in the output. for example, I have XSLT as 
> following:
> 	<NAME><xsl:value-of select="N1" /></NAME>
> I don't need <NAME /> or <NAME> </NAME>.
> One way I can do it is use <xsl:if> outside of <Name> tag, 
> but that's too
> much for me because I have a lot of similar tags.
	

Try

<xsl:template match="*" mode="copy-unless-empty">
 <xsl:if test="node()">
  <xsl:copy>
   <xsl:copy-of select="@*"/>
   <xsl:value-of select="."/>
  </xsl:copy>
 </xsl:if>
</xsl:template>

and then use <xsl:apply-templates mode="copy-unless-empty"/> to process these elements.

10.

Testing for empty elements

David Carlisle

I have a need to identify empty text-only elements and ignore them. The input document might contain an element in one or more of the following forms

<SOME_TAG/>
<SOME_TAG  SOME_ATTRIBUTE="some_value"/>
<SOME_TAG>    </SOME_TAG>
<SOME_TAG>some_text_here</SOME_TAG>

For my purpose all but the last one above are empty elements and need to be ignored. Is there a simple mechanism for identifying such elements. The simple xpath expression

//SOME_TAG[. !=''] ignores just the first two cases, but selects the third one containing the white-space .

Answer: either list SOME_TAG in the strip-space elements list, in which case white space nodes appearing as children of SOME_TAG won't appear in the input tree, so your current test will work, or use

//SOME_TAG[normalize-space(.) != '']

11.

Check for null value and Check if child tag exists

Jeni Tennison


> I'm working on transfering data from and XML file to a
> Database. For this I need to convert my XML File into the canonical
> form with the help of an XSLT. These are a few problems am facing
>
> 1) I need to be able to check if a particular tag has one or more
> child tags and be able to get their values.

You can check whether a particular element has a particular child element by trying to select the child element and testing the resulting node set. If the node set has a node in it, then it shows that the child element exists. If the node set doesn't have a node element, then it doesn't. Empty node sets evaluate as boolean false, while ones with nodes in evaluate as boolean true.

> 2) I also need to be able to finout if a tag has a null value.

By 'null value' you probably mean 'empty' - doesn't have any child nodes - no text, no elements. As above, you can test this by trying to create a node set of the child nodes that show whether it's a 'null' value or not. So if it's any nodes, then you could use:

  node()

If you're only interested in text and element content (i.e. an element will still have a 'null' value if it holds a comment or a processing instruction), then you could use:

  *|text()

Or if you're just concerned about the text content, and you don't care about whitespace, then you could use:

  normalize-space()

This gets the string value of the context node and strips any leading or trailing whitespace, so it only returns true if there is some character data within the element.

Anyway, that's just to answer your questions - as far as doing what you're trying to do is concerned, you are trying to get a row element for every City element in your input XML. If the City element has a parent Region element, then you want to use the @Name of that Region in the row, and the Country/@Name is used as well.

The rowset element in the result corresponds to the World element in your source, so I'd have a World-matching template to create it. I'd apply templates to those City elements directly within that, to save the processor some work in trying to apply templates to everything:

<xsl:template match="World">
   <rowset>
      <xsl:apply-templates select="Country/City |
                                   Country/Region/City" />
   </rowset>
</xsl:template>

[Note: you could use .//City instead, but the above is a little more efficient because it avoids the descendant axis.]

Now, for each City element that's having templates applied to it, you want to create a row element in the result. So again, I'd make a template matching the City elements. In this template, you need to get the value of the City's Region (if it has one) and its Country. You can get these by moving up the node tree, using the ancestor:: axis:

<xsl:template match="City">
   <row>
      <country>
         <xsl:value-of select="ancestor::Country/@Name" />
      </country>
      <region>
         <xsl:value-of select="parent::Region/@Name" />
      </region>
      <city>
         <xsl:value-of select="@Name" />
      </city>
   </row>
</xsl:template>

The one difference between the sample output that you gave and that given by the two templates above is that in your source you have:

  <Country Name="Afghanistan">
     <Region Name="Herat">
        <City Name="Herat" Time="GMT+04:30"></City>
     </Region>
     ...
  </Country>

Giving:

  <row>
     <country>Afghanistan">
     <region />
     <city>Herat</city>
  </row>

Whereas the above templates have Herat as the region as well as the city. I guess that you don't want to have the region specified if its Name is the same as the Name of the City? In that case, you need to change the above template to contain, the following, which tests whether the region is called the same as the city and only adds it if not:

  <region>
     <xsl:variable name="region" select="parent::Region/@Name" />
     <xsl:if test="$region != @Name">
        <xsl:value-of select="$region" />
     </xsl:if>
  </region>
  

12.

Empyt node additional offering

David Carlisle




> 1. is <b code="123"/> is empty node just like <c/> ?

There's no such thing as an "empty node" defined in the XPath data model, so you can define the term any way you like. Popular definitions include "has no child nodes", "has zero-length string value", "has no child elements or text nodes". These are all different, for example <e><!-- comment --></e> is empty under the second and third definitions but not the first.

> 2. what's wrong with my not() function ?
not(.) is true if the current node does not exist. But it always does.

You want

<xsl:when test="not(node())"/>

which is short for

<xsl:when test="not(child::node())"/>

and tests if the current node has child nodes (including text and comments etc.

test="not(node())"    has no child nodes

<a/> and <a x="y"/> would be true
<a><!-- --></a> <a> </a> would be false (unless white space nodes are
stripped in which case <a> </a> would be the same as <a/>


test="not(string(.))"  has zero-length string value

<a/>  <a x="y"/> <a><!-- --></a> <a><b/></a> would all be true

test="not(* or text())" has no child elements or text nodes

<a/> and <a><!-- --></a>  true



<c/> means "has no child nodes" or "has zero-length string value" or "has
 no child elements or text nodes" ?

all of those are true about <c/>

13.

More on empty

David Carlisle

as always involving tests for empty, it depends what you mean by empty

 //*[@name][(not(node()))]

tests for no element children at all (and a name attribute) so

<a name=""> </a> is non-empty, as is
<a name=""><!-- this --></a>

If you want want white space to count as empty then

//*[@name][not(normalize-space()))]

now the two above are selected (ie counted as empty) but so is

<a name=""> <b/> </a>

If sub elements may be there you may want somnething like

 //*[@name][not(*)][not(normalize-space()))]

14.

Output a default value for an empty node

David Carlisle

<xsl:template match="foo">
 <xsl:copy-of select="."/>
</xsl:template>
 
<xsl:template match="foo[not(text()]">
 <foo>hello mum</foo>
</xsl:template>

will convert

<foo>zzz</foo> to <foo>zzz</foo>
and
<foo/>         to <foo>hello mum</foo>

15.

Test for empty elements

Wendell Piez



>If there is a XPath expression to match elements by whether they
>have any text content or not
>Any ideas if such an XPath exists?

select="address[normalize-space()]"

selects all address element children that have some non-whitespace text content within them.

select="address[not(normalize-space()]" selects address elements that contain only whitespace, nothing, or elements containing only whitespace, nothing (or elements containing only whitespace, nothing, etc). This is because it tests for the string value of the element, after whitespace is normalized and trimmed.

This can be changed or adjusted for other definitions of "content".