Solved

XSLT - Transform one XML document to another "very similar" structure

Posted on 2013-01-09
13
225 Views
Last Modified: 2013-01-25
Hi

I have a feeling this is very straightforward becuase the source and destination documents are so similar.

I'm looking for an XSLT transform that will do work, i've been working on a few but have scrapped them because the were so verbose....i feel there is an elegant solution that is just escaping me. So any help would really be appreciated...

Ive shown the XML below, the subtle differences are in the way the numbers and the address have been separated out and also the introduction of an xxxComment element which can just have any random value in at the moment.

many  thanks for any help

Source :

<?xml version="1.0" encoding="UTF-8"?>
<Customers>
	<Type>
		<Name>Purchases</Name>
		<Date>June</Date>
		<id>100</id>
	</Type>
	<Detail>
		<Name>
			<FirstName>Gary</FirstName>
			<LastName>Smith</LastName>
		</Name>
		<Number>
			<Mobile>07712312313</Mobile>
		</Number>
		<Address>
			<Home>
				<Number>12</Number>
				<Street>Fleet Street</Street>
				<County>This county</County>
				<Postcode>A Postcode</Postcode>
				<Country>UK</Country>
			</Home>
		</Address>
	</Detail>
	<Detail>
		<Name>
			<FirstName>Mary</FirstName>
			<LastName>Jones</LastName>
		</Name>
		<Number>
			<Mobile>123456</Mobile>
			<Home>67890</Home>
			<Work>145678</Work>
		</Number>
		<Address>
			<Home>
				<Number>22</Number>
				<Street>Fleet Street</Street>
				<County>This county</County>
				<Postcode>A Postcode</Postcode>
				<Country>UK</Country>
			</Home>
		</Address>
	</Detail>
</Customers>

Open in new window


Destination

<?xml version="1.0" encoding="UTF-8"?>
<Customers>
	<Type>
		<Name>Purchases</Name>
		<Date>June</Date>
		<id>100</id>
	</Type>
	<Detail>
		<CustomerComment value="abc" type="1"/>
		<Name>
			<NameComment value="abc" type="1"/>
			<FirstName>Gary</FirstName>
			<LastName>Smith</LastName>
		</Name>
		<Number>
			<NumberComment value="abc" type="1"/>
			<Number>07712312313</Number>
			<Type>Mobile</Type>
		</Number>
		<Address>
			<AddressComment value="abc" type="1"/>
			<Home>
				<Number>12</Number>
				<Street>Fleet Street</Street>
				<County>This county</County>
				<Postcode>A Postcode</Postcode>
				<Country>UK</Country>
			</Home>
		</Address>
	</Detail>
	<Detail>
		<CustomerComment value="abc" type="2"/>
		<Name>
			<NameComment value="abc" type="2"/>
			<FirstName>Mary</FirstName>
			<LastName>Jones</LastName>
		</Name>
		<Number>
			<Number>
				<NumberComment value="abc" type="2"/>
				<Number>123456</Number>
				<Type>Mobile</Type>
			</Number>
			<Number>
				<NumberComment value="abc" type="2"/>
				<Number>67890</Number>
				<Type>Home</Type>
			</Number>
			<Number>
				<NumberComment value="abc" type="2"/>
				<Number>145678</Number>
				<Type>Work</Type>
			</Number>
		</Number>
		<Address>
			<AddressComment value="abc" type="2"/>
			<Type>Home</Type>
			<Number>22</Number>
			<Street>Fleet Street</Street>
			<County>This county</County>
			<Postcode>A Postcode</Postcode>
			<Country>UK</Country>
		</Address>
		<Address> <!-- If there was a work address-->
			<AddressComment value="abc" type="2"/>
			<Type>Work</Type>
			<Number>22</Number>
			<Street>Fleet Street</Street>
			<County>This county</County>
			<Postcode>A Postcode</Postcode>
			<Country>UK</Country>
		</Address>
	</Detail>
</Customers>

Open in new window

0
Comment
Question by:Molko
  • 7
  • 6
13 Comments
 
LVL 60

Expert Comment

by:Geert Bormans
Comment Utility
You need an identity copy with some templates added for specilisations

