Complex XSL Transformation - xslt-2.0

I am still a beginner with XSLT but I am having a difficult task in hand.
I have a non-xml file which needs to be transformed. The format of the file is a s follows:
type1
type1line1
type1line2
type1line3
type2
type2line1
type2line2
type3
type3line1
type3line2
types (type1, type2, ...) are specified using certain codes which don't have a specific order. Each type has multiple line underneath.
So, I need to transform this file but the problem is that for each type I have to do a different transformation for each of it's underlying lines.
Now, I can read the string line by line and determine that a new type has begun but I don't know how to set a flag (indicating the type) to use it in the underlying lines.
Here is what I have right now:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:param name="testString" as="xs:string">
type1
line1
line2
type1
line1
</xsl:param>
<xsl:template match="/">
<xsl:call-template name="main">
<xsl:with-param name="testString" select="$testString"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="main">
<xsl:param name="testString"/>
<xsl:variable name="iniFile" select="$testString"/>
<config>
<xsl:analyze-string select="$iniFile" regex="\n">
<xsl:non-matching-substring>
<item>
<xsl:choose>
<xsl:when test="starts-with(., 'type1')">
<!-- do a specific transformation-->
</xsl:when>
<xsl:when test="starts-with(., 'type2')">
<!-- do another transformation-->
</xsl:when>
</xsl:choose>
</item>
</xsl:non-matching-substring>
</xsl:analyze-string>
</config>
</xsl:template>
</xsl:stylesheet>
Any idea about how to solve the problem.

I think XSLT 2.1 will allow you to use its powerful stuff like for-each-group on sequences of atomic values like strings but with XSLT 2.0 you have such powerful features only for sequences of nodes so my first step when using XSLT 2.0 with plain string data I want to process/group is to create elements. So you could tokenize your data, wrap each token into some element and then use for-each-group group-starting-with to process each group starting with some pattern like '^type[0-9]+$'.
You haven't really told us what you want to with the data once you have identified a group so take the following as an example you could adapt:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="input" as="xs:string">type1
type1line1
type1line2
type1line3
type2
type2line1
type2line2
type3
type3line1
type3line2</xsl:param>
<xsl:template name="main">
<xsl:variable name="lines" as="element(item)*">
<xsl:for-each select="tokenize($input, '\n')">
<item><xsl:value-of select="."/></item>
</xsl:for-each>
</xsl:variable>
<xsl:for-each-group select="$lines" group-starting-with="item[matches(., '^type[0-9]+$')]">
<xsl:choose>
<xsl:when test=". = 'type1'">
<xsl:apply-templates select="current-group() except ." mode="m1"/>
</xsl:when>
<xsl:when test=". = 'type2'">
<xsl:apply-templates select="current-group() except ." mode="m2"/>
</xsl:when>
<xsl:when test=". = 'type3'">
<xsl:apply-templates select="current-group() except ." mode="m3"/>
</xsl:when>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="item" mode="m1">
<foo>
<xsl:value-of select="."/>
</foo>
</xsl:template>
<xsl:template match="item" mode="m2">
<bar>
<xsl:value-of select="."/>
</bar>
</xsl:template>
<xsl:template match="item" mode="m3">
<baz>
<xsl:value-of select="."/>
</baz>
</xsl:template>
</xsl:stylesheet>
When applied with Saxon 9 (command line options -it:main -xsl:sheet.xsl) the result is
<foo>type1line1</foo>
<foo>type1line2</foo>
<foo>type1line3</foo>
<bar>type2line1</bar>
<bar>type2line2</bar>
<baz>type3line1</baz>
<baz>type3line2</baz>

Related

Transform only the last element of XML and copy the rest in XSLT

