Substring in XSLT2.0 - xslt-2.0

I need help with one scenario: I need to substring before the last occurrence of space, hyphen or full stop characters in a text. I tried with substring-before
For example, If text is 'SIR WILLIAM SIEMENS SQUARE', then I need to have 'SIR WILLIAM SIEMENS' as first text string and 'SQUARE'. Using Substring before I am getting 'SIR' and 'WILLIAM SIEMENS SQUARE'.

First you need to replace (. and -) with space and then it will work. Try like this.
<xsl:template match="/">
<root>
<xsl:variable name="maintext" select="replace(replace(/root/a, '\.', '. '), '-', '- ')"/>
<a><xsl:value-of select="tokenize($maintext, ' ')[position() != last()]"/></a>
<b><xsl:value-of select="tokenize($maintext, ' ')[last()]"/></b>
</root>
</xsl:template>

You should do like this:
<xsl:template match="/">
<a><xsl:value-of select="tokenize(., ' ')[position() != last()]"/></a>
<b><xsl:value-of select="tokenize(., ' ')[last()]"/></b>
</xsl:template>

you can also use analyze-string
<xsl:template match="/">
<xsl:analyze-string select="normalize-space(.)" regex="(.+[\W])([\w]+)">
<xsl:matching-substring>
<a><xsl:value-of select="normalize-space(regex-group(1))"/></a>
<b><xsl:value-of select="regex-group(2)"/></b>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>

Related

Parsing a string using more than one delimiter XSLT 2 3

I have to insert a dot leader at the end of the first line of what could be several lines of text. If the string is over 37 characters, I display the first 37 characters, then the dot leader, and then the rest of the string flows to the following lines. I can get this to work using a space as a delimiter, but if there are no spaces the entire description gets pushed to the second line. So commas and hyphens need to be delimiters, as well, at the very least.
<xsl:template name="substring-before-last">
<xsl:param name="input" />
<xsl:variable name="del" select="'[,|-|\s]+'"/>
<xsl:variable name="string-tokens" select="tokenize($input, $del)"/>
<xsl:variable name="substring">
<xsl:value-of select="$string-tokens[not(. = $string-tokens[last()])]"/>
</xsl:variable>
<xsl:value-of select="$substring"/>
</xsl:template>
<xsl:template name="getFirstLine">
<xsl:param name="input" />
<xsl:variable name="remaining">
<xsl:call-template name="substring-before-last">
<xsl:with-param name="input" select="$input"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="string-length($remaining) >= 37">
<xsl:call-template name="getFirstLine">
<xsl:with-param name="input" select="$remaining"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$remaining"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="addDotLeader">
<xsl:variable name="descript" select="REMOVE-THIS-IS-A-TEST-LINE,XXX,XXXXXXXXXXXX XXXXX,XXX-XXX,XXXX-XXX,XXXXXXXXXXXXXX,XXXX,XXXXXXXX-XXXXXX-XXXXXXXXX" />
<xsl:variable name="length" select="string-length($descript)" as="xs:integer"/>
<xsl:choose>
<xsl:when test="$length >= 37">
<xsl:variable name="remaining">
<xsl:call-template name="getFirstLine">
<xsl:with-param name="input" select="$descript"/>
</xsl:call-template>
</xsl:variable>
<!-- print out first line -->
<xsl:value-of select="$remaining"/>
<!-- print out dot leader -->
<xsl:text> </xsl:text><fo:leader leader-pattern="dots"/><xsl:text> </xsl:text>
<fo:block text-align-last="left" margin-left="8px">
<xsl:value-of select="substring-after($descript,$remaining)"/>
</fo:block>
</xsl:when>
<xsl:otherwise>
<!-- Description fits on first line -->
<xsl:value-of select="$descript"/>
<xsl:text> </xsl:text><fo:leader leader-pattern="dots" /><xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This almost works, except the dashes and commas are stripped out when it gets tokenized. When I try to print out the rest of the string, the substring-after fails because of the missing delimiters.
Actual output:
. . REMOVE THIS IS A TEST...........................
The actual string was REMOVE-THIS-IS-A-TEST-LINE,XXX,XXXXXXXXXXXX XXXXX,XXX-XXX,XXX-XXXXX,XXXXXXXXXXXXXX,XXXX,XXXXXXXX-XXXXXX-XXXXXXXXX
The desired output is:
. . REMOVE-THIS-IS-A-TEST-..........................
LINE,XXX,XXXXXXXXXXXX XXXXX,XXX-XXX,XXX-XXXXX,
XXXXXXXXXXXXXX,XXXX,XXXXXXXX-XXXXXX-XXXXXXXXX
I'm sure there is a better way to do this, maybe start with capturing the first 37 characters before checking delimiters?

