XSLT transformation with dynamic namespace - xslt

I need to transform an XML file to another XML file, where the source file has a dynamic namespace set to xmlns="whatever". My XSLT runs fine without the namespace being in the file, but I get no output with the namespace. How can I cause the schema of the source file to be applied to the destination file?
All help is appreciated and thanks in advance!
EDIT:
I'm trying to copy the namespace uri over to the resulting file:
<xsl:param name="schema">
<xsl:value-of select="namespace-uri()" />
</xsl:param>
<xsl:element name="root" namespace="$schema">
I have verified that schema is holding the correct value, but the problem is that the program appears to take this too literally:
<root xmlns="$schema">
Is this the right way to go about this?
EDIT x2:
I've implemented Alejandro's suggestion of:
<xsl:element name="root" namespace="{$schema}"/>
And that works for the most part, except for the fact that I have to put the namespace on every element or else I get the following structure in the result:
<root xmlns="NAMESPACE">
<foo xmlns="">
etc.
Is there a way to blanket all of the elements with this namespace, other than putting namespace={$schema} on every single line? Bounty and accept for the best answer!
EDIT x3:
Better example:
If I do:
<xsl:element name="root" namespace="{namespace-uri()}>
<xsl:element name="foo">
<xsl:element name="bar">
<!--etc-->
</xsl:element>
</xsl:element>
</xsl:element>
I get:
<root xmlns="NAMESPACE">
<foo xmlns="">
<bar>
<!--etc-->
</bar>
</foo>
<root>
I would like to have them all under namespace NAMESPACE, so I did:
<xsl:element name="root" namespace="{namespace-uri()}>
<xsl:element name="foo" namespace="{namespace-uri()}>
<xsl:element name="bar" namespace="{namespace-uri()}>
<!--etc-->
</xsl:element>
</xsl:element>
</xsl:element>
However this is ugly and tedious to type. Is there an easier way to blanket the namespace over all elements? (hopefully this clarifies what I need)

