XPath and XSL templates

T

Tom Alsberg

Hi there...

I'm recently trying to get a bit acquainted with XML Schemas and XSL.

Now, I have a few questions about XSL stylesheets and templates:

* Is there a way to "enter" a child element of the currently matched
element in the template, so that XPath expressions inside would be
relative to that node? Specifically, I want to do this, in order to
be able to issue xsl:call-template to apply some templates to some
of the child nodes (of course redefining the template many times
with different relative paths is not an option)

* How can I logically AND two expressions in an XPath boolean
expression?

* Can I define more than one template for a match, with one being the
default, but being able to select different ones with
xsl:apply-templates? (I understand that modes are only to select
which elements to process, not which of a few templates to apply to
an element)

* Can you give some suggestions for the following scenario:

What I'm right now working on is defining an XSD type for human names:

<xs:complexType name="personname">
<xs:all minOccurs="1">
<xs:element name="title" type="xs:string" minOccurs="0" />
<xs:element name="given" type="xs:string" />
<xs:element name="middlename" type="xs:string" minOccurs="0" />
<xs:element name="surname" type="xs:string" />
<xs:element name="suffix" type="xs:string" minOccurs="0" />
</xs:all>
</xs:complexType>

What I want, is to have in an XSL stylesheet, a few different
templates for processing a personname, and be able to, in different
contexts (in the same stylesheet), call different templates for it.

E.g., a person:

<name>
<title>Mr.</title>
<given>Richard</given>
<middlename>Lee</middlename>
<surname>Tarpit</surname>
<suffix>the 17th</suffix>
</name>

could be processed as "Richard Tarpit", "Mr. Richard Lee Tarpit",
"Richard Tarpit the 17th", or "Mr. Richard Lee Tarpit the 17th". I
could then define templates for processing the name in those ways,
and in other places within the stylesheet, depending on the context,
call different templates on such a child element.

Thanks, any guidance appreciated,
-- Tom
 
M

Martin Honnen

Tom said:
Now, I have a few questions about XSL stylesheets and templates:

* Is there a way to "enter" a child element of the currently matched
element in the template, so that XPath expressions inside would be
relative to that node? Specifically, I want to do this, in order to
be able to issue xsl:call-template to apply some templates to some
of the child nodes (of course redefining the template many times
with different relative paths is not an option)

I am not sure why you want to enter a child element as you should always
have access to child elements just by using the XPath to it e.g.
./child-element-name
* How can I logically AND two expressions in an XPath boolean
expression?

expression1 and expression2
see http://www.w3.org/TR/xpath#NT-AndExpr
* Can I define more than one template for a match, with one being the
default, but being able to select different ones with
xsl:apply-templates? (I understand that modes are only to select
which elements to process, not which of a few templates to apply to
an element)

I would answer your question with the suggestion to use modes as that is
a way to define more than one template for a match and a way to be able
to select different ones with xsl:apply-templates. However as after the
question you point out that you already know about modes but think they
are not suitable I am not sure what you have in mind.
Maybe you can elaborate.
* Can you give some suggestions for the following scenario:

What I'm right now working on is defining an XSD type for human names:

<xs:complexType name="personname">
<xs:all minOccurs="1">
<xs:element name="title" type="xs:string" minOccurs="0" />
<xs:element name="given" type="xs:string" />
<xs:element name="middlename" type="xs:string" minOccurs="0" />
<xs:element name="surname" type="xs:string" />
<xs:element name="suffix" type="xs:string" minOccurs="0" />
</xs:all>
</xs:complexType>

What I want, is to have in an XSL stylesheet, a few different
templates for processing a personname, and be able to, in different
contexts (in the same stylesheet), call different templates for it.

E.g., a person:

<name>
<title>Mr.</title>
<given>Richard</given>
<middlename>Lee</middlename>
<surname>Tarpit</surname>
<suffix>the 17th</suffix>
</name>

could be processed as "Richard Tarpit", "Mr. Richard Lee Tarpit",
"Richard Tarpit the 17th", or "Mr. Richard Lee Tarpit the 17th". I
could then define templates for processing the name in those ways,
and in other places within the stylesheet, depending on the context,
call different templates on such a child element.

It appears that the example is a follow-up to your previous questions so
lets take that as an example on how to use modes to apply different
templates to the same element: the following stylesheet has two
templates for <name> elements, one with a mode named "short", the other
one with a mode named "full":

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:eek:utput method="html" encoding="UTF-8" />

<xsl:template match="/">
<html>
<head>
<title>Names</title>
</head>
<body>
<div>
<xsl:apply-templates select="name" mode="short" />
</div>
<div>
<xsl:apply-templates select="name" mode="full" />
</div>
</body>
</html>
</xsl:template>

<xsl:template match="name" mode="short">
<xsl:value-of select="concat(given, ' ', surname)" />
</xsl:template>

<xsl:template match="name" mode="full">
<xsl:value-of select="concat(title, ' ', given, ' ', middlename, ' ',
surname, ' ', suffix)" />
</xsl:template>

