building XML where elements can contain themselves
Here should go questions about transforming XML with XSLT and FOP.
-
- Posts: 17
- Joined: Tue Feb 05, 2008 11:14 pm
building XML where elements can contain themselves
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:
The result after processing would look like this:
Any suggestions?
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>
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>
-
- Posts: 89
- Joined: Mon Mar 06, 2006 10:13 pm
Re: building XML where elements can contain themselves
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>
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>
-
- Posts: 17
- Joined: Tue Feb 05, 2008 11:14 pm
Re: building XML where elements can contain themselves
I mostly follow how this works, but not enough to figure out why it leaves out stuff when I change the content to:
Any suggestions?
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?
-
- Posts: 17
- Joined: Tue Feb 05, 2008 11:14 pm
Re: building XML where elements can contain themselves
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:
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.
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>
-
- Posts: 89
- Joined: Mon Mar 06, 2006 10:13 pm
Re: building XML where elements can contain themselves
I realize now I made a couple mistakes, particularly in the first apply-templates choice in the default template.
Revised code:
I'm not sure I understand where you want to put rules. How would it look in your source xml?
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>
Last edited by jkmyoung on Thu Feb 28, 2008 8:09 pm, edited 1 time in total.
-
- Posts: 17
- Joined: Tue Feb 05, 2008 11:14 pm
Re: building XML where elements can contain themselves
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.
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.
Jump to
- Oxygen XML Editor/Author/Developer
- ↳ Feature Request
- ↳ Common Problems
- ↳ DITA (Editing and Publishing DITA Content)
- ↳ SDK-API, Frameworks - Document Types
- ↳ DocBook
- ↳ TEI
- ↳ XHTML
- ↳ Other Issues
- Oxygen XML Web Author
- ↳ Feature Request
- ↳ Common Problems
- Oxygen Content Fusion
- ↳ Feature Request
- ↳ Common Problems
- Oxygen JSON Editor
- ↳ Feature Request
- ↳ Common Problems
- Oxygen PDF Chemistry
- ↳ Feature Request
- ↳ Common Problems
- Oxygen Feedback
- ↳ Feature Request
- ↳ Common Problems
- Oxygen XML WebHelp
- ↳ Feature Request
- ↳ Common Problems
- XML
- ↳ General XML Questions
- ↳ XSLT and FOP
- ↳ XML Schemas
- ↳ XQuery
- NVDL
- ↳ General NVDL Issues
- ↳ oNVDL Related Issues
- XML Services Market
- ↳ Offer a Service