I have an XML like below -
<root>
<row>
<col1>16</col1>
<col2>466</col2>
<col3>144922</col3>
<col4>0</col4>
<col5>5668</col5>
<col6>475</col6>
</row>
</root>
The number of columns can vary inside the root element. It can also be up to col9. My requirement is to modify the last column and copy others as it is for an incoming XML.
I have something like this till now where I am assigning the value to used as the last element in a variable and then trying to call it when the last position is reached-
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"
version="2.0">
<xsl:param name="line88.field2" />
<xsl:param name="rec16.col2" />
<xsl:variable name="col3">
<xsl:choose>
<xsl:when test="$rec16.col2 ='165'">
<xsl:value-of select="'Y'"/>
</xsl:when>
<xsl:when>
------
<xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="row[position() = last()]">
<col9>
<xsl:call-template name="AnotherTemplate">
<xsl:with-param name="inputData">
<xsl:value-of select="$col3" />
</xsl:with-param>
</xsl:call-template>
</col9>
</xsl:template>
<xsl:template name="AnotherTemplate">
<xsl:param name="inputData"></xsl:param>
<xsl:value-of select="$inputData" />
</xsl:template>
</xsl:stylesheet>
But this is not working for me. Just giving me one column with the modified value.Please help.
The desired outcome should be as below where the last column has the value from the variable.
<root>
<row>
<col1>16</col1>
<col2>466</col2>
<col3>144922</col3>
<col4>0</col4>
<col5>5668</col5>
<col6>Y</col6>
</row>
</root>
Without knowing your whole XSLT code. You can use this row template:
<xsl:template match="row/*[starts-with(local-name(),'col') and position() = last()]">
<xsl:element name="{concat('col',position() div 2)}">
<xsl:call-template name="AnotherTemplate">
<xsl:with-param name="inputData">
<xsl:value-of select="$col3" />
</xsl:with-param>
</xsl:call-template>
</xsl:element>
</xsl:template>
It replaces the last col? element by the given value (the result of the xsl:call-template code).

How to access previous and next item based on a condition

I have 2000 TEI-XML-files with letters between different people and a single person in a single folder. I can access the previous and next letter chronologically as the filename starts with the date of the sender (e.g. 2001-02-21.xml). But what I want to achieve is to create an XSLT (2 or 3, doesn't matter) that writes the next letter to or from the specific writer/receiver into the xml-file.
Say I have this:
<correspDesc>
<correspAction type="sent">
<persName key="CMvW">Carl Maria von Weber</persName>
<settlement>Dresden</settlement>
<date when="1817-06-23">23 June 1817</date>
</correspAction>
<correspAction type="received">
<persName key="CB">Caroline Brandt</persName>
<settlement>Prag</settlement>
</correspAction>
</correspDesc>
and i want to add this field as a third child after correspAction:
<correspContext>
<ref type="prev"
target="http://www.weber-gesamtausgabe.de/A041209">Previous letter of
<persName key="CMvW">Carl Maria von Weber</persName>
to <persName key="CB">Caroline Brandt</persName>:
<date from="1817-06-19" to="1817-06-20">June 19/20, 1817</date>
</ref>
<ref type="next"
target="http://www.weber-gesamtausgabe.de/A041217">Next letter of
<persName key="CMvW">Carl Maria von Weber</persName> to
<persName key="CB">Caroline Brandt</persName>:
<date when="1817-06-27">June 27, 1817</date>
</ref>
</correspContext>
how would I do it? In the example Caroline Brandt is the changing sender/receiver. So basically I would need a collection of all XML-files with //correspDesc//persName[#key='CB'] and in each file access the preceding and following one of the collection. How can I achieve that?
begin of the solution
#martin-honnen pointed me the way though I'm certain it is not the most elegant way.
1) I've used collection to copy all correspDesc into one file. I add an attribute 'lookup' with the key of the person and the date the letter was sent combined and an attribute with just 'person' to identify the letters of a correspondence.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes" method="xml" encoding="utf-8" omit-xml-declaration="false"/>
<xsl:template match="/">
<xsl:element name="root">
<xsl:for-each select="collection('?select=*.xml;recurse=no')">
<xsl:element name="correspDesc">
<xsl:attribute name="lookup">
<xsl:choose>
<xsl:when test="//correspAction[#type='sent']/persName/#key='pmb2121'">
<xsl:value-of select="//correspAction[#type='received']/persName/#key"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="//correspAction[#type='sent']/date/#when"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="//correspAction[#type='sent']/persName/#key"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="//correspAction[#type='sent']/date/#when"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name="person">
<xsl:choose>
<xsl:when test="//correspAction[#type='sent']/persName/#key='pmb2121'">
<xsl:value-of select="//correspAction[#type='received']/persName/#key"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="//correspAction[#type='sent']/persName/#key"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates select="//correspAction"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
2) I order the resulting list using the date-field
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:mode on-no-match="shallow-copy" />
<xsl:output indent="yes"
method="xml"
encoding="utf-8"
omit-xml-declaration="false"/>
<xsl:template match="root">
<xsl:element name="root">
<xsl:apply-templates select="correspDesc">
<xsl:sort select="correspAction[#type='sent']/date/#when" data-type="text" order="ascending"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Now my next task is to use param and key to lookup the preceding and following entries.
<xsl:param name="correspList" select="document('correspList.xml')"/>
<xsl:key name="corresp-lookup" match="#lookup"/>
<xsl:key name="correspPerson-lookup" match="#person"/>
I am not sure how I will achieve that but I will update here once I have that code.

