xslt template match

Templates

1. Understanding template match
2. Template not matching, Namespace issues
3. Template match explanation
4. Template priorities
5. Is it possible to mix implicit and explicit stylesheets
6. Matching templates and built in rules
7. Is there a template template
8. What are the rules for template selection
9. Variables within templates
10. Replacing characters in a string
11. Return a value from a named template
12. Empty apply-templates
13. Template match on negated XPATH
14. Arbitrary content in a template
15. Match everything except a given node
16. How to process every node except one
17. Case insensitive apply templates
18. Using round brackets in xpath statements
19. template match explanation
20. Interpretation of absolute location path
21. Apply-templates, or handling mixed content

1.

Understanding template match

Mike Brown


> <xsl:template match="@foo='bar'">
	

As Ken Holman mentioned, the solution is <xsl:template match="@foo[.='bar']">

You also wanted to know what was wrong...

@foo='bar' is an equality test. It evaluates to a boolean (true or false). So you are essentially saying this template matches nodes that are true or false.

In an xsl:template's match attribute, you want a pattern: a subset of XPath expressions that essentially identify node-sets, not a boolean, and against which a given node can be tested for a match.

For example if match="foo" then a given node will match if it is an element named foo. If match="fu/foo" then the node will match if it is an element named foo that is a child of an element named fu.

The main difference between a regular XPath expression and a pattern here is that there is no current node; instead the processor is reading the pattern from right to left and verifying that the current node follows the pattern.

The expression

  fu/foo

would normally be the nodes that are, relative to the current node, something like current node --> child::fu --> child::foo.

In a pattern, it is as if the node being tested is the current node, and means, essentially, current node --> self::foo[parent::fu]

You don't really have to thoroughly get patterns. I have been working with them for nearly 2 years and they're still hard to explain. The important thing to understand is that an xsl:template match pattern is not an instruction to go find nodes. That's what xsl:apply-templates is for. The processor will have a node already, as a result of xsl:apply-templates, usually, and will be looking for a template that is a good match for it.

2.

Template not matching, Namespace issues

David Carlisle et al

  
Kindly bear these slip-ups as coming from someone who's still learning. How
  many different kinds of namespaces are there in XSLT.

Oh yes sorry I didn't mean to criticize. (You could always criticise my typing accuracy if it came to that:-)

However if people use completely wrong terminology then it's not too confusing as you can guess what they mean and react accordingly, but if you are unlucky and the terminology is correct (but doesn't mean what was intended) then it's harder to spot that a translation is required.

How many different kinds of namespaces are there in XSLT.

1. no-namespace

2. default namespace

  and is there any other?

Well if you phrase it that way there are others yes, such as namespaces that are neither the default (and are not no-namespace). But listing the choices like that is confusing as they are really independent.

There is the syntactic/structural distinction:

All namespaces have a namespace name which is a URI such as http://fooobar there is one special case of a "null namespace" for elements that are not in a namespace at all. XSLT essentially treast no-namespace as a namespace with namespace URI the empty string "" so you often hear this called the nul namespace or similar terms, although the namespace rec, says that such elements are not in a namespace. So your first distiction is saying whether the namespace has a namespace URI or not.

Then there is a syntactic issue, if an element is in a namespace say element xx in namespace http://foobar then it may appear with (any) prefix or with no prefix, but Xpath essentially treats all these things as equivalent, just as <x n="2"/> and <x n='2'/> are equivalent.

so

<xx xmlns="http://fooobar"/>
<p:xx xmlns:p="http://fooobar"/>
<zzz:xx xmlns:zzz="http://fooobar"/>

the term "default namespace" refers to using the first unprefixed from above. But this is essentially a syntactic issue apart from one (or two:-) special functions XSLT and XPath can not tell that the default namespace syntax was used, and if it was not used, it can't tell which prefix was used. In all the cases above you woul duse the same match expression something like

match="qqq:xx"  xmlns:qqq="http://fooobar"

James Fuller offers a few links

I think David succintly explained XML namespaces as constrasted with XSLT, here are a few background links older stuff xml.com xml names at W3C James Clarks site zvon

David C later answered another one I include here.

namespace FAQ number 1:

<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" ...
   ...
  <Worksheet

so that is Workbook and Worksheet in namespace urn:schemas-microsoft-com:office:spreadsheet

and this

<xsl:for-each select="Workbook/Worksheet

