• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 240
  • Last Modified:

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

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
Molko
Asked:
Molko
  • 7
  • 6
1 Solution
 
Geert BormansInformation ArchitectCommented:
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
 
Geert BormansInformation ArchitectCommented:
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
 
MolkoAuthor Commented:
Thankyou, looks like a good starting point....
0
Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

 
MolkoAuthor Commented:
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
 
Geert BormansInformation ArchitectCommented:
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
 
MolkoAuthor Commented:
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
 
MolkoAuthor Commented:
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
 
Geert BormansInformation ArchitectCommented:
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
 
Geert BormansInformation ArchitectCommented:
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
 
MolkoAuthor Commented:
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
 
Geert BormansInformation ArchitectCommented:
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
 
MolkoAuthor Commented:
Many many thanks, that really was a great help/starting solution that helped me solve the problem.

Thanks

M
0
 
Geert BormansInformation ArchitectCommented:
welcome
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 7
  • 6
Tackle projects and never again get stuck behind a technical roadblock.
Join Now