2013-09-23 98 views
1

我想將動態XML轉換爲CSV。我搜索了各種選項來實現這一目標,但沒有找到合適的答案。通用XML到CSV轉換

的XML的結構是動態的 - 它可以是一個產品數據,一個地理數據或任何這樣的事情。所以,我無法使用預定義的XSL或腳輪轉換。

標籤名稱應該形成CSV的標頭。 例如:

<Ctry> 
    <datarow> 
    <CtryName>Ctry1</CtryName> 
    <CtryID>12361</CtryID> 
    <State> 
     <datarow> 
     <StateName>State1</StateName> 
     <StateID>12361</StateID> 
     <City> 
      <datarow> 
       <CityName>City1</CityName> 
       <CityID>12361</CityID> 
      </datarow> 
     </City> 
     </datarow> 
     <datarow> 
     <StateName>State2</StateName> 
     <StateID>12361</StateID> 
     </datarow> 
     </State> 
    </datarow> 
</Ctry> 

的CSV應該是這樣的:

Header: CtryName CtryId  StateName StateId  CityName CityID 
Row1: Ctry1  12361  State1  12361  City1  12361 
Row2: Ctry1  12361  State2  12361 

可否請你推薦使用來解決這個問題容易的事嗎?

+2

這聽起來像你問一個程序來讀取你的心你的XML的結構。 –

+0

-1您是否編寫過可能對您有幫助的代碼?如果您只是想讓某人爲您編寫代碼,那麼您可能在Freelancer網站上運氣更好。 – 2013-09-23 03:55:19

+0

是的。我寫了一個基於SAX的解析器,但是不能完全解決這個問題。因此,我一直在尋找任何易於執行此操作的Java庫。我有XML的XSD。基於此,我正在嘗試編寫一個將其轉換爲CSV的通用XSLT。編寫泛型XSLT的任何指針都會有所幫助。 – Supriya

回答

6

下面是一個說明執行此類轉換的通用樣式表的轉錄說明。樣式表唯一的假設是元素<datarows>。給出的結構意味着使用基於請求的結果子元素:

數據:

T:\ftemp>type xml2csv.xml 
    <Ctry> 
    <datarow> 
     <CtryName>Ctry1</CtryName> 
     <CtryID>12361</CtryID> 
     <State> 
     <datarow> 
      <StateName>State1</StateName> 
      <StateID>12361</StateID> 
      <City> 
      <datarow> 
       <CityName>City1</CityName> 
       <CityID>12361</CityID> 
      </datarow> 
      </City> 
     </datarow> 
     <datarow> 
      <StateName>State2</StateName> 
      <StateID>12361</StateID> 
     </datarow> 
     </State> 
    </datarow> 
    </Ctry> 

執行:

T:\ftemp>call xslt2 xml2csv.xml xml2csv.xsl 
    CtryName,CtryID,StateName,StateID,CityName,CityID 
    Ctry1,12361,State1,12361,City1,12361 
    Ctry1,12361,State2,12361 

樣式表:

T:\ftemp>type xml2csv.xsl 
    <?xml version="1.0" encoding="US-ASCII"?> 
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        version="2.0"> 

    <xsl:output method="text"/> 

    <xsl:variable name="fields" 
       select="distinct-values(//datarow/*[not(*)]/name(.))"/> 

    <xsl:template match="/"> 
    <!--header row--> 
    <xsl:value-of select="$fields" separator=","/> 

    <!--body--> 
    <xsl:apply-templates select="*"/> 

    <!--final line terminator--> 
    <xsl:text>&#xa;</xsl:text> 
    </xsl:template> 

    <!--elements only process elements, not text--> 
    <xsl:template match="*"> 
    <xsl:apply-templates select="*"/> 
    </xsl:template> 

    <!--these elements are CSV fields--> 
    <xsl:template match="datarow/*[not(*)]"> 
    <!--replicate ancestors if necessary--> 
    <xsl:if test="position()=1 and ../preceding-sibling::datarow"> 
     <xsl:for-each select="ancestor::datarow[position()>1]/*[not(*)]"> 
     <xsl:call-template name="doThisField"/> 
     </xsl:for-each> 
    </xsl:if> 
    <xsl:call-template name="doThisField"/> 
    </xsl:template> 

    <!--put out a field ending the previous field and escaping content--> 
    <xsl:template name="doThisField"> 
    <xsl:choose> 
     <xsl:when test="name(.)=$fields[1]"> 
     <!--previous line terminator--> 
     <xsl:text>&#xa;</xsl:text> 
     </xsl:when> 
     <xsl:otherwise> 
     <!--previous field terminator--> 
     <xsl:text>,</xsl:text> 
     </xsl:otherwise> 
    </xsl:choose> 
    <!--field value escaped per RFC4180--> 
    <xsl:choose> 
     <xsl:when test="contains(.,'&#x22;') or 
         contains(.,',') or 
         contains(.,'&#xa;')"> 
     <xsl:text>"</xsl:text> 
     <xsl:value-of select="replace(.,'&#x22;','&#x22;&#x22;')"/> 
     <xsl:text>"</xsl:text> 
     </xsl:when> 
     <xsl:otherwise><xsl:value-of select="."/></xsl:otherwise> 
    </xsl:choose> 
    </xsl:template> 

    </xsl:stylesheet> 

