Structural requirements when using "except" in XPATH/XSL - xslt

I am having trouble when using "except" in xpath. Here is the chunk of problem code. (I tried to simplify as much as possible without obscuring the whole problem).:
<!--First, create a variable containing some nodes that we want to filter out.
(I'm gathering elements that are missing child VALUE elements
and whose child DOMAIN and VARIABLE elements only occur once
in the parent list of elements.)
I've confirmed that this part does generate the nodes I want,
but maybe this is the incorrect result structure?-->
<xsl:variable name="badValues">
<xsl:for-each select="$root/A[not(VALUE)]">
<xsl:choose>
<xsl:when test="count($root/A[DOMAIN=current()/DOMAIN and VARIABLE=current()/VARIABLE])=1">
<xsl:copy-of select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<!--Next Loop over the original nodes, minus those bad nodes.
For some reason, this loops over all nodes and does not filter out the bad nodes.-->
<xsl:for-each select="$root/A except $badValues/A"> ...

When you create an xsl:variable without using #select and do not specify the type with the #as, it will create the variable as a temporary tree.
You want to create a sequence of nodes, so that when they are compared in the except operator, they are "seen" as the same nodes. You can do this by specifying as="node()*" for the xsl:variable and by using xsl:sequence instead of xsl:copy-of:
<xsl:variable name="badValues" as="node()*">
<xsl:for-each select="$root/A[not(VALUE)]">
<xsl:choose>
<xsl:when test="count($root/A[DOMAIN=current()/DOMAIN
and VARIABLE=current()/VARIABLE])=1">
<xsl:sequence select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
Alternatively, if you were to use a #select and eliminate the xsl:for-each it would also work. As Martin Honnen suggested, you could use an xsl:key and select the values like this:
<xsl:key name="by-dom-and-var" match="A" use="concat(DOMAIN, '|', VARIABLE)"/>
Then change your badValues to this:
<xsl:variable name="badValues"
select="$root/A[not(VALUE)]
[count(key('by-dom-and-var',
concat(DOMAIN, '|', VARIABLE))/VARIABLE) = 1]"/>>
You can see the difference in the identity of the nodes by using the generate-id() function as you iterate over the items by executing this stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:variable name="root" select="*" as="item()*"/>
<xsl:variable name="originalBadValues">
<xsl:for-each select="$root/A[not(VALUE)]">
<xsl:choose>
<xsl:when test="count($root/A[DOMAIN=current()/DOMAIN
and VARIABLE=current()/VARIABLE])=1">
<xsl:copy-of select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="badValues" as="node()*">
<xsl:for-each select="$root/A[not(VALUE)]">
<xsl:choose>
<xsl:when test="count($root/A[DOMAIN=current()/DOMAIN
and VARIABLE=current()/VARIABLE])=1">
<xsl:sequence select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<!--These are the generated ID values of all the A elements-->
<rootA>
<xsl:value-of select="$root/A/generate-id()"
separator=", "/>
</rootA>
<!--These are the generated ID values for
the original $badValues/A -->
<originalBadValues>
<xsl:value-of select="$originalBadValues/A/generate-id()"
separator=", " />
</originalBadValues>
<!--These are the generated ID values for
the correct selection of $badValues-->
<badValues>
<xsl:value-of select="$badValues/generate-id()"
separator=", " />
</badValues>
<!--The generated ID values for the result of
the except operator filter-->
<final>
<xsl:value-of select="($root/A except $badValues)/generate-id()"
separator=", "/>
</final>
</xsl:template>
</xsl:stylesheet>
Executed against this XML file:
<doc>
<A>
<VALUE>skip me</VALUE>
<DOMAIN>a</DOMAIN>
<VARIABLE>a</VARIABLE>
</A>
<A>
<DOMAIN>a</DOMAIN>
<VARIABLE>a</VARIABLE>
</A>
<A>
<DOMAIN>b</DOMAIN>
<VARIABLE>b</VARIABLE>
</A>
<A>
<DOMAIN>c</DOMAIN>
<VARIABLE>c</VARIABLE>
</A>
<A>
<DOMAIN>a</DOMAIN>
<VARIABLE>a</VARIABLE>
</A>
</doc>
It produces the following output:
<rootA>d1e3, d1e15, d1e24, d1e33, d1e42</rootA>
<originalBadValues>d2e1, d2e9</originalBadValues>
<badValues>d1e24, d1e33</badValues>
<final>d1e3, d1e15, d1e42</final>

