views:

662

answers:

4

What I wish I could do in xsl is the following, but unfortunatly parent/position() is not valid.

XSL

<xsl:template match="li">
  <bullet>
    <xsl:apply-templates/>
  </bullet>
  <!-- if this is the last bullet AND there are no more "p" tags, output footer -->
  <xsl:if test="count(ancestor::div/*) = parent/position()">
    <footer/>
  </xsl:if>
</xsl:template>

XML

<html>
  <div>
    <p>There is an x number of me</p>
    <p>There is an x number of me</p>
    <p>There is an x number of me</p>
    <ul>
      <li>list item</li>
      <li>list item</li>
      <li>list item</li>
      <li>list item</li>
      <li>list item</li>
    </ul>
  </div>
</html>

Anyone have any ideas how to figure out this problem from WITHIN my template match for li?

Thanks!

+2  A: 

A good way to do this in XSLT is:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <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="div">
  <xsl:apply-templates/>
  <footer/>
 </xsl:template>

 <xsl:template match="li">
  <bullet>
    <xsl:apply-templates/>
  </bullet>
 </xsl:template>
</xsl:stylesheet>

The inclusion of the <footer/> is most natural at the end of the template that matches div and there is no need to try to compare any positions.

Dimitre Novatchev
You are deeply ignorant of the last line of my question. I need to do this from WITHIN my template match for li. This is just a contrived example to make it easier to explain.
joe
But I was able to refactor my code to work in the way you described. Thanks! With that being said I am going to keep the question open because moving the <footer/> outside of the match is not always easy if it is a large xsl file where apply-templates on "li" is called in multiple places.
joe
@joe: Without any expletives, I repeat that this is not in the spirit of XSLT. Please, show a complete (but minimal) example with an xml document, the wanted result and how the result should be obtained from the source xml document. Then many people will be able to show a correct solution, which probably will not be comparing positions.
Dimitre Novatchev
A: 

If I understand correctly you are looking for the last li; that is an li with no li elements following it. This can be tested as so:

<xsl:template match="li">
  <bullet>
    <xsl:apply-templates/>
  </bullet>
  <xsl:if test="not(following-sibling::li)">
    <footer />
  </xsl:if>
</xsl:template>

Although in the case you have given it seems to be more in the spirit of XSLT to add the footer when processing the end of the ul:

<xsl:template match="ul">
  <ul>
    <xsl:apply-templates/>
  </ul>
  <footer />
</xsl:template>

<xsl:template match="li">
  <bullet>
    <xsl:apply-templates/>
  </bullet>
</xsl:template>
Oliver Hallam
A: 

You can find the position of the parent node in the source tree by counting its preceding siblings:

<xsl:variable name="parent-position" 
              select="count(../preceding-sibling::*) + 1"/>

If you want to determine if there are any p elements after the parent ul element, you can test this without using positions at all:

<xsl:if test="../following-sibling:p">...</xsl:test>

However, as Dimitre and Oliver have noted, it is more in the spirit of XSLT to add the footer when processing the parent element. Also, the XPath expressions shown only care about order in the original source tree. If you intend to filter elements or reorder with xsl:sort before processing, these paths will not work as desired, as they will look at the original ordering and include all nodes in the source tree.

markusk
+1  A: 

Try this one:

<xsl:template match="li">
  <bullet>
    <xsl:apply-templates/>
  </bullet>
  <xsl:if test="position()=last() and not(../following-sibling::p)">
    <footer/>
  </xsl:if>
</xsl:template>
newtover