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: Tue, 23 Mar 2004 00:49:57 -0700
If I may jump in here and try to help...  

Im going through your code and my head is beginning to spin.  I'm
confused as to why you are using keys here anyway but with more
information maybe you could help me understand.  With that aside the
biggest problem I see with all of this that you are attempting to do to
much of the work for the XSLT processor and not letting it do the work
for you.  So for instance, instead of going and getting every instance
of the element <b> manually using XPath statements within
apply-templates select attributes (e.g. /html/body/h1/b and
html/body/p/b in separate xsl:apply-templates) you would simply just
match the element b in your XPath statement located in the match
attribute of the appropriate template like this:

<xsl:template match="b">
	<b>
        <xsl:apply-templates/>
	</b>
</xsl:template>

The job of the processor when it finds the element xsl:apply-templates
is to select the Result Tree Fragment(the fragment of XML that is
currently in context or select with an XPath statement) of either the
current context node if you don't use a select attribute or the RTF of
the XPath you entered if you do. Keep in mind that XSLT processors don't
like stray text laying around so, for example, if a <b> element is
matched to a template and within the matched <b> element is some text
then an <i> element with some more text before both tags are closed your
RTF would technically look like this:

This is the text that came after the "b" element that this template just
matched.<i>this is the italicized text</i> and this is the remaining
text before the "b" tag closes.

Since processors don't want to start the RTF with text nodes (they don't
care if they end with one) it will spit out the text node wherever it
happens to be at within the process then match the next element to the
proper template etc...  Thus, when you are dealing with this type of XML
data <xsl:apply-templates/> will do both the work of <xsl:value-of
select="."/> as well as its job to continue the functional processing
flow.  If you ever run across the problem of the same text being spit
out by the processor twice you'll probably find an xsl:value-of stuck
smack dab in the middle of your suspect template.

This recursive apply-templates process, if used correctly, will go
through the entire XML data flow element by element and match it to it's
respective template for processing.  So if this was your XML data:

<?xml version="1.0"?>
<html>
  <head>
    <title>this is the title</title>
  </head>
  <body onload="javascript: foo();">
    <h1>
      <b>this is a bold heading 1</b>
    </h1>
    <p>this is the text of the first paragraph.<b>this is some bolded
text in the first paragraph.  <i>this is some bold and italicized
text.</i>  </b> <i>this is italicized text.  </i>And this is just
regular text again</p>
  </body>
</html>

And your job was to convert this XML data to HTML (disregard the fact
that its technically XHTML right now - that's irrelevant in the case of
recursively matching element names to a template. It works with any
variation of element or attribute names you might throw at it) the first
thing you would do is create your stylesheet and within the
xsl:stylesheet element you would create a template with the first spot
within the document you want to transverse stuck between the quotes of
the match attribute.  In this case "html" is the first element we want
to find and then begin processing from there.  So we would put "/"
(meaning root) followed by "html" as the value of the match attribute
("/html").  From this point until the context node changes(e.g. within a
for-each block or value-of element) we will begin our XPath statements
from this point forward. Now the actual transformation from XML to HTML
can begin.  

Something to keep in mind: xsl:apply-templates is extremely powerful
when there are multiple elements nested within an XML dataset that have
the same name and require the same processor output (such as <b>this is
bold</b><b>so is this</b>).  Its not incorrect to use it when there is
only ever going to be one match but your creating an extra process that
doesn't need to be there and as such slowing the transformation of your
XML to HTML.  None the less, if you prefer to separate the processing of
all your elements in separate templates to keep your code clean there's
definitely nothing wrong with that.  No matter which way you choose your
first template is going to look something similar to this.

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="1.0">
<xsl:strip-space elements="*"/>
<!-- this element tells the processor to strip the excess white space
out of the results of all (*) transformed elements so that our output
text doesn't include all of the extra tabs and white space that tends to
exist in XML files and processed as text by the processor -->
<xsl:template match="/html">
  <html>
    <head>
      <title><xsl:value-of select="head/title"/></title>
      <!-- you could also use <xsl:copy-of select="head/title"/> and
accomplish the same thing.  its just a matter of preference and in some
cases ensuring correct processor output of HTML but that's nothing to
worry about here  -->
      <!-- if you had meta tags, and I know you do, given that there are
multiple instance this would be where you put your first apply-templates
with "head/meta-tags" as the value of your select attribute -->
    </head>
    <body>
      <xsl:copy-of select="body/@*"/>
        <!-- and now we take the Result Tree Fragment contained within
the body tag and begin to process it with apply-templates -->
      <xsl:apply-templates select="body"/>
    </body>
  </html>