Related

xslt keep multiple variables in scope depending on a single test

I have many variables and only two cases.
My original approach goes out of scope:
<xsl:choose>
<xsl:when test='$something = 6'>
<xsl:variable name="foo">x</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
<xsl:when test='$something = 7'>
<xsl:variable name="foo">y</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
</xsl:choose>
ie. later, with saxon, XPST0008: Variable x has not been declared (or its declaration is not in scope)
I think it would work if I would choose inside an xsl:variable tag, but then the tests would be repeated over and over and over and over and over:
<xsl:variable name="foo">
<xsl:choose>
<xsl:when test='$something = 6'>x</xsl:when>
<xsl:when test='$something = 7'>y</xsl:when>
</xsl:choose>
</xsl:variable>
<!-- 50 other variables, the two tests repeated for each... -->
Is there a way to keep the variables in scope but also don't repeat myself?
update 1
adding the complete 'sscce' files on request
original approach:
<?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:variable name="something">6</xsl:variable>
<xsl:choose>
<xsl:when test="$something = '6'">
<xsl:variable name="foo">x</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
<xsl:when test="$something = '7'">
<xsl:variable name="foo">y</xsl:variable>
<!-- 50 other variables -->
</xsl:when>
</xsl:choose>
<xsl:value-of select="$foo"/>
</xsl:template>
</xsl:stylesheet>
approach that works but forces to repeat myself:
<?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:variable name="something">6</xsl:variable>
<xsl:variable name="foo">
<xsl:choose>
<xsl:when test='$something = 6'>x</xsl:when>
<xsl:when test='$something = 7'>y</xsl:when>
</xsl:choose>
</xsl:variable>
<!-- 50 other variables, the two tests repeated for each... -->
<xsl:value-of select="$foo"/>
</xsl:template>
</xsl:stylesheet>
example xml file: <xml/>. example saxon command-line: java -jar saxon9he.jar -s:in.xml -xsl:in.xsl -o:out.html
Well, I'd rather process that way :
<xsl:variable name="something">6</xsl:variable>
<xsl:variable name="var.set">
<xsl:choose>
<xsl:when test="$something = '6'">
<foo>x</foo>
<bar>xx</bar>
<!-- 50 other variables, to be inserted as tag -->
</xsl:when>
<xsl:when test="$something = '7'">
<foo>y</foo>
<bar>yy</bar>
<!-- 50 other variables, to be inserted as tag -->
</xsl:when>
</xsl:choose>
</xsl:variable>
The variable var.set will be a nodeset that you will be able to read thanks to the exsl:node-set() extension.
For example to retrieve the value for foo (stored as a node and not as a variable anymore), use something like: <xsl:value-of select="exsl:node-set($var.set)/foo" />. Like this you will handle a single variable, quite as if it were an array of values.
PS: on the root tag of your stylesheet, do not forget to add the exsl namespace declaration xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"
If you know your mappings beforehand, you can store them in their own file.
So instead of having something like this
<xsl:variable name="var1">
<xsl:if test="something = 5">x</xsl:if>
</xsl:variable>
<xsl:value-of select="$var1"/>
You could have this
<xsl:value-of select="document('other.xml')/root/scheme[#number = 5]/#value"/>
With this as other.xml
<root>
<scheme number="5" value="x"/>
<scheme number="6" value="y"/>
</root>
You'll probably want a more complex other.xml, with various groups of colours for each colour scheme, but the idea would be the same, and avoids tests and variables completely.

Order values in variable in xslt or whit

