Process delimited text with xslt 1.0 - xslt

I need to to process a delimited text with xslt that looks like:
abc#mail;#6896;#def#mail;#7467;#hij#mail
The output should be a mailto: link with all addresses and I need to throw away the numbers between them. I am working on Sharepoint, so can only use XSLT 1.0
Edit:
I found this question from about 7 years ago, that is almost the same:
"Regular expression"-style replace in XSLT 1.0
In my case, it doing exactly the opposite: keeping the numbers and throwing out the mail addresses. Can someone help me how to modify the code in the address?
update: as a workaround, I added the delimiter as prefix to my text, and the template works perfect.

This specifically looks for numbers rather than skipping every other item.
<xsl:template name="ExtractNumbers">
<xsl:param name="strInputString"/>
<xsl:variable name="strCurrent" select="substring-before($strInputString, ';#')" />
<xsl:variable name="strRemaining" select="substring-after($strInputString, ';#')" />
<xsl:if test="$strCurrent != ''">
<xsl:if test="string(number($strCurrent)) != 'NaN'">
<xsl:value-of select="$strCurrent" />
<xsl:if test="contains($strRemaining, ';#')">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:if>
<xsl:call-template name="ExtractNumbers">
<xsl:with-param name="strInputString" select="$strRemaining"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

Related

Choose with for-each inside?

I have a parameterignoreAttributes which is a comma separated list of things to look for. I want to set a variable copyAttrib to be equal to whether any of them are exactly matched by name().
If xsl were a procedural language where variables could be reassigned, I'd use something like this:
<xsl:variable name="copyAttrib" select="true()">
<xsl:for-each select="tokenize($ignoreAttributes,',')">
<xsl:if test="compare(., name()) != 0">
<xsl:variable name="copyAttrib" select="false()"/>
</xsl:if>
</xsl:for-each>
Unfortunately, I can't do that, because xsl is functional (so says this other answer). So variables can only be assigned once.
I think the solution would look something like:
<vsl:variable name="copyAttrib">
<xsl:choose>
<xsl:when>
<xsl:for-each select="tokenize($ignoreAttributes, ',')">
<xsl:if test="compare(., name()) != 0"/>
</xsl:for-each>
<xsl:otherwise>
<xsl:value-of select="false()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Obviously not exactly that (otherwise I wouldn't be asking.)
I know that I could bypass the tokenize and for-each loop by just using replaces on ignoreAttributes and changing all the , to | and then using matches, but I'd like to avoid that if possible because then I need to deal with the possibility that ignoreAttributes (which the user provides) might contain some special characters that will change the regex pattern and escape them all.
I have a parameterignoreAttributes which is a comma separated list of things to look for. I want to set a variable copyAttrib to be equal to whether any of them are exactly matched by name().
That sounds to me like
<xsl:variable name="copyAttrib" as="xs:boolean"
select="tokenize($parameterignoreAttributes, ',') = name()"/>
You say:
Unfortunately, I can't do that, because xsl is functional
when what you mean is: "Fortunately, I don't need to do that, because XSLT is functional".
An XSLT-1.0 way of doing this is by using a recursive, named template:
<xsl:template name="copyAttrib">
<xsl:param name="attribs" />
<xsl:choose>
<xsl:when test="normalize-space(substring-before($attribs,',')) = normalize-space(name(.))">
<xsl:value-of select="'true'" />
</xsl:when>
<xsl:when test="normalize-space($attribs) = ''">
<xsl:value-of select="'false'" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="copyAttrib">
<xsl:with-param name="attribs" select="substring-after($attribs,',')" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Apply this template onto the current, the selected, node and wrap it in a <xsl:variable>:
<xsl:variable name="copyAttribResult">
<xsl:call-template name="copyAttrib">
<xsl:with-param name="attribs" select="'a,b,c,...commaSeparatedValues...'" />
</xsl:call-template>
</xsl:variable>
to get either true or false as a result.

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.

How do I render a comma delimited list using xsl:for-each

