5

I have a flat structured XML file as below:

<rs>
    <r id="r1" lev="0"/>
    <r id="r2" lev="1"/>
    <r id="r3" lev="0"/>
    <r id="r4" lev="1"/>
    <r id="r5" lev="2"/>
    <r id="r6" lev="3"/>
    <r id="r7" lev="0"/>
    <r id="r8" lev="1"/>
    <r id="r9" lev="2"/>
</rs>

which I need to transform to a nested one. Rule is something, all r[number(@lev) gt 0] should be nested within r[number(@lev) eq 0]. And the output would be something like that:

<rs>
    <r id="r1">
        <r id="r2"/>
    </r>
    <r id="r3">
        <r id="r4">
            <r id="r5">
                <r id="r6"/>
            </r>
        </r>
    </r>
    <r id="r7">
        <r id="r8">
            <r id="r9"/>
        </r>
    </r>
</rs>

What I have tried is the following transformation:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">

    <xsl:output indent="yes"/>

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

    <xsl:template match="r">
        <xsl:variable name="lev" select="number(@lev)" as="xs:double"/>
        <r>
            <xsl:copy-of select="@id"/>
            <xsl:apply-templates select="following-sibling::r[not(number(@lev) eq $lev)
                                         and 
                                         count(preceding-sibling::r[number(@lev) eq $lev]) eq 1]"/>
        </r>
    </xsl:template>

</xsl:stylesheet>

But, this does not gives me the desired result. Pointing out my coding error or any other approach to get job done, is greatly appreciated.

Cylian
  • 10,970
  • 4
  • 42
  • 55

3 Answers3

3

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kRByLevelAndParent" match="r"
  use="concat(generate-id(preceding-sibling::r
                            [not(@lev >= current()/@lev)][1]),
                          @lev
                          )"/>

 <xsl:template match="/*">
  <rs>
    <xsl:apply-templates select="key('kRByLevelAndParent', '0')"/>
  </rs>
 </xsl:template>

 <xsl:template match="r">
  <r id="{@id}">
    <xsl:apply-templates select=
    "key('kRByLevelAndParent',
         concat(generate-id(), @lev+1)
         )"/>
  </r>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<rs>
    <r id="r1" lev="0"/>
    <r id="r2" lev="1"/>
    <r id="r3" lev="0"/>
    <r id="r4" lev="1"/>
    <r id="r5" lev="2"/>
    <r id="r6" lev="3"/>
    <r id="r7" lev="0"/>
    <r id="r8" lev="1"/>
    <r id="r9" lev="2"/>
</rs>

produces the wanted, correct result:

<rs>
   <r id="r1">
      <r id="r2"/>
   </r>
   <r id="r3">
      <r id="r4">
         <r id="r5">
            <r id="r6"/>
         </r>
      </r>
   </r>
   <r id="r7">
      <r id="r8">
         <r id="r9"/>
      </r>
   </r>
</rs>

Explanation:

Positional grouping using a composite key -- for all its "children" an element is the first preceding sibling such that its lev attribute is less than their respective lev attribute.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • One more question, as it is not possible to use ``xsl:key`` within a ``variable``, is there any other way to do the job without using a ``xsl:key``? – Cylian Jun 20 '12 at 05:45
  • @Cylian, theoretically any problem solved with a key can also be solved without a key - buy maybe just not as efficiently. It is like recursion in a Java or Delphi program - any algorithm that uses recursion can be converted to a non-recursive equivalent using a custom stack or techniques like 'Taylor' recursion. I can't imagine why would want a non xsl:key version, but if you really do, then post it as a new question. I am sure you will get a rapid answer from Dimitre or one of the other regular answerers. – Sean B. Durkin Jun 20 '12 at 07:08
  • 1
    @Dimitre, notice that the OP's style-sheet is marked as 2.0. He did not use the 2.0 tag, but the OP supplied style-sheet is probably a better indicator than the tag(s). – Sean B. Durkin Jun 20 '12 at 08:27
  • @SeanB.Durkin: Do note that Cylian's question isn't tagged "xslt-2.0". Usually an XSLT 1.0 solution is also an XSLT 2.0 solution, and nothing prevents anyone from running this solution with an XSLT 2.0 processor, or changing the `version` attribute from `1.0` to `2.0`. I answered this last night just before going to bed -- of course I know that an XSLT 2.0 solution exists and that usually XSLT 2.0 solutions are easier to write -- I simply didn't have time to also give an XSLT 2.0 solution. – Dimitre Novatchev Jun 20 '12 at 11:57
  • @Cylian: See my reply to Sean's comment. As for not-using a key, such a solution might be possible (or even in your situation a key-based solution might still be possible). You need to edit the question and provide the needed additional information, about which we only get a hint of from your comment. – Dimitre Novatchev Jun 20 '12 at 12:01
  • I agreed Sir, it was my mistake! And in my question I never mentioned that also! Your solution is *Brilliant*, but for me, being a amateur in this field, hard to grasp the concept, while Dr. Kay showed me a way and I find my solution. – Cylian Jun 20 '12 at 12:34
