 |
| 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
|
|
|
|

March 26th, 2010, 08:42 PM
|
|
Registered User
|
|
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
XSLT Custom filter function
Hello,
I need some help here, please.
I want to implement a custom XSLT function to filter a list of people. It is supposed to make a simple comparison and output the ones matching the filter but it is not working properly.
Thanks in advance!
Here are my files:
----------
person.xml
----------
Code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="person.xsl"?>
<!DOCTYPE person_list SYSTEM "validator.dtd">
<person_list>
<person id="1" name="John Smith">
<age>35</age>
<height units="cm">173</height>
<weight units="kg">75</weight>
</person>
<person id="2" name="Tom Stone">
<age>26</age>
<height units="cm">177</height>
<weight units="kg">65</weight>
</person>
<person id="3" name="Mark Glennford">
<age>17</age>
<height units="cm">181</height>
<weight units="kg">61</weight>
</person>
</person_list>
----------
person.xsl
----------
Code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://www.mysite.com/my">
<xsl:function name="my:age">
<xsl:param name="age"/>
<xsl:for-each select="person_list/person">
<xsl:if test="age > $age">
<!-- code to display data: age, height, weight-->
</xsl:if>
</xsl:for-each>
</xsl:function>
<xsl:template match="/">
<xsl:value-of select="my:age(18)"/><!-- isn't it supposed to output the data for person with ids "1" and "2"? -->
</xsl:template>
</xsl:stylesheet>
|
|

March 27th, 2010, 04:49 AM
|
 |