selects so that is Workbook and Worksheet in no-namespace

so selects nothing in this case.

You want

xmlns:x="urn:schemas-microsoft-com:office:spreadsheet" on your xsl:stylseheet then

<xsl:for-each select="x:Workbook/x:Worksheet

etc so that you select elements from this namespace.

> I can't figure out how to get the declaration of such a prefix into 
> the xs:schema result element since both the prefix and the URI to 
> which it refers are unknown until runtime.

Michael Kay answers this one.

The xsl:namespace instruction was added in XSLT 2.0 for this purpose. It allows you to create a namespace node with a prefix and uri, just as you would use xsl:attribute to create an attribute node.

The only way to achieve the same effect in XSLT 1.0 is a workaround: create a result tree fragment containing an element in the relevant namespace, and then copy the resulting namespace node:

<xsl:variable name="temp">
  <xsl:element name="{concat($prefix, ':dummy')}" namespace="{$uri}"/>
</xsl:variable>
<xsl:copy-of select="xx:node-set($temp)//namespace::*[name()=$prefix]"/>

3.

Template match explanation

Mike Brown

>  as far as I know, the following code would look for a
> node named "name" and, if matched, output the containing elements:
> ...
> &lt;xsl:template match="name">
>  &lt;b>Name found.&lt;/b>
>  &lt;xsl:apply-templates/>
> &lt;/xsl:template>
	

No, the "looking" is something that happens elsewhere. It does not happen by virtue of there being a template that matches "name" elements. This template just says what to do *if* *a* name element happens to be encountered in the course of processing.

&lt;xsl:apply-templates/> says "go process the nodes that are children of the current node". For each given node, the best matching template is found and its instructions executed.

Processing begins at the root node.

There are built-in templates for each type of node. There are others, but mainly you need to know about these:

  For root and element nodes: go process children.
  For text nodes: copy node to result tree.
	

4.

Template priorities

Andrew Kimball <akimball@microsoft.com>

Here are some priority examples:

PATTERNPRIORITY
foo0
processing-instruction(foo)0
foo:*-0.25
*-0.5
node()-0.5
text()-0.5
comment()-0.5
processing-instruction()-0.5
foo/bar0.5
foo/*0.5
foo[bar]0.5
*[*]0.5
foo[bar]0.5
*/*0.5

