XSLT 1.0 transformation removing duplicates using variables - xslt-1.0

for several reasons, I need to derive a variable in XSLT 1.0 which can be re-used during the transformation, which collects a unique list of duplicate entries.
The input data is generated in the XSLT into a variable "portlist":
<plist>
<p>12345</p>
<p>12345</p>
<p>9876</p>
<p>12345</p>
<plist>
within my XSLT-template, I need a variable "reducedList" to be re-used several times in transformation. How can I generate a new variable "reducedList" in XSLT which looks like
<plist>
<p>12345</p>
<p>9876</p>
<plist>
I found several examples, but must confess i could not figure out.
My xslt-template looks like
<xsl:template match="stage">
<xsl:variable name="portlist" > <!-- returns a sorted list of all ports -->
<plist>
<xsl:for-each select="provider/server/QMGR"><!-- input from XML -->
<xsl:sort select="."/>
<p><xsl:value-of select="./#port"/></p>
</xsl:for-each>
</plist>
</xsl:variable>
<!-- here i need to derive the new variable reducedList -->
<!-- more code using reducedList follows here -->
</xsl:template>

<xsl:variable name="portlist">
<plist>
<p>12345</p>
<p>12345</p>
<p>9876</p>
<p>12345</p>
</plist>
</xsl:variable>
<xsl:variable name="reducedList">
<plist>
<xsl:copy-of select="ext:node-set($portlist)/plist/p[not(text() = preceding-sibling::p/text())]"/>
</plist>
</xsl:variable>
where ext is your extension namespace with node-set(), e.g. xmlns:ext="urn:schemas-microsoft-com:xslt".

Related

How to preserve attributes in xsl:variable body? [duplicate]

This question already has an answer here:
Setting a variable conditionally with no cut of ancestor axis
(1 answer)
Closed 5 months ago.
data
<mydoc>
<setting>foo</setting>
<foo sound="aaaa"/>
<bar sound="brrr"/>
<cat sound="meow"/>
</mydoc>
if no conditional logic is needed,
<xsl:variable name="result1" select="/mydoc/foo"/>
works fine.
but if I need conditional logic,
<xsl:variable name="result2">
<xsl:choose>
<xsl:when test="/setting='foo'">
<!-- what to put here -->
</xsl:when>
<xsl:when test="/setting='cat'">
<!-- what to put here -->
</xsl:when>
<xsl:otherwise>
<!-- what to put here -->
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
desired output:
when setting = 'foo', result = <foo sound="aaa"/> (the whole element complete with attribute)
when setting = 'cat', result = <cat sound="meow"/> (the whole element complete with attribute)
otherwise, result = <bar sound="brrr"/> (the whole element complete with attribute)
How do I get the result2 variable to be an element with attributes, as it does in result1? I want to use $result2/sound, and some other elements / attributes of $result2 and / or it's children.
<xsl:variable name="result2" select="/mydoc[setting='foo']/foo |
/mydoc[setting='cat']/cat |
/mydoc[not(setting='foo' or setting='cat')]/bar"/>
Because of the conditions in square brackets exactly one member of the union will effectively be selected.
If you put something inside the <xsl:variable> element rather than use select, your variable will contain a result tree fragment, not a nodeset.
If you insist on using xsl:choose (why?) then the only option open to you is to use xsl:copy. However, this has some side effects:
This will create a copy of the selected node/s and make it a child of the variable; in effect, it will create a separate document and the copied nodes will no longer be descendants of the XML input's root;
The variable will be a result-tree-fragment and you won't be able to process it in any way unless you convert it to a node-set first. Here, "process" includes accessing individual nodes inside the variable.
The following example demonstrates this:
XML
<mydoc>
<setting>foo</setting>
<foo bar="a"/>
<bar foo="b"/>
</mydoc>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/mydoc">
<xsl:variable name="temp">
<xsl:choose>
<xsl:when test="setting='foo'">
<xsl:copy-of select="foo"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="bar"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<output>
<xsl:value-of select="exsl:node-set($temp)/foo/#bar" />
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>a</output>
Do note that if you change:
<xsl:value-of select="exsl:node-set($temp)/foo/#bar" />
to:
<xsl:value-of select="$temp/foo/#bar" />
you will get an error.

How do I use XSLT to merge XML elements and then structure elements/attributes of the same name/type?

