replace namespace prefix and add attributes using XSLT - xslt

suppose I have following XML as source:
<ns0:msg xmlns:ns0="namespace0">
<ns0:hdr a_lot_of_attrs="value">
some nodes...
</ns0:hdr>
<ns0:body>
<ns0:data a_lot_of_attrs="value">
<ns1:purchase_order xmlns:ns1="namespace1">some nodes...</ns1:purchase_order>
</ns0:data>
</ns0:body>
</ns0:msg>
And I need following XML as the result:
<a:msg xmlns:a="namespace0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<a:hdr a_lot_of_attrs="value">
some nodes...
</a:hdr>
<a:body>
<a:data a_lot_of_attrs="value">
<b:purchase_order
xsi:schemaLocation="filelocation"
xmlns:b="namespace1"
xmlns:c="namespace2">some nodes...</b:purchase_order>
</a:data>
</a:body>
</a:msg>
Basically I need just to replace the namespace prefix ns0 into a and ns1 into b. Further more, the root element <a:msg> as well as the <b:purchase_order> need to be added by some additional attributes.
My attemp is by using following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="namespace0"
xmlns:ns1="namespace1"
xmlns:a="namespace0"
xmlns:b="namespace1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="ns0 ns1">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<a:msg xmlns:msg="namespace1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:copy>
<xsl:apply-templates select="ns0:msg/*"/>
</xsl:copy>
</a:msg>
</xsl:template>
<xsl:template match="/ns0:msg/ns0:body/ns0:data/ns1:purchase_order">
<b:purchase_order xsi:schemaLocation="filelocation" xmlns:c="namespace2">
<xsl:copy>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</b:purchase_order>
</xsl:template>
<xsl:template match="ns0:*">
<xsl:element name="a:{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="ns1:*">
<xsl:element name="b:{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
It works fine so far, except the node <purchase_order> has been populated 2 times:
<a:msg xmlns:a="namespace0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<a:hdr a_lot_of_attrs="value">
some nodes...
</a:hdr>
<a:body>
<a:data a_lot_of_attrs="value">
<b:purchase_order xmlns:b="namespace1" xmlns:c="namespace2" xsi:schemaLocation="filelocation">
<ns1:purchase_order xmlns:ns0="namespace0" xmlns:ns1="namespace1">some nodes...</ns1:purchase_order>
</b:purchase_order>
</a:data>
</a:body>
</a:msg>
I tried several times by tweaking the second <xsl:template> but could not get it right. Would someone pls advise where I get wrong here and how can I get this done?
Thanks a lot.

You current template that matches ns1:purchase_order contains an xsl:copy as well as the creation of the new b:purchase_order. Therefore you are copying the old node as well as creating a new one.
You can remove the xsl:copy from the template, like so:
<xsl:template match="ns1:purchase_order">
<b:purchase_order xsi:schemaLocation="filelocation" xmlns:c="namespace2">
<xsl:apply-templates select="node()"/>
</b:purchase_order>
</xsl:template>
Note that you don't necessarily have to specify the full path in the template match. You would only really need to do this if you had a second ns1:purchase_order in the XML, at a different location, that you didn't want to match.

Related

How do I match the elements generated by xsl-fo?

I have a .xsl file like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:exslt="http://exslt.org/common">
<xsl:template match="/>
<fo:root>
<fo:block>...</fo:block>
</fo:root>
</xsl:template>
</xsl:stylesheet>
How can I use templates to match and style the generated fo elements? For example, if I want to give my fo:table-cells red backgrounds, I'd like to be able to do
<xsl:template match="fo:table-cell">
<xsl:attribute name="background-color">red</xsl:attribute>
</xsl:template>
I found this and then tried something along the lines of
<xsl:template match="/>
<xsl:variable name="foRoot">
<fo:root>
<fo:block>...</fo:block>
</fo:root>
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($foRoot)" />
</xsl:template>
but this results in a stack overflow due to endless recursion. When I try to avoid this, for example by doing
<xsl:apply-templates select="exslt:node-set($foRoot)/*" />
I get an empty document. When trying to fix that by adding
<xsl:copy-of select="$foRoot" />
right after, I don't get any errors but the table-cells still have a default white background.
If you really use an XSLT 2 processor then first of all you don't need exsl:node-set.
As for your template
<xsl:template match="fo:table-cell">
<xsl:attribute name="background-color">red</xsl:attribute>
</xsl:template>
that would match a FO table-cell but transform it into an attribute. So you rather want
<xsl:template match="fo:table-cell">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="background-color">red</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
as that adds the attribute to a shallow copy of the element and then keeps processing alive for child elements with apply-templates.
Of course you will also need to add the identity transformation template
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
to make sure the elements you don't want to change are copied through. It might be necessary to use modes to separate processing steps if the other templates you have interfere with the identity transformation.

Updating XML node using XSLT

<Instance xsi:type="ButtonConfig">
<Name>ExitButton</Name>
<Height>89</Height>
<Width>120</Width>
<Margin>
<All>-1</All>
<Bottom>0</Bottom>
<Left>400</Left>
<Right>0</Right>
<Top>11</Top>
</Margin>
</Instance>
In the above xml, I need to change the Left Margin to 420. How do I do it using XSLT?
This is almost the “identify transform”, which simply duplicates the input document.
Here’s a simple stylesheet that mostly performs the identity transform, while overriding the output for a <Left/> within a <Margin/> within an <Instance/> that has a <Name/> containing ExitButton. Note that I had to add a namespace definition to your input XML for xsi, which I assume is elsewhere in the document.
<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="Margin/Left[ancestor::Instance/Name[text()='ExitButton']]">
<Left>420</Left>
</xsl:template>
</xsl:stylesheet>
As any XSLT tutorial would tell you: For something simple like this, start with the identity stylesheet, which copies the document essentially unchanged... then add a template which implements the exception to that.
<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="Margin/Left">
<xsl:copy>
<xsl:text>420</xsl:text>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Sort Using XSLT

