Related
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.
I have a problem getting data from a node, when I'm using xml:choose and xml:when. I only get the result NaN or the value from the main xml-file, even if.
Part of the XML-file:
<?xml version="1.0" encoding="UTF-8"?>
<Job xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<Invoice>
<InvoiceLine>
<LineNo>1</LineNo>
<QtyInSecondUnit>56</QtyInSecondUnit>
<Quantity>56</Quantity>
<CustTaric>
<StatNo>34011100</StatNo>
<IssuingCountry>GB</IssuingCountry>
</CustTaric>
</InvoiceLine>
<InvoiceLine>
<LineNo>2</LineNo>
<QtyInSecondUnit>22</QtyInSecondUnit>
<Quantity>0</Quantity>
<CustTaric>
<StatNo>44152020</StatNo>
<IssuingCountry>GB</IssuingCountry>
</CustTaric>
</InvoiceLine>
</Invoice>
</Job>
Part of the XSLT-file:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="Job/Invoice"/>
</xsl:template>
<xsl:template match="Job/Invoice/InvoiceLine">
<xsl:apply-templates select="QtyInSecondUnit"/>
<xsl:apply-templates select="Quantity"/>
<xsl:apply-templates select="CustTaric/StatNo"/>
</xsl:template>
<xsl:template match="QtyInSecondUnit">
<xsl:choose>
<xsl:when test="/Job/Invoice/InvoiceLine/CustTaric/StatNo = '44152020'">
<xsl:value-of select="number(translate(Job/Invoice/InvoiceLine/NetMass,',','.')) div 25"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Quantity">
<xsl:choose>
<xsl:when test="/Job/Invoice/InvoiceLine/CustTaric/StatNo = '44152020'">
<xsl:value-of select="'0'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="CustTaric/StatNo">
<xsl:value-of select="."/>
</xsl:template>
Hope there is somebody how can tell me (the noob) what I'm doing wrong here?
Here's the line producing NaN
<xsl:value-of select="number(translate(Job/Invoice/InvoiceLine/NetMass,',','.')) div 25"/>
There are two problems (one of which is probably where you have over-simplified your XML)
The xpath expression you are using will be relative to the current node you are positioned on. There is no Job element under the current QtyInSecondUnit element
There is no NetMass element in your XML in your question
Assuming NetMass does exist in your actual XML, and is a child of the parent InvoiceLine the expression you want is this
<xsl:value-of select="number(translate(../NetMass,',','.')) div 25"/>
There is also an issue with your xsl:when (possibly)
<xsl:when test="/Job/Invoice/InvoiceLine/CustTaric/StatNo = '44152020'">
This will test for any CustTaric/StatNo anywhere in the document. Perhaps you only want to test for the one in the current InvoiceLine? If so, do this...
<xsl:when test="../CustTaric/StatNo = '44152020'">
Note, you could rewrite your XSLT to put the logic in template matches, rather than xsl:choose
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Job/Invoice/InvoiceLine">
<xsl:apply-templates select="QtyInSecondUnit"/>
<xsl:apply-templates select="Quantity"/>
<xsl:apply-templates select="CustTaric/StatNo"/>
</xsl:template>
<xsl:template match="InvoiceLine[CustTaric/StatNo = '44152020']/QtyInSecondUnit">
<xsl:value-of select="number(translate(../NetMass,',','.')) div 25"/>
</xsl:template>
<xsl:template match="QtyInSecondUnit">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="InvoiceLine[CustTaric/StatNo = '44152020']/Quantity">
<xsl:value-of select="'0'"/>
</xsl:template>
<xsl:template match="Quantity">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="CustTaric/StatNo">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Strictly speaking, the templates that just do <xsl:value-of select="."/> can be removed, as XSLT's built-in templates will do exactly the same thing if there is no matching template in the XSLT.
The two sample xmls are given below:
xml1:
<Root>
<Child1/>
<Child2/>
<Child3/>
</Root>
xml2:
<Root>
<Child0>xml2value</Child0>
<Child2/>
<Child3>xml2value</Child3>
<Child4>xml2value</Child4>
</Root>
I have got these two xmls in two variables. Now I want to filter from xml2 those elements which do not exist in xml1, i.e., the resulting variable should look like below:
<Child0>xml2value</Child0>
<Child4>xml2value</Child4>
How can it be done with xslt?
XSLT 2.0:
<xsl:key name="el-by-name" match="Root/*" use="node-name(.)"/>
<xsl:variable name="xml1" select="document('file1.xml')"/>
<xsl:variable name="xml2" select="document('file2.xml')"/>
<xsl:copy-of select="$xml2/Root/*[not(key('el-by-name', node-name(.), $xml1))]"/>
With XSLT 1.0:
<xsl:key name="el-by-name" match="Root/*" use="name()"/>
<xsl:variable name="xml1" select="document('file1.xml')"/>
<xsl:variable name="xml2" select="document('file2.xml')"/>
<xsl:for-each select="$xml2/Root/*">
<xsl:variable name="child" select="."/>
<xsl:for-each select="$xml1">
<xsl:if test="not(key('el-by-name', name($child)))">
<xsl:copy-of select="$child"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
I have solved the problem with the below code:
<xsl:variable name="output">
<xsl:for-each select="$xml2/Root/*">
<xsl:variable name="cur" select="local-name(.)"/>
<xsl:if test="not($xml1/Root/*[local-name(.)=$cur])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
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
I need to build up a string using XSLT and separate each string with a comma but not include a comma after the last string. In my example below I will have a trailing comma if I have Distribution node and not a Note node for instance. I don't know of anyway to build up a string as a variable and then truncate the last character in XSLT. Also this is using the Microsoft XSLT engine.
My String =
<xsl:if test="Locality != ''">
<xsl:value-of select="Locality"/>,
</xsl:if>
<xsl:if test="CollectorAndNumber != ''">
<xsl:value-of select="CollectorAndNumber"/>,
</xsl:if>
<xsl:if test="Institution != ''">
<xsl:value-of select="Institution"/>,
</xsl:if>
<xsl:if test="Distribution != ''">
<xsl:value-of select="Distribution"/>,
</xsl:if>
<xsl:if test="Note != ''">
<xsl:value-of select="Note"/>
</xsl:if>
[Man there's gotta be a better way to enter into this question text box :( ]
This is very easy to accomplish with XSLT (No need to capture the results in a variable, or to use special named templates):
I. XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*/*">
<xsl:for-each select=
"Locality/text() | CollectorAndNumber/text()
| Institution/text() | Distribution/text()
| Note/text()
"
>
<xsl:value-of select="."/>
<xsl:if test="not(position() = last())">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<root>
<record>
<Locality>Locality</Locality>
<CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>
<Institution>Institution</Institution>
<Distribution>Distribution</Distribution>
<Note></Note>
<OtherStuff>Unimportant</OtherStuff>
</record>
</root>
the wanted result is produced:
Locality,CollectorAndNumber,Institution,Distribution
If the wanted elements should be produced not in document order (something not required in the question, but raised by Tomalak), it is still quite easy and elegant to achieve this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="porderedNames"
select="' CollectorAndNumber Locality Distribution Institution Note '"/>
<xsl:template match="/*/*">
<xsl:for-each select=
"*[contains($porderedNames, concat(' ',name(), ' '))]">
<xsl:sort data-type="number"
select="string-length(
substring-before($porderedNames,
concat(' ',name(), ' ')
)
)"/>
<xsl:value-of select="."/>
<xsl:if test="not(position() = last())">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here the names of the wanted elements and their wanted order are provided in the string parameter $porderedNames, which contains a space-separated list of all wanted names.
When the above transformation is applied on the same XML document, the wanted result is produced:
CollectorAndNumber,Locality,Distribution,Institution
II. XSLT 2.0:
In XSLT this task is even simpler (again, no special function is necessary):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*/*">
<xsl:value-of separator="," select=
"(Locality, CollectorAndNumber,
Institution, Distribution,
Note)[text()]" />
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document, the same correct result is produced:
Locality,CollectorAndNumber,Institution,Distribution
Do note that the wanted elements will be produced in any desired order, because we are using the XPath 2.0 sequence type (vs the union in the XSLT 1.0 solution), which by definition contains items in any desired (specified) order.
I would prefer a short call-template to join the node values together. This also works if a node in the middle of your concatenated list, e.g. Institution, is missing:
<xsl:template name="join">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="$separator" />
</xsl:if>
</xsl:for-each>
</xsl:template>
Here is a short example how to use it:
Sample input document:
<?xml version="1.0" encoding="utf-8"?>
<items>
<item>
<Locality>locality1</Locality>
<CollectorAndNumber>collectorAndNumber1</CollectorAndNumber>
<Distribution>distribution1</Distribution>
<Note>note1</Note>
</item>
<item>
<Locality>locality2</Locality>
<CollectorAndNumber>collectorAndNumber2</CollectorAndNumber>
<Institution>institution2</Institution>
<Distribution>distribution2</Distribution>
<Note>note2</Note>
</item>
<item>
<Locality>locality3</Locality>
<CollectorAndNumber>collectorAndNumber3</CollectorAndNumber>
<Institution>institution3</Institution>
<Distribution>distribution3</Distribution>
</item>
</items>
XSL transformation:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<summary>
<xsl:apply-templates />
</summary>
</xsl:template>
<xsl:template match="item">
<item>
<xsl:call-template name="join">
<xsl:with-param name="list" select="Locality | CollectorAndNumber | Institution | Distribution | Note" />
<xsl:with-param name="separator" select="','" />
</xsl:call-template>
</item>
</xsl:template>
<xsl:template name="join">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="$separator" />
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Generated output document:
<?xml version="1.0" encoding="utf-8"?>
<summary>
<item>locality1,collectorAndNumber1,distribution1,note1</item>
<item>locality2,collectorAndNumber2,institution2,distribution2,note2</item>
<item>locality3,collectorAndNumber3,institution3,distribution3</item>
</summary>
NB: If you were using XSLT/XPath 2.0 then there would be fn:string-join
fn:string-join**($operand1 as string*, $operand2 as string*) as string
which could be used as follows:
fn:string-join({Locality, CollectorAndNumber, Distribution, Note}, ",")
Supposing you have something like the following input XML:
<root>
<record>
<Locality>Locality</Locality>
<CollectorAndNumber>CollectorAndNumber</CollectorAndNumber>
<Institution>Institution</Institution>
<Distribution>Distribution</Distribution>
<Note>Note</Note>
<OtherStuff>Unimportant</OtherStuff>
</record>
</root>
Then this template would do it:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:template match="record">
<xsl:variable name="values">
<xsl:apply-templates mode="concat" select="Locality" />
<xsl:apply-templates mode="concat" select="CollectorAndNumber" />
<xsl:apply-templates mode="concat" select="Institution" />
<xsl:apply-templates mode="concat" select="Distribution" />
<xsl:apply-templates mode="concat" select="Note" />
</xsl:variable>
<xsl:value-of select="substring($values, 1, string-length($values) - 1)" />
<xsl:value-of select="'
'" /><!-- LF -->
</xsl:template>
<xsl:template match="Locality | CollectorAndNumber | Institution | Distribution | Note" mode="concat">
<xsl:value-of select="." />
<xsl:text>,</xsl:text>
</xsl:template>
</xsl:stylesheet>
Output on my system:
Locality,CollectorAndNumber,Institution,Distribution,Note
I think it might be useful to mention,
position() doesn't work right when I use a complicated select
that filters some nodes,
in that case I came up which this trick:
you can define a string variable that hold value of nodes, separated
by a specific character, then by using str:tokenize()
you can create a complete node list which position works fine with it.
something like this:
<!-- Since position() doesn't work as expected(returning node position of current
node list), I got round it by a string variable and tokenizing it in which
absolute position is equal to relative(context) position. -->
<xsl:variable name="measObjLdns" >
<xsl:for-each select="h:measValue[#measObjLdn=$currentMeasObjLdn]/h:measResults" >
<xsl:value-of select="concat(.,'---')"/> <!-- is an optional separator. -->
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="str:tokenize($measObjLdns,'---')" ><!-- Since position() doesn't
work as expected(returning node position of current node list),
I got round it by a string variable and tokenizing it in which
absolute position is equal to relative(context) position. -->
<xsl:value-of select="."></xsl:value-of>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
Do you not have a value that is always going to be there? If you do then you can turn it around and put commas infront of everything apart from the first item (which would be your value that's always there).
This would be a bit messy but might do the trick if there's only a few elements like in your example:
<xsl:if test="Locality != ''">
<xsl:value-of select="Locality"/>
<xsl:if test="CollectorAndNumber != '' or Institution != '' or Distribution != '' or Note != ''">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:if>
<xsl:if test="CollectorAndNumber != ''">
<xsl:value-of select="CollectorAndNumber"/>
<xsl:if test="Institution != '' or Distribution != '' or Note != ''">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:if>
<xsl:if test="Institution != ''">
<xsl:value-of select="Institution"/>
<xsl:if test="Distribution != '' or Note != ''">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:if>
<xsl:if test="Distribution != ''">
<xsl:value-of select="Distribution"/>
<xsl:if test="Note != ''">
<xsl:value-of select="','"/>
</xsl:if>
</xsl:if>
<xsl:if test="Note != ''">
<xsl:value-of select="Note"/>
</xsl:if>