Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Greyhound 102 posts 124 karma points
    Nov 11, 2011 @ 17:18
    Greyhound
    0

    Basic xslt question

    Hi,

    I'm trying to extract a list of nodes by type (global, not currentpage) and then extract the custom properties for display and logic purposes.

    I've got as far as 

    <xsl:template match="/">
    <xsl:for-each select="umbraco.library:GetXmlAll()//Product [@isDoc]">
    <xsl:value-of select="/data [@alias = 'Name']"/>
    </xsl:for-each>
    </xsl:template>

    What I'd like to be able to do is filter the for-each statement so I can select all Product documents that have a certain property set.

    I would also like to be able to then select various properties. Whilst the above for-each statement works, I am not able to filter by property or retrieve any custom document properties.

    Please could someone help?

    Thanks

     

  • Greyhound 102 posts 124 karma points
    Nov 11, 2011 @ 17:31
    Greyhound
    0

    Ok, problem solved. Complete code is below if it helps anyone else:

     <xsl:template match="/">
      <xsl:for-each select="umbraco.library:GetXmlAll()//Product [@isDoc][hpproductTitle != '']">
      <tr>
      <xsl:variable name="productTitle" select="./hpproductTitle" />
      <xsl:if test="$productTitle != ''">
      <td class="hp-borderRight col1">
      <xsl:value-of select="./hpproductTitle" disable-output-escaping="yes"/>
      </td>
      </xsl:if>
      <xsl:if test="$productTitle = ''">
      <td class="hp-borderRightBottom col1">
      <xsl:value-of select="./hpproductTitle" disable-output-escaping="yes"/>
      </td>
      </xsl:if>
      <td class="hp-borderRightBottom col2">
      <xsl:value-of select="./hppriceDescription" disable-output-escaping="yes"/>
      </td>
      </tr>
        </xsl:for-each>
     
      
     
      </xsl:template>
  • Jan Skovgaard 11280 posts 23678 karma points MVP 10x admin c-trib
    Nov 11, 2011 @ 17:46
    Jan Skovgaard
    0

    Hi Greyhound

    What is the property you want to filter on?

    I think you could more easily achieve what you want by using apply-templates. And you should also avoid using the "//" statement in your xpath since it's bad for performance :)

    I think I would do something like

    <xsl:variable name="productRoot" select="$currentPage/ancestor-or-self::*/[@isDoc/Products]" />

    <xsl:template match="/">
       <xsl:apply-templates select="$productRoot/Product[normalize-space()]" />
    </xsl:template>

    <xsl:template match="Product[filterproperty='1']">
        <xsl:value-of select="hpproducttitle" />
    <xsl:template>

    In the above I anticipate that you have a "root" node for your "product" called "products". If that's not the case let me know and we'll figure the XSLT out together :)

    Also I have made a predicate in the match template, which should be the name of your filter property, which you can test to see if it holds a certain value.

    Hope this helps a bit.

    /Jan

     

  • Greyhound 102 posts 124 karma points
    Nov 11, 2011 @ 17:48
    Greyhound
    0

    Hi Jan,

    Thanks for the post - I've just this minute solved it but not sure my xslt is "very good" - any advice on whether my final code below is good practice would be much appreciated.

     

    <xsl:template match="/">
    <xsl:for-each select="umbraco.library:GetXmlAll()//Product [@isDoc][hpproductTitle != '']">
    <xsl:value-of select="./hpproductTitle" disable-output-escaping="yes"/>
    </xsl:for-each>
    </xsl:template>
  • Dan Okkels Brendstrup 101 posts 197 karma points
    Nov 16, 2011 @ 15:44
    Dan Okkels Brendstrup
    1

    Hi Greyhound,

    That code will work, but there are some edge cases that it won't cover, and some optimizations that could be made.

    Instead of calling an extension function to return the entire XML cache, it is better to start from your $currentPage variable, which pinpoints your current position within the entire XML structure (as visible in ~/App_Data/umbraco.config), and then navigate to the closest ancestor element that you need. That is what Jan suggested with the $productRoot variable. What you are currently doint is pulling out the entire XML structure (which can be quite large) and doing a broad search for any Product nodes in the three. If all products are contained within a common ancestor node, then you can target that node directly, which will yield better performance.

    And matching an element's content against an empty string will cover some cases, but not all. You're better off using the normalize-space() function, which strips leading and trailing whitespace and does some other normalizations, and returns the remaining text content. When used in a predicate, this has the effect of being a boolean test for whether there is any text content in the element in question. Chriztian explains it in detail midway through this comment:

    http://our.umbraco.org/forum/developers/xslt/25268-Embedded-for-each,-or-apply-template-to-display-'batches'-of-SQL-data#comment94009

    As Jan suggested, apply-templates is in most cases a more flexible way to go than for-each. If you want to only render products that have content for both their title and their description fields, you could do something like this:

    <xsl:variable name="productRoot" select="$currentPage/ancestor-or-self::*[@isDoc][Product]" />
     
    <xsl:template match="/">
       <xsl:apply-templates select="$productRoot/Product[normalize-space(hpproductTitle) and normalize-space(hpproductDescription)]" />
    </xsl:template>
     
    <xsl:template match="Product">
        <xsl:value-of select="hppriceproductTitle" disable-output-escaping="yes"/>
        <xsl:value-of select="hpproductDescription" disable-output-escaping="yes"/>
    </xsl:template>
    The apply-templates call matches all Product nodes under the chosen ancestor node, but adds a predicate to check if there is content for both the title and the description. If both return true, then the template is applied, which renders the content of both nodes. This technique is especially useful for bulletproofing your markup, so you don't output empty HTML elements for nodes with no content. E.g. if most products have a title but only some have a description, you could do this instead:
    <xsl:template match="/">
      <xsl:apply-templates select="$productRoot/Product[normalize-space(hpproductTitle)]" />
    </xsl:template>
     
    <xsl:template match="Product">
      <h1><xsl:value-of select="hppriceproductTitle"/></h1>
      <xsl:apply-templates select="hpproductDescription[normalize-space()]"/>
    </xsl:template>
     
    <xsl:template match="hpproductDescription">
      <div class="description">
        <xsl:value-of select="." disable-output-escaping="yes"/>
      </div>
    </xsl:template>
  • Greyhound 102 posts 124 karma points
    Nov 19, 2011 @ 15:48
    Greyhound
    0

    Thanks Dan/Jan for the very considered posts.

    I can defintely see the benefits of using the apply-template and the normalize-space() is something thats going to save me heaps of time in the future.

Please Sign in or register to post replies

Write your reply to:

Draft