substring XSLT over multiple value lines for an atttribute - xslt

I know the following xslt will work:
<xsl:attribute name="test">
<xsl:value-of select="substring(title, 1, 4000)"/>
</xsl:attribute>
But not sure what to do if there is something like the following and you want the substring over the whole attribute value not just the title or the substitle.
<xsl:attribute name="test">
<xsl:value-of select="title"/>
<xsl:if test="../../sub_title != ''">
<xsl:text> </xsl:text>
<xsl:value-of select="../sub_title"/>
</xsl:if>
</xsl:attribute>
Is it even possible to apply a substring function over multiple lines that define an attribute?

I think what you are saying is that you want to build up a long string, consisting of the values of a number of other elements, and then truncate the result.
What you could do, is use the concat function to build the attribute value, and then do a substring on that.
<xsl:attribute name="test">
<xsl:value-of select="substring(concat(title, ' ', ../sub_title), 1, 4000)" />
</xsl:attribute>
In this case, if sub_title was empty, you would end up with a space at the end of the test attribute, so you might want to add a normalize-space to this expression
<xsl:value-of select="normalize-space(substring(concat(title, ' ', ../sub_title), 1, 4000))" />
An alternate approach, if you did want to use a more complicated expression, is to do the string calculation in a variable first
<xsl:variable name="test">
<xsl:value-of select="title"/>
<xsl:if test="../../sub_title != ''">
<xsl:text> </xsl:text>
<xsl:value-of select="../sub_title"/>
</xsl:if>
</xsl:variable>
<xsl:attribute name="test">
<xsl:value-of select="substring($test, 1, 4000)" />
</xsl:attribute>
As an aside, you can simplify your code by using "Attribute Value Templates" here, instead of using the more verbose xsl:attribute command. Simply do this..
<myElement test="{substring($test, 1, 4000)}">
Here, the curly braces indicate an expression to be evaluated, rather than output literally.

Related

How to add style attribute in xsl by #variable

I have a variable #expectedLength. I need to assign it to a style attribute.
<xsl:if test="#expectedLength">
<xsl:attribute name="style">
<xsl:value-of select="'width:200px'"/>
</xsl:attribute>
</xsl:if>
I need to replace 200 with the value of #expectedLength. How can I use the variable?
You could change your snippet to
<xsl:if test="#expectedLength">
<xsl:attribute name="style">width: <xsl:value-of select="#expectedLength"/>;</xsl:attribute>
</xsl:if>
That should work with any version of XSLT.
In XSLT 2 and later you can also use the select expression
<xsl:if test="#expectedLength">
<xsl:attribute name="style" select="concat('width: ', #expectedLength, ';')"/>
</xsl:if>
I would prefer to and suggest to set up a template
<xsl:template match="#expectedLength">
<xsl:attribute name="style" select="concat('width: ', #expectedLength, ';')"/>
</xsl:template>
and then to make sure higher up that any attribute nodes are processed.

XSLT 2.0 tokenising delimiters within delimiters

In XSLT 2.0 I have long string (parameter) with a delimiter (;) inside a delimiter (~), more specifically a triplet inside a delimiter.
Data is organized like so:
<parameter>qrsbfs;qsvsv;tfgz~dknk;fvtea;gtvath~pksdi;ytbdi;oiunhu</parameter>
The first tokenize($mystring,'~') in a for-each produces :
qrsbfs;qsvsv;tfgz
dknk;fvtea;gtvath
pksdi;ytbdi;oiunhu
Within that tokenization, I need to treat it by looping again:
qrsbfs
qsvsv
tfgz
dknk
fvtea
gtvath
pksdi
ytbdi
oiunhu
I can do intensive string manipulation to get there using concat, string-length, and substring-before/substring-after, but I wondered if there wasn't a more elegant solution that my neophyte mind wasn't overlooking?
EDIT, adding nested tokenize that returned incorrect results:
<xsl:for-each select="tokenize($myparameter,'~')">
<xsl:for-each select="tokenize(.,';')">
<xsl:if test="position()=1">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position()=2">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position()=3">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
If you wanted a one line solution, you could do something like this, using nested for-in-return statements:
<xsl:sequence select="for $n in tokenize(.,'~') return concat(string-join(tokenize($n,';'),'
'),'
')"/>
If you don't need to tokenize them separately, you could replace the ~ with ; and tokenize all 9 elements at the same time:
tokenize(replace(parameter,'~',';'),';')
For what it's worth, the code in https://xsltfiddle.liberty-development.net/pPqsHUe uses
<xsl:template match="parameter">
<xsl:for-each select="tokenize(., '~')">
<xsl:value-of select="tokenize(., ';')" separator="
"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
and with output method text produces
qrsbfs
qsvsv
tfgz
dknk
fvtea
gtvath
pksdi
ytbdi
oiunhu

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.