</xsl:stylesheet>
 
P

Patrick TJ McPhee

% * Is there a way to "enter" a child element of the currently matched
% element in the template, so that XPath expressions inside would be
% relative to that node? Specifically, I want to do this, in order to

You could use a mode specific to that element

<xsl:template match='frankenstein'>
<xsl:apply-templates mode='child-of-frankenstein'/>
<xsl:template>

this will work with call-template as well. You can also use for-each.
The XPath expressions in for-each will be relative to the node
currently being processed.

% * How can I logically AND two expressions in an XPath boolean
% expression?

Using `and'.

% * Can I define more than one template for a match, with one being the
% default, but being able to select different ones with
% xsl:apply-templates? (I understand that modes are only to select
% which elements to process, not which of a few templates to apply to
% an element)

No, modes are to select which of a few templates to apply to an element.
Anyway, there are a few things which might deal with your problem.

You can assign a predicate to a match expression, and use that to
decide which template to apply

<xsl:template match="car[@colour ='blue']">
...
</xsl:template>

<xsl:template match="car[@colour ='red']">
...
</xsl:template>

<xsl:template match="car">
...
</xsl:template>

Roughly speaking, the XSLT processor will choose the most specific
template which matches a node. It's an error to have two equally
specific templates which match the same node, though.

You can set the priority on a match. For instance, if you could have
templates to match cars by the number of doors and a template to match
cars by colour, and say the colour is more important:

<xsl:template match="car[@colour ='blue']" priority='2'>
...
</xsl:template>

<xsl:template match="car[@colour ='red']" priority='2'>
...
</xsl:template>

<xsl:template match='car[@doors = '2'] priority = '1'>
...
</xsl:template>

<xsl:template match='car[@doors = '4'] priority = '1'>
...
</xsl:template>

<xsl:template match="car" priority = '0'>
...
</xsl:template>

This would match the first template for all blue cars, the second template
for all red cars, the third for all cars with 2 doors which are neither
blue nor red, and the fourth for all cars with 4 doors which are neither
blue nor red. The final template would be used for cars which have a
different number of doors or don't have enough information to match
the previous four templates.

% What I'm right now working on is defining an XSD type for human names:

For a tiny subset of humans (says a guy with two middle names :).

% What I want, is to have in an XSL stylesheet, a few different
% templates for processing a personname, and be able to, in different
% contexts (in the same stylesheet), call different templates for it.

Sounds you want to use a mode to me. Define a template with a mode
<!-- format a name, current node should be a name element.
also, this should take into account names that run surname givenname
and probably all sorts of other things I can't be bothered with now -->
<xsl:template name='format-name' mode='formal'>
<xsl:value-of select='title'/>
<xsl:text> </xsl:text>
<xsl:value-of select='given'/>
<xsl:text> </xsl:text>
<xsl:value-of select='middle'/>
<xsl:text> </xsl:text>
<xsl:value-of select='surname'/>
</xsl:template>

<xsl:template name='format-name' mode='informal'>
<xsl:value-of select='given'/>
<xsl:text> </xsl:text>
<xsl:value-of select='surname'/>
</xsl:template>

<xsl:template name='format-name' mode='anal'>
<xsl:value-of select='title'/>
<xsl:text> </xsl:text>
<xsl:value-of select='given'/>
<xsl:text> </xsl:text>
<xsl:value-of select='middle'/>
<xsl:text> </xsl:text>
<xsl:value-of select='surname'/>
<xsl:text> </xsl:text>
<xsl:value-of select='suffix'/>
</xsl:template>

Which you would call like this:

<xsl:text>I dined with </xsl:text>
<xsl:call-template name='format-name' mode='anal'>
<xsl:text> last week and I was very interesting.</xsl:text>
 
T

Tom Alsberg

Thank you, you answered some of my questions and misunderstandings.
Please read my comments below, though:

I am not sure why you want to enter a child element as you should always
have access to child elements just by using the XPath to it e.g.
./child-element-name

Quoting myself:
If I have a template that works on a <foo> element, and I'm right now
in a template matching <bar>, and that <bar> element has some <foo>
element inside it, e.g.:

<bar>
<foo>
<xyzzy>
Text
</xyzzy>
</foo>
</bar>

I could want to call the template to handle that <foo> element from
within the template matching the <bar>. Without entering that, I
would have to redefine the <foo> template to access foo/xyzzy instead
of just xyzzy.

Of course that would be easy. But say, if I have some type of
element, which contains xyzzy, but elements of that type can have
different names in different locations, I would have to define the
template for each of those names. Example:

<myfoo>
<xyzzy>
Text 1
</xyzzy>
</myfoo>

<hisfoo>
<xyzzy>
Text 2
</xyzzy>
</hisfoo>

<nofoo>
<xyzzy>
Text 3
</xyzzy>
</nofoo>

myfoo, hisfoo, and nofoo are of the same type (call it foo), and the
same template could handle them (template without a match attribute -
only with a name). However, if I cannot enter them inside the parent
template, I would have to call three different templates from within
it - one that accesses myfoo/xyzzy, another that accesses
hisfoo/xyzzy, and another that accesses nofoo/xyzzy.

Thanks. For some reason I missed that.
I would answer your question with the suggestion to use modes as that is
a way to define more than one template for a match and a way to be able
to select different ones with xsl:apply-templates. However as after the
question you point out that you already know about modes but think they
are not suitable I am not sure what you have in mind.
Maybe you can elaborate.

Well, I'm not sure anymore, now with the example you gave it seemed
to work, but I recall I tried something similiar (don't remember
exactly) and it didn't work (the parser told me I have two templates
with the same match).
It appears that the example is a follow-up to your previous questions so
lets take that as an example on how to use modes to apply different
templates to the same element: the following stylesheet has two
templates for <name> elements, one with a mode named "short", the other
one with a mode named "full":
<snip />

Thanks for the example - that seems to be the style of what I need.
I'm not sure why modes didn't work for me at the beginning - I must
have done something wrong.

Thanks again,
-- Tom
 
T

Tom Alsberg

Please read my comments below:

% * Is there a way to "enter" a child element of the currently matched
% element in the template, so that XPath expressions inside would be
% relative to that node? Specifically, I want to do this, in order to

You could use a mode specific to that element

<xsl:template match='frankenstein'>
<xsl:apply-templates mode='child-of-frankenstein'/>
<xsl:template>

But how would this help, though? There could be different children
of frankenstein, with different names (but with the same type/format
of contents), which I would want to process with the same
template... The template shouldn't have to know about what names of
elements it could deal with - when called the context should already
be within that element...

E.g., with my given example of anal human names:

<frankenstein>
<fathername>
<title>Prof.</title>
<given>Cody</given>
<lastname>Frankenstein</lastname>
</fathername>
<mothername>
<title>Mrs.</title>
<given>Lisa</given>
<middlename>Golem</middlename>
<lastname>Hyde</lastname>
<suffix>Junior</suffix>
</motername>
<sistername>
<given>Marjorie</given>
<middlename>Cody</middlename>
<lastname>Frankenstein</lastname>
</sistername>
</frankenstein>

I would then have a template matching frankenstein, and want in some
place to call the name-formatting template for fathername, in another
place call it for mothername, and in another place call it for
sistername...
this will work with call-template as well. You can also use for-each.
The XPath expressions in for-each will be relative to the node
currently being processed.

Yes, but I need some way to make the child element the current
context for the called template, so that I don't have to redefine
that template many times.
% * How can I logically AND two expressions in an XPath boolean
% expression?

Using `and'.