Main issue
I have an XML file which has multiple similar elements with slightly different information. So, lets say I have two elements with the same X attirbute, but different Y attributes. Here is an example:
<FILE>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<ATTRIBUTEY>A</ATTRIBUTEY>
</ELEMENT>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<ATTRIBUTEY>B</ATTRIBUTEY>
</ELEMENT>
</FILE>
What I want to do is--based on the fact that both elements have the same X attribute--merge them into a single element with one attribute X and multiple child elements (of the same type)--each of which contain each of the different attributes Y. So, for example, I want my file to end up like this:
<FILE>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<NEWELEMENT>
<ATTRIBUTEY>A</ATTRIBUTEY>
</NEWELEMENT>
<NEWELEMENT>
<ATTRIBUTEY>B</ATTRIBUTEY>
</NEWELEMENT>
</ELEMENT>
</FILE>
Possible solution?
One possible solution I can think of, but lack the knowledge to execute, is made up of two steps:
Firstly, I could merge all elements which contain the X attribute so that the information is, at the least, in one place. I don't know quite how to do this.
Secdonly, once this is done, this opens me up--in theory--to use my knowledge of XSLT to re-structure the element.
Problem with the possible solution
However, once all elements that containin the X attribute are merged together (first step), I will have a file in which there will be attributes with the same name (specifically, the Y attribute). Here is an example:
<FILE>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<ATTRIBUTEY>A</ATTRIBUTEY>
<ATTRIBUTEY>B</ATTRIBUTEY>
</ELEMENT>
</FILE>
What this means is that--at least with my knowledge--when I execute an XSLT file on the above XML (second step), it cannot distinguish between the two Y elements when sorting them into the two new child elements (NEWELEMENT).
So, let's say I execute the following XSLT on the above XML:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="FILE">
<Record>
<xsl:for-each select = "ELEMENT">
<ELEMENT>
<xsl:copy-of select="ATTRIBUTEX"/>
<NEWELEMENT>
<xsl:copy-of select="ATTRIBUTEY"/>
</NEWELEMENT>
<NEWELEMENT>
<xsl:copy-of select="ATTRIBUTEY"/>
</NEWELEMENT>
</ELEMENT>
</xsl:for-each>
</Record>
</xsl:template>
</xsl:stylesheet>
The output is this:
<Record>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<NEWELEMENT>
<ATTRIBUTEY>A</ATTRIBUTEY>
<ATTRIBUTEY>B</ATTRIBUTEY>
</NEWELEMENT>
<NEWELEMENT>
<ATTRIBUTEY>A</ATTRIBUTEY>
<ATTRIBUTEY>B</ATTRIBUTEY>
</NEWELEMENT>
</ELEMENT>
</Record>
As you can see, the XSLT has taken all and any ATTRIBUTEY elements and put them into each NEWELEMENT rather than distinguishing between them and placing one ATTRIBUTEY in the first NEWELEMENT, and the second ATTRIBUTEY in the second NEWELEMENT. I am in need of an XSLT file which does just that, and, as stated further up, produces an XML that looks like this:
<FILE>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<NEWELEMENT>
<ATTRIBUTEY>A</ATTRIBUTEY>
</NEWELEMENT>
<NEWELEMENT>
<ATTRIBUTEY>B</ATTRIBUTEY>
</NEWELEMENT>
</ELEMENT>
</FILE>
Would anyone be able to help me with this? Any solutions which generate the desired output (above) from the very first example file would be much appreciated! It's a bonus if that solution follows steps one and two. Thanks!
I'm getting your requested output from the XSLT 3.0 below.
Just a couple of notes beforehand:
You mention ATTRIBUTEs but, in fact, everything is an element in your XML document. There are no attributes in the XML sense.
I really don't see the need to create the additional subelements .. you can already have a list of elements of the same name.
Nonetheless, as an exercise in style(sheets), here is a stylesheet that produces your output from your given input:
<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">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes" />
<xsl:template match="/FILE" >
<FILE>
<xsl:for-each-group select="*" group-by="name(.)" >
<xsl:variable name="parentName" as="xs:string" select="current-grouping-key()" />
<xsl:variable name="childNamesSameValues" as="xs:string*" >
<xsl:for-each-group select="current-group()/*" group-by="name(.)" >
<xsl:if test="count(distinct-values(current-group()/text())) eq 1">
<xsl:sequence select="current-grouping-key()" />
</xsl:if>
</xsl:for-each-group>
</xsl:variable>
<xsl:element name="{$parentName}" >
<xsl:for-each-group select="current-group()/*" group-by="name(.)" >
<xsl:choose>
<xsl:when test="current-grouping-key() = $childNamesSameValues">
<xsl:copy-of select="current-group()[1]" />
</xsl:when>
<xsl:otherwise >
<xsl:for-each select="current-group()" >
<xsl:element name="NEW{$parentName}" >
<xsl:copy-of select="." />
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:element>
</xsl:for-each-group>
</FILE>
</xsl:template>
</xsl:stylesheet>

Can we declare global variable and add/Remove/Check value from it?

