Reputation: 353
I'm trying to use xslt to perform a simple map.
First I define a variable pointing to the node which I want to copy and then use "xsl:copy-of select=$var"
Ex. <xsl:variable name="myVar1" select="//cds/cd[@name='2']/node1/node2/addr2" />
This is working fine for a simple test xsl and test xml. However with actual xml, it is just not copying the node at all.
Only difference between test xsl and actual one is that the names of nodes are really long in the actual xsl (xml on which this needs to be applied has long node name), and the test xsl & xml dont have any namespace prefixes.
It works fine if I point the variable only till root node ie <xsl:variable name="myVar1" select="//cds/cd[@name='2']
but I want to copy the node somewhere below that (ie node1/node2/addr2)
I dont see any error in xsl (I use XMLSpy), just wondering if there's any limitation on the length of node names while defining variables?
Thanks, Ashish
Just figured out what is causing the issue but not sure how to fix. Yes, the issues seems to be related to namespace. When I add the namespace "a" in the below sample xml, it stopped working (real xml has a namespace like this).
Input XML
<store>
<cds>
<cd name="0">
<test>this is test 0</test>
</cd>
<cd name="1">
<node1 xmlns="anothernamespace">
<node2 xmlns:a="http://someurl.com/something">
<addr1>
<country>country1</country>
</addr1>
<addr2>
<state>state1</state>
</addr2>
<a:addr3>
<city>city1</city>
<phone>123</phone>
<email>email1</email>
<website xmlns:b="somenamespace">
<b:name>website name</b:name>
<a:category>blog</a:category>
</website>
</a:addr3>
</node2>
</node1>
</cd>
<cd name="2">
<test>this is test2</test>
</cd>
</cds>
</store>
XSLT Code
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:variable name="myVar1" select="//cds/cd[@name='1']/node1/node2/addr3" />
<addr>
<xsl:copy-of select="$myVar1" />
</addr>
</xsl:template>
</xsl:stylesheet>
This works fine if the xml does not have the namespace "a"
Upvotes: 0
Views: 2712
Reputation: 116982
First, you need to declare the namespace in your stylesheet, assign it a prefix, and use that prefix when addressing nodes in that namespace.
The other thing is that when you copy a node, you also copy its namespace. You did not post your expected result, but I assume that's NOT what you want.
Note also that when you copy a node (a:addr3
in your example) into another node (addr
), you will end up with the entire node, including start and end tags, nested inside the target node. Again, I assume that's NOT what you want here.
Try something like the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="http://someurl.com/something"
exclude-result-prefixes="a">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="myVar1" select="//cds/cd[@name='1']/node1/node2/a:addr3" />
<addr>
<xsl:apply-templates select="$myVar1/*" mode="remove-ns"/>
</addr>
</xsl:template>
<xsl:template match="*" mode="remove-ns">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="*" mode="remove-ns"/>
<xsl:apply-templates select="@*|text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
When applied to your (rather weird) example input:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Edited by XMLSpy -->
<store>
<cds>
<cd name="0">
<test>this is test 0</test>
</cd>
<cd name="1">
<node1>
<node2 xmlns:a="http://someurl.com/something">
<addr1>
<country>country1</country>
</addr1>
<addr2>
<state>state1</state>
</addr2>
<a:addr3>
<city>city1</city>
<phone>123</phone>
<email>email1</email>
<website>
<name>website name</name>
</website>
</a:addr3>
</node2>
</node1>
</cd>
<cd name="2">
<test>this is test2</test>
</cd>
</cds>
</store>
the result will be:
<addr>
<city>city1</city>
<phone>123</phone>
<email>email1</email>
<website>
<name>website name</name>
</website>
</addr>
Regarding your updated input:
<store>
<cds>
<cd name="0">
<test>this is test 0</test>
</cd>
<cd name="1">
<node1 xmlns="anothernamespace">
<node2 xmlns:a="http://someurl.com/something">
<addr1>
<country>country1</country>
</addr1>
<addr2>
<state>state1</state>
</addr2>
<a:addr3>
<city>city1</city>
<phone>123</phone>
<email>email1</email>
<website xmlns:b="somenamespace">
<b:name>website name</b:name>
<a:category>blog</a:category>
</website>
</a:addr3>
</node2>
</node1>
</cd>
<cd name="2">
<test>this is test2</test>
</cd>
</cds>
</store>
The problem here is that nodes inherit the namespace of their parent (unless they are explicitly put into another namespace). Here, you need to consider not only the namespace of a:addr3
, but also the namespaces of all nodes that are ancestors of a:addr3
:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ans="anothernamespace"
xmlns:a="http://someurl.com/something"
exclude-result-prefixes="ans a">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="myVar1" select="//cds/cd[@name='1']/ans:node1/ans:node2/a:addr3" />
<addr>
<xsl:apply-templates select="$myVar1/*" mode="remove-ns"/>
</addr>
</xsl:template>
<xsl:template match="*" mode="remove-ns">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="*" mode="remove-ns"/>
<xsl:apply-templates select="@*|text()"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
Note that there is no change in the templates themselves - only the stylesheet namespace declarations and the XPath stored in the $myVar1
variable were changed.
Upvotes: 2
Reputation: 2117
You need to define the 'a' namespace at the top of your xslt and then prefix 'addr3' in your variable select with 'a'. It will look something like the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="http://someurl.com/something">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:variable name="myVar1" select="//cds/cd[@name='1']/node1/node2/a:addr3" />
<addr>
<xsl:copy-of select="$myVar1" />
</addr>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1