XSL 2.0: Format String with _ to CamelCase - xslt

I am using XSLT (2.0) as a java code generator. At the moment I have a XML which describes a database table and I want to generate the entity class for it.
The column names of the table are always lowercase with _ between the words.
Example: bat_valid_from
I want to rename it in the Java class to camelcase with first letter lowercase
Example: batValidFrom
Because I need this quiet often in my codeGen I like to have a function for it.
But I only could achieve this with two sub functions.
<xsl:function name="local:VarName">
<xsl:param name="columnName"/>
<xsl:value-of select="lower-case(substring($columnName,1,1))"/>
<xsl:value-of select="substring(local:VarName_sub($columnName),2)"/>
</xsl:function>
<xsl:function name="local:VarName_sub">
<xsl:param name="columnName"/>
<xsl:value-of select="local:VarName_sub_sub($columnName)"/>
</xsl:function>
<xsl:function name="local:VarName_sub_sub">
<xsl:param name="columnName"/>
<xsl:for-each select="tokenize($columnName, '_')">
<xsl:value-of select="upper-case(substring(.,1,1))"/>
<xsl:value-of select="substring(.,2)"/>
</xsl:for-each>
</xsl:function>
Maybe someone has an idea to simplify this?
Without the sub functions I get the following error:
A sequence of more than one item is not allowed as the first argument of fn:substring()
PS: I haven't posted the whole code to shorten the question

XSLT/XPath 2.0 has supports for expressions. You could do this:
string-join(
for $part in tokenize($input, '_')
return concat(
upper-case(substring($part, 1, 1)),
substring($part, 2)
)
, '')
with $input set to 'bat_valid_from', this expression would produce 'BatValidFrom'.
I'm leaving lower-casing (or not upper-casing) the initial letter as an exercise.

With the hint from Tomalak i was able to make all in one function.
Maybe not light weighted but works like a charm.
<xsl:function name="local:VarName">
<xsl:param name="columnName"/>
<xsl:value-of select="
concat(
lower-case(substring($columnName, 1, 1)),
substring(string-join(for $word in tokenize($columnName, '_')
return concat(
upper-case(substring($word, 1, 1)),
substring($word, 2)), '')
, 2))" />
</xsl:function>

Related

XSL use regex group result + other element outside regex path for one outcome element

<xsl:variable name="groups" as="element(group)*">
<xsl:analyze-string regex="^^([0-9-]+)([A-Z ]*)[ ]*([\(](.*)[\)])*$" select="/fault/informations/restriction1">
<xsl:matching-substring>
<group>
<x><xsl:value-of select="regex-group(1)"/></x>
<y><xsl:value-of select="regex-group(4)"/></y>
</group>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
it was allready in
XSL Analyze-String -> Matching-Substring into multiple variables
but my question is how to use it on example to concate
eg regex-group(1) with other element outside regex path "/fault/informations/restriction2"
so simple IMPUT XML:
<fault >
<information>
<reference1>22-00 X (AA - 03 StoLAT)</reference1>
<opr>Sample sam (66-33) Sample</opr>
</information>
</fault>
and OUTPUT I would like to have
in one element
<mytrouble>
<trouble12>AA - 03 StoLAT , Sample sam Sample</trouble12>
</mytrouble>
so the rgex group 4 - text inside ( ) from <reference1> and the whole text from opr
ps
currently by transforming:
Cannot match item type with required type Error location:
xsl:stylesheet / xsl:template / xsl:element / xsl:variable /
#as
Details XTTE0570 : The required item type of variable ' groups ' is
' element(Q{http://xml.event/1.0}group) ' ,
the supplied item type is ' text() '
GREAT THANX for
michael.hor257k
almost there - just the result indicates header
<trouble12 xmlns:fn="http://www.w3.org/2005/xpath-functions">AA - 03 StoLATSample sam Sample</trouble12>
my question is how to use it on example to concate eg regex-group(1)
with other element outside regex path
"/fault/informations/restriction2"
To refer to another node within the xsl:analyze-string instruction, capture it in a variable first - for example:
<xsl:variable name="opr" select="fault/information/opr" />
<xsl:analyze-string regex="^^([0-9-]+)([A-Z ]*)[ ]*([\(](.*)[\)])*$" select="fault/information/reference1">
<xsl:matching-substring>
<trouble12>
<xsl:value-of select="regex-group(4)"/>
<xsl:value-of select="$opr"/>
</trouble12>
</xsl:matching-substring>
</xsl:analyze-string>

Applying a series of functions to text nodes