I need to sort the below xml by
/parentList/employeesList/personalInfo/middleName
using XSLT. I'm unable to generate the format I'm expecting.The one with Middle name "John" should come up in the output xml. Can you someone help me on this. Below are my XML and XSL's
XML :
<parentList>
<employeesList>
<personalInfo>
<middleName>Mike</middleName>
<lastName>S</lastName>
</personalInfo>
<mailingAddress>
<postalCode>12345</postalCode>
<cityName>CoEmployee CityName</cityName>
<state>PA</state>
<addressLineText>CoEmployee Full Address</addressLineText>
</mailingAddress>
</employeesList>
<employeesList>
<personalInfo>
<middleName>John</middleName>
<lastName>G</lastName>
</personalInfo>
<mailingAddress>
<postalCode>12345</postalCode>
<cityName>CoEmployee CityName</cityName>
<state>PA</state>
<addressLineText>CoEmployee Full Address</addressLineText>
</mailingAddress>
</employeesList>
</parentList>
XSL:
<xsl:template match="parentList">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="/employeesList/personalInfo/middleName" order="ascending"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
You need to put the sort inside a for-each:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="parentList">
<xsl:for-each select="employeesList">
<xsl:sort select="personalInfo/middleName" order="ascending"/>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The problem is in the XPath expression that you are using in your select attribute. It's an absolute expression (starts with /) but there your root element is not employeesList.
When you match parentList, you apply templates to its children, which provide the context for the sort element. That means that a relative XPath expression in the select attribute of sort should be in the context of employeesList.
It should work if you simply change your template to this:
<xsl:template match="parentList">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="personalInfo/middleName" order="ascending"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>

Wrap some fields in CDATA but not others OF THE SAME NAME

Forking from a previous question I asked, how do I ensure that example/one/field and example/three/field are enclosed in CDATA whilst example/two/field is not?
Input:
<?xml version="1.0"?>
<example>
<one>
<field>CDATA required here</field>
</one>
<two>
<field>No CDATA thanks</field>
</two>
<three>
<field>More CDATA please</field>
</three>
</example>
Required output:
<?xml version="1.0"?>
<example>
<one>
<field><![CDATA[CDATA required here]]></field>
</one>
<two>
<field>No CDATA thanks</field>
</two>
<three>
<field><![CDATA[More CDATA please]]></field>
</three>
</example>
I could specify <xsl:output cdata-section-elements="field"/> but this will affect example/two/field as well. I have tried putting in a path like <xsl:output cdata-section-elements="example/one/field example/three/field"/> but this produces an error (Error XTSE0280: Invalid element name. Invalid QName {example/one/field}). Where am I going wrong?
With your current markup I don't think there is a clean way with XSLT. You would need to use different element names or different namespaces at least to allow you and the XSLT processor's serializer to distinguish which elements to output as CDATA sections and which not.
Or you would need to consider to use disable-output-escaping e.g.
<xsl:template match="one/field | three/field">
<xsl:copy>
<xsl:text disable-output-escaping="yes"><![CDATA[<![CDATA[]]></xsl:text>
<xsl:value-of select="."/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
[edit]
Here is a complete sample 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="one/field | three/field">
<xsl:copy>
<xsl:text disable-output-escaping="yes"><![CDATA[<![CDATA[]]></xsl:text>
<xsl:value-of select="."/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note however that disable-output-escaping is an optional serialization feature that is not supported by all XSLT processors.

XSLT: Add namespace to root element

I need to change namespaces in the root element as follows:
input document:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd"
xmlns:ns2="http://www.w3.org/1999/xlink" xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
desired output:
<foo audience="external" xsi:schemaLocation="urn:isbn:1-931666-22-9
http://www.loc.gov/ead/ead.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="urn:isbn:1-931666-22-9">
I was trying to do it as I copy over the whole document and before I give any other transformation instructions, but the following doesn't work:
<xsl:template match="* | processing-instruction() | comment()">
<xsl:copy copy-namespaces="no">
<xsl:for-each select=".">
<xsl:attribute name="audience" select="'external'"/>
<xsl:namespace name="xlink" select="'http://www.w3.org/1999/xlink'"/>
</xsl:for-each>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
Thanks for any advice!
XSLT 2.0 isn't necessary to solve this problem.
Here is an XSLT 1.0 solution, which works equally well as XSLT 2.0 (just change the version attribute to 2.0):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink"
exclude-result-prefixes="xlink"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select=
"namespace::*
[not(name()='ns2')
and
not(name()='')
]"/>
<xsl:copy-of select=
"document('')/*/namespace::*[name()='xlink']"/>
<xsl:copy-of select="#*"/>
<xsl:attribute name="audience">external</xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on this XML document:
<foo
xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd"
xmlns:ns2="http://www.w3.org/1999/xlink"
xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
the wanted result is produced:
<foo xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd"
audience="external"/>
You should really be using the "identity template" for this, and you should always have it on hand. Create an XSLT with that template, call it "identity.xslt", then into the current XSLT. Assume the prefix "bad" for the namespace you want to replace, and "good" for the one you want to replace it with, then all you need is a template like this (I'm at work, so forgive the formatting; I'll get back to this when I'm at home): ... If that doesn't work in XSLT 1.0, use a match expression like "*[namespace-uri() = 'urn:bad-namespace'", and follow Dimitre's instructions for creating a new element programmatically. Within , you really need to just apply-template recursively...but really, read up on the identity template.