I want to achieve
Declare global variable having no value
<xsl:variable name="IsEqual"/>
Check variable value and change according to condition
<xsl:choose>
**// Checking value equal or not**
<xsl:when test="name=$name">
<xsl:choose>
**//Checking variable value**
<xsl:when test="$IsEqual !='Unequal'">
**//Setting variable value**
<xsl:variable name="IsEqual" Select="Equal"/>
</xsl:when>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
**//Setting variable value**
<xsl:variable name="IsEqual" Select="Unequal"/>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$IsEqual"/>
Expected output value of variable $IsEqual.. IF it is not possible then what is another way to achieve this? What should I use instead of variable?
"It's not a bug, it's a feature": XSLT variables are designed not to be changeable. Actually they could be named constants. Working around that is difficult, it can be done using parameters. In most cases that isn't necessary if you use the XSLT programming attempt, where the programm is driven by the data through templates.
The answer to your question is no.
Copied this (own) text from XML and Variables
#Sam: What do you want to accomplish using a global variable? Where do you want to check for equality? I have no idea what you want to do so I can only give you a general example.
Try this xml file:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<root>
<data check="value1">This is data 1</data>
<data check="value2">This is data 2</data>
<data check="value3">This is data 3</data>
</root>
with this xslt file:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="v">value2</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates select="root/data[#check = $v]"/>
</xsl:template>
<xsl:template match="data">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Only the second data element will show up as this one matches the global variable, which stays the same all the time. If you want different values to match with, you can put them into your data file instead using a variable and compare the different elements.
For testing just save the two files (test.xml and test.xsl) into one directory and open test.xml with your browser.
#Sam again: As you insist on changing an xslt variable I have to repeat that this can't be done. Maybe there is a way around using the environment xslt is running in. E.g. PHP, where you can pass functions into the script. I described the technique here: Can PHP communicate with XSLT?
The xslt spec says:
XSLT does not provide an equivalent to the Java assignment operator
x = "value";
because this would make it harder to create an implementation that processes a document other than in a batch-like way, starting at the beginning and continuing through to the end.
(See http://www.w3.org/TR/xslt#variables to prove my answer "no" to your question is correct :-)

Looping a variable length array with namespaces in XSLT

My previous question[1] is related to this. I found the answer for that. Now I want to loop a variable length array with namespaces. My array:
<ns:array xmlns:ns="http://www.example.org">
<value>755</value>
<value>5861</value>
<value>4328</value>
<value>2157</value>
<value>1666</value>
</ns:array>
My XSLT code:(have added the namespace in the root)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://www.example.org">
<xsl:template match="/">
<xsl:variable name="number" select="ns:array" />
<xsl:for-each select="$number">
<xsl:value-of select="$number" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
[1]https://stackoverflow.com/questions/20287219/looping-a-variable-length-array-in-xslt
IMHO you confused yourself by introducing a variable called number which actually contains a node set of value tags. Then, as a consequence you used your variable as singe item/node which does not yield the desired result (presumingly, since you did not really tell us what you want to do with the values).
Also, I think your question does not really have anything to with namespace issues as such. You just have to make sure that the namespaces in your select expressions match the namespaces in your input file.
I would suggest to do without the variable and change the way you retrieve the current value:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://www.example.org">
<xsl:template match="/">
<xsl:for-each select="ns:array">
<!-- Inside here you can work with the `value` tag as the _current node_.
There are two most likely ways to do this. -->
<!-- a) Copy the whole tag to the output: -->
<xsl:copy-of select="." />
<!-- or b1) Copy the text part contained in the tag to the output: -->
<xsl:value-of select="." />
<!-- If you want to be on the safe side with respect to white space
you can also use this b2). This would handle the case that your output
is required not to have any white space in it but your imput XML has
some. -->
<xsl:value-of select="normalize-space(.)" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

How to do a "while"-like loop in XSLT?

I'm a novice in XSLT and I'm struggling a lot with this issue: I need to do a while-like loop in XSLT. I don't think the for-each will be enough to solve this problem.
I have a variable that is the result of a SELECT statement. It can return 0 or an integer. If the value is 0, it needs to do the SELECT again sending another parameter to see if the value is different.
I can only think in using a while-like loop, but maybe it has another way of achieving this? Like using a template and calling itself in the end? Is it possible?
Something like that:
<!-- initiate TEMPLATE -->
<!-- WHILE $VALUE = 0 -->
<xsl:variable name="sql.query">
<sql:param name="SQL_QUERY">SELECT $value FROM date_table WHERE date='$date'</mx:param>
</xsl:variable>
<xsl:variable name="VALUE">
<xsl:value-of select="sql:exec-formula('generic.sql', exsl:node-set($sql.query)//sql:param)" /> <!-- this will bring the result of the SELECT in the variable -->
</xsl:variable>
<xsl:variable name="date">
<xsl:value-of select="$date-1" /> <!-- something like that, it doesn't matter -->
</xsl:variable>
<xsl:if test="$VALUE ='0'">
<!-- call template again -->
</xsl:if>
<!-- end of template -->
<!-- recursive named template -->
<xsl:template name="while">
<xsl:variable name="VALUE">
<!-- your evaluation -->
</xsl:variable>
<!-- evaluate and recurse -->
<xsl:if test="$VALUE=0">
<xsl:call-template name="while"/>
</xsl:if>
</xsl:template>
Like using a template and calling itself in the end?
Correct: tail recursion is the usual solution to this problem, as illustrated by empo. The better XSLT processors will optimize tail recursion so it doesn't comsume the stack. The worse ones will run out of stack space after 500 or so iterations, in which case you need to look for a different solution.
Note: try to avoid this kind of construct
<xsl:variable name="date">
<xsl:value-of select="$date - 1" />
</xsl:variable>
when you could do
<xsl:variable name="date" select="$date - 1" />
It's unnecessarily verbose, and can also be very inefficient because the variable value is a tree rather than a simple string or number.