Its a mistake to think that foo/* has a priority of -0.5. Only patterns consisting of *just* a NodeTest have priority of -0.5. As soon as you introduce filtering [] or composition /, that bumps the default priority up to 0.5 (because foo/* is more specific than foo or *).

5.

Is it possible to mix implicit and explicit stylesheets

Jeni Tennison

I'm not sure which you're counting as implicit and which as explicit, but the answer is 'sort of'.

When you have a literal result element as the stylesheet (so the stylesheet looks like an HTML document with some xsl elements in it to indicate where information should be slotted in), it's equivalent to declaring a stylesheet with a template that matches on the root node, with the same content. For example:

<html xmlns:xsl="...">
  ...
  <td><xsl:value-of select="Section/@Name"/></td>
  <td><xsl:apply-template select="Option"></td>
</html>

is equivalent to:

<xsl:stylesheet version="1.0" xmlns:xsl="...">

<xsl:template match="/">
  <html>
    ...
    <td><xsl:value-of select="Section/@Name"/></td>
    <td><xsl:apply-template select="Option"></td>
  </html>
</xsl:template>

</xsl:stylesheet>

If you transform a stylesheet into the second form, then you can add any other templates you want into it. For example:

<xsl:stylesheet version="1.0" xmlns:xsl="...">

<xsl:template match="/">
  <html>
    ...
    <td><xsl:value-of select="Section/@Name"/></td>
    <td><xsl:apply-template select="Option"></td>
  </html>
</xsl:template>

<xsl:template match='Option[@Type="List"]'>
  <select>...</select>
</xsl:template>

</xsl:stylesheet>

You can also put any top-level elements like xsl:import or xsl:output or xsl:key into it. Unsurprisingly, 'proper' stylesheets like these are a lot more powerful than 'basic' stylesheets, but they are also a lot more complex.

6.

Matching templates and built in rules

Jeni Tennison

This problem stems from applying templates to a larger set of nodes than those you actually want to process. In your root-matching template:

<xsl:template match="/">
  <HTML>
    <HEAD></HEAD>
    <BODY>
      <p>Select clause = //item - i.e. select all
          item' elements in tree regardless of ancestry.</p>
      <xsl:apply-templates select="//item" />
    </BODY>
  </HTML>
</xsl:template>

you apply templates to *all* the item descendents of the root node (i.e. all the item elements in the document).

You have a template that deals with those items for which the @item_ParentID is '2':

<xsl:template match="item[@item_ParentID='2']">
  <p>
    <xsl:value-of select="@item_text" />
  </p>
</xsl:template>

But all the other item elements are processed by the built-in template:

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

Your item elements are nested inside each other, so when your 'File' item is processed by the built-in template, templates are applied to all its children: the 'Save' and 'Documents' items.

So, the 'Save' and 'Documents' items are processed twice, once because they are selected as descendent items of the root node, and once because they are processed as children of the 'File' item.

There are three ways to solve this problem.

The first is to stop the built-in rule applying to the item elements that you don't want to match by adding an item-matching template that does nothing:

<xsl:template match="item" />

The second is to take advantage of the built-in templates and change your root-mode-matching template to process the first level of items, letting the built-in templates process the children of these items until you get to the items that you actually want to give some output for:

<xsl:template match="/">
  <HTML>
    <HEAD></HEAD>
    <BODY>
      <p>Select clause = //item - i.e. select all
          item' elements in tree regardless of ancestry.</p>
      <xsl:apply-templates select="/menu/item" />
    </BODY>
  </HTML>
</xsl:template>

The third is to only process those items that you're actually interested in in the first place and have a general item-matching template to give their content:

<xsl:template match="/">
  <HTML>
    <HEAD></HEAD>
    <BODY>
      <p>Select clause = //item - i.e. select all
          item' elements in tree regardless of ancestry.</p>
      <xsl:apply-templates select="//item[@item_ParentID='2']" />
    </BODY>
  </HTML>
</xsl:template>


<xsl:template match="item">
  <p>
    <xsl:value-of select="@item_text" />
  </p>
</xsl:template>


This latter is probably the most efficient because every time you apply
templates on a node it takes time to find the templates to be applied to
it: the fewer nodes you select to process, the less time it takes.

7.

Is there a template template

James Wilson

E.g. I have a basic template transformation:

<xsl:template match="control">
<xsl:text 
 
CONTROL
 
</xsl:text>
<xsl:apply-templates/>
<xsl:text 
END
 
</xsl:text>
</xsl:template>

I need to apply this kind of template to literally hundreds
of different cells; i.e. I have other cells which require
the same kind of template, except replace "control"
with "[something else]".



you could use a named template, 
 and call it from your 'generic' template:
<xsl:template name='text1'>
    <xsl:choose>
        <xsl:when test='control'>CONTROL</xsl:when>
        <xsl:when test='param1'>PARAM1</xsl:when>
        ...
    </xsl:choose>
</xsl:template>

<xsl:template match='CONTROL|PARAM1|...'>
    <xsl:text>

</xsl:text>
    <xsl:call-template name='text1'/>
    <xsl:text>
END</xsl:text>
</xsl:template>

            

8.

What are the rules for template selection

John Prevost

Q: Expansion I have a problem of understanding about XSL, and I'm sure some of you have the answer to it. My problem comes when I have several templates in a stylesheet, and that more than one match a select value of an apply-templates command. I read somewhere that XSL processors should use the template that has the more 'specific' pattern

No--there is something like that in the XSLT document. Note, however, that the definition of "more specific" may not be quite what you want. According to the current spec, the following rules are used (Section 5.5 in 19990813):

1. Imported rules have lower and lower "import precedences" and each import precedence is basically looked at separately. Hence, everything in your top-level stylesheet overrides everything imported from it, and so on.

2. The priority of the rules is then used to determine which template is used. Priority can be set explicitly on a template, or the "default" value can be used--and it's that value that selects for specificity. But the criteria are pretty simple:

a. A specific element or attribute node which is named with no wildcard has priority 0

b. A name with a namespace and * has priority -0.25

c. If the pattern is just a NodeTest (a wildcard with no namespace, or something like "node()" or "text()"), the priority is -0.5.

d. Otherwise, the priority is 0.5

So:

<template match="para">             priority is 0
<template match="@style">           priority is 0
<template match="xhtml:*">          priority is -0.25
<template match="*">                priority is -0.5
<template match="text()">           priority is -0.5
<template match="para/frob">        priority is 0.5
<template match="para[@style]">     priority is 0.5
<template match="para[@style='a']"> priority is 0.5
        ...

So more than one element name in a path overrides just the element name. Likewise, an element name with a filter overrides. But a filter and a path of element names clash. So do two filters.

In general, if you're actually starting to worry about priority from a specificity point of view, you've probably already hit the point where you need to declare priorities explicitly. (As you do in your example.)

The default priorities are only useful for cases where you're overriding the most incredibly basic templates.

See Mike Kays site for explanation.

9.

Variables within templates

David Carlisle

Q expansion.

If a variable should be visible "for all following siblings and their descendants." why I am getting an error?

Because that refers to siblings and descendants of the <xsl:variable> element in the XML tree of the stylesheet.

so in here

   <xsl:for-each select="invoices/invoice">
      <xsl:variable name="language" 
	select="@xml:lang" />
      <xsl:apply-templates />
   </xsl:for-each>
      
            

the variable scope applies only to the <xsl:apply-templates element (so you could have used the variable language in a select expression on that element)but that is all. The other templates are not in this scope.

If you want to pass information down the recursive apply-templates call, then you need to make it a parameter to the templates

 <xsl:for-each select="invoices/invoice">
      <xsl:apply-templates>
        <xsl:with-param name="language" 
 select="@xml:lang" />
      </xsl:apply-templates>
   </xsl:for-each>
      
and declare language to be a param of any templates that need to use
it.



            

10.

Replacing characters in a string

David Carlisle

Example:
See if there's an apostrophe character, if so, convert this
to \', if not, just output the value of 'name'.

select=""'""
select=""\'""




  <xsl:call-template name="string-replace">
    <xsl:with-param 
name="from"select="&quot;'&quot;"/>
   
    <xsl:with-param name="to" 
select= "&quot;\'&quot;" /> 
    <xsl:with-param name="string" 
select="."/>
  </xsl:call-template>


<!-- replace all occurences of the character(s) `from'
     by the string `to' in the string `string'.-->
<xsl:template name="string-replace" >
  <xsl:param name="string"/>
  <xsl:param name="from"/>
  <xsl:param name="to"/>
  <xsl:choose>
    <xsl:when test="contains($string,$from)">
      <xsl:value-of select="substring-before($string,$from)"/>
      <xsl:value-of select="$to"/>
      <xsl:call-template name="string-replace">
      <xsl:with-param name="string" 
         select="substring-after($string,$from)"/>
      <xsl:with-param name="from" select="$from"/>
      <xsl:with-param name="to" select="$to"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$string"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>


            

11.

Return a value from a named template

Sebastian Rahtz


This you can do now:

<xsl:variable name="foo">
    <xsl:call-template name="bar"/>
  </xsl:variable>

what "bar" does will be in the variable $foo

            

12.

Empty apply-templates

Mike Brown

the spec says: "In the absence of a select attribute, the xsl:apply-templates instruction processes all of the children of the current node, including text nodes."

 so does that mean that these are the same:
 <xsl:apply-templates/>
 <xsl:apply-templates 
 select="*|comment()|text()|processing-instruction()"/>

http://www.w3.org/TR/xpath#data-model says "The children of an element node are the element nodes, comment nodes, processing instruction nodes and text nodes for its content" ... so, yes, you got it.

> that is, the ones omitted are: "@*|.|namespace::*"

Yep.

Excluded are attributes and namespace nodes, both of which are types of node that are merely associated with element nodes, not really being children of them.

DaveP added. Also note that if a match pattern is down to the level of PCDATA, then that PCDATA is also *not* a child of the current node

13.

Template match on negated XPATH

David Carlisle

> I want to match all the BOOKING elements that do not
contain a > LOCATOR[@TYPE='CANCEL'] element.

select="BOOKING[not(LOCATOR[@TYPE='CANCEL'])]"


            

14.

Arbitrary content in a template

Iain Huxley



 Pass the result tree fragment for the output you wish to 
include as a 
parameter:

<xsl:template match="test1">
   <xsl:call-template name="bolditalic">
     <xsl:with-param name="content">
       <!-- example content below will be inserted-->
               <xsl:call-template name="foo"/>
               <xsl:value-of select="test2"/>
               <xsl:apply-templates/>
          Any other literals
       <!-- end of inserted stuff -->
     </xsl:with-param>
   </xsl:call-template>
</xsl:template>

<xsl:template name="bolditalic">
   <xsl:param name="content"/>
   <B>
     <I>
       <xsl:copy-of select="$content"/>
     </I>
   </B>
</xsl:template>

15.

Match everything except a given node

> I want to match every node with an element as a parent except for one node
> with a specific name (news_detail.)
    

Mike Kay

//*/node()[not(self::news_detail)]

