I am pretty new to XSLT and having difficulty in coding logic to iterate and concatenate using XSLT 2.0. I am providing the scenario below.
Input XML:
<ADR_ENTR>
<ADR_ENTR_VW>
<ADR_LINE>
<TEXT>LINE1</TEXT>
</ADR_LINE>
<ADR_LINE>
<TEXT>LINE2</TEXT>
</ADR_LINE>
<ADR_LINE>
<TEXT>LINE3</TEXT>
</ADR_LINE>
<ADR_LINE>
<TEXT>LINE4</TEXT>
</ADR_LINE>
</ADR_ENTR_VW>
</ADR_ENTR>
There can be any number of ADR_LINE aggregates within aggregate, and I need to output first ADR_LINE as is, then ADR_LINE 2 - 4 needs to be concatenated, then ADR_LINE 4 to last ADR_LINE needs to be concatenated.
Sample Output Response:
<ADR_ENTR>
<ADR_ENTR_VW>
<ADR_LINE>
<TEXT>LINE1</TEXT>
</ADR_LINE>
<ADR_LINE>
<TEXT>LINE2 LINE3 LINE 4</TEXT>
</ADR_LINE>
</ADR_ENTR_VW>
</ADR_ENTR>
Any help is greatly appreciated.
This can be accomplished with a simple template that inserts the first <ADL_LINE> into its own element then concatenates all the others into a second:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="ADR_ENTR/ADR_ENTR_VW">
<ADR_ENTR>
<ADR_ENTR_VW>
<ADR_LINE>
<TEXT>
<xsl:value-of select="ADR_LINE[1]"/>
</TEXT>
</ADR_LINE>
<ADR_LINE>
<TEXT>
<xsl:value-of select="string-join(ADR_LINE[position()!=1],' ')"/>
</TEXT>
</ADR_LINE>
</ADR_ENTR_VW>
</ADR_ENTR>
</xsl:template>
</xsl:stylesheet>
This line selects the first <ADR_LINE> by using the position = 1 predicate:
<xsl:value-of select="ADR_LINE[1]"/>
This line then iterates over all the other <ADR_LINE> elements except the first one, concatenating them with a space delimiter:
<xsl:value-of select="string-join(ADR_LINE[position()!=1],' ')"/>
You can also use this.
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ADR_ENTR_VW">
<ADR_ENTR_VW>
<ADR_LINE>
<TEXT>
<xsl:value-of select="normalize-space(ADR_LINE[1])"/>
</TEXT>
</ADR_LINE>
<ADR_LINE>
<TEXT>
<xsl:value-of select="normalize-space(string-join(ADR_LINE[position() != 1],' '))"/>
</TEXT>
</ADR_LINE>
</ADR_ENTR_VW>
</xsl:template>
Related
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>
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
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>
My requirement is to concatenate values coming at run time in the ref element. I could get separate values for each element and the concatenate it but still i will miss out text in between.
I have edited my questions as per suggestion made by Martin.
Here is my input XML:
<?xml version="1.0" encoding="UTF-8"?><rootelement>
<nref ref="f1"/> <nref ref="f2"/> <nref ref="f3"/>
<nref ref="f4"/> <nref ref="f5"/>
<pnotes>
<pnote id="f1">
<p>Some Text.</p>
</pnote>
<pnote id="f2">
<p><ref><year>1812</year>, <vol>3</vol><series>F. &
F.</series><pages>22</pages></ref>, at p. 27.</p>
</pnote>
<pnote id="f3">
<p><ref>[<year>1914</year>],
<vol>2</vol><series>A.B.C.</series><pages>94</pages></ref>.</p>
</pnote>
<pnote id="f4">
<p><ref><year>1955</year>,
<vol>2</vol><series>A.B.C</series><pages>509</pages></ref>.</p>
</pnote>
<pnote id="f5">
<p><ref>[<year>1805</year>], <series>A.C.</series><pages>21</pages> </ref>.</p>
</pnote>
</pnotes></rootelement>
I have tried following XSLT
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="nref">
<xsl:variable name="ref" select="#ref"/>
<noteref>
<xsl:for-each select="//pnotes/pnote">
<xsl:if test="#id = $ref">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</noteref>
</xsl:template>
<xsl:template match="//pnotes[not(pnotes)]"/>
</xsl:stylesheet>
The result i get is not correct.
I need spaces in between the text.
Result :
<?xml version="1.0" encoding="UTF-8"?>
<rootelement>
<noteref> Some Text. </noteref>
<noteref> 1812, 3F. & F.22, at p. 27. </noteref>
<noteref> [1914], 2A.B.C.94. </noteref>
<noteref> 1955, 2A.B.C509. </noteref>
<noteref> [1805], A.C.21. </noteref>
</rootelement>
Required Result is:
<?xml version="1.0" encoding="UTF-8"?>
<rootelement>
<noteref> Some Text. </noteref>
<noteref> 1812, 3 F. & F. 22, at p. 27. </noteref>
<noteref> [1914], 2 A.B.C. 94. </noteref>
<noteref> 1955, 2 A.B.C 509. </noteref>
<noteref> [1805], A.C.21. </noteref>
</rootelement>
Can somebody help me please? Thanks in advance.
You are iterating over elemnts and anyone of them have square brackets:
When you make your first iteration, you get the element <year> and its value is 1902, not [1902]. Your square brackets are in the element <noteref>.
Change your XML to store the year as [1902] or change your for-each to print the square brackets manually.
Simply output the string value of the element to get all text, if you don't want the line breaks the also normalize the string:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<noteref>
<xsl:value-of select="normalize-space()"/>
</noteref>
</xsl:template>
</xsl:transform>
Result is
<rootelement>
<pnote>
<P>
<noteref>[1902], A.C. 12</noteref>.</P>
</pnote>
<pnote>
<P>
<noteref>1902 B.A. 98</noteref>.</P>
</pnote>
<pnote>
<P> This is a text </P>
</pnote>
<pnote>
<P>
<noteref>[1912], 1 R.B. 160</noteref>, at pp. 165, 169.</P>
</pnote>
</rootelement>
Online at http://xsltransform.hikmatu.com/b4GWV2.
I am trying to Extract Integer from a String using Xslt2.0
For Example consider the string "designa80000dd5424d" and i need the two integers inside the string i.e "8000" and "5424"
I tried using translate function as below
select="translate($term,translate($term, '0123456789', ''), '')"
But it combines both the integers and gives the output as "80005424"
i need something which separates them
I tried using translate function as below
select="translate($term,translate($term, '0123456789', ''), '')"
But it combines both the numbers and gives the output as "80005424" i
need something which separates them
I. Here is a complete XSLT 1.0 solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:variable name="vSpaces">
<xsl:call-template name="makeSpaces"/>
</xsl:variable>
<xsl:variable name="vtheNumbers"
select="normalize-space(translate(., translate(.,'0123456789',''), $vSpaces))"/>
<xsl:call-template name="tokenize">
<xsl:with-param name="pStr" select="$vtheNumbers"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="pStr"/>
<xsl:param name="pInd" select="1"/>
<xsl:if test="string-length($pStr)">
<xsl:value-of select=
"concat($pInd, ': ',substring-before(concat($pStr, ' '), ' '), '
')"/>
<xsl:call-template name="tokenize">
<xsl:with-param name="pStr" select="substring-after($pStr, ' ')"/>
<xsl:with-param name="pInd" select="$pInd +1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="makeSpaces">
<xsl:param name="pLen" select="string-length(.)"/>
<xsl:choose>
<xsl:when test="$pLen = 1">
<xsl:value-of select="' '"/>
</xsl:when>
<xsl:when test="$pLen > 1">
<xsl:variable name="vHalfLen" select="floor($pLen div 2)"/>
<xsl:call-template name="makeSpaces">
<xsl:with-param name="pLen" select="$vHalfLen"/>
</xsl:call-template>
<xsl:call-template name="makeSpaces">
<xsl:with-param name="pLen" select="$pLen -$vHalfLen"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>designa80000dd5424dan1733g122</t>
the wanted, correct result is produced:
1: 80000
2: 5424
3: 1733
4: 122
Do note:
The last argument of the outer translate() is a string having the same number of characters as that of the input string, and each of these characters is a space.
II. XPath 2.0 shorter and simpler
This XPath 2.0 expression when evaluated produces the wanted sequence of numbers:
tokenize(., '[^\d]+')[.]
Here is an XSLT - based verification:
<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:variable name="vNumbers"
select="tokenize(., '[^\d]+')[.]"/>
<xsl:for-each select="$vNumbers">
<xsl:value-of select="concat(position(), ': ', ., '
')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document:
<t>designa80000dd5424dan1733g122</t>
the same correct result is produced:
1: 80000
2: 5424
3: 1733
4: 122
You could try it using tokenize with any non-digit sequences as the separator, i.e. using XPath 3.0 tokenize('designa80000dd5424d', '[^0-9]+')[normalize-space()]!number() or in XSLT/XPath 2.0 as for $t in tokenize('designa80000dd5424d', '[^0-9]+')[normalize-space()] return number($t) or you could use xsl:analyze-string (XSLT 2.0) or the analyze-string function (XSLT/XPath 3.0, but available with Saxon 9.7 HE).