Lowercase conversion in XSL - xslt

I have an XML like
<emps>
<emp id='3432'>
<fname>Jack</fname>
<lname>Dawson</lname>
<emp>
<emp id='1122'>
<fname>Jack</fname>
<lname>Thompson</lname>
<emp>
<emps>
I am developing a web application which searches this xml based on the first name entered and comes up with a resultant page. To achieve this I have written an xslt to transform the XML to HTML based on the input search string which is passed as a variable named srchStr.
<xsl:template match="employees">
<xsl:for-each select="emp[fname=$srchStr]">
<tr>
<xsl:variable name="id">
<xsl:value-of select="#id" />
</xsl:variable>
<td>
<a href='detailSearch.do?id={$id}'>
<xsl:value-of select="fname" />
,
<xsl:value-of select="lname" />
</a>
</td>
</tr>
</xsl:for-each
</xsl:template>
But the user may enter the name either in upper case or lower case. So how to convert the first name inside the xml tag fname to lower case and do the comparison?
Can some one put a code snippet to use fn:lower-case inside my xsl.

To convert a string to lower case or uppercase you can use the XPath 1.0 function translate:
First define your alphabets for lower case and upper case letters. Note that the position of each pair of characters needs to be the same:
<xsl:variable name="lcletters">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="ucletters">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>
Then you can convert to upper case:
<xsl:value-of select="translate($toconvert,$lcletters,$ucletters)"/>
or to lower case
<xsl:value-of select="translate($toconvert,$ucletters,$lcletters)"/>

emp[lower-case(fname)=lower-case($srchStr)]
Or, if you have XPath 1.0 only, you may try using translate like here:
http://geekswithblogs.net/TimH/archive/2006/07/06/84229.aspx
Be warned though, the example with translate would not work on names with accents (like mine :)

Related

optimization of XSLT code

