0

So I have a XML document generated by my application like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE AddressBook>
<?xml-stylesheet type="text/xsl" href="stylesheet.xsl"?>
<AddressBook>
 <Item>
  <UserGeneratedElementName1 class="info">Whatever blah blah</UserGeneratedElementName1>
  <UserGeneratedElementName2 class="info">Whatever blah blah</UserGeneratedElementName2>
 </Item>
 <Item>
  <UserGeneratedElementName3 class="info">Whatever blah blah</UserGeneratedElementName3>
 </Item>
 ...
 ...
 Other Items with user-generated elements with user-generated content...
</AddressBook>

And I want to turn it into a HTML document similar to this:

<html>
<head>
 <title>AddressBook</title>
</head>
<body>
 <div class="root">
  <div class="item">
   <b>UserGeneratedElementName1:</b> Whatever blah blah
   <b>UserGeneratedElementName2:</b> Whatever blah blah
  </div>
  <div class="item">
   <b>UserGeneratedElementName3:</b> Whatever blah blah
  </div>
  ...
  ...
  Other transformed items...
 </div>
</body>
</html>

I have tried to get a grasp of the XSLT syntax, but all the guides were either too vague to help me with this or too deep. Also XSLT syntax seems quite confusing. Thanks in advance.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
FNj
  • 33
  • 5
  • "all the guides were either too vague to help me with this or too deep" You probably have missed W3C or google or search in SO. – Emiliano Poggi May 27 '11 at 19:27

2 Answers2

1

Take a look at this question here

Is there an XSLT name-of element?

You can use

<xsl:value-of select ="name(.)"/>

or

<xsl:value-of select ="local-name()"/>

to get the name of a node, depending on if you want to include the full prefixed name, or just the local portion.

You should be able to piece those together with xsl:for-each blocks to iterate through the first 3 levels of items and generate the HTML you're looking for.

Something like this would work for a fixed number of levels.

    <xsl:for-each select="*">
        <html>
            <head>
                <title><xsl:value-of select="local-name()" /></title>
            </head>
            <body>
                <div class="root">
                    <xsl:for-each select="*">
                        <div>
                            <xsl:attribute name="class">
                                <xsl:value-of select="local-name()" />
                            </xsl:attribute>
                            <xsl:for-each select="*">
                                <b><xsl:value-of select="local-name()" />:</b> <xsl:value-of select="." />
                            </xsl:for-each>
                        </div>
                    </xsl:for-each>
                </div>
            </body>
        </html>
    </xsl:for-each>

A more generic approach would look something more like:

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

<xsl:output method="html" indent="yes" version="4.0"/>
<xsl:template match="/">
    <xsl:for-each select="*">
        <html>
            <head>
                <title><xsl:value-of select="local-name()" /></title>
            </head>
            <body>
                <div class="root">
                        <xsl:call-template name="recurseElement">
                            <xsl:with-param name="element" select="." />
                        </xsl:call-template>
                </div>
            </body>
        </html>
    </xsl:for-each>
</xsl:template>
<xsl:template name="recurseElement">
    <xsl:param name="element" />
    <xsl:for-each select="$element/*">
        <xsl:choose>
                <xsl:when test="count(child::*)>0">
                    <div>
                        <xsl:attribute name="class">
                            <xsl:value-of select="local-name()" />
                        </xsl:attribute>
                        <xsl:call-template name="recurseElement">
                            <xsl:with-param name="element" select="." />
                        </xsl:call-template>
                    </div>
                </xsl:when>
                <xsl:otherwise>
                    <b><xsl:value-of select="local-name()" />:</b> <xsl:value-of select="." />
                </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Community
  • 1
  • 1
tschaible
  • 7,635
  • 1
  • 31
  • 34
  • Excuse my ignorance, but wouldn't the code create more than one HTML documents or more precisely wouldn't it create more than one html element in the html document? Which would, of course, make it invalid as a html document may only have one html element. – FNj May 27 '11 at 20:05
  • The bottom xslt would only produce multiple HTML docs if the source XML document had multiple root nodes (which would be invalid XML). In this case, the first loop could have been replaced with 'xsl:for-each select="/"' if that makes more sense. The reason I was handling the root different specifically and wrapping it in HTML is because the example showed the name of the root element being used as the HTML doc's , so I was trying to reproduce that. Without a more detailed description, that was my best guess on the expected output. – tschaible May 27 '11 at 22:13
0

This complete XSLT transformation:

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

 <xsl:template match="AddressBook">
  <html>
    <head>
     <title>AddressBook</title>
    </head>
    <body>
      <div class="root">
       <xsl:apply-templates/>
      </div>
    </body>
  </html>
 </xsl:template>

 <xsl:template match="Item">
  <div class="item"><xsl:apply-templates/></div>
 </xsl:template>

 <xsl:template match="Item/*">
  <b><xsl:value-of select="name()"/>:</b> <xsl:text/>
  <xsl:value-of select="concat(.,'&#xA;            ')"/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<AddressBook>
    <Item>
        <UserGeneratedElementName1 class="info">Whatever blah blah</UserGeneratedElementName1>
        <UserGeneratedElementName2 class="info">Whatever blah blah</UserGeneratedElementName2>
    </Item>
    <Item>
        <UserGeneratedElementName3 class="info">Whatever blah blah</UserGeneratedElementName3>
    </Item> ... ... Other Items with user-generated elements with user-generated content...
</AddressBook>

produces the wanted, correct result:

<html>
   <head>
      <title>AddressBook</title>
   </head>
   <body>
      <div class="root">
         <div class="item">
            <b>UserGeneratedElementName1:</b>Whatever blah blah
            <b>UserGeneratedElementName2:</b>Whatever blah blah
            </div>
         <div class="item">
            <b>UserGeneratedElementName3:</b>Whatever blah blah
            </div> ... ... Other Items with user-generated elements with user-generated content...
</div>
   </body>
</html>

Explanation:

  1. Templates matching any Item element and any element child of an Item element.

  2. Use of the standard XPath name() function.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • What exactly does this line do? `` – FNj May 28 '11 at 08:01
  • @FNj: xsl:value-of select="concat(.,' ')"/> outputs the concatenation of the steing value of the current node (that is matched by the template) with the new-line character (it has the code of 10 or hexadecimal A. You can use a *character reference* to output any character by its code -- in the form `xxx;` (this is in decimal) or `ꯍ` (this is in hexadecimal). – Dimitre Novatchev May 28 '11 at 13:59