adding multiple filters - xslt

As I have mentioned in this post:
dynamic multiple filters in xsl
Basically, I want to apply multiple filters to my xml using "for loop" and these filters are dynamic which are coming from some other xml
sth like this:
foreach(list/field[#ProgramCategory=$Country][not(contain(#Program,$State1][not(contain(#Program,$State2][not(contain(#Program,$State3][not(contain(#Program,$Staten])
The problem is that I can get n no. of states which I am getting through for loop of other xml.
I cannot use document() function as suggested by Dimitre so I was thinking of achieving it by:
<xsl:variable name="allprograms">
<xsl:for-each select="/list2/field2">
<xsl:text disable-output-escaping="yes">[not(contains(#Program,'</xsl:text><xsl:value-of select="#ProgramID"></xsl:value-of><xsl:text disable-output-escaping="yes">'))]</xsl:text>
</xsl:for-each>
</xsl:variable>
gives me something like this:
[not(contains(#Program,'Virginia'))][not(contains(#Program,'Texas'))][not(contains(#Program,'Florida'))]
I want to use this above value as a filter in the for loop below and I am not sure how to achieve that
<xsl:for-each="list/field[not(contains(#Program,'Virginia'))][not(contains(#Program,'Texas'))][not(contains(#Program,'Florida'))]">
Before this I also have a for loop to filter United States
xsl:for-each="list/field $allprograms">
<xsl:value-of select="#ows_ID" />
</xsl:for-each>
I want my answer to be 1082, 1088..
I can add the xml here too if there is any confusion..

Jack,
From the previous solution you just need to add to this:
<xsl:param name="pFilteredStates">
<state>Virginia</state>
<state>Texas</state>
<state>Florida</state>
</xsl:param>
the following (changing the current variable definition that relies on the document() function):
<xsl:variable name="vFiltered" select=
"ext:node-set($pFilteredStates)/*
"/>
Where the "ext:" prefix needs to be bound to this namespace (this is the EXSLT namespace -- if your XSLT processor doesn't implement exslt:node-set() then you need to find what xxx:node-set() extension it implements, or tell us what is your XSLT processor and people will provide this information):
"http://exslt.org/common"
So, your <xsl:stylesheet> may look like the following:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
I still recommend that the $pFilteredStates parameter should be passed by the initiator of the transformation -- in which case you can delete the definition of $vFiltered and replace every reference to it with $pFilteredStates` and the transformation should work OK.

Related

Constructing, not selecting, XSL node set variable

I wish to construct an XSL node set variable using a contained for-each loop. It is important that the constructed node set is the original (a selected) node set, not a copy.
Here is a much simplified version of my problem (which could of course be solved with a select, but that's not the point of the question). I've used the <name> node to test that the constructed node set variable is in fact in the original tree and not a copy.
XSL version 1.0, processor is msxsl.
Non-working XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="entryNodes" select="msxsl:node-set($entries)"/>
<xsl:for-each select="$entryNodes">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<name>X</name>
<entry>1</entry>
<entry>2</entry>
</root>
Wanted output:
X1X2
Actual output:
12
Of course the (or a) problem is the copy-of, but I can't work out a way around this.
There isn't a "way around it" in XSLT 1.0 - it's exactly how this is supposed to work. When you have a variable that is declared with content rather than with a select then that content is a result tree fragment consisting of newly-created nodes (even if those nodes are a copy of nodes from the original tree). If you want to refer to the original nodes attached to the original tree then you must declare the variable using select. A better question would be to detail the actual problem and ask how you could write a suitable select expression to find the nodes you want without needing to use for-each - most uses of xsl:if or xsl:choose can be replaced with suitably constructed predicates, maybe involving judicious use of xsl:key, etc.
In XSLT 2.0 it's much more flexible. There's no distinction between node sets and result tree fragments, and the content of an xsl:variable is treated as a generic "sequence constructor" which can give you new nodes if you construct or copy them:
<xsl:variable name="example" as="node()*">
<xsl:copy-of select="//entry" />
</xsl:variable>
or the original nodes if you use xsl:sequence:
<xsl:variable name="example" as="node()*">
<xsl:sequence select="//entry" />
</xsl:variable>
I wish to construct an XSL node set variable using a contained
for-each loop.
I have no idea what that means.
It is important that the constructed node set is the original (a
selected) node set, not a copy.
This part I think I understand a little better. It seems you need to replace:
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
with:
<xsl:variable name="entries" select="//entry"/>
or, preferably:
<xsl:variable name="entries" select="root/entry"/>
The resulting variable is a node-set of the original entry nodes, so you can do simply:
<xsl:for-each select="$entries">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
to get your expected result.
Of course, you could do the same thing by operating directly on the original nodes, in their original context - without requiring the variable.
In response to the comments you've made:
We obviously need a better example here, but I think I am getting a vague idea of where you want to go with this. But there are a few things you must understand first:
1.
In order to construct a variable which contains a node-set of nodes in their original context, you must use select. This does not place any limits whatsoever on what you can select. You can do your selection all at once, or in stages, or even in a loop (here I mean a real loop). You can combine the intermediate selections you have made in any way sets can be combined: union, intersection, or difference. But you must use select in all these steps, otherwise you will end up with a set of new nodes, no longer having the context they did in the source tree.
IOW, the only difference between using copy and select is that the former creates new nodes, which is precisely what you wish to avoid.
2.
xsl:for-each is not a loop. It has no hierarchy or chronology. All the nodes are processed in parallel, and there is no way to use the result of previous iteration in the current one - because no iteration is "previous" to another.
If you try to use xsl:for-each in order to add each of n processed nodes to a pre-existing node-set, you will end up with n results, each containing the pre-existing node-set joined with one of the processed nodes.
3.
I think you'll find the XPath language is quite powerful, and allows you to select the nodes you want without having to go through the complicated loops you hint at.
It might help if you showed us a problem that can't be trivially solved in XSLT 1.0. You can't solve your problem the way you are asking for: there is no equivalent of xsl:sequence in XSLT 1.0. But the problem you have shown us can be solved without such a construct. So please explain why you need what you are asking for.

Building an xslt variable with elements from multiple files

I'm no XSLT guru and couldn't find a similar example online. I'd like to assemble a list of apps from multiple files into a single var that can be used for searching.
Basically when I replace the original variable decleration with the new one, XSLT doesn't like it. I did output the variable contents to a file and they are identical in formatting of the XML elements so it must be failing around some metadata linked to the variable somewhere.
XML element format in all the files
<include-application name="appname" type="blah"/>
Orignal variable
<xsl:variable name="applications" select="board/packaging/*/include-application"/>
New variable definition
<xsl:variable name="applications">
<xsl:copy-of select="board/packaging/*/include-application"/>
<xsl:for-each select="board/packaging/applications/include">
<xsl:variable name="appset" as="xs:string" select="#name"/>
<xsl:variable name="includefile" as="xs:string" select="concat('../share/appsets/', $appset, '.xml')"/>
<xsl:copy-of select="document($includefile)/applications/include-application"/>
</xsl:for-each>
</xsl:variable>
Then when I try to access the elements to pick something, it fails with the new variable definition (line 39 is the first one in block below).
<xsl:variable name="type" select="$applications[#name = $appname]/#type"/>
<xsl:variable name="appid" select="$app-names/application-package-name[#name = $appname]/appid[#type = $type]/#value"/>
XPath error : Invalid type
runtime error: file xslt/blah.xslt line 39 element variable
Failed to evaluate the expression of variable 'type'.
Thanks
David
Since you are using XSLT 2.0, as evidenced by your use of as=, you need to declare the memory organization of your variable so as to act on it later. I think all you need to do is simply change the first line of your variable to be:
<xsl:variable name="applications" as="element(include-application)*">
...
That will tell the processor not to build a tree, but rather, to build a node set.
There is helpful information and a diagram on page 223 of my XSLT book that is available for free download on a "try and buy" basis at http://www.CraneSoftwrights.com/training/#ptux ... if you decide not to pay for the book, please delete the copy that you download for free.
The problem should be that the variable contains a XML fragment instead than a node-set (see this for example) - different XSLT processor have different extension functions to do the conversion - for example using a Microsoft processor you would do:
<xsl:variable name="type" select="msxsl:node-set($applications)[#name = $appname]/#type"/>
other processors have different functions (or the same function in a different namespace)
Using a combination of the above answers and looking into it more deeply, I used the exslt extension for converting to a node set and it works flawlessly now.
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common">
...
<xsl:variable name="applications" as="element(include-application)*">
...
<xsl:variable name="type" select="exslt:node-set($applications)/include-application[#name = $appname]/#type"/>

Indirect variable/parameter reference (name in another property / another variable)

Is it possible using XSL to access a variable (or a parameter) whose name is stored in another variable (or parameter)? If no, why?
I am new to xsl, coming from other languages, where this functionality is accessible, like bash, ant. Maybe I was wrong even looking for an answer to this question. But since I didn't find it on SO, I think there should be one.
Two examples. I have parameters p1, p2, p3. Then I have a parameter pname whose value is a string p2. I would like to read the value of p2 using pname, something like $$pname or ${$pname}. Or in a more complicated way. If pnumber is equal to 2, then I would like to read the value of the parameter with name concat('p', $pnumber), something I would code asparam-value(concat('p', $pnumber)).
This is possible whenthe XSLT stylesheet accesses itself as a regular XML document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="p1" select="'P1-Value'"/>
<xsl:param name="p2" select="'P2-Value'"/>
<xsl:param name="p3" select="'P3-Value'"/>
<xsl:param name="pName" select="'p3'"/>
<xsl:param name="pNumber" select="2"/>
<xsl:variable name="vDoc" select="document('')"/>
<xsl:template match="/">
<xsl:value-of select=
"concat('Param with name ',
$pName,
' has value: ',
$vDoc/*/xsl:param[#name = $pName]/#select
)"/>
<xsl:text>
</xsl:text>
<xsl:variable name="vParam" select=
"$vDoc/*/xsl:param[#name = concat('p', $pNumber)]"/>
<xsl:value-of select=
"concat('Param with name p',
$pNumber,
' has value: ',
$vParam/#select
)"/>
</xsl:template>
</xsl:stylesheet>
produces the wanted result:
Param with name p3 has value: 'P3-Value'
Param with name p2 has value: 'P2-Value'
Explanation:
The expression document('') selects the document node of the current XSLT stylesheet.
A limitation is that the current XSLT stylesheet must have (be accessible via) a URI (such as residing at a given file and accessible by its filename) -- the above code doesn't produce a correct result if the stylesheet is dynamically generated (a string in memory).
In libxslt the thing is possible through dyn:evaluate extension. Here is the description. There is total of 3 processors mentioned which are said to support this function:
Xalan-J from Apache (version 2.4.1) and
4XSLT, from 4Suite. (version 0.12.0a3)
libxslt from Daniel Veillard et al. (version 1.0.19)
A portable workaround. If you control both the application and the stylesheet, you should pass the parameters as an xml document. Most processors give the option to make parameter a node-set. For example in MSXML I did it using:
xslProc.addParameter("params", xmlParams)
where xslProc is of processor type, created from "Msxml2.XSLTemplate.6.0" using createProcessor method and xmlParams is DomDocument. Inside the stylesheet I was accesing my parameters using something like that:
<xsl:variable name="value">
<xsl:value-of select="$params//*[name() = concat('p', $pnumber)]" />
</xsl:variable>
If the processor does not support node-set external parameters, one may always combine the parameters with the data in one xml document. This works well in memory. If access to external files is possible, one may use document('params.xml') syntax to access the parameters stored in a separate file.
I was also looking for a possibility to parse xml string and have a node-set of it, but it is seems to be available only as an extension in some xslt 2.0 parsers. I wanted 1.0 solution.

Using dynamic xpath in XSLT

I am using a variable say xpathvar in the XSLT whose value will be supplied at the time of call to XSLT. How can i achieve this?
My XSLT file looks like -
<xsl:param name="xpathvar"/>
<xsl:param name="keyxpath"/>
<xsl:template match="/">
<listofResults>
<xsl:for-each select="$xpathvar">
<keyvalues><xsl:value-of select="xalan:evaluate(substring-before($keyxpath,'||'))"/></keyvalues>
<keyvalues><xsl:value-of select="xalan:evaluate(substring-after($keyxpath,'||'))"/></keyvalues>
</xsl:for-each>
</listofResults>
</xsl:template>
</xsl:stylesheet>
If I mention the variable in the for-each, it throws error. Please guide how can I achieve this.
Thanks!
According to Global xsl:param containing xpath expression string it can't be done purely with plain old XSLT, you need to use the evaluate extension see: Xalan evaluate expression
I dont think this can be done like this you must use xpath so example would be
<template match="Node[name=$xpathvar]" />
<xsl:for-each select="*[name()=$xpathvar]">
</xsl:for-each>
I think it does not let you use the variable directly because it is not a nodeset.
If I mention the variable in the
for-each, it throws error
In XSLT 1.0 (it looks you are using Xalan), you can iterate over node sets, only. So $xpathvar should be an instance of node set data type.
In XSLT 2.0 you can iterate over sequence (including scalar values).
Also, if the string containing the "dynamic" XPath expression is simple enough (only QName test step, maybe positional predicates) this could be done (as is already answered in SO) with standar XSLT.

XSL Reuse? YES! But: Element must not contain an xsl:import element! :-(

I am using a heavy stylesheet with a lot of recurring transformations, so I thought it would be smart to reuse the same chunks of code, so I would not need to make the same changes at a bunch of different places. So I discovered , but -alas- it won't allow me to do it. When trying to run it in Sonic Workbench I get the following error:
An xsl:for-each element must not contain an xsl:import element
This is my stylesheet code:
<xsl:template match="/">
<InboundFargoMessage>
<EdiSender>
<xsl:value-of select="TransportInformationMessage/SenderId"/>
</EdiSender>
<EdiReceiver>
<xsl:value-of select="TransportInformationMessage/RecipientId"/>
</EdiReceiver>
<EdiSource>PORLOGIS</EdiSource>
<EdiDestination>FARGO</EdiDestination>
<Transportations>
<xsl:for-each select="TransportInformationMessage/TransportUnits/TransportUnit">
<xsl:import href="TransportCDMtoFDM_V0.6.xsl"/>
</xsl:for-each>
<xsl:for-each select="TransportInformationMessage/Waybill/TransportUnits/TransportUnit">
<xsl:import href="TransportCDMtoFDM_V0.6.xsl"/>
</xsl:for-each>
</Transportations>
</InboundFargoMessage>
</xsl:template>
</xsl:stylesheet>
I will leave out the child xsl-sheets for now, as the problem appears to be happening at the base.
If I cannot use xsl:import, is there any option of reuse?
If I cannot use xsl:import, is there
any option of reuse?
You can use <xsl:import>.
All <xsl:import> elements must be the first element children of <xsl:stylesheet>
As an alternative, an <xsl:include> element has to be globally defined (a child of <xsl:stylesheet>) but can be preceded by any other xslt instruction that can be placed globally.
You need to be aware of and understand well the rules of using these two XSLT instructions. I'd recommend reading a good book on XSLT.
The main unit of reusability in XSLT is the template (<xsl:template>).
The importing stylesheet can use (via <xsl:call-template> or <xsl:apply-templates>) any template that is defined in any imported stylesheet.
Each of included XSL files should contain a template(s).
The main file includes the others in the beginning and then calls the templates with call-template or apply-templates from various places.
Thanks for all the suggestions, which were somewhat helpful, but allow me formulate a complete answer. As suggested, the answer to the question of re-use lies in the xsl:templates. Templates can be defined by enclosing them within . Then, whereever necessary, they can be summoned by adding a element. Also, they can be put into separate xsl sheets, as long as they are imported at the top of the parent xsl sheet.
Thus, the solution to my questions looks as follows:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:far="http://www.itella.com/fargo/fargogate/" xmlns:a="http://tempuri.org/XMLSchema.xsd" xmlns:p="http://tempuri.org/XMLSchema.xsd">
<xsl:import href="TransportCDMtoFDM_V0.6.xsl"/>
<xsl:template match="/">
<InboundFargoMessage>
<EdiSender>
<xsl:value-of select="TransportInformationMessage/SenderId"/>
</EdiSender>
<EdiReceiver>
<xsl:value-of select="TransportInformationMessage/RecipientId"/>
</EdiReceiver>
<EdiSource>PORLOGIS</EdiSource>
<EdiDestination>FARGO</EdiDestination>
<Transportations>
<xsl:for-each select="TransportInformationMessage/TransportUnits/TransportUnit">
<xsl:call-template name="transport"/>
</xsl:for-each>
<xsl:for-each select="TransportInformationMessage/Waybill/TransportUnits/TransportUnit">
<xsl:call-template name="transport"/>
</xsl:for-each>
</Transportations>
</InboundFargoMessage>
</xsl:template>
Where the file 'TransportCDMtoFDM_V0.6.xsl' contains the template called "transport".
There is just one problem left: Using templates, all the nodes mentioned within the template are used, even if they are empty. So the remaining question is how to leave out the empty nodes?