How can I order values in a variable that contains strings that are comma-separated?
It would be OK if the variable was separated on sub-strings of 001a and so on.
My variable is a string of values separated by commas, but because I join strings from more documents they are not in the right order. It is something like this:
001a, 001b, 001d, 100a, 100c, 100d, 001c, 001f, 100b,...
I would like to get this:
001a, 001b, 001c, 001d, 100a, 001b, 100c, 100d, 001f,...
Using XSL 2.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:variable name="unsorted" select="'001a, 001b, 001d, 100a, 100c, 100d, 001c, 001f, 100b'"/>
<xsl:variable name="sorted">
<xsl:for-each select="tokenize($unsorted, ', ')">
<xsl:sort select="." />
<xsl:choose>
<xsl:when test="position()!=last()">
<xsl:value-of select="."/><xsl:text>, </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<unsorted><xsl:value-of select="$unsorted"/></unsorted>
<sorted><xsl:value-of select="$sorted"/></sorted>
</xsl:template>
</xsl:stylesheet>

Retrieving nodes having (or not) a child to apply a conditional template

I read lot of articles but did not find a conclusive help to my problem.
I have an XML document to which I apply an xslt to get a csv file as output.
I send a parameter to my xsl transformation to filter the target nodes to apply the templates.
The xml document looks like that (I removed some unuseful nodes for comprehension):
<GetMOTransactionsResponse xmlns="http://www.exane.com/pott" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exane.com/pott PoTTMOTransaction.xsd">
<MOTransaction>
<Transaction VersionNumber="2" TradeDate="2013-11-20">
<TransactionId Type="Risque">32164597</TransactionId>
<InternalTransaction Type="Switch">
<BookCounterparty>
<Id Type="Risque">94</Id>
</BookCounterparty>
</InternalTransaction>
<SalesPerson>
<Id Type="Risque">-1</Id>
</SalesPerson>
</Transaction>
<GrossPrice>58.92</GrossPrice>
<MOAccount Account="TO1E" />
<Entity>0021</Entity>
</MOTransaction>
<MOTransaction>
<Transaction VersionNumber="1" TradeDate="2013-11-20">
<TransactionId Type="Risque">32164598</TransactionId>
<SalesPerson>
<Id Type="Risque">-1</Id>
</SalesPerson>
</Transaction>
<GrossPrice>58.92</GrossPrice>
<MOAccount Account="TO3E" />
<Entity>0021</Entity>
</MOTransaction>
</GetMOTransactionsResponse>
My xslt is below (sorry it's quite long, and I write it more simple than it really is):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:pott="http://www.exane.com/pott">
<xsl:output method="text" omit-xml-declaration="no" indent="no" />
<xsl:param name="instrumentalSystem"></xsl:param>
<xsl:template name="abs">
<xsl:param name="n" />
<xsl:choose>
<xsl:when test="$n = 0">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:when test="$n > 0">
<xsl:value-of select="format-number($n, '#')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number(0 - $n, '#')" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="outputFormat">
<!--Declaration of variables-->
<xsl:variable name="GrossPrice" select="pott:GrossPrice" />
<xsl:variable name="TransactionId" select="pott:Transaction/pott:TransactionId[#Type='Risque']" />
<xsl:variable name="VersionNumber" select="pott:Transaction/#VersionNumber" />
<!--Set tags values-->
<xsl:value-of select="$Entity" />
<xsl:text>;</xsl:text>
<xsl:value-of select="concat('0000000', pott:MOAccount/#Account) "/>
<xsl:text>;</xsl:text>
<xsl:text>;</xsl:text>
<xsl:value-of select="$TransactionId" />
<xsl:text>;</xsl:text>
<xsl:value-of select="$VersionNumber" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="/">
<xsl:choose>
<!-- BB -->
<xsl:when test="$instrumentalSystem = 'BB'">
<!--xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]"-->
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]">
<xsl:call-template name="outputFormat"></xsl:call-template>
</xsl:for-each>
</xsl:when>
<!-- CP -->
<xsl:when test="$instrumentalSystem = 'CP'">
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[not(pott:InternalTransaction)]">
<xsl:call-template name="outputFormat"></xsl:call-template>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
If parameter = BB, I want to select MOTransaction nodes that have a child Transaction that contains a InternalTransaction node.
If parameter = CP, I want to select MOTransaction nodes that don't have a child Transaction that contains a InternalTransaction node
When I write
pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction], I get the Transaction nodes and not the MOTransaction nodes
I think I am not very far from the expected result, but despite all my attempts, I fail.
If anyone can help me.
I hope being clear, otherwise I can give more information.
Looking at one of xsl:for-each statements, you are doing this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]">
You say you want to select MOTransaction elements, but it is actually selecting the child Transaction elements. To match the logic you require, it should be this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction[pott:InternalTransaction]]">
In fact, this should also work
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction/pott:InternalTransaction]">
Similarly, for the second statement (in the case of the parameter being "CP"), it could look like this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction[not(pott:InternalTransaction)]]">
Alternatively, it could look like this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[not(pott:Transaction/pott:InternalTransaction)]">
They are not quite the same though, as the first will only include MOTransaction elements that have Transaction child elements, whereas the second will include MOTransaction that don't have any Transaction childs at all.
As a slight aside, you don't really need to use an xsl:for-each and xsl:call-template here. It might be better to use template matching.
Firstly, try changing the named template <xsl:template name="outputFormat"> to this
<xsl:template match="pott:MOTransaction">
Then, you can re-write you merge the xsl:for-each and xsl:call-template into a single xsl:apply-templates call.
<xsl:apply-template select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction/pott:InternalTransaction]" />

