By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
459,994 Members | 1,345 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 459,994 IT Pros & Developers. It's quick & easy.

How to create sections from a linear structure with title nodes?

P: n/a
Hi there,

Maybe this is a faq, if so, point me the URL please.

I have to do some xml to xml transformations in xslt, where I have -
simplified - the following:

input:
<doc>
<a/><a/><b/><a><a><a><b/><a><b/><a><a>
</doc>

output:
<doc>
<section><x/><x/></section>
<section><y/><x/><x/><x/></section>
<section><y/><x/></section>
<section><y/><x/><x/>
</doc>

e.g. I want to transform <a/> to <x/> and <b/> to <y/>, but start a
new section at each <b/>

For Example, you could think of a Text with headlines and paragraphs
and I want to start a new Page at each Headline.

Now there is the question, how to do this in xslt. Some Idea was the
following:

<section>
<xsl:apply-templates select="All a-nodes before the first b-node"/>
</section>
<xsl:for-each select="b">
<section>
<xsl:apply-templates select="b"/>
<xsl:apply-templates select="all following a-nodes up to the next
b-node"/>
</section>
</xsl:for-each>

My Problem are now the two xpath-Expressions "All a-nodes before the
first b-node" and "all following a-nodes up to the next b". Googling
around I did not find any useful tips on this, so perhaps someone here
has an Idea?

MfG,

--
Erhard Schwenk
Jul 20 '05 #1
Share this Question
Share on Google+
3 Replies


P: n/a
Erhard

you're going about the problem the wrong way. your XML is essentially
document-oriented rather than strongly structured so you should process it
by matching templates. something like:

<xsl:template match="a">
<x><xsl:apply-templates/></x>
</xsl:template>

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

don't think of XSL as a programming language - it's a declarative way of
specifying how you want the XML transformed. I suggest you read a good book
like Jeni Tennison's 'beginning xslt' which will explain it all in a
sensible order and show you the various techniques.

Andy

"Erhard Schwenk" <es******@fto.de> wrote in message
news:ed**************************@posting.google.c om...
Hi there,

Maybe this is a faq, if so, point me the URL please.

I have to do some xml to xml transformations in xslt, where I have -
simplified - the following:

input:
<doc>
<a/><a/><b/><a><a><a><b/><a><b/><a><a>
</doc>

output:
<doc>
<section><x/><x/></section>
<section><y/><x/><x/><x/></section>
<section><y/><x/></section>
<section><y/><x/><x/>
</doc>

e.g. I want to transform <a/> to <x/> and <b/> to <y/>, but start a
new section at each <b/>

For Example, you could think of a Text with headlines and paragraphs
and I want to start a new Page at each Headline.

Now there is the question, how to do this in xslt. Some Idea was the
following:

<section>
<xsl:apply-templates select="All a-nodes before the first b-node"/>
</section>
<xsl:for-each select="b">
<section>
<xsl:apply-templates select="b"/>
<xsl:apply-templates select="all following a-nodes up to the next
b-node"/>
</section>
</xsl:for-each>

My Problem are now the two xpath-Expressions "All a-nodes before the
first b-node" and "all following a-nodes up to the next b". Googling
around I did not find any useful tips on this, so perhaps someone here
has an Idea?

MfG,

--
Erhard Schwenk

Jul 20 '05 #2

P: n/a
Andy Fish wrote:

The problem is that 'context' that you need to use to generate the sections
depends on nodes that have already been processed, not on ancestor nodes of
your current node. If you were to write this program in a 'proper'
programming language you would basically have a state machine with a
variable saying whether you are already in a section or not, and this is
exactly what XSL (being a functional language) is bad at.

I think you might be able to do something with following-sibling and
preceding-sibling but I've racked my brains and I can't figure out how you'd
apply it

Hmm meanwhile I got some solution like this:

<xsl:template match="/">
<section>
<xsl:apply-templates
select="*[(count(preceding-sibling::a)) = 0 and name != 'a']"/>
</section>

<xsl:for-each select="a">
<xsl:variable name="number" select="1+count(preceding-sibling::a"/>

<section>
<x/>
<xsl:apply-templates
select="../*[count(preceding-sibling::a) = $number
and name != 'a'"
/>
</section>
</xsl:for-each>
</xsl:template>

<xsl:template match="b">
<y/>
</xsl:template>
Seems to work, maybe there is something better. Thanks for your hints
anyway.

BTW: if there is someone who maintains a faq with XSL Snippets, I think
this one would be a great addition. Feel free to copy.


--
Erhard Schwenk

Akkordeonjugend Baden-Württemberg - http://www.akkordeonjugend.de
K-ITX Webhosting - http://webhosting.k-itx.net

Jul 20 '05 #3

P: n/a
In article <ed**************************@posting.google.com >,
Erhard Schwenk <es******@fto.de> wrote:
Maybe this is a faq, if so, point me the URL please.
It's certainly very similar to the question posted here by Marc
Baumgartner (mo***@gmx.de) in June.
input:
<doc>
<a/><a/><b/><a><a><a><b/><a><b/><a><a>
</doc>

output:
<doc>
<section><x/><x/></section>
<section><y/><x/><x/><x/></section>
<section><y/><x/></section>
<section><y/><x/><x/>
</doc>

e.g. I want to transform <a/> to <x/> and <b/> to <y/>, but start a
new section at each <b/>


Here's a general approach to this kind of problem.

Construct an XPath that will identify one member of each group. In
this case, the first <a> is the obvious thing to choose (if there was
always a <b> in the group, the <b> would be a better choice). We can
identify the first <a> in each group as being an <a> whose immediate
preceding-sibling is not an <a>:

a[local-name(preceding-sibling::*[1]) != 'a']

(If there is an <a> at the start without a preceding b, this will
still work because local-name retrns an exmpty string for an empty
node-set.)

Call apply-templates on that path. Use a mode, for reasons that will
be apparent later:

<xsl:template match="doc">
<doc>
<xsl:apply-templates mode="group"
select="a[local-name(preceding-sibling::*[1]) != 'a']"/>
</doc>
</xsl:template>

In the template for the selected representative, output the group
wrapper.

<xsl:template match="a" mode="group">
<section>
...
</section>
</xsl:template>

Now construct an XPath that will select all the elements in the same
group as the representative.

The XPath to select the group members will use something they have in
common with the representative element. Thinking up a test for this
is often the tricky bit. In this case, we want the <b> before the
representative <a> (if there is one) and the <a>s that are following
siblings of the representative <a> without any intervening <b>s. We
can do it by selecting the siblings that have the same number of
following-sibling <b>s as the representative <a>:

../*[count(following-sibling::b) = count(current()/following-sibling::b)]

Note the use of current() to get hold of the representative <a> inside
the predicate. We could have assigned it to a variable instead, but
this is simpler.

Sometimes the obvious test involves comparing two nodes for identity.
For that you can use the trick generate-id(node1) = generate-id(node2).

Inside the wrapper, call apply-templates on the members of the group.
This is why we used a mode for the group template; we are going to
call apply-templates on the representative again in its role as a
member of the group.

<xsl:template match="a" mode="group">
<section>
<xsl:apply-templates select="../*[count(following-sibling::b) =
count(current()/following-sibling::b)]"/>
</section>
</xsl:template>

Now write templates for the group members, which is trivial in this case.

<xsl:template match="a">
<a/>
</xsl:template>

<xsl:template match="b">
<y/>
</xsl:template>

-- Richard
--
Spam filter: to mail me from a .com/.net site, put my surname in the headers.

FreeBSD rules!
Jul 20 '05 #4

This discussion thread is closed

Replies have been disabled for this discussion.