Output all same-name children of a node

S

Stan

Forgive a newbie, please:

I've got XML like this:
<body>
<block>
<p>content of p1</p>
<p>content of p2</p>
<p>content of p3</p>
...
<p>content of pN</p>
</block>
</body>

How can I write an XSLT to spit out the contents of every <p> element,
separated by <p> tags?

This generates the entire contents of the <block> element, of course,
but all in a blob:
<xsl:for-each select="body">
<xsl:value-of select="block">
</xsl:value-of>
</xsl:for-each>

And this generates the contents of the first <p> element followed by a
<p> tag, but not the remaining <p> elements (again, of course):
<xsl:for-each select="nitf/body/body.content/block">
<xsl:value-of select="p"></xsl:value-of>
<p></p>
</xsl:for-each>

I'm sure I'm missing something very simple ...
 
J

Joseph Kesselman

Stan said:
How can I write an XSLT to spit out the contents of every <p> element,
separated by <p> tags?

What you've already got is, in fact, more correct HTML than what you're
trying to produce, since <p> means "start a paragraph". End-paragraph
(</p>) tags also exist in HTML and really should be used, but people
have gotten sloppy abou that since HTML processors will try to guess
where they should have been and recreate them.

If you really insist on doing what you've asked for, it's possible. What
you want to do is rip the content out of every <p> element and replace
it with the content preceeded by (or followed by> a <p> element. Your
second attempt is close, but value-of returns only the value of the
*first* node matching the selection. If you want to process them all,
you need another for-each... or, better, you need to replace your
for-eaches with apply-template calls and let XSLT's normal recursive
processing do the work for you.

For example, I'd solve this with:
<xsl:template match="p">
<xsl:apply-templates/>
<p/>
</xsl:template>

which is a template that matches <p> elements and replaces them with
their content followed by an empty paragraph, sloppy-HTML style. Note
that because this uses apply-templates to retrieve its content, rather
than value-of, it leaves you free to plug in templates to process other
markup that might be present within the paragraph's content.

Whether that's the best solution will depend on what the rest of your
stylesheet looks like and whether you care about the extra <p> at the
end (which can be suppressed with some additional coding).


Learn to think in terms of apply-templates rather than explicit for-each
loops, at least as your first solution. Use for-each only if you really
need a special-case "anonymous local template".
 
P

Pavel Lepin

Joseph said:
Stan said:
How can I write an XSLT to spit out the contents of every
<p> element, separated by <p> tags?
[solution]

which is a template that matches <p> elements and replaces
them with their content followed by an empty paragraph,
sloppy-HTML style.

Note that as far as I can tell the HTML serializer is quite
likely to spit out '<p></p>' instead of just '<p>' for the
result tree like that, since p element's content model is
not empty.

Anyway, it just doesn't make much sense to produce sloppy
HTML (that, depending on context, may be outright invalid),
when it's so easy to do the Right Thing with XSLT.
Use for-each only if you really need a special-case
"anonymous local template".

In seculum seculorum, amen.

The biggest mystery of XSLT for me is why the omniscient
alpha geeks who wrote the specs called the darned
thing 'for-each'. They probably understood what it really
was far better than I do, after all.
 
S

Stan

What you've already got is, in fact, more correct HTML than what you're
trying to produce

I see that my example wasn't clear. I'm not trying to produce empty
paragraphs; I'm trying to put the contents of each <p> node in the XML
inside a <p> tag. I actually expected that my first block of code
would work; I expected the <p> nodes in the <block> node to render the
same way <p> HTML tags render, but they don't. This code:
<xsl:for-each select="body">
<xsl:value-of select="block">
</xsl:value-of>
</xsl:for-each>
produces this:
content of p1content of p2content of p3...content of pN
when I want this:
p>content of p1</p>
<p>content of p2</p>
<p>content of p3</p>
...
<p>content of pN</p>

BUT, this:
better, you need to replace your
for-eaches with apply-template calls and let XSLT's normal recursive
processing do the work for you.

For example, I'd solve this with:
<xsl:template match="p">
<xsl:apply-templates/>
<p/>
</xsl:template>

was an ah-hah moment for me. Works perfectly, and advanced my (so far
very limited) understanding considerably. Thank you very much!!
-Stan
 
R

roy axenov

I see that my example wasn't clear. I'm not trying to
produce empty paragraphs; I'm trying to put the contents
of each <p> node in the XML inside a <p> tag.

Forget the tags. There are no tags, only nodes. Once you
see the nodes, you see the matrix. But I think I understand
what you're getting at.
BUT, this:


was an ah-hah moment for me. Works perfectly, and
advanced my (so far very limited) understanding
considerably.

And now consider this:

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

Unless I'm much mistaken, this should work even better for
you, and should be even more of an eye-opener. (It also was
what Joseph meant when he said your XML was closer to valid
HTML than what you seemed to want as an output of your
transformation.)
 

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,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top