Get conditional value from previous node

I have this XSLT 2.0 template:
<xsl:template match="footnote">
<xsl:variable name = "string" select="./text()"/>
<xsl:variable name = "bool">
<xsl:choose>
<xsl:when test="$string = preceding::footnote/text()">
<xsl:text>false</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>true</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if test="$bool = 'true'">
<xsl:variable name="footnoteCount">
<xsl:call-template name="getItemNumber">
<xsl:with-param name="node" select="."/>
</xsl:call-template>
</xsl:variable>
<!-- DO XSL-FO TRANSFORMATION STUFF-->
</xsl:if>
<xsl:if test="$bool = 'false'">
<xsl:variable name = "footnoteCount">
<xsl:if test="$string = preceding::footnote/text()">
<xsl:value-of select="preceding::footnote/$footnoteCount"/>
</xsl:if>
</xsl:variable>
<!--DO XSL-FO TRANSFORMATION STUFF-->
</xsl:if>
</xsl:template>
Edited sample XML. I'd like to transform this:
<footnote>Foo bar</footnote>
<footnote>Bar foo</footnote>
<footnote>Foo bar</footnote>
<footnote>Foo bar</footnote>
<footnote>Bar</footnote>
<footnote>Foo</footnote>
Into this:
<footnote>Foo bar</footnote>
<footnote>Bar foo</footnote>
<footnote>Bar</footnote>
<footnote>Foo</footnote
And then stylise it using XSL-FO. The aim of this styling is that text in the main body will have a numbered reference, which is represented by $footnotecount, to the footnote which is then rendered at the bottom of the page. I need to transform the document so that duplicate footnates are only rendered once and that the number reference ($footnoteCount) is the same for each duplicate.
So what I'm trying to do with this template is:
Determine whether a footnote element with the current node's text already exists.
If it doesn't exist (i.e, '$bool' is 'true') , find the previous footnote's number, increment it (this is done in the 'getItemNumber' template) and create a 'new' footnote. If it does exist ($bool is 'false'), get the $footnoteCount variable of the node that the text matches and use it for the current node.
It's the scenario in which the footnote already exists that I'm having trouble with. I have no idea how to get the $footnoteCount variable from a previous, specific node dependent on whether than node meets a certain criteria (whether its text is the same as the $string variable in the current node). It's being made more difficult by the fact that the $footnoteCount variable only exists conditionally (even if in practice it will always exist since $bool has to be either true or false).
Does anyone have advice on what to do here?
This looks like a grouping problem, and in XSLT2.0 you can make use of the xsl:for-each-group element to get the distinct elements
<xsl:for-each-group select="footnote" group-by=".">
I think you need a different approach to do your numbering. Firstly you could create a variable to hold a 'look-up' of footnote descriptions and their index
<xsl:variable name="footnotes">
<xsl:for-each-group select="//footnote" group-by=".">
<footnote id="{position()}">
<xsl:value-of select="."/>
</footnote>
</xsl:for-each-group>
</xsl:variable>
This means the footnotes variable contains the following elements
<footnote id="1">Foo bar</footnote>
<footnote id="2">Bar foo</footnote>
<footnote id="3">Bar</footnote>
<footnote id="4">Foo</footnote>
Then, to replace your existing footnote elements with numeric references, you would have a template like this
<xsl:template match="footnote">
<footnote>
<xsl:value-of select="$footnotes/footnote[. = current()]/#id"/>
</footnote>
</xsl:template>
Try the following XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="footnotes">
<xsl:for-each-group select="//footnote" group-by=".">
<footnote id="{position()}">
<xsl:value-of select="."/>
</footnote>
</xsl:for-each-group>
</xsl:variable>
<xsl:template match="/*">
<xsl:apply-templates select="footnote"/>
Footnotes
<xsl:copy-of select="$footnotes"/>
</xsl:template>
<xsl:template match="footnote">
<footnote>
<xsl:value-of select="$footnotes/footnote[. = current()]/#id"/>
</footnote>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML (assuming a root element), the following is output
<footnote>1</footnote>
<footnote>2</footnote>
<footnote>1</footnote>
<footnote>1</footnote>
<footnote>3</footnote>
<footnote>4</footnote>
Footnotes
<footnote id="1">Foo bar</footnote>
<footnote id="2">Bar foo</footnote>
<footnote id="3">Bar</footnote>
<footnote id="4">Foo</footnote>
As well as failing to use xsl:for-each-group, there are many other things wrong with your code. For example, take this:
<xsl:variable name = "bool">
<xsl:choose>
<xsl:when test="$string = preceding::footnote/text()">
<xsl:text>false</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>true</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
You can write that as
<xsl:variable name="bool" as="xs:boolean()"
select="$string = preceding::footnote"/>
Then you do this:
<xsl:if test="$bool = 'true'">
<xsl:variable name="footnoteCount">
<xsl:call-template name="getItemNumber">
<xsl:with-param name="node" select="."/>
</xsl:call-template>
</xsl:variable>
</xsl:if>
which is completely useless: the variable goes out of scope as soon as it is declared, so it can never be referenced.
You need to do some background reading about XSLT, there are a lot of ideas you haven't yet grasped.