I am rendering a list of tickers to html via xslt and I would like for the list to be comma deliimited. Assuming I was going to use xsl:for-each...
<xsl:for-each select="/Tickers/Ticker">
<xsl:value-of select="TickerSymbol"/>,
</xsl:for-each>
What is the best way to get rid of the trailing comma? Is there something better than xsl:for-each?
<xsl:for-each select="/Tickers/Ticker">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="TickerSymbol"/>
</xsl:for-each>
In XSLT 2.0 you could do it (without a for-each) using the string-join function:
<xsl:value-of select="string-join(/Tickers/Ticker, ',')"/>
In XSLT 1.0, another alternative to using xsl:for-each would be to use xsl:apply-templates
<xsl:template match="/">
<!-- Output first element without a preceding comma -->
<xsl:apply-templates select="/Tickers/Ticker[position()=1]" />
<!-- Output subsequent elements with a preceding comma -->
<xsl:apply-templates select="/Tickers/Ticker[position()>1]">
<xsl:with-param name="separator">,</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Ticker">
<xsl:param name="separator" />
<xsl:value-of select="$separator" /><xsl:value-of select="TickerSymbol" />
</xsl:template>
I know you said xsl 2.0 is not an option and it has been a long time since the question was asked, but for all those searching for a posibility to do what you wanted to achieve:
There is an easier way in xsl 2.0 or higher
<xsl:value-of separator=", " select="/Tickers/Ticker/TickerSymbol" />
This will read your /Tickers/Ticker elements and insert ', ' as separator where needed
If there is an easier way to do this I am looking forward for advice
Regards Kevin

Using xsl:variable in a xsl:foreach select statement