While searching for some profiling tools for XSLT, I came across this post. Since a lot of people there suggested to just post the code and offered to give feedback on that, I was wondering if anyone could give me some feedback on mine. I tried this (http://www.saxonica.com/documentation/#!using-xsl/performanceanalysis), but the output html is not very detailed.
I'm new to XSLT and usually work with python/perl, where regex support is much better (however, I won't rule out the possibility that it's just my very basic understanding of XSLT). For the purpose of this project however, I had to work with XSLT. It could be that I'm forcing it to do things in a very unnatural way. Any comments -on performance in particular, but anything else is also welcome, as I'd like to learn- are welcome!
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:template name="my_terms">
<xsl:variable name="excludes" select="not (codeblock or draft-comment or filepath or shortdesc or uicontrol or varname)"/>
<!-- leftover example of how to work with excludes var -->
<!--<xsl:if test=".//*[$excludes]/text()[contains(.,'access management console')]"><li class="prodterm"><b>PB QA:access management console should be "AppCenter"</b></li></xsl:if>-->
<!-- Loop through all sentences and check for deprecated stuff -->
<xsl:for-each select=".//*[$excludes]/text()">
<xsl:variable name="sentenceList" select="tokenize(., '[\.!\?:;]\s+')"/>
<xsl:variable name="segment" select="."/>
<!-- main sentence loop -->
<xsl:for-each select="$sentenceList">
<xsl:variable name="sentence" select="."/>
<!-- very rudimentary sentence length check -->
<xsl:if test="count(tokenize(., '\W+')) > 30"> <li class="prodterm"><b>Sentence too long:</b> <xsl:value-of select="."/></li></xsl:if>
<!-- efforts to flag the shady case of the gerund -->
<xsl:if test="matches(., '\w+ \w+ing (the|a)')">
<!-- some extra checks to weed out the false positives -->
<xsl:if test="not(matches(., '\b(on|about|for|before|while|when|after|by|a|the|an|some|all|every) \w+ing (the|a)', '!i')) and not(matches(., 'during'))">
<li class="prodterm"><b>Possible unclear usage of gerund. If so, consider rewriting:</b> <xsl:value-of select="."/></li>
</xsl:if>
</xsl:if>
<!-- comma's after certain starting phrases -->
<xsl:if test="matches(., '^\s*Therefore[^,]')"><li class="prodterm"><b>Use a comma after starting a sentence with 'Therefore':</b> <xsl:value-of select="."/></li></xsl:if>
<xsl:if test="matches(., '^\s*(If you|Before|When)[^,]+$')"><li class="prodterm"><b>Use a comma after starting a sentence with 'Before', 'If you' or 'When':</b> <xsl:value-of select="."/></li></xsl:if>
<!-- experimenting with phrasal verbs (if there are a lot of verbs in phrasalVerbs.xml, it will be better to have this as the main loop (and do it outside the sentence loop)) -->
<xsl:for-each select="document('phrasalVerbs.xml')/verbs/verb[matches($sentence, concat('.* ', ./#text, ' .*'))]">
<xsl:variable name="verbPart" select="."/>
<xsl:for-each select="$verbPart/particles/particle/#text[matches($sentence, .) and not(matches($sentence, concat($verbPart/#text, ' ', .)))]">
<xsl:variable name="particle" select="."/>
<li class="prodterm"><b>Separated phrasal verb found in:</b> <xsl:value-of select="$sentence"/></li>
</xsl:for-each>
</xsl:for-each>
<!-- checking if conditionals (should be followed by then) -->
<xsl:if test="matches($sentence, '^\s*If\b', '!i') and not(matches($sentence, '\bthen\b', '!i'))"><li class="prodterm"><b>Conditional If found, but no then:</b> <xsl:value-of select="."/></li></xsl:if>
<!-- very dodgy way of detecting passive voice -->
<!--<xsl:if test="matches($sentence, '\b(are|can be|must be) \w+ed\b', '!i')"><li class="prodterm"><b>PB QA:Possible passive voice. If so, consider using active voice for:</b> <xsl:value-of select="."/></li></xsl:if>-->
<xsl:for-each select='document("generalDeprecatedTermsAndPhrases.xml")/terms/dt'>
<xsl:variable name="pattern" select="./#pattern"/>
<xsl:variable name="message" select="./#message"/>
<xsl:variable name="regexFlag" select="./#regexFlag"/>
<!-- <xsl:if test="matches($sentence, $pattern, $regexFlag)"> -->
<xsl:if test="matches($sentence, concat('(^|\W)', $pattern, '($|\W)'), $regexFlag)"> <!-- This is the work around for not being able to use \b when variable is passed on inside matches() -->
<li class="prodterm"><b><xsl:value-of select="$message"/> in: </b> <xsl:value-of select="$sentence"/> </li>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
To get an idea, the stripped down version of my "generalDeprecatedTermsAndPhrases.xml" looks like this:
<dt pattern='to be able to' message="Use 'to' instead of 'to be able to'" regexFlag="i"></dt>
</terms>
The reason that Saxon's profile is not very detailed is that your code is so monolithic: it's all in one great big template rule.
However, being monolithic isn't by itself the cause of any performance problems.
First observation is a functionality problem: your variable
<xsl:variable name="excludes" select="not (codeblock or draft-comment or filepath or shortdesc or uicontrol or varname)"/>
doesn't do what you think. It's evaluated with the root document node as the context item, and its value is a boolean which is true if the outermost element has a name which is not one of those listed. So I think your xsl:for-each that uses [$excludes] as a predicate is applying to all elements, whereas I suspect you intended it to apply to selected elements. I don't know how much that affects the performance.
The main influence on performance will be the cost of evaluating the regular expressions. The best way to find out which ones are causing the problem is to measure the impact of removing them one-by-one. When you've isolated the problem, there may be a way of rewriting the regular expression to make it perform better (e.g. by making it avoid backtracking).

How to prevent self closing tags as well empty tags after transforming

I have in an input file:
<a></a>
<b/>
<c>text</c>
I need to converting this to string. Using transformer I am getting below output:
<a/> <!-- Empty tags should not collapse-->
<b/>
<c>text</c>
If I use xslt and output method is "HTML", I get the below output:
<a></a> <!-- This is as expected-->
<b></b> <!-- This is not expected-->
<c>text</c>
I want the structure same as in input file. It is required in my application since I need to calculate index and it will be very difficult to change the index calution logic.
What would be the correct XSLT to use?
What XSLT processor? XSLT is merely a language to transform xml so "html output" is dependent on the processor.
I'm going to guess this first solution is too simple for you but i've had to use this to avoid processing raw html
<xsl:copy-of select="child::node()" />
as this should clone the raw input.
In my case, I have used the following to extract all nodes that had the raw attribute:
<xsl:for-each select="xmlData//node()[#raw]">
<xsl:copy-of select="child::node()" />
</xsl:for-each>
Other options:
2) Add an attribute to each empty node depending on what you want it to do later ie role="long", role="short-hand".
3)
Loop through each node (xsl:for-each)
<xsl:choose>
<xsl:when test="string-length(.)=0"> <!-- There is no child-->
<xsl:copy-of select="node()" />
</xsl:when>
<xsl:otherwise>
...whatever normal processing you have
</xsl:otherwise>
4) Redefine your problem. Both are valid XHTML/XML, so perhaps your problem can be reframed or fixed elsewhere.
Either way, you may want to add more information in your question so that we can reproduce your problem and test it locally.
P.S. Too much text/code to put in a comment, but that's where this would belong.
A possible alternative is to use disable-output-escaping like this:
<xsl:text disable-output-escaping="yes"><a></a></xsl:text>
But I understand that this is a dirty solution...

