XSLT 2 not recognizing NULL value in TSV - xslt

I am parsing a TSV file and using only few column values.
If some of the column values are NULL, the XSLT is not recognizing them and counting the next column instead.
If I change the NULL value with some data, the XSLT is working fine.
Can someone help?
<?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:output method="xml" indent="yes"/>
<xsl:param name="identifier"/>
<xsl:param name="csvData"/>
<xsl:template match="/">
<xsl:variable name="csv" select="$csvData"/>
<xsl:variable name="data">
<data>
<xsl:analyze-string select="$csv" regex="\n">
<xsl:non-matching-substring>
<row>
<xsl:analyze-string select="." regex="\t" flags="x">
<xsl:non-matching-substring>
<col>
<xsl:value-of select="normalize-space(.)"/>
</col>
</xsl:non-matching-substring>
</xsl:analyze-string>
</row>
</xsl:non-matching-substring>
</xsl:analyze-string>
</data>
</xsl:variable>
<xsl:result-document>
<Events>
<xsl:for-each select="$data//row">
<Attendance>
<RetID><xsl:value-of select="../$identifier"/></RetID>
<AccountId><xsl:value-of select="col[1]"/></AccountId>
<EventId><xsl:value-of select="col[2]"/></EventId>
<EventName><xsl:value-of select="col[3]"/></EventName>
<EventDate><xsl:value-of select="col[4]"/></EventDate>
<EventTime><xsl:value-of select="col[5]"/></EventTime>
</Attendance>
</xsl:for-each>
</Events>
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>

Assuming a "NULL" value in the tab separated data is indicated by two consecutive tab characters then I would suggest to replace
<xsl:analyze-string select="." regex="\t" flags="x">
<xsl:non-matching-substring>
<col>
<xsl:value-of select="normalize-space(.)"/>
</col>
</xsl:non-matching-substring>
</xsl:analyze-string>
with
<xsl:for-each select="tokenize(., '\t')">
<col>
<xsl:value-of select="normalize-space(.)"/>
</col>
</xsl:analyze-string>

Related

Multiple Counters in XSLT