Some help on how groupings actually work would be much appreciated

I'm trying to get my head around how the different grouping techniques work. What causes a group, how are each group defined, and how the key are formed for each group.
If I wanted to use "group-adjacent" to move all following siblings of a specific element name into the first of preceding sibling of a given type. Would this be doable? I know how I can do this with recursive templates, and to some extent with keys in xslt 1.0. But I cannot get the 2.0 groups to work for me.
Lets say that I want to move all fig elements into the first preceding para, given that there are no other kinds of elements in between the fig(s) and the preceding para element, in this simple xml.
<root>
<first_lvl>
<title>First heading</title>
<para>First para under first heading</para>
<para>Second para under first heading</para>
<fig>fig1</fig>
<fig>fig 2</fig>
<table>Table A</table>
<fig>fig 3</fig>
<para>Third para under first heading</para>
<para>Fourth para under first heading</para>
<fig>fig4</fig>
</first_lvl>
</root>
Desired result:
<root>
<first_lvl>
<title>First heading</title>
<para>First para under first heading</para>
<para>Second para under first heading
<fig>fig1</fig>
<fig>fig 2</fig>
</para>
<table>Table A</table>
<fig>fig 3</fig>
<para>Third para under first heading</para>
<para>Fourth para under first heading
<fig>fig4</fig>
</para>
</first_lvl>
</root>
How can I set a grouping up that takes care of every directly following fig element?
This doesn't work:
<xsl:template match=para[following-sibling::*[1][self::fig]]>
<xsl:for-each-group select"folowing-sibling::*" group-adjacent="boolean(self::fig)">
<xsl:apply-templates select="current-group()" mode="move"/>
</xsl:for-each-group>
</xsl:template>
And then I've added atemplate to build content for each fig inside the para, and one to ignore those figs when they appear later on in the processing.
No luck though.
I have no other values to group by, other that the fact that they are fig elements.
What am I missing here?
I would start with a group-starting-with on those para followed by a fig and then inside use group-adjacent to identify only the first group of adjacent figs. With the verbosity of XSLT that looks a bit convoluted but does the job as far as I have understood your requirements:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes" />
<xsl:template match="*[para and fig]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
<xsl:choose>
<xsl:when test="self::para[following-sibling::*[1][self::fig]]">
<xsl:variable name="para-head" select="."/>
<xsl:for-each-group select="tail(current-group())" group-adjacent="boolean(self::fig)">
<xsl:choose>
<xsl:when test="position() = 1">
<xsl:copy select="$para-head">
<xsl:apply-templates select="node(), current-group()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6qVRKxh
I have taken the liberty to use XSLT 3 instead of 2 but you simply would have to spell out the identity transformation declared by the xsl:mode on-no-match="shallow-copy" and make sure you use
<xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
<xsl:apply-templates select="$para-head/node(), current-group()"/>
</xsl:element>
instead of the XSLT 3 only xsl:copy with a select:
<xsl:copy select="$para-head">
<xsl:apply-templates select="node(), current-group()"/>
</xsl:copy>
and instead of the XPath 3 tail function you use subsequence e.g.
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:output method="xml" indent="yes" />
<xsl:template match="*[para and fig]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
<xsl:choose>
<xsl:when test="self::para[following-sibling::*[1][self::fig]]">
<xsl:variable name="para-head" select="."/>
<xsl:for-each-group select="subsequence(current-group(), 2)" group-adjacent="boolean(self::fig)">
<xsl:choose>
<xsl:when test="position() = 1">
<xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
<xsl:apply-templates select="$para-head/node(), current-group()"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
http://xsltransform.hikmatu.com/bFDb2BN
On the other hand, I am not sure whether an attempt not using xsl:for-each-group but rather a template matching para[following-sibling::*[1][self::fig]] and then consuming following sibling figs (which can be done easily in XSLT 3 with xsl:iterate) is not more compact:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="para[following-sibling::*[1][self::fig]]">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
<xsl:iterate select="following-sibling::*">
<xsl:choose>
<xsl:when test="self::fig">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:break/>
</xsl:otherwise>
</xsl:choose>
</xsl:iterate>
</xsl:copy>
</xsl:template>
<xsl:template match="fig[preceding-sibling::*[not(self::fig)][1][self::para]]"/>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6qVRKxh/3

