Altova Mailing List Archives>Archive Index >microsoft.public.xsl Archive Home >Recent entries >Thread Prev - Converting a very flat simple xml to hierarchy xml >Thread Next - Re: Converting a very flat simple xml to hierarchy xml Re: Converting a very flat simple xml to hierarchy xmlTo: NULL Date: 5/2/2007 8:09:00 PM
Hi Craig,
It's quite a common requirement [ I don't know who all these people are that
keep producing flat XML - but I wish they'd stop it! :) ]
You need to look for following-siblings at the start of each hierarchy - but
then filter those following siblings down to only those where the immediate
preceding sibling (e.g. preceding-sibling::*[1] - because 1 in a reverse
axis is the closest) with the correct name is the element that started this
branch.
Assuming that your elements aren't really called <a>, <b> and <c> and so
on - and that you've given that merely as an example of your real data. And
assuming that the text nodes in your example are there only for
demonstration purposes - rather than providing any assistance in the 'quest'
Let's try making it static-data driven - so all you have to modify is the
static data to make it work with your real-life XML. Perhaps something
like...
== XSL1 ======================================
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:staticdata="urn:my-static-data">
<xsl:output method="xml" indent="yes"/>
<staticdata:hierarchy>
<element name="a"/>
<element name="b"/>
<element name="c"/>
<element name="d"/>
</staticdata:hierarchy>
<xsl:variable name="gvHierarchy"
select="document('')/*/staticdata:hierarchy/element/@name"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="*[local-name() = $gvHierarchy[1]]"
mode="hierarchic"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="hierarchic">
<xsl:param name="pHierarchyLevel" select="1"/>
<xsl:variable name="vThisElementID" select="generate-id()"/>
<xsl:copy>
<xsl:copy-of select="@* | text()"/>
<xsl:apply-templates select="following-sibling::*[local-name() =
$gvHierarchy[number($pHierarchyLevel +
1)]][generate-id(preceding-sibling::*[local-name() =
$gvHierarchy[number($pHierarchyLevel)]][1]) = $vThisElementID]"
mode="hierarchic">
<xsl:with-param name="pHierarchyLevel" select="$pHierarchyLevel + 1"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
=============================================
Hmmm, say then that we didn't know what element names were going to be
thrown at us - we'd need to build that list of names dynamically. Something
like...
== XSL2 ======================================
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" exclude-result-prefixes="msxsl"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kDistinctRootChildElementNames" match="/*/*"
use="local-name()"/>
<xsl:variable name="rtf_Hierarchy">
<xsl:for-each select="/*/*[generate-id() =
generate-id(key('kDistinctRootChildElementNames',local-name()))]">
<element name="{local-name()}"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="gvHierarchy"
select="msxsl:node-set($rtf_Hierarchy)/element/@name"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="*[local-name() = $gvHierarchy[1]]"
mode="hierarchic"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="hierarchic">
<xsl:param name="pHierarchyLevel" select="1"/>
<xsl:variable name="vThisElementID" select="generate-id()"/>
<xsl:copy>
<xsl:copy-of select="@* | text()"/>
<xsl:apply-templates select="following-sibling::*[local-name() =
$gvHierarchy[number($pHierarchyLevel +
1)]][generate-id(preceding-sibling::*[local-name() =
$gvHierarchy[number($pHierarchyLevel)]][1]) = $vThisElementID]"
mode="hierarchic">
<xsl:with-param name="pHierarchyLevel" select="$pHierarchyLevel + 1"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
=============================================
Then, perhaps to get really fancy - we could provide a mechanism that would
allow us to override the behaviour for certain conditions. Maybe...
== XSL2 ======================================
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" exclude-result-prefixes="msxsl"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kDistinctRootChildElementNames" match="/*/*"
use="local-name()"/>
<xsl:variable name="rtf_Hierarchy">
<xsl:for-each select="/*/*[generate-id() =
generate-id(key('kDistinctRootChildElementNames',local-name()))]">
<element name="{local-name()}"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="gvHierarchy"
select="msxsl:node-set($rtf_Hierarchy)/element/@name"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="*[local-name() = $gvHierarchy[1]]"
mode="hierarchic"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="hierarchic">
<xsl:param name="pHierarchyLevel" select="1"/>
<xsl:variable name="vThisElementID" select="generate-id()"/>
<xsl:variable name="vChildren" select="following-sibling::*[local-name() =
$gvHierarchy[number($pHierarchyLevel +
1)]][generate-id(preceding-sibling::*[local-name() =
$gvHierarchy[number($pHierarchyLevel)]][1]) = $vThisElementID]"/>
<xsl:apply-templates select="." mode="process">
<xsl:with-param name="pChildren" select="$vChildren"/>
<xsl:with-param name="pHierarchyLevel" select="$pHierarchyLevel"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" mode="process">
<xsl:param name="pChildren"/>
<xsl:param name="pHierarchyLevel"/>
<xsl:choose>
<xsl:when test="$pHierarchyLevel = count($gvHierarchy)">
<leaf type="{local-name()}" value="{text()}"/>
</xsl:when>
<xsl:otherwise>
<folder type="{local-name()}" value="{text()}">
<xsl:apply-templates select="$pChildren" mode="hierarchic">
<xsl:with-param name="pHierarchyLevel" select="$pHierarchyLevel + 1"/>
</xsl:apply-templates>
</folder>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- do something special for any <a> elements we come across -->
<xsl:template match="a" mode="process">
<xsl:param name="pChildren"/>
<xsl:param name="pHierarchyLevel"/>
<the_big_cahunA value="{text()}">
<xsl:apply-templates select="$pChildren" mode="hierarchic">
<xsl:with-param name="pHierarchyLevel" select="$pHierarchyLevel + 1"/>
</xsl:apply-templates>
</the_big_cahunA>
</xsl:template>
</xsl:stylesheet>
=============================================
All of these will strip any comments or processing instructions that may be
present in the input. If you wanted to preserve those you'd have a little
additional work to do.
HTH
M
<craigg75@g...> wrote in message
news:1178079533.186121.232530@y......
> Been trying for a long time to make this work. Here's my starting xml:
>
> <root>
> <a>top 1</a>
> <b>b 110</b>
> <b>b 111</b>
> <b>b 112</b>
> <c>c 11</c>
> <b>b 120</b>
> <b>b 121</b>
> <c>c 12</c>
> <b>b 130</b>
> <b>b 131</b>
> <b>b 132</b>
> <b>b 133</b>
> <c>c 13</c>
> <b>b 140</b>
> <c>c 14</c>
> <a>top 2</a>
> <b>b 210</b>
> <b>b 211</b>
> <b>b 212</b>
> <c>c 21</c>
> <b>b 220</b>
> <b>b 221</b>
> <c>c 22</c>
> </root>
>
> And here is the xml I want to end up:
>
> <root>
> <a>top 1
> <c>c 11
> <b>b 110</b>
> <b>b 111</b>
> <b>b 112</b>
> </c>
> <c>c 12
> <b>b 120</b>
> <b>b 121</b>
> </c>
> <c>c 13
> <b>b 130</b>
> <b>b 131</b>
> <b>b 132</b>
> <b>b 133</b>
> </c>
> <c>c 14
> <b>b 140</b>
> </c>
> </a>
> <a>top 2
> <c>c 21
> <b>b 210</b>
> <b>b 211</b>
> <b>b 212</b>
> </c>
> <c>c 22
> <b>b 220</b>
> <b>b 221</b>
> </c>
> </a>
> </root>
>
> I've tried a lot of things. It's hard to move the c element up to be a
> parent of b.
>
> Thanks if anyone can figure it out :)
>
> Craig
>
| ||||||
| Company | Legal | Press | Partners | Careers | Sitemap | Contact Us | Altova Blog | Mobile | Full Site | |||
|
