match cases based on range with alphabets - xslt

I've the below XML.
<para>39B</para>
<para>76</para>
Here I'm able to match the exact number, but the problem came up when there is a range mentioned like below.
if the range 39A-39P
print case1
if the range 72-85
print case2
The code i tried
<xsl:template match="text()">
<xsl:analyze-string select="." regex="([\w]+)">
<xsl:matching-substring>
<xsl:variable name="regex1">
<xsl:choose>
<xsl:when test="contains(regex-group(1), '^39[A-P]$')">
<xsl:text>CAse 1</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Case 2</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select = "$regex1"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
please let me know how can i solve this.
Thanks

With XSLT 2.0 you can use regular expressions so for the alphanumeric case you can use
<xsl:template match="para[matches(., '^39[A-P]$')]">case1</xsl:template>
and a simple number comparison for the second case
<xsl:template match="para[. ge 72 and . le 85]">case2</xsl:template>

Related

XSL - Removing text blocks from string

I have one question regarding xslt. In my input I have strings like:
<dxrCardNumber2>[EBERG615] [104699] [104913]</dxrCardNumber2>
and I have to remove all brackets and values between the brackets, if they contains a letter.
The result should be:
=> <dxrCardNumber2>104699 104913</dxrCardNumber2>
The position of the data I have to remove is random. I tried it with tokenize and then I can filter the not relevant entries out. But at teh end I have the problem to combine all entries again in one string.
Assuming you can use XSLT 2.0 or higher, you could do:
<xsl:template match="dxrCardNumber2">
<xsl:copy>
<xsl:value-of select="tokenize(translate(., '[]', ''), ' ')[matches(., '^\d+$')]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="dxrCardNumber2">
<xsl:analyze-string select="." regex="\[([A-Z0-9]+)\]\s+\[([0-9]+)\]\s+\[([0-9]+)\]" flags="i">
<xsl:matching-substring>
<dxrCardNumber2><xsl:value-of select="concat(regex-group(2),' ')"/>
<xsl:value-of select="regex-group(3)"/></dxrCardNumber2>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>

idiomatic alternative to choose -> test -> value-of (XSLT 1.0)

In the work I do I seem to see a lot of code liek this..
<xsl:choose>
<xsl:when test="long_xpath_to_optional/#value1">
<xsl:value-of select="long_xpath_to_optional/#value"/>
</xsl:when>
<xsl:when test="another_long_xpath_to_optional/#value">
<xsl:value-of select="another_long_xpath_to_optional/#value"/>
</xsl:when>
<etc>
</etc>
<otherwise>
<xsl:value-of select="default_long_xpath_to_value"/>
</otherwise>
</xsl:choose>
its very long and very repetitive.
When I'm were working in some other (psuedo) language I would go
let values = concat(list(long_xpath_to_optional_value),list(another_long_xpath_to_optional_value))
let answer = tryhead(values,default_long_xpath_to_value)
i.e. create a list of values in priority order, and then take the head.
I only evaluate each path once
how would you do something similar in XSLT 1.0 (we can use node-sets).
I was wondering if you can create a node-set somehow
You can - but it's not going to be any shorter:
<xsl:variable name="values">
<xsl:apply-templates select="long_xpath_to_optional/#value" mode="values"/>
<xsl:apply-templates select="another_long_xpath_to_optional/#value" mode="values"/>
<xsl:apply-templates select="default_long_xpath_to_value/#value" mode="values"/>
</xsl:variable>
<xsl:value-of select="exsl:node-set($values)/value[1]" xmlns:exsl="http://exslt.org/common"/>
and then:
<xsl:template match="#value" mode="values">
<value>
<xsl:value-of select="."/>
</value>
</xsl:template>
But at least the repetition is eliminated.
Alternatively, you could do:
<xsl:template match="#value" mode="values">
<xsl:value-of select="."/>
<xsl:text>|</xsl:text>
</xsl:template>
and then:
<xsl:value-of select="substring-before($values, '|')"/>
To use variables you write
<xsl:variable name="value1" select="long_xpath_to_optional/#value1"/>
<xsl:variable name="value2" select="another_long_xpath_to_optional/#value"/>
<xsl:variable name="value3" select="default_long_xpath_to_value"/>
and then in XPath 2 or 3 all you would need is ($value1, $value2, $value3)[1] or head(($value1, $value2, $value3)) but in XSLT 1 with XPath 1 all you can write as a single expression is ($value1 | $value2 | $value3)[1] which sorts in document order so unless the document order is the same as your test order this wouldn't work to check the values; rather you would need to maintain the
<xsl:choose>
<xsl:when test="$value1">
<xsl:value-of select="$value1"/>
</xsl:when>
<xsl:when test="$value2">
<xsl:value-of select="$value2"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value3"/>
</xsl:otherwise>
</xsl:choose>
Of course in XPath 2 you wouldn't really need the variables and could use (long_xpath_to_optional/#value1, another_long_xpath_to_optional/#value, default_long_xpath_to_value)[1] as well directly.

