2

XML structure:

<units>
    <unit>
        <lesson>
            <name>Sample 1</name>
            <sections>
                <section type="IN">
                    <title> Sample Title 1 </title>
                </section>
            </sections>
        </lesson>
        <lesson>
            <name>Sample 2</name>
            <sections>
                <section type="OF">
                    <title> Sample Title 2 </title>
                </section>
            </sections>
        </lesson>
        <lesson>
            <name>Sample 3</name>
            <sections>
                <section type="IN">
                    <title> Sample Title 3</title>
                </section>
            </sections>
        </lesson>
        <lesson>
            <name>Sample 4</name>
            <sections>
                <section type="AS">
                    <title> Sample Title 4</title>
                </section>
            </sections>
        </lesson>
        <lesson>
            <name>Sample 5</name>
            <sections>
                <section type="IN">
                    <title> Sample Title 5</title>
                </section>
            </sections>
        </lesson>
    </unit>
</units>

My requirement is to get the values of title element and display as follows (Grouping similar data and display)

IN:
Sample Title 1
Sample Title 3
Sample Title 5
OF:
Sample Title 2
AS:
Sample Title 5

I have used following-sibling option to get the expected output. Since the XML structure is huge(I have pasted only the snippet), I cannot hard-code the path using ../../ and all in XSLT. Please help me in getting the expected output.

dirin
  • 351
  • 1
  • 3
  • 3
  • Grouping using "the following-sibling option" is O(N^2) in time complexity -- this is *especially slow* for large XML documents as in your case. Learn, understand and use the more efficient Muenchian method for grouping. Read more about it here: http://www.jenitennison.com/xslt/grouping/muenchian.html – Dimitre Novatchev Apr 27 '11 at 02:58

2 Answers2

3

It's better to solve this using grouping than either of the sibling axes:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" />
    <xsl:key name="bySectionType" match="section" use="@type" />
    <xsl:template match="/">
        <xsl:apply-templates select="units/unit/lesson/sections/section" />
    </xsl:template>
    <xsl:template match="section" />
    <xsl:template
        match="section[generate-id()=
                       generate-id(key('bySectionType', @type)[1])]">
        <xsl:value-of select="concat(@type, ':&#xA;')" />
        <xsl:apply-templates select="key('bySectionType', @type)" mode="out" />
    </xsl:template>
    <xsl:template match="section" mode="out">
        <xsl:value-of select="concat(normalize-space(title), '&#xA;')" />
    </xsl:template>
</xsl:stylesheet>

Output:

IN:
Sample Title 1
Sample Title 3
Sample Title 5
OF:
Sample Title 2
AS:
Sample Title 4

For completeness, the following stylesheet achieves the same result using the preceding and following axes:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" />
    <xsl:template match="/">
        <xsl:apply-templates select="units/unit/lesson/sections/section" />
    </xsl:template>
    <xsl:template match="section" />
    <xsl:template 
        match="section[not(preceding::section[@type=current()/@type])]">
        <xsl:value-of select="concat(@type, ':&#xA;')" />
        <xsl:apply-templates select=".|following::section[@type=current()/@type]"
            mode="out" />
    </xsl:template>
    <xsl:template match="section" mode="out">
        <xsl:value-of select="concat(normalize-space(title), '&#xA;')" />
    </xsl:template>
</xsl:stylesheet>

This is a far less efficient solution. The normal way to solve this is with the Muenchian Method for grouping, as shown above.

Wayne
  • 59,728
  • 15
  • 131
  • 126
  • @lwburk - Can't we achieve this result using XPATH Axes? – dirin Apr 26 '11 at 19:00
  • @dirin - Yes, but the trick then is figuring out when you're at the first node that has a given type, so that you can output the header for that node only. This requires you to examine each nodes siblings, which is less efficient. (And your case is a little different because of the nesting. You'd probably use `preceding` (not `preceding-sibling`).) – Wayne Apr 26 '11 at 19:04
  • @lwburk - As I have mentioned earlier, the code snippet that is pasted in the earlier post is part of the original XML file. I tried to incorporate your code in my XSLT file. And there are so many other templates which is overriding this and I am not getting the expected output. Do you have any other option other than this? Please let me know. – dirin Apr 26 '11 at 19:15
  • @lwburk - Let me try the solution you have provided using preceding and get back to you. – dirin Apr 26 '11 at 19:16
  • @dirin - We can only provide desired output on the given input. Any additional requirements are hidden from us and can't be followed. – Wayne Apr 26 '11 at 19:17
  • @lwburk - Is it possible to enforce the sequence in the output? Eventhough the XML contain the section type in the order of IN,OF and AS, the output should always generate in the following sequence - OF,AS and IN. Is this possible? – dirin Apr 26 '11 at 21:35
  • 1
    @dirin: You could isolate @lwburk's answer with modes (even with modes under some namespace!). An specific order such as OF, AS and IN would mean that you know those keys in advance. Then you don't even need grouping! –  Apr 26 '11 at 22:21
  • @Alejandro - Are you talking about the display of output in the sequence. If so, can you please provide XSLT code to do the same? – dirin Apr 27 '11 at 00:07
  • 1
    @dirin: Please, ask your new questions as ... NEW QUESTIONS. You got an excellent answer to your question -- accept it, then ask another question. @lwburk, anybody else, or even @Michael-Kay himself cannot substitute the proper dose of initial learning you must undertake -- in this respect XSLT is not different from any other programming language. – Dimitre Novatchev Apr 27 '11 at 02:52
1

Here is the solution in XSLT 2.0:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" />
    <xsl:template match="/">
        <xsl:for-each-group select="//section" group-by="@type">
           <xsl:value-of select="@type, ':&#xa;'" separator=""/>
           <xsl:value-of select="current-group()/title" separator="&#xA;" />
           <xsl:value-of select="'&#xa;'"/>
        </xsl:for-each-group>              
    </xsl:template>
</xsl:stylesheet>
Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • We are actually using XSLT 1.0. The sequence mentioned here is not about ascending or descending order. I would require the ouput to be displayed in OF,AS and IN(XML can contain these information in any order) – dirin Apr 27 '11 at 00:08