RE: [xsl] FW: key, generate-id, ignoring my template

Subject: RE: [xsl] FW: key, generate-id, ignoring my template
From: "M. David Peterson" <m.david@xxxxxxxxxx>
Date: Wed, 24 Mar 2004 01:39:18 -0700
Ah, ok... I now can see what you are trying to accomplish with keys.
Thank you for the clarification and for the broken down XML...  Much
easier to understand your dilemma.  There are really two ways of doing
this.  The first and easiest is to use to separate apply-templates, the
first that uses "form/page[@name = 'MEDICAL']" as the select attribute
value.  The second would use "form/page[@name = 'DENTAL']".  The second
would be to use keys but there is no need to go to all that effort when
you can accomplish what you want using the above method.

Now as far as grouping the data correctly we will use keys.  But in an
effort to convert all of my XSLT programming to functional based
methodologies I have been using keys a little differently lately.  As
such, and to expand upon my last email I've created a solution that
utilizes ZERO conditional logic (beyond the use of built-in conditional
logic in XPath) and ZERO for-each loops.  It may seem a little
complicated at first but I promise that when you get it, YOU WILL GET IT
and you will understand the true power that exists using true functional
based methodologies in all of your XSLT programming.  I spent a bit of
time on this one simply because Im in the middle of writing technical
documentation for a project I recently finished and I needed a tutorial
that explained in detail the concepts I used in this project.  So, this
is going to become my tutorial.  Don't worry, Ill change the XML dataset
to more generic terms :)

So, using the exact XML dataset you sent me the following XSLT:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                xmlns:xalan="http://xml.apache.org/xalan";
                version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output method="html" indent="yes" encoding="iso-8859-1"/>
<xsl:key name="plans" match="benefit_plan" use="."/>

  <xsl:template match="form">
    <!-- obviously there is other code that goes here to transform the
rest of the data, but I will leave that up to you -->
    <xsl:apply-templates select="page[@name = 'MEDICAL']"/>
    <!-- here is where you would put any data that is necessary to
seperate the results of the the medical and dental transforms -->
    <br/>
    <xsl:apply-templates select="page[@name = 'DENTAL']"/>
    <!-- insert remaining transformation code that goes after DENTAL
here -->
  </xsl:template>
  
  <xsl:template match="heading2">
    <!-- because heading2 is used for both medical and dental it makes
sense to make it as a template -->
    <h2><xsl:value-of select="."/></h2>
  </xsl:template>
  
  <xsl:template match="pcp">
    <!-- now we will make two variables, one that contains only the name
of the distinct benefit plans and one then contains a transformed
version of the original XML that is grouped according to these distinct
names -->
    <xsl:variable name="distinct_groups">
      <xsl:element name="distinct_groups">
        <!-- by using apply-templates along with our generate-id and key
functions we can, again, allow the data to drive the transformation.
notice the use of "mode" as well.  this allows us to have multiple
templates that match the same element name but are used for different
purposes -->
        <xsl:apply-templates select="row[generate-id(benefit_plan) =
generate-id(key('plans', benefit_plan))]/benefit_plan"
mode="distinct_groups"/>
      </xsl:element>
    </xsl:variable>
    <xsl:variable name="grouped_content">
      <xsl:element name="grouped_content">
        <!-- with our distinct groups variable we can now take it and
use it as the select attribute value of apply-templates.  Notice that
use of the xalan:nodeset function.  Depending on what processor you use
you will need to change the namespace decleration above to match what it
requires to implement its version of nodeset and then change the
xalan:nodeset to the proper namespace and name value.  e.g. Microsoft
uses the "msxml" namespace as such ..
msxsl="urn:schemas-microsoft-com:xslt" .. and you would replace
xalan:nodeset with msxsl:node-set .-->
        <!-- note as well the use of the with-param to pass the full set
of rows (using "*") along with each pass through the distinct_groups
nodeset -->
        <xsl:apply-templates
select="xalan:nodeset($distinct_groups)//benefit_plan"
mode="group_related_rows">
          <xsl:with-param name="all_rows" select="*"/>
        </xsl:apply-templates>
      </xsl:element>
    </xsl:variable>
    <!-- with the resulting grouped RTF we can now use a simple
