Unlocking the Mysteries of Data View Web Part XSL Tags – Part 20 – <xsl:import>

This entry is part 20 of 21 in the series Unlocking the Mysteries of Data View Web Part XSL Tags

Cross-posted from EndUserSharePoint.com

Whoa, Bessie! I forgot about one of the most useful XSL tags for Data View Web Parts (DVWPs)!  <xsl:import> allows you to be highly modular in storing your XSL templates for reuse.  I mentioned this back in Part 13 briefly, but I should go into it in some more detail.  This post will be sort of a twofer: I’ll explain <xsl:import> and also give you a few of the templates that I find myself storing separately from my DVWPs and reusing again and again.  (Thanks to James Love and his recent great post Sharepoint Designer – DataView Web Parts – streamlining the Newsletter Layout and follow up questions for jogging my memory.)

At the top of the XSL section of the DVWP, immediately after the <xsl:stylesheet> line (I’ve truncated it below to keep things neat and tidy.), you can add an <xsl:import> tag.  This is the *only* place in the XSL that <xsl:import> is permissible.  You can have multiple <xsl:import>s, but they need to be in this exact location.

<Xsl>
<xsl:stylesheet [snip] >
  <xsl:import href="/XSL/Utilities.xsl"/>
  <xsl:output method="html" indent="no"/>

Depending on whether I’m working with WSS or MOSS, I’ll store the common XSL in a different place. As far as I know, there’s no accepted best practice on this; as long as you are consistent you’ll be better off than most people.  In WSS, I create a Document Library at the root of the Site Collection called XSL.  In MOSS, I store the common XSL in /Style Library/XSL Style Sheets/Style Library/XSL Style Sheets is where the XSL for the Content Query Web Part (CQWP) lives, so it seems as good a place as any.

Each of the common XSL files needs to take the following form.  You can include as many templates as you want, of course.

<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet [snip] >
  <xsl:template name="GetUserName">
    <xsl:param name="Person"/>
      <xsl:value-of select="substring-after(substring-before(substring-after($Person, 'ID='), '&lt;'), '&gt;')"/>
  </xsl:template>
</xsl:stylesheet>

The template above called GetUserName does pretty much what it says: given a value from a Person or Group column, the template strips out the user’s name.

You must include the XML definition and the <xsl:stylesheet>; tag again, even though it is in your DVWP to start with.  I can’t really tell you why, frankly, but if you don’t, you’ll run into trouble.  Trust me on this.

This external XSL file can contain any templates that you want to use in more than one place, or just templates that you’d like to isolate from the DVWP for better management.  A few caveats: You lose some of the Intellisense, SharePoint Designer doesn’t play nicely with your formatting once you move XSL into this separate file, and parameters and variables must be defined and scoped properly. I generally build all of the XSL inside the actual DVWP and then move it out into the separate file for use elsewhere.

So what type of other things might you include in these separate XSL files which you import into DVWPs with <xsl:import>?  Well, here are a few of my commonly used utility templates, which I usually store in Utilities.XSL. (Most of these are from my blog, so they won’t be new to my regular readers.  I’m including links to the original blog posts in case you’d like more information.)

This template returns the user’s ID  from the blob of HTML which represents the user in a SharePoint Person or Group column:

<xsl:template name="GetUserID">
	<xsl:param name="Person"/>
	<xsl:value-of select=" substring-before(substring-after($Person, 'ID='), '&quot;')"/>
</xsl:template>

These two templates help to format the values in a multi-select column more nicely. The first is for use with values from a list DataSource and the second is for use with the values returned from a DataSource which uses a Web Service to retrieve list contents, usually from a different SharePoint Site Collection. Displaying a Multi-Select Column “Nicely”

<xsl:template name="MultiSelectDisplay">
  <xsl:param name="MultiSelectValue"/>
  <xsl:param name="MultiSelectSeparator"/>
  <xsl:choose>
    <xsl:when test="contains($MultiSelectValue, ';')">
      <xsl:value-of select="concat(substring-before($MultiSelectValue, ';'), $MultiSelectSeparator)" disable-output-escaping="yes"/>
      <xsl:call-template name="MultiSelectDisplay">
        <xsl:with-param name="MultiSelectValue" select="substring-after($MultiSelectValue, ';')"/>
        <xsl:with-param name="MultiSelectSeparator" select="$MultiSelectSeparator"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$MultiSelectValue" disable-output-escaping="yes"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Displaying a Multi-Select Column “Nicely” in a DVWP with Web Services