XSLT mapping and summing source children into a single target attribute

I have a source document with XML structure similar to this:
<FOO>
<BAR>x</BAR>
<BAR>y</BAR>
<BAR>z</BAR>
</FOO>
My target XML must have an attribute with a number that represents the numerical sum of x, y and z. Where x = 1, y = 2 and z = 3
NOTE: The x, y and z are not actually numbers in the source document. They are letters and need to be mapped to the numbers that they represent first.
In this case, the target should look something like:
<Target Sum=6>
</Target>
Anyone have an XSLT example that would do what I need?
Thanks in advance
<?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" indent="yes"/>
<xsl:template match="/">
<Target Sum="{sum(FOO/BAR)}" />
</xsl:template>
EDIT:
This is a bit verbose, and there is probably a more elegant way to do it, but essentially I have a named template here that recursively calls itself to calculate the sum after the hard-coded mapping occurs:
<?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" indent="yes"/>
<xsl:template match="/">
<Target>
<xsl:attribute name="Sum">
<xsl:call-template name="GetSum">
<xsl:with-param name="CurrentNode" select="FOO/BAR[position()=1]"/>
</xsl:call-template>
</xsl:attribute>
</Target>
</xsl:template>
<xsl:template name="GetSum">
<xsl:param name="CurrentNode"/>
<xsl:param name="Number" select="0"/>
<xsl:variable name="Recursive_Result">
<xsl:variable name="MappedNumber">
<xsl:choose>
<xsl:when test="$CurrentNode/. = 'x'">1</xsl:when>
<xsl:when test="$CurrentNode/. = 'y'">2</xsl:when>
<xsl:when test="$CurrentNode/. = 'z'">3</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- if there is a next sibling, recursively call GetSum -->
<xsl:choose>
<xsl:when test="$CurrentNode/following-sibling::BAR[1]">
<xsl:call-template name="GetSum">
<xsl:with-param name="CurrentNode" select="$CurrentNode/following-sibling::BAR[1]"/>
<xsl:with-param name="Number">
<xsl:value-of select="$MappedNumber"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$MappedNumber"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- add the recursive_result to the number passed into the template. this will eventually build a sum -->
<xsl:value-of select="$Recursive_Result + $Number"/>
</xsl:template>
</xsl:stylesheet>
A much simpler, shorter and efficient solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kMap" match="#value" use="../#key"/>
<xsl:variable name="vMaps" as="element()*">
<map key="x" value="1"/>
<map key="y" value="2"/>
<map key="z" value="3"/>
</xsl:variable>
<xsl:template match="/*">
<Target Sum="{sum(key('kMap', BAR, document('')))}"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<FOO>
<BAR>x</BAR>
<BAR>y</BAR>
<BAR>z</BAR>
</FOO>
the wanted, correct result is produced:
<Target Sum="6"/>
Explanation: Appropriate use of xsl:key, the 3rd argument of the key() function and AVT.

xslt: keeping namespace declaration on root when root element is not known in advance

