xslt: apply normalize-space to a subtree - xslt

My XML input looks like:
<?xml version="1.0" ?>
<input>
<record>
<name>James Smith</name>
<country>United Kingdom</country>
<opt>
good social skills,
<qualification>MSc</qualification>,
10 years of experience
</opt>
<section>1B</section>
</record>
<record>
<name>Rafael Pérez</name>
<country>Spain</country>
<section>2A</section>
</record>
<record>
<name>Marie-Claire Legrand</name>
<country>France</country>
<opt>
clear voice,
<qualification>MBA</qualification>,
3 years of experience
</opt>
<section>1B</section>
</record>
</input>
I want to output the text nodes under the <opt> tag between parentheses, removing the starting and ending spaces and new lines around the contents of its children. This would be very easy if I had only a text child applying the function normalise-space() to it, but this function cannot be applied to a set of nodes.
A MWE of my code looks as follows:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" encoding="utf-8"/>
<xsl:template match="input">
<xsl:text>------------------------------------------
</xsl:text>
<xsl:for-each select="record">
<xsl:apply-templates
select="node()[not(self::text()[not(normalize-space())])]"/>
<xsl:text>
------------------------------------------
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="qualification">
<xsl:choose>
<xsl:when test=". = 'MBA'">Master in Business Administration</xsl:when>
<xsl:when test=". = 'MSc'">Master in Sciences</xsl:when>
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="name|country">
<xsl:value-of select="."/>
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template match="section">
<xsl:text>Section: </xsl:text>
<xsl:value-of select="."/>
<xsl:text>.</xsl:text>
</xsl:template>
<xsl:template match="opt">
<xsl:text>(</xsl:text>
<xsl:apply-templates/>
<xsl:text>), </xsl:text>
</xsl:template>
</xsl:stylesheet>
but gives me a wrong output, having spaces inside of the parentheses, as below:
------------------------------------------
James Smith, United Kingdom, (
good social skills,
Master in Sciences,
10 years of experience
), Section: 1B.
------------------------------------------
Rafael Pérez, Spain, Section: 2A.
------------------------------------------
Marie-Claire Legrand, France, (
clear voice,
Master in Business Administration,
3 years of experience
), Section: 1B.
------------------------------------------
The output want is:
------------------------------------------
James Smith, United Kingdom, (good social skills,
Master in Sciences,
10 years of experience), Section: 1B.
------------------------------------------
Rafael Pérez, Spain, Section: 2A.
------------------------------------------
Marie-Claire Legrand, France, (clear voice,
Master in Business Administration,
3 years of experience), Section: 1B.
------------------------------------------
I understand I have to modify the template "opt", but I cannot find how.

Try perhaps something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/input">
<xsl:text>------------------------------------------
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="record">
<xsl:apply-templates/>
<xsl:text>
------------------------------------------
</xsl:text>
</xsl:template>
<xsl:template match="qualification">
<xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test=". = 'MBA'">Master in Business Administration</xsl:when>
<xsl:when test=". = 'MSc'">Master in Sciences</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="name|country">
<xsl:value-of select="."/>
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template match="section">
<xsl:text>Section: </xsl:text>
<xsl:value-of select="."/>
<xsl:text>.</xsl:text>
</xsl:template>
<xsl:template match="opt">
<xsl:text>(</xsl:text>
<xsl:apply-templates/>
<xsl:text>), </xsl:text>
</xsl:template>
<xsl:template match="opt/text()">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
</xsl:stylesheet>
The result is different than the one you show, but you say it doesn't matter - and the whitespaces inside the parentheses are removed:
------------------------------------------
James Smith, United Kingdom, (good social skills, Master in Sciences, 10 years of experience), Section: 1B.
------------------------------------------
Rafael Pérez, Spain, Section: 2A.
------------------------------------------
Marie-Claire Legrand, France, (clear voice, MBx, 3 years of experience), Section: 1B.
------------------------------------------

