|
Subject: Re: [xsl] 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 |
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 <= $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 |
|---|
|
| <- Previous | Index | Next -> |
|---|---|---|
| [xsl] Getting a NodeList from a Nod, Allistair Crossley | Thread | RE: [xsl] Netscape7.1 bug or bad co, Jim Fuller |
| RE: [xsl] how to test for the eleme, Michael Kay | Date | Re: [xsl] msxml xpath problem, David Carlisle |
| Month |