I have xml documents that follow a schema where most of the defined elements are allowed to be the root of a valid instance. I also have several xslt's v2.0 which translate it in various ways (put it into a normal form, a compact form, a different dialect, ...) These xslt's are all based on an identity transform with templates added to make the desired modification. The problem is that there is a proliferation of namespace attributes because there are some elements that come from outside the default namespace.
I have tried the recommended procedures for inserting the namespace on the root element, but I can't seem to get it right. The issues are:
1. the transformation may change the name, and sometimes the content of the root element, so I still need the templates for each of the global elements, and since I don't know which one will be root, I can't just insert namespace elements where needed (I don't know where they will be needed for a particular document.
2. I thought about implementing this as multi-pass, or simply an independent xslt, since I want the same result for several different xslts. In this case, what I would need is an identity transform that takes all the namespaces and prefixes from all elements in the document, and inserts them into the root. This would, I hope, automatically remove the namespace attributes from the children? However, I tried the following
<?xml version="1.0" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template name="start" match="/">
<xsl:copy>
<xsl:for-each select="*">
<xsl:copy>
<xsl:for-each select="descendant::*">
<xsl:call-template name="add-ns">
<xsl:with-param name="ns-namespace">
<xsl:value-of select="namespace-uri()"/>
</xsl:with-param>
<xsl:with-param name="ns-prefix">
<xsl:value-of
select=" prefix-from-QName( QName(namespace-uri(),name()))"/>
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template name="add-ns">
<xsl:param name="ns-prefix" select="'x'"/>
<xsl:param name="ns-namespace" select="'someNamespace'"/>
<xsl:namespace name="{$ns-prefix}" select="$ns-namespace"/>
</xsl:template>
<xsl:template match="node()|#* ">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
And this works for all prefixes that appear on elements, but it doesn't catch the prefixes of attributes. Here is a test document:
<RuleML xmlns="http://www.ruleml.org/0.91/xsd">
<Assert textiri="xy>z">
<Importation xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="abc"
textiri="urn:common-logic:demo1"
xlink:href="http://common-logic.org/x>cl/demos.xml"/>
<a:anything xmlns:a="http://anything.org"
xmlns:xlink="http://www.w3.org/1999/xlink"/>
</Assert>
</RuleML>
I want it to produce:
<RuleML xmlns="http://www.ruleml.org/0.91/xsd" xmlns:a="http://anything.org" xmlns:xlink="http://www.w3.org/1999/xlink" >
<Assert textiri="xy>z">
<Importation xml:id="abc"
textiri="urn:common-logic:demo1"
xlink:href="http://common-logic.org/x>cl/demos.xml"/>
<a:anything/>
</Assert>
</RuleML>
but instead I get
<RuleML xmlns="http://www.ruleml.org/0.91/xsd" xmlns:a="http://anything.org">
<Assert textiri="xy>z">
<Importation xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="abc"
textiri="urn:common-logic:demo1"
xlink:href="http://common-logic.org/x>cl/demos.xml"/>
<a:anything xmlns:xlink="http://www.w3.org/1999/xlink"/>
</Assert>
</RuleML>
Tara
Does the following do what you want?
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:template match="#* | node()">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="descendant::*/namespace::*"/>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With Saxon 9.3 it seems to do the job on the sample you posted.
I am however not sure what you want to do if there are several elements in different default namespaces or several elements in different namespaces but using the same prefix. For instance with
<root xmlns="http://example.com/ns1">
<foo xmlns="http://example.com/ns2">
<pf:bar xmlns:pf="http://example.com/ns3">
<pf:foobar xmlns:pf="http://example.com/ns4"/>
</pf:bar>
</foo>
</root>
Saxon simply reports the error
Error at xsl:copy-of on line 15 of test2011061801Xsl2.xsl:
XTDE0430: Cannot create two namespace nodes with the same prefix mapped to different URIs
(prefix="", URI=http://example.com/ns2, URI=http://example.com/ns1)
in built-in template rule
[edit]
If you don't want an error to be reported you could try to implement a strategy to pull up namespace nodes as far up as possible but to avoid any collisions. That can be done with for-each-group, as in the following sample XSLT 2.0:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:template match="#* | text() | processing-instruction() | comment()">
<xsl:copy/>
</xsl:template>
<xsl:template match="*">
<xsl:copy copy-namespaces="no">
<xsl:for-each-group select="descendant-or-self::*/namespace::*" group-by="local-name()">
<xsl:copy-of select="."/>
</xsl:for-each-group>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With the input being
<root xmlns="http://example.com/ns1">
<foo xmlns="http://example.com/ns2">
<pf:bar xmlns:pf="http://example.com/ns3">
<pf:foobar xmlns:pf="http://example.com/ns4"/>
</pf:bar>
</foo>
</root>
Saxon 9.3 outputs
<?xml version="1.0" encoding="UTF-8"?><root xmlns="http://example.com/ns1" xmlns:pf="http://example.com/ns3">
<foo xmlns="http://example.com/ns2">
<pf:bar>
<pf:foobar xmlns:pf="http://example.com/ns4"/>
</pf:bar>
</foo>
</root>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*:RuleML">
<xsl:copy>
<xsl:for-each select="descendant::node()">
<xsl:choose>
<xsl:when test="self::text()"/>
<xsl:otherwise>
<xsl:for-each select="namespace::node()">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="(node() | #*) except namespace::node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="(node() | #*) except namespace::node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Resources