XSL for each loop selecting position of first occurrence

In the following code snippet, I'm trying to get position of EMP_ID field from the available fields. This works fine if there's just one occurrence of EMP_ID.
But if there are more than one occurences then variable 'empid_field' will have positions of all the occurrences appended one after the other. i.e if EMP_ID is at postions 1, 8, and 11, then 'empid_field' would be '1811'.
Is there any way I get position of first occurrence only? Or Can I get comma separated positions atleast? (Code sample would be highly appreciated as I'm new to XSL programming)
<xsl:variable name="empid_field">
<xsl:for-each select="$fields">
<xsl:if test="internalName='EMP_ID'">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
The easiest solution which is in my mind is to extend this. But I think there are also solutions which look more pretty.
<xsl:variable name="empid_field">
<xsl:for-each select="$fields">
<xsl:if test="internalName='EMP_ID'">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="first_empid_field">
<xsl:value-of select="$empid_field[1]"/>
</xsl:variable>
The variable $first_empid_field will only have the first position value.
Ok got something ...
Created a comma separated string and picked the part before the delimiter.
<xsl:variable name="empid_fields" >
<xsl:for-each select="$fields">
<xsl:if test="internalName='EMP_ID'">
<xsl:value-of select="position()" />
<xsl:text >, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="empid_field" >
<xsl:value-of select="substring-before($empid_fields, ', ')" />
</xsl:variable>

Removing white space inside quotes in XSLT

I'm trying to format a table from XML. Lets say I have this line in the XML
<country>Dominican Republic</country>
I would like to get my table to look like this
<td class="country DominicanRepublic">Dominican Republic</td>
I've tried this:
<td class="country {country}"><xsl:value-of select="country"/></td>
then this:
<xsl:element name="td">
<xsl:attribute name="class">
<xsl:text>country </xsl:text>
<xsl:value-of select="normalize-space(country)"/>
</xsl:attribute>
<xsl:value-of select="country"/>
</xsl:element>
The normalize-space() doesn't remove the space between the two parts of the name and I can't use <xsl:strip-space elements="country"/> because I need the space when I display the name inside the table cell.
How can I strip the space from the value inside the class, but not the text in the cell?
Use the translate() function to replace spaces ' ' with nothing '':
<xsl:element name="td">
<xsl:attribute name="class">
<xsl:text>country </xsl:text>
<xsl:value-of select="translate(country,' ','')"/>
</xsl:attribute>
<xsl:value-of select="country"/>
</xsl:element>
You can use normalize-space(), which will remove any leading and trailing white space and convert multiple spaces between characters into a single space. Then, send the results through translate() to replace any remaining spaces:
<xsl:element name="td">
<xsl:attribute name="class">
<xsl:text>country </xsl:text>
<xsl:value-of select="translate(normalize-space(country),' ','')"/>
</xsl:attribute>
<xsl:value-of select="normalize-space(country)"/>
</xsl:element>
You will need to split your string by whitespaces recursively, have a look at this topic:
Does XSLT have a Split() function?
Or you can try this replace function implementation: http://geekswithblogs.net/Erik/archive/2008/04/01/120915.aspx