Help with multi-pass XSLT using node-set() - xslt

I need to display certain data in a tabular form, and would prefer to use multi-pass
xslt using node-set() so that I can avoid deploying additional tools (like xsltproc).
Right now, I'm able to perform the required task in two steps i.e.
Step 1: convert XML-1 to XMl-2 using identity template (xsl:copy, using xsl:element to
add dynamic elements 'dev' and 'qa`):
<projectteam>
<member>
<name>John</name>
<role>dev</role>
<hrs>100</hrs>
</member>
<member>
<name>Peter</name>
<role>qa</role>
<hrs>80</hrs>
</member>
</projectteam>
To
<projectteam>
<member>
<name>John</name>
<dev>100</dev>
</member>
<member>
<name>Peter</name>
<qa>80</qa>
</member>
<projectteam>
And then, use another XSLT-FO style sheet to transform XML #2 into a PDF document with the required layout:
name | dev | qa |
-----------------
John | 100 | |
Peter| | 80 |
-----------------
Total| 100 | 80 |
I've tried using node-set() (incorrectly I suppose) to combine both the steps, but
it wouldn't work as the result I get is as follows:
name | dev | qa |
-----------------
Total| 0 | 0 |
Stylesheet-1: converts XML-1 to XML-2, imports another stylesheet 'projDisplay.xsl',
uses node-set() to invoke the imported stylesheet, but the data doesn't get displayed?
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:exslt="http://exslt.org/common"
>
<!-- import stylesheet to display XML-2 in tabular form -->
<xsl:import href="projDisplay.xsl"/>
<xsl:template match="/">
<fo:root>
<xsl:apply-templates/>
</fo:root>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:variable name="newXmlData">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:variable>
<!-- ==> This is where my problem is - it goes to the template defined in -->
<!-- projDisplay.xsl (xslt-fo, pretty big one with page layout etc. hence -->
<!-- not included here, but it works on its own though) as I can see the -->
<!-- table header, and an empty totals row, but non of the rows are displayed -->
<xsl:apply-templates
select="exslt:node-set($newXmlData)/projectteam" mode="display"
/>
</xsl:template>
<!-- replace element 'role' with a new element - role name (i.e. dev or qa) -->
<!-- and set its value as 'hrs -->
<xsl:template match="role">
<xsl:element name="{.}"> <xsl:value-of select="../hrs"/> </xsl:element>
</xsl:template>
<!-- eliminate element 'hrs' -->
<xsl:template match="hrs"/>
</xsl:stylesheet>
The commented section in the stylesheet doesn't look right to me. Any suggestions
about how to correct it?
Thanks!

Here's the solution that works. It's based on Tomalak's original solution with some minor
modifications (like mode flags etc.) Again, thanks to Tomalak!!
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
version="1.0">
<xsl:import href="projDisplay.xsl"/>
<xsl:template match="/">
<!-- store intermediate form as RTF -->
<xsl:variable name="newXmlData">
<xsl:apply-templates mode="filter"/>
</xsl:variable>
<!-- Now apply templates (with xslt-fo) defined in projDisplay.xsl with -->
<!-- the root template as <xsl:template match="projectteam" mode="display"> -->
<!-- to the above RTF (i.e. after the original XML has be convertedr) -->
<xsl:apply-templates select="exslt:node-set($newXmlData)/projectteam" mode="display"/>
</xsl:template>
<!-- use identity templates to copy and modify original XML -->
<xsl:template match="#*|node()" mode="filter">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="filter"/>
</xsl:copy>
</xsl:template>
<!-- replace element 'role' with a new element - role name (i.e. dev or qa) -->
<!-- and set its value as 'hrs -->
<xsl:template match="member/role" mode="filter">
<xsl:element name="{.}"> <xsl:value-of select="../hrs"/> </xsl:element>
</xsl:template>
<!-- eliminate element 'hrs' -->
<xsl:template match="hrs" mode="filter"/>
</xsl:stylesheet>

What about:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:exslt="http://exslt.org/common"
exclude-result-prefixes="exslt"
>
<xsl:import href="projDisplay.xsl"/>
<xsl:template match="/">
<!-- store intermediary format as a RTF -->
<xsl:variable name="newXmlData">
<xsl:apply-templates />
</xsl:variable>
<!-- now we can the apply imported rules -->
<xsl:apply-templates
select="exslt:node-set($newXmlData)/projectteam"
mode="import"
/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="member/role">
<xsl:element name="{.}">
<xsl:value-of select="../hrs" />
</xsl:element>
</xsl:template>
<xsl:template match="member/*" />
<xsl:template match="projectteam" mode="import">
<fo:root>
<xsl:apply-imports />
</fo:root>
</xsl:template>
</xsl:stylesheet>

Related

XSLT combine "params" with "match="#*|node()"

GOAL
Output an XML with the exact input names and variables as the XML my XSLT receive + 2 params from external sources
PSEUDOCODE - XSLT(1.0) I'm using variables instead of params so it's easier to test
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="StatusCode" select="'11111111111'">
</xsl:variable>
<xsl:variable name="StatusMessage" select="'########'">
</xsl:variable>
<!-- BLOCK 1 -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- ------- -->
<!-- BLOCK 2 -->
<xsl:template match="/">
<StatusCode>
<xsl:value-of select="$StatusCode"/>
</StatusCode>
<StatusMessage>
<xsl:value-of select="$StatusMessage"/>
</StatusMessage>
</xsl:template>
<!-- ------- -->
</xsl:stylesheet>
PROBLEM
If I try "BLOCK 1" or "BLOCK 2" independently, they work, but I couldn't as desired combine them both
Your BLOCK 2 template is applied first, because it matches the root node. This template contains no xsl:apply-templates instructions - so the other template is never executed.
The question is WHERE do you want the added nodes to appear. You could do:
<!-- BLOCK 2 -->
<xsl:template match="/">
<xsl:apply-templates/>
<StatusCode>
<xsl:value-of select="$StatusCode"/>
</StatusCode>
<StatusMessage>
<xsl:value-of select="$StatusMessage"/>
</StatusMessage>
</xsl:template>
<!-- ------- -->
but this would place the added nodes outside the root element, making the result an XML fragment instead of well-formed XML document.

How to iterate each xml tag having different names and values ? How to start iterating from a particular tag ? in xslt 2.0

I have the below xml text,
<SUBSCRIBER>
<Anumber>639081000000</Anumber>
<FirstCallDate>20130430104419</FirstCallDate>
<SetyCode>TNT</SetyCode>
<Status>ACT</Status>
<RoamIndicator/>
<PreloadCode>P1</PreloadCode>
<CreationDate>20130116100037</CreationDate>
<PreActiveEndDate/>
<ActiveEndDate>20130804210541</ActiveEndDate>
<GraceEndDate>20140502210541</GraceEndDate>
<RetailerIndicator/>
<OnPeakAccountID>9100</OnPeakAccountID>
<OnPeakSmsExpDate>20130804210504</OnPeakSmsExpDate>
<UnivWalletAcc/>
<UnliSmsOnCtl>20130606211359</UnliSmsOnCtl>
<UnliSmsTriCtl/>
<UnliSmsGblCtl/>
<UnliMocOnCtl>20130606211359</UnliMocOnCtl>
<UnliMocTriCtl/>
<UnliMocGblCtl/>
<UnliSurfFbcCtl>20130606212353</UnliSurfFbcCtl>
</SUBSCRIBER>
How can I iterate/parse over each xml tag name and get the value ( I need the name of the tag and value in a different variables) ? And also, How can I start iterating from particular tag name ? Ex: I would like to start iterating UnivWalletAcc
Please advise.
So far, I have tried the following,
<xsl:template match="SUBSCRIBER">
<xsl:variable name="tagName">
<xsl:value-of select="/*/*/name()"/>
</xsl:variable>
<xsl:variable name="tagValue">
<xsl:value-of select="/*/*/text()"/>
</xsl:variable>
<xsl:value-of select="$tagName"/>
<xsl:value-of select="$tagValue"/>
</xsl:template>
As an alternative to Veenstra's solution, instead of the xsl:if in the SUBSCRIBER/* template, you can control the iteration in the apply-templates:
<xsl:template match="SUBSCRIBER">
<data>
<xsl:apply-templates select="UnivWalletAcc,
UnivWalletAcc/following-sibling::*" />
</data>
</xsl:template>
With the following XSLT you can loop through all childs of the node SUBSCRIBER:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Identity template that will loop over all nodes and attributes -->
<xsl:template match="#*|node()">
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<!-- Template to match the root and create new root -->
<xsl:template match="SUBSCRIBER">
<data>
<xsl:apply-templates select="#*|node()" />
</data>
</xsl:template>
<!-- Template to loop over all childs of SUBSCRIBER node -->
<xsl:template match="SUBSCRIBER/*">
<!-- This will test if we have seen the UnivWalletAcc node already, if so, output something, otherwise, output nothing -->
<xsl:if test="preceding-sibling::UnivWalletAcc or self::UnivWalletAcc">
<tag>
<tagName><xsl:value-of select="name()" /></tagName>
<tagValue><xsl:value-of select="." /></tagValue>
</tag>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Replace all instances of a string in XML with ****

I have a XSL that needs to filter out specific data found in the XML.
Somewhere in my XML there will be a node like:
<id root="2.16.840.1.113883.3.51.1.1.6.1" extension="9494949494949" />
The XSL I have below deletes the extension node and adds a nullFlavor="MSK" to the node.
What I need to do now, is take the value from the extension node, and search the entire XML document for that value, and replace it with **.
But I'm not sure how to take the extension attribute, and find all instances of that value in the XML (they could be burried in text and inside attributes) and turn them into ** (4 *).
The example below is just an example. I cannot hard code the XSL to look at specific nodes, it needs to look through all text / attribute text in the xml (reason for this is there are 5+ different versions of XML that this will be applied to).
I need to find the Extension in the node, then replace (delete really) that value from the rest of the XML. I'm looking for a 1 solution fits all messages, so a global search->wipe of the Extension value.
Example:
<identifiedPerson classCode="IDENT">
<id root="2.16.840.1.113883.3.51.1.1.6.1" extension="9494949494949" displayable="true" />
<addr use="PHYS">
<city>KAMLOOPS</city>
<country>CA</country>
<postalCode>V1B3C1</postalCode>
<state>BC</state>
<streetAddressLine>1A</streetAddressLine>
<streetAddressLine>2A</streetAddressLine>
<streetAddressLine>9494949494949</streetAddressLine>
<streetAddressLine>4A</streetAddressLine>
</addr>
<note text="9494949494949 should be stars"/>
Should be (The below XSLT already masks the extension in the node with the matching OID).
<identifiedPerson classCode="IDENT">
<id root="2.16.840.1.113883.3.51.1.1.6.1" nullFlavor="MSK" displayable="true" />
<addr use="PHYS">
<city>KAMLOOPS</city>
<country>CA</country>
<postalCode>V1B3C1</postalCode>
<state>BC</state>
<streetAddressLine>1A</streetAddressLine>
<streetAddressLine>2A</streetAddressLine>
<streetAddressLine>****</streetAddressLine>
<streetAddressLine>4A</streetAddressLine>
</addr>
<note text="**** should be stars"/>
Any help would be appreciated.
I am able to use XSL 2.0
I have the current XSL.IT works fine. It matches any tag where the root is '2.16.840.1.113883.3.51.1.1.6.1', kills all attributes and adds a nullFlavor="MSK". However, this will not search the entire XML for that same #.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="attrToKeep" select="'root'" />
<xsl:template match="* | node()">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*">
<xsl:choose>
<xsl:when test="../#root = '2.16.840.1.113883.3.51.1.1.6.1'">
<xsl:copy-of select=".[contains($attrToKeep, name())]" />
<xsl:attribute name="nullFlavor">MSK</xsl:attribute>
<!-- Need some way to use the value found in this node and hide the extension -->
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Any help would be appreciated.
Thanks,
Try using a variable to hold the value of the text to be replaced. Like this:
<xsl:variable
name="rootVar"
select="//*[#root = '2.16.840.1.113883.3.51.1.1.6.1']/#extension" />
And then you should just be able to use the replace function to replace them.
<xsl:template match="'//#*' | text()">
<xsl:sequence select="replace(., $rootVar, '****')"/>
</xsl:template>
The XSLT 2.0 stylesheet
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="replacement" select="'****'"/>
<xsl:param name="new" select="'MKS'"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="identifiedPerson">
<xsl:copy>
<xsl:apply-templates select="#* , node()">
<xsl:with-param name="to-be-replaced" select="id/#extension" tunnel="yes"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="identifiedPerson//text()">
<xsl:param name="to-be-replaced" tunnel="yes"/>
<xsl:sequence select="replace(., $to-be-replaced, $replacement)"/>
</xsl:template>
<xsl:template match="identifiedPerson//#*">
<xsl:param name="to-be-replaced" tunnel="yes"/>
<xsl:attribute name="{name()}" namespace="{namespace-uri()}" select="replace(., $to-be-replaced, $replacement)"/>
</xsl:template>
<xsl:template match="identifiedPerson/id">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="nullFlavor" select="$new"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="identifiedPerson/id/#extension"/>
</xsl:stylesheet>
transforms
<identifiedPerson classCode="IDENT">
<id root="2.16.840.1.113883.3.51.1.1.6.1" extension="9494949494949" displayable="true" />
<addr use="PHYS">
<city>KAMLOOPS</city>
<country>CA</country>
<postalCode>V1B3C1</postalCode>
<state>BC</state>
<streetAddressLine>1A</streetAddressLine>
<streetAddressLine>2A</streetAddressLine>
<streetAddressLine>9494949494949</streetAddressLine>
<streetAddressLine>4A</streetAddressLine>
</addr>
<note text="9494949494949 should be stars"/>
</identifiedPerson>
with Saxon 9.4 into
<?xml version="1.0" encoding="UTF-8"?><identifiedPerson classCode="IDENT">
<id root="2.16.840.1.113883.3.51.1.1.6.1" displayable="true" nullFlavor="MKS"/>
<addr use="PHYS">
<city>KAMLOOPS</city>
<country>CA</country>
<postalCode>V1B3C1</postalCode>
<state>BC</state>
<streetAddressLine>1A</streetAddressLine>
<streetAddressLine>2A</streetAddressLine>
<streetAddressLine>****</streetAddressLine>
<streetAddressLine>4A</streetAddressLine>
</addr>
<note text="**** should be stars"/>
</identifiedPerson>
So for the sample it solves that problem I think. I am not sure whether there can be more context around that sample and whether you want to change values outside of the identifiedPerson element as well or don't want to change them (which above stylesheet does). If other elements also need to be changed consider to post longer input and wanted result samples to illustrate and also explain what determines the node where the value to be replaced is found.
[edit]
Based on your comment I adapted the stylesheet, it now has a parameter to pass in a id (e.g. 2.16.840.1.113883.3.51.1.1.6.1), then it looks for an element of any name with a root attribute having that passed in id value and replaces the extension attribute value found in all attributes and all text nodes found in the document. Furthermore a nullFlavor attribute is added to the element with the id and its extension attribute is removed.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="root-id" select="'2.16.840.1.113883.3.51.1.1.6.1'"/>
<xsl:variable name="to-be-replaced" select="//*[#root = $root-id]/#extension"/>
<xsl:param name="replacement" select="'****'"/>
<xsl:param name="new" select="'MKS'"/>
<xsl:template match="comment() | processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:sequence select="replace(., $to-be-replaced, $replacement)"/>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}" select="replace(., $to-be-replaced, $replacement)"/>
</xsl:template>
<xsl:template match="*[#root = $root-id]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="nullFlavor" select="$new"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#root = $root-id]/#extension"/>
</xsl:stylesheet>

Remove whitespace from HTML generated using XSL

Background
Maintain readable XSL source code while generating HTML without excessive breaks that introduce spaces between sentences and their terminating punctuation. From Rethinking XSLT:
White space in XSLT stylesheets is especially problematic because it serves two purposes: (1) for formatting the XSLT stylesheet itself; and (2) for specifying where whitespace should go in the output of XSLT-processed XML data.
Problem
An XSL template contains the following code:
<xsl:if test="#min-time < #max-time">
for
<xsl:value-of select="#min-time" />
to
<xsl:value-of select="#max-time" />
minutes
</xsl:if>
<xsl:if test="#setting">
on <xsl:value-of select="#setting" /> heat
</xsl:if>
.
This, for example, generates the following output (with whitespace exactly as shown):
for
2
to
3
minutes
.
All major browsers produce:
for 2 to 3 minutes .
Nearly flawless, except for the space between the word minutes and the punctuation. The desired output is:
for 2 to 3 minutes.
It might be possible to eliminate the space by removing the indentation and newlines within the XSL template, but that means having ugly XSL source code.
Workaround
Initially the desired output was wrapped in a variable and then written out as follows:
<xsl:value-of select="normalize-space($step)" />.
This worked until I tried to wrap <span> elements into the variable. The <span> elements never appeared within the generated HTML code. Nor is the following code correct:
<xsl:copy-of select="normalize-space($step)" />.
Technical Details
The stylesheet already uses:
<xsl:strip-space elements="*" />
<xsl:output indent="no" ... />
Related
Storing html tags within an xsl variable
Question
How do you tell the XSLT processor to eliminate that space?
Thank you!
Instead of using copy-of you can apply the identity template with an additional template that trims the spaces from the text nodes. You only create one variable like in your first workaround.
You call:
<li><xsl:apply-templates select="$step" mode="nospace" />.</li>
The templates:
<xsl:template match="text()" mode="nospace" priority="1" >
<xsl:value-of select="normalize-space(.)" />
</xsl:template>
<xsl:template match="node() | #*" mode="nospace">
<xsl:copy>
<xsl:apply-templates select="node() | #*" mode="nospace" />
</xsl:copy>
</xsl:template>
I. 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:template match="t[#max-time > #min-time]">
<span>
<xsl:value-of select=
"concat('for ', #min-time, ' to ', #max-time, ' minutes')"/>
</span>
<xsl:apply-templates select="#setting"/>
<xsl:text>.</xsl:text>
</xsl:template>
<xsl:template match="#setting">
<span>
<xsl:value-of select="concat(' on ', ., ' heat')"/>
</span>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (none has been presented!):
<t min-time="2" max-time="3" setting="moderate"/>
produces the wanted, correct result:
<span>for 2 to 3 minutes</span>
<span> on moderate heat</span>.
and it is displayed by the browser as:
for 2 to 3 minutes
on moderate heat.
When the same transformation is applied on this XML document:
<t min-time="2" max-time="3"/>
again the correct, wanted result is produced:
<span>for 2 to 3 minutes</span>.
and it is displayed by the browser as:
for 2 to 3 minutes.
II. Layout (visual) solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" xmlns:gen="gen:gen" xmlns:gen-attr="gen:gen-attr"
exclude-result-prefixes="my gen">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:layout>
<span>for <gen-attr:min-time/> to <gen-attr:max-time/> minutes</span>
<gen-attr:setting><span> on <gen:current/> heat</span></gen-attr:setting>
<gen:literal>.</gen:literal>
</my:layout>
<xsl:variable name="vLayout" select="document('')/*/my:layout/*"/>
<xsl:variable name="vDoc" select="/"/>
<xsl:template match="node()|#*">
<xsl:param name="pCurrent"/>
<xsl:copy>
<xsl:apply-templates select="node()|#*">
<xsl:with-param name="pCurrent" select="$pCurrent"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="$vLayout">
<xsl:with-param name="pCurrent" select="$vDoc/*"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="gen-attr:*">
<xsl:param name="pCurrent"/>
<xsl:value-of select="$pCurrent/#*[name() = local-name(current())]"/>
</xsl:template>
<xsl:template match="gen-attr:setting">
<xsl:param name="pCurrent"/>
<xsl:variable name="vnextCurrent" select=
"$pCurrent/#*[name() = local-name(current())]"/>
<xsl:apply-templates select="node()[$vnextCurrent]">
<xsl:with-param name="pCurrent" select="$vnextCurrent"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="gen:current">
<xsl:param name="pCurrent"/>
<xsl:value-of select="$pCurrent"/>
</xsl:template>
<xsl:template match="gen:literal">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
This transformation gives us an idea how to make a visual (skeletal) representation of the wanted output and use it to "populate" it with the wanted data from the source XML document.
The result is identical with that of the first solution. If this transformation is run "as-is" it will produce a lot of namespaces -- they are harmless and will not be produced if the layout is in a separate XML file.

Changing a namespace value in an XSL transformation?

I'm not sure if that is possible, as I'm very new to XSLT and stuff, but maybe some of you could help me here? It's a bit tricky and I haven't found anything like it on the internet:
The problem is that I have an input xml with namespaces declared and all and I only need to make slight changes to it (adding or deleting attributes, or shifting them to other locations). But at the same time, I have to update the namespace references in the document's document tag. So, for example, the input xml might look something like this:
<order
xmlns="some.url.01"
xmlns:ns2="some.other.url"
xmlns:ns3="another.one"
>
<orderEntry>
<orderControl>
<mandant>test</mandant>
<businessUnit>test</businessUnit>
<inboundChannel>test</inboundChannel>
<timestamp>timestamp</timestamp>
<requestedDocuments>
<ns2:document>orderForm</ns2:document>
</requestedDocuments>
</orderControl>
</orderEntry>
</order>
the resulting xml should look like this:
<order
xmlns="some.url.02"
xmlns:ns2="some.other.url.02"
xmlns:ns3="another.one.02"
>
<orderEntry>
<orderControl>
<mandant>test</mandant>
<businessUnit>test</businessUnit>
<inboundChannel>test</inboundChannel>
<!-- deleted timestamp for example -->
<requestedDocuments>
<ns2:document>orderForm</ns2:document>
</requestedDocuments>
</orderControl>
</orderEntry>
</order>
but the only thing I get is:
<order
xmlns="some.url.02"
>
<orderEntry>
<orderControl>
<mandant>test</mandant>
<businessUnit>test</businessUnit>
<inboundChannel>test</inboundChannel>
<!-- deleted timestamp for example -->
<requestedDocuments>
<ns2:document xmlns:ns2="some.other.url.02">orderForm</ns2:document>
</requestedDocuments>
</orderControl>
</orderEntry>
</order>
Now maybe for one or two of you it might not be that big a deal, but I have the restriction that the output document should look one-to-one the same as the input document except for the requested changes (namespace changes and deletion).
My XSLT looks a like this:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="some.url.02"
xmlns:ns2="some.other.url.02"
xmlns:ns3="another.one.02"
>
<xsl:output method="xml" version="1.0" encoding="UTF-8" standalone="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:choose>
<xsl:when test="name(.) != 'timestamp'">
<xsl:element name="{node-name(.)}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{node-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Can somebody please help? Namespaces are tricky :(
P.S.: Whoever edited my entry: Thanks :)
You can set the namespace on the output element with the namespace attribute:
<xsl:element name="{node-name(.)}" namespace="http://www.bar.org">
// ...
</xsl:element>
Note that the namespace must be a URI and although I expect you know this it's probably a good idea to use URIs in your example.
Here is a link to the excellent ZVON tutorial which has worked examples:
http://www.zvon.org/xxl/XSLTreference/Output/xslt_element_namespace.html
I agree that namespaces are tricky. As you know the prefix is semantically irrelevant, but many systems allow you to choose your prefix for aesthetic reasons. Also look at Saxon (http://saxon.sourceforge.net/)
EDIT I think you will find your answer here:
XSLT root tag namespace instead of element attribute namespace
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns1_src="some.url.01"
xmlns:ns2_src="some.other.url"
xmlns:ns3_src="another.one"
xmlns="some.url.02"
xmlns:ns2="some.other.url.02"
xmlns:ns3="another.one.02"
>
<!--
Note that all the source namespaces got their own new "*_src" prefix.
The target namespaces take over the original prefixes.
"some.url.02" is the new global namespace.
-->
<xsl:output method="xml" version="1.0" encoding="UTF-8" standalone="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- the identity template to copy everything, unless
it has been declared otherwise -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<!-- three templates to handle elements -->
<xsl:template match="ns1_src:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node() | #*" />
</xsl:element>
</xsl:template>
<xsl:template match="ns2_src:*">
<xsl:element name="ns2:{local-name()}">
<xsl:apply-templates select="node() | #*" />
</xsl:element>
</xsl:template>
<xsl:template match="ns3_src:*">
<xsl:element name="ns3:{local-name()}">
<xsl:apply-templates select="node() | #*" />
</xsl:element>
</xsl:template>
<!-- three templates to handle attributes -->
<xsl:template match="#ns1_src:*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:template>
<xsl:template match="#ns2_src:*">
<xsl:attribute name="ns2:{local-name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:template>
<xsl:template match="#ns3_src:*">
<xsl:attribute name="ns3:{local-name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:template>
<!-- timestamps will be ignored -->
<xsl:template match="ns1_src:timestamp" />
</xsl:stylesheet>
Output:
<order xmlns="some.url.02">
<orderEntry>
<orderControl>
<mandant>test</mandant>
<businessUnit>test</businessUnit>
<inboundChannel>test</inboundChannel>
<requestedDocuments>
<ns2:document xmlns:ns2="some.other.url.02">orderForm</ns2:document>
</requestedDocuments>
</orderControl>
</orderEntry>
</order>
<xsl:template match="a:*">
<xsl:element name="{local-name()}"
namespace="http://example.com/B">
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
It searches for any element in namespace with prefix a and replaces it with an element with the same name of namespace http://example.com/B. All attributes are copied 'as is' and then all children are evaluated.
Add your custom processing in or around that as needed.
Are you using Ant's XSLT task to do your transformation?
If the answer is yes, you may want to switch from the default XSLT engine that comes with Sun JDK 1.5+. Read this.
Also, read this article about namespaces in XSLT