How to use xslt to alter xml namespaces - xslt

I'm trying to use xslt to change namespaces an xml file. I was hoping to get something a bit closer than I have so far but I'm going around in circles so I thought I'd ask the question a little earlier than I ordinarily would...
My XML file is
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ns2:apple xmlns:ns2="http://veg.com/app/api/apple" xmlns:ns1="http://veg.com/app/api" xmlns:ns3="http://veg.com/app/api/apple/red"
xmlns:ns4="http://veg.com/app/banana" xmlns:ns5="http://veg.com/app/api/pear" xmlns:ns6="http://veg.com/app/api/orange"
ns1:created="2016-05-23T16:47:55+01:00" ns1:href="http://falseserver:8080/app/api/apple/1" ns1:id="1">
<ns2:name>granny smith</ns2:name>
<ns2:flavour>sweet</ns2:flavour>
<ns2:origin>southwest region</ns2:origin>
</ns2:apple>
The only part I want to change are the urls in the namespace atributes of the root element to (veg to fruit)
<ns2:apple xmlns:ns2="http://fruit.com/app/api/apple" xmlns:ns1="http://fruit.com/app/api" xmlns:ns3="http://fruit.com/app/api/apple/red"
xmlns:ns4="http://fruit.com/app/banana" xmlns:ns5="http://fruit.com/app/api/pear" xmlns:ns6="http://fruit.com/app/api/orange"
ns1:created="2016-05-23T16:47:55+01:00" ns1:href="http://falseserver:8080/app/api/apple/1" ns1:id="1">
I've tried a few things but have failed spectacularly so far. My last attempt was
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:element name="{local-name()}" namespace="http://fruit.com/app/api/apple">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
which has given me a ridiculous result -
<?xml version="1.0" encoding="UTF-8"?><ns0:apple xmlns:ns0="http://fruit.com/app/api/apple" ns1:created="2016-05-23T16:47:55+01:00" ns1:href="http://falseserver:8080/app/api/apple/1" ns1:id="1">
<ns1:name xmlns:ns1="http://fruit.com/app/api/apple">granny smith</ns1:name>
<ns2:flavour xmlns:ns2="http://fruit.com/app/api/apple">sweet</ns2:flavour>
<ns3:origin xmlns:ns3="http://fruit.com/app/api/apple">southwest region</ns3:origin>
</ns0:apple>
I think my first and biggest issue is trying to do the correct match. As you can see I have resorted to a * but this is out of desperation rather than because I think it's the right way to go! I'm also not sure why it drops all the other namespace attributes from the tag bu this also seems to happen quite consistently in all the ways I've tried so far. I've no idea how I've managed to get the new 'tag namespaces' in the document but I suspect if I could at least get the start of the xsl doc correct I'd be much closer to the answer...

