Add space between nodes in XSLT - xslt-2.0

XML file :
<string-name>
<surname>Husebo</surname><given-names>BS</given-names>
</string-name>, <string-name>
<surname>Ballard</surname> <given-names>C</given-names></string-name>
XSL :
<xsl:template match="surname">
<xsl:choose>
<xsl:when test = "following-sibling::node()[not(./*) and normalize-space(.)='']">
<xsl:text> </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
output : Husebo BS, Ballard C
I want to add a space between < surname> and < given-name> tag after checking a blank space. ex - For first < string-name> there is no space between < surname> and < given-name> so after checking that after < /surname> a blankspace should be added. but in the 2nd < /string-name> tag there a blankspace is already exists so no space will be added there. please help !!!

Use this code hope it will help you:
<xsl:template match="surname[not(following-sibling::node()[1][self::text()[. = ' ']])]">
<xsl:apply-templates/>
<xsl:text> </xsl:text>
</xsl:template>
And if you want to make more specific then check that immediate following-sibling is given-name then use this code:
<xsl:template match="surname[not(following-sibling::node()[1][self::text()[. = ' ']]) and following-sibling::node()[1][self::given-names]]">
<xsl:apply-templates/>
<xsl:text> </xsl:text>
</xsl:template>

As long as given-names is not optional, you can check if the next text node is the same as the next node:
<xsl:template match="surname">
<xsl:choose>
<xsl:when test="following-sibling::node()[1] != following-sibling::text()[1]">
<xsl:apply-templates/>
<xsl:text> </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Related

XSLT: Delete certain characters from string with nested elements

