Akhil Paul
Akhil Paul

Reputation: 721

How to find random elements in XML transformation using XSL

I'm facing an issue while transforming XML like I can't able to fetch the elements in their correct order. Those elements are random and can't predict the order they came.

Here is my XML

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<toc>
    <layout>
        <header>Item 1</header>
        <tocItem>item one - a</tocItem>
        <tocItem>item one - b</tocItem>
        <header>Item 2</header>
        <tocItem>item two - a</tocItem>
        <tocItem>item two - b</tocItem>
        <tocItem>item two - c</tocItem>
        <tocItem>item two - d</tocItem>
        <tocItem>item two - e</tocItem>
        <header>Item 3</header>
        <tocItem>item three - a</tocItem>
        <header>Item 4</header>
        <tocItem>item four - a</tocItem>
        <tocItem>item four - b</tocItem>
        <tocItem>item four - c</tocItem>
        <header>Item 5</header>
        <tocItem>item five - a</tocItem>
        <tocItem>item five - b</tocItem>
    </layout>
    <layout>
        <header>Item 1</header>
        <tocItem>item one - a</tocItem>
        <tocItem>item one - b</tocItem>
        <header>Item 2</header>
        <tocItem>item two - a</tocItem>  
    </layout>
    <layout>
        <header>Item 1</header>
        <tocItem>item one - a</tocItem>
        <tocItem>item one - b</tocItem>
        <tocItem>item one - c</tocItem>
        <tocItem>item one - d</tocItem>
        <tocItem>item one - e</tocItem>
        <header>Item 2</header>
        <tocItem>item two - c</tocItem>
        <tocItem>item two - d</tocItem>
        <tocItem>item two - e</tocItem>
        <header>Item 4</header>
        <tocItem>item four - a</tocItem>
        <tocItem>item four - b</tocItem>
        <header>Item 5</header>
        <tocItem>item five - a</tocItem>  
    </layout>
</toc>

And here goes XSL

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:template match="/">

            <html lang="en">
            <head>
                <meta charset="UTF-8" />
                <title>Title</title>
            </head>

            <body>
                <div class="toc">
                    <xsl:for-each select="/toc/layout">
                    <div class="layout">                    
                        <xsl:for-each select="/toc/layout/header">
                            <div class="header">
                                <p><xsl:value-of select="header" /></p>
                            </div>
                        </xsl:for-each>
                        <xsl:for-each select="/toc/layout/tocItem">
                            <div class="tocItem">
                                <p><xsl:value-of select="tocItem" /></p>
                            </div>
                        </xsl:for-each>                    
                    </div>  
                    </xsl:for-each>              
                </div>
            </body>

            </html>
    </xsl:template>
 </xsl:stylesheet>

When I tried the above method, it is just repeating the first header element and first tocItems. And I got all the elements when trying this code <xsl:value-of select="." /> inside the layout div. My goal is to fetch them as one by one order. Like below.

Items 1

item one - a

item one - b

Items 2

item two - a

item two - b

item two - c

Upvotes: 0

Views: 131

Answers (2)

Martin Honnen
Martin Honnen

Reputation: 167696

In general, if you want to just transform all those elements to HTML div with a class attribute based on the element name then one template suffices doing that:

  <xsl:template match="toc | layout | header | tocItem">
      <div class="{local-name()}">
          <xsl:apply-templates/>
      </div>
  </xsl:template>

And to preserve the input order it is best or at least easiest to simply apply-templates.

An example is

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="html" indent="no" html-version="5"/>

  <xsl:template match="/">
    <html>
      <head>
        <title>.NET XSLT Fiddle Example</title>
      </head>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="toc | layout | header | tocItem">
      <div class="{local-name()}">
          <xsl:apply-templates/>
      </div>
  </xsl:template>

</xsl:stylesheet>

That uses the XSLT 3 only declaration <xsl:mode on-no-match="shallow-copy"/> to set up the identity transformation as a default processing but in earlier versions you can simply spell that out as

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

See https://xsltfiddle.liberty-development.net/ncdD7ne to experiment with it online.

Upvotes: 1

Michael Kay
Michael Kay

Reputation: 163458

I haven't examined what you are trying to achieve but your xsl:for-each is clearly wrong. When you write <xsl:for-each select="/toc/layout">, then the context node within the for-each is a <layout> element and one would expect further selections to be relative to that element, rather than absolute paths from the root of the document. I don't understand why you have two levels of xsl:for-each in the first place.

Upvotes: 1

Related Questions