I accepted the solution of michael.hor257k yesterday but I didn't realize that applying the function normalize-space() to each child node will also remove blanks between them, which was not exactly what I want.
So I found a way to apply the function normalize-space() to a whole subtree by defining a variable computing the whole subtree from all the nodes of the subtree. Once this string is defined, I can apply the function normalize-space() to the string itself, outputting the result I want:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" encoding="utf-8"/>
<xsl:template match="input">
<xsl:text>------------------------------------------
</xsl:text>
<xsl:for-each select="record">
<xsl:apply-templates
select="node()[not(self::text()[not(normalize-space())])]"/>
<xsl:text>
------------------------------------------
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="qualification">
<xsl:choose>
<xsl:when test=". = 'MBA'">Master in Business Administration</xsl:when>
<xsl:when test=". = 'MSc'">Master in Sciences</xsl:when>
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="name|country">
<xsl:value-of select="."/>
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template match="section">
<xsl:text>Section: </xsl:text>
<xsl:value-of select="."/>
<xsl:text>.</xsl:text>
</xsl:template>
<xsl:template match="opt">
<xsl:variable name="text-in-parenthesis">
<xsl:apply-templates/>
</xsl:variable>
<xsl:text>(</xsl:text>
<xsl:value-of select="normalize-space($text-in-parenthesis)"/>
<xsl:text>), </xsl:text>
</xsl:template>
</xsl:stylesheet>
With the input:
<?xml version="1.0" ?>
<input>
<record>
<name>James Smith</name>
<country>United Kingdom</country>
<opt>
good social skills,
<qualification>MSc</qualification>,
10 years of experience
</opt>
<section>1B</section>
</record>
<record>
<name>Rafael Pérez</name>
<country>Spain</country>
<section>2A</section>
</record>
<record>
<name>Marie-Claire Legrand</name>
<country>France</country>
<opt>
clear voice,
<qualification>MBA</qualification>,
3 years of experience
</opt>
<section>1B</section>
</record>
</input>
I get:
------------------------------------------
James Smith, United Kingdom, (good social skills, Master in Sciences, 10 years of experience), Section: 1B.
------------------------------------------
Rafael Pérez, Spain, Section: 2A.
------------------------------------------
Marie-Claire Legrand, France, (clear voice, Master in Business Administration, 3 years of experience), Section: 1B.
------------------------------------------
This gives me the output I was looking for. There are no more indentations, but the elements of the subtree are separated.

Related

XML element name has empty space, how to address it in XSLT?

I started learning Linux couple of days ago and currently I'm stuck at XSLT. Sorry if it's a stupid question or already answered elsewhere, I'm quite new here.
My XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<lac xmlns:t="http://smth.de">
<header>
<order id="20210323346730329408"/>
<adress id="IZ0009"/>
</header>
<items>
<item id="1"><material><code>IS-0001-BT-1</code><lotcode/></material><qty>10,000000</qty><expiry/></item>
<item id="2"><material><code>IS-0001-BT-2</code><lotcode/></material><qty>20,000000</qty><expiry/></item>
<item id="3"><material><code>IS-0001-AZ-1</code><lotcode/></material><qty>30,000000</qty><expiry/></item>
<item id="4"><material><code>IS-0001-AZ-2</code><lotcode/></material><qty>40,000000</qty><expiry/></item>
</items>
</lac>
I want to get this output:
Order ID,Adress ID,Item ID,MaterialCode,MaterialLotCode,MaterialQty,Expiry
20210323346730329408,IZ0009,1,IS-0001-BT-1,,10,000000,
20210323346730329408,IZ0009,2,IS-0001-BT-2,,20,000000,
20210323346730329408,IZ0009,3,IS-0001-AZ-1,,30,000000,
20210323346730329408,IZ0009,4,IS-0001-AZ-2,,40,000000,
This is what I got sofar, Your help is very welcome:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:text>Order ID,Adress ID,Item ID,MaterialCode,MaterialLotCode,MaterialQty,Expiry
</xsl:text>
<xsl:apply-templates mode="runHeader"/>
<xsl:apply-templates mode="runItems"/>
</xsl:template>
<xsl:template match="header" mode="runHeader">
<xsl:apply-templates mode="runOrder"/>
<xsl:apply-templates mode="runAdress"/>
</xsl:template>
<xsl:template match="order" mode="runOrder">
<xsl:value-of select="./#id"/>
<xsl:text>,</xsl:text>
</xsl:template>
<xsl:template match="adress" mode="runAdress">
<xsl:value-of select="./#id"/>
<xsl:text>,</xsl:text>
</xsl:template>
<xsl:template match="items" mode="runItems">
<xsl:for-each select="//item">
<xsl:value-of select="./#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="./material"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="./lotcode"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="./qty"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="./expiry"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Many thanks in advance.
I would suggest a different approach:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/lac">
<!-- header row -->
<xsl:text>Order ID,Adress ID,Item ID,MaterialCode,MaterialLotCode,MaterialQty,Expiry
</xsl:text>
<!-- common data -->
<xsl:variable name="common">
<xsl:value-of select="header/order/#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="header/adress/#id"/>
<xsl:text>,</xsl:text>
</xsl:variable>
<!-- data rows -->
<xsl:for-each select="items/item">
<xsl:copy-of select="$common"/>
<xsl:value-of select="#id"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="material/code"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="material/lotcode"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="qty"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="expiry"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note that this assumes that each item has at most one material (your XML structure allows for more).
--
P.S. That's not how you spell address.

