I have this requirement to pad strings so that they become 10 characters long.
Example strings :
AB..12
ABC...123
I need to add dots in the middle of the string (where dots are already present; at least one dot will always be present in the input string) in order to get the total length to 10 characters long:
AB......12
ABC....123
What would be a good way to accomplish this?
I have come up with this idea, which works, but is really not "nice".
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="test1" select="'AB..12'"/>
<xsl:variable name="dots" select="if(string-length($test1)=9) then '..'
else if(string-length($test1)=8) then '...'
else if(string-length($test1)=7) then '....'
else if(string-length($test1)=6) then '.....'
else ''"/>
<Result>
<Test1><xsl:value-of select="concat(substring-before($test1,'.'),$dots,substring-after($test1,'.'))"/></Test1>
</Result>
</xsl:template>
</xsl:stylesheet>
If you use a regular expression you can match the dots and compute the length; the following isn't really very compact but should do the job:
<xsl:param name="separator" as="xs:string" select="'.'"/>
<xsl:param name="pattern" as="xs:string" expand-text="no">^([^.]+)(\.+)([^.]+)$</xsl:param>
<xsl:template match="item">
<xsl:copy>
<xsl:analyze-string select="." regex="{$pattern}">
<xsl:matching-substring>
<xsl:value-of
select="regex-group(1),
(1 to 10 - string-length(regex-group(1)) - string-length(regex-group(3))) ! $separator,
regex-group(3)" separator=""/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:copy>
</xsl:template>
I have assumed you have the string in an element to match on e.g. item in above sample, but you could of course stuff the xsl:analyze-string into a function instead and call it with any string you have.
Not a pure XSLT 3.0 solution but with the replace-with() function available as a Saxon extension, it's
replace-with($input, '\.+',
function($s){string-join((1 to 10-string-length($input))!'.')})
Thanks for both of your answers. Here's a simple answer I came up with based on your answers.
<xsl:variable name="output" select="concat(substring-before($input,'.'),string-join((1 to 10-string-length($input)+1)!'.'),substring-after($input,'.'))"/>
So basically take what's before the first dot, add the number of missing dots+1 and then add what's after the first dot.
Related
I currently have the following code to combine multiple instances of Ustrd into one returned value:
<Ustrd>
<xsl:value-of select="a:RmtInf/a:Ustrd"/>
</Ustrd>
This returns:
<Ustrd>Item-1 Item-2 Item-3</Ustrd>
The problem is that I need to limit this to 18 characters, and the substring function does not work with a sequence of items.
Tried:
<Ustrd>
<xsl:value-of select="substring(a:RmtInf/a:Ustrd, 1, 18"/>
</Ustrd>
Expected Result:
<Ustrd>Item-1 Item-2 Item</Ustrd>
Use string-join first e.g. substring(string-join(a:RmtInf/a:Ustrd, ' '), 1, 18). In XPath 3.1 you can also write that as a:RmtInf/a:Ustrd => string-join(' ') => substring(1, 18).
Here's a way this could be done in XSLT 1.0.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Ustrd>
<xsl:variable name="temp">
<xsl:for-each select="RmtInf/Ustrd">
<xsl:value-of select="."/>
<xsl:if test="position()!=last()">
<xsl:value-of select="' '"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="substring($temp,1,18)"/>
</Ustrd>
</xsl:template>
</xsl:stylesheet>
(Only need to add your namespace.)
See it working here: https://xsltfiddle.liberty-development.net/pPgzCL4
Given example path C:\example\innerExample\file.txt, I want to extract filename with extension using this regex, you can see it here.
<xsl:analyze-string select="$filePath" regex="$regexPattern" flags="mis">
<xsl:matching-substring>
<xsl:value-of select="concat(regex-group(2), regex-group(3))"/>
</xsl:matching-substring>
</xsl:analyze-string>
This is my xslt code, is there anything I'm missing?
Without going into your attempt (which I cannot reproduce), I believe you can extract the filename with extension simply by using:
<xsl:value-of select="tokenize($filepath, '\\')[last()]"/>
Demo: http://xsltransform.hikmatu.com/6qVRKvN
You haven't shown us a complete but minimal examples with the proper values but with the correction of not escaping the / in the square brackets I think your pattern works with XSLT/XPath 2 and later:
Input
<root>
<data>C:\example\innerExample\file.txt</data>
</root>
is at https://xsltfiddle.liberty-development.net/jyRYYhM transformed with
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="regexPattern" as="xs:string">^(.*)[/|\\](.*)(\..*)</xsl:param>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="data">
<xsl:copy>
<xsl:analyze-string select="." regex="{$regexPattern}" flags="mis">
<xsl:matching-substring>
<xsl:value-of select="concat(regex-group(2), regex-group(3))"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
into
<root>
<data>file.txt</data>
</root>
(I have used XSLT 3 there but I think there has been no change between XSLT 2 and 3 in terms of xsl:analyze-string).
For a project, I'm stuck with XSLT-1.0/XPATH-1.0 and need a fast way to strip a lowercase prefix from attribute values.
Example attribute values are:
"cmdValue1", "gfValue2", "dTestCase3"
The values I need are:
"Value1", "Value2", "TestCase3"
I came up with this XPath expression but it is too slow for my application:
substring(#attr, 1 + string-length(substring-before(translate(#attr, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '..........................'), '.')))
In essence the above does replace all uppercase chars to dots, then creates a substring from the original attribute value starting from the first found dot position (first uppercase char).
Does anyone know a shorter/faster way to do this in XSLT-1.0/XPATH-1.0?
There are not many functions in XSLT 1.0 which we could use instead, so I tried the following recursive template to avoid the use of the translate function.
Because it is 1.5 times slower, it does not answer your question. I can just avoid someone trying the same thing:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xml:space="default" exclude-result-prefixes="" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes" />
<xsl:template match="/">
<out>
<xsl:call-template name="removePrefix">
<xsl:with-param name="prefixedName" select="xml/#attrib" />
</xsl:call-template>
</out>
</xsl:template>
<xsl:template name="removePrefix">
<xsl:param name="prefixedName" />
<xsl:choose>
<xsl:when test="substring-before('_abcdefghijklmnopqrstuvwxyz', substring($prefixedName, 1,1))">
<xsl:call-template name="removePrefix">
<xsl:with-param name="prefixedName" select="substring($prefixedName,2)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$prefixedName" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You don't need to calculate the prefix's length and manually extract the substring. Instead, just directly ask for everything that comes after it:
substring-after(#attr,
substring-before(translate(#attr,
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'..........................'),
'.'))
This isn't a huge improvement, but it might shave 7-8% (based on some really rough and quick tests).
I have following xml
<xml>
<xref>
is determined “in prescribed manner”
</xref>
</xml>
I want to see if we can process xslt 2 and return the following result
<xml>
<xref>
is
</xref>
<xref>
determined
</xref>
<xref>
“in prescribed manner”
</xref>
</xml>
I tried few options like replace the space and entities and then using for-each loop but not able to work it out. May be we can use tokenize function of xslt 2.0 but don't know how to use it. Any hint will be helpful.
# JimGarrison: Sorry, I couldn't resist. :-) This XSLT is definitely not elegant but it does (I assume) most of the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:variable name="left_quote" select="'<'"/>
<xsl:variable name="right_quote" select="'>'"/>
<xsl:template name="protected_tokenize">
<xsl:param name="string"/>
<xsl:variable name="pattern" select="concat('^([^', $left_quote, ']+)(', $left_quote, '[^', $right_quote, ']*', $right_quote,')?(.*)')"/>
<xsl:analyze-string select="$string" regex="{$pattern}">
<xsl:matching-substring>
<!-- Handle the prefix of the string up to the first opening quote by "normal" tokenizing. -->
<xsl:variable name="prefix" select="concat(' ', normalize-space(regex-group(1)))"/>
<xsl:for-each select="tokenize(normalize-space($prefix), ' ')">
<xref>
<xsl:value-of select="."/>
</xref>
</xsl:for-each>
<!-- Handle the text between the quotes by simply passing it through. -->
<xsl:variable name="protected_token" select="normalize-space(regex-group(2))"/>
<xsl:if test="$protected_token != ''">
<xref>
<xsl:value-of select="$protected_token"/>
</xref>
</xsl:if>
<!-- Handle the suffix of the string. This part may contained protected tokens again. So we do it recursively. -->
<xsl:variable name="suffix" select="normalize-space(regex-group(3))"/>
<xsl:if test="$suffix != ''">
<xsl:call-template name="protected_tokenize">
<xsl:with-param name="string" select="$suffix"/>
</xsl:call-template>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="xref">
<xsl:call-template name="protected_tokenize">
<xsl:with-param name="string" select="text()"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Notes:
There is the general assumption that white space only serves as a token delimiter and need not be preserved.
“ and rdquo; seem to be invalid in XML although they are valid in HTML. In the XSLT there are variables defined holding the quote characters. They will have to be adapted once you find the right XML representation. You can also eliminate the variables and put the characters right into the regular expression pattern. It will be significantly simplified by this.
<xsl:analyze-string> does not allow a regular expression which may evaluate into an empty string. This comes as a little problem since either the prefix and/or the proteced token and/or the suffix may be empty. I take care of this by artificially adding a space at the beginning of the pattern which allows me to search for the prefix using + (at least one occurence) instead of * (zero or more occurences).
Is it possible to split a tag at lower to upper case boundaries i.e.
for example, tag 'UserLicenseCode' should be converted to 'User License Code'
so that the column headers look a little nicer.
I've done something like this in the past using Perl's regular expressions,
but XSLT is a whole new ball game for me.
Any pointers in creating such a template would be greatly appreciated!
Thanks
Krishna
Using recursion, it is possible to walk through a string in XSLT to evaluate every character. To do this, create a new template which accepts only one string parameter. Check the first character and if it's an uppercase character, write a space. Then write the character. Then call the template again with the remaining characters inside a single string. This would result in what you want to do.
That would be your pointer. I will need some time to work out the template. :-)
It took some testing, especially to get the space inside the whole thing. (I misused a character for this!) But this code should give you an idea...
I used this XML:
<?xml version="1.0" encoding="UTF-8"?>
<blah>UserLicenseCode</blah>
and then this stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="text"/>
<xsl:variable name="Space">*</xsl:variable>
<xsl:template match="blah">
<xsl:variable name="Split">
<xsl:call-template name="Split">
<xsl:with-param name="Value" select="."/>
<xsl:with-param name="First" select="true()"/>
</xsl:call-template></xsl:variable>
<xsl:value-of select="translate($Split, '*', ' ')" />
</xsl:template>
<xsl:template name="Split">
<xsl:param name="Value"/>
<xsl:param name="First" select="false()"/>
<xsl:if test="$Value!=''">
<xsl:variable name="FirstChar" select="substring($Value, 1, 1)"/>
<xsl:variable name="Rest" select="substring-after($Value, $FirstChar)"/>
<xsl:if test="not($First)">
<xsl:if test="translate($FirstChar, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '..........................')= '.'">
<xsl:value-of select="$Space"/>
</xsl:if>
</xsl:if>
<xsl:value-of select="$FirstChar"/>
<xsl:call-template name="Split">
<xsl:with-param name="Value" select="$Rest"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
and I got this as result:
User License Code
Do keep in mind that spaces and other white-space characters do tend to be stripped away from XML, which is why I used an '*' instead, which I translated to a space.
Of course, this code could be improved. It's what I could come up with in 10 minutes of work. In other languages, it would take less lines of code but in XSLT it's still quite fast, considering the amount of code lines it contains.
An XSLT + FXSL solution (in XSLT 2.0, but almost the same code will work with XSLT 1.0 and FXSL 1.x:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:testmap="testmap"
exclude-result-prefixes="f testmap"
>
<xsl:import href="../f/func-str-dvc-map.xsl"/>
<testmap:testmap/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/>
'<xsl:value-of select="f:str-map($vTestMap, 'UserLicenseCode')"
/>'
</xsl:template>
<xsl:template name="mySplit" match="*[namespace-uri() = 'testmap']"
mode="f:FXSL">
<xsl:param name="arg1"/>
<xsl:value-of select=
"if(lower-case($arg1) ne $arg1)
then concat(' ', $arg1)
else $arg1
"/>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on any source XML document (not used), the expected correct result is produced:
' User License Code'
Do note:
We are using the DVC version of the FXSL function/template str-map(). This is a Higher-order function (HOF) which takes two arguments: another function and a string. str-map() applies the function on every character of the string and returns the concatenation of the results.
Because the lower-case() function is used (in the XSLT 2.0 version), we are not constrained to only the Latin alphabet.