16.

How to process every node except one

Jeni Tennison

>Now, my XSLT file should be like this:
>
>1. Match all <node> elements
>2. If element contains "name:", "subject:" or "date:", then continue 
>processing
>3. If element contains the unwanted name "Jones" then:
>3a. Skip until element contains "name:" again
>3b. Process 3 again
>4. Show value of element

Are you sure you *really* need to go about it in this way? The way you're thinking here is very procedural: you're trying to process each node in turn and use the results of processing this node to determine which nodes to process next. This is causing you problems because XSLT is much better suited for declarative solutions to problems.

This is really a grouping problem: you want to output all the information about a particular person (unless they are 'Jones'). So I'd first create groups of nodes for each person using xsl:key:

<xsl:key name="people"
         match="node"
         use="(self::node | preceding-sibling::node)[starts-with(.,
'name:')][last()]"
/>

Just to explain the use expression here. This is searching for the node that holds the 'name' for the group. This can be either the node itself or one of its preceding siblings. Of course, the further down the file you get, the more preceding siblings there are that have names (for example, Miller's subject: node has the names 'Miller', 'Jones' and 'Smith' to choose from). You want the last of these (the most recent).

[Aside: note that this is [last()] rather than [1] because the braces around the node set (self::node | preceding-sibling::node) mean that the node set gets given in document order rather than reverse document order.]

You can then process only those nodes that start with 'name:', secure in the knowledge that you can get the rest of the nodes using the key. You can also filter out any nodes that you want. Basically, you're only processing the first node in each group - in this particular case it's easy to identify which those are because their content starts with 'name:'.

<xsl:template match="root">
  <xsl:apply-templates select="node[starts-with(., 'name:') and
                                    substring-after(., ' ') != 'Jones']"
                       mode="process-group" />
