tags:

views:

410

answers:

2

I have a an attribute who's value may be one or more text strings all delimited by a comma. I wish to transform using XSL the attribute value(s) into their own element;

e.g

<post title='Hello World" tags="Test,Hello,World />

In which I would like it transformed to;

<post>
<title>Hello World</title>
<tag>Test</tag>
<tag>Hello</tag>
<tag>World</tag>
</post>

Is this possible? TIA

+2  A: 

There are several ways to do this.

I. Using a recursively-called named template in XSLT 1.0 This transformation:

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

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

    <xsl:template match="@*[not(name()='tags')]">
      <xsl:element name="{name()}">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:template>

    <xsl:template match="@tags">
      <xsl:call-template name="tokenize">
        <xsl:with-param name="pText" 
         select="concat(., ',')"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template name="tokenize">
      <xsl:param name="pText"/>

      <xsl:if test="string-length($pText)">
        <tag>
          <xsl:value-of select=
           "substring-before($pText, ',')"/>
        </tag>

        <xsl:call-template name="tokenize">
          <xsl:with-param name="pText" select=
           "substring-after($pText, ',')"/>
        </xsl:call-template>
      </xsl:if>
    </xsl:template>
</xsl:stylesheet>

when applied on the originally-provided XML document (corrected to be well-formed):

<post title="Hello World" 
      tags="Test,Hello,World" />

produces the required result:

<post>
   <title>Hello World</title>
   <tag>Test</tag>
   <tag>Hello</tag>
   <tag>World</tag>
</post>

II. Using the str-split-to-words template/function from FXSL 1.x

Here FXSL provides the tokenization functionality:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
>

   <xsl:import href="strSplit-to-Words.xsl"/>

   <xsl:output indent="yes" omit-xml-declaration="yes"/>

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

    <xsl:template match="@*[not(name()='tags')]">
      <xsl:element name="{name()}">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:template>

    <xsl:template match="@tags">
    <xsl:variable name="vwordNodes">
      <xsl:call-template name="str-split-to-words">
        <xsl:with-param name="pStr" select="."/>
        <xsl:with-param name="pDelimiters" 
                  select="','"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:apply-templates select="ext:node-set($vwordNodes)/*"/>
    </xsl:template>

  <xsl:template match="word">
    <tag>
      <xsl:value-of select="."/>
    </tag>
  </xsl:template>

</xsl:stylesheet>

When applied on the same XML document as before, the same correct output is produced.

III. Using the XPath 2.0 standard function tokenize() from an XSLT 2.0 transformation

This is the easiest way -- if one can use an XSLT 2.0 processor.

The following XSLT 2.0 transformation:

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

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

    <xsl:template match="@*[not(name()='tags')]">
      <xsl:element name="{name()}">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:template>

    <xsl:template match="@tags">
    <xsl:for-each select="tokenize(.,',')">
      <tag><xsl:value-of select="."/></tag>
    </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

when applied on the same XML document again produces the wanted result.

Dimitre Novatchev
Dimitre, thank you for your verbose solutions. Have run them with slight mods and it works a treat. XSL 1.0 tho.Thank You
AJ
A: 

The first thing you should do is find the person who used an attribute where he should have used elements and make him stop. The reason we accept the verbosity of XML is that it gives us the benefit of not having to figure out how to parse data. If you're going to pack your XML with data that has to be parsed, why are you using XML in the first place?

Robert Rossney
THis is not 100% correct there are the so called *list types* in XML and XML Schema (such as IDREFS), that are supposed to be space-separated list of Ids or Names. And if one uses XPath 2.0/XSLT2.0 this type of input isn't so challenging.
Dimitre Novatchev