apply-templates using xalan:nodeset to convert it to a nodeset that can
be parsed normally by our processor -->
    <xsl:apply-templates select="xalan:nodeset($grouped_content)"/>
  </xsl:template>
  
  <xsl:template match="benefit_plan" mode="distinct_groups">
    <!-- in this template we simply copy the name of the benefit_plan to
the attribute name plan and the plan_desc to the attribute name
plan_desc.  This will allow us the ability to avoid conditional logic
later on when we print out the name of the plan_desc for each group -->
    <xsl:copy>
        <xsl:attribute name="plan">
          <xsl:value-of select="."/>
        </xsl:attribute>
        <xsl:attribute name="plan_desc">
          <xsl:value-of select="following-sibling::plan_desc"/>
        </xsl:attribute>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="benefit_plan" mode="group_related_rows">
  <!-- now we take the rows we passed in the all_rows variable and
compare each value the value of each element within our distinct group
nodeset created in our last transformation.  If it makes a match it
passes the RTF along to our next template to copy the elements and
values into the correct group set -->
  <!-- keep in mind what is happening right now.  the processor just
took the first value within our distinct groups XML data and matched it
to this template.  So everything that happens within this template now
will be associated with that element. -->
  <!-- so from within here we can apply-templates to the all_rows
variable we passed along and create a nodeset of only those elements
that match the value of the @plan attribute -->
  <!-- when the processor has finished going through each element of our
distinct group dataset we will have all of our data matched and grouped
into a nodeset that we can then parse without using any sort of logic.
We just use apply-templates and create templates that know each element
-->
  <xsl:param name="all_rows"/>
  <xsl:variable name="plan" select="@plan"/>
    <xsl:copy>
      <xsl:attribute name="plan"><xsl:value-of
select="@plan"/></xsl:attribute>
      <xsl:attribute name="plan_desc"><xsl:value-of
select="@plan_desc"/></xsl:attribute>
      <xsl:apply-templates select="$all_rows[benefit_plan = $plan]"
mode="group_related_rows"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="row" mode="group_related_rows">
  <!-- the processor goes through every row element within the all_rows
variable from our preceding template and passed those that match our
criteria to this template.  Now all we have to do is copy it into RTF
--> 
    <row number="{@number}">
      <xsl:copy-of select="*"/>
    </row>
  </xsl:template>
  
  <!-- here's where the transformation of the finalized grouped XML will
begin -->
  
  <xsl:template match="grouped_content">
    <!-- a simple apply-templates using the element benefit_plan will
allow us to access each set of grouped data individually and transform
the contents as a group -->
    <xsl:apply-templates select="benefit_plan"/>
  </xsl:template>
  
  <xsl:template match="benefit_plan">
    <table>
      <tr>
        <td colspan="2">
          <hr/>
        </td>
      </tr>
      <tr>
      <!-- because we copied the plan description name during the first
phase of our transformation we dont need any conditional logic to test
for it. So now we can just print it out into the first column of our
table -->
        <td valign="top">
          <xsl:value-of select="@plan_desc"/>
        </td>
        <td valign="top">
        <!-- in the second column we can apply-templates to each row
using the number attribute to sort them correctly -->
          <table>
            <xsl:apply-templates select="row" mode="plan_members">
              <xsl:sort select="@number"/>
            </xsl:apply-templates>
          </table>
        </td>
      </tr>
    </table>
  </xsl:template>
  
  <xsl:template match="row" mode="plan_members">
  <!-- each row is matched to this template which prints out the value
of covrg_desc to a row within our column and that's it, transformation
complete -->
    <tr>
      <td> | </td>
      <td>
        <xsl:value-of select="covrg_desc"/>
      </td>
    </tr>
  </xsl:template>
  
</xsl:stylesheet>


**** Produces the following HTML output...

