XSLT embedding (positional grouping key?) problem. newbie

I

I.M. Postor

Hello,

Maybe this is a case of positional grouping, but my level of xslt has me
stuck here.

I have some XML where some elements haven't the proper name (level2
instead of level3) and aren't properly embedded. A level2 element should
always be embedded in a level1 element. a level3 element should always
be embedded in a level2 element. An element <levelX type="single">
cannot embed other levelX elements. The amount of elements <levelX
type="single"> _to_be_embedded_ depends on the value of the attribute
"embed" above.

Input:

<?xml version="1.0" ?>
<root>
<level1 type="other"><name>A</name>
<level2 type="group" embed="4"><name>B</name>
<level3 type="single"><name>C</name>
</level3>
</level2> <!-- level2 type="group" is closed here; too soon-->

<level2 type="single"><name>D</name>
</level2>
<level2 type="single"><name>E</name>
</level2>
<level2 type="single"><name>F</name>
</level2>
<!-- level2 type="group" closing tag should be here -->
<level2 type="single"><name>G</name>
</level2>
<level2 type="single"><name>H</name>
</level2>
<level2 type="single"><name>I</name>
</level2>
<!-- ...-->
</level1>
</root>




So in this example the element with name C is already properly embedded,
but not D, E, F.


I have this stylesheet:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" >
<xsl:eek:utput method="xml" indent="yes" />
<xsl:template match="*/comment()" />


<!-- identity template -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>


<xsl:template match="/root/level1/level2[@type='group']">
<xsl:variable name="volumes" select="@embed" as="xs:integer" />
<xsl:variable name="already_embedded"
select="count(child::level3)" as="xs:integer" />
<xsl:variable name="to_embed"
select="$volumes - $already_embedded" as="xs:integer" />

<xsl:element name="level2">
<xsl:copy-of select="@*" />
<xsl:copy-of select="node()" />

<xsl:for-each
select="following-sibling::level2[position() &lt;= $to_embed]">
<xsl:element name="level3X"> <!-- X just as a marker -->
<xsl:copy-of select="@*" />
<xsl:copy-of select="node()" />
</xsl:element>
</xsl:for-each>

</xsl:element>
</xsl:template>

</xsl:stylesheet>



which produces:

<?xml version="1.0" encoding="UTF-8"?>
<root>
<level1 type="other">
<name>A</name>
<level2 type="group" embed="4">
<name>B</name>
<level3 type="single">
<name>C</name>
</level3>
<level3X type="single">
<name>D</name>
</level3X>
<level3X type="single">
<name>E</name>
</level3X>
<level3X type="single">
<name>F</name>
</level3X>
</level2>

<level2 type="single">
<name>D</name>
</level2>
<level2 type="single">
<name>E</name>
</level2>
<level2 type="single">
<name>F</name>
</level2>

<level2 type="single">
<name>G</name>
</level2>
<level2 type="single">
<name>H</name>
</level2>
<level2 type="single">
<name>I</name>
</level2>

</level1>
</root>



where of course D-F are doubled, which is not what I want.
I think I might have to do something clever with keys and generate-id() like:

<xsl:template match="/root/level1/level2[@type='single']">
<xsl:if generate-id(.) != generate-id(key('???', ???))
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>


But, given that this is the right approach, I have no idea how to declare a key
in the toplevel (directly under xsl:stylesheet), give it a value under the
for-each loop _and_ get to that value in the level2[@type='single'] template.

Any help is appreciated,


Cheers
 
P

p.lepin

I.M. Postor said:
I have some XML where some elements haven't the proper
name (level2 instead of level3) and aren't properly
embedded. A level2 element should always be embedded in a
level1 element. a level3 element should always be
embedded in a level2 element. An element <levelX
type="single"> cannot embed other levelX elements. The
amount of elements <levelX type="single">
_to_be_embedded_ depends on the value of the attribute
"embed" above.

<?xml version="1.0"?>
<root>
<level1 type="other"><name>A</name>
<level2 type="group" embed="4"><name>B</name>
<level3 type="single"><name>C</name>
</level3>
</level2>
<!-- level2 type="group" is closed here; too soon-->
<level2 type="single"><name>D</name>
</level2>
<level2 type="single"><name>E</name>
</level2>
<level2 type="single"><name>F</name>
</level2>
<!-- level2 type="group" closing tag should be here -->
<level2 type="single"><name>G</name>
</level2>
<level2 type="single"><name>H</name>
</level2>
<level2 type="single"><name>I</name>
</level2>
<!-- ...-->
</level1>
</root>

So in this example the element with name C is already
properly embedded, but not D, E, F.

I have this stylesheet:
[XSLT]

which produces:

[result with copied instead of moved nodes]
where of course D-F are doubled, which is not what I
want.

The problem, of course, is that you're using the identity
transformation, but fail to exclude the nodes you're trying
to move.

The following works on your sample XML, although you might
need to tweak it if you intend to use it:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput
method="xml"
version="1.0"
encoding="iso-8859-1"/>
<xsl:template match="*/comment()"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template
match="*[@type = 'group']">
<xsl:variable name="volumes" select="@embed"/>
<xsl:variable
name="already_embedded"
select="count(child::*[@type = 'single'])"/>
<xsl:variable
name="to_embed"
select="$volumes - $already_embedded"/>
<xsl:element name="{name()}">
<xsl:copy-of select="@*"/>
<xsl:copy-of select="node()"/>
<xsl:apply-templates
select="
following-sibling::*[@type = 'single']
[
position() &lt;= $to_embed
]"
mode="move"/>
</xsl:element>
</xsl:template>
<!-- exclude the nodes that we've moved -->
<xsl:template
match="
*
[
(
preceding-sibling::*[@type = 'group'][1]/@embed
)&gt;
count
(
preceding-sibling::*[@type = 'group'][1]/
*[@type = 'single']|
preceding-sibling::*[@type = 'single']
[
generate-id
(
preceding-sibling::*[@type='group'][1]
) =
generate-id
(
current()/
preceding-sibling::*[@type='group'][1]
)
]
)
]"/>
<xsl:template match="level2" mode="move">
<xsl:element name="level3">
<xsl:attribute name="moved">yes</xsl:attribute>
<xsl:copy-of select="@*"/>
<xsl:copy-of select="node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
 
I

I.M. Postor

Hello Pavel

The following works on your sample XML, although you might
need to tweak it if you intend to use it:

'Off the shelf', I think is the expression: worked immediately. Thanks
much. I'm still studying on the exclusion template though.


Cheers
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top