Re: xsl:key

Subject: Re: xsl:key
From: Jeni Tennison <Jeni.Tennison@xxxxxxxxxxxxxxxx>
Date: Thu, 22 Jun 2000 11:16:56 +0100
Hi Rhonda, 

>My aim is to extract paragraphs from the source tree
>(temp.xml) to be output to a result tree according to
>two criteria:
>      * Target doc="contract" and
>      * Target host="true"
>This is an extension of my original question.
>The output I receive is all paragraphs disregardless
>of attribute doc and host values.  I have attempted
>to achieve my goal using two methods. I hope that you/other
>might be able to assist me.

You're really close with both your methods, but I think that you need to
have a look at XPath and how to put XPath expressions together, because
that's where many of your problems lie.

>ATTEMPT 1: Using the Key() function
>I realise that only one key value can be used and
>then the other key value can be used on the filtered
>result - unless the intersection saxon extension
>function is used.

That's true, but you can make keys that combine two values, using concat().
 So, we can index on a string like 'contract-true' using:

  <xsl:key name="blueprint" match="Para"
           use="concat(ancestor::Target/@doc, '-', ancestor::Target/@host)" />

Note that now that you've changed the way your input is structured, what
you're after are the 'doc' and 'host' attributes of the Target ancestors of
the particular Para, not a sibling.

>However here I still receive
>all paragraphs, it seems without referencing
>the index created by my xsl:key?

Have a look at your template:

><xsl:template match="Para">
>  <xsl:element name="{name()}">
>       <xsl:copy-of select="key('blueprint', contract)"/>
>       <xsl:apply-templates/>
>   </xsl:element>

You're matching all 'Para' elements, and then (for all of them), creating
an element called 'Para'.  Within that element, you're putting a copy of
the node set that you get when you retrieve the nodes from the 'blueprint'
key, using the value of the 'contract' element as the key.

This is far from what you want to do.  What you want to do is select all
the Para elements that are in the list that is retrieved when you use the
key 'contract-true' on the 'blueprint' key, and process only them, making a
copy of them.  When you only want to process a subset of nodes, then you
should only apply templates to that subset, which means changing your
root-node matching template:

<xsl:template match="/">
  <xsl:apply-templates select="key('blueprint', 'contract-true')" />

<xsl:template match="Para">
  <xsl:copy-of select="." />

I'm not sure what exactly you want your output to look like: this just
copies the relevant 'Para' elements: you might want to wrap them in another
element, in which case you should put it in the root-node matching template.

>I have also, since simply typed the value of contract
>in the key, quotes do not seem to make any difference
>either way.

They do make a difference in how it's interpreted, just not in your overall
result because either way it wasn't working! :)  When you put 'contract' in
quotes, it interprets it as the string 'contract', which is what you want.
When you don't put it in quotes, it interprets it as an XPath expression,
and tries to find a child element of the current node that is called
'contract', and use its content as the value to index into the key.

>ATTEMPT 2: Using a template match
>*** the following template is never used ***
>*** and I did hope that this template would ***
>*** select paragraphs according to attrib values ***
>*** specified? ***
><xsl:template match='Para/Target[@doc="contract"
>                         and @host="true"]'>
>  <xsl:element name="{name()}">
>       <xsl:copy-of select="attribute::node()"/>
>       <xsl:apply-templates/>
>   </xsl:element>

You need to change the XPath expression that you're using here.  Let me
explain what the XPath you're using says:

  Para/Target[@doc="contract" and @host="true"]

Find an element called 'Target' that
  has a parent element called 'Para' and
  has a 'doc' attribute with a value of 'contract' and
  has a 'host' attribute with a value of 'true'

There are no such Paras in your input.

What you want to say is:

Find an element called 'Para' that
  has an ancestor element called 'Target' that
    has a 'doc' attribute with a value of 'contract' and
  has an ancestor element called 'Target' that
    has a 'host' attribute with a value of 'true'

This can be expressed as:

  Para[ancestor::Target[@doc="contract"] and





Given your current input, a better version (because it involves less
hunting around for matching nodes) is:

Find an element called 'Para' that
  has a parent element called 'Target' that
    has a 'host' attribute with a value of 'true' and
  has a grandparent element called 'Target' that
    has a 'doc' attribute with a value of 'contract'

This can be expressed as:


You should also make sure that you *only* process those 'Para' elements,
because the XSLT Processor has built-in templates that match other elements
and will output their textual content.  Again, this means putting your
XPath expression higher up in your templates: make sure that you then adapt
it according to the current node within the template you put it in.  For

  <xsl:template match="/">
      select="//Para[ancestor::Target[@doc='contract'] and
                     ancestor::Target[@host='true']]" />


  <xsl:template match="Template">
      select="Target[@doc="contract"]/Target[@host="true"]/Para" />

I hope that this helps,


Dr Jeni Tennison
Epistemics Ltd, Strelley Hall, Nottingham, NG8 6PE
Telephone 0115 9061301 ? Fax 0115 9061304 ? Email

 XSL-List info and archive:

Current Thread