Re: [xsl] Apply-templates - how to omit top level element tags?

Subject: Re: [xsl] Apply-templates - how to omit top level element tags?
From: Jon Gorman <jonathan.gorman@xxxxxxxxx>
Date: Thu, 8 Sep 2005 09:36:28 -0500
On 9/7/05, Mike Schinkel <mikes@xxxxxxxxx> wrote:
> >> Plenty of ways to do this and it's hard to tell advise any particular
> method without knowing more about your XML.templates and your style.
> Thanks.  What do you mean by "style?"  Can you quantify this, or is it
> just a subjective thing?

To some extent it is a subjective thing.  With this problem there are
a lot of ways to do it.  I gave three possibilities if I recall
correctly, most of the others recommended the same things.  You also
seem to be talking about a couple of different problems, but all of
them fairly basic.  One is the use of the "element name" in different
contexts.  The more powerful way to deal with this is modes.  If the
structure is simple enough, you can just match further up the tree as
Michael Kay just sent.  As for the trademark, a simple match should do
the trick, something like what you had in your output below.

The problem is that you need to understand how the tree is processed,
some of the built-in rules and XPath.

There is generally two recommend "approaches" to creating stylesheets,
pull & push, although I tend to use a middle road.  Pulling relies
heavily on value-of and for-each loops, building a document by
grabbing nodes from the source xml document.  It doesn't use
apply-templates.  It is tended to be used with more data-centric
outputs where the input is pretty predictable.  I find a lot of
newbies end up using this heavily because they get confused about the
processing model and the built-in rules.

Push tends to rely on the processing model, using apply-templates.  To
process different parts of a document that might have similar elements
(imagine a metadata part of the document vs the actual document) many
use modes.  It is used primarily when working with "narrative"
documents due to their unpredictable nature.  Remember apply-templates
essentially says find any templates that match my select (or by
default the children nodes of the current node) and apply them.

WIthout knowning more about the type of documents you are processing
and how your other templates are dealing with the processing model
it's hard to give advice on the snippets.

I've seen cases where people use a purely pull model in the
<xsl:template match="/"></xsl:template>, but ask something that would
lead one to believe it was more of a push model.  When someone
suggests a template that would convert the particular element how they
want, it doesn't work because that element is never actually seen by
the processing model.

> Also, why is it not possible to be able to output:
>         FooXXX
> When you start with
>         <Name>Foo<myns:MyX></Name>
> Where the XSL has this:
>         <xsl:template match="myns:MyX">XXX</xsl:template>

It is possible to do this.  Without seeing your stylesheet I have no
clue why it doesn't work for you.  Possibilities include, but are not
limited to: namespace is not correct, so it never actually sees any of
the nodes with the myns prefix, you have a template that matches the
Name terminates the processing at that node (think base case), perhaps
you just never call that template, or even the input is literally how
you describe it and invalid XML and never even reaches the XSLT
processor and is failing at the parser (is it really <mnsy:MyX> and
not <mnsy:MyX />).  If the namespace is set up correctly (look through
the archives) you should be able to have just that template in a
stylesheet and  that with the default rules will produce fooXXX.  It
worked fine for me.

> I either get:
>         <Name>FooXXX</Name>

Ah, is this output changing every time you run the script, or did you
tinker with the stylesheet and didn't tell us ;).  Looks like you're
using copy somewhere.

> Or just:
>         Foo
Namespace issues?  Just using <xsl:value-of />?
> But not what I want, which is:
>         FooXXX
> Why do I have to write a specific template to accomplish this?

Ya don't.

>In the
> case above I want to output the value contained in <Name> that way only
> once, but I also want the write of the XML to be able to embed other
> references into it that I can parse with apply-templates.

So use the default rules.  Remember, your Name node in XPath can be
seen as a tree, rep in crude ASCII form below

Parent and current node - Name (element node):
           Child - Foo (text node)
           Child - myns:MyX (element node)

If you process the Name node and do "value-of", you're asking for the
string representation of the Name node, or the string-value as per the
XPath (this is all in the specs, I'm assuming you are using XSLT 1.0
instead of XSLT 2.0)

for element nodes

"The string-value of an element node is the concatenation of the
string-values of all text node descendants of the element node in
document order."

So it only selects the text nodes for the value.  What it really
sounds like what you want is not the value-of the Name node, but of
the text nodes and a special value for the myns:MyX, so don't try
putting out a value in the Name template.  That's my only guess at
what you did since you told us the results of your efforts but no clue
as to what you actually did to cause it.

so lets say you have something like
Parent and current node - Name (element node):
           Child - Foo (text node)
           Child - myns:MyX (element node)
           Child - bar (Text node)
ie <Name>Foo<myns:MyX />bar</Name>

If you do
<xsl:template match="Name">
<xsl:value-of select="."/>
<xsl:apply-templates />

You're going to get some funky output. Off the top of my head probably
something like
FoobarXXXFoobar (you "pull" the value, then the "push" will match both
the text nodes and the template for myns:MyX)

However, if you just use the template you gave, you'll get

just like you want.

> Having to create a template for a single case is like having to write a
> function in Java called Add37(x) simply to add 37 to the variable "x" in
> only one place in the code.  Why can't I just do it inline rather than
> have to worry about logic in two places and also worry about potential
> sideaffects or name collisions? Is it because XSL simply doesn't provide
> such a capability?

Ummm, the entire point of XSL is to be as side-effect free as
possible.  Name collision can be problematic, but no more than it
always is.  In fact, XSLT gives you lots of ways to handle name
collisions such as modes, choose blocks with XPath tests, and simply
controlling how processing matches the templates.

I'm not sure how you are even causing "side-effects".  It looks more
like you have some other templates in your stylesheet but don't know
what they are doing because you don't understand the processing model.
 This isn't really a side effect in the sense you call count(foo) and
it actually increments foo by one.

Ah, a long email, but to essentially sum it up, check for namespace
problems.  Perhaps show us some more snippets of the stylesheet.
Skim through the XPath spec so you actually understand what templates
are being matched.  It sounds like you have a identity transform
template somewhere but you don't want it.  You seem to be heading in
the right direction, but it feels like you're playing around with too
large of a stylesheet that you don't understand.  Think of this as
really, really complex induction proof.

That's probably more advice then you wanted, but again, give us some
more info, or better yet go skim through the actual specs for the
language and play around with some simpler samples.

Jon Gorman

Current Thread