Page 1 of 1

building XML where elements can contain themselves

Posted: Mon Feb 11, 2008 8:46 pm
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?

Re: building XML where elements can contain themselves

Posted: Tue Feb 12, 2008 1:15 am
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>

Re: building XML where elements can contain themselves

Posted: Tue Feb 26, 2008 9:19 pm
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?

Re: building XML where elements can contain themselves

Posted: Tue Feb 26, 2008 11:42 pm
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.

Re: building XML where elements can contain themselves

Posted: Wed Feb 27, 2008 10:31 pm
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?

Re: building XML where elements can contain themselves

Posted: Thu Feb 28, 2008 2:12 am
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.

Re: building XML where elements can contain themselves

Posted: Thu Feb 28, 2008 3:00 am
by tsh138
:|