xslt 1.0 rewrite values of a node according to a map

While transforming a document, I need to 'look up' certain node contents in a 'map', and write those values.
I inlined my 'map' in the transformation.
<xsl:variable name="inlinedmap">
<kat id="stuff">value</kat>
<!-- ... -->
</xsl:variable>
<xsl:variable name="map" select="document('')/xsl:stylesheet/xsl:variable[#name='inlinedmap']" />
<xsl:template match="/">
<xsl:for-each select="/*/foo">
<!-- 'bar' contents should equal to contents of 'kat' -->
<xsl:variable name="g" select="$map/key[.=bar]"/>
<xsl:choose>
<xsl:when test="$g != ''">
<xsl:value-of select="$g/#id"/>
</xsl:when>
<xsl:otherwise>
ERROR
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
I'm always getting ERROR value.
I can't put map value's into attributes, because they contain letters that get escaped.
How can I make it work?
I think there are a few problems here:
You seem to be looking for key elements in your variable, but they're called kat there (typo?)
You seem to be trying to reference the bar child of the context node inside the loop, but you need to use current() to do that
You should create this map as elements in your own namespace instead of an xsl:variable
Here's a complete example. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my">
<my:vars>
<kat id="stuff">value</kat>
<!-- ... -->
</my:vars>
<xsl:variable name="map" select="document('')/*/my:vars/*"/>
<xsl:template match="/">
<xsl:for-each select="/*/foo">
<!-- 'bar' contents should equal to contents of 'kat' -->
<xsl:variable name="g" select="$map[.=current()/bar]"/>
<xsl:choose>
<xsl:when test="$g != ''">
<xsl:value-of select="$g/#id"/>
</xsl:when>
<xsl:otherwise>
ERROR
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Applied to this input:
<root>
<foo><bar>value</bar></foo>
<foo><bar>value1</bar></foo>
<foo><bar>value2</bar></foo>
<foo><bar>value3</bar></foo>
</root>
Produces this output (one match):
stuff
ERROR
ERROR
ERROR