I have a sequence of transformations that result with a penultimate output containing a code line of the following structure:
<hi rendition="#rf-Lyricist">
[Lyricist: <anchor xml:id="w1s"/>Some Name]<anchor xml:id="w1e"/>
</hi>
Transforming the tei:hi element into is not a problem. The square brackets surrounding the text should be transformed into a nested tei:supplied, so the desired result is:
<note type="lyricist">
<supplied reason="provided-by-editor" cert="1" resp="#NN">
Lyricist: <anchor xml:id="w1s"/>Some Name<anchor xml:id="w1e"/>
</supplied>
</note>
I tried several ways of achieving this, inlcuding:
<xsl:template match="tei:body/tei:div[1]/tei:lg/tei:l/tei:hi[(#rendition='#rf-Lyricist')]">
<!-- This method deletes all tei:anchor within the lyricist line. -->
<xsl:analyze-string select="." regex="\[.*\]">
<xsl:matching-substring>
<xsl:element name="note" namespace="http://www.tei-c.org/ns/1.0">
<xsl:attribute name="type">
<xsl:value-of select="'lyricist'"/>
</xsl:attribute>
<xsl:element name="supplied" namespace="http://www.tei-c.org/ns/1.0">
<xsl:attribute name="reason">
<xsl:value-of select="'provided-by-editor'"/>
</xsl:attribute>
<xsl:attribute name="cert">
<xsl:value-of select="'1'"/>
</xsl:attribute>
<xsl:attribute name="resp">
<xsl:value-of select="'#ND'"/>
</xsl:attribute>
<xsl:value-of select="translate(., '[]', '')"/>
</xsl:element>
</xsl:element>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
I always get a result, where not only the square brackets, but also the tei:anchor elements are deleted, such as:
<note type="lyricist">
<supplied reason="provided-by-editor" cert="1" resp="#ND">
Lyricist: Some Name
</supplied>
</note>
The same method for replacing square brackets works perfectly well within a text node that has no further elements inside.
To implement "The square brackets surrounding the text should be transformed into a nested [..]supplied" I would use e.g.
<xsl:stylesheet 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"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="hi">
<note type="lyricist">
<xsl:variable name="content">
<xsl:apply-templates mode="brackets-to-elements"/>
</xsl:variable>
<xsl:for-each-group select="$content/node()" group-starting-with="osb">
<xsl:choose>
<xsl:when test="self::osb">
<xsl:for-each-group select="tail(current-group())" group-ending-with="csb">
<xsl:choose>
<xsl:when test="position() eq 1">
<supplied>
<xsl:apply-templates select="current-group()[not(position() = last())]"/>
</supplied>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</note>
</xsl:template>
<xsl:mode name="brackets-to-elements" on-no-match="shallow-copy"/>
<xsl:template match="hi//text()" mode="brackets-to-elements">
<xsl:apply-templates select="analyze-string(., '\[|\]')" mode="analyze"/>
</xsl:template>
<xsl:template match="fn:match[. = '[']" mode="analyze">
<osb/>
</xsl:template>
<xsl:template match="fn:match[. = ']']" mode="analyze">
<csb/>
</xsl:template>
</xsl:stylesheet>
which is the approach outlined in my comment, that is, to use one pass to find and convert square brackets in text nodes inside of hi elements to elements like e.g. osb and csb and a second pass on that intermediary tree to use standard/classic pairs of `for-each-group group-starting-with="osb"/group-ending-with="csb" to establish the wrapper element.
Output is e.g.
<note type="lyricist">
<supplied>Lyricist: <anchor xml:id="w1s"/>Some Name</supplied><anchor xml:id="w1e"/>
</note>

how can I Create a list variable from other lists in a condition

I have a list requiredStyles that is composed of other list depending on a series variable.
but it's not working correctly I get the output:
Warning: will not be created contain a cell paragraph with Adiv Adiv2 ADiv3 AC1 C1 fld-c1 ACount AJur
It doesn't seem to be creating requiredStyles as a list. Please assist.
requiredStyles
<xsl:variable name="requiredStyles">
<xsl:choose>
<xsl:when test="$series = 'Alpha'">
<xsl:value-of select=" ( $styleList1 , $styleList2 , $styleList3 , $styleList4 ) "/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select=" ( $styleList1 , $styleList2 , $styleList3 ) "/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test=" string-join( w:tr[ w:tc[1]/w:p[ w:pPr/w:pStyle[ #w:val = ( $requiredStyles ) ] ] ]/w:tc[2]//w:t , '' ) = '' ">
<xsl:call-template name="outputErrorMessage">
<xsl:with-param name="messageText" as="xs:string">
<xsl:variable name="temp" as="xs:string+">
<xsl:text>Warning: will not be created contain a cell paragraph with "</xsl:text>
<xsl:value-of select=" ( $requiredStyles ) " separator='", or "'/>
<xsl:text>" style.</xsl:text>
</xsl:variable>
<xsl:sequence select=" string-join( $temp , '' ) "/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
Moving it to and xpath if did the trick
Fiddle solution
<xsl:variable name="requiredStyles2" as="xs:string+"
select="
if ($series = 'Alpha')
then
( $style1 , $style2 , $style3 , $style4 )
else
( $style1 , $style2 , $style4 )
"
/>

How to create dynamic columns (same number of columns in header & row) using XSLT 2.0

I have an XML data source:
<ws:Report_Data xmlns:ws="urn:com.ws.report/Expense_Data">
<ws:Report_Entry>
<ws:uID>
<ws:id>1</ws:id>
</ws:uID>
<ws:Journal_Entry_Group>
<ws:Ledger_Accounts ws:Descriptor="Q1: TEST1">
<ws:ID ws:type="Ledger_Account_ID" ws:parent_type="Account_Set_ID"
ws:parent_id="Standard">Q1</ws:ID>
</ws:Ledger_Accounts>
</ws:Journal_Entry_Group>
<ws:line>
<ws:Number>000123</ws:Number>
</ws:line>
<ws:line>
<ws:Number>000124</ws:Number>
</ws:line>
</ws:Report_Entry>
<ws:Report_Entry>
<ws:uID>
<ws:id>2</ws:id>
</ws:uID>
<ws:Journal_Entry_Group>
<ws:Ledger_Accounts ws:Descriptor="Q1: TEST1">
<ws:ID ws:type="Ledger_Account_ID" ws:parent_type="Account_Set_ID"
ws:parent_id="Standard">Q1</ws:ID>
</ws:Ledger_Accounts>
<ws:Ledger_Accounts ws:Descriptor="Q2: TEST2">
<ws:ID ws:type="Ledger_Account_ID" ws:parent_type="Account_Set_ID"
ws:parent_id="Standard">Q2</ws:ID>
</ws:Ledger_Accounts>
</ws:Journal_Entry_Group>
<ws:line>
<ws:Number>000596</ws:Number> </ws:line>
</ws:Report_Entry>
</ws:Report_Data>
Based on the number of ws:ledger_accounts returned, I want to create the columns of the ledger account id and the description. The column header is generated dynamically as well (adding number _1, _2 and so on in the Ledger_Account_ID and Ledger_Account_Descheader name).
Based on the sample data source above, the second report entry has the highest number of ws:Journal_Entry_Group/ws:Ledger_Accounts returned, and so my header column should have 2 and the data row should have exact number of columns as well. The delimiter is <|> while the data row is enclosed with "".
Example expected Output:
uID<|>Ledger_Account_ID_1<|>Ledger_Account_ID_2<|>Ledger_Account_Desc_1<|>Ledger_Account_Desc_2|Number
"1"<|>"Q1"<|>""<|>"Q1: TEST1"<|>""<|>"000123"
"1"<|>"Q1"<|>""<|>"Q1: TEST1"<|>""<|>"000124"
"2"<|>"Q1"<|>"Q2"<|>"Q1: TEST1"<|>"Q2: TEST2"<|>"000596"
The XSLT code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="xsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ws="urn:com.ws.report/Expense_Data">
<xsl:output method="text"/>
<xsl:variable name="linefeed" select="'
'"/>
<xsl:variable name="delimiter"><![CDATA["<|>"]]></xsl:variable>
<xsl:variable name="delimiter1"><![CDATA[<|>]]></xsl:variable>
<xsl:template match="/ws:Report_Data">
<xsl:text>uID</xsl:text>
<xsl:value-of select="$delimiter1"/>
<xsl:value-of select="ws:Report_Entry[count(.//ws:Ledger_Accounts) = max(../ws:Report_Entry/count(.//ws:Ledger_Accounts))]//ws:Ledger_Accounts/concat('Ledger_Account_ID',$delimiter1)" separator=""/>
<xsl:value-of select="ws:Report_Entry[count(.//ws:Ledger_Accounts) = max(../ws:Report_Entry/count(.//ws:Ledger_Accounts))]//ws:Ledger_Accounts/concat('Ledger_Account_Desc',$delimiter1)" separator=""/>
<xsl:text>Number</xsl:text>
<xsl:value-of select="$linefeed"/>
<xsl:apply-templates select="/ws:Report_Data/ws:Report_Entry/ws:line"/>
</xsl:template>
<xsl:template match="/ws:Report_Data/ws:Report_Entry/ws:line ">
<xsl:text>"</xsl:text>
<xsl:value-of select="../ws:uID/ws:id"/>
<xsl:value-of select="$delimiter"/>
<!-- Ledger_Account_IDs -->
<xsl:if test="count(../ws:Journal_Entry_Group/ws:Ledger_Accounts) > 1">
<xsl:for-each select="../ws:Journal_Entry_Group/ws:Ledger_Accounts">
<xsl:value-of select="normalize-space(ws:ID[#ws:type='Ledger_Account_ID'])"/>
<xsl:if test="position()!=last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
</xsl:if>
<xsl:value-of select="$delimiter"/>
<!-- Ledger_Account_Desc -->
<xsl:if test="count(../ws:Journal_Entry_Group/ws:Ledger_Accounts) > 1">
<xsl:for-each select="../ws:Journal_Entry_Group/ws:Ledger_Accounts">
<xsl:value-of select="normalize-space(#ws:Descriptor)"/>
<xsl:if test="position()!=last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
</xsl:if>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="ws:Number"/>
<xsl:text>"</xsl:text>
<xsl:value-of select="$linefeed"/>
</xsl:template>
</xsl:stylesheet>
But the output is incorrect, the data row is not sync up with the number of columns in the header. For the first two lines it only creates 1 ledger account id column, should have at least another ""<|> before it goes to Ledger_Account_Desc column and the header can’t make it incremental.
Incorrect Output:
uID<|>Ledger_Account_ID<|>Ledger_Account_ID<|>Ledger_Account_Desc<|>Ledger_Account_Desc|Number
"1”<|>"Q1"<|>"Q1: TEST1"<|>"000123"
"1”<|>"Q1"<|>"Q1: TEST1"<|>"000124"
"2"<|>"Q1"<|>"Q2"<|>"Q1: TEST1"<|>"Q2: TEST2"<|>"000596"
I would use the separator attribute of value-of and then use a function to wrap each value in quotes; XSLT 3 would be
<?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"
xpath-default-namespace="urn:com.ws.report/Expense_Data"
xmlns:ws="urn:com.ws.report/Expense_Data"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="delimiter" as="xs:string"><![CDATA[<|>]]></xsl:param>
<xsl:param name="linefeed" as="xs:string" select="'
'"/>
<xsl:output method="text"/>
<xsl:function name="mf:quote" as="xs:string">
<xsl:param name="input" as="xs:string"/>
<xsl:sequence select="'"' || $input || '"'"/>
</xsl:function>
<xsl:variable name="max-accounts" select="max(//Report_Entry/count(.//Ledger_Accounts))"/>
<xsl:template match="/">
<xsl:value-of
select="'uID',
(1 to $max-accounts)!('Ledger_Account_ID_' || .),
(1 to $max-accounts)!('Ledger_Account_Desc_1' || .),
'Number'"
separator="{$delimiter}"/>
<xsl:value-of select="$linefeed"/>
<xsl:apply-templates select="//line"/>
</xsl:template>
<xsl:template match="line">
<xsl:value-of
select="(../uID/id,
for $i in 1 to $max-accounts return string(../descendant::Ledger_Accounts[$i]/ID),
for $i in 1 to $max-accounts return string(../descendant::Ledger_Accounts[$i]/#ws:Descriptor),
Number) ! mf:quote(.)"
separator="{$delimiter}"/>
<xsl:value-of select="$linefeed"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pNvtBGj

XSLT 2.0 xsl:result-document every n elements

I have a XML as follows:
<list>
<element>1</element>
<element>2</element>
<element>3</element>
<element>4</element>
<element>5</element>
<element>6</element>
<element>7</element>
<element>8</element>
<element>9</element>
<element>10</element>
<element>11</element>
<element>12</element>
<element>13</element>
<element>14</element>
<element>15</element>
<element>16</element>
<element>17</element>
<element>18</element>
<element>19</element>
<element>20</element>
Then I use this template to write the content to a file
<xsl:template match="list">
<xsl:result-document href="file:///c:/temp/bic.txt">
<xsl:for-each select="element">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:result-document>
</xsl:template>
But now I want to split the output so each five elements go to a different file. How can I do that?
One way is to use positional grouping:
<xsl:template match="list">
<xsl:for-each-group select="element" group-adjacent="(position() - 1) idiv 5">
<xsl:result-document href="bic-{position()}.txt" method="text">
<xsl:value-of select="current-group()" separator=""/>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>

XPaths are not working in XSLT global functions

I have implemented the following xslt global function, but it is not allowing to use XPath expressions.
For Example: //Track_Chainings/Track_Chaining[Track_ID=$TrackID]/Begin_Adjacent_Track_ID !='' is throwing exception.
<xsl:function name="conn:getConnetion" as="xs:string">
<xsl:param name="TYPE" as="xs:string"/>
<xsl:param name="TrackID" as="xs:string"/>
<xsl:param name="SwitchID" as="xs:string"/>
<xsl:choose>
<xsl:when test="($TYPE = 'TrackBegin')">
<xsl:if test="((//Track_Chainings/Track_Chaining[Track_ID=$TrackID]/Begin_Adjacent_Track_ID !='') and (//Track_Chainings/Track_Chaining[Track_ID=$TrackID]/Begin_Adjacent_Track_ID !='0'))">
<xsl:sequence select="concat(concat((TrackID * 10000) , '#'), (concat((//Track_Chainings/Track_Chaining[Track_ID=$TrackID]/Begin_Adjacent_Track_ID * 10000), 1)))"/>
</xsl:if>
</xsl:when>
<xsl:when test="($TYPE = 'TrackEnd')">
<xsl:if test="((//Track_Chainings/Track_Chaining[Track_ID=$TrackID]/End_Adjacent_Track_ID !='') and (//Track_Chainings/Track_Chaining[Track_ID=$TrackID]/End_Adjacent_Track_ID !='0'))">
<xsl:sequence select="concat(concat(concat((TrackID * 10000) , 1) , '#'), (//Track_Chainings/Track_Chaining[Track_ID=$TrackID]/End_Adjacent_Track_ID * 10000))"/>
</xsl:if>
</xsl:when>
<!-- <xsl:otherwise></xsl:otherwise> -->
</xsl:choose>
</xsl:function>
Could you please let us know if our implementation is having any issues.
Thank you in advance
There is no context node or context item in a function, thus, if you want to access nodes from a certain document, you will need to define an xsl:param for that document or node and pass it in in the function call e.g.
<xsl:function name="conn:getConnetion" as="xs:string">
<xsl:param name="TYPE" as="xs:string"/>
<xsl:param name="TrackID" as="xs:string"/>
<xsl:param name="SwitchID" as="xs:string"/>
<xsl:param name="track-doc" as="document-node()"/>
<xsl:choose>
<xsl:when test="($TYPE = 'TrackBegin')">
<xsl:if test="(($track-doc//Track_Chainings/Track_Chaining[Track_ID=$TrackID]/Begin_Adjacent_Track_ID !='') and ($track-doc//Track_Chainings/Track_Chaining[Track_ID=$TrackID]/Begin_Adjacen
....
and then call the function with the right document e.g. <xsl:value-of select="conn:getConnection('foo', 't1', 's2', /)"/>

Resources