</xsl:template>

The following template then matches on 'node' in the 'process-group' mode. You don't have to worry about selecting which nodes to process here: you've selected them in the calling template (above), so you can guarantee that the only nodes that reach this template are going to be the ones you're interested in. The template uses the key to get the other nodes relevant to the person and applies templates to them.

<xsl:template match="node" mode="process-group">
  ----
  <xsl:for-each select="key('people', .)"><xsl:text>
    </xsl:text>
    <xsl:apply-templates select="." />
  </xsl:for-each>
</xsl:template>

You could definitely solve this problem without using keys, but I think the select expression in the for-each would be really hideous (mind you, the use expression in the xsl:key is nothing to write home about).

17.

Case insensitive apply templates

> Is there any way to select case-_in_sensitively? Do I need
> to write functions which create different mixed-case versions of what's
> typed, and then use these versions with "or" in the select predicate/filter?
> Or is there an easier way (I hope)?
    

Mike Brown

If $foo is what the user entered,

<xsl:apply-templates
  select="someElement[translate(.,'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
  'abcdefghijklmnopqrstuvwxyz') = translate($foo,
  'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')]" />

would apply the best matching templates for each someElement element that has a string-value that is the same as $foo, case-insensitively.

Jenni Tennison adds One good method for testing in a case-insensitive way is to translate both the things you're testing into lowercase (or uppercase) and testing for equality or whatever with the translated strings.

Define variables for uppercase and lowercase letters:
<xsl:variable name="lowercase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />

Then use the translate() function to translate the two things you're comparing into lowercase and compare them:

  translate($word1, $uppercase, $lowercase) =
  translate($word2, $uppercase, $lowercase)

You can put this test within a predicate in an XPath, of course.

18.

Using round brackets in xpath statements

David Carlisle

If I do

 <xsl:apply-template select="foo//bar[1]"/>

I get mutltiple results from a tree

  <foo>
   <x><bar/></x>
   <x><bar/></x>
   <x><bar/></x>
  </fo>