<h2> 2004 Medical Options for Wxxxx,Donald x</h2>
<table>
  <tr>
    <td colspan="2">
      <hr>
    </td>
  </tr>
  <tr>
    <td valign="top">PacifiCare / UA Net Basic</td><td valign="top">
      <table>
        <tr>
          <td> | </td><td>Employee Only</td>
        </tr>
        <tr>
          <td> | </td><td>Employee+Spouse/DomesticPartnr</td>
        </tr>
        <tr>
          <td> | </td><td>Employee+Child</td>
        </tr>
        <tr>
          <td> | </td><td>Family</td>
        </tr>
      </table>
    </td>
  </tr>
</table>
<table>
  <tr>
    <td colspan="2">
      <hr>
    </td>
  </tr>
  <tr>
    <td valign="top">PacifiCare / UA Net Direct</td><td valign="top">
      <table>
        <tr>
          <td> | </td><td>Employee Only</td>
        </tr>
        <tr>
          <td> | </td><td>Employee+Spouse/DomesticPartnr</td>
        </tr>
        <tr>
          <td> | </td><td>Employee + Child(ren)</td>
        </tr>
        <tr>
          <td> | </td><td>Family</td>
        </tr>
      </table>
    </td>
  </tr>
</table>
<table>
  <tr>
    <td colspan="2">
      <hr>
    </td>
  </tr>
  <tr>
    <td valign="top">PacifiCare / UA Net Choice</td><td valign="top">
      <table>
        <tr>
          <td> | </td><td>Employee + Child(ren)</td>
        </tr>
        <tr>
          <td> | </td><td>Employee Only</td>
        </tr>
        <tr>
          <td> | </td><td>Employee+Spouse/DomesticPartnr</td>
        </tr>
      </table>
    </td>
  </tr>
</table>
<br>
<h2>2004 Dental Options for Wxxxx,Donald x</h2>
<table>
  <tr>
    <td colspan="2">
      <hr>
    </td>
  </tr>
  <tr>
    <td valign="top">Delta Exclsv Pnl Optn</td><td valign="top">
      <table>
        <tr>
          <td> | </td><td>Employee Only</td>
        </tr>
        <tr>
          <td> | </td><td>Employee+Spouse/DomesticPartnr</td>
        </tr>
        <tr>
          <td> | </td><td>Employee + Child(ren)</td>
        </tr>
      </table>
    </td>
  </tr>
</table>


*** Which if Im not mistaken is exactly what you wanted.  Lets hopes so!
;)

Hope this all has helped and thanks for giving me just the sample
project I was looking for for this portion of my documentation.

Let me know if I can be of any more help :)

Best of luck!

<M:D/>




-----Original Message-----
From: Laura Madonna [mailto:Laura.Madonna@xxxxxxxxx] 
Sent: Tuesday, March 23, 2004 11:49 AM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: [xsl] FW: key, generate-id, ignoring my template

Thank you for all this information.  I have been studying it this
morning and trying to apply it to what I am doing.
To answer your question:  "What is it that you are trying to accomplish
here?"
The data is given to me in xml in rows from the database, one row per
Benefit_Plan and Covrg_desc combination.  I need the distinct
<benefit_plan> and <plan_desc> to display once and all the associated
<covrg_desc> to display.

I have Medical and Dental data to group.  I want the Medical Data first.
Right now, I get all the Medical Plans and the Dental plans in the same
table.

I have stripped down the XML to be easier to deal with - it is below
this mock-up of how I want the data displayed.

_______________________________________________________
PacifiCare/UA NetBasic   | Employee Only  
                         | Employee+Spouse/DomesticPartnr 
                         | Employee + Child(ren)  
                         | Family 
_______________________________________________________
PacifiCare / UA Net Direct| Employee Only  
                          | Employee+Spouse/DomesticPartnr 
                          | Employee + Child(ren)  
                          | Family 
_______________________________________________________ 
PacifiCare / UA Net Choice| Employee Only  
                          | Employee+Spouse/DomesticPartnr 
                          | Employee + Child(ren)  
                          | Family 
_______________________________________________________ 
=== xml ===
<?xml version='1.0'?>
<form>
   <page name='MEDICAL'>    
        <heading2> 2004 Medical Options for Wxxxx,Donald x</heading2>

            <pcp>
               <row number='0'>                 
                  <benefit_plan>PBASIC</benefit_plan>
                  <plan_desc>PacifiCare / UA Net Basic</plan_desc>

                  <covrg_desc>Employee Only</covrg_desc>
                  <price>109.1</price>                               
               </row>   
               <row number='1'>                   
                  <benefit_plan>PBASIC</benefit_plan>
                  <plan_desc>PacifiCare / UA Net Basic</plan_desc>

 