I need to maintain 2 counters in my xslt - EntryID and RowID. I have a xml which contains Name and Certifications the person holds. Now I need to maintain one counter for each person (EntryID) and one for each certification (RowID). And on top of the certifications, I need to add one more certification "New" which will have a RowID one number higher than the maximum number certifications the person holds.
Below is the XML:
<?xml version='1.0' encoding='UTF-8'?>
<wd:Report_Data xmlns:wd="urn:com.workday.report/bsvc">
<wd:Report_Entry>
<Name>Ram</Name>
<Certifications>
<Certificate>AWS</Certificate>
<Certificate>Workday</Certificate>
<Certificate>SAP</Certificate>
</Certifications>
</wd:Report_Entry>
<wd:Report_Entry>
<Name>Nitin</Name>
<Certifications>
<Certificate>Workday</Certificate>
</Certifications>
</wd:Report_Entry>
<wd:Report_Entry>
<Name>Joe</Name>
<Certifications>
<Certificate>SAP</Certificate>
<Certificate>AWS</Certificate>
</Certifications>
</wd:Report_Entry>
</wd:Report_Data>
The expected output is below. The name should appear only in the first row.
EntryID,Name,RowID,Certification
1,Ram,1,AWS
1,,2,Workday
1,,3,SAP
1,,4,NEW --> New certificate with row id 4 as Ram already has 3 certifications
2,Nitin,1,Workday --> Entry ID is 2 for Nitin and Row ID restarts from 1
2,,2,NEW
3,Joe,1,SAP
3,,2,AWS
3,,3,NEW
The XSLT I am able to build so far, but not giving desired output.
<?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"
xmlns:wd="urn:com.workday.report/bsvc"
xmlns:etv="urn:com.workday/etv"
exclude-result-prefixes="xs wd" version="3.0">
<xsl:output method="text" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="delimeter" select="','"/>
<xsl:variable name="lineFeed" select="'
'"/>
<root>
<xsl:for-each select="wd:Report_Data/wd:Report_Entry">
<EntryID><xsl:value-of select="position()"/></EntryID>
<xsl:value-of select="$delimeter"/>
<Name><xsl:value-of select="Name"/></Name>
<xsl:value-of select="$delimeter"/>
<xsl:for-each select="Certifications/Certificate">
<RowID><xsl:value-of select="position()"/></RowID>
<xsl:value-of select="$delimeter"/>
<xsl:value-of select="."/>
</xsl:for-each>
<xsl:value-of select="$lineFeed"/>
<EntryID><xsl:value-of select="position()"/></EntryID>
<xsl:value-of select="$delimeter"/>
<xsl:value-of select="$delimeter"/>
<text>NEW</text>
<xsl:value-of select="$lineFeed"/>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Please help me with correct XSLT.
To produce the wanted output with XSLT 3, I would use something like
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:wd="urn:com.workday.report/bsvc"
expand-text="yes">
<xsl:param name="delimeter" select="','"/>
<xsl:param name="lineFeed" select="'
'"/>
<xsl:output method="text"/>
<xsl:template match="wd:Report_Data">
<xsl:value-of select="'EntryID','Name','RowID','Certification'" separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
<xsl:apply-templates select="wd:Report_Entry"/>
</xsl:template>
<xsl:template match="wd:Report_Entry">
<xsl:apply-templates select="Certifications/Certificate"/>
</xsl:template>
<xsl:template match="Certificate">
<xsl:variable name="entry-id" as="xs:integer">
<xsl:number count="wd:Report_Entry"/>
</xsl:variable>
<xsl:variable name="row-id" as="xs:integer">
<xsl:number/>
</xsl:variable>
<xsl:value-of select="$entry-id, (../../Name[$row-id = 1], '')[1], $row-id, ." separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
<xsl:if test="position() = last()">
<xsl:value-of select="$entry-id, '', $row-id + 1, 'NEW'" separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The XML result you show can be produced quite easily using:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc"
exclude-result-prefixes="wd">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/wd:Report_Data">
<root>
<xsl:for-each select="wd:Report_Entry">
<row>
<EntryID>
<xsl:value-of select="position()" />
</EntryID>
<Name>
<xsl:value-of select="Name" />
</Name>
<xsl:for-each select="Certifications/Certificate">
<Certificate>
<RowID>
<xsl:value-of select="position()" />
</RowID>
<cName>
<xsl:value-of select="." />
</cName>
</Certificate>
</xsl:for-each>
<Certificate>
<RowID>
<xsl:value-of select="count(Certifications/Certificate) + 1" />
</RowID>
<cName>NEW</cName>
</Certificate>
</row>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
To get a "flat" CSV output, you can do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/wd:Report_Data">
<!-- header -->
<xsl:text>EntryID,Name,RowID,Certification
</xsl:text>
<!-- data -->
<xsl:for-each select="wd:Report_Entry">
<xsl:variable name="entry-data">
<xsl:value-of select="position()" />
<xsl:text>,</xsl:text>
<xsl:value-of select="Name" />
<xsl:text>,</xsl:text>
</xsl:variable>
<!-- output -->
<xsl:for-each select="Certifications/Certificate">
<xsl:copy-of select="$entry-data"/>
<xsl:value-of select="position()" />
<xsl:text>,</xsl:text>
<xsl:value-of select="." />
<xsl:text>
</xsl:text>
</xsl:for-each>
<!-- new certificate -->
<xsl:copy-of select="$entry-data"/>
<xsl:value-of select="count(Certifications/Certificate) + 1" />
<xsl:text>,NEW
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
which in XSLT 3.0 can be reduced to:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc"
expand-text="yes">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/wd:Report_Data">
<!-- header -->
<xsl:text>EntryID,Name,RowID,Certification
</xsl:text>
<!-- data -->
<xsl:for-each select="wd:Report_Entry">
<xsl:variable name="entry-data">{position()},{Name}</xsl:variable>
<!-- output -->
<xsl:for-each select="Certifications/Certificate, 'NEW'">{$entry-data},{position()},{.}
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

I want to generate the hyperlink for all figure callout in the text and id should be same as figure id