convert into camel case based on the string position

I've the below XML.
<title>1. IN AGENTS IN GENERAL</title>
Here i'm trying to convert this into camel case, and if there are a set of words, if they occur they have to be changed into lower case. But the condition is, if these words come in starting, they have to be changed into camel case, else lower case.
And my XSL is
<xsl:template match="title">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="title/page"/>
<xsl:param name="Conjunction">^(of|to|and|the|for|on|or|an|as|by|of|it|between|with|in|into|on|onto|here|a)$</xsl:param>
<xsl:template match="title/text()">
<xsl:analyze-string select="." regex="(\w)(\w*)">
<xsl:matching-substring>
<xsl:value-of
select="if (matches(., $Conjunction, 'i'))
then lower-case(.)
else concat(upper-case(regex-group(1)), lower-case(regex-group(2)))"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
and as per the input the Expected output should be
1. In Agents in General
but what is get is
1. in Agents in General
here is the working demo. DEmo
please let me know how can i get this done.
Thanks
If you can assume a full-stop delimits each sentence, and you want the first word of each sentence to be capitalised regardless, you could tokenize the text by full-stop...
<xsl:for-each select="tokenize(., '\.')">
You then apply your regular expression on each part separately, but include a check on the position in the matching-substring so that it doesn't convert the first match to lower case.
Try this template
<xsl:template match="title/text()">
<xsl:for-each select="tokenize(., '\.')">
<xsl:if test="position() > 1">.</xsl:if>
<xsl:analyze-string select="." regex="(\w)(\w*)">
<xsl:matching-substring>
<xsl:value-of
select="if (matches(., $Conjunction, 'i') and position() > 2)
then lower-case(.)
else concat(upper-case(regex-group(1)), lower-case(regex-group(2)))"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:for-each>
</xsl:template>
In your current code, try changing:
<xsl:value-of select="if (matches(., $Conjunction, 'i'))
to
<xsl:value-of select="if (matches(., $Conjunction, 'i') and position() != 3)

regex pattern match in XSLT

I've the below XSL template
<xsl:template match="para">
<xsl:analyze-string select="." regex="\d+(?=[-, \d]*$)">
<xsl:matching-substring>
<xsl:variable name="prent">
<xsl:for-each select="document('C:\Users\u0138039\Desktop\Proview\SG\Commentary_SG_XML-03032014\SG-Singapore Precedents of Pleadings\title.xml')/entry/file">
<xsl:value-of select="normalize-space(document(concat('C:\Users\u0138039\Desktop\Proview\SG\Commentary_SG_XML-03032014\SG-Singapore Precedents of Pleadings\',./#name))//chapter/title[//page/#num=regex-group(1)])"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cha">
<xsl:value-of select="substring-before(substring-after($prent,'CHAPTER '),' ')"/>
</xsl:variable>
<xsl:variable name="size">
<xsl:value-of select="string-length($cha)"/>
</xsl:variable>
<xsl:variable name="conct">
<xsl:choose>
<xsl:when test="$size>'1'">
<xsl:value-of select="concat('er:#SPPR_CH_',$cha,'/pg_',regex-group(1))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('er:#SPPR_CH_0',$cha,'/pg_',regex-group(1))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<a href="{$conct}">
<xsl:value-of select="regex-group(1)"/>
</a>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
and below XML
<para>this is 1989 representing text 245</para>
<para>this is sample text 235</para>
<para>this is 234 sample text with comma seperator 345,756</para>
when i run this it is throwing me an error as below.
Invalid regular expression '\d+(?=[-, \d]*$)' - Details: - XTDE1140: The effective value of the 'regex' attribute of the <xsl:analyze-string> instruction must conform to the required syntax for regular expressions
here what i'm trying to achieve is, to get the number at the end of the text, ignoring any other characters, when i tested the regex, it is working fine. and it is here, but when i implement the same in my XSLT, it is throwing me an error.
The expected output is
<a href="245"/>
<a href="235"/>
<a href="345"/>,<a href="756"/>
please let me know how can i fix this.
Thanks

