XSLT starts with not 'E' or 'Z' - xslt

I am very new to XSLT. I am trying to build a custom list view in SharePoint 2010.
Dependant on what the user wishes to see, will depend on what items are returned.
If say they want to see view 1: it will get all ref thats starts with E.
If view 2: all ref start with Z.
If view 3: all ref starts with every other letter.
I have the following code:
<xsl:variable name="dvt_StyleName">Table</xsl:variable>
<xsl:variable name="RowLimit" select="30" />
<xsl:variable name="query_string" select="substring-after($current_url,'sheet=')"/>
<xsl:variable name="query_sheet" select="substring-before($query_string,'&acYear=')"/>
<xsl:variable name="query_year" select="substring-after($query_string,'acYear=')"/>
<!--sheet query assignment-->
<xsl:variable name="qOptions">
<xsl:choose>
<xsl:when test="$query_sheet = '2'">E</xsl:when>
<xsl:when test="$query_sheet = '3'">Z</xsl:when>
<xsl:otherwise>A|B|C|D|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!--debugging--->
<xsl:value-of select="$qOptions"/>
<!-- end of debugging--->
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[starts-with(#Title, $qOptions)]"/>
The oitherwise statement currently does not work because i am trying to get multiple conditions into the starts-with statement. I know i am doing this very wrong so any help would be greatly appreciated. If you could try and be a little more hand-holdy with me that would be great cause the syntax of this language really confuses me!
Thanks in advance

As you dealing with single letters, you could define your "otherwise" like this...
<xsl:otherwise>ABCDFGHIJKLMNOPQRSTUVWXY</xsl:otherwise>
Then your Rows variable could be re-written like so
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[contains($qOptions, substring(#Title, 1, 1))]"/>
i.e Does the $qOptions variable contain the first letter of the Title attribute
EDIT: If, you want to match "everything else" rather than "every other letter", then consider re-writing it to this, slightly more long winded way....
<xsl:variable name="qOptions">
<xsl:choose>
<xsl:when test="$query_sheet = '2'">E</xsl:when>
<xsl:when test="$query_sheet = '3'">Z</xsl:when>
<xsl:otherwise>!EZ</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[(not(starts-with($qOptions, '!')) and contains($qOptions, substring(#Title, 1, 1))) or (starts-with($qOptions, '!') and not(contains($qOptions, substring(#Title, 1, 1))))]"/>

Related

Need advice on using xsl choose with contains and substring

I think I have this working but I need advice. I'd like to know if this is a good setup for the requirement that I have.
I've got a requirement to apply different transformation rules to an element based on what is contained in that element. I've tried to search for 'xsl' and 'choose', 'contains', 'substring'. I can't seem to find a solution applicable to this situation.
Here are the various scenarios for this element:
If it begins with U, I need everything before the '/'
Original Value: UJXXXXX/001
Transformed : UJXXXXX
If it begins with ECG_001 I need everything after ECG_001
Original Value: ECG_0012345678
Transformed : 12345678
If it does not meet the above criteria and contains a '/' take everyting after the '/'
Original Value: F5M/12345678
Transformed : 12345678
If it does not meet 1,2, or 3 just give me the value
Original Value : 12345678
Transformed : 12345678
Here is what I have so far:
<xsl:variable name="CustomerPO">
<xsl:choose>
<xsl:when test="contains(substring(rma/header/CustomerPO,1,1), 'U')">
<xsl:value-of select="substring-before(rma/header/CustomerPO,'/')"/>
</xsl:when>
<xsl:when test="contains(rma/header/CustomerPO, 'ECG_001')">
<xsl:value-of select="substring-after(rma/header/CustomerPO,'ECG_001')"/>
</xsl:when>
<xsl:when test="contains(rma/header/CustomerPO, '/')">
<xsl:value-of select="substring-after(rma/header/CustomerPO, '/')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="rma/header/CustomerPO"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Any feedback on potential loopholes or a more efficient way to accomplish this is appreciated.
Thanks.
Your XSLT looks fine. You might consider using the starts-with function rather than substring, I find it easier to read, but I am not sure it is any faster.
My own style would be to use template rules.
<xsl:template match="CustomerPO[starts-with(., 'U')]">
<xsl:value-of select="substring-before(., '/')"/>
</xsl:template>
<xsl:template match="CustomerPO[starts-with(., 'ECG_001')]">
<xsl:value-of select="substring-after(., 'ECG_001')"/>
</xsl:template>
etc.

xslt comma separate list of values

I am creating a summary view of Microsoft InfoPath form(s) using a custom XSLT Stylesheet.
I have a selection of the radio buttons on the form which are clicked "Yes", "No"
e.g.
What is the primary construction of the building?
Steel Yes
No
Timber Yes
NO
etc....
In the stylesheet I have
<tr>
<td>
The primary contruction of the building is
<xsl:choose>
<xsl:when test="/my:myFields/my:BrickPrimaryConstruction= 'true'">
Brick
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="/my:myFields/my:TimberPrimaryConstruction = 'true'">
Timber Framed
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="/my:myFields/my:ConcretePrimaryConstruction = 'true'">
Concrete Framed
</xsl:when>
</xsl:choose>
etc....
What I want to achieve in the final HTML output is something like:
The primary construction of the building is Brick, Concrete Framed, Prefabricated
I have not got much experience of XSLT, but what is the best way of achieving this?
Avoiding this:
, Concrete Framed, Prefabricated,
or
Brick, , Prefabricated
Normally in C# I would assign to a string and check if it is empty before appending a comma and then trim commas on the ends however I know that I cannot assign variables in xslt.
EDIT
I also mentioned that I wanted to be able to reuse the function in other situations such as
<xsl:value-of select="/my:myFields/my:Road"/>,
<xsl:value-of select="/my:myFields/my:District"/>,
<xsl:value-of select="/my:myFields/my:City"/>,
<xsl:value-of select="/my:myFields/my:County"/>,
<xsl:value-of select="/my:myFields/my:Postcode"/>
where these would be separated by comma or a new line character, but there is the possibility that the "District" for example might be blank resulting in "Road, , City" etc.
Try this:
<tr>
<td>
The primary contruction of the building is
<xsl:for-each select="/my:myFields/my:*[ends-with(local-name(), 'PrimaryConstruction') and (.='true')]">
<xsl:if test="position()!=1" xml:space="preserve">, </xsl:if>
<xsl:choose>
<xsl:when test="self::my:BrickPrimaryConstruction">Brick</xsl:when>
<xsl:when test="self::my:TimberPrimaryConstruction">Timber Framed</xsl:when>
<xsl:when test="self::my:ConcretePrimaryConstruction">Concrete Framed</xsl:when>
etc...
</xsl:choose>
</xsl:for-each>
Basically, the for-each loops over all the relevant fields, so that you can check their position and only emit the comma if you're not on the first item (XSLT has a 1-based index). Since the for-each already filters only the relevant entries, we then only have to check the type, not whether the value is true or not.
Note that you can extend the same principle to emit an "and" for the last item instead of a comma if you like to do so.
Maybe look at Implode.
Then, you could do
<xsl:variable name="theItems">
<xsl:if test="/my:myFields/my:BrickPrimaryConstruction= 'true'">
<foo>Brick</foo>
</xsl:if>
</xsl:variable>
<xsl:call-template name="implode">
<xsl:with-param name="items" select="exsl:node-set($theItems)" />
</xsl:call-template>
You have to convert $theItems, which is a xml fragment, to a node-set. This can only be archived using non-standart methods. XML.com shows some methods to archive it using various XML processors.

How can I remove whitespace when declaring an XSL variable?

I have to create an XSL variable with a choose in it. Like the following:
<xsl:variable name="grid_position">
<xsl:choose>
<xsl:when test="count(/Element) >= 1">
inside
</xsl:when>
<xsl:otherwise>
outside
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
And later in my code, I do an xsl if:
<xsl:if test="$grid_position = 'inside'">
{...code...}
</xsl:if>
Problem is that my variable is never = 'inside' because of the line breaks and indent. How can I remove whitespaces from my variable? I know I can remove it using disable-output-escaping="yes" when I use it in a xsl:copy-of, but it's not working on the xsl:variable tag. So how can I remove those whitespace and line breaks?
That's what <xsl:text> is for:
<xsl:variable name="grid_position">
<xsl:choose>
<xsl:when test="count(/Element) >= 1">
<xsl:text>inside</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>outside</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
It allows you to structure your code and control whitespace at the same time.
In fact, you should stay clear of text nodes in XSL that are not wrapped in <xsl:text> to avoid these kinds of bugs in the future, too (i.e. when code gets re-formatted or re-factored later).
For simple cases, like in your sample, doing what Jim Garrison suggests is also an option.
As an aside, testing for the existence of an element with count() is superfluous. Selecting it is enough, since the empty node-set evaluates to false.
<xsl:when test="/Element">
The simplest way is not to put the whitespace there in the first place:
<xsl:variable name="grid_position">
<xsl:choose>
<xsl:when test="count(/Element) >= 1">inside</xsl:when>
<xsl:otherwise>outside</xsl:otherwise>
</xsl:choose>
</xsl:variable>
The strategies in the other answers are good, in fact preferable to this one when feasible. But there are times when you don't have control over (or it's harder to control) what's in the variable. In those cases, you can strip away the surrounding space when you're testing the variable:
Instead of
<xsl:if test="$grid_position = 'inside'">
use
<xsl:if test="normalize-space($grid_position) = 'inside'">
normalize-space() strips the leading and trailing whitespace, and collapses other repeating white spaces to single ones.
Just use:
<xsl:variable name="grid_position" select=
"concat(substring('inside', 1 div boolean(/Element)),
substring('outside', 1 div not(/Element))
)
"/>

XSLT: contains() for multiple strings

I have a variable in XSLT called variable_name which I am trying to set to 1, if the Product in question has attributes with name A or B or both A & B.
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:if test="#attributename='A' or #attributename='B'">
<xsl:value-of select="1"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
Is there any way to match multiple strings using the if statement, as mine just matches if A is present or B is present. If both A & B are present, it does not set the variable to 1. Any help on this would be appreciated as I am a newbie in XSLT.
You can use xsl:choose statement, it's something like switch in common programming languages:
Example:
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:choose>
<xsl:when test="#attributename='A'">
1
</xsl:when>
<xsl:when test=" #attributename='B'">
1
</xsl:when>
<!--... add other options here-->
<xsl:otherwise>1</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
This will set new variable with name variable_name with the value of attribute product/attributes.
For more info ... http://www.w3schools.comwww.w3schools.com/xsl/el_choose.asp
EDIT: And another way (a little dirty) by OP's request:
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:if test="contains(text(), 'A') or contains(text(), 'B')">
1
</xsl:if>
</xsl:for-each>
</xsl:variable>
It will be helpful if you provide the xml you're writing your xslt against.
This might not help...
Is it 'legal' to have two XML element attributes with the same name (eg. <element x="1" x="2" />)?
Is this what you are trying to process? Try parsing your XML file through xmllint or something like it to see if it is valid.
xmllint --valid the-xml-file.xml
My guess is that you will get a 'attribute redefined' error.

"Regular expression"-style replace in XSLT 1.0

I need to perform a find and replace using XSLT 1.0 which is really suited to regular expressions. Unfortunately these aren't available in 1.0 and I'm also unable to use any extension libraries such as EXSLT due to security settings I can't change.
The string I'm working with looks like:
19;#John Smith;#17;#Ben Reynolds;#1;#Terry Jackson
I need to replace the numbers and ; # characters with a ,. For example the above would change to:
John Smith, Ben Reynolds, Terry Jackson
I know a recursive string function is required, probably using substring and translate, but I'm not sure where to start with it.
Does anyone have some pointers on how to work this out? Here's what I've started with:
<xsl:template name="TrimMulti">
<xsl:param name="FullString" />
<xsl:variable name="NormalizedString">
<xsl:value-of select="normalize-space($FullString)" />
</xsl:variable>
<xsl:variable name="Hash">#</xsl:variable>
<xsl:choose>
<xsl:when test="contains($NormalizedString, $Hash)">
<!-- Do something and call TrimMulti -->
</xsl:when>
</xsl:choose>
</xsl:template>
I'm hoping you haven't simplified the problem too much for asking it on SO, because this shouldn't be that much of a problem.
You can define a template and recursively call it as long as you keep the input string's format consistent.
For example,
<xsl:template name="TrimMulti">
<xsl:param name="InputString"/>
<xsl:variable name="RemainingString"
select="substring-after($InputString,';#')"/>
<xsl:choose>
<xsl:when test="contains($RemainingString,';#')">
<xsl:value-of
select="substring-before($RemainingString,';#')"/>
<xsl:text>, </xsl:text>
<xsl:call-template name="TrimMulti">
<xsl:with-param
name="InputString"
select="substring-after($RemainingString,';#')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$RemainingString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I tested this template out with the following call:
<xsl:template match="/">
<xsl:call-template name="TrimMulti">
<xsl:with-param name="InputString">19;#John Smith;#17;#Ben Reynolds;#1;#Terry Jackson</xsl:with-param>
</xsl:call-template>
</xsl:template>
And got the following output:
John Smith, Ben Reynolds, Terry Jackson
Which seems to be what you're after.
The explanation of what it is doing is easy to explain if you're familiar with functional programming. The InputString parameter is always in the form [number];#[name];#[rest of string]. Each call of the TrimMulti template chops off the [number];# part and prints off the [name] part, then passes the remaining expression to itself recursively.
The base case is when InputString is in the form [number];#[name], in which case the RemainingString variable won't contain ;#. Since we know this is the end of the input, we don't output a comma this time.
If the ';' and '#' characters are not valid in the input because they are delimiters then why wouldn't the translate function work? It might be ugly (you have to specify all valid characters in the second argument and repeat them in the third argument) but it would be easier to debug.
translate($InputString, ';#abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUZ0123456789,- ', ', abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUZ0123456789,- ')