XSLT Strip All Tabs In Text Output

Silly, simple question. When I output text, it still get the tabs based on my formatted/indented XSL structure. How do I instruct the transformer to ignore the spacing in the stylesheet while still keeping it neatly formatted?
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="Foo/Bar"></xsl:apply-templates>
</xsl:template>
<xsl:template match="Bar">
<xsl:for-each select="AAA"><xsl:for-each select="BBB"><xsl:value-of select="Label"/>|<xsl:value-of select="Value"/><xsl:text>
</xsl:text></xsl:for-each></xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Produces output line by line with no tabs:
SomeLabel|SomeValue
SomeLabel|SomeValue
SomeLabel|SomeValue
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="Foo/Bar"></xsl:apply-templates>
</xsl:template>
<xsl:template match="Bar">
<xsl:for-each select="AAA">
<xsl:for-each select="BBB">
<xsl:value-of select="Label"/>|<xsl:value-of select="Value"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Produces output with tabs:
SomeLabel|SomeValue
SomeLabel|SomeValue
SomeLabel|SomeValue
Update:
Adding this does not fix it:
<xsl:output method="text" indent="no"/>
<xsl:strip-space elements="*"></xsl:strip-space>
This is contrived, but you can imagine the XML looks like this:
<Foo>
<Bar>
<AAA>
<BBB>
<Label>SomeLabel1</Label>
<Value>SomeValue1</Value>
</BBB>
<BBB>
<Label>SomeLabel2</Label>
<Value>SomeValue2</Value>
</BBB>
<BBB>
<Label>SomeLabel3</Label>
<Value>SomeValue3</Value>
</BBB>
</AAA>
</Bar>
</Foo>
What you could try is wrapping all your current text nodes in xsl:text. For example, try this
<xsl:for-each select="BBB">
<xsl:value-of select="Label"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="Value"/>
<xsl:text>|</xsl:text>
</xsl:for-each>
Alternatively, you could make use of the concat function.
<xsl:for-each select="BBB">
<xsl:value-of select="concat(Label, '|')"/>
<xsl:value-of select="concat(Value, '|')"/>
</xsl:for-each>
You could even combine the two statements into one if you wanted
<xsl:for-each select="BBB">
<xsl:value-of select="concat(Label, '|', Value, '|')"/>
</xsl:for-each>
EDIT: If you prefer not to enter the separator | so many times, you make use of template matching to output the fileds. First, replace the value-of with apply-templates like so
<xsl:for-each select="BBB">
<xsl:apply-templates select="Label"/>
<xsl:apply-templates select="Value"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
Then you would have one specific template to match Label, where you wouldn't need to output the separator, and another more generic template matching any child of BBB
<xsl:template match="BBB/Label" priority="1">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="BBB/*">
<xsl:text>|</xsl:text><xsl:value-of select="." />
</xsl:template>
(The priority here is needed to ensure Label is matched by the first template, and not the general one). Of course, you could also not do apply-templates on Label in this case, and just do xsl:value-of for that one.
Furthermore, if the fields were being output in the order they appear in the XML, you could simplify the for-each to just this
<xsl:for-each select="BBB">
<xsl:apply-templates />
<xsl:text>
</xsl:text>
</xsl:for-each>

