I'm relatively new to XSL and am attempting to elegantly transform a Google Calendar feed into something more readable.
I would appreciate your eyes on whether there are optimizations to be made. In particular, I would like your advice on template use. I've read a lot about how for-each is not appropriate to use willy-nilly (rather, one should attempt to make judicious use of templates).
Thank you very much.
Original XML (showing only one event):
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'>
<id>http://www.google.com/calendar/feeds/bachya1208%40gmail.com/public/full</id>
<updated>2011-09-19T21:32:50.000Z</updated>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>
<title type='text'>John Doe</title>
<subtitle type='text'>John Doe</subtitle>
<link rel='alternate' type='text/html' href='https://www.google.com/calendar/embed?src=bachya1208#gmail.com'/>
<link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full'/>
<link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/batch'/>
<link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full?max-results=25'/>
<link rel='next' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full?start-index=26&max-results=25'/>
<author>
<name>John Doe</name>
<email>johndoe#gmail.com</email>
</author>
<generator version='1.0' uri='http://www.google.com/calendar'>Google Calendar</generator>
<openSearch:totalResults>1334</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>25</openSearch:itemsPerPage>
<gCal:timezone value='America/Denver'/>
<gCal:timesCleaned value='0'/>
<entry>
<id>http://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds</id>
<published>2011-09-14T21:15:16.000Z</published>
<updated>2011-09-14T21:15:16.000Z</updated>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>
<title type='text'>Oil Change</title>
<content type='text'/>
<link rel='alternate' type='text/html' href='https://www.google.com/calendar/event?eid=bHAwdXBucG5kbmtwMHJ1cWh0N2VmODRrZHMgYmFjaHlhMTIwOEBt' title='alternate'/>
<link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds'/>
<author>
<name>John Doe</name>
<email>johndoe#gmail.com</email>
</author>
<gd:comments>
<gd:feedLink href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds/comments'/>
</gd:comments>
<gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/>
<gd:where valueString='9955 E Arapahoe Road, Englewood, CO 80112 (Go Subaru Arapahoe)'/>
<gd:who email='johndoe#gmail.com' rel='http://schemas.google.com/g/2005#event.organizer' valueString='bachya1208#gmail.com'/>
<gd:when endTime='2011-09-29T11:30:00.000-06:00' startTime='2011-09-29T10:30:00.000-06:00'/>
<gd:transparency value='http://schemas.google.com/g/2005#event.opaque'/>
<gCal:anyoneCanAddSelf value='false'/>
<gCal:guestsCanInviteOthers value='true'/>
<gCal:guestsCanModify value='false'/>
<gCal:guestsCanSeeGuests value='true'/>
<gCal:sequence value='0'/>
<gCal:uid value='lp0upnpndnkp0ruqht7ef84kds#google.com'/>
</entry>
</feed>
XSLT:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template name="formatDateTime">
<xsl:param name="dateTime" />
<xsl:value-of select="concat(substring-before($dateTime, 'T'), ' ', substring-before(substring-after($dateTime, 'T'), '.'))" />
</xsl:template>
<xsl:template match="/">
<Events>
<xsl:apply-templates select="/*/*[local-name()= 'entry']" />
</Events>
</xsl:template>
<xsl:template match="*[local-name()= 'entry']">
<xsl:variable name="startDateTime" select="*[name() = 'gd:when']/#*[local-name() = 'startTime']" />
<xsl:variable name="endDateTime" select="*[name() = 'gd:when']/#*[local-name() = 'endTime']" />
<Event>
<EventTitle>
<xsl:value-of select="*[local-name() = 'title'][1]" />
</EventTitle>
<StartDateTime>
<xsl:call-template name="formatDateTime">
<xsl:with-param name="dateTime" select="$startDateTime" />
</xsl:call-template>
</StartDateTime>
<EndDateTime>
<xsl:call-template name="formatDateTime">
<xsl:with-param name="dateTime" select="$endDateTime" />
</xsl:call-template>
</EndDateTime>
<Who>
<xsl:value-of select="*[local-name() = 'author']/*[local-name() = 'name']" />
</Who>
<Where>
<xsl:value-of select="*[name() = 'gd:where']/#*[local-name() = 'valueString']" />
</Where>
<Status>
<xsl:value-of select="*[name() = 'gd:eventStatus']/#*[local-name() = 'value']" />
</Status>
</Event>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-16"?>
<Events>
<Event>
<EventTitle>Oil Change</EventTitle>
<StartDateTime>2011-09-29 10:30:00</StartDateTime>
<EndDateTime>2011-09-29 11:30:00</EndDateTime>
<Who>John Doe</Who>
<Where>9955 E Arapahoe Road, Englewood, CO 80112 (Go Subaru Arapahoe)</Where>
<Status>http://schemas.google.com/g/2005#event.confirmed</Status>
</Event>
</Events>
Your approach looks fine to me. I think your XPath code would be much cleaner and would probably run faster if you used regular element selection instead of local-name. The reason you probably struggled with your XPath was because you're consuming XML that has a default namespace of http://www.w3.org/2005/Atom, and that namespace isn't declared in your stylesheet. Here's a snippet of how a more simplified stylesheet could look, using an f: prefix for the feed namespace:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://www.w3.org/2005/Atom"
xmlns:gd="http://schemas.google.com/g/2005">
<!-- ... -->
<xsl:template match="/">
<Events>
<xsl:apply-templates select="//f:entry" />
</Events>
</xsl:template>
<xsl:template match="f:entry">
<xsl:variable name="startDateTime" select="gd:when/#startTime" />
<xsl:variable name="endDateTime" select="gd:when/#endTime" />
<Event>
<EventTitle>
<xsl:value-of select="f:title[1]" />
</EventTitle>
<StartDateTime>
<xsl:call-template name="formatDateTime">
<xsl:with-param name="dateTime" select="$startDateTime" />
</xsl:call-template>
</StartDateTime>
<EndDateTime>
<xsl:call-template name="formatDateTime">
<xsl:with-param name="dateTime" select="$endDateTime" />
</xsl:call-template>
</EndDateTime>
<Who>
<xsl:value-of select="f:author/f:name" />
</Who>
<Where>
<xsl:value-of select="gd:where/#valueString" />
</Where>
<Status>
<xsl:value-of select="gd:eventStatus/#value" />
</Status>
</Event>
</xsl:template>
<!-- etc -->
</xsl:stylesheet>
I would be inclined to replace the formatDateTime template with a match template:
<xsl:template match="#*" mode="formatDateTime">
<xsl:value-of select="concat(substring-before(., 'T'),
' ', substring-before(substring-after(., 'T'), '.'))" />
</xsl:template>
and change the calls to
<StartDateTime>
<xsl:apply-templates select="$startDateTime" mode="formatDateTime"/>
</StartDateTime>
<EndDateTime>
<xsl:apply-templates select="$endDateTime" mode="formatDateTime"/>
</EndDateTime>
Just because the call-template syntax is so verbose.
(and I would probably inline the variables too - they don't see to add value).
Here is a complete transformation that is derived from the provided, solving the default namespace problem (as already done by #Jacob), but also completely eliminating the unnecessary template matching the document node (/) and assuring that two unwanted namespaces will not appear on every (literal result) element in the output:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="http://www.w3.org/2005/Atom"
xmlns:gd="http://schemas.google.com/g/2005"
exclude-result-prefixes="a gd">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="a:entry[1]">
<Events>
<xsl:apply-templates select="../a:entry" mode="process"/>
</Events>
</xsl:template>
<xsl:template match="a:entry" mode="process">
<xsl:variable name="startDateTime" select="gd:when/#startTime" />
<xsl:variable name="endDateTime" select="gd:when/#endTime" />
<Event>
<EventTitle>
<xsl:value-of select="a:title[1]" />
</EventTitle>
<StartDateTime>
<xsl:call-template name="formatDateTime">
<xsl:with-param name="dateTime" select="$startDateTime" />
</xsl:call-template>
</StartDateTime>
<EndDateTime>
<xsl:call-template name="formatDateTime">
<xsl:with-param name="dateTime" select="$endDateTime" />
</xsl:call-template>
</EndDateTime>
<Who>
<xsl:value-of select="a:author/a:name" />
</Who>
<Where>
<xsl:value-of select="gd:where/#valueString" />
</Where>
<Status>
<xsl:value-of select="gd:eventStatus/#value" />
</Status>
</Event>
</xsl:template>
<xsl:template name="formatDateTime">
<xsl:param name="dateTime" />
<xsl:value-of select="concat(substring-before($dateTime, 'T'), ' ', substring-before(substring-after($dateTime, 'T'), '.'))" />
</xsl:template>
<xsl:template match="text()|a:entry"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'>
<id>http://www.google.com/calendar/feeds/bachya1208%40gmail.com/public/full</id>
<updated>2011-09-19T21:32:50.000Z</updated>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>
<title type='text'>John Doe</title>
<subtitle type='text'>John Doe</subtitle>
<link rel='alternate' type='text/html' href='https://www.google.com/calendar/embed?src=bachya1208#gmail.com'/>
<link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full'/>
<link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/batch'/>
<link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full?max-results=25'/>
<link rel='next' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full?start-index=26&max-results=25'/>
<author>
<name>John Doe</name>
<email>johndoe#gmail.com</email>
</author>
<generator version='1.0' uri='http://www.google.com/calendar'>Google Calendar</generator>
<openSearch:totalResults>1334</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>25</openSearch:itemsPerPage>
<gCal:timezone value='America/Denver'/>
<gCal:timesCleaned value='0'/>
<entry>
<id>http://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds</id>
<published>2011-09-14T21:15:16.000Z</published>
<updated>2011-09-14T21:15:16.000Z</updated>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/>
<title type='text'>Oil Change</title>
<content type='text'/>
<link rel='alternate' type='text/html' href='https://www.google.com/calendar/event?eid=bHAwdXBucG5kbmtwMHJ1cWh0N2VmODRrZHMgYmFjaHlhMTIwOEBt' title='alternate'/>
<link rel='self' type='application/atom+xml' href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds'/>
<author>
<name>John Doe</name>
<email>johndoe#gmail.com</email>
</author>
<gd:comments>
<gd:feedLink href='https://www.google.com/calendar/feeds/johndoe%40gmail.com/public/full/lp0upnpndnkp0ruqht7ef84kds/comments'/>
</gd:comments>
<gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/>
<gd:where valueString='9955 E Arapahoe Road, Englewood, CO 80112 (Go Subaru Arapahoe)'/>
<gd:who email='johndoe#gmail.com' rel='http://schemas.google.com/g/2005#event.organizer' valueString='bachya1208#gmail.com'/>
<gd:when endTime='2011-09-29T11:30:00.000-06:00' startTime='2011-09-29T10:30:00.000-06:00'/>
<gd:transparency value='http://schemas.google.com/g/2005#event.opaque'/>
<gCal:anyoneCanAddSelf value='false'/>
<gCal:guestsCanInviteOthers value='true'/>
<gCal:guestsCanModify value='false'/>
<gCal:guestsCanSeeGuests value='true'/>
<gCal:sequence value='0'/>
<gCal:uid value='lp0upnpndnkp0ruqht7ef84kds#google.com'/>
</entry>
</feed>
the wanted, correct result is produced:
<Events>
<Event>
<EventTitle>Oil Change</EventTitle>
<StartDateTime>2011-09-29 10:30:00</StartDateTime>
<EndDateTime>2011-09-29 11:30:00</EndDateTime>
<Who>John Doe</Who>
<Where>9955 E Arapahoe Road, Englewood, CO 80112 (Go Subaru Arapahoe)</Where>
<Status>http://schemas.google.com/g/2005#event.confirmed</Status>
</Event>
</Events>
Related
I need to replace the child node with another node without affecting its sub nodes, I tried matching the child node but was unable to
This is the xml format
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header>
<MessageId>{A124-B421-C325-D467}</MessageId>
<Action>find</Action>
</Header>
<Body>
<MessageParts xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Run xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/Run">
<RunObject class="entity">
<A1>NA</A1>
<A2>False</A2>
<Object class="entity">
<A3>02</A3>
</Object>
<A4>ER</A4>
</RunObject>
</Run>
</MessageParts>
</Body>
</Envelope>
This is the xml format that i require
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header>
<MessageId>{A124-B421-C325-D467}</MessageId>
<Action>find</Action>
</Header>
<Body>
<Document>
<Item>
<A1>NA</A1>
<A2>False</A2>
<Base>
<A3>02</A3>
</Base>
<A4>ER</A4>
</Item>
</Document>
</Body>
</Envelope>
This is the code that i through which i tried to change the format
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/Run"
exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- move all elements to no namespace -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<!-- rename MessageParts to Document + skip the Run wrapper -->
<xsl:template match="m:MessageParts">
<Document>
<xsl:apply-templates select="r:Run/*"/>
</Document>
</xsl:template>
<!-- rename RunObject to Item + reorder child nodes -->
<xsl:template match="r:RunObject[#class='entity']">
<Item>
<xsl:apply-templates select="r:A1" />
<xsl:apply-templates select="r:A2" />
<xsl:template match="r:Object[#class='entity']>
<Base>
<xsl:apply-templates select="r:A3" />
</Base>
</xsl:Template>
<xsl:apply-templates select="r:A4" />
</Item>
</xsl:template>
</xsl:stylesheet>
I tried matching the Object element but was unable to since i am already matching its parent element that is RunObject
Your mistake is that you cannot define a template inside of a template. So move the <xsl:template match="r:Object[#class='entity']> to the root level and add an <xsl:apply-templates select="r:Object" /> at its place.
The stylesheet could look like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message" xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/Run" exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- move all elements to no namespace -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<!-- rename MessageParts to Document + skip the Run wrapper -->
<xsl:template match="m:MessageParts">
<Document>
<xsl:apply-templates select="r:Run/*"/>
</Document>
</xsl:template>
<!-- rename RunObject to Item + reorder child nodes -->
<xsl:template match="r:RunObject[#class='entity']">
<Item>
<xsl:apply-templates select="r:A1" />
<xsl:apply-templates select="r:A2" />
<xsl:apply-templates select="r:Object" />
<xsl:apply-templates select="r:A4" />
</Item>
</xsl:template>
<xsl:template match="r:Object[#class='entity']">
<Base>
<xsl:apply-templates select="r:A3" />
</Base>
</xsl:template>
</xsl:stylesheet>
Its output is (nearly) as desired:
<?xml version="1.0" encoding="UTF-8"?>
<Envelope>
<Header>
<MessageId>{A124-B421-C325-D467}</MessageId>
<Action>find</Action>
</Header>
<Body>
<Document>
<Item>
<A1>NA</A1>
<A2>False</A2>
<Base>
<A3>02</A3>
</Base>
<A4>ER</A4>
</Item>
</Document>
</Body>
</Envelope>
I added the adjective "nearly", because this result has no namespace, while the sample of your desired outcome is in the namespace
xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
But because you defined a template that removes the namespaces of all elements, I did ignore that. I simply assume that this part of your question is erroneous.
If you like to change that, restrict the first template and add a general identity template:
<!-- move all elements to no namespace -->
<xsl:template match="*[namespace-uri() != 'http://schemas.microsoft.com/dynamics/2011/01/documents/Message']">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
This would make your output containing the root namespace:
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header>
<MessageId>{A124-B421-C325-D467}</MessageId>
<Action>find</Action>
</Header>
<Body>
<Document xmlns="">
<Item>
<A1>NA</A1>
<A2>False</A2>
<Base>
<A3>02</A3>
</Base>
<A4>ER</A4>
</Item>
</Document>
</Body>
</Envelope>
The XML output i am getting is not in the format that i require so tried some code to change the format but was unable to
This is the output that i am getting
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header>
<MessageId>{A124-B421-C325-D467}</MessageId>
<Action>find</Action>
</Header>
<Body>
<MessageParts xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Run xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/Run">
<RunObject class="entity">
<A1>NA</A1>
<A2>False</A2>
<A3>02</A3>
<A4>ER</A4>
</RunObject>
<RunObject class="entity">
<A1>NA</A1>
<A2>False</A2>
<A3>03</A3>
<A4>ER</A4>
</RunObject>
</Run>
</MessageParts>
</Body>
</Envelope>
This is the XML output that i require
<?xml version="1.0" encoding="UTF-8"?>
<Document>
<Item>
<A3>NA</A3>
<A4>False</A4>
<A2>02</A2>
<A1>ER</A1>
</Item>
<Item>
<A3>NA</A3>
<A4>False</A4>
<A2>03</A2>
<A1>ER</A1>
</Item>
</Document>
This is the code that i have used to change the format of the xml that i was getting
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/Run"
exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- move all elements to no namespace -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="m:Envelope"/>
<xsl:template match="m:Header"/>
<xsl:template match="m:MessageId"/>
<xsl:template match="m:Action"/>
<xsl:template match="m:Body"/>
<!-- rename MessageParts to Document + skip the Run wrapper -->
<xsl:template match="m:MessageParts">
<Document>
<xsl:apply-templates select="r:Run/*"/>
</Document>
</xsl:template>
<!-- rename RunObject to Item + reorder child nodes -->
<xsl:template match="r:RunObject[#class='entity']">
<Item>
<xsl:apply-templates select="r:A3" />
<xsl:apply-templates select="r:A4" />
<xsl:apply-templates select="r:A2" />
<xsl:apply-templates select="r:A1" />
</Item>
</xsl:template>
</xsl:stylesheet>
I tried the above code but was not able to change the format of the xml
Instead of:
<xsl:template match="m:Envelope"/>
that removes the Envelope node and all its descendants, you need to remove only the Envelope wrapper and continue processing its contents:
<xsl:template match="m:Envelope">
<xsl:apply-templates/>
</xsl:template>
Likewise for the other wrappers. In fact, you could replace all of these:
<xsl:template match="m:Envelope"/>
<xsl:template match="m:Header"/>
<xsl:template match="m:MessageId"/>
<xsl:template match="m:Action"/>
<xsl:template match="m:Body"/>
with:
<xsl:template match="m:*">
<xsl:apply-templates select="*"/>
</xsl:template>
How to rename the parent node and rearrange the child nodes simultaneously.
I tried some code but was unable to get the desired output.
This was the output that i was getting
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header> </Header>
<Body>
<MessageParts xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Run xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/Run">
<RunObject class="entity">
<A1>NA</A1>
<A2>False</A2>
<A3>02</A3>
<A4>ER</A4>
</RunObject>
<RunObject class="entity">
<A1>NA</A1>
<A2>False</A2>
<A3>03</A3>
<A4>ER</A4>
</RunObject>
</Run>
</MessageParts>
</Body>
</Envelope>
I wanted to remove the namespaces, rename the Parent tags and rearrange the child tags.
So i used this code
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/Run"
exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- move all elements to no namespace -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<!-- rename MessageParts to Document + skip the Run wrapper -->
<xsl:template match="m:MessageParts">
<Document>
<xsl:apply-templates select="r:Run/*"/>
</Document>
</xsl:template>
<xsl:template match="r:RunObject[#class='entity']">
<xsl:copy>
<xsl:apply-templates select="A3" />
<xsl:apply-templates select="A4" />
<xsl:apply-templates select="A2" />
<xsl:apply-templates select="A1" />
</xsl:copy>
</xsl:template>
<!-- rename RunObject to Item -->
<xsl:template match="r:RunObject[#class='entity']">
<Item>
<xsl:apply-templates />
</Item>
</xsl:template>
</xsl:stylesheet>
I was able to remove the namespaces, rename the parent tags but was unable to rearrange the child tags. The output i got was this
?xml version="1.0" encoding="UTF-8"?>
<Envelope>
<Header> </Header>
<Body>
<Document>
<Item>
<A1>NA</A1>
<A2>False</A2>
<A3>02</A3>
<A4>ER</A4>
</Item>
<Item>
<A1>NA</A1>
<A2>False</A2>
<A3>03</A3>
<A4>ER</A4>
</Item>
</Document>
</Body>
</Envelope>
But the desired output is this
<?xml version="1.0" encoding="UTF-8"?>
<Envelope>
<Header> </Header>
<Body>
<Document>
<Item>
<A3>02</A3>
<A4>ER</A4>
<A2>False</A2>
<A1>NA</A1>
</Item>
<Item>
<A3>03</A3>
<A4>ER</A4>
<A2>False</A2>
<A1>NA</A1>
</Item>
</Document>
</Body>
</Envelope>
Problem #1:
You have two templates matching the same nodes:
<xsl:template match="r:RunObject[#class='entity']">
Only the last one of these will be applied.
Problem #2:
The elements A1, A2, A3 and A4 are in a namespace inherited from their Run ancestor. You need to use the prefix bound to that namespace when selecting them.
See if this works for you:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/Run"
exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- move all elements to no namespace -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<!-- rename MessageParts to Document + skip the Run wrapper -->
<xsl:template match="m:MessageParts">
<Document>
<xsl:apply-templates select="r:Run/*"/>
</Document>
</xsl:template>
<!-- rename RunObject to Item + reorder child nodes -->
<xsl:template match="r:RunObject[#class='entity']">
<Item>
<xsl:apply-templates select="r:A3" />
<xsl:apply-templates select="r:A4" />
<xsl:apply-templates select="r:A2" />
<xsl:apply-templates select="r:A1" />
</Item>
</xsl:template>
</xsl:stylesheet>
INPUT XML
<root>
<file1>
<commodity>
<units>1</units>
<obj>mango</obj>
</commodity>
<commodity>
<units>5</units>
<obj>guava</obj>
</commodity>
</file1>
<file2>
<category>
<object>guava</object>
<type>CAT1</type>
<colour>green</colour>
</category>
<category>
<object>mango</object>
<type>CAT2</type>
<colour>yellow</colour>
</category>
</file2>
</root>
I need to compare the values of obj in file1 and object in file2 under root, if same I need to take their corresponding units, type and colour and produce the following output using xslt.
OUTPUT XML
<output>
<com>
<name>guava</name>
<num>5</num>
<category>CAT1</category>
<col>green</col>
</com>
<com>
<name>mango</name>
<num>1</num>
<category>CAT2</category>
<col>yellow</col>
</com>
</output>
I tried the below XSLT but the response is not as expected. Its not looping properly. Could you please tell me where I am going wrong.
<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" />
<xsl:key name="object-search" match="root/file1/commodity" use="obj" />
<xsl:template match="/">
<output>
<xsl:for-each select="key('object-search', //category/object)">
<com>
<name>
<xsl:value-of select="obj" />
</name>
<num>
<xsl:value-of select="units" />
</num>
<category>
<xsl:value-of
select="//root/file2/category/type" />
</category>
<col>
<xsl:value-of
select="//root/file2/category/colour" />
</col>
</com>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Try it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="cat" match="category" use="object" />
<xsl:template match="/root">
<output>
<xsl:for-each select="file1/commodity">
<com>
<name>
<xsl:value-of select="obj" />
</name>
<num>
<xsl:value-of select="units" />
</num>
<xsl:variable name="cat" select="key('cat', obj)" />
<category>
<xsl:value-of select="$cat/type" />
</category>
<col>
<xsl:value-of select="$cat/colour" />
</col>
</com>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Note that the result is slightly different from what you posted:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<com>
<name>mango</name>
<num>1</num>
<category>CAT2</category>
<col>yellow</col>
</com>
<com>
<name>guava</name>
<num>5</num>
<category>CAT1</category>
<col>green</col>
</com>
</output>
Alternatively, you could do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="com" match="commodity" use="obj" />
<xsl:template match="/root">
<output>
<xsl:for-each select="file2/category">
<xsl:variable name="com" select="key('com', object)" />
<com>
<name>
<xsl:value-of select="$com/obj" />
</name>
<num>
<xsl:value-of select="$com/units" />
</num>
<category>
<xsl:value-of select="type" />
</category>
<col>
<xsl:value-of select="colour" />
</col>
</com>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
and get:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<com>
<name>guava</name>
<num>5</num>
<category>CAT1</category>
<col>green</col>
</com>
<com>
<name>mango</name>
<num>1</num>
<category>CAT2</category>
<col>yellow</col>
</com>
</output>
I have a generic template I've designed with 2 params title and category.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:param name="category" />
<xsl:param name="title" />
<xsl:template name="test-group">
<fo:block>
<xsl:value=of select="$title" />
</fo:block>
<fo:block>
<xsl:value-of select="$category" />
</fo:block>
</xsl:template>
</xsl:stylesheet>
In the parent template I have the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:include href="templates/generic_template.xsl" />
...
<xsl:call-template name="test-group">
<xsl:with-param name="category" select="'animals'" />
<xsl:with-param name="title" select="'DOGS'" />
</xsl:call-template>
...
</xsl:stylesheet>
However, when the transform completes title and category are blank. I'm using FOP 2.0 so I'm not sure if this is a known shortcoming.
When defining a xsl:template that takes parameters, the parameter names used within the xsl:template should be declared using xls:param elements nested within.
<xsl:template name="test-group">
<xsl:param name="category" />
<xsl:param name="title" />
...
</xsl:template>
The parameters being attached to the xsl:template and not the xsl:stylesheet.
This is similar to when calling the template with xsl:call-template, except you are specifying the values instead using xsl:with-param.