Sharepoint List and XSLT - conditional loops with variables? - xslt

Here is my scenario: I'm working on an e-commerce site built in Sharepoint 2010. I'm making a product catalog powered by a List, and styled by an XSLT stylesheet.
The (current) problem: Certain products have promotions from time to time. I have a field in my list with the text of the promotion, plus a checkmark (yes/no) if it's on promotion. If it is, my stylesheet will display that promotion text-otherwise it'll just have the product as normal.
What I need however is a dynamic way to add a superscript number like 1 to the end of the promotion text. This is for disclaimer purposes.
On a page only containing products on promotion, this is easy enough: I just use the position() function, wrapped by <sup></sup> to increment by 1.
Here is part of my code, starting the loop:
<div>
<xsl:for-each select="$Rows">
<xsl:call-template name="dvt_1.rowview" />
</xsl:for-each>
</div>
Then the template:
<xsl:template name="dvt_1.rowview">
<xsl:variable name="PromotionVar"><xsl:value-of select="#OnPromotion"/> </xsl:variable>
Then testing for the promotion:
<xsl:if test="$PromotionVar='Yes'">
<p class="text-red">Promotion: <xsl:value-of select="#PromotionText1" disable-output-escaping="yes"/>
<sup>
<xsl:variable name="i" select="position()" />
<xsl:copy>
<xsl:value-of select="$i"/>
</xsl:copy>
</sup>
</p>
</xsl:if>
</xsl:template>
However on pages that have products that are not on promotion, this doesn't work as position() is based on all matched records.
Now if this were another scripting language like Javascript, I could just assign a variable and increment it in an if statement.. but from searching on Stackoverflow, I understand that XSLT is a functional language, and variables are actually immutable... so how should I approach this?
Here is another answer that may have what I need.. or not. It's all a bit complicated.
In XSLT how do I increment a global variable from a different scope?
Does anyone know how I should approach this? Thanks.

In the extreme off-chance somebody out there reads this question, I've figured out a solution using JQuery.
First, I make up some classes for my tags named "promo_super" for promotions, and "disclosure_super" for disclosures. This is in my XSLT stylesheet:
<xsl:if test="$PromotionVar='Yes'">
<p class="text-red"><strong>Promotion:
<xsl:value-of select="#PromotionText1" disable-output-escaping="yes"/>
<sup>X</sup>
</strong></p>
</xsl:if>
Then in my JQuery script, which I put at the bottom of the page (so it executes after the list has been rendered):
<script>
var promoNumber = 0;
$(".promo_super").each(function() {
var text = $(this).text();
promoNumber = promoNumber + 1;
text = text.replace("X", promoNumber);
$(this).text(text);
});
In this code I create a variable that will be used to number my promotions. Then, for each instance of that class (.promo_super), I replace the 'X' in between the tags, and I increment that number by 1.
var disclosureNumber = 0;
$(".disclosure_super").each(function() {
var text = $(this).text();
disclosureNumber = disclosureNumber + 1;
text = text.replace("X", disclosureNumber);
$(this).text(text);
});
</script>
I do the same thing for my disclosures, using a different variable and class so I don't add the numbers together. Sorting order is handled by the View's Filters, so assuming both lists are sorted the same, the numbers will match up.
Maybe this isn't the most elegant solution but it seems to work fine!

Related

XSL getting out of context using dynamic XPATH

