Solved

XSLT Mapping

Posted on 2014-02-17
7
237 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
Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

 

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

Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

Join & Write a Comment

Suggested Solutions

Purpose To explain how to place a textual stamp on a PDF document.  This is commonly referred to as an annotation, or possibly a watermark, but a watermark is generally different in that it is somewhat translucent.  Watermark’s may be text or graph…
This is an explanation of a simple data model to help parse a JSON feed
In this fourth video of the Xpdf series, we discuss and demonstrate the PDFinfo utility, which retrieves the contents of a PDF's Info Dictionary, as well as some other information, including the page count. We show how to isolate the page count in a…
In this fifth video of the Xpdf series, we discuss and demonstrate the PDFdetach utility, which is able to list and, more importantly, extract attachments that are embedded in PDF files. It does this via a command line interface, making it suitable …

762 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

21 Experts available now in Live!

Get 1:1 Help Now