4

I have an XML structure like the following:

<Root>
  <!-- ...other stuff -->
  <Events>
    <Event date="0000-00-00">Event Description etc...</Event>
    <Event date="0000-00-00">Event Description etc...</Event>
    <Event date="0000-00-00">Event Description etc...</Event>
  </Events>
  <!-- ...other stuff -->
</Root>

Then I have XSLT in the Stylesheet like so:

<xsl:variable name="Events" select="/Root/Events/Event" />

<xsl:template match="/">
  <!-- Stuff -->    
  <xsl:apply-templates select="$Events" />
  <!-- Stuff -->    
</xsl:template>

<xsl:template match="Event">
  <!-- Regular Event Template Transformation here -->
</xsl:template>    

<!-- ERROR HAPPENS HERE -->
<xsl:template match="not(node())">
  <p class="message">There are currently no upcoming events</p>
</xsl:template>

What I WANT to do is have two templates, one which only shows when there are no events. I KNOW i can use XSLT with <xsl:choose> and <xsl:when> tests to do a count of elements and just call the right template like I would do in procedural languages, but I'm trying to learn how to do this with template processing.

The error I'm getting is : Expected end of the expression, found '('. not -->(<-- node())

Emiliano Poggi
  • 24,390
  • 8
  • 55
  • 67
Armstrongest
  • 15,181
  • 13
  • 67
  • 106

4 Answers4

8

not(node()) is not a valid XSLT pattern, try this:

<xsl:template match="Event">
  <!-- Regular Event Template Transformation here -->
</xsl:template>    

<xsl:template match="Events[not(Event)]">
  <p class="message">There are currently no upcoming events</p>
</xsl:template>
Max Toro
  • 28,282
  • 11
  • 76
  • 114
  • The first template will match **Event** nodes with (any) child nodes. The second is the opposite. I don't think it solves OP's question. – Emiliano Poggi Sep 06 '11 at 21:15
  • @Dimitre Don't think is the most precise and correct. If OP does not change how templates are applied (as shown in my answer) the second template will never be executed. – Emiliano Poggi Sep 07 '11 at 05:10
  • @_empo: @Max_Toro has provided a complete solution -- the OP should just use it (such as copy and paste). – Dimitre Novatchev Sep 07 '11 at 14:32
  • For clarity, to anyone trying to do the same thing, this DOES only work when I'm using `/Root/Events` in my `$Events` variable and not `/Root/Events/Event` as my Question stated. So, I changed it. As a side note, in my case, my `Events` node is actually `` so I needed to change the 1st template to : `` to make it work. And it works. ^_^ Thank-you. – Armstrongest Sep 07 '11 at 16:13
  • This is cool! I'm using a slightly different version as a fall-back for unrecognised nodes, whereby anything with children (select="*") is rendered as a div block and any leaf node (select="*[not(*)"] is rendered as a key=value... – Alex May 23 '12 at 08:24
3

This might work better

<xsl:template match="*[not(node())]">
  <!-- ... -->

See also this answer here: XSLT To remove empty nodes and nodes with -1

Community
  • 1
  • 1
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • This works, but just curious... as I understand it, the star represents a set of `Event` right? So is this saying match any `Event` with no nodes or "The Set" if it has no nodes? – Armstrongest Sep 06 '11 at 19:44
  • 1
    Hmm, actually thinking of it again, I'm surprised it does work... I just intended to correct your syntax, not the semantics – Lukas Eder Sep 06 '11 at 19:52
  • This is a match of any element (can be **Root**, **Events**, **Event**) which does not have a child node (element or text). It **partially** solves OP's question and casually. – Emiliano Poggi Sep 06 '11 at 21:19
  • 1
    I see. So, it's anything without a node. It COULD work... but I believe not in my specific case... as the `Events` node will have attribute nodes. – Armstrongest Sep 07 '11 at 16:04
2

New answer

Reading your question thoroughly, I realize that my last solutions leads you nowhere. My new answer follows:

Since you need to know whether are any elements inside Events you need to change the / matching template:

<xsl:template match="/">
   <!-- Stuff -->    
   <xsl:apply-templates select="/Root/Events" />
   <!-- Stuff -->    
</xsl:template>

Now a template that only matches a populate Events along with the required Event template:

<xsl:template="Events[count(child::Event) &gt; 0]">
   <xsl:apply-template select='./Event'/>
</xsl:template>

<xsl:template="Event">
    <!-- some stuff -->
</xsl:template>

Now the template that matches an empty Events:

<xsl:template="Events[count(child::Event) = 0]">
   <p class="message">There are currently no upcoming events</p>
</xsl:template>

Old answer

Try with:

<!-- This matches every node with an empty string repr. Maybe you want
     to replace "self::*" to avoid attributes, etc." -->
<xsl:template match="*[string-length(self::*) = 0]">
</xsl:template>

Details:

If I recall correctly, in XPath/XSL '*' matches any type of node, i.e., text nodes, element nodes, comment nodes, attribute nodes, etc... So in a given context *[string-length(self::*) = 0] may match with a attribute; for instance, you may have a select='@*' somewhere else. So this template may be applied to attributes as well as elements.

If you're sure it won't match any attribute, you may leave it as it is. However, I like that my code expresses the right ideas. So, if this template your be applied to Event elements only, I would change the match for something like:

<xsl:template match="Event[string-length(self::*) = 0]">
</xsl:template>

Taking a peek at the XSLT spec at http://www.w3.org/TR/xslt, it seems that * matches only elements. However, I would test it.

manu
  • 3,544
  • 5
  • 28
  • 50
1

Your processor throws an error because your matching pattern results in a boolean value and not in a node.


Try this transform out (which keeps the required variable):

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

    <xsl:variable name="Events" select="/Root/Events/Event"/>

    <xsl:template match="Root">
        <xsl:apply-templates select="$Events
            | Events[not(Event)]"/>  
    </xsl:template>

    <xsl:template match="Event">
        <p class="message">There is an event</p>
    </xsl:template>    

    <xsl:template match="Events">
        <p class="message">There are currently no upcoming events</p>
    </xsl:template>

</xsl:stylesheet>

When applied on your input, produces:

<p class="message">There is an event</p>
<p class="message">There is an event</p>
<p class="message">There is an event</p>

When applied on input without events, such as:

<Root>
  <Events/>
</Root>

produces:

<p class="message">There are currently no upcoming events</p>
Emiliano Poggi
  • 24,390
  • 8
  • 55
  • 67
  • 1
    I've tried this... and I've found that you can't use this with the $Events variable e.g. `match="$Events[not(Event)]"` My structure needs to use a variable, unfortunately... the example I gave was simplified to reflect the structure I'm getting back. – Armstrongest Sep 06 '11 at 21:54
  • Check now my answer. I've edited the templates so that the variable is used. Hope it's what you are searching for, otherwise I think I need much clearer information about your requirements. – Emiliano Poggi Sep 06 '11 at 22:13
  • I think it was my misunderstanding of how the "match" parameter works. i was trying to do this: `` but it didn't like the `$` – Armstrongest Sep 07 '11 at 16:09
  • Yes, I realize that... I had changed the value of the variable. I guess what I was hoping with in the beginning was to match "null" but it didn't make much sense when I thought of it. – Armstrongest Sep 07 '11 at 18:10
  • Variables are allowed in template match only in XSLT 2.0 – Emiliano Poggi Sep 07 '11 at 18:10
  • It's still not clear what is the answer which worked for you. If no one here, you can add an answer yourself (and accept it after a while). I'm curious.. – Emiliano Poggi Sep 07 '11 at 18:12