Please check and suggest,
what should be the right code and idref value should be same as figure id. I am trying with analyze-string but unable to do.
Please check and suggest,
what should be the right code and idref value should be same as figure id. I am trying with analyze-string but unable to do.
input
<book>
<figure id="ch01fig01">
<label>Figure 01</label>
<figcaption>xxx</figcaption>
</figure>
<p>This is a Figure 01 and this is figure 02</p>
<p>This is a Figure 01 and this is figure 02</p>
<figure id="ch01fig02">
<label>Figure 02</label>
<figcaption>xxx</figcaption>
</figure>
</book>
output
<book>
<figure id="ch01fig01">
<label>Figure 01</label>
<figcaption>xxx</figcaption>
</figure>
<p>This is a <internal idref="ch01fig01">Figure 01</internal> and this is <internal idref="ch01fig02">Figure 02</internal></p>
<p>This is a <internal idref="ch01fig01">Figure 01</internal> and this is <internal idref="ch01fig02">Figure 02</internal></p>
<figure id="ch01fig02">
<label>Figure 02</label>
<figcaption>xxx</figcaption>
</figure>
</book>
xslt
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//text()[not(parent::label)]">
<xsl:analyze-string select="." regex="figure\s+\d+" flags="i">
<xsl:matching-substring>
<internal>
<xsl:attribute name="idref">
<xsl:call-template name="mk">
<xsl:with-param name="mk11" select="."/>
</xsl:call-template>
</xsl:attribute>
<xsl:value-of select="."/>
</internal>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template name="mk">
<xsl:param name="mk11"/>
<xsl:for-each select="//figure">
<xsl:if test="child::label eq $mk11">
<xsl:value-of select="#id"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
First consider using a key to look up the figures...
<xsl:key name="figures" match="figure" use="lower-case(label)" />
(I am using lower-case here, because you have a "figure 02" in the text, but "Figure 02" in the label).
Your main problem is that within xsl:matching-substring you are no longer within the context of the original node you are matching, so you are probably getting an error along the lines of "the context item is not a node"
To get around this, define a variable to allow you to reference the original document...
<xsl:variable name="doc" select="/" />
Then to get the figure value using the key, you can do this...
<xsl:value-of select="key('figures', lower-case($mk11), $doc)/#id" />
So, this will look up the key in the context of the original document.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:key name="figures" match="figure" use="lower-case(label)" />
<xsl:variable name="doc" select="/" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//text()[not(parent::label)]">
<xsl:analyze-string select="." regex="figure\s+\d+" flags="i">
<xsl:matching-substring>
<internal>
<xsl:attribute name="idref">
<xsl:call-template name="mk">
<xsl:with-param name="mk11" select="."/>
</xsl:call-template>
</xsl:attribute>
<xsl:value-of select="."/>
</internal>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template name="mk">
<xsl:param name="mk11"/>
<xsl:value-of select="key('figures', lower-case($mk11), $doc)/#id" />
</xsl:template>
</xsl:stylesheet>
In fact, you can simplify this by doing away with the named template, and making use of Attribute Value Templates to create the idref attribute
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:key name="figures" match="figure" use="lower-case(label)" />
<xsl:variable name="doc" select="/" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//text()[not(parent::label)]">
<xsl:analyze-string select="." regex="figure\s+\d+" flags="i">
<xsl:matching-substring>
<internal idref="{key('figures', lower-case(.), $doc)/#id}">
<xsl:value-of select="."/>
</internal>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
I used another way instead of the key as well it is also working good. Below is code done some changes.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="figs" select="//figure" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[not(parent::label)]">
<xsl:choose>
<xsl:when test="matches(., 'Figure', 'i')">
<xsl:analyze-string select="." regex="(Figure ([0-9]+))" flags="i">
<xsl:matching-substring>
<xsl:variable name="ids" select="for $ss in $figs
return
if(matches($ss/label, regex-group(1), 'i'))
then $ss/#id
else ()"></xsl:variable>
<internal idref="{$ids[1]}">
<xsl:value-of select="."/>
</internal>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Below is the outcome after running XSLT.
<?xml version="1.0" encoding="UTF-8"?>
<book>
<figure id="ch01fig01">
<label>Figure 01</label>
<figcaption>xxx</figcaption>
</figure>
<p>This is a <internal idref="ch01fig01">Figure 01</internal> and this is <internal idref="ch01fig02">figure 02</internal>
</p>
<p>This is a <internal idref="ch01fig01">Figure 01</internal> and this is <internal idref="ch01fig02">figure 02</internal>
</p>
<figure id="ch01fig02">
<label>Figure 02</label>
<figcaption>xxx</figcaption>
</figure>
</book>

