Combining XML elements with XSL

M

mikea_59

I am having trouble combining similar elements. Elements can be
combined if they have the same name and the same attribute values. I
can handle single level elements but am having problems with
multi-level elments (level of element nesting is unbound). I cannot
rely on fixed element names in the translator code since the translator
will be general purpose, elements names are not fixed.

Example input

<test>
<A x="0">
<B>1</B>
</A>
<A x="0">
<C>2</C>
</A>
<A x="0">
<D x="0">
<E>3</E>
</D>
</A>
<A x="0">
<D x="0">
<E>3</E>
</D>
</A>
<A x="0">
<D x="1">
<E>5</E>
</D>
</A>
<A x="1">
<B>4</B>
</A>
</test>

example output:

<test>
<A x="0">
<B>1</B>
<C>2</C>
<D x="0">
<E>3</E>
</D>
<D x="1">
<E>5</E>
</D>
</A>
<A x="1">
<B>4</B>
</A>
</test>

Any help would be greatly appreciated - still an XSL newbie
 
J

Joris Gillis

I am having trouble combining similar elements. Elements can be
combined if they have the same name and the same attribute values. I
can handle single level elements but am having problems with
multi-level elments (level of element nesting is unbound). I cannot
rely on fixed element names in the translator code since the translator
will be general purpose, elements names are not fixed.
Hi,

I'm afraid you'll have to use an 'xx:node-set' extension function to solve
this (in XSLT1.0).

Here's one solution (tested with Saxon), gives correct output for your
input.


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0" xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">

<xsl:eek:utput method="xml" indent="yes"/>

<xsl:strip-space elements="*"/>
<xsl:key name="element" match="*" use="concat(local-name(),@*)"/>

<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:variable name="set">
<xsl:copy-of select="key('element',concat(local-name(),@*))/*"/>
</xsl:variable>
<xsl:apply-templates select="text()"/>
<xsl:apply-templates
select="exsl:node-set($set)/*[generate-id()=generate-id(key('element',concat(local-name(),@*)))]"/>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>

regards,
Joris Gillis
 
M

mikea_59

That worked - thanks a lot! What a great NG.

So, is this strictly a 1.0 solution? Doesn't 2.0 eliminate the need
to use node-set() by enabling temporary templates? Is node-set() even
available in 2.0 - in Saxon? How would I change this for 2.0?

Sorry for all the ???

Thanks again
mikea
 
J

Joris Gillis

So, is this strictly a 1.0 solution?
Everything that can be achieved in XSLT1.0 certainly can in XSLT2.0, isn't
that logic;)
Doesn't 2.0 eliminate the need
to use node-set() by enabling temporary templates?
Excerpt form the latest XSLT2.0 working draft:
"The result tree fragment data-type is eliminated. A variable-binding
element with content (and no as attribute) now constructs a temporary
tree, and the value of the variable is the root node of this tree (see 9.3
Values of Variables and Parameters). With an as attribute, a
variable-binding element may be used to construct an arbitrary sequence.
These features eliminate the need for the xx:node-set extension function
provided by many XSLT 1.0 implementations."
available in 2.0 - in Saxon? How would I change this for 2.0?

I don't have experience with 2.0, but I guess writing '$set' in stead of
'exsl:node-set($set)' will do...


regards,
Joris Gillis
 
M

mikea_59

Joris said:
Everything that can be achieved in XSLT1.0 certainly can in XSLT2.0, isn't
that logic;)

Yes, very logical, but when I set stylesheet to version=2.0 and
re-run the translation in Saxon, I get an error:

"A sequence of more than one item is not allowed as the first
argument of generate-id()"

I guess it's not related to node-set()
Excerpt form the latest XSLT2.0 working draft:
"The result tree fragment data-type is eliminated. A variable-binding
 
J

Joris Gillis

"A sequence of more than one item is not allowed as the first
argument of generate-id()"

Really? Then they have changed the behaviour of generate-id().

Solution:
change
<xsl:apply-templates
select="exsl:node-set($set)/*[generate-id()=generate-id(key('element',concat(local-name(),@*)))]"/>
into
<xsl:apply-templates
select="exsl:node-set($set)/*[generate-id()=generate-id(key('element',concat(local-name(),@*))[1])]"/>
 
D

David Carlisle

Really? Then they have changed the behaviour of generate-id().

it's a more or less pervasive change, as part of the stricter tying
introduced in 2 most instances of functions that expect a single node
complain if given more than one node rather than silently taking the
first in doc order.

David
 
M

mikea_59

It looks like that did the trick, thanks a lot. I'm still not sure I
follow the syntax of the select attribute, I guess I'll go back to my
Kay book...

Thanks again,
mikea
 
J

Joris Gillis

Tempore 23:15:45 said:
It looks like that did the trick, thanks a lot. I'm still not sure I
follow the syntax of the select attribute

you mean this line?

<xsl:apply-templates select="$set/*[generate-id()=generate-id(key('element',concat(local-
name(),@*))[1])]"/>

well, the trick is to let the xslt processor create a key lookup table based on the node-set in the variable in stead of a lookup table indexing all nodes in the input XML document.

The 'generate-id()' + 'key()' stuff is just basic Muenchian grouping technique...
 

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

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,822
Latest member
israfaceZa

Latest Threads

Top