I have to apply a series of functions to text nodes.
At present, it looks like such:
<xsl:function name="local:enhanceTypo" as="text()">
<xsl:param name="s"/>
<xsl:value-of select="
replace(
replace(
translate($s,'<>','‹›')
,'a.a.O.','a. a. O.'
)
,'z.B.','z. B.')
"/>
</xsl:function>
There is a lot more to be done in local:enhanceTypo and it seems to be funny to make this as a series of nested function calls.
Is there a simple-to-read and -understand way in XSLT2 to sequentially apply a lot of functions to my string?
Certainly chaining variables is one way. I often reuse the same variable name when doing this:
<xsl:variable name="v" select="replace($v, ....)"/>
<xsl:variable name="v" select="replace($v, ....)"/>
<xsl:variable name="v" select="replace($v, ....)"/>
because it's then easy to see what's going on, and to add extra calls into the sequence. I can't speak for other processors, but in Saxon when you do this, the variables are automatically inlined, so it's exactly as if you wrote the deeply nested function call, just easier to read.
If you're into higher-order functions, then with 3.0 there's another way. You can define a sequence of functions like this:
<xsl:variable name="replacements" select="
replace(?, "a", "A"),
replace(?, "b", "B"),
replace(?, "c", "C")"/>
and then you can do a fold-left operation over this sequence:
fold-left($replacements, $string, function ( $in, $f ) { $f($in) })
If your original and replacement strings are in two xs:string* variables $in and $out, then you could instead define the sequence of functions as
<xsl:variable name="replacements" select="
for-each-pair($in, $out, function($i, $o){ replace(?, $i, $o) })"/>
That is, for each pair of input and output strings you create a function that replaces this input by this output, and then you apply the sequence of functions using fold-left.
Fun, eh?

Dynamic choose condition in XSLT

How to use the choose condition in XSLT when the below requirement is needed
<xsl:choose>
<xsl:when test="contains(#name,'top %d holdings' ) ">
<!--code-->
</xsl:when>
</xsl:choose>
It should select all the data containing....
top 5 holdings
top 10 holdings
top 30 holdings
top 27 holdings
top * holdings
If you were using XSLT2.0 here, you could use the matches function which allows you to match text by means of a regular expression
<xsl:when test="matches(#name, '.*top \d+ holdings.*')">
On the other hand, if you were using XSLT 1.0, then the matches function is not available. One way you could do it in your very specific case is extract the text before "holdings" that occurs before "top" and check it is a number:
<xsl:when test="string(number(substring-before(substring-after(#name, 'top '), ' holdings' ) )) != 'NaN'">
You can use substring-before() and substring-after() to get the text between top and holdings, and then use the translate() function to remove numbers and the * character, and then verify that the result is an empty string.
<xsl:choose>
<xsl:when
test="translate(
substring-before(substring-after(#name, 'top '), ' holdings' ),
'0123456789*',
'') = '' ">
<!--code-->
</xsl:when>
</xsl:choose>

how to parse the value from xml through xsl

<block4>
<tag>
<name>50K</name>
<value>/001/002/300060000120135670
CREDIT AGRICOLE ASSET MANAGEMENT</value>
</tag>
</block4>
I need to get output that looks like:
/001/002,/300060000120135670,CREDIT AGRICOLE ASSET MANAGEMENT
I have done like this in XSL, but I didn't get the output I wanted. Can anyone please give me some idea how I could get that output?
<xsl:for-each select ="block4/tag[name = '50K']">
<xsl:value-of select="
concat(
substring(value,1,8),
(concat(substring(value,9,'
'),',')),
substring-after(value,'
')
)
" />,<xsl:text/>
</xsl:for-each>
concat takes any number of arguments, no need to nest those calls. Besides, substring takes a beginning and an optional length, not a terminating character. Try something like this instead:
<xsl:for-each select ="block4/tag[name = '50K']">
<xsl:value-of select="
concat(
substring(value, 1, 8), ',',
substring(substring-before(value,'
'),9), ',',
substring-after(value,'
')
)
" />,<xsl:text/>
</xsl:for-each>
I've kept the final comma in, which is one of the many things you did not really specify.
Why not use XSLT 2.0 tokenize() function?
See Here

Find value in sequence using XSL

I want to check if a value exists in a sequence defined as
<xsl:variable name="some_seq" select="/root/word[#optional='no']/text()"/>
In the past, I've had success with Priscilla Walmsleys function. For clarity, I reproduce it here as follows:
<xsl:function name="functx:is-value-in-sequence" as="xs:boolean">
<xsl:param name="value" as="xs:anyAtomicType?"/>
<xsl:param name="seq" as="xs:anyAtomicType*"/>
<xsl:sequence select="$value=$seq"/>
</xsl:function>
However, this time I need to make a case-insensitive comparison, and so I tried to wrap both $value and $seq with a lower-case(). Obviously, that didn't help much, as $seq is a sequence and lower-case() takes only strings.
Question: what is the best way to either 1) construct a sequence of lower-case strings, or 2) make a case-insensitive comparison analogous to $value=$seq above? TIA!
Question: what is the best way to
either 1) construct a sequence of
lower-case strings
Not many people realize that you can use a function as the last location step in an XPATH 2.0 expression.
You can create a sequence of lower-case() string values with this expression:
/root/word[#optional='no']/text()/lower-case(.)
or 2) make a case-insensitive
comparison analogous to $value=$seq
above?
Using that strategy, you can define a custom function that compares the lower-case() value of the $value and each string value in the $seq:
<xsl:function name="functx:is-value-in-sequence" as="xs:boolean">
<xsl:param name="value" as="xs:anyAtomicType?"/>
<xsl:param name="seq" as="xs:anyAtomicType*"/>
<xsl:sequence select="some $word in $seq/lower-case(.)
satisfies ($word = $value/lower-case(.))"/>
</xsl:function>
Use a "for-expression" inside the function to prepare a lower-case version of the sequence
<xsl:variable name="lcseq" select="for $i in $seq return lower-case($i)"/>
See Michael Kay's "XSLT 2.0 and XPATH 2.0, 4th ed", p. 640
(I haven't tested this)