XSLT 2.0 tokenising delimiters within delimiters - xslt

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

Related

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 to convert elements in a concatenated line

I have this situation:
Two variables:
<xsl:variable name="varDep" select="DepAir"/>
<xsl:variable name="varArr" select="ArrAir"/>
Value of variables:
<testa>
<DepAir>SDU</DepAir>
<DepAir>CGH</DepAir>
</testa>
<testb>
<ArrAir>CGH</ArrAir>
<ArrAir>SDU</ArrAir>
</testb>
And I need transform in a concatenated line, like this:
<db:P_IAT>SDU;CGH;CGH;SDU;</db:P_IAT>
How can I do that?
Kind dynamic way will be to put loop for DepAir and ArrAir inside above variables as below:
<xsl:variable name="varDep">
<xsl:for-each select="//DepAir">
<xsl:value-of select="concat(., ';')"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="varArr">
<xsl:for-each select="//ArrAir">
<xsl:value-of select="concat(., ';')"/>
</xsl:for-each>
</xsl:variable>
And then use concat inside required node:
<required_node><xsl:value-of select="concat($varDep, $varArr)"/></required_node>
Note! In next questions please put your XSL (what you have tried) to avoid "Down Votes". Hope it will help in your case.

How to pass multiple incoming tags in single output tag

We have a GivenName field which comes as multiple tag in input. for ex:
<PersonName>
<Surname>BNWHBQQ</Surname>
<GivenName>Adam</GivenName>
<GivenName>Sam</GivenName>
<GivenName>Peter</GivenName>
</PersonName>
we need to concatenate all GivenName present in input and pass it in one tag, for ex:
<db:PR_OFFENDER>
<db:SURNAME>BNWHBQQ</db:SURNAME>
<db:GIVEN_NAME>Adam Sam Peter</db:GIVEN_NAME>
</db:PR_OFFENDER>
I tried:
I tried using for loop but thats of no use as i am getting multiple in output as well, something like:
<xsl:if test="out:PartyEntity/out:Person/out:PersonName/out:GivenName">
<xsl:for-each select="out:PartyEntity/out:Person/out:PersonName/out:GivenName">
< db:GIVEN_NAME>
<xsl:value-of select="normalize-space(.)"/>
</db:GIVEN_NAME>
</xsl:for-each>
</xsl:if>
I can use something like below, But the output does not look nice, and I can have multiple given names in input, so this format wont work either.
<xsl:value-of select="concat(out:PartyEntity/out:Person/out:PersonName/out:GivenName[1],' ',out:PartyEntity/out:Person/out:PersonName/out:GivenName[2], ' ')"/>
Thanks in Advance,
Vivek
Such a template shoud work properly:
<xsl:if test="out:GivenName">
<db:GIVEN_NAME>
<xsl:for-each select="out:GivenName">
<xsl:value-of select="normalize-space(.)"/>
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</db:GIVEN_NAME>
</xsl:if>
You can see it working here: http://xsltransform.net/pNmBxZz/1

XSL for each loop selecting position of first occurrence

In the following code snippet, I'm trying to get position of EMP_ID field from the available fields. This works fine if there's just one occurrence of EMP_ID.
But if there are more than one occurences then variable 'empid_field' will have positions of all the occurrences appended one after the other. i.e if EMP_ID is at postions 1, 8, and 11, then 'empid_field' would be '1811'.
Is there any way I get position of first occurrence only? Or Can I get comma separated positions atleast? (Code sample would be highly appreciated as I'm new to XSL programming)
<xsl:variable name="empid_field">
<xsl:for-each select="$fields">
<xsl:if test="internalName='EMP_ID'">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
The easiest solution which is in my mind is to extend this. But I think there are also solutions which look more pretty.
<xsl:variable name="empid_field">
<xsl:for-each select="$fields">
<xsl:if test="internalName='EMP_ID'">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="first_empid_field">
<xsl:value-of select="$empid_field[1]"/>
</xsl:variable>
The variable $first_empid_field will only have the first position value.
Ok got something ...
Created a comma separated string and picked the part before the delimiter.
<xsl:variable name="empid_fields" >
<xsl:for-each select="$fields">
<xsl:if test="internalName='EMP_ID'">
<xsl:value-of select="position()" />
<xsl:text >, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="empid_field" >
<xsl:value-of select="substring-before($empid_fields, ', ')" />
</xsl:variable>

how to check repeated elements in as string sequence/array?

I am using xslt 1.0 stylesheet to worlk on xml file data.
I have a variable in xslt which conatins many string separated by white space or new line charater.
i.e. the variable is "ServiceList", when I print it using follwong,
<xsl:value-of select="$ServiceList"/>
It prints following out put
hgd.sdf.gsdf sdf.sdh.duyg dsf.sdf.suos
jhs.sdu.sdfi
hdf.sdi.seij dsf.dsf.diuh
edr.sdi.sdhg dfh.dfg.dfg.fdg.idjf kjs.dfh.dfgj djg.dfs.dgji
I used follwing code to get each string separately.
<xsl:variable name="tokenizedSample" select="str:tokenize($ServiceList,'
')"/>
<xsl:for-each select="$tokenizedSample">
<xsl:variable name="serviceProvide" select="."/>
<xsl:variable name="tokenized1" select="str:tokenize($serviceProvide,' ')"/>
<xsl:for-each select="$tokenized1">
<xsl:variable name="serviceP" select="."/>
<xsl:value-of select="$serviceP"/>
</xsl:for-each>
</xsl:for-each>
the above code give me each string as separate one.
I have to chek is there any repeating string in above sequence/array. If it repeates it should show me the string is repeating.
This would be so much easier in XSLT 2.0
<xsl:variable name="tokenizedSample" select="tokenize($ServiceList, '
')"/>
<xsl:if test="count($tokenizedSample) != count(distinct-values($tokenizedSample))">...