Wrox Programmer Forums
Go Back   Wrox Programmer Forums > XML > XSLT
|
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
 
Old March 26th, 2010, 08:42 PM
Registered User
 
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
Default 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 &gt; $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>
 
Old March 27th, 2010, 04:49 AM
samjudson's Avatar
Friend of Wrox
 
Join Date: Aug 2007
Posts: 2,128
Thanks: 1
Thanked 189 Times in 188 Posts
Default

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 &gt; $age">
       <!-- code to display data: age, height, weight-->
     </xsl:if>
      </xsl:for-each>
   </xsl:function>
__________________
/- Sam Judson : Wrox Technical Editor -/

Think before you post: What have you tried?
 
Old March 27th, 2010, 08:24 AM
Registered User
 
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
Default

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 &gt; $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!
 
Old March 27th, 2010, 08:38 AM
samjudson's Avatar
Friend of Wrox
 
Join Date: Aug 2007
Posts: 2,128
Thanks: 1
Thanked 189 Times in 188 Posts
Default

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)"/>
__________________
/- Sam Judson : Wrox Technical Editor -/

Think before you post: What have you tried?
 
Old March 27th, 2010, 08:56 AM
Registered User
 
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
Default

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!
 
Old March 28th, 2010, 03:15 PM
Registered User
 
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
Default

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 &gt; $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 &lt; $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/>
 
Old March 28th, 2010, 05:07 PM
samjudson's Avatar
Friend of Wrox
 
Join Date: Aug 2007
Posts: 2,128
Thanks: 1
Thanked 189 Times in 188 Posts
Default

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 &lt; $age]"/>
   </xsl:function>
__________________
/- Sam Judson : Wrox Technical Editor -/

Think before you post: What have you tried?
 
Old March 28th, 2010, 06:00 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

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
 
Old March 29th, 2010, 10:33 AM
Registered User
 
Join Date: Mar 2010
Posts: 10
Thanks: 0
Thanked 0 Times in 0 Posts
Default

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

Quote:
Originally Posted by samjudson View Post
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 &lt; $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 &lt; $age] and $root/person_list/person[age &gt; 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!





Similar Threads
Thread Thread Starter Forum Replies Last Post
Filter XML file with an XSLT using DTD menhir666 XSLT 1 February 3rd, 2009 11:50 AM
Filter using ObjectDataSource and Custom Objects tna55 ASP.NET 2.0 Basics 5 July 17th, 2007 01:07 PM
add function to custom toolbar Vince_421 Access VBA 0 May 20th, 2006 10:36 AM
custom JavaScript Trim function crmpicco Javascript How-To 2 January 18th, 2006 12:53 PM
FUNCTION FILTER DATA FROM SQL DATABASE abofoma00 Javascript 1 November 21st, 2005 11:28 AM





Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
Copyright (c) 2020 John Wiley & Sons, Inc.