I have an XSLT stylesheet like the following:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:XQHeaderFunc="java:com.sonicsw.xq.service.xform.HeaderExtension"
xmlns:saxon="http://saxon.sf.net/">
<saxon:script language="java" implements-prefix="XQHeaderFunc" src="java:com.sonicsw.xq.service.xform.HeaderExtension" />
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="processId" select="XQHeaderFunc:getProperty(XQHeaderFunc:new(),'processId',-1)" />
<xsl:value-of select="XQHeaderFunc:setProperty(XQHeaderFunc:new(),'processId',string(#id),-1)"/>
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<!-- Other stuff -->
</xsl:stylesheet>
I want to transform this stylesheet using a second XSLT stylesheet to remove anything that has to do with the XQHeaderFunc and saxon namespaces. Is there a way I can do this?
I have now tried the following, which successfully deals with the elements, but the namespace declaration doesn't seem to want to disappear.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:XQHeaderFunc="java:com.sonicsw.xq.service.xform.HeaderExtension"
xmlns:saxon="http://saxon.sf.net/"
exclude-result-prefixes="XQHeaderFunc saxon">
<xsl:param name="XQHeaderReplacement" />
<xsl:variable name="apos">'</xsl:variable>
<!-- Copy all nodes -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- Remove saxon script tag -->
<xsl:template match="saxon:script" />
<!-- Remove elements with setProperty calls -->
<xsl:template match="*[starts-with(#select, 'XQHeaderFunc:setProperty')]" />
<!-- Replace getProperty calls with replacement value-->
<xsl:template match="#select[starts-with(., 'XQHeaderFunc:getProperty')]">
<xsl:attribute name="select">
<xsl:value-of select="concat($apos, $XQHeaderReplacement, $apos)"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:XQHeaderFunc="java:com.sonicsw.xq.service.xform.HeaderExtension"
xmlns:saxon="http://saxon.sf.net/">
<xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="processId" select="''" />
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<!-- Other stuff -->
</xsl:stylesheet>
I would add
exclude-result-prefixes="foo"
onto your
<xsl:stylesheet>
element. Then the namespace declaration for foo will be omitted if possible, i.e. if there no elements or attributes in that namespace to output.
FYI, the reason tnat this line
<xsl:template match="xsl:stylesheet/#xmlns:foo" />`
throws an error is because xmlns:foo in the input document is not an attribute; it's a pseudoattribute. What your match pattern is asking to match is an attribute named foo that is in a namespace corresponding to a namespace prefix xmlns. Since you have not declared a namespace prefix xmlns in your stylesheet, you get the error "prefix xmlns is not defined."
Update:
I see from your posted output (and my own testing) that exclude-result-prefixes hasn't been effective in removing the namespace declaration.
1) First I would ask why it matters. It doesn't change the namespace of anything in your output XSLT. Are you just trying to remove it for aesthetic reasons?
2) Looking at the XSLT 1.0 spec, it appears to me that <xsl:copy> pays no attention to exclude-result-prefixes:
Instantiating the xsl:copy element creates a copy of the current node.
The namespace nodes of the current node are automatically copied as
well...
AFAICT, only literal result elements will omit namespace nodes based on exclude-result-prefixes.
On that basis, I would try replacing <xsl:copy> in your identity template (for elements) with <xsl:element name="{name()}"> or some variant thereof. You would then need a separate identity template for non-element nodes.
I replaced your identity template with the following two templates:
<!-- Copy elements -->
<xsl:template match="*" priority="-1">
<xsl:element name="{name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<!-- Copy all other nodes -->
<xsl:template match="node()|#*" priority="-2">
<xsl:copy />
</xsl:template>
and that gave what I believe is the desired output, with no extraneous ns declarations:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="processId" select="''" />
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<!-- Other stuff -->
</xsl:stylesheet>
(I have adjusted the whitespace for clarity.)
Related
I have a requirement to add a namespace prefix to all my xml elements.
I have a working xslt for this but unfortunately it doesn't copy the attributes. What do I need to change to also copy these attributes.
This my code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:ns0="http://ns.hr-xml.org/2007-04-15" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*">
<xsl:element name="ns0:{local-name()}">
<xsl:apply-templates select="*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The input XML is this:
<HumanResource schemaLocation="http://ns.hr-xml.org/2007-04-15 ../HumanResource.xsd">
<HumanResourceId idOwner="StaffingCompany">
<IdValue>123COMP</IdValue>
</HumanResourceId>
<HumanResourceId idOwner="StaffingCustomer">
<IdValue>456CUST</IdValue>
<!--double hre-id, due fms wnr-code swap-->
</HumanResourceId>
<HumanResourceStatus status="x:Assigned"/>
<ReferenceInformation>
<StaffingSupplierId idOwner="StaffingCustomer">
<IdValue>105964</IdValue>
</StaffingSupplierId>
<StaffingCustomerId idOwner="StaffingCompany">
<IdValue>MIN0000612</IdValue>
</StaffingCustomerId>
<StaffingCustomerId idOwner="StaffingCustomer">
<IdValue>ELI</IdValue>
</StaffingCustomerId>
<StaffingSupplierOrgUnitId idOwner="StaffingCompany">
<IdValue>2606379</IdValue>
</StaffingSupplierOrgUnitId>
<StaffingCustomerOrgUnitId idOwner="StaffingCustomer">
<IdValue>ELI</IdValue>
</StaffingCustomerOrgUnitId>
</ReferenceInformation>
<ResourceInformation>
<PersonName>
<FormattedName>J</FormattedName>
<LegalName>C</LegalName>
<GivenName>J</GivenName>
<PreferredGivenName>J</PreferredGivenName>
<MiddleName>C.J.</MiddleName>
<FamilyName primary="true"></FamilyName>
</PersonName>
<EntityContactInfo>
<EntityName></EntityName>
<PersonName>
<FormattedName>Liesbeth Kramer</FormattedName>
</PersonName>
<ContactMethod>
<Telephone>
<FormattedNumber>070-3102150</FormattedNumber>
</Telephone>
</ContactMethod>
</EntityContactInfo>
<PostalAddress>
<CountryCode>NL</CountryCode>
<PostalCode></PostalCode>
<Municipality></Municipality>
<DeliveryAddress>
<AddressLine></AddressLine>
<StreetName></StreetName>
<BuildingNumber></BuildingNumber>
</DeliveryAddress>
</PostalAddress>
</ResourceInformation>
<Profile/>
<Preferences/>
<UserArea>
<ns2:HumanResourceAdditionalNL xmlns:ns2="http://ns.setu.nl/2008-01">
<ns2:BirthDate>1961-07-13</ns2:BirthDate>
<ns2:Sex>female</ns2:Sex>
</ns2:HumanResourceAdditionalNL>
</UserArea>
</HumanResource>
My result with my code looks like this:
<?xml version='1.0' ?>
<ns0:HumanResource xmlns:ns0="http://ns.hr-xml.org/2007-04-15">
<ns0:HumanResourceId>
<ns0:IdValue>123COMP</ns0:IdValue>
</ns0:HumanResourceId>
<ns0:HumanResourceId>
<ns0:IdValue>456CUST</ns0:IdValue>
</ns0:HumanResourceId>
<ns0:HumanResourceStatus/>
<ns0:ReferenceInformation>
As you can see, the ns0: is added ok but I lost the attributes.
You have lost the attributes because you have no code to select them. You currently do <xsl:apply-templates select="*|node()" />, but * select elements, and node() selects elements, text nodes, comments and processing instructions. You need to change it to this...
<xsl:apply-templates select="#*|node()" />
You would then need a separate template to match attributes and copy them.
<xsl:template match="#*">
<xsl:copy />
</xsl:template>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:ns0="http://ns.hr-xml.org/2007-04-15" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*">
<xsl:element name="ns0:{local-name()}">
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:copy />
</xsl:template>
</xsl:stylesheet>
Alternatively, you could use xsl:copy-of to copy the attributes. Try this XSLT too
<xsl:stylesheet version="1.0" xmlns:ns0="http://ns.hr-xml.org/2007-04-15" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*">
<xsl:element name="ns0:{local-name()}">
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Note that <xsl:apply-templates /> is short-hand for <xsl:apply-templates select="node()" />
I am trying to copy all child nodes to a specific node, except a few. Haven't been able to get this to work? Any pointers of what I am doing wrong?
Using this XML:
<ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">
<ns0:Header>
<wsse:Sec xmlns:wsse="http://docs.x.org/wsse/">
<saml:Ass xmlns:saml="http://docs.x.org/saml/">
<ds:Sign xmlns:ds="http://docs.x.org/ds/">
<ds:SignVal>SignatureValue</ds:SignVal>
</ds:Sign>
<saml:subj>SubjectValue</saml:subj>
</saml:Ass>
</wsse:Sec>
<To>http://localhost:8080/Test/</To>
<Action>SendTest</Action>
</ns0:Header>
<ns0:Body>...</ns0:Body>
</ns0:Envelope>
The wanted result is to just get the Sec tag and all children:
<wsse:Sec xmlns:wsse="http://docs.x.org/wsse/">
<saml:Ass xmlns:saml="http://docs.x.org/saml/">
<ds:Sign xmlns:ds="http://docs.x.org/ds/">
<ds:SignVal>SignatureValue</ds:SignVal>
</ds:Sign>
<saml:subj>SubjectValue</saml:subj>
</saml:Ass>
</wsse:Sec>
I have tried numerous XSL including this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="Header">
<xsl:copy-of select="*"/>
</xsl:template>
<!-- Exclude these -->
<xsl:template match="To" />
<xsl:template match="Action" />
</xsl:stylesheet>
The result is I get values but no tags...
You have not accounted for namespaces in your XSLT. In your XML, Header is in namespace http://schemas.xmlsoap.org/soap/envelope/, but your XSLT is trying to match a Header in no namespace.
You need to declare the namespaces in your XSLT, and use them in the template matches
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsse="http://docs.x.org/wsse/">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="ns0:Header">
<xsl:copy-of select="wsse:Sec"/>
</xsl:template>
<xsl:template match="ns0:Body" />
</xsl:stylesheet>
Note this XSLT doesn't need templates matching "To" and "Action" because of the explicit copy of wsse:Sec using this approach. However, you do need to template to ensure any test within ns0:Body isn't picked up.
Another approach is to use the identity template, and then you would have the templates to exclude To and Action (and Body)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsse="http://docs.x.org/wsse/">
<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="ns0:Envelope|ns0:Header">
<xsl:apply-templates />
</xsl:template>
<!-- Exclude these -->
<xsl:template match="ns0:Body|To|Action" />
</xsl:stylesheet>
Note there is a template matching ns0:Envelope and ns0:Header as although you don't want these elements themselves, you do need to process the child nodes.
You would need to use XSLT 2 or 3 with
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:wsse="http://docs.x.org/wsse/"
exclude-result-prefixes="#all"
version="3.0">
<xsl:template match="/">
<xsl:copy-of select="//wsse:Sec" copy-namespaces="no"/>
</xsl:template>
</xsl:stylesheet>
to get the posted result with a simple copy instruction: https://xsltfiddle.liberty-development.net/bnnZVw
In XSLT 1 the copy will always copy the in-scope namespace xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" so to remove it from the result you would need to run your code through some kind of transformation stripping in-scope namespaces (other than the one of the element itself):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wsse="http://docs.x.org/wsse/"
exclude-result-prefixes="wsse"
version="1.0">
<xsl:template match="#*">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="//wsse:Sec"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bnnZVw/1
I am looking for XSL to transform provided input to expected output.I have just provided sample but actual input xml had more than 1000 nodes and as too many nodes not able to use CDATA section in XSL, could you please help.
Input:
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
Output:
<note>
<to><![CDATA[Tove]]></to>
<from><![CDATA[Jani]]></from>
<heading><![CDATA[Reminder]]></heading>
<body><![CDATA[Don't forget me this weekend!]]></body>
</note>
You can achieve this by using the cdata-section-elements attribute of the xsl:output element which specifies all the elements that should be output in CDATA sections.
So use the following XSLT-1.0:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" cdata-section-elements="to from heading body" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See that the cdata-section-elements denotes the elements to from heading body to enclose their content in a CDATA section. The identity template just copies all of the file with regard to this.
If your elements are in a namespace, you have to prefix the element's names in the cdata-section-elements with the appropriate namespace-prefix.
For example, if you have the following XML with a namespace on the root element, all children nodes are in that namespace, too.
<?xml version="1.0" encoding="utf-8"?>
<Bank xmlns="http://xxyy.x.com" Operation="Create">
<Customer type="random">
<CustomerId>Id10</CustomerId>
<CountryCode>CountryCode19</CountryCode>
<LanguageCode>LanguageCode20</LanguageCode>
<AddressArray>
<Address type="primary">
<StreetAddress>179 Alfred St</StreetAddress>
<City>Fortitude Valley</City>
<County>GR</County>
<Country>India</Country>
</Address>
</AddressArray>
</Customer>
</Bank>
Use this XSLT (pay attention to the namespace declaration on the xsl:stylesheet element):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:ns0="http://xxyy.x.com">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" cdata-section-elements="ns0:CustomerId ns0:CountryCode ns0:LanguageCode ns0:StreetAddress ns0:City ns0:County ns0:Country" omit-xml-declaration="yes" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Make sure that the namespace of the XSLT matches the namespace of the XML, here both are http://xxyy.x.com, but in your sample XML it is xxyy.x.com.
EDIT 2:
If you have a large amount of elements you can either add them all to cdata-section-elements (maybe by constructing it by another stylesheet) or use the solution I found here: Wrapping all elements in CDATA. It is kind of a hack.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<xsl:variable name="CDATABegin" select="'<![CDATA['" />
<xsl:variable name="CDATAEnd" select="']]>'" />
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="text()[normalize-space()]">
<xsl:value-of select="$CDATABegin" disable-output-escaping="yes"/>
<xsl:value-of select="." disable-output-escaping="yes"/>
<xsl:value-of select="$CDATAEnd" disable-output-escaping="yes"/>
</xsl:template>
</xsl:stylesheet>
This wraps all non-empty text() nodes in CDATA sections. But here, too, you'd have to mention all elements in template matching rules containing the CDATA wrapping code.
Which variant would be easier to apply depends on the greater scenario.
I need to add an additional namespace to an already namespaced XML file but only if a particular element does not exist.
My XML doc looks like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<everyone xmlns="AAA" xmlns:ns2="BBB" xmlns:ns3="CCC" company="TestingCorp">
<common>Stuff Here</common>
<ns2:person id="123">
<ns3:firstname>Billy</ns3:firstname>
<ns2:lastname>Bobby</ns2:lastname>
</ns2:person>
</everyone>
... and if there is no ns3:firstname element in the person element, I'd like to add a new namespace and (e.g. xmlns:frog="FFF") and also an additional element within person as shown below:
Desired Output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<everyone xmlns="AAA" xmlns:ns2="BBB" xmlns:ns3="CCC" xmlns:frog="FFF" company="TestingCorp">
<common>Stuff Here</common>
<ns2:person id="123">
<frog:title>
<Master/>
</frog:title>
<ns2:lastname>Bobby</ns2:lastname>
</ns2:person>
</everyone>
My XSL doc currently is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Copy Everything -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:element name="ns:{local-name()}">
<xsl:attribute name="frog">fff</xsl:attribute>
<xsl:apply-templates select="node()|#*" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
.... unfortunately this does not work.
I've tried lots of different things but can't seem to achieve this using XSLT v1.0. Any help would be greatly appreciated.
First you need to declare the various namespaces in your stylesheet, as well as the "AAA" default namespace
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="AAA"
xmlns:frog="FFF"
xmlns:ns2="BBB"
xmlns:ns3="CCC">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Copy Everything -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- for a Person with no firstname, add a frog:title -->
<xsl:template match="ns2:person[not(ns3:firstname)]">
<xsl:copy>
<!-- must handle attributes before elements/text nodes -->
<xsl:apply-templates select="#*" />
<frog:title>
<Master/>
</frog:title>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This will produce
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<everyone xmlns="AAA" xmlns:ns2="BBB" xmlns:ns3="CCC" company="TestingCorp">
<common>Stuff Here</common>
<ns2:person id="123">
<frog:title xmlns:frog="FFF">
<Master/>
</frog:title>
<ns2:lastname>Bobby</ns2:lastname>
</ns2:person>
</everyone>
If the xmlns:frog absolutely must be on the everyone element rather than on each frog:title then you could add another template
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="document('')/xsl:stylesheet/namespace::frog" />
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
to copy the namespace declaration off the stylesheet element (though this would mean that every output document has an xmlns:frog declaration even if it doesn't involve any frog:* elements).
Edit: apparently Xalan doesn't like the copy-of namespaces from document(''), as an alternative, if you know that the document element will always have the same name then you can hard code that as a literal result element
<xsl:template match="/*">
<everyone xmlns:frog="FFF">
<xsl:copy-of select="namespace::*" />
<xsl:apply-templates select="#*|node()" />
</everyone>
</xsl:template>
(technically it will do what you want even without the explicit xmlns:frog in this template, since literal result elements always get the namespace declarations that are in scope at the point in the stylesheet where they are declared, but the intention is arguably clearer if you include it)
This mailing list post gives some possible insights into the reason for document('') not working as it should.
I have a custom XML schema that is evolving: elements are added, others deleted, and the namespace changes to reflect the new version (e.g., from "http://foo/1.0" to "http://foo/1.1"). I want to write an XSLT that converts XML documents from the old schema to the new one. My first attempt works, but it's verbose and unscalable, and I need help refining it.
Here's a sample document for the 1.0 schema:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo:rootElement xmlns:foo="http://foo/1.0">
<obsolete>something</obsolete>
<stuff>
<alpha>a</alpha>
<beta>b</beta>
</stuff>
</foo:rootElement>
In the 1.1 schema, the "obsolete" element goes away but everything else remains. So the XSLT needs to do two things:
Remove the tag
Change the namespace from http://foo/1.0 to http://foo/1.1
Here's my solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:foo1="http://foo/1.0"
xmlns:foo="http://foo/1.1"
exclude-result-prefixes="foo1">
<xsl:output method="xml" standalone="yes" indent="yes"/>
<!-- Remove the "obsolete" tag -->
<xsl:template match="foo1:rootElement/obsolete"/>
<!-- Copy the "alpha" tag -->
<xsl:template match="foo1:rootElement/stuff/alpha">
<alpha>
<xsl:apply-templates/>
</alpha>
</xsl:template>
<!-- Copy the "beta" tag -->
<xsl:template match="foo1:rootElement/stuff/beta">
<beta>
<xsl:apply-templates/>
</beta>
</xsl:template>
<!-- Copy the "stuff" tag -->
<xsl:template match="foo1:rootElement/stuff">
<stuff>
<xsl:apply-templates/>
</stuff>
</xsl:template>
<!-- Copy the "rootElement" tag -->
<xsl:template match="foo1:rootElement">
<foo:rootElement>
<xsl:apply-templates/>
</foo:rootElement>
</xsl:template>
</xsl:stylesheet>
Although this produces the output I want, notice that I have a copying template for every element in the schema: alpha, beta, etc. For complex schemas with hundreds of kinds of elements, I'd have to add hundreds of templates!
I thought I could eliminate this problem by reducing all those copying templates into a single identity transform, like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:foo1="http://foo/1.0"
xmlns:foo="http://foo/1.1"
exclude-result-prefixes="foo1">
<xsl:output method="xml" standalone="yes" indent="yes"/>
<xsl:template match="foo1:rootElement/obsolete"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="foo1:rootElement/stuff">
<xsl:copy>
<xsl:call-template name="identity"/>
</xsl:copy>
</xsl:template>
<xsl:template match="foo1:rootElement">
<foo:rootElement>
<xsl:apply-templates/>
</foo:rootElement>
</xsl:template>
</xsl:stylesheet>
But it produces:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo:rootElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:foo="http://foo/1.1">
<stuff xmlns:foo="http://foo/1.0">
<stuff>
<alpha>a</alpha>
<beta>b</beta>
</stuff>
</stuff>
</foo:rootElement>
The "stuff" element was copied, which is what I want, but it's wrapped in another "stuff" element, tagged with the old namespace. I don't understand why this is happening. I've tried many ways of fixing this but have been unsuccessful. Any suggestions? Thanks.
This transformation:
<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:param name="pOldNS" select="'http://foo/1.0'"/>
<xsl:param name="pNewNS" select="'http://foo/1.1'"/>
<xsl:template match="/*">
<xsl:element name="{name()}" namespace="{$pNewNS}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:copy-of select="namespace::*[not(.=$pOldNS)]"/>
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:choose>
<xsl:when test="namespace-uri()=$pOldNS">
<xsl:attribute name="{name()}" namespace="{$pNewNS}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="obsolete"/>
</xsl:stylesheet>
when applied on this XML document:
<foo:rootElement xmlns:foo="http://foo/1.0">
<obsolete>something</obsolete>
<stuff>
<alpha x="y" foo:t="z">a</alpha>
<beta>b</beta>
</stuff>
</foo:rootElement>
produces the wanted, correct result (obsolute elements deleted, namespace upgraded, attribute nodes correctly processed, including nodes that were in the old namespace):
<foo:rootElement xmlns:foo="http://foo/1.1">
<stuff>
<alpha x="y" foo:t="z">a</alpha>
<beta>b</beta>
</stuff>
</foo:rootElement>
Main advantages of this solution:
Works correctly when there are attributes, belonging to the old-schema namespace.
General, allows the old and new namespace to be passed as external parameters to the transformation.
While you might want to copy the attributes, elements need to be regenerated in the new namespace. So I'd suggest to do it like this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:foo1="http://foo/1.0">
<xsl:template match="foo1:rootElement/obsolete"/>
<xsl:template match="attribute()">
<xsl:copy/>
</xsl:template>
<xsl:template match="element()">
<xsl:variable name="name" select="local-name()"/>
<xsl:element name="{$name}" namespace="http://foo/1.1">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>