XSLT 1.1 nodeset - xslt

I have this...
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common">
<xsl:variable name="data">
<root>
<test>1000</test>
<test>2000</test>
<test>3000</test>
</root>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="$data/root/test">
<xsl:for-each select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And I thought that with XSLT 1.1 that the $data variable would be treated as a node-set and that therefore standard XSLT stuff - like for-each - should work.
I don't get an error, but I get no output - it's as though the $data nodeset is completely empty.
I've also tried this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common">
<xsl:variable name="data">
<root>
<test>1000</test>
<test>2000</test>
<test>3000</test>
</root>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="exslt:node-set($data)/root/test">
<xsl:for-each select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
With the same results. (Infact, I've done this before with no problems)
I'm using Saxon.
What am I missing? (I'm not in a position to use XSLT 2.0 by the way)
Thanks

You wrote:
I don't get an error, but I get no
output
The problem is here:
<xsl:for-each select="."/>
Your question:
What am I missing?
Answer: You are missing your template.

<xsl:template match="/">
<xsl:for-each select="exslt:node-set($data)/root/test">
<xsl:for-each select="."/>
</xsl:for-each> </xsl:template>
The error is in the following (empty) instruction:
<xsl:for-each select="."/>
This (probably) has to be:
<xsl:value-of select="."/>
or
<xsl:copy-of select="."/>
or ... ?

Related

Specify output format of xslt

I know this is asked before but I can't find a simple example. Just trying to change output format. My existing XSLT is this and works but output format is always .txt.
<?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:text>*,</xsl:text>
<xsl:value-of select="//AccountFields/AccountID"/>
<xsl:text>
</xsl:text>
<xsl:for-each select="//TransactionLine">
<xsl:variable name="i" select="position()" />
<xsl:value-of select="//AccountFields/AccountID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="ItemFields/ItemID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="TransactionLineFields/UnitQuantity"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="TransactionLineFields/UnitPrice"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I tried adding <xsl:result-document href="Order.csv" method="text"> but the file fails to generate completely and the tool I'm using doesn't give me a clear way to see errors.
<?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:result-document href="Order.csv" method="text">
<xsl:text>*,</xsl:text>
<xsl:value-of select="//AccountFields/AccountID"/>
<xsl:text>
</xsl:text>
<xsl:for-each select="//TransactionLine">
<xsl:variable name="i" select="position()" />
<xsl:value-of select="//AccountFields/AccountID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="ItemFields/ItemID"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="TransactionLineFields/UnitQuantity"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="TransactionLineFields/UnitPrice"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:result-element>
</xsl:template>
</xsl:stylesheet>
xsl:result-document is only available in XSLT 2.0 and later versions.
If you can use XSLT-2.0, you could pass parameters and set the method attribute with an attribute value template to
"xml" | "html" | "xhtml" | "text" | "json" | "adaptive" | eqname
Here, the file extension and the output method are set dynamically by parameters passed to the stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="fileExt" select="'.csv'" />
<xsl:param name="fileMethod" select="'xml'" />
<xsl:template match="/">
<xsl:result-document href="{concat('Order',$fileExt)}" method="{$fileMethod}">
<xsl:text>*,</xsl:text>
...
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>
It has been tested with Saxon.
If you wanted to change the xsl:output method instead, have a look at this question.

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>

XSLT: output unmatched elements from two pieces of xml

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>

Concatenate data into one variable

An XML file has data like:
<AddtlStsRsnInf>/00000002/Level 2 Reject</AddtlStsRsnInf>
<AddtlStsRsnInf>The Transaction Reference Number is</AddtlStsRsnInf>
<AddtlStsRsnInf>not unique.</AddtlStsRsnInf>
How do you concatenate the data from all the three tags into a variable?
Thanks and regards,
Kiran
This may help:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="concat" match="/data">
<xsl:for-each select="AddtlStsRsnInf">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Given:
<?xml version="1.0"?>
<data>
<AddtlStsRsnInf>/00000002/Level 2 Reject</AddtlStsRsnInf>
<AddtlStsRsnInf>The Transaction Reference Number is</AddtlStsRsnInf>
<AddtlStsRsnInf>not unique.</AddtlStsRsnInf>
</data>
You can wrap it in a variable (v) using:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="concat" match="/data">
<xsl:variable name="v">
<xsl:for-each select="AddtlStsRsnInf">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$v" />
</xsl:template>
</xsl:stylesheet>
I think you would use something like:
<xsl:variable name="myVar" select="fn:string-join(//AddtlStsRsnInf/text(), ' ')" />
You'll need to adjust the XPath query if you're only supposed to select some AddtlStsRsnInf nodes.

Using XSLT, how do I separate nodes based on their value?

I have a pretty flat XML structure that I need to reorder into categorised sections and, for the life of me, I can't figure out how to do it in XSLT (not that I'm by any means an expert.)
Basically, the original XML looks kinda like:
<things>
<thing>
<value>one</value>
<type>a</type>
</thing>
<thing>
<value>two</value>
<type>b</type>
</thing>
<thing>
<value>thee</value>
<type>b</type>
</thing>
<thing>
<value>four</value>
<type>a</type>
</thing>
<thing>
<value>five</value>
<type>d</type>
</thing>
</things>
And I need to output something like:
<data>
<a-things>
<a>one</a>
<a>four</a>
</a-things>
<b-things>
<b>two</b>
<b>three</b>
</b-things>
<d-things>
<d>five</d>
</d-things>
</data>
Note that I can't output <c-things> if there aren't any <c> elements, but I do know ahead of time what the complete list of types is, and it's fairly short so handcoding templates for each type is definitely possible. It feels like I could probably hack something together using <xsl:if> and <xsl:for-each> but it also feels like there must be a more ... 'templatey' way to do it. Can anyone help?
Cheers.
As you are using Saxon, use the native XSLT 2.0 grouping.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="things">
<data>
<xsl:for-each-group select="thing" group-by="type">
<xsl:element name="{concat(current-grouping-key(),'-things')}">
<xsl:for-each select="current-group()">
<xsl:element name="{current-grouping-key()}">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</data>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0 you can group with keys. This approach is called Muenchian Grouping.
The xsl:key defines an index containing thing elements, grouped by the string value of their type element. Function key() returns all nodes from the key with the specified value.
The outer xsl:for-each selects the thing elements that are the first returned by key() for their value.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="thing" match="thing" use="type" />
<xsl:template match="things">
<data>
<xsl:for-each select="thing[generate-id(.)=generate-id(key('thing',type)[1])]">
<xsl:element name="{concat(type,'-things')}">
<xsl:for-each select="key('thing',type)">
<xsl:element name="{type}">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>
The generic solution is to use an XSL key:
<xsl:key name="kThingByType" match="thing" use="type" />
<xsl:template match="things">
<xsl:copy>
<xsl:apply-templates select="thing" mode="group">
<xsl:sort select="type" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="thing" mode="group">
<xsl:variable name="wholeGroup" select="key('kThingByType', type)" />
<xsl:if test="generate-id() = generate-id($wholeGroup[1])">
<xsl:element name="{type}-thing">
<xsl:copy-of select="$wholeGroup/value" />
</xsl:element>
</xsl:if>
</xsl:template>
The above yields:
<things>
<a-thing>
<value>one</value>
<value>four</value>
</a-thing>
<b-thing>
<value>two</value>
<value>thee</value>
</b-thing>
<d-thing>
<value>five</value>
</d-thing>
</things>
In XSLT 2, you can do this very elegantly. Say you have a template for formatting each thing before it is wrapped in an <a> element:
<xsl:template match="thing" mode="format-thing">
<xsl:value-of select="value/text()"/>
</xsl:template>
Then you can apply that to each thing of some $type to build the <a-things> elements via a function:
<xsl:function name="my:things-group" as="element()">
<xsl:param name="type" as="xs:string"/>
<xsl:param name="things" as="element(thing)*"/>
<xsl:element name="{ concat($type, '-things') }">
<xsl:for-each select="$things[type/text() eq $type]">
<xsl:element name="{ $type }">
<xsl:apply-templates select="." mode="format-thing"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:function>
Then you can call that function for each unique type (a, b, d in your sample input) to build the entire output and you're done:
<xsl:template match="/">
<data>
<xsl:sequence select="
for $type in distinct-values(things/thing/type/text())
return my:things-group($type, /things/thing)
"/>
</data>
</xsl:template>
Of course, asking the question made it obvious...
My solution does use an <xsl:if>, but I can't see how it couldn't now I think about it. My solution looks basically like:
<xsl:if test="/things/thing/type = 'a'">
<a-things>
<xsl:apply-templates select="/things/thing[type='a']" mode="a" />
</a-things>
</if>
<xsl:template match="/things/thing[type='a']" mode="a">
<a><xsl:value-of select="value"/>
</xsl:template>
And repeat for the other types. I've coded it up, and it seems to work just fine.
<a-things>
<xsl:for-each select="thing[type = 'a']">
<a><xsl:value-of select="./value" /></a>
</xsl:for-each>
</a-things>
If you want to get really snazzy, replace the <a-things> and the predicate with parameters and use attribute value templates:
<xsl:param name="type" />
<xsl:element name="{$type}-things">
<xsl:for-each select="thing[type = $type]">
<xsl:element name="{$type}"><xsl:value-of select="./value" /></xsl:element>
</xsl:for-each>
</xsl:element>
And using grouping, you can do it without the if:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="things">
<data>
<xsl:for-each select="thing[not(type=preceding-sibling::thing/type)]">
<xsl:variable name="type"><xsl:value-of select="type" /></xsl:variable>
<xsl:element name="concat($type, '-things')">
<xsl:for-each select="../thing[type=$type]">
<xsl:element name="$type">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>