How to fix collection() function problem in XSLT

collection() function does not encount all 50 letters, only the one on which the transformation is done.
I have to transform many xml-letters in a csv file with the parameters "Key", "bezeichnung", and "reference" from any letter located in the same folder. When I execute the transformation, I get only the data from the actually transformed file and not from all 50 letters. What is the problem here with the collection() function because everything else works
<xsl:variable name="briefe"
select="collection('./?select=l_*.xml')"/>
<xsl:template match="/">
<xsl:text>"Key","Bezeichnung","Referenz"</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each-group select="//tei:correspAction[#type='received']/tei:placeName" group-by="#key">
<xsl:sort select="." />
<xsl:text>"</xsl:text><xsl:value-of select="current-grouping-key()" /><xsl:text>",</xsl:text>
<xsl:text>"</xsl:text><xsl:value-of select="distinct-values(//tei:correspAction[#type='received']/tei:placeName)" /><xsl:text>",</xsl:text>
<xsl:text>"</xsl:text><xsl:value-of select="//tei:correspAction[#type='received']/tei:placeName/#ref" /><xsl:text>"</xsl:text>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

XSLT 2 - pick item from tokenize()'d list by index

My environment is SAXON (last nights build) using XSLT 2.0. My real problem is that the XML document specification is sub-optimal, and in a way, my problem relates to fixing/working around that design issue.
I have a node type (<weaponmodesdata>) where all the direct children are |-separated string lists of 1-or-many elements (each child of the same <weaponmodesdata> will have the same length). I need to go over the various modes represented and "unspin" them out to separate item lists (in plain text), rather than having them all smooshed together.
Unfortunately right now I'm getting a really stubborn
XPTY0020: Required item type of the context item for the child axis is node(); supplied
value has item type xs:string
error on the lines where I pass the node that needs to be split up into my little template.
Currently I have
<xsl:template match="trait" mode="attack">
<xsl:for-each select="tokenize(weaponmodesdata/mode, '\|')">
<xsl:variable name="count" select="position()"/>
<xsl:value-of select="name"/><xsl:text> - </xsl:text>
<xsl:call-template name="split_weaponmode">
<xsl:with-param name="source" select="weaponmodesdata/damage"/>
<xsl:with-param name="item" select="$count"/>
</xsl:call-template>
<xsl:text> </xsl:text>
<xsl:call-template name="split_weaponmode">
<xsl:with-param name="source" select="weaponmodesdata/damtype"/>
<xsl:with-param name="item" select="$count"/>
</xsl:call-template>
<!-- more will go here eventually -->
<xsl:text>.
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="split_weaponmode">
<xsl:param name="source"/>
<xsl:param name="item"/>
<xsl:variable name="parts" select="tokenize($source, '\|')"/>
<xsl:for-each select="$parts">
<xsl:if test="position() = $item">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
An example XML subtree relating to my issue:
<character>
<trait id="1">
<name>Spear</name>
<weaponmodesdata>
<mode>1H Thrust|2H Thrust|Thrown</mode>
<damage>thr+2|thr+3|thr+3</damage>
<damtype>imp|imp|imp</damtype>
</weaponmodesdata>
</trait>
<trait id="2">
<name>Broadsword</name>
<weaponmodesdata>
<mode>1H Thrust|1H Swing</mode>
<damage>thr+1|sw+2</damage>
<damtype>imp|cut</damtype>
</weaponmodesdata>
</trait>
</character>
Example desired output:
Spear - 1H Thrust; thr+2 imp.
Spear - 2H Thrust; thr+3 imp.
Spear - Thrown; thr+3 imp.
Broadsword - 1H Thrust; thr+1 imp.
Broadsword - 1H Swing; sw+2 cut.
One issue (that one causing the error message) with your code is that your for-each operates on a sequence of string value (i.e. inside the for-each body the context item is a string value), yet you have relative XPath expressions like weaponmodesdata/damage that require a context node to makes sense. So you would need to use a variable outside of the for-each to store your context node.
But I think you can simplify your code to
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="trait">
<xsl:variable name="this" select="."/>
<xsl:variable name="count" select="count(tokenize(weaponmodesdata/*[1], '\|'))"/>
<xsl:for-each-group select="weaponmodesdata/*/tokenize(., '\|')" group-by="position() mod $count">
<xsl:value-of select="$this/name"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="current-group()"/>
<xsl:text>.
</xsl:text>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
If you want to stick with your approach of calling templates then make sure you store the context node of the template using e.g. <xsl:variable name="this" select="."/> so that you can access it inside of the for-each iterating over a string item.

Dynamic line wraping- condition based in XSLT1&2

My output type is text.
I am preparing for Reports.
My text output got to accept only 50 character width after that which has to be wrapped in to the next line.
I have a solution to line wrap for the elements in the text.
Is there any way to to wrap for the entire reports instead of doing for the every line?
Can I do it for the whole document?
I have solutions for line wrap, my problem is that I have many conditions like below:
Firstname lastname route (condition1 ) (condition2) (condition3)
(condition4)..go on...
Let us assume:
First name fixedwidth is 15,
lastname fixed width is 15,city fixed width is 3...
after that condition1 will have 10 width ,condition2 have 15 fixed with then go on...
importantly these conditions are option only...
So 15+emptyspace+15+emptyspace+3 =36 My condition will start from 36 th column..
After the first wrap I got to continue from the same line for the upcoming conditions.
So for the next item i got find the start and end locations.
How to solve this problem ?
xml input:
<?xml version="1.0" encoding="UTF-8"?>
<passengerlist>
<passengers>
<Firstname>JOHNNNNNNNNNNNN</Firstname>
<lastname>MARKKKKKKKKKKKK</lastname>
<comments>abcdefh abc abcde abc dekf jl</comments>
<route>air</route>
</passengers>
<!-- <passengers>
<Firstname>ANTONYYYYYYYYYYY</Firstname>
<lastname>NORMAN</lastname>
<comments>abcdefddddddddghhhhhhhhhhhhhh</comments>
<route>air</route>
</passengers>
<passengers>
<Firstname>BRITTOOOOOOOOOO</Firstname>
<lastname>MARKKKKKKK</lastname>
<comments>abcdedfffghghghghghghghghghghghghgh</comments>
<route>cruise</route>
</passengers> -->
</passengerlist>
XSLT Code:
<!-- For line Wrapping -->
<xsl:template name="callEmpty">
<xsl:param name="callEmpty"/>
<xsl:variable name="LNemptyCheck" select="$callEmpty"></xsl:variable>
</xsl:template>
<xsl:template name="text_wrapper">
<xsl:param name="Text"/>
<xsl:choose>
<xsl:when test="string-length($Text)">
<xsl:value-of select="substring($Text,1,15)"/>
<xsl:if test="string-length($Text) > 15">
<xsl:value-of select="$newline"/>
</xsl:if>
<xsl:call-template name="wrapper_helper">
<xsl:with-param name="Text" select="substring($Text,16)"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="wrapper_helper">
<xsl:param name="Text"/>
<xsl:value-of select="substring($Text,1,15)"/>
<xsl:text>
</xsl:text>
<xsl:call-template name="text_wrapper">
<xsl:with-param name="Text" select="substring($Text,15)"/>
</xsl:call-template>
</xsl:template>
<!-- Template for Line wrapping -->
<xsl:template match="/">
<xsl:for-each select="passengerlist/passengers">
<xsl:value-of select="Firstname"/>
<xsl:text> </xsl:text>
<xsl:value-of select="lastname"/>
<xsl:text> </xsl:text>
<xsl:value-of select="route"/>
<xsl:text> </xsl:text>
<xsl:variable name="firstwrap">
<xsl:if test="route='air'">
<xsl:value-of select="Firstname"/>
<xsl:text> </xsl:text>
<xsl:value-of select="comments"/>
</xsl:if>
</xsl:variable>
<xsl:call-template name="text_wrapper">
<xsl:with-param name="Text" select="$firstwrap"/>
</xsl:call-template>
Output:
JOHNNNNNNNNNNNN MARKKKKKKKKKKKK air JOHNNNNNNNNNNNN
abcdefh abc ab
bcde abc dekf jl
MARKKKKKKKKKKKK abcdefh abc ab bcde abc dekf jl
Expected out:
JOHNNNNNNNNNNNN MARKKKKKKKKKKKK air JOHNNNNNNNNNNNN abcdefh abc ab
bcde abc dekf jl MARKKKKKKKKKKKK abcdefh abc abbcde abc dekf jl
Please help me to sort out my problem or tell me Is it possible in XSLT?
I'm not sure what exactly your problem is (I cannot see any significant difference between output you got and output you expected). But I think it is possible make it simpler. I prepared some testing input xml (just very simple for demonstration).
<?xml version="1.0" encoding="UTF-8"?>
<Input>
<Line>Some long text is on the first line.</Line>
<Line>Some longer text is on the second line.</Line>
<Line>But the longest text occures on the third line.</Line>
</Input>
In following xslt I store the result of processing of each line (i.e. copy of its text and append additional text based on some conditions) into a variable. Then I wrap this variable at once using a user function (it could be done with named template as well).
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:my="my-ns">
<xsl:output method="text" />
<xsl:variable name="newLineCharacter" select="'
'" />
<xsl:variable name="maxLineWidth" select="50" />
<xsl:template match="/">
<xsl:apply-templates select="Input/Line" />
</xsl:template>
<xsl:template match="Line">
<!-- Process the line and store the result into variable-->
<xsl:variable name="processedText">
<xsl:value-of select="." />
<xsl:text> </xsl:text>
<xsl:if test="position() >= 1">
<xsl:text>First condition is true. </xsl:text>
</xsl:if>
<xsl:if test="position() >= 2">
<xsl:text>Second condition is true. </xsl:text>
</xsl:if>
<xsl:if test="position() >= 3">
<xsl:text>Third condition is true. </xsl:text>
</xsl:if>
<!-- et cetera, et cetera ...-->
</xsl:variable>
<!-- Wrap the text stored in a variable -->
<xsl:value-of select="my:wrapText($processedText, $maxLineWidth)" />
</xsl:template>
<xsl:function name="my:wrapText">
<xsl:param name="textToBeWrapped" />
<xsl:param name="maximumWidth" />
<xsl:value-of select="substring($textToBeWrapped,1,$maximumWidth)" />
<xsl:value-of select="$newLineCharacter" />
<xsl:if test="string-length($textToBeWrapped) > $maximumWidth">
<!-- Recursive call of my:wrapText to wrap the rest of the text -->
<xsl:value-of select="my:wrapText(substring($textToBeWrapped,$maximumWidth+1), $maximumWidth)" />
</xsl:if>
</xsl:function>
</xsl:stylesheet>
And the output is
Some long text is on the first line. First conditi
on is true.
Some longer text is on the second line. First cond
ition is true. Second condition is true.
But the longest text occures on the third line. Fi
rst condition is true. Second condition is true. T
hird condition is true.
I hope it will meet your needs.

Eliminate line-breaks with XSLT 2.0 analyze-string

I use the XSLT 2.0 element analyze-string in a stylesheet that transforms XML to HTML; specifically, I use it to convert string encoding for subscripts in chemical formulae to HTML subscripts. Therefore, the result is a string, to go in a p or td element, with embedded mark-up.
The transformation is supposed to produce output like H2O but in fact inserts a line-break in the HTML:
H
<sub>2</sub>O
and this break is (correctly) interpreted by the browser as a space:
H
2O
which is ugly.
Is there a way to remove the line-break? I've tried putting the whole analyze-string element on one line and that doesn't work.
The input would be something like
<OrdinaryStructralFormula>H$_2$O</OrdinaryStructuralFormula>
for a simple case and
<OrdinaryStructralFormula>C$_2$OH$_5$$^-</OrdinaryStructuralFormula>
for a more-complicated one. Note that the subscript pattern can match multiple times in the general case and can be either in the middle or at the end of the string. The pattern also has to match and eliminate any notation for charge: the $^- bit at the end of the second example.
The XSLT processor is Saxon 9.4 and the XSLT template follows.
<xsl:template name="formula">
<xsl:param name="formula"/>
<xsl:if test="$formula">
<xsl:variable name="f" select="translate($formula, '$', '')"/>
<xsl:analyze-string select="$f" regex="(_)(\d+)|(\^)\d*\+|(\^)\d*\-">
<xsl:matching-substring>
<xsl:if test="regex-group(1)='_'">
<sub><xsl:value-of select="regex-group(2)"/></sub>
</xsl:if>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:if>
</xsl:template>
I cannot reproduce the reported result.
This transformation (which is what you should have given us, but you only provided a template):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:call-template name="formula">
<xsl:with-param name="formula" select="/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="formula">
<xsl:param name="formula"/>
<xsl:if test="$formula">
<xsl:variable name="f" select="translate($formula, '$', '')"/>
<xsl:analyze-string select="$f" regex="(_)(\d+)|(\^)\d*\+|(\^)\d*\-">
<xsl:matching-substring>
<xsl:if test="regex-group(1)='_'">
<sub><xsl:value-of select="regex-group(2)"/></sub>
</xsl:if>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document with Saxon 9.1.05:
<formula>H$_2$O</formula>
produces the wanted, correct result:
H<sub>2</sub>O
When the same transformation is applied on the second XML document:
<OrdinaryStructuralFormula>C$_2$OH$_5$$^-</OrdinaryStructuralFormula>
Again the wanted correct result is produced:
C<sub>2</sub>OH<sub>5</sub>
Do note: I ran the same transformations with two other XSLT 2.0 processors: XQSharp (XMLPrime) and AltovaXML (XML-SPY) and got exactly the same, correct results.

Resources