<xsl:template name="MultiSelectDisplayWS">
  <xsl:param name="MultiSelectValue"/>
  <xsl:param name="MultiSelectSeparator"/>
  <xsl:choose>
    <xsl:when test="contains($MultiSelectValue, ';#')">
      <xsl:value-of select="concat(substring-before(substring-after($MultiSelectValue, ';#'), ';#'), $MultiSelectSeparator)" disable-output-escaping="yes"/>
      <xsl:call-template name="MultiSelectDisplayWS">
        <xsl:with-param name="MultiSelectValue" select="substring-after(substring-after($MultiSelectValue, ';#'), ';#')"/>
        <xsl:with-param name="MultiSelectSeparator" select="$MultiSelectSeparator"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="substring-after($MultiSelectValue, ';#')" disable-output-escaping="yes"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

The Alpha template lets you display an alphabetical navigational filter for values in a list. Alpha Selection of List Items in a Data View Web Part (DVWP)

<xsl:template name="Alpha">
<xsl:param name="Rows"/>
<xsl:param name="RemainingLetters"/>
<xsl:variable name="ThisLetter" select="substring($RemainingLetters, 1, 1)"/>
<td>
<xsl:attribute name="style">
<xsl:if test="$ThisLetter = $Letter">font-weight:bold;</xsl:if>
</xsl:attribute>
<xsl:choose>
<xsl:when test="count(/dsQueryResponse/Rows/Row[starts-with(@Title, $ThisLetter)])">
<a href="{$URL}?Letter={$ThisLetter}"><xsl:value-of select="$ThisLetter"/></a>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$ThisLetter"/>
</xsl:otherwise>
</xsl:choose>
</td>
<xsl:if test="string-length($RemainingLetters) &gt; 1">
<xsl:call-template name="Alpha">
<xsl:with-param name="Rows" select="$Rows"/>
<xsl:with-param name="RemainingLetters" select="substring-after($RemainingLetters, $ThisLetter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

I built the following two templates together when I wanted to be able to display the start of blog posts without the Rich Text formatting. You can use them together or separately. Displaying the First N Words of a Long Rich Text Column with XSL

<xsl:template name="StripHTML">
  <xsl:param name="HTMLText"/>
  <xsl:choose>
   <xsl:when test="contains($HTMLText, '<')">
    <xsl:call-template name="StripHTML">
      <xsl:with-param name="HTMLText" select="concat(substring-before($HTMLText, 'LT_SIGN'),
        substring-after($HTMLText, 'GT_SIGN'))"/>
    </xsl:call-template>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="$HTMLText"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
  <xsl:template name="FirstNWords">
    <xsl:param name="TextData"/>
<xsl:param name="WordCount"/>
    <xsl:param name="MoreText"/>
    <xsl:choose>
      <xsl:when test="$WordCount > 1 and
            (string-length(substring-before($TextData, ' ')) > 0 or
            string-length(substring-before($TextData, '  ')) > 0)">
        <xsl:value-of select="concat(substring-before($TextData, ' '), ' ')" disable-output-escaping="yes"/>
        <xsl:call-template name="FirstNWords">
          <xsl:with-param name="TextData" select="substring-after($TextData, ' ')"/>
          <xsl:with-param name="WordCount" select="$WordCount - 1"/>
          <xsl:with-param name="MoreText" select="$MoreText"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="(string-length(substring-before($TextData, ' ')) > 0 or
            string-length(substring-before($TextData, '  ')) > 0)">
        <xsl:value-of select="concat(substring-before($TextData, ' '), $MoreText)" disable-output-escaping="yes"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$TextData" disable-output-escaping="yes"/>
      </xsl:otherwise>
  </xsl:choose>
</xsl:template>

The FixEscapedChars template was one of the first recursive templates which I wrote. (Many of these templates are recursive, and recursion is very powerful!) It helps to clean up escaped apostrophes in text values. Recursive XSL Templates in DVWPs

<xsl:template name="FixEscapedChars">
  <xsl:param name="MultiSelectValue"/>
  <xsl:variable name="Apostrophe">'</xsl:variable>
  <xsl:choose>
    <xsl:when test="contains($MultiSelectValue, '&amp;#39;')">
      <xsl:call-template name="FixEscapedChars">
        <xsl:with-param name="MultiSelectValue" select="concat(substring-before($MultiSelectValue, '&amp;#39;'), $Apostrophe, substring-after($MultiSelectValue, '&amp;#39;'))"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$MultiSelectValue"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

GetPercentileValue lets you get the value of a column which represents a given percentile in a set of values.

<xsl:template name="GetPercentileValue">
  <xsl:param name="Rows"/>
  <xsl:param name="ColumnName"/>
  <xsl:param name="Percentile"/>
  <xsl:variable name="Pos" select="ceiling($Percentile div 100 * count($Rows))"/>
  <xsl:for-each select="$Rows">
    <xsl:sort select="@*[name()=$ColumnName]" order="ascending" data-type="number"/>
    <xsl:if test="position() = $Pos">
      <xsl:value-of select="@*[name()=$ColumnName]"/>
    </xsl:if>
  </xsl:for-each>
</xsl:template>

