I have an xml as follows
<feed>
<entry>
<id>4</id>
<updated>2012-11-18T16:55:54Z</updated>
<title>ASSIGNED</title>
</entry>
<entry>
<id>3</id>
<updated>2011-01-16T16:55:54Z</updated>
<title>ASSIGNED</title>
</entry>
<entry>
<id>2</id>
<updated>2014-12-01T16:55:54Z</updated>
<title>EXPIRED</title>
</entry>
<entry>
<id>1</id>
<updated>2013-01-12T16:55:54Z</updated>
<title>COMPLETED</title>
</entry>
<entry>
<id>1</id>
<updated>2012-01-09T16:55:54Z</updated>
<title>ASSIGNED</title>
</entry>
<entry>
<id>1</id>
<updated>2011-04-18T16:55:54Z</updated>
<title>COMPLETED</title>
</entry>
</feed>
I want to sort by with ASSIGNED first, then followed by EXPIRED, and then COMPLETED.
If there are more than one entries in each of these categories, I would like to sort by updated value descending.
I can sort by updated descending using xsl:sort, but how do I sort based on a set of strings {ASSIGNED, EXPIRED, COMPLETED} in an order
Appreciate your response!
You can use a translate in the xsl:sort line to convert the first character of the strings "ASSIGNED", "EXPIRED", and "COMPLETED" into simple "1", "2", "3". Since the first characters of your strings are unique, that's all that it takes; it would be harder if there were two strings starting with an "A".
The following example forces a hardcoded <feed> (as the template match itself removes it) and uses an Identity Transform for all other elements.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/feed">
<feed>
<xsl:apply-templates select="entry">
<xsl:sort select="translate (title, 'AaEeCc', '112233')" />
<xsl:sort select="updated" />
</xsl:apply-templates>
</feed>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Related
TL;DR; Why can't I use the element name in the XPATH going against a msxsl:node-set? It always returns nothing, as if the node-set is empty, when debugging shows that it is not empty.
Details: I need to use a node-set in an XSLT 1.0 document because my source XML is missing an important node. Instead of having to rewrite the entire XSLT, I'd like to instead inject a node-set so that my XSLT processing can continue as normal. I would like to use XPATH on the node-set but I am not able to use the actual element names, instead only a * works, but I am not sure why, or how I can access the actual element names in the XPATH.
Here is my XML (example only, the XML document here is the least important, see XSLT):
<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="generic.xslt" ?>
<ParentNode xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" i:noNamespaceSchemaLocation="generic.xsd">
<SomeChildNode>text</SomeChildNode>
</ParentNode>
Here is my XSLT:
<?xml version="1.0" encoding="utf-16"?>
<xsl:stylesheet version="1.0" xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:a="http://schemas.datacontract.org/2004/07/MeM.BizEntities" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" indent="yes" encoding="utf-16" omit-xml-declaration="no" />
<!-- Global Variables, used in multiple places -->
<xsl:variable name="empty"/>
<!-- Match Templates -->
<xsl:template match="ParentNode">
<ArrayOfSalesOrder>
<xsl:for-each select="SomeChildNode">
<xsl:call-template name="SomeChildNodeTemplate">
<xsl:with-param name="order" select="."/>
</xsl:call-template>
</xsl:for-each>
</ArrayOfSalesOrder>
</xsl:template>
<xsl:template name="SomeChildNodeTemplate">
<xsl:variable name="someRTF">
<Items>
<Item>
<Code>code</Code>
<Price>75</Price>
<Quantity>1</Quantity>
</Item>
<Item>
<Code>code2</Code>
<Price>100</Price>
<Quantity>3</Quantity>
</Item>
</Items>
</xsl:variable>
<xsl:call-template name="ItemsTemplate">
<xsl:with-param name="items" select="msxsl:node-set($someRTF)"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="ItemsTemplate">
<xsl:param name="items"/>
<ItemsTransformed>
<xsl:for-each select="$items/Item">
<NewItem>
<NewCode>
<xsl:value-of select="Code"/>
</NewCode>
</NewItem>
</xsl:for-each>
</ItemsTransformed>
<ItemsTransformedThatWorksButNotHowIWant>
<xsl:for-each select="$items/*/*">
<NewItem>
<NewCode>
<xsl:value-of select="*[1]"/>
</NewCode>
<NewPrice>
<xsl:value-of select="*[2]"/>
</NewPrice>
<NewQuantity>
<xsl:value-of select="*[3]"/>
</NewQuantity>
</NewItem>
</xsl:for-each>
</ItemsTransformedThatWorksButNotHowIWant>
</xsl:template>
</xsl:stylesheet>
I would expect to be able to use XPATH to query into the node-set such that I can use their proper element names. This doesn't seem to be the case, and I'm struggling to understand why. I know there can be namespacing issues, but trying *:Item etc. doesn't work for me. I am able to use *[local-name()='Item'] but this seems like a horrible work around, not to mention that I'll have to rewrite any downstream templates and that is what I'm trying to avoid by using the node-set in the first place.
Result:
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfSalesOrder xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:a="http://schemas.datacontract.org/2004/07/MeM.BizEntities" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ItemsTransformed />
<ItemsTransformedThatWorksButNotHowIWant>
<NewItem>
<NewCode>code</NewCode>
<NewPrice>75</NewPrice>
<NewQuantity>1</NewQuantity>
</NewItem>
<NewItem>
<NewCode>code2</NewCode>
<NewPrice>100</NewPrice>
<NewQuantity>3</NewQuantity>
</NewItem>
</ItemsTransformedThatWorksButNotHowIWant>
</ArrayOfSalesOrder>
As you can see, I can get it to work with * but this is not very usable on a more complex structure. What am I doing wrong? Does this have to do with namespaces?
I would expect to see something under the <ItemsTransformed /> node, but instead it is just empty, and so far I can't get anything except the * to work.
The SO question below is what I was using, I thought I had an answer there, but I can't get the XPATH to work.
Reference:
XSLT 1.0 - Create node set and pass as a parameter
The problem here is that your stylesheet has a default namespace:
xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2"
Therefore, when you do:
<xsl:variable name="someRTF">
<Items>
<Item>
<Code>code</Code>
<Price>75</Price>
<Quantity>1</Quantity>
</Item>
<Item>
<Code>code2</Code>
<Price>100</Price>
<Quantity>3</Quantity>
</Item>
</Items>
</xsl:variable>
you are populating your variable with elements in the default namespace, so the variable actually contains:
<Items xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2">
<Item>
<Code>code</Code>
<Price>75</Price>
<Quantity>1</Quantity>
</Item>
<Item>
<Code>code2</Code>
<Price>100</Price>
<Quantity>3</Quantity>
</Item>
</Items>
Naturally, when you try later to select something like:
<xsl:for-each select="xyz:node-set($someRTF)/Items/Item">
you select nothing, because both Items and Item are in the default namespace and you're not calling them by their fully qualified name.
--- edit: ---
The problem can be easily solved by making sure that the root element of the variable - and by extension, all its descendants - are in no namespace.
Here's a simplified example (will run with any input):
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="someRTF">
<Items xmlns="">
<Item>
<Code>code</Code>
<Price>75</Price>
<Quantity>1</Quantity>
</Item>
<Item>
<Code>code2</Code>
<Price>100</Price>
<Quantity>3</Quantity>
</Item>
</Items>
</xsl:variable>
<xsl:template match="/">
<ArrayOfSalesOrder>
<ItemsTransformed>
<xsl:for-each select="exsl:node-set($someRTF)/Items/Item">
<NewItem>
<NewCode>
<xsl:value-of select="Code"/>
</NewCode>
</NewItem>
</xsl:for-each>
</ItemsTransformed>
</ArrayOfSalesOrder>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<ArrayOfSalesOrder xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2">
<ItemsTransformed>
<NewItem>
<NewCode>code</NewCode>
</NewItem>
<NewItem>
<NewCode>code2</NewCode>
</NewItem>
</ItemsTransformed>
</ArrayOfSalesOrder>
In the below XML, need to replace the namespace by using XPath.
<application xmlns="http://ns.adobe.com/air/application/4.0">
<child id="1"></child>
<child id="2"></child>
</application>
I tried with
/application/#xmlns
and
/*[local-name()='application']/#[local-name()='xmlns']
Both failed to give the desire output. To replace the text, I have used xmltask replace.
<xmltask source="${temp.file1}" dest="${temp.file1}">
<replace path="/application/#xmlns" withText="http://ns.adobe.com/air/application/16.0" />
</xmltask>
The problem is that xmlns is not an attribute. You cannot select it with XPath.
A namespace is part of the node name in XML: <foo xmlns="urn:foo-namespace" /> and <foo xmlns="urn:bar-namespace" /> are not two nodes with the same name and different attributes, they are two nodes with different names and no attributes.
If you want to change a namespace, you must construct a completely new node.
XSLT is better-suited to this task:
<!-- update-air-ns.xsl -->
<xsl:transform
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:air4="http://ns.adobe.com/air/application/4.0"
xmlns="http://ns.adobe.com/air/application/16.0"
>
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="air4:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
</xsl:transform>
This XSLT transformation does two things:
the first template (identity template) copies nodes recursively, unless there is a better matching template for a given node
the second template matches elements in the air4 namespace and constructs new elements that have the same local name but a different namespace. This happens because of the default namespace declaration in the XSLT. The http://ns.adobe.com/air/application/16.0 namespace is used for all newly constructed elements.
Applied to your input XML, the result is
<application xmlns="http://ns.adobe.com/air/application/16.0">
<child id="1"/>
<child id="2"/>
</application>
You can use Ant's xslt task:
<xslt in="${temp.file1}" out="${temp.file1}" style="update-air-ns.xsl" />
I have a list of order lines with each one product on them. The products in may form a self-referencing hierarchy. I need to order the lines in such a way that all products that have no parent or whose parent is missing from the order are at the top, followed by their children. No child may be above its parent in the end result.
So how can i order the following xml:
<order>
<line><product code="3" parent="1"/></line>
<line><product code="2" parent="1"/></line>
<line><product code="6" parent="X"/></line>
<line><product code="1" /></line>
<line><product code="4" parent="2"/></line>
</order>
Into this:
<order>
<line><product code="6" parent="X"/></line>
<line><product code="1" /></line>
<line><product code="2" parent="1"/></line>
<line><product code="3" parent="1"/></line>
<line><product code="4" parent="2"/></line>
</order>
Note that the order within a specific level is not important, as long as the child node follows at some point after it's parent.
I have a solution which works for hierarchies that do not exceed a predefined depth:
<order>
<xsl:variable name="level-0"
select="/order/line[ not(product/#parent=../line/product/#code) ]"/>
<xsl:for-each select="$level-0">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:variable name="level-1"
select="/order/line[ product/#parent=$level-0/product/#code ]"/>
<xsl:for-each select="$level-1">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:variable name="level-2"
select="/order/line[ product/#parent=$level-1/product/#code ]"/>
<xsl:for-each select="$level-2">
<xsl:copy-of select="."/>
</xsl:for-each>
</order>
The above sample xslt will work for hierarchies with a maximum depth of 3 levels and is easily extended to more, but how can i generalize this and have the xslt sort arbitrary levels of depth correctly?
To start with, you could define a couple of keys to help you look up the line elements by either their code or parent attribute
<xsl:key name="products-by-parent" match="line" use="product/#parent" />
<xsl:key name="products-by-code" match="line" use="product/#code" />
You would start off by selecting the line elements with no parent, using a key to do this check:
<xsl:apply-templates select="line[not(key('products-by-code', product/#parent))]"/>
Then, within the template that matches the line element, you would just copy the element, and then select its "children" like so, using the other key
<xsl:apply-templates select="key('products-by-parent', product/#code)"/>
This would be a recursive call, so it would recursively look for its children until no more are found.
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="products-by-parent" match="line" use="product/#parent"/>
<xsl:key name="products-by-code" match="line" use="product/#code"/>
<xsl:template match="order">
<xsl:copy>
<xsl:apply-templates select="line[not(key('products-by-code', product/#parent))]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="line">
<xsl:call-template name="identity"/>
<xsl:apply-templates select="key('products-by-parent', product/#code)"/>
</xsl:template>
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Do note the use of the XSLT identity transform to copy the existing nodes in the XML.
Very interesting problem. I would do this in two passes: first, nest the elements according to their hierarchy. Then output the elements, sorted by the count of their ancestors.
XSLT 1.0 (+ EXSLT node-set() function):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="product-by-code" match="product" use="#code" />
<!-- first pass -->
<xsl:variable name="nested">
<xsl:apply-templates select="/order/line/product[not(key('product-by-code', #parent))]" mode="nest"/>
</xsl:variable>
<xsl:template match="product" mode="nest">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="../../line/product[#parent=current()/#code]" mode="nest"/>
</xsl:copy>
</xsl:template>
<!-- output -->
<xsl:template match="/order">
<xsl:copy>
<xsl:for-each select="exsl:node-set($nested)//product">
<xsl:sort select="count(ancestor::*)" data-type="number" order="ascending"/>
<line><product><xsl:copy-of select="#*"/></product></line>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your input, the result is:
<?xml version="1.0" encoding="UTF-8"?>
<order>
<line>
<product code="6" parent="X"/>
</line>
<line>
<product code="1"/>
</line>
<line>
<product code="3" parent="1"/>
</line>
<line>
<product code="2" parent="1"/>
</line>
<line>
<product code="4" parent="2"/>
</line>
</order>
This still leaves the issue of the existing/missing parent X - I will try to address that later.
i have some requirement, where i need to replace the references in the same xml file.
limitation is to use xslt 1.0 only.
below is my sample input xml.
<org>
<depts>
<dept>
<deptId>1009</deptId>
<deptName>IT</deptName>
<deptAccessCode>IT-1009</deptAccessCode>
</dept>
<dept>
<deptId>2344</deptId>
<deptName>BPO</deptName>
<deptAccessCode>BP-2344</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>abc</name>
<dept>
<REFERENCE>
<LocationXPath>/org/depts/dept[2]</LocationXPath>
</REFERENCE>
</dept>
<employee>
</employees>
</org>
now i want to replace the node REFERENCE with actual data at the XPath /org/depts/dept[2].
so the output xml should be like below.
<org>
<depts>
<dept>
<deptId>1009</deptId>
<deptName>IT</deptName>
<deptAccessCode>IT-1009</deptAccessCode>
</dept>
<dept>
<deptId>2344</deptId>
<deptName>BPO</deptName>
<deptAccessCode>BP-2344</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>abc</name>
<dept>
<deptId>2344</deptId>
<deptName>BPO</deptName>
<deptAccessCode>BP-2344</deptAccessCode>
</dept>
<employee>
</employees>
</org>
i have several REFERENCE nodes in different elements referencing to different xpaths across the xml tree, which i need to replace them with actual data.
<someWhereInTheXmlTree>
<sometag>
<REFERENCE>
<LocationXPath>some/reference[1]/to/a/node[3]/in/the[4]/same/xml</LocationXPath>
</REFERENCE>
</sometag>
<someWhereInTheXmlTree>
...
<ffff>
<bbbb>
<REFERENCE>
<LocationXPath>abc/xyz[1]/node[4]/element</LocationXPath>
</REFERENCE>
</bbbb>
<ffff>
please help me on this.
Thanks in advance for the help.
So far i have implemented one XSLT to replace the references but now i am facing unwanted empty name spaces.
Here is my XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tib="http://www.tibco.com/bw/xslt/custom-functions"
xmlns="http://www.realestate.org/residential/2010/schemas" >
<xsl:output omit-xml-declaration="no" indent="yes" method = "xml" />
<xsl:strip-space elements="*"/>
<xsl:param name="myxml" />
<xsl:template match="node()|#*">
<xsl:param name="isNodeToReplace"><xsl:call-template name="ReferenceCheck" /></xsl:param>
<xsl:choose>
<xsl:when test="$isNodeToReplace='true'">
<xsl:call-template name="replaceWithData">
<xsl:with-param name="ref"><xsl:value-of select="." /></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="ReferenceCheck">
<xsl:choose>
<xsl:when test="name(child::*[1])='REFERENCE' and name(child::*[1]//child::*[1])='LocationXPath'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replaceWithData">
<xsl:param name="ref" />
<xsl:copy-of select="tib:evaluate($myxml,$ref)" />
</xsl:template>
</xsl:stylesheet>
in the above XSLT i am passing the entire xml (same xml, which is being processed) as a parameter $myxml
Below is my sample input XML --this is just a snippet of xml,The actual xml file which i am dealing with is too large and contains so complex tree structure.How ever this sample xml is suffice enough to produce my problem.
Input file
<?xml version="1.0" encoding="UTF-8"?>
<org xmlns="http://www.realestate.org/residential/2010/schemas">
<depts>
<dept>
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>TOM</name>
<dept>
<REFERENCE>
<LocationXPath>/org/depts/dept[1]</LocationXPath>
</REFERENCE>
</dept>
</employee>
</employees>
</org>
my output file
<?xml version="1.0" encoding="UTF-8"?>
<org xmlns="http://www.realestate.org/residential/2010/schemas">
<depts>
<dept>
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>TOM</name>
<dept xmlns="">
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</employee>
</employees>
</org>
Where as Expected output
<?xml version="1.0" encoding="UTF-8"?>
<org xmlns="http://www.realestate.org/residential/2010/schemas">
<depts>
<dept>
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>TOM</name>
<dept>
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</employee>
</employees>
</org>
so i am getting unwanted empty name space in << dept xmlns="">> in the replaced root element.
Hope this could clearly explain my problem
Thanks in Advance
ultimately i have found the solution at the link below to remove the unwanted empty name spaces.
http://social.msdn.microsoft.com/forums/en-US/xmlandnetfx/thread/0de59291-ef3a-4a4c-9ca5-17923b16a504
Here is the new XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tib="http://www.tibco.com/bw/xslt/custom-functions"
xmlns="http://www.realestate.org/residential/2010/schemas" >
<xsl:output omit-xml-declaration="no" indent="yes" method = "xml" />
<xsl:strip-space elements="*"/>
<xsl:param name="myxml" />
<xsl:template match="node()|#*">
<xsl:param name="isNodeToReplace"><xsl:call-template name="ReferenceCheck" /></xsl:param>
<xsl:choose>
<xsl:when test="$isNodeToReplace='true'">
<xsl:call-template name="replaceWithData">
<xsl:with-param name="ref"><xsl:value-of select="." /></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="ReferenceCheck">
<xsl:choose>
<xsl:when test="name(child::*[1])='REFERENCE' and name(child::*[1]//child::*[1])='LocationXPath'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replaceWithData">
<xsl:param name="ref" />
<xsl:apply-templates select="tib:evaluate($myxml,$ref)" mode="move-to-namespace">
<xsl:with-param name="namespace" select="'http://www.realestate.org/residential/2010/schemas'" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" mode="move-to-namespace">
<xsl:param name="namespace" />
<xsl:element name="{local-name()}" namespace="{$namespace}">
<xsl:copy-of select="#*" />
<xsl:apply-templates select="node()" mode="move-to-namespace">
<xsl:with-param name="namespace" select="$namespace"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="text() | comment() | processing-instruction()" mode="move-to-namespace">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
if any xslt expert refines it further to avoid any unnecessary instruction with proper explanation its very glad.
Thanks in advance
In general:
Not posiible in pure XSLT 1.0.
Not possible in pure XSLT 2.0
May be possible in pure XSLT 3.0 -- read about the
<xsl:evaluate> instruction.
In XSLT 1.0 you may be lucky if your XSLT processor implements the EXSLT dyn:evaluate() extension function (a few do).
Otherwize, you will have to write an extension function to select the
nodes and return them back.
If there are restrictions on the syntax of the XPath expressions, then it may be possible to implement a pure XSLT 1.0 solution.
This is not possible in pure XSLT 1.0. You have to use extension function, e.g. Xalan evaluate expression
At some point in an XSLT program, I have the following:
<xsl:for-each select="tags/tag">
<xsl:apply-templates select="//shows/show[film=//films/film[tag=current()/#id]/#id]|//shows/show[group=//groups/group[film=//films/film[tag=current()/#id]/#id]/#id]">
<xsl:sort select="date" data-type="text" order="ascending"/>
<xsl:sort select="time" data-type="text" order="ascending"/>
</xsl:apply-templates>
</xsl:for-each>
It seems that the XPath expression //shows/show[film=//films/film[tag=current()/#id]/#id]|//shows/show[group=//groups/group[film=//films/film[tag=current()/#id]/#id]/#id], which is rather complex, considerably slows down the execution of the program (compared to the execution time before adding the quoted piece of code -- processing the same data, of course).
Do you think this is normal due to the relatively complex nature of the expression, and do you see how I could improve it so it performs better?
NB: in the XPath expression, film and //films/film, group and //groups/group refer to distinct elements.
See below a stripped-down sample of the XML input.
<program>
<tags>
<tag id="1">Tag1</tag>
<tag id="2">Tag2</tag>
<tag id="3">Tag3</tag>
</tags>
<films>
<film id="1">
Film1
<tag>2</tag><!-- References: /program/tags/tag/#id=2 -->
</film>
<film id="2">
Film2
<tag>1</tag><!-- References: /program/tags/tag/#id=1 -->
</film>
<film id="3">
Film3
<tag>3</tag><!-- References: /program/tags/tag/#id=3 -->
</film>
<film id="4">
Film4
<tag>3</tag><!-- References: /program/tags/tag/#id=3 -->
</film>
</film>
<groups>
<group id="1">
<film>3</film><!-- References: /program/films/film/#id=3 -->
<film>4</film><!-- References: /program/films/film/#id=4 -->
</group>
</groups>
<shows>
<show id="1"><!-- Show with film (=simple) -->
<film>1</film><!-- References: /program/films/film/#id=1 -->
<date>2011-12-12</date>
<time>12:00</time>
</show>
<show id="2"><!-- Show with group (=combined) -->
<group>1</group><!-- References: /program/groups/group/#id=1 -->
<date>2011-12-12</date>
<time>14:00</time>
</show>
</shows>
</program>
Explanations:
A tag is a property attached to a film (in fact, it's rather a category).
A group is an enumeration of films.
A show references either a film or a group.
What I want: for each tag, I'm looking for the shows referencing a film having the current tag and the shows referencing a group where at least one of the films has the current tag.
Double slashes in XPath are performance and CPU hogs when working with large documents (since every node in the document must be evaluated). If you can replace it with either an absolute or relative path you should have a noticeable improvement. If you can post the input schema and required output, we could be more specific?
e.g. With an absolute path
//shows/show[film=//films/film[tag=current()/#id]/#id]
becomes
/myroot/somepath/shows/show[film=/myroot/somepath/films/film[tag=current()/#id]/#id]
or if the shows and films are relative to the current node
./relativexpath/shows/show[film=./relativexpath/somepath/films/film[tag=current()/#id]/#id]
The answer by nonnb very likely points to the problem, however not really to an efficient solution ("cheaper" axis are better, but that alone doesn't make the speed such as when indexing data).
Note that the big problem is that the XPath expression predicate does another full traversal of the tree for each evaluation. You should use keys for stuff like this; this will (in most or even all XSLT implementations) make an indexed lookup possible, thereby reducing the runtime a lot.
Define keys for the films, groups and shows by id:
<xsl:key name="filmByTag" match="film" use="tag" />
<xsl:key name="groupsByFilm" match="group" use="tag" />
<xsl:key name="showsByFilm" match="show" use="film" />
<xsl:key name="showsByGroup" match="show" use="group" />
And then use it like this (not tested, but you should get the idea):
<xsl:variable name="films" select="key('filmByTag', #id)/#id" />
<xsl:apply-templates select="key('showsByFilm', $films)/#id|key('showsByGroups', key('groupsByFilm', $films)/#id)/#id">
Your XPath expression seems to be doing a three-way join so unless it's optimized the performance is likely to be O(n^3) in the size of the source document. Optimization involves replacing the serial searches of the document by indexed lookups. There are two ways of achieving this: you can hand-optimize it by replacing the filter expressions with calls on the key() function (as indicated by Dimitre), or you can use an optimizing XSLT processor such as Saxon-EE, which should do the same optimizations automatically.
Define a key with xsl:key and then use the key function for the cross reference instead of that comparison you currently have. Show us a sample of the XML so that we can understand its structure, then we can help with concrete code.
Here are two complete solutions that should exhibit better performance:
Do note: Better performance will be registered on sufficiently large input samples only. On small input samples it isn't worth it to optimize.
I. Not using // (but not using keys)
<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:variable name="vFilms" select="/*/films/film"/>
<xsl:variable name="vShows" select="/*/shows/show"/>
<xsl:variable name="vGroups" select="/*/groups/group"/>
<xsl:variable name="vTags" select="/*/tags/tag"/>
<xsl:template match="/*">
<xsl:for-each select="$vTags">
<xsl:apply-templates select=
"$vShows
[film
=
$vFilms
[tag=current()/#id]
/#id
or
group
=
$vGroups
[film
=
$vFilms
[tag=current()/#id]
/#id
]
/#id
]
">
<xsl:sort select="date" data-type="text" order="ascending"/>
<xsl:sort select="time" data-type="text" order="ascending"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="show">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
II. Using keys
<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:key name="kShowByFilmId" match="show"
use="film"/>
<xsl:key name="kShowByGroupId" match="show"
use="group"/>
<xsl:key name="kGroupByFilmId" match="group"
use="film"/>
<xsl:key name="kFilmByTag" match="film"
use="tag"/>
<xsl:variable name="vTags" select="/*/tags/tag"/>
<xsl:template match="/*">
<xsl:for-each select="$vTags">
<xsl:apply-templates select=
"key('kShowByFilmId',
key('kFilmByTag', current()/#id)/#id
)
|
key('kShowByGroupId',
key('kGroupByFilmId',
key('kFilmByTag', current()/#id)/#id
)
/#id
)
">
<xsl:sort select="date" data-type="text" order="ascending"/>
<xsl:sort select="time" data-type="text" order="ascending"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="show">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>