In the code provided (your "spectacular failure") you have left the XSLT processor to think up new prefixes. When you don't specify a prefix in xsl:element, an XSLT 1.0 processor is expected to dream one up. This changes in XSLT 2.0, which is required to put the element in the default namespace. If you want to use the prefix ns2, then specify
<xsl:element name="ns2:{local-name()}" namespace="http://fruit.com/app/api/apple">
which will work in either release (except that in XSLT 1.0, processors have blanket permission to change namespace prefixes any way they want: but most processors don't do this in ordinary circumstances).
This will work for the namespaces that are actually used in element names. It will drop namespaces that are unused. Your example appears to retain some unused namespaces from the source document, but changing the prefix.
If you know that the root element of your document is always ns2:apple, then generate that element in the output using a literal result element with all the desired namespaces.
If you don't know the name of the root element in advance, then controlling its namespace declarations is quite hard to achieve in XSLT 1.0 (and rarely needed!). In XSLT 2.0 you can do it using the xsl:namespace instruction.

Related

XSLT Getting two different output for same input XML for same XSL

I have been trying my XSLT code in the online tool [XSLT 1.0 processor]:
http://www.freeformatter.com/xsl-transformer.html
Recently, I had to make use of xs:dateTime and hence started using the tool that uses XSLT 2.0 processor,
http://xsltransform.net/
Now, when i was trying to solve a problem, i see that i get different output for the same input XML in these two processors. The code posted here is not the real code i am working on; this is to simulate the weird output i faced.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<book>
<title></title>
</book>
<phone>apple</phone>
</items>
XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:template match="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<!-- ignore empty elements -->
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
<xsl:template match="phone">
<newphone><xsl:apply-templates /></newphone>
</xsl:template>
</xsl:stylesheet>
Output from http://xsltransform.net/ : [output 1]
<?xml version="1.0" encoding="UTF-8"?>
<items>
<newbook>
<newtitle/>
</newbook>
<newphone>apple</newphone>
</items>
Output from http://www.freeformatter.com/xsl-transformer.html : [output 2]
<?xml version="1.0" encoding="UTF-8"?>
<items>
<newphone>apple</newphone>
</items>
Expected output XML is output 2 .
Any idea why this different output behavior?
note: I looked at another SO question: Generating two different outputs for the same XSL file?, but that is different.
Edit
To add more clarity on which tool produced which output:
http://xsltransform.net/ produced output 1
http://www.freeformatter.com/xsl-transformer.html produced output 2
Update[02/10/2014] - regarding solution
Since the correct answer was first provided by Jim Garrison, that's marked as answer. However, there are other important points as well, as pointed out by others, Hence I am wrapping up all here.
<xsl:template match="*[not(normalize-space())]" />
The above template eliminates the empty nodes in both XSLT1 and XSLT2. Hence output 2 is correct
*Reason for getting output 1 from the tool - http://xsltransform.net/ :*
As #michael.hor257k pointed out, there are two templates that matches the <book> element.
The tool was using Saxon-HE 9.5.1.3, and the output 1 behavior was probably a bug that was fixed in the latest maintenance release [#Michael Kay explained well in his answer]
As #Ian Roberts mentioned in his answer, there is a concept of default priorities that are assigned to different types of templates.
Changing the template syntax to:
<xsl:template match="*[not(normalize-space())]" priority="2"/>
yields the same output in both the tools regardless of Saxon version, because we are explicitly defining the priority to the template.
Output 2 is correct. I ran your example in Oxygen/XML, using both XSLT1 and XSLT2 and got the correct results for both. Your template
<xsl:template match="*[not(normalize-space())]" />
eliminates the empty nodes in both XSLT1 and XSLT2.
Therefore the only possible conclusion is that the online tool that produced your Output 1 has a bug.
I get the correct result running with Saxon-EE 9.5.1.4; the xsltransform site is running Saxon-HE 9.5.1.3. It's probably a bug that we fixed in the latest maintenance release; I'll do some chasing to see if I can finger it.
Note that an empty <book> element matches two of your templates:
<xsl:template match="*[not(normalize-space())]" />
and:
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
The same thing applies to an empty <title> element. Such conflicts must be resolved by the XSLT processor, using the priorities specified in the XSLT specification.
The result you report from http://xsltransform.net/ is generated by Saxon 9.5.1 - an XSLT 2.0 engine. You will get a different result (on the same site) if you switch to an XSLT 1.0 engine (Saxon 6.5 or Xalan 2.7.1).
I don't know if the conflict resolution rules have changed in XSLT 2.0 compared to XSLT 1.0; I am getting a different result myself when using Saxon 8.9 - also an XSLT 2.0 processor. Perhaps we will be fortunate enough to hear from Michael Kay regarding this difference (I have added a Saxon tag to your question to get his attention)..
Output 2 is definitely the correct one according to both the 1.0 and 2.0 XSLT specs for the stylesheet you give in the question. However you say this is simplified when compared to your real stylesheet.
The thing that makes it work is the default priorities that are assigned to different types of templates. These are the same in both XSLT versions, and a match pattern that involves a predicate will overrule one that is just a single element name test.
However a match that involves hierarchy (e.g. match="items/book") or anything more complex than just a single element name will get the same priority as the *[....] rule by default - 0.5. If you've got any of these kinds of patterns in your real XSLT then you need to add an explicit priority higher than 0.5 to the "blank-suppressor" template.
<xsl:template match="*[not(normalize-space())]" priority="2"/>

Replacing NameSpaces one from Another using XSLT

I have one XML input file in which I am getting some Namespaces which I wanted to replace from another using XSLT. Actually I am new XSLT so not able to find proper solution. below is the XML input payload and Output payload which I want. Could anyone help me on that.
Input Payload:
<ns:createOrderResponse xmlns:ns="http://services.oms.ecom.ecc.com"><ns:return type="com.ecc.ecom.oms.beans.xsd.CreateOrderResponse"><ns:omsGeneratedOrderId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" /><ns:responseCode>99</ns:responseCode><ns:responseDesc>INVALID ORDER</ns:responseDesc><ns:sellerSiteId>10196</ns:sellerSiteId><ns:serverProcElapsedTime>8</ns:serverProcElapsedTime><ns:siteGeneratedOrderId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" /><ns:subResponse><responseCode xmlns="http://beans.oms.ecom.ecc.com/xsd">1144</responseCode><responseDescription xmlns="http://beans.oms.ecom.ecc.com/xsd">Order Total mismatch</responseDescription></ns:subResponse><ns:subResponse><responseCode xmlns="http://beans.oms.ecom.ecc.com/xsd">1147</responseCode><responseDescription xmlns="http://beans.oms.ecom.ecc.com/xsd">Order Grand Total and sum of OrderItem Grand Total mismatch</responseDescription></ns:subResponse><ns:transactionNumber>0717299145</ns:transactionNumber></ns:return></ns:createOrderResponse>
Desired Output:
<ns:createOrderResponse xmlns:ns="http://services.oms.ecom.ecc.com"><ns:return type="com.ecc.ecom.oms.beans.xsd.CreateOrderResponse"><ns:omsGeneratedOrderId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" /><ns:responseCode>99</ns:responseCode><ns:responseDesc>INVALID ORDER</ns:responseDesc><ns:sellerSiteId>10196</ns:sellerSiteId><ns:serverProcElapsedTime>8</ns:serverProcElapsedTime><ns:siteGeneratedOrderId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" /><ns:subResponse><responseCode xmlns="http://services.oms.ecom.ecc.com">1144</responseCode><responseDescription xmlns="http://services.oms.ecom.ecc.com">Order Total mismatch</responseDescription></ns:subResponse><ns:subResponse><responseCode xmlns="http://services.oms.ecom.ecc.com">1147</responseCode><responseDescription xmlns="http://services.oms.ecom.ecc.com">Order Grand Total and sum of OrderItem Grand Total mismatch</responseDescription></ns:subResponse><ns:transactionNumber>0717299145</ns:transactionNumber></ns:return></ns:createOrderResponse>
Basically I wanted to replace:
Namespace :http://beans.oms.ecom.shc.com/xsd
from
Namespace :http://services.oms.ecom.ecc.com
CAVEAT: The following is untested but demonstrates the ideas.
If you know which specific elements and attributes to expect, that's relatively easy -- it's the same as any other "when you see this element, output this other element instead" template, recursing to cover the whole document. Start with the identity stylesheet and add appropriate templates of the form:
<xsl:template match="wrongnamespace:fred"
xmlns:wrongnamespace="http://services.oms.ecom.ecc.com">
<rightnamespace:fred xmlns:rightnamespace="http://beans.oms.ecom.shc.com/xsd">
<xsl:apply-templates/>
</rightnamespace:fred>
</xsl:template>
and (for attributes)
<xsl:template match="#wrongnamespace:george"
xmlns:wrongnamespace="http://services.oms.ecom.ecc.com">
<xsl:attribute name="ns:george" value="."
namespace="http://beans.oms.ecom.shc.com/xsd"/>
</xsl:template>
... and so on.
If you don't know what elements or attributes to expect, this becomes uglier. In XSLT 1.0 and XPath 1.0 there's no easy way to say "any element node" (though I believe that was fixed in the 2.0 version of these specs). Sticking with 1.0 because it's more widely supported, the easiest solution is to use priorities: have a higher-priority template that catches the attributes, then have another template which handles other namespaced nodes (which would therefore be elements).
<xsl:template match="#*[namespace-uri(.)="http://services.oms.ecom.ecc.com"
priority="1">
<xsl:attribute name="concat('ns:',localname(.))" value="."
namespace="http://beans.oms.ecom.shc.com/xsd"/>
</xsl:template>
<xsl:template match="node()[namespace-uri(.)="http://services.oms.ecom.ecc.com"
priority="1">
<xsl:element name="concat('ns:',localname(.))"
namespace="http://beans.oms.ecom.shc.com/xsd">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
Again, adding these to the canonical identity stylesheet will do what you need; it'll catch and change these two cases while copying everything else unaltered.
CAVEAT: THIS WILL NOT CHANGE URIS APPEARING IN STRINGS. You could add a template which looks for text() nodes containing the old URI and outputs the new one instead; I'm leaving that as an exercise for the reader.
CAVEAT: THIS WILL NOT CHANGE NAMESPACE NODES. You may wind up with leftover declarations of the old prefix and namespace. Another template could be added to clean that up; this too is left as an exercise for the reader.
That should give you enough to get you started. Have fun. Programming in XSLT does require learning to think in terms of rules and replacements rather than procedures, but when you get used to that it can be a very expressive tool.
(Standard plug: If you want an off-the-shelf tested solution, you can probably find one on Dave Pawson's XSL FAQ website. That's a resource you really need to be aware of if working in XSLT; it'll save you a lot of work, and it has some solutions that are very clever and very non-obvious.)

Cannot find external method "..." (must be public) XSLT

I'm trying to do some operation on dates using XSL. I found a tutorial on IBM developerWorks with this script.
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times"
version="1.0"
>
<xsl:output method="html"/>
<!-- B -->
<xsl:variable name="now" select="date:date-time()"/>
<xsl:template match="/">
<!-- The rest of the Web site HTML material would go here -->
<xsl:call-template name="date-section"/>
</xsl:template>
<xsl:template name="date-section">
<p>This page was loaded at <xsl:text/>
<!-- C -->
<xsl:value-of select="concat(date:hour-in-day($now), ':',
date:minute-in-hour($now), ':',
date:second-in-minute($now))"/>
<xsl:text> on </xsl:text>
<xsl:value-of select="concat(date:day-in-month($now), ' ',
date:month-name($now), ' ',
date:year($now))"/>
</p>
<p>
<!-- D -->
<xsl:variable name="days-elapsed"
select="concat('-P',date:day-in-month($now),'D')"/>
<xsl:variable name="one-month-hence"
select="date:add($now, 'P1M')"/>
<xsl:variable name="next-month-start"
select="date:add($one-month-hence, $days-elapsed)"/>
<xsl:variable name="seconds"
select="date:seconds(
date:difference($now, $next-month-start)
)"/>
<xsl:text>The next month starts in </xsl:text>
<xsl:value-of select="$seconds div (3600*24)"/>
<xsl:text> days</xsl:text>
</p>
</xsl:template>
I get the following errors:
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.add' (must be public).
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.difference' (must be public).
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.seconds' (must be public).
[ERROR]: Cannot convert data-type 'void' to 'real'.
[FATAL]: Could not compile stylesheet
Any ideas how to fix it. I'm using IntelliJ to run the xsl.
Ultimately I want to to be able to add days to a date. For some reason when I try to use xsl 2 date functions it just tells me they do not exist(And yes I do change the header to version 2 :) ). So I'm trying to make it work with a 3rd party library. The date-time() function from the http://exslt.org/dates-and-times namespace is working but I can't seam to be able to call other functions form that namespace.
the question is a bit old but the answers were, IMHO, not quite correct and my answer might help some other people stumbling upon this thread (as I just did):
The functions used are XSLT 1.0 extension functions, there are different modules (e.g. dates-and-times), each with mandatory and optional functions (and elements). See EXSLT
The issue here is that XALAN does support the module dates-and-times, but not all optional functions.
As suggested, you can surely try to find a "better" XSLT-Processor, but it might require some work to integrate. I personally use on the Linux command line xsltproc, which supports more of the dates-and-times module (try xsltproc --dumpextensions | sort for an overview).
Alternatively, you could try the approach describing how to get current date and time in XSLT 1.0. I haven't tried it myself yet (as said, xsltproc is a good enough workaround), but using a similar approach you should be able to implement what you need without having to touch your tool/java code.
For sake of completeness, my tool with the same problem is Freeplane from freeplane.org, which also uses XALAN (I guess that it's part of standard Java libraries) and fails on similar dates-and-times functions.
Hope this helps,
Eric
I don't know Xalan, so I don't know why it's failing to find these methods.
But if you want to use the XSLT 2.0 date/time functions, you need to invoke an XSLT 2.0 processor, and the obvious one for the Java environment is Saxon (current version is 9.4). Switching processor requires a little bit more than changing the version number: for example, if you are running it from the command line then you will need to use a different command.

Varying xpath-default-namespace in XML source files

I have a set of XML files that I am processing with an XSL transform. They have a default namespace, so my XSL transform must contain the declaration:
xpath-default-namespace="urn:CZ-RVV-IS-VaV-XML-NS:data-1.2.2"
The problem is that this value changes from time to time, and my transform suddenly stops working, until I look at an example from the new file, extract this namespace ID and put it in the transform, whereby the transform stops working for old files. Is there a way to pass this as a parameter, or set it somehow at runtime? I have tried the parameter syntaxes that I looked up in various tutorials, but none have worked for this particular use.
I have searched all sorts of forums and found references to namespace-agnostic coding of XSL, but not figured out how to do it. Ian Williams' book "XSLT and Xpath" states that the default namespace must be declared, or you get nothing in the output stream, which is how it has worked for me. But I really don't want to have to change this by hand regularly, I want to give the user something that will work, without needing constant attention from me.
The only 100% reliable way I have invented so far is to use a standard programming language to open both the XML source and XSL transform as text files, extract the URI from the XML source, paste it into the XSL transform, close both files and then, finally run the actual transform. This works, but is incredibly dorky, at least to my taste. How can I better deal with changing default namespaces?
Pete
The value of xpath-default-namespace must be a static URI, so you'll have to pre-process the stylesheet if you want it to vary. One way to do that would be to use XSLT. Apply the following meta-stylesheet to your primary stylesheet each time, and then invoke the pre-processed result instead.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Pass in the new namespace URI as a stylesheet parameter -->
<xsl:param name="new-uri" required="yes"/>
<!-- By default, copy everything as is -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- But update the value of #xpath-default-namespace -->
<xsl:template match="#xpath-default-namespace">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="$new-uri"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is a bit of a strange use case though, because namespaces weren't really designed to be so dynamic. They were designed to qualify names, i.e. make up part of a name. When you look at it that way, dynamic namespaces don't make a lot of sense. Imagine a database whose table and field names arbitrarily changed every once in a while, forcing you to rewrite all your SQL scripts to keep up with the changes. That's what this is akin to.
Have you tried defining a stylesheet parameter <xsl:param name="xpdn"/> and using it in the stylesheet declaration or top level template declaration as in
<xsl:template match="...." xpath-default-namespace="$xpdn">
I can't find anything in the spec that says this won't work (but I'm not in a position to try it just now).

Merge multiple xslt stylesheets

I have a xslt stylesheet with multiple xsl:imports and I want to merge them all into the one xslt file.
It is a limitation of the system we are using where it passes around the xsl stylesheet as a string object stored in memory. This is transmitted to remote machine where it performs the transformation. Since it is not being loaded from disk the href links are broken, so we need to remove the xsl:imports from the stylesheet.
Are there any tools out there which can do this?
You can use an XSL stylesheet to merge your stylesheets. However, this is equivalent to using the xsl:include element, not xsl:import (as Azat Razetdinov has already pointed out). You can read up on the difference here.
Therefore you should first replace the xsl:import's with xsl:include's, resolve any conflicts and test whether you still get the correct results. After that, you could use the following stylesheet to merge your existing stylesheets into one. Just apply it to your master stylesheet:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="xsl:include">
<xsl:copy-of select="document(#href)/xsl:stylesheet/*"/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The first template replaces all xsl:include's with the included stylesheets by using the document function, which reads in the file referenced in the href attribute. The second template is the identity transformation.
I've tested it with Xalan and it seems to work fine.
It is impossible to include imported stylsheets into the main file without breaking import precedence. For example, you define a top-level variable in an imported stylesheet and redefine it in the main file. If you merge two files into one, you’ll get two variables with the same name and import precedence, which will result in an error.
The workaround is two replace xsl:import’s with xsl:include’s and resolve any conflicts. After that you are safe to replace xsl:include instructions with the corresponding files’ contents, because that is what XSLT-processor does:
The inclusion works at the XML tree level. The resource located by the href attribute value is parsed as an XML document, and the children of the xsl:stylesheet element in this document replace the xsl:include element in the including document. The fact that template rules or definitions are included does not affect the way they are processed.
A Manual merge is probably going to be the best option.
The main consideration will probably be to make sure that the logic for matching templates works in the combined stylesheet.
Why would you want to? They're usually seperated for a reason afterall (often maintainability)
You could always write the merge yourself - read the XSL files in, select the template items you're interested in and write to a new master XSL file...
import multiple xsl in single xsl
<xsl:import href="FpML_FXOption_Trade_Template1.xsl"/>
<xsl:apply-imports/>
<calypso:keyword>
<calypso:name>DisplayOptionStyle</calypso:name>
<calypso:value>Vanilla</calypso:value>
</calypso:keyword>
<xsl:import href="FpML_FXOption_Trade_Template2.xsl"/>
<xsl:apply-imports/>