building XML where elements can contain themselves

Here should go questions about transforming XML with XSLT and FOP.
tsh138
Posts: 17
Joined: Tue Feb 05, 2008 11:14 pm

building XML where elements can contain themselves

Post by tsh138 »

I'm trying to figure out a way to generate XML where the elements can contain themselves. The input data is a set of nodes where some have a value to signify the start or the end of a group. The node signifying the START is handled different than the following nodes. The node set being worked with would look something like:

Code: Select all

<item>
<name>item1</name>
<value>1</value>
</item>
<item>
<name>item2</name>
<value>2</value>
</item>
<item subgroup="START">
<name>subgroup1</name>
<description>subgroup1 description</description>
</item>
<item>
<name>item4</name>
<value>4</value>
</item>
<item subgroup="START">
<name>subgroup2</name>
<description>subgroup2 description</description>
</item>
<item subgroup="END">
<name>item6</name>
<value>6</value>
</item>
<item subgroup="END">
<name>item7</name>
<value>7</value>
</item>
<item>
<name>item8</name>
<value>8</value>
</item>
<item subgroup="START">
<name>subgroup3</name>
<description>subgroup3 description</description>
</item>
<item subgroup="END">
<name>item10</name>
<value>10</value>
</item>
The result after processing would look like this:

Code: Select all

<group>
<Value>
<name>item1</name>
<value>1</value>
</Value>
<Value>
<name>item2</name>
<value>2</value>
</Value>
<group>
<name>subgroup1</name>
<description>subgroup1 description</description>
<Value>
<name>item4</name>
<value>4</value>
</Value>
<group>
<name>subgroup2</name>
<description>subgroup2 description</description>
<Value>
<name>item6</name>
<value>6</value>
</Value>
</group>
<Value>
<name>item7</name>
<value>7</value>
</Value>
</group>
<Value>
<name>item8</name>
<value>8</value>
</Value>
<group>
<name>subgroup3</name>
<description>subgroup3 description</description>
<Value>
<name>item10</name>
<value>10</value>
</Value>
</group>
</group>
Any suggestions?
jkmyoung
Posts: 89
Joined: Mon Mar 06, 2006 10:13 pm

Re: building XML where elements can contain themselves

Post by jkmyoung »

Use a key to reference the previous item.

Making this work for nested groups was a little of a hassle. Reply back if you have any questions.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="prevItem" match="item" use="generate-id(preceding-sibling::item[@subgroup][1])"/>
<xsl:template match="/*">
<group>
<xsl:apply-templates select="item[count(preceding-sibling::item[@subgroup='START']) = count(preceding-sibling::item[@subgroup='END'])]"/>
</group>
</xsl:template>
<xsl:template match="item[@subgroup='START']">
<xsl:param name="depth" select="0"/>
<xsl:choose>
<xsl:when test="$depth = 0">
<group>
<xsl:copy-of select="name"/>
<xsl:copy-of select="description"/>
<xsl:apply-templates select="key('prevItem', generate-id())"/>
<xsl:apply-templates select="key('prevItem', generate-id())[@subgroup='START']">
<!-- deal with sublist -->
<xsl:with-param name="depth" select="1"/>
</xsl:apply-templates>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="key('prevItem', generate-id())[@subgroup='START']">
<!-- further sublist -->
<xsl:with-param name="depth" select="$depth + 1"/>
</xsl:apply-templates>
<xsl:for-each select="key('prevItem', generate-id())[@subgroup='END']">
<!-- end of sublist? -->
<xsl:apply-templates select="key('prevItem', generate-id())">
<xsl:with-param name="depth" select="$depth - 1"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="item">
<xsl:param name="depth" select="0"/>
<xsl:if test="$depth = 0">
<xsl:if test="name = 'item6'"><xsl:value-of select="generate-id(preceding-sibling::item[@subgroup][1])"/></xsl:if>
<Value>
<xsl:copy-of select="name"/>
<xsl:copy-of select="value"/>
</Value>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
tsh138
Posts: 17
Joined: Tue Feb 05, 2008 11:14 pm

Re: building XML where elements can contain themselves

Post by tsh138 »

I mostly follow how this works, but not enough to figure out why it leaves out stuff when I change the content to:

Code: Select all


<?xml version="1.0" encoding="UTF-8"?>
<items>
<item subgroup="START">
<name>primary grouping</name>
<description>primary grouping description</description>
</item>
<item>
<name>item1</name>
<value>1</value>
</item>
<item>
<name>item2</name>
<value>2</value>
</item>
<item subgroup="START">
<name>subgroup1</name>
<description>subgroup1 description</description>
</item>
<item>
<name>item4</name>
<value>4</value>
</item>
<item subgroup="START">
<name>subgroup2</name>
<description>subgroup2 description</description>
</item>
<item subgroup="END">
<name>item6</name>
<value>6</value>
</item>
<item subgroup="END">
<name>item7</name>
<value>7</value>
</item>
<item>
<name>item8</name>
<value>8</value>
</item>
<item subgroup="START">
<name>subgroup3</name>
<description>subgroup3 description</description>
</item>
<item subgroup="END">
<name>item10</name>
<value>10</value>
</item>
<item subgroup="END">
<name>item11</name>
<value>11</value>
</item>
</items>


