Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT line counter - is it that hard?

Tags:

xslt

I have cheated every time I've needed to do a line count in XSLT by using JScript, but in this case I can't do that. I simply want to write out a line counter throughout an output file. This basic example has a simple solution:

<xsl:for-each select="Records/Record">
   <xsl:value-of select="position()"/>
</xsl:for-each>

Output would be:

1

2

3

4

etc...

But what if the structure is more complex with nested foreach's :

<xsl:for-each select="Records/Record">
   <xsl:value-of select="position()"/>
   <xsl:for-each select="Records/Record">
       <xsl:value-of select="position()"/>
   </xsl:for-each>
</xsl:for-each>

Here, the inner foreach would just reset the counter (so you get 1, 1, 2, 3, 2, 1, 2, 3, 1, 2 etc). Does anyone know how I can output the position in the file (ie. a line count)?

like image 591
Mr AH Avatar asked May 26 '10 20:05

Mr AH


3 Answers

While it is quite impossible to mark the line numbers for the serialization of an XML document (because this serialization per se is ambiguous), it is perfectly possible, and easy, to number the lines of regular text.

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

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

 <xsl:template name="numberLines">
  <xsl:param name="pLastLineNum" select="0"/>
  <xsl:param name="pText" select="."/>

  <xsl:if test="string-length($pText)">
   <xsl:value-of select="concat($pLastLineNum+1, ' ')"/>

   <xsl:value-of select="substring-before($pText, '&#xA;')"/>
   <xsl:text>&#xA;</xsl:text>

   <xsl:call-template name="numberLines">
    <xsl:with-param name="pLastLineNum"
      select="$pLastLineNum+1"/>
    <xsl:with-param name="pText"
      select="substring-after($pText, '&#xA;')"/>
   </xsl:call-template>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document:

<t>The biggest airlines are imposing "peak travel surcharges"
this summer. In other words, they're going to raise fees
without admitting they're raising fees: Hey, it's not a $30
 price hike. It's a surcharge! This comes on the heels of
 checked-baggage fees, blanket fees, extra fees for window
 and aisle seats, and "snack packs" priced at exorbitant
 markups. Hotels in Las Vegas and elsewhere, meanwhile, are
 imposing "resort fees" for the use of facilities (in other
 words, raising room rates without admitting they're
 raising room rates). The chiseling dishonesty of these
 tactics rankles, and every one feels like another nail in
 the coffin of travel as something liberating and
 pleasurable.
</t>

produces the desired line-numbering:

1 The biggest airlines are imposing "peak travel surcharges"
2 this summer. In other words, they're going to raise fees
3 without admitting they're raising fees: Hey, it's not a $30
4  price hike. It's a surcharge! This comes on the heels of
5  checked-baggage fees, blanket fees, extra fees for window
6  and aisle seats, and "snack packs" priced at exorbitant
7  markups. Hotels in Las Vegas and elsewhere, meanwhile, are
8  imposing "resort fees" for the use of facilities (in other
9  words, raising room rates without admitting they're
10  raising room rates). The chiseling dishonesty of these
11  tactics rankles, and every one feels like another nail in
12  the coffin of travel as something liberating and
13  pleasurable.
like image 64
Dimitre Novatchev Avatar answered Oct 12 '22 20:10

Dimitre Novatchev


A line in an XML file is not really the same as an element. In your first example you don't really count the lines - but the number of elements.

An XML file could look like this:

<cheeseCollection>
<cheese country="Cyprus">Gbejna</cheese><cheese>Liptauer</cheese><cheese>Anari</cheese>
</cheeseCollection>

Or the exact same XML file can look like this:

<cheeseCollection>
    <cheese
       country="Cyprus">Gbejna</cheese>
    <cheese>Liptauer</cheese>
    <cheese>Anari</cheese>
</cheeseCollection>

which the XSLT will interpet exactly the same - it will not really bother with the line breaks.

Therefore it's hard to show line numbers in the way you want using XSLT - it's not really meant for for that kind of parsing.

Someone correct me if I'm wrong, but I'd say you would need Javascript or some other scripting language to do what you want.

like image 27
becquerel Avatar answered Oct 12 '22 18:10

becquerel


Thanks for the responses guys - yup you're totally correct, some external function is the only way to get this behaviour in XSLT. For those searching, this is how I did this when using a compiled transform in .Net 3.5:

Create a helper class for your function(s)

/// <summary>
/// Provides functional support to XSLT
/// </summary>
public class XslHelper
{
    /// <summary>
    /// Initialise the line counter value to 1
    /// </summary>
    Int32 counter = 1;

    /// <summary>
    /// Increment and return the line count
    /// </summary>
    /// <returns></returns>
    public Int32 IncrementCount()
    {
        return counter++;
    }
}

Add an instance to an args list for XSLT

XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(XmlReader.Create(s));
XsltArgumentList xslArg = new XsltArgumentList();
XslHelper helper = new XslHelper();
xslArg.AddExtensionObject("urn:helper", helper);
xslt.Transform(xd.CreateReader(), xslArg, writer);

Use it in you XSLT

Put this in the stylesheet declaration element:

 xmlns:helper="urn:helper"   

Then use like so:

<xsl:value-of select="helper:IncrementCount()" />
like image 29
Mr AH Avatar answered Oct 12 '22 18:10

Mr AH