XSLT 1.0 Split comma seperated string into named nodes

A customer supplied XML contains the delivery address as a comma separated string, I would like to split this string into named nodes using XSLT 1.0.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<text>
Company, Streetaddress 20, 1234 AA, City
</text>
</root>
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<text>
<COMPANY>Company</COMPANY>
<ADDRESS>Streetaddress 20</ADDRESS>
<ZIPCODE>1234 AA</ZIPCODE>
<CITY>City</CITY>
</text>
</root>
I tried several recursive templates for XSLT 1.0 which do a fine job splitting but the resulting nodes are identically named.
If possible, how can this be achieved using XSLT 1.0?
Does it have to be a recursive template? How about a straight-forward chain of substring-before and substring-after like this:
<xsl:template match="text">
<xsl:copy>
<COMPANY>
<xsl:value-of select="normalize-space(substring-before(., ','))"/>
</COMPANY>
<xsl:variable name="s1" select="substring-after(., ',')"/>
<ADDRESS>
<xsl:value-of select="normalize-space(substring-before($s1, ','))"/>
</ADDRESS>
<xsl:variable name="s2" select="substring-after($s1, ',')"/>
<ZIPCODE>
<xsl:value-of select="normalize-space(substring-before($s2, ','))"/>
</ZIPCODE>
<CITY>
<xsl:value-of select="normalize-space(substring-after($s2, ','))"/>
</CITY>
</xsl:copy>
</xsl:template>
For the fun of it, here is a generic version using a recursive template.
<xsl:template match="text">
<xsl:copy>
<xsl:call-template name="parse-comma-separated">
<xsl:with-param name="elements" select="'COMPANY,ADDRESS,ZIPCODE,CITY'"/>
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="parse-comma-separated">
<xsl:param name="elements"/>
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($elements, ',')">
<xsl:element name="{normalize-space(substring-before($elements, ','))}">
<xsl:value-of select="normalize-space(substring-before($text, ','))"/>
</xsl:element>
<xsl:call-template name="parse-comma-separated">
<xsl:with-param name="elements" select="substring-after($elements, ',')"/>
<xsl:with-param name="text" select="substring-after($text, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{normalize-space($elements)}">
<xsl:value-of select="normalize-space($text)"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
As the supplied XML only had the address as single node (Streettaddress 20) I added a second variable to split the address string into streetaddress and housenumber.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:template match="text">
<root>
<COMPANY>
<xsl:value-of select="normalize-space(substring-before(., ','))"/>
</COMPANY>
<xsl:variable name="s1" select="substring-after(., ',')"/>
<xsl:variable name="address_temp" select="normalize-space(substring-before($s1, ','))"/>
<ADDRESS>
<xsl:value-of select="normalize-space(substring-before($address_temp, ' '))"/>
</ADDRESS>
<HOUSENUMBER>
<xsl:value-of select="normalize-space(substring-after($address_temp, ' '))"/>
</HOUSENUMBER>
<xsl:variable name="s2" select="substring-after($s1, ',')"/>
<ZIPCODE>
<xsl:value-of select="normalize-space(substring-before($s2, ','))"/>
</ZIPCODE>
<CITY>
<xsl:value-of select="normalize-space(substring-after($s2, ','))"/>
</CITY>
</root>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<COMPANY>Company</COMPANY>
<ADDRESS>Streetaddress</ADDRESS>
<HOUSENUMBER>20</HOUSENUMBER>
<ZIPCODE>1234 AA</ZIPCODE>
<CITY>City</CITY>
</root>

CSV to XML XSLT: How to quote excape

