tags:

views:

511

answers:

3

We need to parse an XML file with XSLT into a CVS file. The code below works but only if the fields in the XML are always constant.

The fields in the our XML file will always vary. How can I change my XSLT file so that it dynamically includes in the CSV file all the fields from the XML file?

XML:

<?xml version="1.0" encoding="iso-8859-1"?>
<data>
    <row>
        <customerID>06104539-463E-4B1A-231-34342343434</customerID>
        <contactID>23434-99F2-4325-B228-6F343483469389FB</contactID>
        <firstName>Jim</firstName>
        <lastName>Smith</lastName>
    </row>
    <row>
        <customerID>223434-463E-4B1A-231-A1E7EA248796</customerID>
        <contactID>6675767-99F2-4325-B234328-6F83469389FB</contactID>
        <specialID>112332</specialID>
        <firstName>John</firstName>
        <middleName>S.</middleName>
        <lastName>Jones</lastName>
    </row>
</data>

XSLT:

<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

<xsl:template match="/">
    customerID,contactID<br/>
    <xsl:for-each select="data/row">
        <xsl:value-of select="customerID"/>,<xsl:value-of select="contactID"/><br/>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Here is the ASP.NET file that parses the files above:

<%@ Page Language="c#" %>
<%@ import Namespace="System.Xml" %>
<%@ import Namespace="System.Xml.Xsl" %>
<%@ import Namespace="System.Xml.XPath" %>
<%@ import Namespace="System.IO" %>
<%@ import Namespace="System.Text" %>
<script runat="server">

    public void Page_Load(Object sender, EventArgs E) {

     string xmlPath = Server.MapPath("test2.xml");
     string xslPath = Server.MapPath("test2.xsl");

     StreamReader reader = null;;
     XmlTextReader xmlReader = null;

     FileStream fs = new FileStream(xmlPath, FileMode.Open, FileAccess.Read);
     reader = new StreamReader(fs,Encoding.UTF7);
     xmlReader = new XmlTextReader(reader);
     XPathDocument doc = new XPathDocument(xmlReader);

     XslTransform xslDoc = new XslTransform();
     xslDoc.Load(xslPath);

     xslDoc.Transform(doc,null, Response.Output);

     reader.Close();
     xmlReader.Close();

    }

</script>
+1  A: 

Won't another for-each loop do the job for you?

<?xml version="1.0" encoding="ISO-8859-1"?>  
  <xsl:stylesheet version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;  
    <xsl:template match="/">  
      customerID,contactID<br/>  
      <xsl:for-each select="data/row">  
        <xsl:for-each select="*">  
          <xsl:value-of select="."/>,  
        </xsl:for-each>  
        <br/>      
      </xsl:for-each>  
   </xsl:template>  
</xsl:stylesheet>

Regards,

Aaron

sipwiz
yes, I ultimately need to include all fields from each row as well, but what is tricky in this task is (1) the title line needs to include a DISTINCT list of all field names form all rows, and (2) each row needs to supply the appropriate "empty" fields which do not occur in that row but do in others
Edward Tanguay
A: 

The problem here is that the first "row" could contain 3 elements and other rows 5 elements. So it will be difficult to extract the header.

To sort use xsl:sort

To count use the xslt function count()

Sort after the number of elements, get the header, create a collection of items in the header then, for each row, scan it for those elements.

Bogdan Gavril
+3  A: 

I remembered doing something similar a ways back, and looking through my archive work, lo-and-behold:

<?xml version="1.0" encoding="ISO-8859-1"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

    <xsl:key name="muench" match="/data/row/*" use="local-name()"/>

    <xsl:template match="/">
     <xsl:for-each select="/data/row/*[generate-id() = generate-id(key('muench',local-name())[1])]">
      <xsl:if test="not(position()=1)">,</xsl:if><xsl:value-of select="local-name()"/>
     </xsl:for-each>
     <br/>
     <xsl:for-each select="/data/row">
      <xsl:variable name="current" select="."/>
      <xsl:for-each select="/data/row/*[generate-id() = generate-id(key('muench',local-name())[1])]">
       <xsl:variable name="field" select="local-name()"/>
       <xsl:if test="not(position()=1)">,</xsl:if><xsl:value-of select="$current/*[local-name()=$field]"/>    
      </xsl:for-each>
      <br/>
     </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

All hail the ugly but functional.

I'm seriously not proud of this, but I've updated for your data and tested it and it works.

annakata
right on, that's it, 1000 thanks!
Edward Tanguay