tags:

views:

35

answers:

2

Continuing my previous question.

XML

<albums>
    <album title="new_zealand">
        <album title="auckland">
            <image title="mt_eden_railroad_station"/>
        </album>
    </album>
</albums>

Expected output

<div>
    <a href="#new_zealand">new_zealand</a>
</div>

<div id="new_zealand">
    <a href="#new_zealand/auckland">auckland</a>
</div>

<div id="new_zealand/auckland">
    <img id="new_zealand/auckland/mt_eden_railroad_station"/>
</div>

XSL, that doesn't work

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="html"/>

    <xsl:template match="/">
        <xsl:apply-templates select="/albums | //album"/>
    </xsl:template>

    <xsl:template match="albums | album">
        <div>
            <xsl:attribute name="id">
                <!--  I need to insert '/' between all parents titles and concat them. -->
            </xsl:attribute>
            <xsl:apply-templates select="album/@title | image"/>
        </div>
    </xsl:template>

    <xsl:template match="album/@title">
        <a href="{concat('#', .)}">
            <xsl:value-of select="."/>
        </a>
    </xsl:template>

    <xsl:template match="image">
        <img id="{@title}"/>
    </xsl:template>

</xsl:stylesheet>
+1  A: 

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:strip-space elements="*"/>

 <xsl:template match="/*">
  <xsl:apply-templates/>
 </xsl:template>

 <xsl:template match="album">
  <xsl:param name="pLastPath"/>

  <xsl:variable name="vPath">
   <xsl:for-each select="ancestor-or-self::album">
     <xsl:value-of select="@title"/>
     <xsl:if test="not(position()=last())">/</xsl:if>
   </xsl:for-each>
  </xsl:variable>

  <div>
   <xsl:if test="$pLastPath">
    <xsl:attribute name="id"><xsl:value-of select="$pLastPath"/></xsl:attribute>
   </xsl:if>
    <a href="#{$vPath}"><xsl:value-of select="@title"/></a>
  </div>
     <xsl:apply-templates select="node()">
       <xsl:with-param name="pLastPath" select="$vPath"/>
     </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="image">
  <xsl:param name="pLastPath"/>
   <div id="{$pLastPath}">
     <img id="{$pLastPath}/{@title}"/>
   </div>
     <xsl:apply-templates select="node()">
       <xsl:with-param name="pLastPath" select="$pLastPath"/>
     </xsl:apply-templates>
 </xsl:template>
</xsl:stylesheet>

when applied to the provided XML document:

<albums>
    <album title="new_zealand">
        <album title="auckland">
            <image title="mt_eden_railroad_station"/>
        </album>
    </album>
</albums>

produces the wanted, correct result:

<div>
   <a href="#new_zealand">new_zealand</a>
</div>
<div id="new_zealand">
   <a href="#new_zealand/auckland">auckland</a>
</div>
<div id="new_zealand/auckland">
   <img id="new_zealand/auckland/mt_eden_railroad_station"/>
</div>

UPDATE: @Tomalak has raised concern that slashes are invalid in HTML id attribute values.

Please, feel free to substitute the "/" character in this solution with whatever valid character (such as "_") you want.

Dimitre Novatchev
Slashes are invalid in HTML IDs according to the spec.
Tomalak
@Tomalak: I am not regarding this as an HTML question but as a pure XSLT question. The OP can use whatever he wants in the string and that is not the essence of this solution. :)
Dimitre Novatchev
`/` is a valid id in HTML5 and, more importantly, it works well in all browsers. Can't find it in the spec right now, but http://validator.w3.org/ allows it.
NV
@Tomalak: Please, see the update of my answer.
Dimitre Novatchev
+3  A: 

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="albums">
        <div>
            <xsl:apply-templates mode="output"/>
        </div>
        <xsl:apply-templates/>
    </xsl:template>
    <xsl:template match="album">
        <xsl:param name="pPath"/>
        <xsl:variable name="vNextPath" select="concat($pPath,@title,'/')"/>
        <div id="{$pPath}{@title}">
            <xsl:apply-templates mode="output">
                <xsl:with-param name="pPath" select="$vNextPath"/>
            </xsl:apply-templates>
        </div>
        <xsl:apply-templates>
            <xsl:with-param name="pPath" select="$vNextPath"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="image" mode="output">
        <xsl:param name="pPath"/>
        <img id="{$pPath}{@title}"/>
    </xsl:template>
    <xsl:template match="album" mode="output">
        <xsl:param name="pPath"/>
        <a href="#{$pPath}{@title}">
            <xsl:value-of select="@title"/>
        </a>
    </xsl:template>
</xsl:stylesheet>

Output:

<div>
   <a href="#new_zealand">new_zealand</a>
</div>
<div id="new_zealand">
   <a href="#new_zealand/auckland">auckland</a>
</div>
<div id="new_zealand/auckland">
   <img id="new_zealand/auckland/mt_eden_railroad_station" />
</div>

Edit: Just for fun, other stylesheet with same logic for albums ans album

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="albums|album">
        <xsl:param name="pPath"/>
        <xsl:variable name="vPath" select="concat($pPath,@title)"/>
        <xsl:variable name="vNextPath"
             select="concat($vPath,substring('/',1 div boolean($vPath)))"/>
        <div>
            <xsl:if test="$vPath">
                <xsl:attribute name="id">
                    <xsl:value-of select="$vPath"/>
                </xsl:attribute>
            </xsl:if>
            <xsl:apply-templates mode="output">
                <xsl:with-param name="pPath" select="$vNextPath"/>
            </xsl:apply-templates>
        </div>
        <xsl:apply-templates>
            <xsl:with-param name="pPath" select="$vNextPath"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="image" mode="output">
        <xsl:param name="pPath"/>
        <img id="{$pPath}{@title}"/>
    </xsl:template>
    <xsl:template match="album" mode="output">
        <xsl:param name="pPath"/>
        <a href="#{$pPath}{@title}">
            <xsl:value-of select="@title"/>
        </a>
    </xsl:template>
</xsl:stylesheet>
Alejandro
Nice and compact. +1
Tomalak