how to set newline and tab in paragraph using xslt

I want to set newline and tab in paragraph using XSLT values from xml retrieve using XML Parsing.Following XSLT code separates each word in paragraph.But,I want to separate newline in paragraph wherever necessary and also I want to set tab before starting a paragraph...
sample.xml
<item>
<id>0</id>
<desc>Review your resume, and make sure that you can explain everything on it. Arrive at the interview ten minutes early to give yourself an opportunity to collect your thoughts and relax. Be aware that many employers will have their receptionist’s record the time you came in. If you rush in at the last minute, an employer may have serious concerns about your ability to arrive on time for a normal day at work.</desc>
</item>
xslt:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="t">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="text()" name="insertBreaks">
<xsl:param name="pText" select="."/>
<xsl:choose>
<xsl:when test="not(contains($pText, '
'))">
<xsl:copy-of select="$pText"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-before($pText, '
')"/>
<br />
<xsl:call-template name="insertBreaks">
<xsl:with-param name="pText" select="substring-after($pText, '
')"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
To set a newline you can use:
<xsl:text>
</xsl:text>

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

How to get Coldfusion XSLT 'contains' function to work?

I am trying to convert xml dumps similar to this one
<?xml version="1.0" encoding="UTF-8"?>
<report>
<report_header>
<c1>desc</c1>
<c2>prname</c2>
<c3>prnum</c3>
<c4>cdate</c4>
<c5>phase</c5>
<c6>stype</c6>
<c7>status</c7>
<c8>parent</c8>
<c9>location</c9>
</report_header>
<report_row>
<c1></c1>
<c2>IT Project Message Validation</c2>
<c3>IT-0000021</c3>
<c4>12/14/2010 09:56 AM</c4>
<c5>Preparation</c5>
<c6>IT Projects</c6>
<c7>Active</c7>
<c8>IT</c8>
<c9>/IT/BIOMED</c9>
</report_row>
<report_row>
<c1></c1>
<c2>David, Michael John Morning QA Test</c2>
<c3>IT-0000020</c3>
<c4>12/14/2010 08:12 AM</c4>
<c5>Preparation</c5>
<c6>IT Projects</c6>
<c7>Active</c7>
<c8>IT</c8>
<c9>/IT/BIOMED</c9>
</report_row>
</report>
with the xslt below, to csv. Unfortunately the contains function does not work.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="report">
<xsl:apply-templates select="report_header"/>
<xsl:apply-templates select="report_row"/>
</xsl:template>
<xsl:template match="report_header">
<xsl:for-each select="*">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="report_row">
<xsl:param name="value" />
<xsl:for-each select="*">
<xsl:value-of select="$value" />
<xsl:if test="(contains($value,','))">
<xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
</xsl:if>
<xsl:if test="not(contains($value,','))">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position() != last()">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:for-each>
<xsl:if test="position() != last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I get the following dump. I expected the qualifiers around the prname column on the second row.
desc,prname,prnum,cdate,phase,stype,status,parent,location
,IT Project Message Validation,IT-0000021,12/14/2010 09:56 AM,Preparation,IT Projects,Active,IT,/IT/BIOMED
,David, Michael John Morning QA Test,IT-0000020,12/14/2010 08:12 AM,Preparation,IT Projects,Active,IT,/IT/BIOMED
I have only used the coldfusion xmltransform function to test it.
The provided code has issues, some of them reported in the answer of Mads Hansen.
The main problem is that the code is unnecessarily complicated.
Below is a simple solution that produces what seems to be wanted:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="report_header/*">
<xsl:value-of select="."/>
<xsl:call-template name="processEnd"/>
</xsl:template>
<xsl:template match="report_row/*[contains(., ',')]">
<xsl:text>"</xsl:text>
<xsl:value-of select="."/>
<xsl:text>"</xsl:text>
<xsl:call-template name="processEnd"/>
</xsl:template>
<xsl:template match="report_row/*[not(contains(., ','))]">
<xsl:value-of select="."/>
<xsl:call-template name="processEnd"/>
</xsl:template>
<xsl:template name="processEnd">
<xsl:choose>
<xsl:when test="position() != last()">,</xsl:when>
<xsl:otherwise><xsl:text> </xsl:text></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<report>
<report_header>
<c1>desc</c1>
<c2>prname</c2>
<c3>prnum</c3>
<c4>cdate</c4>
<c5>phase</c5>
<c6>stype</c6>
<c7>status</c7>
<c8>parent</c8>
<c9>location</c9>
</report_header>
<report_row>
<c1></c1>
<c2>IT Project Message Validation</c2>
<c3>IT-0000021</c3>
<c4>12/14/2010 09:56 AM</c4>
<c5>Preparation</c5>
<c6>IT Projects</c6>
<c7>Active</c7>
<c8>IT</c8>
<c9>/IT/BIOMED</c9>
</report_row>
<report_row>
<c1></c1>
<c2>David, Michael John Morning QA Test</c2>
<c3>IT-0000020</c3>
<c4>12/14/2010 08:12 AM</c4>
<c5>Preparation</c5>
<c6>IT Projects</c6>
<c7>Active</c7>
<c8>IT</c8>
<c9>/IT/BIOMED</c9>
</report_row>
</report>
the wanted, correct result is produced:
desc,prname,prnum,cdate,phase,stype,status,parent,location ,IT Project Message Validation,IT-0000021,12/14/2010 09:56 AM,Preparation,IT Projects,Active,IT,/IT/BIOMED ,"David, Michael John Morning QA Test",IT-0000020,12/14/2010 08:12 AM,Preparation,IT Projects,Active,IT,/IT/BIOMED
I don't think that contains() is your issue.
The issue is that your report_row template has an <xsl:param name="value"/> that is never assigned a value. You have logic that is driven from that param, which never fires. Because $value is empty, it will never contain() , or any other character.
You could get the desired behavior by adding a select attribute to the xsl:param:
<xsl:template match="report_row">
<xsl:param name="value" select="." />
You could simplify your stylesheet and logic by making more of a "push" style, which can be easier to debug and maintain than "pull" style stylesheets that attempt to implement procedural logic in XSLT.
Something like the following stylesheet achieve the same thing:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="*/report_header/*"/>
<xsl:apply-templates select="*/report_row/*"/>
</xsl:template>
<!-- For all but the last item, apply templates for the content, then add a comma -->
<xsl:template match="*[following-sibling::*]">
<xsl:apply-templates/>
<xsl:text>,</xsl:text>
</xsl:template>
<!-- If it's the last element in a group, add a newline char -->
<xsl:template match="*[not(following-sibling::*)]">
<xsl:apply-templates />
<!--Line break-->
<xsl:text>
</xsl:text>
</xsl:template>
<!-- If any values contains a comma, wrap it in quotes -->
<xsl:template match="text()[contains(.,',')]">
<xsl:text>"</xsl:text>
<xsl:value-of select="."/>
<xsl:text>"</xsl:text>
</xsl:template>
</xsl:stylesheet>
Produces the following output:
desc,prname,prnum,cdate,phase,stype,status,parent,location
,IT Project Message Validation,IT-0000021,12/14/2010 09:56 AM,Preparation,IT Projects,Active,IT,/IT/BIOMED
,"David, Michael John Morning QA Test",IT-0000020,12/14/2010 08:12 AM,Preparation,IT Projects,Active,IT,/IT/BIOMED