4

I'm using xslt version 2, I'm trying to transform an xml to a fo output, and I'm stuck on a specific problematic.

Here is what looks like my input:

    <a1/>
    <a1/>
    <b/>
    <c/>
    <d/>
    <a2/>
    <b/>
    <c/>
    <a1/>
    <a1/>
    <a1/>
    <a1/>
    <b/>
    <c/>
    <d/>

This data, functionally speaking, contains a list of 'sets' defined by a1|a2,b?,c?,d?.

My problem is that I don't see how I can count the number of a1 tags for a specific 'set'.

Indeed, I have written my xsl and I get an output like that:

<fo:table>
    <fo:row>
        <fo:cell>b: </fo:cell>
        <fo:cell>b value</fo:cell>
    </fo:row>
    <fo:row>
        <fo:cell>a1: </fo:cell>
        <fo:cell>number of a1 ???</fo:cell> <-- what I am trying to retrieve
    </fo:row>
    <fo:row>
        ...
    </fo:row>
    ...
</fo:table>

I have done an apply-template on a1+|a2 tags, and I do nothing if a1 tag has a following sibling that equals to a1. I think there must be a way to count the tags with preceding sibling (but then how to insure to count only the corresponding one?)

Any hints would be appreciated!

Edit: On the above example of input, the first count should be 2:

    <a1/>
    <a1/>
    <b/>
    <c/>
    <d/>

then it should be 4, and not 6:

    <a1/>
    <a1/>
    <a1/>
    <a1/>
    <b/>
    <c/>
    <d/>
skoll
  • 232
  • 1
  • 2
  • 9

3 Answers3

10

Your question is not really clear.
What should "the corresponding one" be? Counting all a1 before the current one would be:

 count(preceding-sibling::a1) 

if needed you may add predicates like:

 count(preceding-sibling::a1[/corresponding one/]) 

To count only the presiding sibling a1 which are in a sequence of a1 nodes try this: Find the first node which is not an a1.

<xsl:variable name="firstnota1" select="preceding-sibling::*[not (self::a1)][1]" />

The wonted result than, is count all nodes before the current a1 minus the count of nodes before the first not a1 + this node it self.

<xsl:value-of select="count(preceding-sibling::*) 
       -  count($firstnota1/preceding-sibling::* | $firstnota1)"/>

Or without variable:

<xsl:value-of 
      select="count(preceding-sibling::*)
             -  count( preceding-sibling::*[not (self::a1)][1]
                      /preceding-sibling::*
                      | preceding-sibling::*[not (self::a1)][1] )"/>
hr_117
  • 9,589
  • 1
  • 18
  • 23
  • the problem is I have a1-a1-b-c-d-a1-a1-a1-b-c-d... first time it is 2, second time 3. I will edit my post – skoll Jul 13 '13 at 13:58
  • Sure it only counts the preceding-siblings. If you like to have the count including self add `+1`: `select="count(..) - count(..) +1" – hr_117 Jul 15 '13 at 12:54
  • Ok that's it, my mistake was I did not understand the | $firstnota1, so i ignored it. Instead I had to remove it and then to add 1. So it handles both cases : with or without different previous sibling. – skoll Jul 15 '13 at 13:31
2

I would use for-each-group group-adjacent="boolean(self::a1)"> e.g.

<xsl:stylesheet
  version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">

<xsl:output method="xml" indent="yes"/>

<xsl:template match="root">
  <xsl:for-each-group select="*" group-adjacent="boolean(self::a1)">
    <xsl:choose>
      <xsl:when test="current-grouping-key()">
        <xsl:value-of select="'Count is: ', count(current-group())"/>
      </xsl:when>
      <xsl:otherwise>
        <!-- process other elements here -->
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>

If the input is

<root>
    <a1/>
    <a1/>
    <b/>
    <c/>
    <d/>
    <a2/>
    <b/>
    <c/>
    <a1/>
    <a1/>
    <a1/>
    <a1/>
    <b/>
    <c/>
    <d/>
</root>

then Saxon 9 outputs Count is: 2Count is: 4 so it outputs the numbers you want (badly formatted, admittedly). If you don't get any output then perhaps the elements you have posted have a different parent element than the one I have chosen (i.e. root). Or you use namespaces and the self::a1 needs to be adapted.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • I really don't get how to make it work with that, it seems i never have "current-grouping-key()" – skoll Jul 15 '13 at 13:14
  • Ok +1 my mistake I was trying to use the foreach-group in the element, not parent. But I'm displaying 421 - 421 - 421 instead of 4 - 2 - 1... It seems that the test test="current-grouping-key()" is always valid ? – skoll Jul 15 '13 at 15:35
  • No just forget it. It is a difference in conception: I build a fo table from a1, using for-each-group I should build it from parent. thanks for explanations. – skoll Jul 15 '13 at 15:42
  • Well... maybe do not forget it :) Do you think there is a way to retrieve values from groups of (a1+,b?,c?,d?) Using for-each-group seems to be really cleaner than my solution – skoll Jul 15 '13 at 15:45
  • I am sure there is a way e.g. `...` but it might be better you ask a new question where you present small but complete and representative samples of the XML input and the corresponding FO output. Then I am sure we can find a way using `for-each-group`. – Martin Honnen Jul 15 '13 at 15:55
1

Try following xpath

count(preceding-sibling::a1)-count(preceding-sibling::b[1]/preceding-sibling::a1)

It means: count of preceding a1 at all minus count of a1 preceding previous b

Jirka Š.
  • 3,388
  • 2
  • 15
  • 17
  • Thank you for the answer, I'll try it when I have the opporunity to do so. – skoll Jul 13 '13 at 14:15
  • b, c and d are optionnal (xsd says so), I will try to find if it is really optionnal or if my data will always contain one particular (if not, is there a way to find the max of count(preceding-sibling::b[1]/preceding-sibling::a1) / same with c / same with d ?) – skoll Jul 13 '13 at 14:19
  • You could store the name of actual element into variable `` and then use it in second count `` – Jirka Š. Jul 13 '13 at 14:27