I've been working on CSV to XML conversions using this style-sheet:
XSLT 2.0 to convert CSV to XML format
I needed to account for both comma and pipe delimiters so I changed the style sheet to this:
<?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" exclude-result-prefixes="xs">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="csv-uri" as="xs:string" select="'file:///c:/test.csv'"/>
<xsl:template match="/" name="csv2xml">
<Entity>
<Rows>
<xsl:choose>
<xsl:when test="unparsed-text-available($csv-uri)">
<xsl:variable name="csv" select="unparsed-text($csv-uri)" />
<xsl:variable name="pipe" select="'\|'"/>
<xsl:analyze-string select="replace($csv,$pipe,',')" regex='\r\n?|\n' >
<xsl:non-matching-substring>
<xsl:if test="not(position()=0)" >
<Row>
<xsl:for-each select="tokenize(.,',')" >
<xsl:element name="Column_{position()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</Row>
</xsl:if>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="error">
<xsl:text>Error reading file: "</xsl:text>
<xsl:value-of select="$csv-uri"/>
</xsl:variable>
<xsl:message>
<xsl:value-of select="$error"/>
</xsl:message>
<xsl:value-of select="$error"/>
</xsl:otherwise>
</xsl:choose>
</Rows>
</Entity>
</xsl:template>
</xsl:stylesheet>
However, I've been having some difficulty accounting for quoted values from the input.
Currently if a row in the csv is:
1,2,3,4,5,"testing,1,1,1"
red,white,blue,green
dogs|cats|rabbits
the "testing,1,1,1" gets split into 4 columns into the CSV instead of one column.
output:
<?xml version="1.0" encoding="UTF-8"?>
<Entity>
<Rows>
<Row>
<Column_1>1</Column_1>
<Column_2>2</Column_2>
<Column_3>3</Column_3>
<Column_4>4</Column_4>
<Column_5>5</Column_5>
<Column_6>"testing</Column_6>
<Column_7>1</Column_7>
<Column_8>1</Column_8>
<Column_9>1"</Column_9>
</Row>
<Row>
<Column_1>red</Column_1>
<Column_2>white</Column_2>
<Column_3>blue</Column_3>
<Column_4>green</Column_4>
</Row>
<Row>
<Column_1>dogs</Column_1>
<Column_2>cats</Column_2>
<Column_3>rabbits</Column_3>
</Row>
</Rows>
</Entity>
I've done some research and using regex='("[^"]*")+' can accomplish this. But I'm not entirely sure how to implement this without removing something I have that I need (maybe probably in the Analyze-string block?). I need help please! Its probably something simple, so please school me or point me in the right direction. Any advice would be helpful.
You can just add another xsl:analyze-string to process the xsl:non-matching-substring from the first xsl:analyze-string...
CSV Input
1,2,3,4,5,"testing,1,1,1"
red,white,blue,green
dogs|cats|rabbits
Modified XSLT 2.0
<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 indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="csv-uri" as="xs:string" select="'file:///c:/users/dhaley/desktop/so.csv'"/>
<xsl:template match="/" name="csv2xml">
<Entity>
<Rows>
<xsl:choose>
<xsl:when test="unparsed-text-available($csv-uri)">
<xsl:variable name="csv" select="unparsed-text($csv-uri)" />
<xsl:variable name="pipe" select="'\|'"/>
<xsl:analyze-string select="replace($csv,$pipe,',')" regex='\r\n?|\n' >
<xsl:non-matching-substring>
<xsl:if test="not(position()=0)" >
<Row>
<xsl:analyze-string select="." regex=""([^"]*)",?|([^,]+),?">
<xsl:matching-substring>
<xsl:element name="Column_{position()}">
<xsl:value-of select="normalize-space(concat(regex-group(1),regex-group(2)))"/>
</xsl:element>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:element name="Column_{position()}"/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</Row>
</xsl:if>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="error">
<xsl:text>Error reading file: "</xsl:text>
<xsl:value-of select="$csv-uri"/>
</xsl:variable>
<xsl:message>
<xsl:value-of select="$error"/>
</xsl:message>
<xsl:value-of select="$error"/>
</xsl:otherwise>
</xsl:choose>
</Rows>
</Entity>
</xsl:template>
</xsl:stylesheet>
XML Output
<Entity>
<Rows>
<Row>
<Column_1>1</Column_1>
<Column_2>2</Column_2>
<Column_3>3</Column_3>
<Column_4>4</Column_4>
<Column_5>5</Column_5>
<Column_6>testing,1,1,1</Column_6>
</Row>
<Row>
<Column_1>red</Column_1>
<Column_2>white</Column_2>
<Column_3>blue</Column_3>
<Column_4>green</Column_4>
</Row>
<Row>
<Column_1>dogs</Column_1>
<Column_2>cats</Column_2>
<Column_3>rabbits</Column_3>
</Row>
</Rows>
</Entity>
The regex from the second xsl:analyze-string is using captured substrings to ignore the quotes and the comma. Here's an easier to read version:
"([^"]*)",?|([^,]+),?
If you want to keep the quotes, move them inside the parens:
("[^"]*"),?|([^,]+),?