注意上面代碼按照RFC4180轉義單個字段。

我的簡歷有一個鏈接到我的網站,在那裏你會找到的免費XML資源,包括XSLT樣式表RFC4180 CSV文件轉換成XML文件的目錄。

這是一個XSLT 1.0解決答案,如要求通過原來的海報:

t:\ftemp>type xml2csv1.xsl 
<?xml version="1.0" encoding="US-ASCII"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
       version="1.0"> 

<xsl:output method="text"/> 

<xsl:variable name="firstFieldName" 
       select="name((//datarow/*[not(*)])[1])"/> 

<xsl:key name="names" match="datarow/*[not(*)]" use="name(.)"/> 

<xsl:template match="/"> 
    <!--header row--> 
    <xsl:for-each select="//datarow/*[not(*)] 
         [generate-id(.)= 
         generate-id(key('names',name(.))[1])]"> 
    <xsl:if test="position()>1">,</xsl:if> 
    <xsl:value-of select="name(.)"/> 
    </xsl:for-each> 

    <!--body--> 
    <xsl:apply-templates select="*"/> 

    <!--final line terminator--> 
    <xsl:text>&#xa;</xsl:text> 
</xsl:template> 

<!--elements only process elements, not text--> 
<xsl:template match="*"> 
    <xsl:apply-templates select="*"/> 
</xsl:template> 

<!--these elements are CSV fields--> 
<xsl:template match="datarow/*[not(*)]"> 
    <!--replicate ancestors if necessary--> 
    <xsl:if test="position()=1 and ../preceding-sibling::datarow"> 
    <xsl:for-each select="ancestor::datarow[position()>1]/*[not(*)]"> 
     <xsl:call-template name="doThisField"/> 
    </xsl:for-each> 
    </xsl:if> 
    <xsl:call-template name="doThisField"/> 
</xsl:template> 

<!--put out a field ending the previous field and escaping content--> 
<xsl:template name="doThisField"> 
    <xsl:choose> 
    <xsl:when test="name(.)=$firstFieldName"> 
     <!--previous line terminator--> 
     <xsl:text>&#xa;</xsl:text> 
    </xsl:when> 
    <xsl:otherwise> 
     <!--previous field terminator--> 
     <xsl:text>,</xsl:text> 
    </xsl:otherwise> 
    </xsl:choose> 
    <!--field value escaped per RFC4180--> 
    <xsl:choose> 
    <xsl:when test="contains(.,'&#x22;') or 
        contains(.,',') or 
        contains(.,'&#xa;')"> 
     <xsl:text>"</xsl:text> 
     <xsl:call-template name="escapeQuote"/> 
     <xsl:text>"</xsl:text> 
    </xsl:when> 
    <xsl:otherwise><xsl:value-of select="."/></xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

<!--escape a double quote in the current node value with two double quotes--> 
<xsl:template name="escapeQuote"> 
    <xsl:param name="rest" select="."/> 
    <xsl:choose> 
    <xsl:when test="contains($rest,'&#x22;')"> 
     <xsl:value-of select="substring-before($rest,'&#x22;')"/> 
     <xsl:text>""</xsl:text> 
     <xsl:call-template name="escapeQuote"> 
     <xsl:with-param name="rest" select="substring-after($rest,'&#x22;')"/> 
     </xsl:call-template> 
    </xsl:when> 
    <xsl:otherwise> 
     <xsl:value-of select="$rest"/> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

</xsl:stylesheet> 
+0

嗨肯,感謝您的答覆...我試圖運行我的Java程序,但我得到這個異常..「發生異常:javax.xml.transform.TransformerConfigurationException:javax.xml.transform.TransformerConfigurationException:javax.xml。 transform.TransformerException:javax.xml.transform.TransformerException:找不到函數:distinct-values「...我嘗試了不同版本的xalan.jar。你能讓我知道如何解決這個問題嗎? – Supriya

+0

您稱之爲「問題」的是您在原始問題中未指明您僅限於XSLT 1.0。我使用XSLT 2.0給了你一個答案。由於我無法在用戶界面中發佈替代答案,因此我編輯了此答案以包含該解決方案的XSLT 1.0版本。 –

+0

嗨,Ken,這個xslt很棒!非常感謝! – Supriya