XSLT Concatenate element values - xslt-2.0

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.

Related

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

Getting an error while using format- number - NaN

I have to select the value of v1 which has match as V0 and (Need to make a 13 digit number , also remove the decimal point)
I am getting NaN.
Please guide.
XSLT:
<?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" >
<xsl:output method="text" encoding="utf-8" />
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:template match="A">
<xsl:value-of select="format-number(B[match = V0]/v1 * 100,'0000000000000')"/>
</xsl:template>
INPUT:
<A>
<B>
<match>V0</match>
<v1>34.56</v1>
</B>
<B>
<match>V1</match>
<v1>34.54</v1>
</B>
Expected - 0000000003456
Actual - NaN
Just change
<xsl:value-of select="format-number(B[match = V0]/v1 * 100,'0000000000000')"/>
to
<xsl:value-of select="format-number(B[match = 'V0']/v1 * 100,'0000000000000')"/>
And the rest will follow.
Output is:
000000003456
You forgot to put your element value into single quotes.

How to check if given comma separated values is ascending numerical sequence

I would like to check if given comma separated value is ascending numeric sequence.
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<isSequence>
<seq name="a" value="1, 2, 3"/>
<seq name="b" value="3, 1, 5, 6"/>
<seq name="c" value="15, 16, 18, 0"/>
<seq name="d" value="21, 22, 23, 24, 25"/>
<seq name="e" value="A, B, C"/>
</isSequence>
</root>
I have tried below code
<?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="/">
<xsl:for-each select="root/isSequence/seq">
<xsl:if test="subsequence(#value, 0)">
<xsl:value-of select="#name"/> is an ascending numeric sequence
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Expected Result
a is an ascending numeric sequence
d is an ascending numeric sequence
Assuming you are looking for an integer sequence then I think (in XSLT 3) this can be expressed as
<xsl:template match="seq[let $items := tokenize(#value, ',\s+')!xs:integer(.) return every $p in 1 to count($items) satisfies $items[$p] = $items[1] + ($p - 1)]">
<xsl:value-of select="#name"/><xsl:text> is an ascending numeric sequence
</xsl:text>
</xsl:template>
Full example (https://xsltfiddle.liberty-development.net/6r5Gh2H)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text"/>
<xsl:template match="seq[let $items := tokenize(#value, ',\s+')!xs:integer(.) return every $p in 1 to count($items) satisfies $items[$p] = $items[1] + ($p - 1)]">
<xsl:value-of select="#name"/><xsl:text> is an ascending numeric sequence
</xsl:text>
</xsl:template>
</xsl:stylesheet>
In XSLT 2 with XPath 2 I don't think you have let to express that compactly in a match pattern but you can of course use a variable at XSLT level:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output method="text"/>
<xsl:template match="seq[every $i in tokenize(#value, ',\s+') satisfies $i castable as xs:integer]">
<xsl:variable name="items" select="for $item in tokenize(#value, ',\s+') return xs:integer($item)"/>
<xsl:if test="every $p in 1 to count($items) satisfies $items[$p] = $items[1] + ($p - 1)">
<xsl:value-of select="#name"/><xsl:text> is an ascending numeric sequence
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
http://xsltransform.hikmatu.com/bFDb2BU

Separating data into groups

I think I'm missing something simple here. I have a source XML file
<Inventory Division="B" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<StackGroup name="Warehouse">
<Stack>
<Mainstack name="PRIMARY">
<MainstackGroup name="GROUP_PRIMARY">
<MainstackLayer sequence="1">
<StackLayerRef id="LAYER_1"/>
</MainstackLayer>
</MainstackGroup>
</Mainstack>
<Mainstack name="SECONDARY">
<MainstackGroup name="GROUP_SECONDARY">
<MainstackLayer sequence="2">
<StackLayerRef id="LAYER_2"/>
</MainstackLayer>
</MainstackGroup>
</Mainstack>
</Stack>
</StackGroup>
</Inventory>
I'm applying a stylesheet:
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Mainstack>
<NumberOfStacks>
<xsl:value-of select="count(/Inventory/StackGroup/Stack/Mainstack)"/>
</NumberOfStacks>
<StackDisplayOrder>
<xsl:apply-templates select="/Inventory/StackGroup/Stack/Mainstack" mode="order"/>
</StackDisplayOrder>
<xsl:apply-templates select="/Inventory/StackGroup/Stack/Mainstack" mode="stacklist"/>
</Mainstack>
</xsl:template>
<xsl:template match="/Inventory/StackGroup/Stack/Mainstack" mode="order">
<Index><xsl:value-of select="position() - 1" /></Index>
</xsl:template>
<xsl:template match="/Inventory/StackGroup/Stack/Mainstack" mode="stacklist">
<Stack>
<Index><xsl:value-of select="position() - 1" /></Index>
<Name>
<xsl:value-of select="/Inventory/StackGroup/Stack/Mainstack/#name"/>
</Name>
<GroupName>
<xsl:value-of select="/Inventory/StackGroup/Stack/Mainstack/MainstackGroup/#name"/>
</GroupName>
<SequenceNo>
<xsl:value-of select="/Inventory/StackGroup/Stack/Mainstack/MainstackGroup/MainstackLayer/#sequence"/>
</SequenceNo>
<LayerNo>
<xsl:value-of select="/Inventory/StackGroup/Stack/Mainstack/MainstackGroup/MainstackLayer/StackLayerRef/#id"/>
</LayerNo>
</Stack>
</xsl:template>
</xsl:stylesheet>
I get the following output:
<?xml version="1.0" encoding="UTF-8"?>
<Mainstack>
<NumberOfStacks>2</NumberOfStacks>
<StackDisplayOrder>
<Index>0</I`enter code here`ndex>
<Index>1</Index>
</StackDisplayOrder>
<Stack>
<Index>0</Index>
<Name>PRIMARY SECONDARY</Name>
<GroupName>GROUP_PRIMARY GROUP_SECONDARY</GroupName>
<SequenceNo>1 2</SequenceNo>
<LayerNo>LAYER_1 LAYER_2</LayerNo>
</Stack>
<Stack>
<Index>1</Index>
<Name>PRIMARY SECONDARY</Name>
<GroupName>GROUP_PRIMARY GROUP_SECONDARY</GroupName>
<SequenceNo>1 2</SequenceNo>
<LayerNo>LAYER_1 LAYER_2</LayerNo>
</Stack>
</Mainstack>
The template obviously finds two matches and concatenates the data from both. How can I separate them?
Thanks in advance!
Use relative paths so change e.g.
<Name>
<xsl:value-of select="/Inventory/StackGroup/Stack/Mainstack/#name"/>
</Name>
to
<Name>
<xsl:value-of select="#name"/>
</Name>
and so on for all the paths in the template.

How do i modify the text content within a specified set of nodes using XSL?

I have a list of node ids. I want to append "-Selected" to all the text nodes within the given set of node ids. Please let me know how we can achieve the same using XSL?
Input:
<node1 id="a">
<node2 id="b">
<node3 id="c">Text node0</node3>
<node4 id="d">
<node5 id="e">Text node1</node5>
<node6 id="f">Text node2</node6>
</node4>
</node2>
<node7 id="g">Text node3
<node8 id="h" align="center">Text node4</node8>
<node9 id="i">Text node5</node9>
</node7>
<node10 id="j">Text node6
</node10>
<node11 id="h">Text node7
</node11>
</node1>
Input Param: List of node ids
<nodes>
<node>b</node>
<node>g</node>
<node>h</node>
</nodes>
If the input param has node 'a' then i need all the text nodes to be appended with selected.
Expected output:
<node1 id="a">
<node2 id="b">
<node3 id="c">Text node0 - Selected<node3/>
<node4 id="d">
<node5 id="e">Text node1 - Selected</node5>
<node6 id="f">Text node2 - Selected</node6>
</node4>
</node2>
<node7 id="g">Text node3 - Selected
<node8 id="h" align="center">Text node4 - Selected</node8>
<node9 id="i">Text node5 - Selected</node9>
</node7>
<node10 id="j">Text node6
</node10>
<node11 id="h">Text node7 - Selected
</node11>
</node1>
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pNodes">
<nodes>
<node>b</node>
<node>g</node>
<node>h</node>
</nodes>
</xsl:param>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#id = $pNodes/*/*]//text()">
<xsl:value-of select="concat(., ' Selected')"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML Document, produces the wanted, correct result.
This XSLT 2.0 stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pNodes">
<nodes>
<node>b</node>
<node>g</node>
<node>h</node>
</nodes>
</xsl:param>
<xsl:template match="text()" >
<xsl:value-of select="." />
<xsl:if test="ancestor::*[#id = $pNodes/*/*] and normalize-space()!=''">
<xsl:text> - Selected</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="#* | * | comment() | processing-instruction()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Uses a modified identity transform. The template match on text() has an if statement that evaluates whether the text node is a descendant of an element with an #id who's value matches one of the specified nodes, and that the text node is not all whitespace(you can adjust that as necessary, but otherwise). If it meets those criteria, it outputs " - selected".
Applied to the sample input it produces this output:
<?xml version="1.0" encoding="UTF-8"?><node1 id="a">
<node2 id="b">
<node3 id="c"/>
<node4 id="d">
<node5 id="e">Text node1 - Selected</node5>
<node6 id="f">Text node2 - Selected</node6>
</node4>
</node2>
<node7 id="g">Text node3
- Selected<node8 id="h" align="center">Text node4 - Selected</node8>
<node9 id="i">Text node5 - Selected</node9>
</node7>
<node10 id="j">Text node6
</node10>
<node11 id="h">Text node7
- Selected</node11>
</node1>

Resources