I'm trying to reformat an XML I get from an appliance into an HTML table, and it's format is not usual.
It use unique references in node name's, like this:
/network/content/host/content/REF_1/content
/network/content/network/content/REF_2/content
and then, it use the same references to another part of the file, as a value of a content node, like this:
/rules/content/rules/content/REF_3/content/sources/content/name = REF_1
/rules/content/rules/content/REF_3/content/destinations/content/name = REF_2
I'm trying to write a template for content that instead of getting me REF_ID which is unique, I try to get the name, in the other branch leaf. this mean I'm trying to find a value that is out of my actual context.
I'm able to retrieve the name XPATH using this variable:
<xsl:variable name='objName' select="concat('/storage/objects/',#linkclass,'/content/',#linktype,'/content/',current(),'/content/name/content')" />
but, I'm not able to use this XPATH in a query like:
<xsl:value-of select="{$objName}">
I suppose this doesn't work because it's out of context but when I ask statically for one of those XPATH I get the value.
My full code is not very complicated:
<xsl:template match="content">
<xsl:variable name='objXPATH' select="concat('/storage/objects/',#linkclass,'/content/',#linktype,'/content/',current(),'/content/name/content')" />
<xsl:variable name='obj' select="{$objXPATH}" />
<xsl:element name="a">
<xsl:attribute name="href">
#<xsl:value-of select="."/>
</xsl:attribute>
<xsl:value-of select="$obj"/>
<br />
</xsl:element>
</xsl:template>
I need help to fix this, I'm on it since one day with no evolution, and it's driving me crazy. I'm more like a script kiddie than a real developer.
Dynamic evaluation (treating a string in a variable as an XPath expression and evaluating it) is available as a vendor extension in a number of XSLT processors, and it becomes part of the standard with the introduction of xsl:evaluate in XSLT 3.0. If your XSLT processor doesn't have such an extension you may be able to write it yourself. Alternatively, if you explain the problem better, we may be able to suggest a solution that does not require dynamic evaluation.

Manage flags in xslt?

All,
i am searching in a list of fields those who has the type clob and i am writing it separed by a comma like this [field1, field2, ... fieldn]
my problem is how to identify the first matched field to write it without comma ( i can't use position() because the first field matched can be the first of the list or the last of the list)
I want to make this algorithm in xslt,
variable is_first = TRUE;
if(is_first) {
do smthng;
isfirst = False;
}
Actually it is not possible to make something like this in xslt since variable are immutable. There probably could be workarounds but you have to specify your need in more details.
edit:
If your input is string with values separated by commas...
<xsl:variable name="inputString" select="'field1,field2,field3a,field4,field3b'" />
... you could use tokenize() functions...
<xsl:variable name="tokenized" select="tokenize($inputString, ',')" />
... and then select items corresponding to your condition
<!-- Select item corresponding to condition (e.g. it contains 3). Take first one if there are several such items -->
<xsl:value-of select="$tokenized[contains(., '3')][1]" />
Edit2:
You can use separator attribute of xsl:value-of (xslt 2.0) for output of delimited values.
Assuming following variable
<xsl:variable name="list">
<item>first</item>
<item>second</item>
<item>third</item>
</xsl:variable>
this <xsl:value-of select="$list/item" separator="," /> makes desired output first,second,third
You need to write this using functional code rather than procedural code. It's not possible to do the conversion without seeing the context (it's much easier to work from the problem rather than from the solution in a lower-level language).
But the most common equivalent in XSLT would take the form
<xsl:for-each select=".....">
<xsl:if test="position() = 1"><!-- first time code --></xsl:if>
....
</xsl:for-each>

convert <xsl:variable> string value , to the value which I can select with <xsl:value-of select>

I have a variable from which I need to dynamically generate nodes
<xsl:template match="banner_discount_1 | banner_discount_2 | banner_discount_3">
<xsl:variable name="link">banner_discount_<xsl:value-of select="substring-after(name(.) ,'banner_discount_')" />_link</xsl:variable>
<xsl:value-of select="$link" />
</xsl:template>
<xsl:value-of> selects the string, but I want to be able to select the node which name matches the name of a variable.
In my case the node looks something like this:
<banner_discount_1_link />
<banner_discount_2_link />
...
Here is the xml I'm using
<banner_discount_1> 12 </banner_discount_1>
<banner_discount_2> 21 </banner_discount_2>
<banner_discount_3> 32 </banner_discount_3>
<banner_discount_1_link> link1 </banner_discount_1_link>
<banner_discount_2_link> link2 </banner_discount_2_link>
<banner_discount_3_link> link3 </banner_discount_3_link>
#MartinHonnen is on the right track, but you need to set the selection context as well.
Since you're in a template that's selecting the banner_discount_ nodes, that is your context. From your XML sample, it looks like the nodes you want to select are siblings, so this should work:
<xsl:value-of select="../*[local-name() = $link]"/>
It is preferable to target the nodes directly, but if they could be anywhere in the document, then you may resort to
<xsl:value-of select="//*[local-name() = $link]"/>
This is a last resort because it is potentially O(n) with respect to the number of nodes in the document.
Use <xsl:value-of select="*[local-name() = $link]"/>. If that does not help then consider to show a sample of the XML.