Need XSLT for multi-line item

I already tried a lot of things here on stack overflow but I still face the same problem.
Let me try to explain my issue and what I need to achieve. For this I have the following XML:
<authorizationGroups>
<authorizationGroup> <!-- can be multiple -->
<name>OGroup 1</name>
<application> <!-- can be multiple -->
<uid>646</uid>
<applicationFunctions> <!-- can be multiple -->
<name>auth function 11</name>
<name>auth function 12</name>
</applicationFunctions>
</application>
<role>5000682864</role>
<role>5000685391</role>
</authorizationGroup>
<authorizationGroup> <!-- can be multiple -->
<name>OGroup 8</name>
<application> <!-- can be multiple -->
<uid>646</uid>
<applicationFunctions> <!-- can be multiple -->
<name>auth function 13</name>
<name>auth function 14</name>
</applicationFunctions>
</application>
<role>5000683374</role>
<role>5000685391</role>
</authorizationGroup>
I need to get out something like this:
<resource-types>
<resource-types>
<resource-type>
<name>OGroup 1</name>
<actions>
auth function 11,
auth function 12
</actions>
</resource-type>
<resource-type>
<name>OGroup 8</name>
<actions>
auth function 13,
auth function 14
</actions>
</resource-type>
</resource-types>
My problem is that when I use the XSLT I always end up with all the "auth functions xx" within one .
My current code snippet looks like this (there is obviously more than this part):
<resource-types>
<xsl:call-template name="resource_types"/>
</resource-types>
<xsl:template name="resource_types">
<resource-types>
<xsl:for-each select="/authorizationGroups/authorizationGroup/name">
<resource-type>
<name>
<xsl:value-of select="text()"/>
</name>
<actions>
<xsl:for-each select="/authorizationGroups/authorizationGroup/application/applicationFunctions">
<xsl:value-of select=".//text()"/>
,
</xsl:for-each>
</actions>
</resource-type>
</xsl:for-each>
</resource-types>
</xsl:template>
Now I receive all "auth functions xx" in one go. My understanding of XSLT is limited, so my main question is how can I limit the search for the to the part of the XML document I am in.
I assumed that this was a very easy action, but after three days research on the net and stack overflow I haven't come up with an answer.
cu
Andreas
The issue is, I think, is that you are iterating over name elements, like so...
<xsl:for-each select="/authorizationGroups/authorizationGroup/name">
However, when you come to get the applicationFunctions within this xsl:for-each, you do this...
<xsl:for-each
select="/authorizationGroups/authorizationGroup/application/applicationFunctions">
Because of the forward slash at the start of the expression, this will get all applicationFunctions relative to the root of the document, and not the authorizationGroup you are currently in.
What you need to do is this...
<xsl:for-each select="../application/applicationFunctions">
The .. is to get the parent element, because you are currently positioned on the name element which is at the same level as the application element.
Actually, what would be slighty better would be it initially itereate over authorizationGroup to start with
<xsl:for-each select="/authorizationGroups/authorizationGroup">
And then iterate over the applicationFunctions like so
<xsl:for-each select="application/applicationFunctions">
Either enclose the line break in <xsl:text> like (untested):
<xsl:for-each select="/authorizationGroups/authorizationGroup/application/applicationFunctions">
<xsl:value-of select=".//text()"/>
<xsl:text>,
</xsl:text>
</xsl:for-each>
or include it as an escape in <xsl:value-of> like (untested):
<xsl:value-of select="concat(.//text(), ',
')"/>