Formatting scientific number representation in xsl

I have the following value in my XML -1.8959581529998104E-4. I want to format this to the exact number it should be using XSL to give me -0.000189595815299981.
format-number(-1.8959581529998104E-4,'0.000000;-0.000000') gives me NaN.
Any ideas?
Cheers
Andez
XSLT 1.0 does not have support for scientific notation.
This: number('-1.8959581529998104E-4')
Result: NaN
This: number('-0.000189595815299981')
Result: -0.000189595815299981
XSLT 2.0 has support for scientific notation
This: number('-1.8959581529998104E-4')
Result: -0.000189595815299981
EDIT: A very simple XSLT 1.0 workaround:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="number[substring-after(.,'E')]">
<xsl:variable name="vExponent" select="substring-after(.,'E')"/>
<xsl:variable name="vMantissa" select="substring-before(.,'E')"/>
<xsl:variable name="vFactor"
select="substring('100000000000000000000000000000000000000000000',
1, substring($vExponent,2) + 1)"/>
<xsl:choose>
<xsl:when test="starts-with($vExponent,'-')">
<xsl:value-of select="$vMantissa div $vFactor"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$vMantissa * $vFactor"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this input:
<number>-1.8959581529998104E-4</number>
Output:
-0.00018959581529998104
This is based on user357812 answer. But I made it act like a function and handle non-scientific notation
<xsl:template name="convertSciToNumString" >
<xsl:param name="inputVal" select="0"/>
<xsl:variable name="vExponent" select="substring-after($inputVal,'E')"/>
<xsl:variable name="vMantissa" select="substring-before($inputVal,'E')"/>
<xsl:variable name="vFactor"
select="substring('100000000000000000000000000000000000000000000',
1, substring($vExponent,2) + 1)"/>
<xsl:choose>
<xsl:when test="number($inputVal)=$inputVal">
<xsl:value-of select="$inputVal"/>
</xsl:when>
<xsl:when test="starts-with($vExponent,'-')">
<xsl:value-of select="format-number($vMantissa div $vFactor, '#0.#############')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number($vMantissa * $vFactor, '#0.#############')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Usage:
<xsl:template match="X">
<X>
<xsl:call-template name="convertSciToNumString">
<xsl:with-param name="inputVal" select="text()"/>
</xsl:call-template>
</X>
</xsl:template>
This should handle a mix of scientific notation and decimal values.
Another possible workaround without a template:
<xsl:stylesheet version="1.0" ... xmlns:java="http://xml.apache.org/xslt/java">
...
<xsl:value-of select="format-number(java:java.lang.Double.parseDouble('1E-6'), '0.000')"/>
The logic doesn't appear to work correctly in the above answers by Moop and user357812 when determining vFactor in one particular scenario.
If vExponent is a single-digit positive number (without a preceding '+' sign), then vFactor is set to an empty string. This is because an assumption was made that the 1st character of vExponent would be a plus/minus sign and therefore the 2nd character onwards were of interest. The vMantissa variable is then multiplied by an empty string which results in the template outputting NaN.
If vExponent is a multi-digit positive number (without a preceding '+' sign), then vFactor is set to an incorrect value. Because of the aforementioned assumption, the 1st digit is ignored and the vMantissa is then multiplied by an incorrect vFactor.
Therefore, I've modified the previously posted code a little so that it can handle scientific numbers of the forms: 2E-4, 2E+4 and 2E4.
<xsl:template name="convertSciToNumString" >
<xsl:param name="inputVal" select="0"/>
<xsl:variable name="vMantissa" select="substring-before(., 'E')"/>
<xsl:variable name="vExponent" select="substring-after(., 'E')"/>
<xsl:variable name="vExponentAbs" select="translate($vExponent, '-', '')"/>
<xsl:variable name="vFactor" select="substring('100000000000000000000000000000000000000000000', 1, substring($vExponentAbs, 1) + 1)"/>
<xsl:choose>
<xsl:when test="number($inputVal)=$inputVal">
<xsl:value-of select="$inputVal"/>
</xsl:when>
<xsl:when test="starts-with($vExponent,'-')">
<xsl:value-of select="format-number($vMantissa div $vFactor, '#0.#############')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number($vMantissa * $vFactor, '#0.#############')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Just tried this with xsltproc using libxslt1.1 in version 1.1.24 under Linux:
XSLT 1.1 is able to read in exponential/scientific format now even without any dedicated template, it seems to simply work :-))