Friend of Wrox
|
|
Join Date: Aug 2007
Posts: 2,128
Thanks: 1
Thanked 189 Times in 188 Posts
|
|
When you call a function it does not set the 'focus' so you cannot refer to the current context. [ http://www.w3.org/TR/xslt20/#stylesheet-functions]
Therefore the xsl:for-each statement is invalid because of the select.
You either need to pass in the current context as another parameter, or define a global variable to use as the starting point.
Code:
<xsl:variable name="root" select="/"/>
<xsl:function name="my:age">
<xsl:param name="age"/>
<xsl:for-each select="$root/person_list/person">
<xsl:if test="age > $age">
<!-- code to display data: age, height, weight-->
</xsl:if>
</xsl:for-each>
</xsl:function>
|
|

March 27th, 2010, 08:24 AM
|
|
Registered User
|
|
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Thank you very much for the answer! My person.xsl file looks like this at the moment:
Code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://www.mysite.com/my">
<xsl:variable name="root" select="/"/>
<xsl:function name="my:age">
<xsl:param name="age"/>
<xsl:for-each select="$root/person_list/person">
<xsl:if test="age > $age">
<div align="center"><h2><xsl:value-of select="@name"/></h2></div>
<b><u>Age</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="age"/></i><br/>
<b><u>Height</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="height"/></i><br/>
<b><u>Weight</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="weight"/></i><br/><br/>
</xsl:if>
</xsl:for-each>
</xsl:function>
</xsl:stylesheet>
And this is the output I get:
35
173
75
26
177
65
17
181
61
How do I test it against $age? When I this bit in the end of the file
Code:
<xsl:template match="/">
<output>
<xsl:value-of select="my:age(18)"/>
</output>
</xsl:template>
</xsl:stylesheet>
I get an error: Error during XSLT transformation: An unknown XPath extension function was called.
Thanks again!
|
|

March 27th, 2010, 08:38 AM
|
 |
Friend of Wrox
|
|
Join Date: Aug 2007
Posts: 2,128
Thanks: 1
Thanked 189 Times in 188 Posts
|
|
Without the final template your input XML is just being processed by the built in templates, which just output all the text and ignore the elements.
Why you get the error at the end I don't know. It works fine for me using Saxon 9.1.0.2 and Kernow for Saxon as my editor. You are definitely using a XSLT 2.0 compatible processor yes? You're not just trying to view this in a browser (none of which support XSLT 2.0).
Although I will say that using <xsl:value-of> will strip all the XML from your output, and just output the text. Use <xsl:sequence> instead.
<xsl:sequence select="my:age(18)"/>
|
|

March 27th, 2010, 08:56 AM
|
|
Registered User
|
|
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Apologizes here. I was trying it in the browser (which I supposed was the problem, but decided to ask just in case).
Thank you very much again!
|
|

March 28th, 2010, 03:15 PM
|
|
Registered User
|
|
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Hello again,
I have another question. How can I tackle the problem of copying and pasting the same body text each time I want to show the filtered results (as the template/pattern is the same every time, the only difference is the filter)?
For example, what if I have two functions for age: "older_than" and "younger_than" which both have the parameter "age".
Code:
<xsl:function name="my:older_than">
<xsl:param name="age"/>
<xsl:for-each select="$root/person_list/person">
<xsl:if test="age > $age">
<div align="center"><h2><xsl:value-of select="@name"/></h2></div>
<b><u>Age</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="age"/></i><br/>
<b><u>Height</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="height"/></i><br/>
<b><u>Weight</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="weight"/></i><br/><br/>
</xsl:if>
</xsl:for-each>
</xsl:function>
Code:
<xsl:function name="my:younger_than">
<xsl:param name="age"/>
<xsl:for-each select="$root/person_list/person">
<xsl:if test="age < $age">
<div align="center"><h2><xsl:value-of select="@name"/></h2></div>
<b><u>Age</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="age"/></i><br/>
<b><u>Height</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="height"/></i><br/>
<b><u>Weight</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="weight"/></i><br/><br/>
</xsl:if>
</xsl:for-each>
</xsl:function>
Can't I have something that replaces the following bit automatically in my functions implementation:
Code:
<div align="center"><h2><xsl:value-of select="@name"/></h2></div>
<b><u>Age</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="age"/></i><br/>
<b><u>Height</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="height"/></i><br/>
<b><u>Weight</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="weight"/></i><br/><br/>
|
|

March 28th, 2010, 05:07 PM
|
 |
Friend of Wrox
|
|
Join Date: Aug 2007
Posts: 2,128
Thanks: 1
Thanked 189 Times in 188 Posts
|
|
Yes, that's exactly what templates are.
Code:
<xsl:template match="person">
<div align="center"><h2><xsl:value-of select="@name"/></h2></div>
<b><u>Age</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="age"/></i><br/>
<b><u>Height</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="height"/></i><br/>
<b><u>Weight</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="weight"/></i><br/><br/>
</xsl:template>
<xsl:function name="my:younger_than">
<xsl:param name="age"/>
<xsl:apply-templates select="$root/person_list/person[age < $age]"/>
</xsl:function>
|
|

March 28th, 2010, 06:00 PM
|
 |
Wrox Author
|
|
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
|
|
The facility you are looking for is called "higher-order functions": it allows you to pass a function as a parameter to another function: in this case the function you pass in would evaluate [age lt $age] or [age gt $age]. Unfortunately this isn't available as a built-in language feature until XPath 2.1, which has only just been published as a first working draft. However, if you want to play with the idea,
(a) Dimitre Novatchev in his FXSL library has shown how you can simulate higher-order functions using template rules, even in XSLT 1.0
(b) there are extensions in Saxon that achieve the same effect (see saxon:function() and saxon:call()).
__________________
Michael Kay
http://www.saxonica.com/
Author, XSLT 2.0 and XPath 2.0 Programmer\'s Reference
|
|

March 29th, 2010, 10:33 AM
|
|
Registered User
|
|
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Thank you very much again!
And thanks to mhkay for the additional information!
|
|

March 29th, 2010, 11:53 AM
|
|
Registered User
|
|
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Quote:
Originally Posted by samjudson
Code:
<xsl:template match="person">
<div align="center"><h2><xsl:value-of select="@name"/></h2></div>
<b><u>Age</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="age"/></i><br/>
<b><u>Height</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="height"/></i><br/>
<b><u>Weight</u>:</b><xsl:text> </xsl:text><i><xsl:value-of select="weight"/></i><br/><br/>
</xsl:template>
<xsl:function name="my:younger_than">
<xsl:param name="age"/>
<xsl:apply-templates select="$root/person_list/person[age < $age]"/>
</xsl:function>
|
Excuse me, is it possible to amend the apply-template line, so I can achieve something like this:
Code:
<xsl:apply-templates select="$root/person_list/person[age < $age] and $root/person_list/person[age > 18]"/>
i.e. checking two conditions at the same time. I tried with if test but it didn't work out properly.
Thanks in advance!
|
|
 |