Solved

XSLT Mapping

Posted on 2014-02-17
7
245 Views
Last Modified: 2014-02-19
Hi,

I am an amateur in XSLT transformation, could anyone please help me with the XSLT mapping, if possible please provide me the logic or a short code snippet and i can develop the rest. Attached are the source XML and the result XML.

MainDocument.xml is the source and MainDocument1.xml is the result after the mapping. I am trying to implement the result from source using XSLT.
MainDocument1.xml
MainDocument.xml
0
Comment
Question by:kalyangkm
  • 4
  • 3
7 Comments
 
LVL 35

Expert Comment

by:mccarl
ID: 39866527
It is actually a fairly tricky thing to do, to convert elements that are only related by their relative position into a heirarchy of elements. But the following should produce what you need. Let me know if there are any issues with it or if you have questions about it.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="Test">
   <xsl:output method="xml" indent="yes"/>
   
   <xsl:template match="/ns:SenderFile_MT">
      <ns:ReceiverFile_MT>
      <xsl:apply-templates select="Record/HEADER" />
      <xsl:apply-templates select="Record/ITEM" />
      <xsl:apply-templates select="Record/Trailer" />
      </ns:ReceiverFile_MT>
   </xsl:template>

   <xsl:template match="ITEM">
      <ITEM>
      <xsl:apply-templates />
      <xsl:apply-templates select="(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']" />
      </ITEM>
   </xsl:template>

   <xsl:template match="ItemDetail">
      <ItemDetail>
      <xsl:apply-templates />
      <xsl:apply-templates select="following-sibling::*[1][name() = 'Address']" />
      </ItemDetail>
      <xsl:apply-templates select="(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']" />
   </xsl:template>

   <xsl:template match="Address">
      <Address>
      <xsl:apply-templates />
      </Address>
      <xsl:apply-templates select="following-sibling::*[1][name() = 'Address']" />
   </xsl:template>
   
   <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Open in new window

0
 

Author Comment

by:kalyangkm
ID: 39868831
Hi Mccarl,

First of thanks for your continous responses (FYI, You solved my SAX parsing issue).

I got it working for single recordset but now i am trying to implement it for multiple Recordsets. Please check the attachement for detials. I have changed your code a bit but i am having difficulty in looping each record with Header as reference, so the result is that though the rest of the XML looks good, i am not able to achieve the same for Header and Trailer. Could you please suggest

I have attached the source xml with 2 recordsets (with multiple Header, item, and tailer combinations) and the your code with my updates.
MainDocument-Actual.xsl
MainDocument-Result.xml
MainDocument-Multirec.xml
0
 
LVL 35

Accepted Solution

by:
mccarl earned 500 total points
ID: 39869024
It looks like we are making progress then..

However, it seems that you forgot to tell me what the expected output should look like when there are more than 1 <Record> element in the input. I can think of 2 possibilities off the top of my head, but you really need to tell me exactly what you want.

The first one, is that you would actually like a separate <message> element for each <Record> in the input. If so, you could try this...
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="Test">
	<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/ns:SenderFile_MT">
		<ns0:Messages xmlns:ns0="http://sap.com/xi/XI/SplitAndMerge">
		  <xsl:for-each select="Record">
			<ns0:Message>
				<ns1:ReceiverFile_MT xmlns:ns1="Test">
       			        <xsl:apply-templates select="HEADER"/>
       			        <xsl:apply-templates select="ITEM"/>
       			        <xsl:apply-templates select="Trailer"/>
				</ns1:ReceiverFile_MT>
			</ns0:Message>
		  </xsl:for-each>
		</ns0:Messages>
	</xsl:template>
	<xsl:template match="ITEM">
		<ITEM>
			<xsl:apply-templates/>
			<xsl:apply-templates select="(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']"/>
		</ITEM>
	</xsl:template>
	<xsl:template match="ItemDetail">
		<ItemDetail>
			<xsl:apply-templates/>
			<xsl:apply-templates select="following-sibling::*[1][name() = 'Address']"/>
		</ItemDetail>
		<xsl:apply-templates select="(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']"/>
	</xsl:template>
	<xsl:template match="Address">
		<Address>
			<xsl:apply-templates/>
		</Address>
		<xsl:apply-templates select="following-sibling::*[1][name() = 'Address']"/>
	</xsl:template>
	<xsl:template match="@*|node()">
		<xsl:copy>
			<xsl:apply-templates select="@*|node()"/>
		</xsl:copy>
	</xsl:template>
</xsl:stylesheet>

Open in new window