Suppose you have this XML input:
<root xmlns="survivors">
<louis/>
<francis/>
</root>
Meaning that every element is under default namespace wich its URI is "survivors".
As Welbog wrote you can select francis element with:
/*/*[local-name()='francis']
or
/*[local-name()='root']/*[local-name()='francis']
But, that also select francis element from these XML inputs:
<root xmlns="survivors" xmlns:n="no-survivors">
<louis/>
<n:francis/>
</root>
or
<root xmlns="survivors">
<louis/>
<francis xmlns="no-survivors"/>
</root>
You could also strengthen the predicate with some namespace URI. But, wich one? An option could be the default namespace for root element like:
/*/*[local-name()='francis'][namespace-uri()=namespace-uri(/*)]
Surely this make XPath expression very verbose.
In XSLT 2.0 you could use xsl:xpath-default-namespace attribute like:
<xsl:value-of select="/root/francis" xpath-default-namespace="survivors"/>
But that's not good for your case because you don't know the URI in advance.
EDIT: xsl:element 's attributes are AVT (Attribute Value Template) so you need this:
<xsl:element name="root" namespace="{$schema}"/>
Also, I recomend you to declare the param as a string data type (not RTF like now), something like:
<xsl:param name="schema" select="namespace-uri()"/>
EDIT 2: Maybe I was not clear. You don't need xsl:element/#namespace in every case. Following your statement that every element is in only one default namespace, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='bar']">
<xsl:element name="newbar" namespace="{namespace-uri()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
With this input:
<root xmlns="whatever">
<foo/>
<bar/>
</root>
Output:
<root xmlns="whatever">
<foo></foo>
<newbar></newbar>
</root>
Edit 2: I was showing you that when you are copying an element you are also copying the namespace needed for expand the QName. So, if want to transform this:
<root xmlns="whatever">
<foo/>
<bar/>
</root>
Into this:
<root xmlns="whatever">
<foo>
<bar/>
</foo>
</root>
You can use this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="*[1]|following-sibling::*[1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Since the namespace is part of the full name of any elements your XPath is referencing, and since you don't know the namespace of the source file in advance, you'll have to use the local names of the elements you're accessing instead of their full names.
Let's say you have a source file like this:
<root xmlns="survivors">
<louis/>
<francis/>
</root>
One way to access these elements using XSLT is to specify a namespace that matches the source file's default namespace:
<xsl:stylesheet
version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:surv="survivors"
>
<xsl:template match="surv:louis">
<!-- ETC -->
That way works when you know the namespace. When you don't know the namespace, you can ignore it using the XPath function local-name() like this:
<xsl:stylesheet
version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="*[local-name() = 'louis']">
<!-- ETC -->
Using local-name() means you can ignore the namespace in the source document. You'll have to be careful if there are multiple elements with the same local name in different namespace, though. It's not exactly a robust solution, but if you can't trust your namespace then you don't have that many options anyway.
I'd imagine that having a variable namespace is a bigger problem in and of itself. If that's under your control, you should correct it. If it's not under your control, you should push to have it corrected.

XSLT is rarely executed in a vaccum. It's almost always a part of some other application that loads the XSLT, loads the source document, and then produces the output.
Assuming the above is your scenario, I wouldn't solve this problem directly with XSLT. Instead, I'd use standard XML technologies to examine the source XML document and discover the default namespace. Then, I'd load the XSLT document and use string substitution or some other technique to inject the resultant namespace at the appropriate point in the transform. Then I'd go ahead and run the transform normally.
This will make your XSLT much more natural to write and maintain. I'm a pro with XSLT and use it constantly, and this is how I would solve it. I couldn't imagine the ugliness of a stylesheet that had to use local-name() comparisons constantly. What a pain. (Of course, in much more complex scenarios there may be no choice. Fortunately yours isn't one of them.)
If you don't have this option, I sympathize.

Related

Replace namespace node string value with xslt---what is wrong with my xsl

I am trying to replace namespace string using xslt.
I have the source namespace string and target namespace string in another xml file in the format of "namespace source="xxx" target="xxx"". All source namespace strings in my to-be-transformed xml should be changed to the corresponding target value. Only elements need to be considered as attributes don't have namespace.
I'm using the JDK default xalan xslt processor, which supports xslt 1.0. I'd like to stick to xslt 1.0 if possible, but I can change processor and use xslt 2.0 if really needed.
For example, to-be-transformed input xml:
<root xmlns="http://ns1" xmlns:ns2="http://ns2-old" xmlns:ns3="http://ns3">
<ElementA xmlns="http://ns4-old">
<ElementB/>
<ns2:elementD/>
</ElementA>
</root>
The output xml should be ("http://ns2-old" is changed to "http://ns2-new", and "http://ns4-old" is changed to "http://ns4-new"):
<root xmlns="http://ns1" xmlns:ns2="http://ns2-new" xmlns:ns3="http://ns3">
<ElementA xmlns="http://ns4-new">
<ElementB/>
<ns2:elementD/>
</ElementA>
</root>
Here is my xsl that is not working:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="nsmapdoc" select="document('my-map-file')"/>
<xsl:key name="nsmap" match="//namespace/#target" use="#source"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- process each element -->
<xsl:template match="element()">
<xsl:for-each select="namespace::*">
<!-- for each namespace node of the element, save the string value-->
<xsl:variable name="sourceuri"><xsl:value-of select="."/>
</xsl:variable>
<!-- get the target value for this namespace-->
<xsl:variable name="targeturi">
<xsl:for-each select="$nsmapdoc">
<xsl:value-of select="key('nsmap', $sourceuri)"/>
</xsl:for-each>
</xsl:variable>
<!-- if target value exists, replace the current namespace node string value with the target value-->
<xsl:if test="$targeturi">
<xsl:value-of select="$targeturi"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I have a few questions:
For the identity template that do the copy, why doing "match="node()|#*" instead of just "match="node()"? Is attribute also a node?
I am not sure if I did correctly to get the namespace string value (like "http://ns1", "http://ns2-old") for the element.
I think I got the key correctly. However, I am not sure if I used the type correctly---looks like targeturi variable is not a string. If key does not have the entry, what will lookup the entry return? In my case, I should replace the namespace value only for the namespace that has an entry in the map.
How to write a new string value for the namespace node?
I need to process each namespace nodes for the element. Is it the right way to define a variable inside ?
please help to see what is wrong with my xsl. Any suggestion is appreciated.
I think you have two, possibly three, separate questions here.
The first question is: how to move elements from one namespace to another, using a "map" of source-to-target namespaces. Let me answer this question first. Given:
XML
<root xmlns="http://ns1" xmlns:ns2="http://ns2-old" xmlns:ns3="http://ns3">
<ElementA xmlns="http://ns4-old">
<ElementB/>
<ns2:ElementD/>
</ElementA>
</root>
map.xml
<root>
<namespace source="http://ns2-old" target="http://ns2-new"/>
<namespace source="http://ns4-old" target="http://ns4-new"/>
</root>
The following stylesheet:
XSLT 1.0
<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:param name="nsmapdoc" select="document('map.xml')"/>
<xsl:template match="*">
<xsl:variable name="old-ns" select="namespace-uri()"/>
<xsl:variable name="map-entry" select="$nsmapdoc/root/namespace[#source=$old-ns]"/>
<xsl:variable name="new-ns">
<xsl:choose>
<xsl:when test="$map-entry">
<xsl:value-of select="$map-entry/#target"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$old-ns"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{local-name()}" namespace="{$new-ns}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://ns1">
<ElementA xmlns="http://ns4-new">
<ElementB/>
<ElementD xmlns="http://ns2-new"/>
</ElementA>
</root>
Note:
ElementA and its child ElementB have been moved from namespace URI "http://ns4-old" to URI "http://ns4-new";
ElementD has been moved from namespace URI "http://ns2-old" to URI "http://ns2-new";
The prefix of ElementD has been stripped off; this is a meaningless, cosmetic change, and it should not present any problems for the receiving application;
The root element has remained in its original namespace;
Unused namespace declarations have been discarded.
Note also that we are not using a key to lookup the new namespace: using a key across documents is quite awkward in XSLT 1.0 and I have chosen to do without.
The second question is how to copy the unused namespace declarations. There are several possible answers to choose from:
It's not necessary to copy them, since that are not used for anything;
It's not possible to copy them;
It is possible to copy them by copying some element from the source document; however copying an element also copies its namespace - so this can be done only if there is a known element that is supposed to stay in its original namespace. For example, if you know beforehand that the root element is not supposed to be moved to another namespace, you can add another template to the stylesheet:
to get this result:
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://ns1" xmlns:ns2="http://ns2-old" xmlns:ns3="http://ns3">
<ElementA xmlns="http://ns4-new">
<ElementB/>
<ElementD xmlns="http://ns2-new"/>
</ElementA>
</root>

Xslt - How do you check for a grandchild node with a certain path name. (xpath 1.0)

What I want to do is given an element as context, I want to determine if it has a child with a given name and determine if that child has a node with a given name so I can do operations with it. It is important that I do this in XPath 1.0 syntax.
The code that I've gotten so far is this.
<xsl:for-each select="child::*">
<xsl:if test="contains(name(), 'description')">
<xsl:for-each select="child::*">
<xsl:if test="contains(name(), 'text')">
<xsl:value-of select="node()"/>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
It works, but it's big and ugly and I know that there's a way to condense it. The for-eachs there are unnecessary, since I'm only expecting one child node to be named description, and for it to only have one text node.
I feel like this solution should work
<xsl:for-each select="./description/text">
..
</xsl:for-each>
But it isn't, and I'm not really good enough with XPath Syntax to know why.
The reason I'm asking is because though I've found answers that detect whether a child node has a name, and I've found answers that can get to that child node's context, I haven't found an answer that combines the two, though maybe I just haven't been searching hard enough, in which case I apologize.
Edit: Woops, sorry yeah I forgot to mention that the contains() part of the code was also just a hack because I wasn't sure how to compare their values with equality.
Also as long as the answer is there, <xsl:for-each select="description/text"> does not work either.
A sample of the XML in question is this
<leaf>
<description>
<text> Various Words
</text>
</description>
</leaf>
where the context is the leaf and I am trying to get to the text node.
Edit: The Second Coming:
The problem for me was that my XSLT file was using a default namespace (in my case named a). If I had added that then Borodin's answer would have been correct.
To be specific, this is the code which ended up working for me in the end, in case anyone wants to know.
<xsl:for-each select="a:description/a:text>
<xsl:value-of select="node()"/>
</xsl:for-each>
Thanks Guys ^-^
Do you really want to check whether the element names contain those strings? Or, as your narrative says, do you want elements with that exact name?
To do something like what you have already written, use
<xsl:for-each select="*[contains(name(), 'description')]/*[contains(name(), 'text')]">
<xsl:value-of select="node()"/>
</xsl:for-each>
But if you know the complete names it is a lot neater:
<xsl:for-each select="description/text">
<xsl:value-of select="node()"/>
</xsl:for-each>
If that doesn't work then we need to see more of your source XML and your transform.
Update
If I use this XML
<leaf>
<description>
<text>Various Words</text>
</description>
<description>
<text>More Words</text>
</description>
<description>
<text>Other Words</text>
</description>
</leaf>
and apply this stylesheet
<?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"/>
<xsl:template match="/leaf">
<xsl:for-each select="description/text">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
the output is the expected Various WordsMore WordsOther Words. I don't know how to help you unless you describe your situation better, except to say that transforms should be written with another template rather than for-each wherever possible. Like this variation which produces the same output as above.
<?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"/>
<xsl:template match="/leaf">
<xsl:apply-templates select="description/text"/>
</xsl:template>
<xsl:template match="text">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>

XSLT: How to categorize individual elements which are located on the same level

Dear community,
I would like to transform an initial xml which has this format:
<h2>title1</h2>
<div>sometext1</div>
<div>sometext2</div>
<h2>title2</h2>
<div>sometext3</div>
<div>sometext4</div>
<h2>title3</h2>
<div>sometext5</div>
<div>sometext6</div>
into
<cat name="title1">
<div>sometext1</div>
<div>sometext2</div>
</cat>
<cat name="title2">
<div>sometext3</div>
<div>sometext4</div>
</cat>
<cat name="title3">
<div>sometext5</div>
<div>sometext6</div>
</cat>
I have tried to execute a double for-each and create a variable to hold the "select" option to execute the inner for-each, but seems like it is required to use the node-set() function. Even if I try to include it, it does not work. Do you have any thoughts on how to resolve this issue, using XSLT 1.0 and preferrably without using any other namespaces?
Here's one way of doing this that does not depend on nested loops.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="x" match="div" use="preceding-sibling::h2[1]"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()[not(name()='div')]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="h2">
<cat name="{text()}">
<xsl:apply-templates select="key('x',.)"/>
</cat>
</xsl:template>
</xsl:stylesheet>
It first builds an index (xsl:key) that maps each div to its immediately preceding h2. Then we have a simple identity transform that skips the div entries. For each h2 encountered we generate the <cat> and then output the <div...> tags indexed from that h2.

Add additional namespace/schemalocations to XML file using XSLT

I want to convert:
<ppx xmlns="http://www.p.com/ppx/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.p.com/ppx/1 http://www.p.com/ppx/1/ppx.xsd">
<p></p></ppx>
into:
<ppx xmlns="http://www.p.com/ppx/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ppxx="http://www.m.com/mExt/v1"
xmlns:ppxtpx="http://www.m.com/mExt/v3"
xsi:schemaLocation="http://www.p.com/ppx/1 http://www.p.com/ppx/1/ppx.xsd
http://www.m.com/mExt/v1 http://www.m.com/mExt/v1/ppxv1.xsd
http://www.m.com/mExt/v3 http://www.m.com/mExt/v3/ppxv3.xsd">
<p></p></ppx>
I need to add a few namespace declarations and their associated schemaLocations to an existing XML file without changing anything else in that XML.
In principle it's easy: it just needs a standard "modified identity template" pattern:
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="ppx">
<ppx xmlns="http://www.p.com/ppx/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ppxx="http://www.m.com/mExt/v1"
xmlns:ppxtpx="http://www.m.com/mExt/v3"
xsi:schemaLocation="http://www.p.com/ppx/1 http://www.p.com/ppx/1/ppx.xsd
http://www.m.com/mExt/v1 http://www.m.com/mExt/v1/ppxv1.xsd
http://www.m.com/mExt/v3 http://www.m.com/mExt/v3/ppxv3.xsd">
<xsl:apply-templates/>
</ppx>
</xsl:template>
However, it could get a bit more complicated depending on how much the input can vary from the example you have shown us. For example if the root element will not always be named ppx, or if the namespaces to be added are not known in advance. So you may need to explain more details of the problem

XSLT: how to write redundant xmlns?

I need the following output for bizzare system which expects same xmlns declared in parent and child and refuses to work otherwise. I.e that's what expected:
<root xmlns="http://something">
<element xmlns="http://something" />
</root>
I can create xmlns in root with
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:element name="root" namespace="http://something">
<xsl:element name="node" namespace="http://something" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
However it doesn't add xmlns into childnode because node's parent has the same xmlns. How to force XSLT to write xmlns disregarding parent?
The XML schema specification expressly prohibits attributes named xmlns, so an XSLT stylesheet cannot create such attributes directly using <xsl:attribute>. I can only see two options for you...
One option is to create dummy attributes using a different name (e.g. xmlnsx):
<xsl:template match="/">
<xsl:element name="root">
<xsl:attribute name="xmlnsx">http://something</xsl:attribute>
<xsl:element name="node">
<xsl:attribute name="xmlnsx">http://something</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:template>
... and then replace all occurrences of the attribute xmlnsx with xmlns in some post-processing step (such as a SAX filter or other stream editor). However, this solution involves inserting a non-XSLT step into the pipeline.
The other option is pure, if ugly, XSLT. You could generate the required XML directly, using xsl:text and disable-output-escaping, like this:
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><root xmlns="http://something"></xsl:text>
<xsl:text disable-output-escaping="yes"><node xmlns="http://something"></xsl:text>
<xsl:text disable-output-escaping="yes"></root></xsl:text>
</xsl:template>
Note that the XSLT 1.0 specification is pretty loose when it comes to serialization, so a particular XSLT processor could still conceivable strip the redundant namespace declarations from this second solution. However, it worked in the four processors that I tried (namely Saxon, MSXML, MSXML.NET and LIBXML).