Convert invoice XML data to flat CSV format via XSLT

Posted on 2004-04-02
Last Modified: 2008-02-26
As a novice XSLT, I'm struggling to read docs, and interpret results to resolve this query, hence the question. Given the following XML data, I want to output each invoice on a separate line, but with the contents of all the address info included. All of the <PHONE_NUMBER> relative to the account are also to be repeated on each invoice record. Could someone help in providing an XSLT solution.

    <ADDRESS >
      <FLAT_NO>Flat 1A</FLAT_NO>
      <STREET_NAME>Skipworth Road</STREET_NAME>
      <FLAT_NO>Flat 1A</FLAT_NO>
      <STREET_NAME>Skipworth Road</STREET_NAME>

The output would look something like:

01_04_2004~Flat 1A~ ~Skipworth Road~Coventry~CV6 8HT~Flat 1A~~Skipworth Road~Coventry~CV6 8HT~C11111~MR~John~Dempsey~02476222222,07801651711~~0.00~ 171.74~P0001~01_04_2004~20.34
01_04_2004~Flat 1A~ ~Skipworth Road~Coventry~CV6 8HT~Flat 1A~~Skipworth Road~Coventry~CV6 8HT~C11111~MR~John~Dempsey~02476222222,07801651711~~0.00~ 171.74~P0002~2004_01_15~5.18
Question by:jackdempsey
  • 2

Assisted Solution

jeffqyzt earned 25 total points
ID: 10742107
The point to remember is that if you want to refer to the parent inside your for-each, you need to use the full XPath back to it.  The following stylesheet does what you want, I think.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="">
   <xsl:template match="/">
      <xsl:for-each select="/FILE/LETTER/INVOICES/INVOICE">
         <xsl:value-of select="/FILE/LETTER/RUN_DATE"/>~
         <xsl:value-of select="/FILE/LETTER/ADDRESS/FLAT_NO"/>~
         <xsl:value-of select="/FILE/LETTER/ADDRESS/STREET_NO"/>~
         <xsl:value-of select="/FILE/LETTER/ADDRESS/STREET_NAME"/>~
         <xsl:value-of select="/FILE/LETTER/ADDRESS/AREA"/>~
         <xsl:value-of select="/FILE/LETTER/ADDRESS/POST_CODE"/>~
         <xsl:value-of select="/FILE/LETTER/INVOICE_ADDRESS/FLAT_NO"/>~
         <xsl:value-of select="/FILE/LETTER/INVOICE_ADDRESS/STREET_NO"/>~
         <xsl:value-of select="/FILE/LETTER/INVOICE_ADDRESS/STREET_NAME"/>~
         <xsl:value-of select="/FILE/LETTER/INVOICE_ADDRESS/AREA"/>~
         <xsl:value-of select="/FILE/LETTER/INVOICE_ADDRESS/POST_CODE"/>~
         <xsl:value-of select="/FILE/LETTER/ACCOUNT/ACCOUNT_NO"/>~
         <xsl:value-of select="/FILE/LETTER/ACCOUNT/TITLE"/>~
         <xsl:value-of select="/FILE/LETTER/ACCOUNT/NAME1"/>~
         <xsl:value-of select="/FILE/LETTER/ACCOUNT/NAME2"/>~
         <xsl:for-each  select="/FILE/LETTER/PHONE_NUMBER">
            <xsl:value-of  select="."/>,
         <xsl:value-of select="/FILE/LETTER/ARREARS/LAST_PAYMENT_DATE"/>~
         <xsl:value-of select="/FILE/LETTER/ARREARS/LAST_PAYMENT_AMOUNT"/>~
         <xsl:value-of select="/FILE/LETTER/ARREARS/ACCOUNT_BALANCE"/>~
         <xsl:value-of select="INVOICE_NUMBER"/>~
         <xsl:value-of select="INVOICE_DATE"/>~
         <xsl:value-of select="INVOICE_AMOUNT"/>
LVL 12

Accepted Solution

dfiala13 earned 100 total points
ID: 10742379
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="">
<xsl:output method="text" />

<xsl:template match="/">
      <xsl:for-each select="FILE/LETTER">
            <xsl:variable name="run"><xsl:value-of select="RUN_DATE" /></xsl:variable>
            <xsl:variable name="addr">
                  <xsl:for-each select="ADDRESS">
                        <xsl:call-template name="Address" />
            <xsl:variable name="invaddr">
                  <xsl:for-each select="INVOICE_ADDRESS">
                        <xsl:call-template name="Address" />
            <xsl:variable name="acct">
                  <xsl:value-of select="concat(ACCOUNT/ACCOUNT_NO, '~',  ACCOUNT/TITLE,  '~',  ACCOUNT/NAME1,'~',  ACCOUNT/NAME2)" />
            <xsl:variable name="phone">
            <xsl:for-each  select="PHONE_NUMBER">
            <xsl:value-of  select="."/><xsl:if test="position() &lt; last()">,</xsl:if>
            <xsl:for-each select="INVOICES/INVOICE">
                  <xsl:value-of select="concat($run,'~',$addr,'~',$invaddr,'~',$acct, '-', $phone)" />
                  <xsl:value-of select="concat('~',INVOICE_NUMBER,'~',INVOICE_DATE,'~',INVOICE_AMOUNT)" />


<xsl:template name="Address">
      <xsl:value-of select="concat(FLAT_NO, '~',  STREET_NO,  '~',  STREET_NAME,'~',  AREA,'~',  POST_CODE)" />


Expert Comment

ID: 10742641

dfiala13's answer is much more portable than mine (and it correctly puts each output on a single line, what I get for using a web browser to view :), but you can see two different approaches to reaching the parent from the child.  There is the direct access method (my example) and then there is the "store it in a variable for later use" method (dfiala13's).  I think dfiala has the better solution here, but there are times when each approach would be useful.  


Author Comment

ID: 10757850
jeffqyzt, you helped me to understand the structure a bit clearer now, and as you say dfiala13 provided me with the solution I required when I applied it to my scenario, even to the extent of introducing me to variables. (always learning)

thanks to both of you. And as pointed out, my environment requires the portability also, hence the points . cheers

Featured Post

The New “Normal” in Modern Enterprise Operations

DevOps for the modern enterprise offers many benefits — increased agility, productivity, and more, but digital transformation isn’t easy, especially if you’re not addressing the right issues. Register for the webinar to dive into the “new normal” for enterprise modern ops.

Question has a verified solution.

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

Suggested Solutions

Title # Comments Views Activity
XML Data Missing in PHP SimpleXML 8 71
XSLT Display Label Name 1 27
Cannot open file from ASP if user is "logged in" 13 30
Need a modeling tool 2 18
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. 
Finds all prime numbers in a range requested and places them in a public primes() array. I've demostrated a template size of 30 (2 * 3 * 5) but larger templates can be built such 210  (2 * 3 * 5 * 7) or 2310  (2 * 3 * 5 * 7 * 11). The larger templa…

830 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