Any suggestions?
tsh138
Posts: 17
Joined: Tue Feb 05, 2008 11:14 pm

Re: building XML where elements can contain themselves

Post by tsh138 »

Another thing I'm trying to do is use this to generate output with a specific order to the elements. Basically, take the previous output example and for each item create a rule. The on constraint is that inside each group the values must come before the rules, for example:

Code: Select all


<group>
<Value>
<name>item1</name>
<value>1</value>
</Value>
<Value>
<name>item2</name>
<value>2</value>
</Value>
<group>
<name>subgroup1</name>
<description>subgroup1 description</description>
<Value>
<name>item4</name>
<value>4</value>
</Value>
<group>
<name>subgroup2</name>
<description>subgroup2 description</description>
<Value>
<name>item6</name>
<value>6</value>
</Value>
<Rule>
<name>item6</name>
<value>6</value>
</Rule>
</group>
<Value>
<name>item7</name>
<value>7</value>
</Value>
<Rule>
<name>item7</name>
<value>7</value>
</Rule>
<Rule>
<name>item4</name>
<value>4</value>
</Rule>
</group>
<Value>
<name>item8</name>
<value>8</value>
</Value>
<group>
<name>subgroup3</name>
<description>subgroup3 description</description>
<Value>
<name>item10</name>
<value>10</value>
</Value>
<Rule>
<name>item10</name>
<value>10</value>
</Rule>
</group>
<Rule>
<name>item8</name>
<value>8</value>
</Rule>
<Rule>
<name>item1</name>
<value>1</value>
</Rule>
<Rule>
<name>item2</name>
<value>2</value>
</Rule>
</group>
In this example I placed the rules following any subgroups, but they can be placed prior to any subgroup. Any suggestions on how I might tweak your snippet to do this? I'm working in XSLT 2.0 so if there is an easy way to do it in that let me know.
jkmyoung
Posts: 89
Joined: Mon Mar 06, 2006 10:13 pm

Re: building XML where elements can contain themselves

Post by jkmyoung »

I realize now I made a couple mistakes, particularly in the first apply-templates choice in the default template.
Revised code:

Code: Select all

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="prevItem" match="item" use="generate-id(preceding-sibling::item[@subgroup][1])"/>
<xsl:template match="/*">
<group>
<xsl:apply-templates select="item[not(preceding-sibling::item[@subgroup='START'])]"/>
</group>
</xsl:template>
<xsl:template match="item[@subgroup='START']">
<xsl:param name="depth" select="0"/>
<xsl:choose>
<xsl:when test="$depth = 0">
<group>
<xsl:copy-of select="name"/>
<xsl:copy-of select="description"/>
<xsl:apply-templates select="key('prevItem', generate-id())"/>
<xsl:apply-templates select="key('prevItem', generate-id())[@subgroup]">
<!-- deal with sublist -->
<xsl:with-param name="depth" select="1"/>
</xsl:apply-templates>
</group>
</xsl:when>
<xsl:otherwise>
<!-- dealing with sublist -->
<xsl:apply-templates select="key('prevItem', generate-id())[@subgroup]">
<xsl:with-param name="depth" select="$depth + 1"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="item[@subgroup='END']">
<xsl:param name="depth" select="0"/>
<xsl:choose>
<xsl:when test="$depth=0">
<Value>
<xsl:copy-of select="name"/>
<xsl:copy-of select="value"/>
</Value>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="key('prevItem', generate-id())">
<xsl:with-param name="depth" select="$depth - 1"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="item">
<xsl:param name="depth" select="0"/>
<xsl:if test="$depth = 0">
<Value>
<xsl:copy-of select="name"/>
<xsl:copy-of select="value"/>
</Value>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I'm not sure I understand where you want to put rules. How would it look in your source xml?
Last edited by jkmyoung on Thu Feb 28, 2008 8:09 pm, edited 1 time in total.
tsh138
Posts: 17
Joined: Tue Feb 05, 2008 11:14 pm

Re: building XML where elements can contain themselves

Post by tsh138 »

Thanks for the help...

The source XML would be the same. The only change is in the output. The only requirement is that a rule exists for each item and that inside of each group all items preceed all rules. Within the items/rules sets there is no order requirement. So items could be in the order item4-item1-item3-item2 and the rules could be rule1-rule3-rule2-rule4 and groups can occur anywhere. Hope this helps.

One solution is to generate the rule as part of the item generation, storing the result of the first apply-templates in a variable, and then sorting the contents of that variable so that for each group the items come before the rules. The problem with this is that it uses to many resources. I'm hoping for some direction on a better solution.
tsh138
Posts: 17
Joined: Tue Feb 05, 2008 11:14 pm

Re: building XML where elements can contain themselves

Post by tsh138 »

:|
Post Reply