Creating a variable number of nodes on target document without corresponding data on source document

I'm trying to map two documents witht the BizTalk Mapper and my target document should look like this:
<root>
<complexType>
<property>example</property>
</complexType>
<filler>
<padding>9999999</padding>
</filler>
<filler>
<padding>9999999</padding>
</filler>
<filler>
<padding>9999999</padding>
</filler>
</root>
The number of <filler> nodes that I should create is variable (from 0 to 9). It is basically the result of a calculation (based on some data provided in the source document).
Is there a way to create those <filler> nodes with some combination of functoids?
I tried to use the Table Looping functoid (created a table with only one column, the padding char '9') but doesn't really work because it creates as many <filler> nodes as rows are defined in the table, which is not what I want since the number of rows would have to be variable (again, based on a calculation).
What I currently do is pass the message (XmlDocument) to a C# method and then I programmatically append the <filler> nodes.
I'm hoping that there is a more "BizTalk-y" way of doing this with the Mapper.
I suspect that you will have to solve this problem by altering the XSLT.
Add some logic to create as many filler nodes as the result of your calculation dictates - you could create a template which you call in a loop perhaps, which would append a new filler section.
Hope this points you in the right direction.
As pointed out, XSLT can create nodes on the target document at will (I didn't know this and this was the key part).
Turns out that what I needed is a simple for-loop in XSLT. Once I realized this, a quick Google search yielded the following results:
http://quomon.com/question-How-to-make-a-for-loop-in-xslt-not-for-each-809.aspx
http://snippets.dzone.com/posts/show/930
Another thing worth noting is that (as pointed out by the first link), XSLT is a functional language, not procedural, so sometimes you do have to resort to using recursion or an extension.
This case is definitely one of those times since I couldn't use a careful selection of nodes using the select attribute on an xsl:for-each (since this filler data wasn't part of the source document).
Specifically, for this case, what I did was:
Add a Scripting functoid.
Add two inputs:
A constant with value "1" (this is the initial value of the i variable)
The length of the loop (number of times to repeat the body of the loop)
Paste the following XSLT template as an "Inline XSLT Call Template" script:
<xsl:template name="ForLoop">
<xsl:param name="i" /> <!-- index counter, 1-based, will be incremented with every recursive call -->
<xsl:param name="length" /> <!-- exit loop when i >= length -->
<!-- Output the desired node(s) if we're still looping -->
<!-- The base case is when i > length (in that case, do nothing) -->
<xsl:if test="$i <= $length">
<Filler>
<Padding>999999</Padding>
</Filler>
</xsl:if>
<!-- Call the ForLoop template recursively, incrementing i -->
<xsl:if test="$i <= $length">
<xsl:call-template name="ForLoop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="length">
<xsl:value-of select="$length"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>

How Can I Reduce "When" Statement in XSLT

Hello I am writing a XSLT statement where I need to implement 1500 conditional statements like -
<xsl:when test="ID = '51'">
<br>
<xsl:text>background: url('rightcolumn_seniorliv.jpg') no-repeat;</xsl:text>
<br>
</xsl:when>
<br>
<xsl:when test="ID = '52'">
<br>
<xsl:text>background: url('rightcolumn_seniorliv.jpg') no-repeat;</xsl:text>
<br>
</xsl:when>
If i write statement like this way then my pages will be very slow. How can i reduce my code and write this statement in a smart way?
<xsl:variable name="idlist">
<ids>
<id>50</id>
<id>59</id>
<id>66</id>
...
</ids>
</xsl:variable>
<xsl:key name="idk" match="id" use="."/>
<xsl:when test="key('idk', ID, $idlist)">...
This is XSLT 2.0 but can be adapted to work with 1.0.
I can't see all your cases, but if the pattern from your first two continues and you want that same 'rightcolumn_seniorliv.jpg' for IDs 51 thru 1551 then
<xsl: when test="ID>'50' and ID<'1552'">
it sounds like these ID cases are going well beyond logic and into the data realm. i obviously dont know anything about your app, but perhaps an ID to imagename mapping somewhere (probably database) would be in order. depending on what process generates the XML file (the first one, before XSLT transformation), you might want to just set the image name (or lack thereof) explicitly thru this mapping. then lose the when all together