This will get you started

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>
    
    <xsl:template match="node()">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Detail">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <CustomerComment value="abc">
                <xsl:attribute name="type">
                    <xsl:number count="Detail"/>
                </xsl:attribute>
            </CustomerComment>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Detail/Number/*">
        <Number>
            <CustomerComment value="abc">
                <xsl:attribute name="type">
                    <xsl:number count="Detail"/>
                </xsl:attribute>
            </CustomerComment>
            <Number><xsl:value-of select="."/></Number>
            <Type><xsl:value-of select="name()"/></Type>
        </Number>
    </xsl:template>
    
 

Open in new window

0
 
LVL 60

Expert Comment

by:Geert Bormans
Comment Utility
The processing of the first and second Detail are not consistent in your result description. But I think the above can get you started anyway
0
 

Author Comment

by:Molko
Comment Utility
Thankyou, looks like a good starting point....
0
 

Author Comment

by:Molko
Comment Utility
Hi

Thanks, that was a good starting point and has got me on my way, I can see your solution - thankyou.

I have another related question (I actually made an error in my original question), I would like the output be modified as such

<NEW_Customer id ="100"> rather than <Customers>

So it would look like


<NEW_Customer id=100>
	<Type>
.....
....
        </NEW_Customers>

Open in new window


I dont know how to intercept your initial copy-of statement and replace "Customers" with NEW_Customer and also add the attribute

Thanks in advance
0
 
LVL 60

Expert Comment

by:Geert Bormans
Comment Utility
Each element that is not captured by a specific template, will be captured by this one

   <xsl:template match="node()">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>
 
If you add a template for a specific element, the above template will no longer hold for that specific element

So by simply adding something like this
 
  <xsl:template match="/Customers">
        <NEW_Customer id="100">
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </NEW_Customer>

you will get what you need
0
 

Author Comment

by:Molko
Comment Utility
Thanks, I did try something similar prior to you response as I figured that was the way go.

It does work, but.....actually XML record starts like

<?xml version="1.0" encoding="UTF-8"?>
<DATA xmlns="urn:DATA " xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:data="urn:DATA " xmlns:xs="http://www.w3.org/2001/XMLSchema">
<Customers>
.....
....
</Customers>

Open in new window


Which mean the transform does not appear to work, however if i remove the above and just have the raw XML (as i originally supplied) then is appears to work

So i am not really sure what is going on.
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 

Author Comment

by:Molko
Comment Utility
To clarify

With the namspace declarations, it appears the original template is not matching on anything specific,  so it just ends up copy the original document verbatim

If i remove the namespace declarations then is works

How do i accomodate the namespace
0
 
LVL 60

Expert Comment

by:Geert Bormans
Comment Utility
aha, I assume you get an exact copy of the source XML?

There is a namespace involved xmlns="urn:DATA
which means that by default all elements are in that namespace

Here is what you need to do

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns="urn:DATA "
     xmlns:dta="urn:DATA "
    version="1.0">

Now all nodes in the result tree will be in the same namespace
and all elements that get a dta: prefix will be understood to be from your source XML

This means that you need to change all templates

eg.

<xsl:template match="dta:Detail">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <CustomerComment value="abc">
                <xsl:attribute name="type">
                    <xsl:number count="dta:Detail"/>
                </xsl:attribute>
            </CustomerComment>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>

note the added dta:
you need to do that for all templates you have
0
 
LVL 60

Expert Comment

by:Geert Bormans
Comment Utility
OK, I was writing so I missed your last message.
That one just confirms what I thought, so happy to make the right analysis, means that the proposed solution is right too
0
 

Author Comment

by:Molko
Comment Utility
Thanks, the namespace qualification did trick....thankyou

I have one last question if you dont mind :-)

I understand the logic flow, in that each element that is not processed by specific template, will be captured by :

   <xsl:template match="node()">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>

And it works great :-)

How would i modify sub element so that, for example

--- source --
            <Address>
                  <Home>
                        <Number>12</Number>
                        <Street>Fleet Street</Street>
                        <County>This county</County>
                        <Postcode>A Postcode</Postcode>
                        <Country>UK</Country>
                  </Home>
            </Address>

--- destination ---

            <Address>
                  <Home>
                        <Number>12</Number>
                        <NEW-Street>Fleet Street</Street>
                        <County>This county</County>
                        <Postcode>A Postcode</Postcode>
                        <Country>UK</Country>
                  </Home>
            </Address>


I guess i would need a new template to catch the Address, but how would I transform 'Street' to 'NEW-Street', would i need a futher template that  matches on 'Address/Street' ?

    <xsl:template match="Address">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>

Thanks
0
 
LVL 60

Accepted Solution

by:
Geert Bormans earned 500 total points
Comment Utility
You don't need a template for Adress, you need one for street

<xsl:template match="Address/*/Street">
     <NEW-street>
          <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="node()"/>
....

if you want this for all Street

If you only want this to happen for Home Streets

<xsl:template match="Address/Home/Street">
     <NEW-street>
          <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="node()"/>
...
0
 

Author Comment

by:Molko
Comment Utility
Many many thanks, that really was a great help/starting solution that helped me solve the problem.

Thanks

M
0
 
LVL 60

Expert Comment

by:Geert Bormans
Comment Utility
welcome
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
.NET Xpath read sequence 5 43
SQL Remove Elements from XML 6 39
[PHP] Hash a XML file/string 3 57
XML error 2 41
The Problem How to write an Xquery that works like a SQL outer join, providing placeholders for absent data on the outer side?  I give a bit more background at the end. The situation expressed as relational data Let’s work through this.  I’ve …
Many times as a report developer I've been asked to display normalized data such as three rows with values Jack, Joe, and Bob as a single comma-separated string such as 'Jack, Joe, Bob', and vice versa.  Here's how to do it. 
This video discusses moving either the default database or any database to a new volume.
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

763 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now