2

Dimitre tends to give answers to questions using XSLT 1.0 unless otherwise requested. That may be a correct guess, but I think it's worth pointing out that XSLT 2.0 is now quite widely available and used, and that the code for grouping problems in XSLT 2.0 is much simpler (it may not always be much shorter, but it is much more readable). Unlike Dimitre, I don't have the time or inclination to give beautiful complete and tested solutions to every question, but if you want to see an XSLT 2.0 solution to this problem there is one in a paper I wrote some years ago here:

http://www.saxonica.com/papers/ideadb-1.1/mhk-paper.xml

Search for the recursive template name="process-level".

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • 1
    Michael Kay: I admit to everything you say about my answers and take full responsibility for this :) – Dimitre Novatchev Jun 20 '12 at 14:33
  • 1
    I just wish XSLT 2 was as widely used as it was available. None of the major browsers, or the .NET framework support it yet, without third party libraries. – Flynn1179 Jun 20 '12 at 21:59
  • 1
    What's so bad about third-party libraries? Most programming languages come from someone other than the platform vendor. XSLT 2.0 is now available both on .NET and on the browser from third parties, so you don't have to wait any longer. – Michael Kay Jun 21 '12 at 10:42
  • @Dr. Kay: It was really glad to know that there are some third-party support exists for some browsers. Would you please, Dr. Kay point me to some of those references. – Cylian Jun 22 '12 at 04:09
0

As I need to apply the transformation within temporary variables, using xsl:key would not help. And if I have to use Dimitre's solution I had to change my existing code.

And obviously it was my mistake that I have not describe much in this regard in my question.

From the link at //programlisting[contains(.,'xsl:template name="process-level"')] provided by Dr. Kay I have concluded the solution, may be some other person could be use it later:

The stylesheet

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs">
    <xsl:output indent="yes"/>

    <xsl:template match="/*">
        <rs>
            <xsl:call-template name="process-level">
                <xsl:with-param name="context" 
                    select="r"/>
                <xsl:with-param name="level" 
                    select="0"/>
            </xsl:call-template>
        </rs>
    </xsl:template>

    <xsl:template name="process-level">
        <xsl:param name="context" required="yes" as="element()*"/>
        <xsl:param name="level" as="xs:double"/>
        <xsl:for-each-group select="$context"
            group-starting-with="*[number(@lev) eq $level]">
            <xsl:element name="{name()}">
                <!--<xsl:variable name="position" as="xs:double">
                    <xsl:number level="any" count="*[starts-with(local-name(), 'r')]"/>
                </xsl:variable>-->
                <xsl:copy-of select="@id"/>
                <xsl:call-template name="process-level">
                    <xsl:with-param name="context" select="current-group()[position() != 1]"/>
                    <xsl:with-param name="level" select="$level + 1"/>
                </xsl:call-template>
            </xsl:element>
        </xsl:for-each-group>
    </xsl:template>

</xsl:stylesheet>

The input XML

<rs>
    <r id="r1" lev="0"/>
    <r id="r2" lev="1"/>
    <r id="r3" lev="0"/>
    <r id="r4" lev="1"/>
    <r id="r5" lev="2"/>
    <r id="r6" lev="3"/>
    <r id="r7" lev="0"/>
    <r id="r8" lev="1"/>
    <r id="r9" lev="2"/>
</rs>

And the result

<rs>
   <r id="r1">
      <r id="r2"/>
   </r>
   <r id="r3">
      <r id="r4">
         <r id="r5">
            <r id="r6"/>
         </r>
      </r>
   </r>
   <r id="r7">
      <r id="r8">
         <r id="r9"/>
      </r>
   </r>
</rs>
Cylian
  • 10,970
  • 4
  • 42
  • 55