</xsl:template>

<xsl:template match="h1 | p | b | i">
<!-- now here's where the funs starts. In every case where all we want
to do is copy the element name and the text following it leading up to
the next tag all we have to do is join the elements within our match
attribute using "|" and then use the following markup elements to output
the proper name and content.  We then use apply-templates to print out
the text and continue the process.  I added a copy-of and used
select="@*" (meaning select attributes all) to pick up those stray
attributes that sometimes are hanging around in the source XML.  It also
allows us to use this same template for other tags that have attributes
that can simply be copied over without any sort of translation (e.g.
<img src="foo.jpg"> would work but <image source="foo.jpg/> or other
variations would not)  -->
  <xsl:copy>
    <xsl:element name="name()">
    <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:copy>  
</xsl:template>
</xsl:stylesheet>

And there you have it.  When the above XML is processed by the above
XSLT you get this result:

<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>this is the title</title>
</head>
<body onload="javascript: foo();">
<h1>
<b>this is a bold heading 1</b>
</h1>
<p>this is the text of the first paragraph.<b>this is some bolded text
in the first paragraph.  <i>this is some bold and italicized
text.</i></b><i>this is italicized text.  </i>And this is just regular
text again</p>
</body>
</html>

Which is exactly what we wanted :)

Obviously this is a very basic example, but by understanding how the
apply-templates process is designed to work you can now begin to apply
this knowledge to your stylesheets and begin to get the results you
truly want.

Now back to the keys...  What is it that you are trying to accomplish
here?  It seems to me your data is already grouped correctly and as such
you don't need to use keys to match element and/or attribute names
and/or there respective values to other external data that potentially
belongs to that particular data group.  If this is the case then what
you really need to do is decide within your first match statement just
what data it is you want to transform and then transform it.

So if your trying to transform the MEDICAL page then do this for your
opening template:

<xsl:template match="/form/page[@name = 'MEDICAL']">

And then change the value when you want to transform the DENTAL.

If this is being transformed dynamically by a web server then simply
create a variable called pageName, pass the value of the transform you
want to do (e.g. "MEDICAL") to it and put that in place of the
'MEDICAL'.  You can also set the value of the variable to a default
value that you can either leave alone or have the process change when it
begins the transformation process.

Example: 

<xsl:variable name="pageName">MEDICAL</xsl:variable>

<xsl:template match="/form/page[@name = $pageName]">


Well, I hope I have helped lead you down the path of truth and
apply-templates righteousness and given you the correct keys to eternal
XSLT happiness(I'm starting to get punchy; must be late ;).  If I can
help with anything else let me know.

Have a great day!

<M:D/>


-----Original Message-----
From: Laura Madonna [mailto:Laura.Madonna@xxxxxxxxx] 
Sent: Monday, March 22, 2004 5:16 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: [xsl] FW: key, generate-id, ignoring my template

That did help, but now I am having trouble getting the children
<covrg_desc> to print for each key row-plan_desc-page.
I am only getting the first instance of <covrg_desc> to print for each
vBenePlan.
Thank you.
Laura

-----Original Message-----
From: Andreas L. Delmelle [mailto:a_l.delmelle@xxxxxxxxxx]
Sent: Monday, March 22, 2004 3:57 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: [xsl] FW: key, generate-id, ignoring my template


> -----Original Message-----
> From: Laura Madonna [mailto:Laura.Madonna@xxxxxxxxx]
>
> (I have cut down the xsl and xml).
> The problem is, I don't think the template to match on "page" is
working:
>
>

Hi,

No, actually, I believe your problem is the variable definitions.
They return you nodes from the whole document, instead of just the
context
node...

It's ok (even obligatory) to have the *key* definitions as children of
the
xsl:stylesheet, but you probably want to move the variable declarations
into
the matching template, and while you're at it, go for compound keys,
like

<xsl:key name="row-plan_desc-page" match="row"
         use="concat(ancestor::page/@name,
           ' ', plan_desc)" />
<xsl:key name="row-heading-page" match="row"
         use="concat(ancestor::page/@name,
           ' ', heading)" />

In combination with:

<xsl:template match="/form/page/pcp">
  <xsl:variable name="vBenePlan"
                select="row[generate-id() = generate-id(
                  key('row-plan_desc-page', concat(
                    ancestor::page/@name,' ',plan_desc)))]" />
  <xsl:variable name="vHeading"
                select="row[generate-id() = generate-id(
                  key('row-heading-page', concat(
                    ancestor::page/@name,' ',heading)))]" />

Hope this helps!

Cheers,

Andreas

Current Thread