Thanks, for some reason I missed that in my reference.
% * Can I define more than one template for a match, with one being the
% default, but being able to select different ones with
% xsl:apply-templates? (I understand that modes are only to select
% which elements to process, not which of a few templates to apply to
% an element)

No, modes are to select which of a few templates to apply to an element.
Anyway, there are a few things which might deal with your problem.

OK, I understood that (from the example Martin gave)... Not sure
anymore, but I tried to use it in a similar (but apparently incorrect)
way and misunderstood their purpose afterwards when reading more
about them. Martin's example works.
% What I'm right now working on is defining an XSD type for human names:

For a tiny subset of humans (says a guy with two middle names :).

Sorry for that :)... Well, that's just for some little format I'm
practicing (learning) on. The suffix thingy is not really necessary
either, only gives me more options to try stuff with.
% What I want, is to have in an XSL stylesheet, a few different
% templates for processing a personname, and be able to, in different
% contexts (in the same stylesheet), call different templates for it.

Sounds you want to use a mode to me. Define a template with a mode

OK. But, see the previous example (Frankenstein, father, mother, and
sister), for where the problem is.
Which you would call like this:

<xsl:text>I dined with </xsl:text>
<xsl:call-template name='format-name' mode='anal'>
<xsl:text> last week and I was very interesting.</xsl:text>

Thanks,
-- Tom
 
P

Patrick TJ McPhee

% > You could use a mode specific to that element
% >
% > <xsl:template match='frankenstein'>
% > <xsl:apply-templates mode='child-of-frankenstein'/>
% > <xsl:template>
%
% But how would this help, though?

It depends a lot on what you're trying to do.

% There could be different children
% of frankenstein, with different names (but with the same type/format
% of contents), which I would want to process with the same
% template... The template shouldn't have to know about what names of
% elements it could deal with - when called the context should already
% be within that element...

OK, you could do it like this:

<xsl:template match="*" mode='child-of-frankenstein'>
...
</xsl:template>

This template will be evaluated for each child node of 'frankentsein'.
The context will be set to the node. You could also do it like this:

<xsl:template match="frankenstein/*">
...
</xsl:template>

and leave out the mode attribute of the apply-templates element above.


% Yes, but I need some way to make the child element the current
% context for the called template, so that I don't have to redefine
% that template many times.

This can be done using xsl:for-each and xsl:apply-templates. You could
also assign a node to a parameter before calling the template.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,052
Latest member
LucyCarper

Latest Threads

Top