<covrg_desc>Employee+Spouse/DomesticPartnr</covrg_desc>
                  <price>283.61</price>                 
                </row>
                <row number='2'>                   
                  <benefit_plan>PBASIC</benefit_plan>
                  <plan_desc>PacifiCare / UA Net Basic</plan_desc>

                  <covrg_desc>Employee+Child</covrg_desc>
                  <price>283.61</price>                 
                </row>
              <row number='3'>
                <benefit_plan>PBASIC</benefit_plan>
                <plan_desc>PacifiCare / UA Net Basic</plan_desc>
                <covrg_desc>Family</covrg_desc>
                <price>391.51</price>         
              </row>
              <row number='4'>
               <benefit_plan>PDIRCT</benefit_plan>
               <plan_desc>PacifiCare / UA Net Direct</plan_desc>
               <covrg_desc>Employee Only</covrg_desc>
               <price>127.55</price>                
             </row>
             <row number='5'>
               <benefit_plan>PDIRCT</benefit_plan>
               <plan_desc>PacifiCare / UA Net Direct</plan_desc>
               <covrg_desc>Employee+Spouse/DomesticPartnr</covrg_desc>
               <price>314.01</price>
             </row>
             <row number='6'>
               <benefit_plan>PDIRCT</benefit_plan>
               <plan_desc>PacifiCare / UA Net Direct</plan_desc>
               <covrg_desc>Employee + Child(ren)</covrg_desc>
               <price>292.92</price>
             </row>
             <row number='7'>
               <benefit_plan>PDIRCT</benefit_plan>
               <plan_desc>PacifiCare / UA Net Direct</plan_desc>
               <covrg_desc>Family</covrg_desc>
               <price>442.7</price>
             </row>
             <row number='8'>
               <benefit_plan>PCHOIC</benefit_plan>            
               <plan_desc>PacifiCare / UA Net Choice</plan_desc>
               <covrg_desc>Employee Only</covrg_desc>
               <price>181.08</price>
             </row>
             <row number='9'>
                 <benefit_plan>PCHOIC</benefit_plan>
                 <plan_desc>PacifiCare / UA Net Choice</plan_desc>
                 <covrg_desc>Employee+Spouse/DomesticPartnr</covrg_desc>
                 <price>426.7</price>
             </row>
             <row number='10'>
                <benefit_plan>PCHOIC</benefit_plan>
                <plan_desc>PacifiCare / UA Net Choice</plan_desc>
                <covrg_desc>Employee + Child(ren)</covrg_desc>
                <price>394.07</price>
             </row>                
             </pcp>     
   </page>
   <page name='DENTAL'>     
            <heading2>2004 Dental Options for Wxxxx,Donald x</heading2>

            <pcp>
               <row number='0'>                  
                  <benefit_plan>DEPO</benefit_plan>
                  <plan_desc>Delta Exclsv Pnl Optn</plan_desc>

                  <covrg_desc>Employee Only</covrg_desc>
                  <price>0</price>                               
               </row>
               <row number='1'>                 
                  <benefit_plan>DEPO</benefit_plan>
                  <plan_desc>Delta Exclsv Pnl Optn</plan_desc>

 
<covrg_desc>Employee+Spouse/DomesticPartnr</covrg_desc>
                  <price>13.5</price>                               
               </row>
               <row number='2'>                
                  <benefit_plan>DEPO</benefit_plan>
                  <plan_desc>Delta Exclsv Pnl Optn</plan_desc>

                  <covrg_desc>Employee + Child(ren)</covrg_desc>
                  <price>16.36</price>

               </row>                                                 
         </pcp>                  
   </page>
</form>

-----Original Message-----
From: M. David Peterson [mailto:m.david@xxxxxxxxxx]
Sent: Tuesday, March 23, 2004 12:50 AM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: [xsl] FW: key, generate-id, ignoring my template

Current Thread