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...

  • What leads you to the conclusion both cases get executed? – Glenn Stevens Feb 27 '13 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> – Kremena Lalova Feb 27 '13 at 15:25
  • Initially I tried without using the if statement but substring-before returned null if the string was not found. – Kremena Lalova Feb 27 '13 at 15:28
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.

| improve this answer | |
  • 2
    Dominic, this is a great explanation of what was wrong with Kremena's XSLT. – Glenn Stevens Feb 28 '13 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... – Kremena Lalova Feb 28 '13 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. – Dominic Cronin Feb 28 '13 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>
| improve this answer | |
  • Thank you Siva. I was just about to answer my own question with the same answer. – Kremena Lalova Feb 27 '13 at 15:59
  • I used <xsl:otherwise> instead of the second when but believe that it is the same output. – Kremena Lalova Feb 27 '13 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 – Siva Charan Feb 27 '13 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. – Kremena Lalova Feb 27 '13 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. – Alvin Reyes Feb 28 '13 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>
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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