I'm trying to iterate through an xml document using xsl:foreach but I need the select=" " to be dynamic so I'm using a variable as the source. Here's what I've tried:
...
<xsl:template name="SetDataPath">
<xsl:param name="Type" />
<xsl:variable name="Path_1">/Rating/Path1/*</xsl:variable>
<xsl:variable name="Path_2">/Rating/Path2/*</xsl:variable>
<xsl:if test="$Type='1'">
<xsl:value-of select="$Path_1"/>
</xsl:if>
<xsl:if test="$Type='2'">
<xsl:value-of select="$Path_2"/>
</xsl:if>
<xsl:template>
...
<!-- Set Data Path according to Type -->
<xsl:variable name="DataPath">
<xsl:call-template name="SetDataPath">
<xsl:with-param name="Type" select="/Rating/Type" />
</xsl:call-template>
</xsl:variable>
...
<xsl:for-each select="$DataPath">
...
The foreach threw an error stating: "XslTransformException - To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
When I use the msxsl:node-set() function though, my results are blank.
I'm aware that I'm setting $DataPath to a string, but shouldn't the node-set() function be creating a node set from it? Am I missing something? When I don't use a variable:
<xsl:for-each select="/Rating/Path1/*">
I get the proper results.
Here's the XML data file I'm using:
<Rating>
<Type>1</Type>
<Path1>
<sarah>
<dob>1-3-86</dob>
<user>Sarah</user>
</sarah>
<joe>
<dob>11-12-85</dob>
<user>Joe</user>
</joe>
</Path1>
<Path2>
<jeff>
<dob>11-3-84</dob>
<user>Jeff</user>
</jeff>
<shawn>
<dob>3-5-81</dob>
<user>Shawn</user>
</shawn>
</Path2>
</Rating>
My question is simple, how do you run a foreach on 2 different paths?
Try this:
<xsl:for-each select="/Rating[Type='1']/Path1/*
|
/Rating[Type='2']/Path2/*">
Standard XSLT 1.0 does not support dynamic evaluation of xpaths. However, you can achieve your desired result by restructuring your solution to invoke a named template, passing the node set you want to process as a parameter:
<xsl:variable name="Type" select="/Rating/Type"/>
<xsl:choose>
<xsl:when test="$Type='1'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path1/*"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$Type='2'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path2/*"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
...
<xsl:template name="DoStuff">
<xsl:param name="Input"/>
<xsl:for-each select="$Input">
<!-- Do stuff with input -->
</xsl:for-each>
</xsl:template>
The node-set() function you mention can convert result tree fragments into node-sets, that's correct. But: Your XSLT does not produce a result tree fragment.
Your template SetDataPath produces a string, which is then stored into your variable $DataPath. When you do <xsl:for-each select="$DataPath">, the XSLT processor chokes on the fact that DataPath does not contain a node-set, but a string.
Your entire stylesheet seems to be revolve around the idea of dynamically selecting/evaluating XPath expressions. Drop that thought, it is neither possible nor necessary.
Show your XML input and specify the transformation your want to do and I can try to show you a way to do it.

How to implement Carriage return in XSLT

I want to implement carriage return within xslt.
The problem is I have a varible:
Step 1 = Value 1 breaktag Step 2 = Value 2 as a string and would like to appear as
Step 1 = Value 1
Step 2 = Value 2
in the HTML form but I am getting the br tag on the page.Any good ways of implementing a line feed/carriage return in xsl would be appreciated
As an alternative to
<xsl:text>
</xsl:text>
you could use
<xsl:text>
</xsl:text> <!-- newline character -->
or
<xsl:text>
</xsl:text> <!-- carriage return character -->
in case you don't want to mess up your indentation
This works for me, as carriage-return + life feed.
<xsl:text>
</xsl:text>
The "
" string does not work.
The cleanest way I've found is to insert !ENTITY declarations at the top of the stylesheet for newline, tab, and other common text constructs. When having to insert a slew of formatting elements into your output this makes the transform sheet look much cleaner.
For example:
<?xml version="1.0"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY nl "<xsl:text>
</xsl:text>">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="step">
&nl;&nl;
<xsl:apply-templates />
</xsl:template>
...
</xsl:stylesheet>
use a simple carriage return in a xsl:text element
<xsl:text>
</xsl:text>
Try this at the end of the line where you want the carriage return. It worked for me.
<xsl:text><![CDATA[<br />]]></xsl:text>
I was looking for a nice solution to this, as many would prefer, without embedding escape sequences directly in the expressions, or having weird line breaks inside of a variable. I found a hybrid of both this approaches actually works, by embedding a text node inside a variable like this:
<xsl:variable name="newline"><xsl:text>
</xsl:text></xsl:variable>
<xsl:value select="concat(some_element, $newline)" />
Another nice side-affect of this is that you can pass in whatever newline you want, be it just LF, CR, or both CRLF.
--Daniel
Here is an approach that uses a recursive template, which looks for
in the string from the database and then outputs the substring before.
If there is a substring after
remaining, then the template calls itself until there is nothing left.
In case
is not present then the text is simply output.
Here is the template call (just replace #ActivityExtDescription with your database field):
<xsl:call-template name="MultilineTextOutput">
<xsl:with-param name="text" select="#ActivityExtDescription" />
</xsl:call-template>
and here is the code for the template itself:
<xsl:template name="MultilineTextOutput">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<xsl:variable name="text-before-first-break">
<xsl:value-of select="substring-before($text, '
')" />
</xsl:variable>
<xsl:variable name="text-after-first-break">
<xsl:value-of select="substring-after($text, '
')" />
</xsl:variable>
<xsl:if test="not($text-before-first-break = '')">
<xsl:value-of select="$text-before-first-break" /><br />
</xsl:if>
<xsl:if test="not($text-after-first-break = '')">
<xsl:call-template name="MultilineTextOutput">
<xsl:with-param name="text" select="$text-after-first-break" />
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" /><br />
</xsl:otherwise>
</xsl:choose>
Works like a charm!!!
I believe that you can use the xsl:text tag for this, as in
<xsl:text>
</xsl:text>
Chances are that by putting the closing tag on a line of its own, the newline is part of the literal text and outputted as such.
I separated the values by Environment.NewLine and then used a pre tag in html to emulate the effect I was looking for
This is the only solution that worked for me. Except I was replacing
with \r\n