XSLT - Preserving disable-output-escaping in a copy-of

I seem to be having an issue preserving the disable-output-escaping when using that value inside of an xsl:copy-of.
Here's my code:
<xsl:call-template name="Display">
<xsl:with-param name="text">
<xsl:value-of select="content" disable-output-escaping="yes" />
</xsl:with-param>
</xsl:call-template>
<xsl:template name="Display">
<xsl:param name="text" />
<span><xsl:copy-of select="$text" /></span>
</xsl:template>
Any special characters that were kept as-is from the xsl:value-of statement are escaped when they're used in the xsl:copy-of statement.
For example:
<xsl:value-of select="$text" disable-output-escaping="yes"> will display this: รจ
<xsl:copy-of select="$text"> will display &#232
I'd like to know if there is any way around this?
As per Spec, the disable-output-escaping attribute can be specified only on <xsl:value-of> and the <xsl:text> instructions.
You need the DOE only on the xslt instruction that actually outputs the value, not on one that sets a parameter value.
Solution:
Replace:
<span><xsl:copy-of select="$text"/></span>
with:
<span><xsl:value-of select="$text" disable-output-escaping="yes"/></span>
Do note: Typically one should avoid using DOE, as it breaks the XSLT architectural model and usually isn't needed. Also, the DOE feature isn't mandatory and not all XSLT 1.0 processors support it.
Note 2: You don't actually need DOE in your case at all. The output from the XSLT transformation should be displayed by the browser as expected.
disable-output-escaping controls the action of the serializer when handed a text node. It's meaningless when the text node isn't being handed to a serializer, for example when it is added to a temporary tree.

XSL: How best to store a node in a variable and then use it in future xpath expressions?

I need to be able to store a node set in variable and then perform more filting/sorting on it afterward. All the examples I've seen of this involve either using XSL2 or extensions neither of which are really an option.
I've a list of hotels in my XML doc that can be sorted/filtered and then paged through 5 at a time. I'm finding though I'm repeating alot of the logic as currently I've not found a good way to store node-sets in xsl variable and then use xpath on them for further filtering/sorting.
This is the sort of thing I'm after (excuse the code written of the top of my head so might not be 100%):
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="3_star_or_less" select="/results/hotels[number(rating) <= 3]" />
<xsl:for-each select="3_star_or_less">
<xsl:sort select="rating" />
</xsl:for-each>
Has anyone got an example of how best to do this sort of thing?
Try this example:
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="three_star_or_less"
select="$hotels[number(rating) <= 3]" />
<xsl:for-each select="$three_star_or_less">
<xsl:sort select="rating" />
<xsl:value-of select="rating" />
</xsl:for-each>
There is no problem storing a node-set in a variable in XSLT 1.0, and no extensions are needed. If you just use an XPath expression in select attribute of xsl:variable, you'll end up doing just that.
The problem is only when you want to store the nodes that you yourself had generated in a variable, and even then only if you want to query over them later. The problem here is that nodes you output don't have type "node-set" - instead, they're what is called a "result tree fragment". You can store that to a variable, and you can use that variable to insert the fragment into output (or another variable) later on, but you cannot use XPath to query over it. That's when you need either EXSLT node-set() function (which converts a result tree fragment to a node-set), or XSLT 2.0 (in which there are no result tree fragments, only sequences of nodes, regardless of where they come from).
For your example as given, this doesn't seem to be a problem. Rubens' answer gives the exact syntax.
Another note, if you want to be able to use the variable as part of an XPath statement, you need to select into the variable with <xsl:copy-of select="."/> instead of <xsl:value-of select="."/>
value-of will only take the text of the node and you wont be able to use the node-set function to return anything meaningful.
<xsl:variable name="myStringVar">
<xsl:value-of select="."/>
</xsl:variable>
<!-- This won't work: -->
<Output>
<xsl:value-of select="node-set($myStringVar)/SubNode" />
</Output>
<xsl:variable name="myNodeSetVar">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- This will work: -->
<Output>
<xsl:value-of select="node-set($myNodeSetVar)/SubNode" />
</Output>