Charles Said
Charles Said

Reputation: 25

XSLT for-each loop where each node is a different element

I am building a blogging website for a school project. A blog post's content consists of a number of elements, each of which can either be plain text, links, images, or videos. All these elements must be shown in the order which they are stored with in the XML file, and each one will be displayed in a new <p> tag.

Hence, the idea is to somehow loop all these elements, and each iteration should perform an `xsl:choose to decide how to display the current iteration's element.

Take this code for example: how can I make it perform only the TEXT part when the element is a b:blogPostContent/b:blogPostText type, and the LINK part when the element is a b:blogPostContent/b:blogPostLink?

<xsl:for-each select="b:blogPostContent/*">
    <p>

        <!--TEXT-->
        <xsl:value-of select="."/>

        <!--LINK-->
        <xsl:element name="a">
            <xsl:attribute name="href">
                <xsl:value-of select="./@target"/>
            </xsl:attribute>
            <xsl:attribute name="target">
                _blank
            </xsl:attribute>
            <xsl:value-of select="."/>
        </xsl:element>

    </p>
</xsl:for-each> 

Upvotes: 2

Views: 3901

Answers (2)

Tomalak
Tomalak

Reputation: 338108

Hence, the idea is to somehow loop all these elements, and each iteration should perform an xsl:choose to decide how to display the current iteration's element.

The idea is not bad, but driven by an imperative thought pattern. XSLT is not an imperative language and most of the time it's better to follow declarative patterns.

<xsl:template match="b:blogPostContent">
  <article>
    <xsl:apply-templates select="*" mode="wrap_p" />
  </article>
</xsl:template>

<xsl:template select="*" mode="wrap_p">
  <p>
    <xsl:apply-templates select="." />
  </p>
</xsl:template>

<xsl:template match="b:blogPostText">
  <xsl:value-of select="." />
</xsl:template>

<xsl:template match="b:blogPostLink">
  <a href="{@target}" target="_blank">
    <xsl:value-of select="."/>
  </a>
</xsl:template>

<xsl:template match="b:blogPostList">
  <ul>
    <xsl:apply-templates select="*" />
  </ul>
</xsl:template>

and so on.

This way you get small, dedicated, modular templates that do one thing and one thing only, while the XSLT engine decides which template serves best for which input node.


Note that you never have to write <xsl:element> or <xsl:attribute> unless you want to create them dynamically (names based on variables, for example). If you want an <a> in the output, just put an <a> in the XSLT.

Upvotes: 1

Mathias M&#252;ller
Mathias M&#252;ller

Reputation: 22617

Use a choose element like this:

<xsl:choose>
  <xsl:when test="name() = 'b:blogPostText'">
    <xsl:value-of select="."/>
  </xsl:when>
  <xsl:otherwise>
    <xsl:element name="a">
        <xsl:attribute name="href">
            <xsl:value-of select="./@target"/>
        </xsl:attribute>
        <xsl:attribute name="target">_blank</xsl:attribute>
        <xsl:value-of select="."/>
    </xsl:element>
  </xsl:otherwise>
</xsl:choose>

Another possiblity is to match those elements in separate templates. Let's say your template matches the b:blogPostContent element (actually, you did not show the structure of your input XML):

<xsl:template match="b:blogPostContent">
   <p>
     <xsl:apply-templates select="b:blogPostText|b:blogPostLink"/>
   </p> 
</xsl:template>

<xsl:template match="b:blogPostText">
  <xsl:value-of select="."/>
</xsl:template>

<xsl:template match="b:blogPostLink">
  <a>
     <xsl:attribute name="href">
         <xsl:value-of select="./@target"/>
     </xsl:attribute>
     <xsl:attribute name="target">_blank</xsl:attribute>
     <xsl:value-of select="."/>
  </a>
</xsl:template>

Note that <xsl:element name="a"> is exactly the same as <a>. That is why I have shortened the notation. (It was inconsistent anyway, since you did not write <xsl:element name="p">).

Upvotes: 1

Related Questions