Show the separator once only the 2nd value exist - xslt

<handlingInstruction>
<handlingInstructionText>CTAC | MARTINE HOEYLAERTS</handlingInstructionText>
</handlingInstruction>
<handlingInstruction>
<handlingInstructionText>PHON | 02/7225235</handlingInstructionText>
</handlingInstruction>
I have The above given xml structure I concatenate them and use a comma as a separator using below code
> <xsl:value-of
> select="concat(handlingInstruction[1]/handlingInstructionText,
> ',',
> handlingInstruction[2]/handlingInstructionText)"/>
I would like to ask how will I make the comma separator appear only once the 2nd exist the shortest way possible. Thanks in advance

If you don't want to use xsl:for-each, try:
<xsl:template match="/root">
<xsl:apply-templates select="handlingInstruction/handlingInstructionText"/>
</xsl:template>
<xsl:template match="handlingInstructionText">
<xsl:value-of select="."/>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:template>
(continued from here: https://stackoverflow.com/a/34679465/3016153)

<xsl:for-each select="handlingInstruction">
<xsl:value-of select="handlingInstructionText"/>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
This will iterate over all handlingInstruction elements and output the value of the handlingInstructionText element. It will add to the end of each element, if it is not the last one (which the first one would be if there was only one), a comma.
In your example, you only used two handlingInstruction elements. If you want to only use two with this method, do
<xsl:for-each select="handlingInstruction[position()<3]">
<xsl:value-of select="handlingInstructionText"/>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
Note the < there. That is actually a less than sign (<), but we can't use that in xml so we use the entity defined for it.

Here is a second way to do it, which avoids the for-each loop.
If you are using xslt version 2, there is a string-join function which could be used like:
<xsl:value-of select="string-join(//handlingInstruction/handlingInstructionText,',')"/>
The string-join method takes a sequence of strings (which the nodes selected will be converted to by taking their content) and concatenates them with the separator. If there is only one string, a separator will not be added.
Alternatively, xslt 2 also provides a separator attribute on the value-of element. Thus
<xsl:value-of select="//handlingInstruction/handlingInstructionText" separator=","/>
produces the same result.

Related

XSLT / conditional for-each with same parent (uncle) node value

I need some help understanding how I can select only certain nodes having same parent (or uncle) element value. My XML looks like this
<shipment>
<goodslines>
<goodsline>
<position>1</position>
<packagenumbers>
<packagenumber>123</packagenumber>
</packagenumbers>
</goodsline>
<goodsline>
<position>1</position>
<packagenumbers>
<packagenumber>456</packagenumbers>
</packagenumbers>
</goodsline>
<goodsline>
<position>2</position>
<packagenumbers>
<packagenumber>789</packagenumbers>
</packagenumbers>
</goodsline>
</goodslines>
</shipment>
and the desired output would be:
123,456
789
So I would need to do for-each to "packagenumber" - level so, that it would take in consideration the "position" - element from upper level
The XSL might be something like this?
<xsl:for-each select="shipment/goodslines/goodsline[some condition here?]/packagenumbers/packagenumber">
<xsl:value-of select="current()"/>
<xsl:if test="not(position() = last())">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:for-each>
Any help would be appreciated.
If your xml always has the same structure you can use this:
<xsl:template match="goodsline[position = preceding-sibling::goodsline[1]/position]">
<xsl:text>,</xsl:text>
<xsl:value-of select="packagenumbers/packagenumber"/>
</xsl:template>
<xsl:template match="goodsline">
<xsl:text>
</xsl:text>
<xsl:value-of select="packagenumbers/packagenumber"/>
</xsl:template>
More specific templates (the one with the condition) will always hit first and have higher priority. There is probably another simple solution with for-each-group.

XSLT 2.0 how to test for position() in tokenize() on output

In XSLT 2.0 I have a parameter than comes in as a delimited string of document names like:
ms609_0080.xml~ms609_0176.xml~ms609_0210.xml~ms609_0418.xml
I tokenize() this string and cycle through it with xsl:for-each to pass each document to a key. The results from the key I then assemble into a comma-delimited string to output to screen.
<xsl:variable name="list_of_corresp_events">
<xsl:variable name ="tokenparam" select="tokenize($paramCorrespdocs,'~')"/>
<xsl:for-each select="$tokenparam">
<xsl:choose>
<xsl:when test=".[position() != last()]">
<xsl:value-of select="document(concat($paramSaxondatapath, .))/(key('correspkey',$correspid))/#xml:id"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(document(concat($paramSaxondatapath, .))/(key('correspkey',$correspid))/#xml:id, ', ')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
Everything works fine except that when I output the variable $list_of_corresp_events it looks like the following, with an unexpected trailing comma:
ms609-0080-2, ms609-0176-1, ms609-0210-1, ms609-0418-1,
Ordinarily the last comma should not appear based on test=".[position() != last()]" ? Possibly positions don't work for tokenized data? I didn't see a way to apply string-join() to this.
Many thanks.
Improving on the solution from #zx485, try
<xsl:for-each select="$tokenparam">
<xsl:if test="position()!=1">, </xsl:if>
<xsl:value-of select="document(concat($paramSaxondatapath, .))/(key('correspkey',$correspid))/#xml:id"/>
</xsl:for-each>
Two things here:
(a) you don't need to repeat the same code in both conditional branches
(b) it's more efficient to output the comma separator before every item except the first, rather than after every item except the last. That's because evaluating last() involves an expensive look-ahead.
Change
<xsl:when test=".[position() != last()]">
to
<xsl:when test="position() != last()">
Then it should all work as desired.
It seems you can simplify this to
<xsl:variable name="list_of_corresp_events">
<xsl:value-of select="for $t in tokenize($paramCorrespdocs,'~') document(concat($paramSaxondatapath, $))/(key('correspkey',$correspid))/#xml:id" separator=", "/>
</xsl:variable>
or with string-join
<xsl:variable name="list_of_corresp_events" select="string-join(for $t in tokenize($paramCorrespdocs,'~') document(concat($paramSaxondatapath, $))/(key('correspkey',$correspid))/#xml:id, ', ')"/>

XSLT 2.0 tokenising delimiters within delimiters

In XSLT 2.0 I have long string (parameter) with a delimiter (;) inside a delimiter (~), more specifically a triplet inside a delimiter.
Data is organized like so:
<parameter>qrsbfs;qsvsv;tfgz~dknk;fvtea;gtvath~pksdi;ytbdi;oiunhu</parameter>
The first tokenize($mystring,'~') in a for-each produces :
qrsbfs;qsvsv;tfgz
dknk;fvtea;gtvath
pksdi;ytbdi;oiunhu
Within that tokenization, I need to treat it by looping again:
qrsbfs
qsvsv
tfgz
dknk
fvtea
gtvath
pksdi
ytbdi
oiunhu
I can do intensive string manipulation to get there using concat, string-length, and substring-before/substring-after, but I wondered if there wasn't a more elegant solution that my neophyte mind wasn't overlooking?
EDIT, adding nested tokenize that returned incorrect results:
<xsl:for-each select="tokenize($myparameter,'~')">
<xsl:for-each select="tokenize(.,';')">
<xsl:if test="position()=1">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position()=2">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position()=3">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
If you wanted a one line solution, you could do something like this, using nested for-in-return statements:
<xsl:sequence select="for $n in tokenize(.,'~') return concat(string-join(tokenize($n,';'),'
'),'
')"/>
If you don't need to tokenize them separately, you could replace the ~ with ; and tokenize all 9 elements at the same time:
tokenize(replace(parameter,'~',';'),';')
For what it's worth, the code in https://xsltfiddle.liberty-development.net/pPqsHUe uses
<xsl:template match="parameter">
<xsl:for-each select="tokenize(., '~')">
<xsl:value-of select="tokenize(., ';')" separator="
"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
and with output method text produces
qrsbfs
qsvsv
tfgz
dknk
fvtea
gtvath
pksdi
ytbdi
oiunhu

XSLT:How to deal with testing the value of an element?

I have an xml file in which there is tag namely, <Gender/> It carries either 'M' or 'F' as data, now my work is to test the value and write <Gender_Tag>Male</Gender_Tag> or <Gender_Tag>Female</Gender_Tag> according to the values M or F respectively .. I tried this code .. It used to work in other circumstances..
All relative paths expressed in a template are evaluated against the current node. Your template match Gender elements, so Gender='M' returns true if there is any Gender's child named 'Gender' with the value 'M'. I guess this is not the case...
Use the dot to express the current node (here a Gender element):
<xsl:template match="root/details/Gender">
<Gender_Tag>
<xsl:choose>
<xsl:when test=".='M'">
<xsl:text>Male</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Female</xsl:text>
</xsl:otherwise>
</xsl:choose>
</Gender_Tag>
</xsl:template>
EDIT: You may use two templates too
<xsl:template match="root/details/Gender[.='M']">
<Gender_Tag>Male</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender[.='F']">
<Gender_Tag>Female</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender">
<xsl:choose>
<xsl:when test="normalize-space(text())='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:when>
<xsl:otherwise>
<Gender_Tag>Female</Gender_Tag>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
My example differs in two points from Scoregraphic's:
It uses xsl:choose to ensure, that only one Gender_Tag element is created (that also means, that if the text is not 'M', it is always a Female)
Use of normalize-space() strips white space around the text content of the element.
Untested, but may work...
<xsl:template match="root/details/Gender">
<xsl:if test="text()='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:if>
<xsl:if test="text()='F'">
<Gender_Tag>Female</Gender_Tag>
</xsl:if>
</xsl:template>
Without seeing XML its hard to be certain, but I think your sample XSLT should be:
<xsl:template match="root/details/Gender">
<xsl:if test=".='M'">
<Gender_Tag><xsl:text>Male</xsl:text></Gender_Tag>
</xsl:if>
<xsl:if test=".='F'">
<Gender_Tag><xsl:text>Female</xsl:text></Gender_Tag>
</xsl:if>
</xsl:template>
Use of choose as per another answer would be better (though I think it should be two explicit when clauses rather than a when and an otherwise)

XSL for-each: how to detect last node?

I have this simple code:
<xsl:for-each select="GroupsServed">
<xsl:value-of select="."/>,<br/>
</xsl:for-each></font>
I'm trying to add a comma for each item added.
This has 2 flaws:
Case of when there's only 1 item: the code would unconditionally add a comma.
Case of when there's more than 1 item: the last item would have a comma to it.
What do you think is the most elegant solution to solve this?
I'm using XSLT 2.0
If you're using XSLT 2.0, the canonical answer to your problem is
<xsl:value-of select="GroupsServed" separator=", " />
On XSLT 1.0, the somewhat CPU-expensive approach to finding the last element in a node-set is
<xsl:if test="position() = last()" />
Final answer:
<xsl:for-each select="GroupsServed">
<xsl:value-of select="."/>
<xsl:choose>
<xsl:when test="position() != last()">,<br/></xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:variable name="GROUPS_SERVED_COUNT" select="count(GroupsServed)"/>
<xsl:for-each select="GroupsServed">
<xsl:value-of select="."/>
<xsl:if test="position() < $GROUPS_SERVED_COUNT">
,<br/>
</xsl:if>
</xsl:for-each></font>
Insert the column delimiter before each new item, except the first one. Then insert the line break outside the for-each loop.
<xsl:for-each select="GroupsServed">
<xsl:if test="position() != 1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
<br/>
In other words, treat every item like the last item. The exception is that the first item does not need a comma separator in front of it. The loop ends after the last item is processed, which also tells us where to put the break.