 |
| XSLT General questions and answers about XSLT. For issues strictly specific to the book XSLT 1.1 Programmers Reference, please post to that forum instead. |
Welcome to the p2p.wrox.com Forums.
You are currently viewing the XSLT section of the Wrox Programmer to Programmer discussions. This is a community of software programmers and website developers including Wrox book authors and readers. New member registration was closed in 2019. New posts were shut off and the site was archived into this static format as of October 1, 2020. If you require technical support for a Wrox book please contact http://hub.wiley.com
|
|
|
|

February 9th, 2010, 07:50 PM
|
|
Registered User
|
|
Join Date: Feb 2010
Posts: 5
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Help with numbering using XSLT
I'm trying to figure out how to do some unusual numbering using XSLT, and I'm hoping someone might be able to help me with it.
For purposes of simplicity, I have created very basic XML and XSLT examples - these are obviously not what I'm actually using. However, they encapsulate my problem quite nicely.
Given the following XML:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="fruit.xslt"?>
<fruitList>
<fruit>
<name>apple</name>
<color>red</color>
<owner>Bob</owner>
</fruit>
<fruit>
<name>apple</name>
<color>yellow</color>
<owner>Joe</owner>
</fruit>
<fruit>
<name>apple</name>
<color>green</color>
<owner>Bob</owner>
</fruit>
<fruit>
<name>pear</name>
<color>green</color>
<owner>Betty</owner>
</fruit>
<fruit>
<name>pear</name>
<color>yellow</color>
<owner>Joe</owner>
</fruit>
<fruit>
<name>banana</name>
<color>yellow</color>
<owner>Betty</owner>
</fruit>
<fruit>
<name>banana</name>
<color>green</color>
<owner>Betty</owner>
</fruit>
<fruit>
<name>banana</name>
<color>brown</color>
<owner>Joe</owner>
</fruit>
</fruitList>
and the following XSLT:
Code:
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp " ">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<title>FRUIT!</title>
</head>
<body>
<xsl:for-each select="fruitList/fruit">
<xsl:if test="normalize-space(name) != normalize-space(preceding-sibling::fruit[1]/name)">
FRUIT: <xsl:value-of select="name"/><br />
</xsl:if>
<xsl:number/>-<xsl:value-of select="color"/>, <xsl:value-of select="owner"/><br />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I get the following output:
Code:
FRUIT: apple
1-red, Bob
2-yellow, Joe
3-green, Bob
FRUIT: pear
4-green, Betty
5-yellow, Joe
FRUIT: banana
6-yellow, Betty
7-green, Betty
8-brown, Joe
This is almost what I want, but not quite. I want to be able to reset the count at 1 each time I start a new fruit. In other words, I want to see this:
Code:
FRUIT: apple
1-red, Bob
2-yellow, Joe
3-green, Bob
FRUIT: pear
1-green, Betty
2-yellow, Joe
FRUIT: banana
1-yellow, Betty
2-green, Betty
3-brown, Joe
My first thought was to create a variable which I would increment each time I output a line, and reset each time I output a FRUIT header. However, XSLT does not allow changing the value of a variable as the XML is processed. Ideally, of course, the XML would be structured differently to make this easy - however in my situation I have no control over the design of the XML I have to process.
Any suggestions would be greatly appreciated.
Thank you!
|
|

February 10th, 2010, 05:11 AM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
Very easy in XSLT 2.0 using grouping:
Code:
<xsl:for-each-group select="fruit" group-adjacent="name">
FRUIT <xsl:value-of select="current-grouping-key()"/>
<xsl:for-each select="current-group()">
<xsl:value-of select="position()"/>:
etc...
In XSLT 1.0 I think you can do it by taking advantage of the fact that a pattern used in xsl:number can contain a variable reference.
Code:
<xsl:variable name="name" select="name"/>
<xsl:number count="*[name=$name]"/>
will count only those elements having the same "name" as the current element.
(A warning though, this is the kind of feature that some of the weaker XSLT 1.0 processors might not implement correctly.)
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
|
|

February 10th, 2010, 03:21 PM
|
|
Registered User
|
|
Join Date: Feb 2010
Posts: 5
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Thank you - and an additional question
Quote:
Originally Posted by mhkay
In XSLT 1.0 I think you can do it by taking advantage of the fact that a pattern used in xsl:number can contain a variable reference.
|
Thank you very much, that worked perfectly.
I now have run into another snag, however, and I hope you might be able to help here.
Take the XML I posted earlier, and move one of the "apple" items somewhere else in the list, such that they are no longer in order. Obviously, this messes up the formatting, as the format looks to the previous sibling to see if we are on the same fruit name. I thought that the logical solution to this was to use a "sort" in my loop, but this doesn't really fix the problem.
My XSLT now look like this:
Code:
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp " ">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<title>FRUIT!</title>
</head>
<body>
<xsl:for-each select="fruitList/fruit">
<xsl:sort select="name"/>
<xsl:variable name="name" select="name" />
<xsl:if test="normalize-space(name) != normalize-space(preceding-sibling::fruit[1]/name)">
FRUIT: <xsl:value-of select="name"/><br />
</xsl:if>
<xsl:number count="*[name=$name]"/>-<xsl:value-of select="color"/>, <xsl:value-of select="owner"/><br />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
When the XML has the items out of order (by moving one of the "apple" nodes down the list, for example), my output looks like this. (Note that he items are actually being sorted and displaying in the correct order, but the "previous sibling" logic is still looking at the original unordered data):
Code:
FRUIT: apple
1-red, Bob
2-yellow, Joe
FRUIT: apple
3-green, Bob
FRUIT: banana
1-yellow, Betty
2-green, Betty
FRUIT: banana
3-brown, Joe
FRUIT: pear
1-green, Betty
2-yellow, Joe
As you can see, the third apple node is being numbered correctly, but the header ("FRUIT: apple") is being incorrectly applied. The reason is that in the ORIGINAL XML, the previous sibling of that apple node is NOT another apple node. This triggers a display of the header. What I need to check is not the previous sibling, but the node previously returned by the for-each loop. I need the previous sibling of the now sorted list, but it is giving me the previous sibling of the original unsorted list.
Thank you for your help thus far, and as before, any additional help would be appreciated.
Thanks!
|
|

