[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message]

Re: Getting a NodeList from a NodeList II (Full Proble

Subject: Re: Getting a NodeList from a NodeList II (Full Problem Shown with Code)
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 2 Jul 2003 11:05:15 +0100
selectnodes order by
Hi Allistair,

> The report I am trying to generate is based on department, so I loop
> over unique departments and for each one I get the employees in it
> and print some information. I use a msxml function getDepartments
> which is my way of getting a different set of departments depending
> on the filterDepartment node value (which was a Query String
> parameter).
[snip]
> <xsl:for-each select="comp:getDepartments(/)">
>   <xsl:sort select="." order="ascending" />
>   <xsl:variable name="deptName" select="." />
>   <xsl:for-each select="//employee[./@department = $deptName]">
>     <xsl:sort select="@fullname" order="ascending" />
>     <xsl:value-of select="./@fullname" />
>     .. other code
>   </xsl:for-each>
> </xsl:for-each>
[snip]
> <msxsl:script language="JScript" implements-prefix="comp">
>   function getDepartments(nodeList) {
>     var dept = (nodeList.item(0).selectSingleNode("//filterDepartment")).getAttribute("value");
>     if(dept == "All Staff") {
>       return nodeList.item(0).selectNodes("//employee/@department[not(. = preceding::employee/@department)]");
>     } else if(dept == "Some Staff") {
>       return nodeList.item(0).selectNodes("//employee/@department[not(. = preceding::employee/@department) and (" +
>           "(. = 'Sales') " +
>           "or (. = 'I.T'))]");
>     }
>     return nodeList.item(0).selectNodes("//employee/@department[not(. = preceding::employee/@department) and (. = '" + dept + "')]");
>   }
> </msxml:script>

I can see why you're having trouble here, but you can do this in XSLT.
Obviously you can select the department that you're filtering by
easily enough:

  <xsl:variable name="dept"
                select="/employees/filterDepartment/@value" />

And you can create a selection of the first employee in each
department, used as a basis for the future filtering, in the way you
are (or you could use keys to get this set, but one thing at a time):

  <xsl:variable name="employees"
    select="/employees/employee
              [not(@department =
                   preceding-sibling::employee/@department)]" />

Now comes the part where you need to filter $employees based on $dept.
You can do this with a predicate, with a test that returns true if you
want a particular employee and false otherwise. If $dept is 'All
Staff', you don't need any additional filtering, you want the
employee:

  $employees[$dept = 'All Staff' ...]

If $dept is 'Some Staff' then you want the employee if their
@department is 'Sales' or 'I.T':

  $employees[$dept = 'All Staff' or
             ($dept = 'Some Staff' and
              (@department = 'Sales' or @department = 'I.T')) ...]

Otherwise, you want the employee if their department is the same as
$dept:

  $employees[$dept = 'All Staff' or
             ($dept = 'Some Staff' and
              (@department = 'Sales' or @department = 'I.T')) or
             @department = $dept]

(This is very fiddly, I know. In XPath 2.0, there are conditional
expressions, so you can do:

  if ($dept = 'All Staff') then
    $employees
  else if ($dept = 'Some Staff') then
    $employees[@department = 'Sales' or @department = 'I.T']
  else
    $employees[@department = $dept]

which is a lot more intuitive.)

So you can use:

  <xsl:variable name="dept"
                select="/employees/filterDepartment/@value" />
  <xsl:variable name="employees"
    select="/employees/employee
              [not(@department =
                   preceding-sibling::employee/@department)]" />
  <xsl:for-each
    select="$employees[$dept = 'All Staff' or
                       ($dept = 'Some Staff' and
                        (@department = 'Sales' or
                         @department = 'I.T')) or
                       @department = $dept]">
    ...
  </xsl:for-each>

instead of your current MSXML-specific function.

By the way, the above is selecting <employee> elements rather than
department attributes, so you need to change the code inside the
<xsl:for-each> a little. I'd really recommend using keys for selecting
the <employee> elements that belong to the same department as the
employee you're processing. Use a key that indexes all the <employee>
elements by their department:

<xsl:key name="employees" match="employee" use="@department" />

and then select the employees using the key() function:

  key('employees', $deptName)

In other words, you <xsl:for-each> should look like:

  <xsl:for-each
    select="$employees[$dept = 'All Staff' or
                       ($dept = 'Some Staff' and
                        (@department = 'Sales' or
                         @department = 'I.T')) or
                       @department = $dept]">
    <xsl:sort select="@department" order="ascending" />
    <xsl:variable name="deptName" select="@department" />
    <xsl:for-each select="key('employees', $deptName)">
      <xsl:sort select="@fullname" order="ascending" />
      <xsl:value-of select="@fullname" />
      ...
    </xsl:for-each>
  </xsl:for-each>

> I could not think of another way to make my XSL "aware" of Query
> String parameters other than to add them as top level "filter" nodes
> with value attributes and then use a function like this.

That's fine, and it works, but you might also consider using XSLT
parameters to pass in these kinds of filters.

> Well, what I want to do is filter the employees brought back
> depending on some time filters I have which define a date range to
> search within.
[snip]
> I thought about it differently after posting the message and decided
> to put the whole condition that I would have done in the function
> into the select so that I selected all emps in the current dept.
> that had 1 or more approvals whose timestamp falls between the 2
> filters.
>
> <xsl:for-each select="//employee[(./@department = $deptName) and
> (./approvals/approval [(@timestamp >= $filterFDate) and not
> (@timestamp > $filterTDate)])]">
>     <xsl:sort select="@fullname" order="ascending" />
>     <xsl:value-of select="./@fullname" />
>   </xsl:for-each>
>
> This seems to work fine in actual fact...but, as this is my first
> outing with XSL I would really appreciate some constructive
> criticism about my problem and how I have attempted to solve it and
> if I could have done things better.

That looks great. It's how I would have done it. Note that with the
key suggestion above, it should look like:

  <xsl:for-each
    select="key('employees', $deptName)
              [approvals/approval[@timestamp >= $filterFDate and
                                  @timestamp &lt;= $filterTDate]]">
    ...
  </xsl:for-each>

I've used <= rather than not(... > ...) in the above just because it
makes more sense to me.

> And shortly, I shall be having to change the report so that you can
> order by not only department but employee name too. At the moment
> the only way I can think of doing that is to create a whole separate
> XSL sheet for it??

There's rarely a requirement to use a whole separate stylesheet. At
the very least you can do something along the lines of:

  <xsl:choose>
    <xsl:when test="$orderBy = 'department'">
      ... code that does ordering by department ...
    </xsl:when>
    <xsl:when test="$orderBy = 'name'">
      ... code that does ordering by name ...
    </xsl:when>
  </xsl:choose>

Let us know if you have any problems/questions about the above. If you
want to know about how to use keys to select the employees with unique
departments, have a look at
http://www.jenitennison.com/xslt/grouping/muenchian.html.
  
Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread

PURCHASE STYLUS STUDIO ONLINE TODAY!

Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced!

Buy Stylus Studio Now

Download The World's Best XML IDE!

Accelerate XML development with our award-winning XML IDE - Download a free trial today!

Don't miss another message! Subscribe to this list today.
Email
First Name
Last Name
Company
Subscribe in XML format
RSS 2.0
Atom 0.3
Site Map | Privacy Policy | Terms of Use | Trademarks
Free Stylus Studio XML Training:
W3C Member
Stylus Studio® and DataDirect XQuery ™are products from DataDirect Technologies, is a registered trademark of Progress Software Corporation, in the U.S. and other countries. © 2004-2013 All Rights Reserved.