Re: [xsl] Key problem. Ordering XML with conditions than generate new associations.

Subject: Re: [xsl] Key problem. Ordering XML with conditions than generate new associations.
From: Geert Josten <Geert.Josten@xxxxxxxxxxx>
Date: Wed, 28 Sep 2005 22:41:47 +0200

Gabriel Osorio wrote:

About: "RE: [xsl] Another <xsl:key> problem "

My intentions are educational. :-) I'm a beginner.

I thought so, that is why I asked you to start a new thread...

Now my problem is to order nodes in groups based on certain characteristics
of their children. (Using DIV tags.) And perhaps seeing to other way I can
find my own solution.

You found a complex example.. :-)

There are several references to tutorials on, among which

Really I do not have opinion. I used your template and did not obtain
results, because of it I sent the xml again.

Many templates have been sent. You mean mine? The one that was generating a XSL in ax-xsl namespace?

Your recursion solution is elegant, but I can't understand the siblings use.

That can't have been mine. :-) Who are you referring to? Not very relevant actually.

May be you can help me...

My own problem is:

This is language's teacher board offer example. I need sort teacher's offers
by price. And sort teachers from cheapest to most expensive.

With this rule: "If the course can pay with credit card or is not informed,
it's a promotion."

The promotions are a new group. And carry out the teacher's rules.

Other additional complication: courses have two or more teachers. And the
sorted group uses the first teacher's name.

It appears to me that your xml is already sorted to price, though empty pay and Credit pay grades/courses are not separated into a new group (grade?).

If you would just like to sort the grades to price, you could do the following:

<!-- promotions -->
<xsl:apply-templates select="//grade[(var/pay = 'Credit') or (var/pay = '')]" />
<!-- others -->
<xsl:apply-templates select="//grade[(var/pay != 'Credit') and (var/pay != '')]">
  <xsl:sort select="var/price" data-type="number" />

No need for keys in that case.

Reordering the courses along to teachers is more difficult as it requires regrouping the information. This can be done like this:

<xsl:key name="courses-by-teacher" match="course" use="teacher" />
<xsl:key name="teachers" match="teacher" use="." />

<xsl:for-each select="//teacher">
  <!-- alphabetize them -->
  <xsl:sort />

  <!-- teacher occur multiple times, take first occurrence -->
  <xsl:if test="generate-id(.) = generate-id(key('teachers', .)[1])">
      <xsl:copy-of select="." />
      <xsl:copy-of select="key('courses-by-teacher', .)" />

<snip />

My XSL is:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"

<xsl:key name="order1" match="grade" use="((substring(var/pay,1,1))='') or
((substring(var/pay,1,1))='C')" />
<xsl:key name="order2" match="grade"
use="concat(((substring(var/pay,1,1))='') or
((substring(var/pay,1,1))='C'),course[1]/teacher)" />

Try to use key names that explain what is matched and what is used as key.

Besides, I think I would move the boolean to a predicate in the match pattern and define separate keys for the true and the false case. Why using a boolean where a string is expected?

Looking more carefully at order1, I think you could have replaced it with a (global) variable just as well:

<xsl:variable name="promotions" select="//grade[((substring(var/pay,1,1))='') or ((substring(var/pay,1,1))='C')]" />

Doesn't that make much more sense?

<xsl:template match='root'> <table>
<!--Expecial logic for C pay nodes.-->

    <xsl:for-each select="grade[count(. | key('order1', 'true')[1]) = 1]">
      <xsl:if test="generate-id()=generate-id(key('order1', 'true'))">

I am pretty certain that the predicate in the select of this for-each is doing the same as the test in the if. They are both selecting the first in document order that matches the given key 'true', which refers to the promotion courses (or grades?) I guess.

      <tr><td>Credit Offer</td></tr>
      <xsl:for-each select="key('order1', 'true')">
            <xsl:sort select="var/price" order="ascending"
                  <xsl:call-template name="course"/>
  <!--Logic to group nodes with standar pays-->

    <xsl:for-each select="grade[count(. | key('order2',
concat('false',course[1]/teacher))[1]) = 1]">
    <xsl:if test="generate-id()=generate-id(key('order2',

Here again.

      <tr><td><div><xsl:attribute name="id"><xsl:value-of
      <tr><td>Teacher's Leader: <xsl:value-of
      <xsl:for-each select="key('order2',

Ah, this makes sense. You are picking the courses that belong to a certain teacher, right? A good use case for a key. Though, how is this related to credit/non-credit courses? It is not obvious that you have limited your expression to the credit courses (or are it grades?) by the 'false'. This becomes much more obvious when using separate keys for credit and non-credit courses...

<xsl:sort select="var/price" order="ascending" data-type="number"/>
<xsl:call-template name="course"/>
<!--The rest.-->
<xsl:template name="course"> <tr>
<td><xsl:value-of select="position()"/>.</td>
<td>Price: <xsl:value-of select="var/price"/>.</td>
<td><xsl:apply-templates select='course'/></td>
<xsl:template match="course">
<br/>teacher: <xsl:value-of select="teacher"/>&#xa0;&#xa0;
class: <xsl:value-of select="class"/>&#xa0;&#xa0;
shift: <xsl:value-of select="shift"/>&#xa0;&#xa0;
Pay: <xsl:value-of select="../var/pay"/>

Try to keep your solution as plain and simple as possible. It should be obvious from naming and expressions what you are trying to achieve.


Current Thread