The second possibility is that you want it pretty much like what you get now, but with the <HEADER> and <Trailer> records merged somehow. But if so, you still need to tell me how they should be merged. The <Trailer> record would be pretty straightforward as I would assume that you would just want the sum of all the individual <Trailer>/<NoofRecords> elements. But how to merge the date/time in the <HEADER>? Should it just pick the first date? Should it find the earliest (or latest) of all the dates? The following just takes the first <HEADER> in the input file...
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="Test">
	<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/ns:SenderFile_MT">
		<ns0:Messages xmlns:ns0="http://sap.com/xi/XI/SplitAndMerge">
			<ns0:Message>
				<ns1:ReceiverFile_MT xmlns:ns1="Test">
       			        <xsl:apply-templates select="(Record/HEADER)[1]"/>
       			        <xsl:apply-templates select="Record/ITEM"/>
       			        <Trailer>
       			           <NoofRecords>
       			              <xsl:value-of select="sum(Record/Trailer/NoofRecords)" />
       			           </NoofRecords>
       			        </Trailer>
				</ns1:ReceiverFile_MT>
			</ns0:Message>
		</ns0:Messages>
	</xsl:template>
	<xsl:template match="ITEM">
		<ITEM>
			<xsl:apply-templates/>
			<xsl:apply-templates select="(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']"/>
		</ITEM>
	</xsl:template>
	<xsl:template match="ItemDetail">
		<ItemDetail>
			<xsl:apply-templates/>
			<xsl:apply-templates select="following-sibling::*[1][name() = 'Address']"/>
		</ItemDetail>
		<xsl:apply-templates select="(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']"/>
	</xsl:template>
	<xsl:template match="Address">
		<Address>
			<xsl:apply-templates/>
		</Address>
		<xsl:apply-templates select="following-sibling::*[1][name() = 'Address']"/>
	</xsl:template>
	<xsl:template match="@*|node()">
		<xsl:copy>
			<xsl:apply-templates select="@*|node()"/>
		</xsl:copy>
	</xsl:template>
</xsl:stylesheet>

Open in new window

0
Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

 

Author Closing Comment

by:kalyangkm
ID: 39869466
Hi Mccarl,

Thanks for multiple options. I very much appreciate that. One closing question

can you explain the tag from the code. what exactly following-sibling doing here.

<xsl:apply-templates select="(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']"/>
0
 
LVL 35

Expert Comment

by:mccarl
ID: 39869499
Ok, we probably need to build up the answer so that you can fully appreciate what is going on here... Note that following-sibling (as do a lot of XPath operators) is context-sensitive, so the result that we get depends on what element we are currently processing in the XML.

following-sibling::*   -   by itself will return to you all the elements that are at the same level as the current element that come after the current element in the document that have the same parent as the current element. Quick example...

<root>
   <parent>
      <a/>
      <b/>
      <c/>
      <d/>
   </parent>
   <parent>
      <e/>
      <f/>
   </parent>
</root>

If we are currently processing element <b> then following-sibling::* will return the set of elements <c> and <d>. Not <a> because it comes before <b> and not <e> or <f> because they have a different parent element.

following-sibling::ItemDetail   -   Does the same as the above but it will only return elements after the current element that are ItemDetail elements and have the same parent as the current element. Pretty straightforward.

(following-sibling::ItemDetail | following-sibling::ITEM)   -   Will return elements after the current element that are either ItemDetail or ITEM and have the same parent. We include ITEM elements so that we know where to "stop" including subsequent elements, other wise the very first ITEM would include ALL ItemDetails from the rest of the Record element. This is the tricky part of converting a file where elements are only related because they are next to each other, to a file where there is a proper heirarchy.

(following-sibling::ItemDetail | following-sibling::ITEM)[1]   -   Gets just the first element that is either ItemDetail or ITEM that follows the current element in the document.

(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']   -   This is the final piece of the puzzle... As above we select the first element that follows the current that is either ItemDetail or ITEM, and then "if" that element is an ItemDetail element, we select it, otherwise that element must be an ITEM element and we DON'T select it.

Basically, the above XSL works on recursion, after processing an ItemDetail element we look for the next ItemDetail element as call the same template again. So the above xpath selects the next ItemDetail element for each step of the recursion, but then if the next element is an ITEM element, nothing is selected which ends the recursion.


That's all quite long winded but hopefully you can follow and see what we are doing there. The <Address> processing is quite similar but a little bit simpler because ANY element that is NOT an Address terminates the recursion for including Addresses. However, for ItemDetails there is a little bit more work because (as one of the orders demonstrates) you can have an interleaving of ItemDetails and Address within the one ITEM, so we have to account for that.

Let me know if you have any further queues or require further clarification on the above.
0
 

Author Comment

by:kalyangkm
ID: 39871037
This could be as detailed as possible, thanks. the most tricky part is the interleaving of ItemDetails and Address which require the following, which took quite sometime to figure it out. But one final thing i dont see any condition for the recursive occurence of ITEM. I suppose its something simple which i am missing. The issue is i am having problems debugging the XSLT with my editix xslt tool otherwise i shouldnt have given you this trouble.

<xsl:template match="ItemDetail">
            <ItemDetail>
                  <xsl:apply-templates/>
                  <xsl:apply-templates select="following-sibling::*[1][name() = 'Address']"/>
            </ItemDetail>
            <xsl:apply-templates select="(following-sibling::ItemDetail | following-sibling::ITEM)[1][name() = 'ItemDetail']"/>
      </xsl:template>
0
 
LVL 35

Expert Comment

by:mccarl
ID: 39872292
No, you are right. There is nothing in there to deal with recursion for ITEM elements because there is no relationship between successive ITEM elements. One element in the input corresponds directly with one element in the output. I admit that the distinction is subtle between this and the ItemDetail/Address elements, but with these other elements one or two or more of them need to be gathered together and placed within the previous ITEM/ItemDetail parent element.
0

Featured Post

Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
eclipse package explorer vs project explorer view 2 179
parse convert xml feed to text (python) 2 92
iframe detection of parent window scale 20 85
AvlTree-Node Data type 4 14
This article will show, step by step, how to integrate R code into a R Sweave document
A short article about problems I had with the new location API and permissions in Marshmallow
An introduction to basic programming syntax in Java by creating a simple program. Viewers can follow the tutorial as they create their first class in Java. Definitions and explanations about each element are given to help prepare viewers for future …

856 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