because each <bar> is #1 in its sequence, whereas I really only want the first <bar> from the result sequence.

but you don't if you do <xsl:apply-template select="(foo//bar)[1]"/>

foo//bar[1] All the bar which are the first child of any descendent of any foo child of the current node.

foo/descendant::bar[1] All the bar which are the first descendant of any foo child of the current node.

foo//bar All the bar which are descendants of any foo child of the current node.

(foo//bar)[1] The first node in document order in the node set constructed above. Note if there is more than one foo child this is the only one guaranteed to select at most one node

Just what do the () do here?

Make the [] apply to the whole node set constructed by foo//bar rather than just the last step in //bar[1].

The rec says:

NOTE: The meaning of a Predicate depends crucially on which axis applies. For example, preceding::foo[1] returns the first foo element in reverse document order, because the axis that applies to the [1] predicate is the preceding axis; by contrast, (preceding::foo)[1] returns the first foo element in document order, because the axis that applies to the [1] predicate is the child axis.

Ah. This quote is just the spec being deliberately obscure. The child axis stuff is just a red herring.

What it means is that the meaning of [1] depends on the axis. child::*[1] picks the first child, ancestor::*[1] picks the parent (not the first ancestor in document order) but some expressions, if they are not just a single step don't have an axis. eg (foo//bar)[1] or (following::x | ancestor::y)[2] in these cases document order is always used. But rather than say document order is used the spec says the child axis applies. But the only use of the axis here is to determine that position() refers to document rather than reverse document order.

The xpath selection only happens once in each case.

foo//bar[1] is short for child::foo/descendant-or-self::node()/child::bar[1]

the scope of the [1] here is just the last step ie child::bar

so you get the first bar child of every descendant of every foo child.

whereas (foo//bar)[1] is short for (...)[1] so you get the first node selected by whatever I should have filled in for ....

You can of course mix and match

(  (foo//xxx)[1] | (foo[4]//yyy[3])[2]  )  [1]

selects the first node in document order that is either

the first xxx that is a descendant of foo or the second yyy that is a third yyy child of a descendant of the fourth foo

So, the important thing to consider with [ predicates ] is which node set they apply to. In the abscence of () then this is normally just the last step i.e. the stuff after the last / but using () you can apply them to any node set.

19.

template match explanation

Mike Brown


>  as far as I know, the following code would look for a
> node named "name" and, if matched, output the containing elements:
> ...
> <xsl:template match="name">
>  <b>Name found.</b>
>  <xsl:apply-templates/>
> </xsl:template>
	

No, the "looking" is something that happens elsewhere. It does not happen by virtue of there being a template that matches "name" elements. This template just says what to do *if* *a* name element happens to be encountered in the course of processing.

<xsl:apply-templates/> says "go process the nodes that are children of the current node". For each given node, the best matching template is found and its instructions executed.

Processing begins at the root node.

There are built-in templates for each type of node. There are others, but mainly you need to know about these:

For root and element nodes: go process children.
For text nodes: copy node to result tree.

20.

Interpretation of absolute location path

Mike Kay

It's a common misunderstanding, exacerbated by the terminology of the spec, that "/doc/x" is an "absolute" location path (or "global" in your words). It isn't: it's relative to the root of the tree that contains the context node.

21.

Apply-templates, or handling mixed content

Michael Kay


> <xsl:apply-templates select="foo"/>

> ...XSLT seeks out and does all the <foo> tags
> and inflicts the foo template to them. I get that.

> But when parsing an XHTML <p> tag, it may
> have text mish-mashed in and between <b> and <i>
> and <span> tags...in no fixed order.

> I will lose all the style stuff if I just do...

> <xsl:template match="p">
>    <xsl:value-of select="."/>
> </xsl:template>

> ...as the <b>, <i> and <span> will go bye-bye with
> all their text siphoned out from them, yes? I don't
> want that, obviously.

The standard design pattern is to define a template rule for each element, and in each such template rule, call apply-templates to process the children. That way you do a depth-first traversal of the entire tree, processing each element as it is encountered. If the source format is similar to the result format, you define an identity template rule as the default rule, so things just get copied across.

For example

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

processes the <bold> element, replacing it with a <b> element, and calling apply-templates to process all its children (text nodes and element nodes) with their appropriate rules.

You don't ever need to use xsl:value-of, and normally you shouldn't use it except when processing a node that you know has no children.