xslt aggregation sum

I want do some sum of the values and return it as a row or column with the data.
taking the below xml as example:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<root>
<default0>
<Group>
<groupEntry>
<Day>Mon</Day>
<ID>111</ID>
<Number>-3</Number>
</groupEntry>
</Group>
<Group>
<groupEntry>
<Day>Tue</Day>
<ID>222</ID>
<Number>4</Number>
</groupEntry>
</Group>
<Group>
<groupEntry>
<Day>Tue</Day>
<ID>444</ID>
<Number>5</Number>
</groupEntry>
<Breakdown>
<Details>
<Day>Tue</Day>
<ID>444</ID>
<Number>-3</Number>
</Details>
<Details>
<Day>Tue</Day>
<ID>444</ID>
<Number>8</Number>
</Details>
</Breakdown>
</Group>
<Group>
<groupEntry>
<Day>Fri</Day>
<ID>333</ID>
<Number>-3</Number>
</groupEntry>
</Group>
</default0>
</root>
My below xslt :
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>ID,Day,Number</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="groupEntry|Details">
<xsl:text>
</xsl:text>
<xsl:value-of select="ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Number"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
returns this result:
ID,Day,Number
111,Mon,-3
222,Tue,4
444,Tue,5
444,Tue,-3
444,Tue,8
333,Fri,-3
However I want to get the total number by Day and report it in the result as either of the below 2 options
create 1 summary row like:
ID,Day,Number
Mon,Mon,-3
111,Mon,-3
Tue,Tue,9
222,Tue,4
444,Tue,5
444,Tue,-3
444,Tue,8
Fri,Fri,-3
333,Fri,-3
create an extra column:
ID,Day,Number,TotalNumber
111,Mon,-3,-3
222,Tue,4,9
444,Tue,5,9
444,Tue,-3,9
444,Tue,8,9
333,Fri,-3,-3
Does anyone know if this is possible?
In either option, you would probably need to define a key to group the elements by Day
<xsl:key name="days" match="groupEntry|Details" use="Day"/>
Then you can just add your extra column like so
<xsl:value-of select="sum(key('days', Day)/Number)"/>
Here is the full XSLT for the first option
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="days" match="groupEntry|Details" use="Day"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>ID,Day,Number,TotalNumber</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="groupEntry|Details">
<xsl:text>
</xsl:text>
<xsl:value-of select="ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Number"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="sum(key('days', Day)/Number)"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
This should output the following results
ID,Day,Number,TotalNumber
111,Mon,-3,-3
222,Tue,4,14
444,Tue,5,14
444,Tue,-3,14
444,Tue,8,14
333,Fri,-3,-3
In the second option, you would want to add a total line for the first occurrence of a particular Day. You can do this by checking if the current element is the first element in the key for that day
<xsl:if test="generate-id() = generate-id(key('days', Day)[1])">
Here is the XSLT for the second case
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="days" match="groupEntry|Details" use="Day"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>ID,Day,Number</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="groupEntry|Details">
<xsl:if test="generate-id() = generate-id(key('days', Day)[1])">
<xsl:text>
</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="sum(key('days', Day)/Number)"/>
</xsl:if>
<xsl:text>
</xsl:text>
<xsl:value-of select="ID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Day"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="Number"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
This should output the following results
ID,Day,Number
Mon,Mon,-3
111,Mon,-3
Tue,Tue,14
222,Tue,4
444,Tue,5
444,Tue,-3
444,Tue,8
Fri,Fri,-3
333,Fri,-3