February 10th, 2010, 04:47 PM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
You don't actually say what output you want here.
But you're getting into territory where you really need to understand XSLT grouping: which is very easy in XSLT 2.0, and rather hard in XSLT 1.0 (lookup Muenchian grouping). If you have any possibility of moving forward, then do.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
|
|

February 10th, 2010, 07:49 PM
|
|
Registered User
|
|
Join Date: Feb 2010
Posts: 5
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Quote:
Originally Posted by mhkay
You don't actually say what output you want here.
|
Sorry, I thought it was clear from my description at the end. What i got was this:
Code:
FRUIT: apple
1-red, Bob
2-yellow, Joe
FRUIT: apple
3-green, Bob
FRUIT: pear
1-green, Betty
2-yellow, Joe
FRUIT: banana
1-yellow, Betty
2-green, Betty
3-brown, Joe
What I wanted was this:
Code:
FRUIT: apple
1-red, Bob
2-yellow, Joe
3-green, Bob
FRUIT: pear
1-green, Betty
2-yellow, Joe
FRUIT: banana
1-yellow, Betty
2-green, Betty
3-brown, Joe
Note how in the first example (what I actually got), the third apple is separated from the others by a header, where it should not be. Note also that to see this behavior, you have to alter my original input XML as I mentioned earlier, by moving one of the apple entries further down in the document.
Quote:
Originally Posted by mhkay
But you're getting into territory where you really need to understand XSLT grouping: which is very easy in XSLT 2.0, and rather hard in XSLT 1.0 (lookup Muenchian grouping). If you have any possibility of moving forward, then do.
|
I will look at that. If you could give me some guidelines on how it would be accomplished using XSLT 2.0, I would appreciate that.
Thanks!
|
|

February 10th, 2010, 08:06 PM
|
|
Registered User
|
|
Join Date: Feb 2010
Posts: 5
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Based on your recommendation, I looked into using the "for-each-group" functionality, and ran into some troubles. I haven't used this before, so I may just be going about it the wrong way. Here is the XSLT I came up with, based on your earliest suggestion and examples from elsewhere on the web:
Code:
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp " ">
]>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<title>FRUIT!</title>
</head>
<body>
<xsl:for-each-group select="fruit" group-adjacent="name">
FRUIT <xsl:value-of select="current-grouping-key()"/>
<xsl:for-each select="current-group()">
<xsl:value-of select="position()"/>-<xsl:value-of select="color"/>, <xsl:value-of select="owner"/><br />
</xsl:for-each>
</xsl:for-each-group>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This XSLT doesn't work, however. I get the following error "Keyword xsl:template may not contain xsl:for-each-group." I must be doing something wrong, however, because multiple examples on the web had for-each-group tags inside of template tags. Does this point to our system not supporting XSLT 2.0? If that is the case, the I have no choice but to implement this in 1.0.
Any thoughts on my approach? Did I miss something in my XSLT?
Thanks again.
|
|

February 10th, 2010, 08:48 PM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
Moving to XSLT 2.0 is definitely the right way to go, but if you want to use the XSLT 2.0 language then you will need to process it with an XSLT 2.0 processor. The error message shows that you tried to run it with an XSLT 1.0 processor, which obviously isn't going to work. You suggest that you can't change your processor but you haven't indicated why.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
|
|

February 10th, 2010, 09:36 PM
|
|
Registered User
|
|
Join Date: Feb 2010
Posts: 5
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Quote:
Originally Posted by mhkay
Moving to XSLT 2.0 is definitely the right way to go, but if you want to use the XSLT 2.0 language then you will need to process it with an XSLT 2.0 processor. The error message shows that you tried to run it with an XSLT 1.0 processor, which obviously isn't going to work.
|
I suspected as much. Thanks for confirming that.
Quote:
Originally Posted by mhkay
You suggest that you can't change your processor but you haven't indicated why.
|
I have been tasked to develop the XSLT to work directly in Internet Explorer 7, as that is what all of our users are using. It looks like IE 7 must not support XSLT 2.0. That's a platform decision that was made higher up, and I can't override it. I have looked into Muenchian grouping in 1.0, as you suggested, but honestly, that looks overly complicated for what seems to me to be a fairly simple task. I'll have to talk to the supplier about the XML, and see if I can just count on the records always being sorted. If not, I may have to reconsider the XSLT approach - and rather just write some JavaScript to parse through the XML and render it. Grouping and numbering is a pretty trivial task in an imperative language like JavaScript.
|
|

February 11th, 2010, 05:08 AM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
It depends a bit what you mean by "trivial". With Muenchian grouping in XSLT 1.0, it will take about five lines of code that only XSLT developers understand. With Javascript, it will take about 100 lines of code that only Javascript developers understand. But if you know Javascript and don't know XSLT, then I guess the 100 lines are easier to write than the 5.
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
|
|
 |