This set of templates is just a smattering of what I carry around in my toolkit for use from project to project.  Hopefully one or two of them will turn out to be useful for you. Also, as you can see from the way each template is written, they are highly reusable, so they are excellent candidates for placement in a separate XSL file which is imported into your DVWPs with <xsl:import>.

Series Navigation<< Unlocking the Mysteries of Data View Web Part XSL Tags – Part 19 – Miscellaneous – More Math / Number FunctionsUnlocking the Mysteries of the SharePoint Data View Web Part XSL Tags eBook Now Available >>

21 Comments

  1. Do you have an XSL template for taking a List View converted to Data View, and locking the left 3 columns in place while allowing the right columns to scroll left and right, and also allowing the entire table to scroll up and down locking the headers in place? Similar to the built in Project Gantt chart but for a data view.

    Reply
    • Linda:

      That’s pretty darn specific, don’t you think!?!!? No, I don’t. The trick is going to be to get the right HTML rendered in your XSL. I’m guessing that the HTML may be the hard part in this one. You’ll need to end up with some embedded tables, I think.

      M.

      Reply
  2. Mark, long time reader (and fan), first time commenter. Just wanted to thank you, this post provided me the solution I needed for a DVWP solution that displayed apostrophes with the ugly #39’s! Thanks much, your posts are very much appreciated!

    Reply
  3. Wow… this is HUGE!! Thanks in large part to your e-book, I’ve become really proficient at developing dataviews (delivered in 30 minutes or less or it’s free) but the most time consuming part of it was updating web parts across public facing sites per the client’s request. It never dawned on me that I could seperate the templates from the base constructors to allow for centrally managed dataviews. I salute you, sir!

    Reply
  4. First of all, thank you very much for all your hard work!

    I was using one of your template, GetUserName and it’s working great! The only concern I have is, I have a column of type ‘People/Person’ that allows multiple selection. Now when I use GetSUserName template, it only returns one name(the last one). I could see why it’s doing so, just wondering if you already have something that would handle that case? If not, even hint will be much appreciated.

    Reply
  5. Hi Marc, I realize this is an extremely old post but I am hoping you can help.

    We have a site collection that is going to have multiple subsites in it. I am trying to import an xsl document that hels with dates. (you have mentioned it in other blogs) anyway if I use a relative path it seems to render ok in SPD 2007 but the browser will not display any information and says there is an issue with the webpart.

    If I try to use the full path I get an 0x80004005 : Access is denied. error in designer and of course nothing in the broweser. I have no idea how ot fix this.
    Ideas?

    Reply
  6. We are finding that caching is causing an issue that only xsl:import gets around. If we cache a page and the page is initially built with an auth’d user, anonymous gets an access denied when a list view webpart is directly calling an xsl file that does a dsqueryresponse against a view. However, the auth’d user doesn’t get that access denied.

    When the cache is created by an anonymous user – all anonymous and auth’d users can view the output of the rendering.

    Now, if we take the same xsl file that does the dsqueryresponse and set it aside, create a new empty file that does nothing more than an xsl:import of the one we set aside, everyone can see the output without access denied – regardless of who builds the cache entry (either anon or auth).

    The thing that has me stumped is whether this is a permmask thing that is coming back on the rows which the xsl:import is somehow disregarding before the cache is built/stored or if the import is doing something else to tell the cache/webpart that the output was not to be security trimmed. If you can help, it would be much appreciated!

    Reply
    • Mike:

      It’s not clear how you are doing your caching. I assume you’re on SharePoint 2010? Caching is generally per user. If whatever your caching mechanism you’re using isn’t doing it per user, you’re likely to have issues.

      M.

      Reply
  7. Thanks for getting back… It loss lie we are actually dealing with the 2010 bug that prevents anonymous from transforming and caching XSL…primarily affects list view webparts since XSL is commonly used here to change the display of an already anon list…

    Reply
    • So storing your XSL in a separate file gets you around the caching problem? I don’t think I’ve ever run into it.

      I tend to use DVWPs instead of XLVVWPs because I find them to be more malleable. I’m also stodgy.

      M.

      Reply
  8. Hey Marc thanks for this series–its taken me a long-way in a short amount of time. I have a question, I’m working with the “People Search Core Results” xslt. I’m looking to create dynamic HTML table colums with the xslt for-each tag. Specificially, I’m working with the “for-each” that calls the SingleResult template in the People Search Core Results xslt. But it seems to me that the idea to wrap the results of a for-each with a HTML table tag so that the items can places within td tags seems pretty useful. Have you come across anything on this?

    Reply
    • Taffy:

      You can emit any markup you need. It’s usually best to think about what you want the final markup to be and then implement it in the XSL. I’ll sometimes dummy up the markup in an empty HTML page first so that I can be sure I’ll be able to style it the way I want to.

      M.

      Reply

Have a thought or opinion?