select xsl:variable values of its child-nodes - xslt

I have an .ods file and want to access the values of table-cells in table-rows by the value of the first column for the given row. So their heading in my case.
So the calc table looks like this:
First_Name | Last_Name
Peter | Parker
Emma | Stone
...
Here is my xslt-export-filter file:
SuperBasicExportFilter.xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
exclude-result-prefixes="table text office"
>
<xsl:output
method="xml"
indent="yes"
encoding="UTF-8"
omit-xml-declaration="no"
/>
<xsl:template match="/">
<xsl:variable name="columnHeadings">
<xsl:for-each select="//table:table/table:table-row[not(preceding::table:table-row)]//table:table-cell">
<xsl:element name="heading">
<xsl:attribute name="name" select="text:p" />
<xsl:value-of select="position()" />
</xsl:element>
</xsl:for-each>
</xsl:variable>
<html>
<body>
<h1>Hello</h1>
<xsl:message>columnHeadings: <xsl:value-of select="$columnHeadings" /></xsl:message>
<table>
<xsl:for-each select="//table:table/table:table-row">
<xsl:if test="position() > 1">
<tr>
<td>
First Column Value
<xsl:value-of select="table:table-cell[1]/text:p" />
<!-- <xsl:value-of select="table:table-cell[$columnHeadings/heading[#name='First_Name']]/text:p" /> -->
</td>
<td>
Second Column Value
<xsl:value-of select="table:table-cell[2]/text:p" />
<!-- <xsl:value-of select="table:table-cell[$columnHeadings/heading[#name='Last_Name']]/text:p" /> -->
</td>
</tr>
</xsl:if>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The message shows "columnHeadings: 1234567891011121314" and so on. So it is setting the position values correctly.
I tried getting the values based on the "name" attribute on the "heading" element.
But I can't get the values individually in any way. It seems I can't use the $columnHeadings with any XPath expression. It just returns "Xpath evaluation returned no result".
I tried
wrapping the "heading" elements with a "columnHeadings" element inside the variable and setting the "as" value of the variable to "element()"
using the "node-set" function (after importing the "exslt" ns)
using <xsl:variable name="columnHeadingsNode" select="document('')//xsl:variable[#name = 'columnHeadings']" /> to then get the value
using the xsl:key element like <xsl:key name="columnHeadings" match="//table:table/table:table-row[not(preceding::table:table-row)]//table:table-cell" use="text:p" /> - but this way I can't access it based on the "name"
What other things can I try to access the variable contents with a xpath expression?
Is it even possible to access the values like table:table-cell[$columnHeadings/heading[#name='Last_Name']]?
Answers to comments:
Which XSLT processor are you using?
I'm using whatever libreoffice 7.4.5.1 is using.
Can I change that?
The xsl:vendor is "libxslt" and the version is "1.0" according to the <xsl:value-of select="system-property('xsl:vendor')"/> and xsl:version values.
Do you get an error on <xsl:attribute name="name" select="text:p" />?
I actually do not for some reason. The test runs through without errors. I get a new browser tab with the produced xml output and no errors.
I tried ticking the "The filter needs XSLT 2.0 processor" but then I can't test run the filter anymore and don't get any output.
What's the overall purpose of this exercise?
I want to be able to select the values in the columns by their respective column heading, instead of the index, because I want to make it as portable as possible. At least I think that would help to achieve that goal. I have 184 columns. The column names won't change as likely as the index of the column, I believe.

The intent <xsl:attribute name="name" select="text:p" /> fails to create an attribute with a value in XSLT 1; it should raise an error but it seems your XSLT processor kind of ignores the select.
So try
<xsl:attribute name="name">
<xsl:value-of select="text:p"/>
</xsl:attribute>
instead.
That way, I would think that e.g. <xsl:variable name="columnHeadings-ns" select="exsl:node-set($columnHeadings)" xmlns:exsl="http://exslt.org/common"/> should allow you to use e.g. <xsl:value-of select="table:table-cell[$columnHeadings-ns/heading[#name='First_Name']]/text:p"/>.

Related

xslt - adding Lp. to node

I'm new in xslt, so I've some problems with adding Lp to my transformation.
This's my simple xml data:
<booking>
<bookingID>ww1</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww2</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww3</bookingID>
<voucherNumber>R-108</voucherNumber>
</booking>
<booking>
<bookingID>ww4</bookingID>
<voucherNumber>R-109</voucherNumber>
</booking>
<booking>
<bookingID>ww5</bookingID>
<voucherNumber>R-109</voucherNumber>
</booking>
<booking>
<bookingID>ww6</bookingID>
<voucherNumber>R-110</voucherNumber>
</booking>
The key is voucherNumber, i need to add Lp for the same voucherNumber
I'need output text file to look like this:
ID;VN,LP
ww1;108;1
ww2;108;2
ww3;108;3
ww4;109;1
ww5;109;2
ww6;110;1
I add the key on voucherNumber
<xsl:key name="x" match="booking" use="voucherNumber"/>
in for-each statement I've add this code: it's adding me on the last position (i know that i can change this for another position) the number of count my items for the same voucherNumber, but how i can add number Lp for the other items?
<xsl:choose>
<xsl:when test="generate-id(.) =generate-id(key('x',voucherNumber)[last()])">
<xsl:value-of select="count(key('x',voucherNumber)) "/>
</xsl:when>
<xsl:otherwise>
-- need LP for other items --
</xsl:otherwise>
</xsl:choose>
I can use only version 1.0 of xslt stylesheet.
Thank you for your advice
Best regards
It looks like you are trying to use Muenchian Grouping here, but what you probably should do is start off by selected the booking elements with the first occurrence of each distinct voucherNumber
<xsl:for-each select="booking[generate-id() = generate-id(key('x',voucherNumber)[1])]">
Then, you have a nested xsl:for-each where you get all the booking elements within that group (i.e. the booking elements with the same voucherNumber)
<xsl:for-each select="key('x', voucherNumber)">
Then, within this next xsl:for-each you can use the position() function to get the count of the record within that specific group
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:key name="x" match="booking" use="voucherNumber"/>
<xsl:template match="/*">
<xsl:for-each select="booking[generate-id() =generate-id(key('x',voucherNumber)[1])]">
<xsl:for-each select="key('x', voucherNumber)">
<xsl:value-of select="bookingID" />
<xsl:text>,</xsl:text>
<xsl:value-of select="substring-after(voucherNumber, '-')" />
<xsl:text>,</xsl:text>
<xsl:value-of select="position()" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note, this assumed your actual XML is well-formed and there is a single root element containing all your booking elements.
I have no idea what "Lp" means. Assuming you want to number the bookings sequentially, restarting on voucherNumber, try something like:
-- Edit --
The proper solution here would be to use <xsl:number> to number the nodes. However, since I could not find a single combination of attributes that would work the same way with all XSLT 1.0 processors, I have resorted to the following hack:
<xsl:key name="booking-by-voucherNumber" match="booking" use="voucherNumber"/>
<xsl:template match="/root">
<xsl:for-each select="booking">
<!-- get id and voucher number -->
<xsl:variable name="id" select="generate-id()" />
<xsl:for-each select="key('booking-by-voucherNumber', voucherNumber)">
<xsl:if test="generate-id()=$id">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
<!-- new line -->
</xsl:for-each>
</xsl:template>

XSLT Sort elements using dictionary

I have an XML which is a list of countries with their codes:
<Countries>
<Country Code="DE" />
<Country Code="FR" />
</Countries>
I apply XSLT to this XML to transform into HTML and I want elements to be sorted alphabetically by country name rather than code.
So I defined CountryCodeDictionaryEN variable which is a dictionary of country codes and names. It works fine in <xsl:value-of /> but not inside <xsl:sort />
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:variable name="CountryCodeDictionaryEN">
<item key="Germany"
value="DE" />
<item key="France"
value="FR" />
... all other countries ...
</xsl:variable>
<xsl:template match="/">
<table>
<xsl:for-each select="Countries/Country">
<xsl:sort select="msxsl:node-set($CountryCodeDictionaryEN)/item[#value = #Code]/#key"/>
<tr>
<td>
<xsl:variable name="Code" select="#Code"/>
<xsl:value-of select="msxsl:node-set($CountryCodeDictionaryEN)/item[#value = $Code]/#key"/>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
item[#value = #Code] compares the value attribute of an item against the Code attribute of the same item. Since the item elements don't have a Code attribute, the select expression will always result in an empty node set, so every node will get the same (empty string) sort key. And since xsl:sort is stable (items with the same sort key value retain their original relative ordering) the overall effect is no sorting.
You need to use current() to "break out" of the predicate and refer to the node being sort-ed:
<xsl:sort select="msxsl:node-set($CountryCodeDictionaryEN)
/item[#value = current()/#Code]/#key"/>
This will compare the value of the item against the Code of the Country.

XSLT use value-of inside select (Nested value-of select)

I am trying to pass the result of a value-of select to a parameter of another value-of select.
Here is the full code with pseudo statement (myVar)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" />
<xsl:variable name="data" select="document('user.xml')/user/data" />
<xsl:template match="/">
<xsl:for-each select="form/field">
<p class="field">
<xsl:attribute name="style">left:<xsl:value-of
select="left" />;top:<xsl:value-of select="top" />;
</xsl:attribute>
myVar = <xsl:value-of select="text" />
<xsl:value-of select="$data[#key = {#myVar}]/#value" />
</p>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I am trying to pass the result of a value-of select to a parameter of
another value-of select.
myVar = <xsl:value-of select="text" />
<xsl:value-of select="$data[#key = {#myVar}]/#value" />
The above line is syntactically incorrect -- select is the only attribute that doesn't accept AVT.
In order to achieve the wanted result replace the above with:
<xsl:value-of select="$data[#key = current()/text]/#value" />
Explanation: Proper use of the XSLT current() function.

Comparing an xs:boolean value in XSLT?

I have an xml schema with an element defined of type xs:boolean like this:
<xs:element name="useful"
minOccurs="1" maxOccurs="1"
type="xs:boolean"/>
I'm trying to use a choose/when/otherwise block in XSL to output something specific based on the value of that, like this:
<xsl:choose>
<xsl:when test="#useful = 0">No</xsl:when>
<xsl:otherwise>Yes</xsl:otherwise>
</xsl:choose>
I've tried every variation I can think of for that comparison (using true(), false(), 1, 0, removing the #, using //#) and it always prints out 'Yes'. What am I doing wrong here?
EDIT (by request from Martin Honnen):
Here's a sample of XML that's being used:
<?xml version="1.0"?>
<functions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="functions.xsd">
<function>
<name>func1</name>
<useful>0</useful>
</function>
<function>
<name>func2</name>
<useful>1</useful>
</function>
</functions>
This causes problems with using xsl:template I think, so I'm using xsl:for-each to loop through each function, and trying to output something specific for each useful tag. xsl:template can only be used at the top level of the xslt, correct?
The full XSLT file is
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html><body>
<h2>Functions</h2>
<table border="1">
<tr>
<th>Functions</th>
<th>Useful</th>
</tr>
<xsl:for-each select="functions/function">
<tr>
<td>
<xsl:value-of select="name"/>
</td>
<td>
<xsl:choose>
<xsl:when test="#useful = 0">No</xsl:when>
<xsl:otherwise>Yes</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:for-each>
</table>
</body></html>
</xsl:template>
</xsl:stylesheet>
I think the schema defines an element, but you are testing the value of an attribute -- this may be your main problem.
<xsl:choose>
<xsl:when test="#useful = 0">No</xsl:when>
<xsl:otherwise>Yes</xsl:otherwise>
</xsl:choose>
I've tried every variation I can think
of for that comparison (using
true(), false(), 1, 0,
removing the #, using //#) and it
always prints out 'Yes'. What am I
doing wrong here?
Most probably the current node is not an element that has an attribute named useful.
Here is how to work in XSLT 1.0 with the useful element type as defined by the schema:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="useful/text()[. = 'true' or .='1']">
Yes
</xsl:template>
<xsl:template match="useful/text()[. = 'false' or .='0']">
No
</xsl:template>
</xsl:stylesheet>
when this transformation is applied to the following XML document:
<t>
<useful>true</useful>
<useful>false</useful>
<useful>1</useful>
<useful>0</useful>
</t>
the wanted, correct result is produced:
<t>
<useful>
Yes
</useful>
<useful>
No
</useful>
<useful>
Yes
</useful>
<useful>
No
</useful>
</t>
In XSLT 2.0 (Schema-aware processor), you need to import the schema (use <xsl:import-schema>) then you can simply use:
('no', 'yes')[number(useful)+1]
where the current node is the parent of the useful element.
Your schema defines an element named "useful" but the schema is not relevant at all unless you are trying to use schema aware XSLT 2.0.
Doing #useful in XPath selects an attribute of that name of the context node which does not make any sense with the schema you have, as that defines an element.
So show us your instance XML the XSLT is processing, then we can show you some XSLT code:
<xsl:template match="useful">
<xsl:choose>
<xsl:when test=". = 'false' or . = 0">No</xsl:when>
<xsl:otherwise>Yes</xsl:otherwise>
</xsl:choose>
</xsl:template>
With schema-aware XSLT 2.0 you could do
<xsl:template match="useful">
<xsl:value-of select="if (data(.)) then 'Yes' else 'No'"/>
</xsl:template>

How do I get an XML node with xpath in a loop, based on an attribute with the same name as the attribute in the tree I'm searching?

I cant really formulate that better, so I'll go with an example instead:
XML:
<root>
<foo>
<bar id="1">sdf</bar>
<bar id="2">sdooo</bar
</foo>
<feng>
<heppa id="4">hihi</heppa>
<heppa id="2">sseeapeea</heppa>
<heppa id="1">....</heppa>
</feng>
</root>
XSLT:
<xsl:for-each select="/root/foo/bar">
<p>
<xsl:value-of select="." />: <xsl:value-of select="/root/feng/heppa[#id = #id]" />
</p>
</xsl:for-each>
Desired output:
<p>sdf: ....</p>
<p>sdooo: sseeapeea</p>
Actual output:
<p>sdf: hihi</p>
<p>sdooo: hihi</p>
For selecting nodes with XPath 1.0 only, you need to do a node set comparison:
/root/feng/heppa[#id=/root/foo/bar/#id]
Of course, this has NxM complexity (as the others XSLT solutions)
With XSLT 1.0 you should use keys because there are cross references:
<xsl:key name="kBarById" select="bar" use="#id"/>
<xsl:template match="/root/feng/heppa[key('kBarById',#id)]">
<p>
<xsl:value-of select="concat(key('kBarById',#id),': ',.)"/>
</p>
</xsl:template>
I assume you mean /root/foo/bar since /root/foo elements don't have id.
You're comparing the #id with itself, so of course it's true for the first node examined. You can use current() to reference the current node in an expression:
<xsl:for-each select="/root/foo/bar">
<p>
<xsl:value-of select="." />: <xsl:value-of select="/root/feng/heppa[#id = current()/#id]" />
</p>
</xsl:for-each>
Another solution is to read the id attribute into a variable.
<xsl:for-each select="/root/foo/bar">
<xsl:variable name="id" select="#id"/>
<p>
<xsl:value-of select="." />: <xsl:value-of select="/root/feng/heppa[#id = $id]" />
</p>
</xsl:for-each>
This might be handier, if your real use case is more complicated and you need to use the value of the id multiple times in this for-each section.