10

We have Multimedia Component that works with external and internal images. When we have external images we have two cases:

  • External Image that has query string parametes ... ex: http://yahoo.com/someimage.jpg?param1=1&param2=2&+_+=.jpg

  • External Image that has no query string parameters ... ex: http://yahoo.com/someimage.jpg

In our component template transformation we would like to display the second type the way it is stored, and the first one we would like to be: http://yahoo.com/someimage.jpg?param1=1&param2=2

The code looks like:

<xsl:if test="document(@xlink:href)/tcm:Component/tcm:Data/tcm:IsExternalMultimediaFile = 'true' and contains(document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename, '&amp;+_+=') = 'false'">
  <xsl:text disable-output-escaping="yes"><![CDATA[<Image>]]></xsl:text>
   <xsl:text disable-output-escaping="yes"><![CDATA[<img src="]]></xsl:text>

   <xsl:value-of select="document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename" />        

  <xsl:text disable-output-escaping="yes"><![CDATA[" alt="]]></xsl:text>
  <xsl:value-of select="@xlink:title" />
  <xsl:text disable-output-escaping="yes"><![CDATA["  />]]></xsl:text>
  <xsl:text disable-output-escaping="yes"><![CDATA[</Image>]]></xsl:text>
</xsl:if>

<xsl:if test="document(@xlink:href)/tcm:Component/tcm:Data/tcm:IsExternalMultimediaFile = 'true' and contains(document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename, '&amp;+_+=') = 'true'">
  <xsl:text disable-output-escaping="yes"><![CDATA[<Image>]]></xsl:text>
   <xsl:text disable-output-escaping="yes"><![CDATA[<img src="]]></xsl:text>      

   <xsl:value-of select="substring-before(document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename, '&amp;+_+=')" />      

  <xsl:text disable-output-escaping="yes"><![CDATA[" alt="]]></xsl:text>
  <xsl:value-of select="@xlink:title" />
  <xsl:text disable-output-escaping="yes"><![CDATA["  />]]></xsl:text>
  <xsl:text disable-output-escaping="yes"><![CDATA[</Image>]]></xsl:text>
</xsl:if>

For some reason both the cases get executed. Can someone help? The solution is probably only a error in the if but I am not sure where...

3
  • What leads you to the conclusion both cases get executed? Commented Feb 27, 2013 at 15:15
  • the image appears on the page twice and this is how the transformed code look like... <Image> <img src="cdn.hw.net/UploadService/…" alt="External-img-no-ext" /> </Image> <Image> <img src="cdn.hw.net/UploadService/…" alt="External-img-no-ext" /> </Image> Commented Feb 27, 2013 at 15:25
  • Initially I tried without using the if statement but substring-before returned null if the string was not found. Commented Feb 27, 2013 at 15:28

3 Answers 3

12

The problem is indeed "an error in the if". You have used the XPath 'contains' function, which returns a boolean, and then compared it with a string (either 'true' or 'false'). In the case of comparing a boolean to a string, the string is converted to a boolean.

The rules for this conversion are that a non-empty string is true, and an empty string is false, so 'true' and 'false' both evaluate to true. To demonstrate this, try running this XSLT against a document of your choice:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" />
  <xsl:template match="/">
    <xsl:if test="contains('foo','oo') = 'true'">
      Foo contains oo "equals" 'true'
    </xsl:if>
    <xsl:if test="contains('foo','oo') = 'false'">
      Foo contains oo "equals" 'false'
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

The output is:

Foo contains oo "equals" 'true'      
Foo contains oo "equals" 'false'

For this kind of test, you are better off just using the boolean value returned by contains(), or of course if you want the opposite, you can use the not() function.

3
  • 2
    Dominic, this is a great explanation of what was wrong with Kremena's XSLT. Commented Feb 28, 2013 at 10:12
  • I think you are very right. I tried it too and am not sure why in our case it works typecasted to string when I have only one confition in the if, but when I add AND (second condition) it does not... Commented Feb 28, 2013 at 13:59
  • Your first condition compares a string with a string. That's fine. The second condition compares a boolean to a string, which forces the string to be evaluated as a boolean. Commented Feb 28, 2013 at 14:05
8

You should better follow my XSLT, since the way you declared is not the right approach.

    <xsl:if test="document(@xlink:href)/tcm:Component/tcm:Data/tcm:IsExternalMultimediaFile = 'true'">
        <xsl:choose>
            <xsl:when test="contains(document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename, '&amp;+_+=') = 'false'">
                <xsl:element name="Image">
                    <xsl:element name="img">
                        <xsl:attribute name="src"><xsl:value-of select="document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename"/></xsl:attribute>
                        <xsl:attribute name="alt"><xsl:value-of select="@xlink:title"/></xsl:attribute>
                    </xsl:element>
                </xsl:element>
            </xsl:when>
            <xsl:when test="contains(document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename, '&amp;+_+=') = 'true'">
                <xsl:element name="Image">
                    <xsl:element name="img">
                        <xsl:attribute name="src"><xsl:value-of select="substring-before(document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename, '&amp;+_+=')"/></xsl:attribute>
                        <xsl:attribute name="alt"><xsl:value-of select="@xlink:title"/></xsl:attribute>
                    </xsl:element>
                </xsl:element>
            </xsl:when>
        </xsl:choose>
    </xsl:if>
9
  • Thank you Siva. I was just about to answer my own question with the same answer. Commented Feb 27, 2013 at 15:59
  • I used <xsl:otherwise> instead of the second when but believe that it is the same output. Commented Feb 27, 2013 at 16:13
  • @KremenaLalova: Never it will be same. If you feel it is coming twice then you are passing the same field twice or two different fields which has IsExternalMultimediaFile='true' and URL matches with contains Commented Feb 27, 2013 at 16:17
  • Oh yes deffinately. What I meant was that instead of <xsl:when test="contains(document(@xlink:href)/tcm:Component/tcm:Data/tcm:MultimediaFilename, '&amp;+_+=') = 'true'"> I used <xsl:otherwise> and since otherwise of false is true or null it worked the same way. Commented Feb 27, 2013 at 16:27
  • 1
    @SivaCharan, +1 for the helpful answer. It might also help other readers to understand what's wrong with the posted approach (in a nice way of course). One other alternative with XSLT is to do a template match for certain scenarios (e.g. <template match="some_xpath">). The conditional tests work, but might miss a chance to recursively "apply-templates" for additional transformations. Commented Feb 28, 2013 at 7:27
4

Consider matching on just the img/@href as well as using variables to help manage this requirement, especially if you're already using an "identity" XSLT approach.

Dominic's example helps clarify how XSLT handles string and boolean comparisons; XSL variables can help isolate, test, and mange your logic. Siva shows how to simplify writing out the XML nodes and points out when not to use otherwise. This approach might make updates easier as well as keep the ability match on specific nodes and attributes independently.

Matching on just image/@href

Note: when matching an attribute directly, you can use document(.).

<xsl:variable name="Pattern" select="'&amp;+_+='" />
...
<xsl:template match="xhtml:img/@xlink:href">

<!-- optional shortcuts for the Data path, boolean checks, and strings -->    
<xsl:variable name="ImageData" select="document(.)/tcm:Component/tcm:Data" />
<xsl:variable name="IsExternal" select="$ImageData/tcm:IsExternalMultimediaFile" />
<xsl:variable name="FileName" select="$ImageData/tcm:MultimediaFilename" />
<xsl:variable name="IncludesPattern" select="contains($FileName, $Pattern)" />

<xsl:variable name="ModifiedName">
  <xsl:choose>
    <xsl:when test="$IsExternal and $IncludesPattern">
      <xsl:value-of select="substring-before($FileName, $Pattern)" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$FileName" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:variable>

<xsl:attribute name="href"><xsl:value-of select="$ModifiedName" /></xsl:attribute>
</xsl:template>

Testing img

To double-check the logic and inspect details from the img node level, use something like the following (temporarily).

<xsl:template match="xhtml:img">
<xsl:variable name="ImageData" select="document(@xlink:href)/tcm:Component/tcm:Data" />
<xsl:variable name="IsExternal" select="$ImageData/tcm:IsExternalMultimediaFile" />
<xsl:variable name="FileName" select="$ImageData/tcm:MultimediaFilename" />
<xsl:variable name="IncludesPattern" select="contains($FileName, $Pattern)" />
<xsl:variable name="TrimmedPath" select="substring-before($FileName, $Pattern)" />

<xsl:comment>IsExternal: <xsl:value-of select="$IsExternal" />,
FileName: <xsl:value-of select="$FileName" />,
IncludesPattern: <xsl:value-of select="$IncludesPattern" />
</xsl:comment>

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

But when you're done testing, just use the basic identity templates if you're not already doing so.

Identity Example

Remove namespaces:

<xsl:template match="@* | node()">
  <xsl:element name="{name()}">
    <xsl:apply-templates select="@* | node()" />
  </xsl:element>
</xsl:template>

Basic copy:

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.