2010-02-04 62 views
2

我不知道是否有人可以幫助我將XML文件壓扁爲CSV格式。我今天整天都在做這件事,雖然我找到了一些簡單的例子,但我的問題卻是微不足道的。請看下面的例子...將XML壓縮爲CSV(VB.NET/C#.NET/XSLT)

示例XML:

<data> 
    <val_A>1</val_A> 
    <val_A>2</val_A> 
    <val_B>3</val_B> 
    <val_B>4</val_B> 
    <val_C> 
     <val_D>5</val_D> 
     <val_D>6</val_D> 
    </val_C> 
    <val_E> 
     <val_F>7</val_F> 
     <val_F>8</val_F> 
    </val_E> 
</data> 

預期輸出:

val_A,val_B,val_C,val_D,val_E,val_F 
1,3,,5,,7 
1,3,,5,,8 
1,3,,6,,7 
1,3,,6,,8 
1,4,,5,,7 
1,4,,5,,8 
1,4,,6,,7 
1,4,,6,,8 
2,3,,5,,7 
2,3,,5,,8 
2,3,,6,,7 
2,3,,6,,8 
2,4,,5,,7 
2,4,,5,,8 
2,4,,6,,7 
2,4,,6,,8 

注意,數據節點有子節點的幾個不同的結構。是否可以編寫XSL來扁平化?

第二個問題是有可能以通用的方式編寫XSL,以便它不需要知道節點名稱,因此只適用於任何供給的XML。

任何幫助將不勝感激。

感謝 大衛

+0

1467背後的邏輯是什麼,但不是1457?你似乎在最後有一個'up'有時 – 2010-02-05 00:04:40

+0

啊,我的錯誤,我已經更新了預期的輸出。 – DavidReid 2010-02-06 08:38:04

回答

1

雖然不是基於XSL的解決方案,但爲了將XML文件壓縮爲CSV,您可以查看可在here處獲得的開源項目XMLtoCSV。它可以完成這項工作,而無需根據要求指定xml節點名稱。

1

它看起來像你想輸出節點值的給定名稱的每個節點的所有可能的組合。

所以,每個節點val_Aval_Bval_Dval_F有2個OCCURENCES,而節點val_Cval_E具有1個occurence。這會產生2 x 2 x 1 x 2 x 1 x 2 = 16種組合。

我只能想到在XSLT中使用兩階段過程來做到這一點。由於您聲明瞭VB.Net,我假設您將能夠在XSLT中使用Microsoft擴展函數,從而允許您創建節點集。

第一步是創建一個包含XML中唯一節點名稱節點集的變量。這個變量(稱爲節點名稱在下面的例子中)將持有的節點列表,像這樣

<node>val_A</node> 
<node>val_B</node> 
<node>val_C</node> 
.... 

我已經使用meunchian分組來實現這一目標。

下一步將遞歸調用一個命名模板,一次爲XML中的每個可能的節點名稱(使用先前創建的節點集)。爲與名稱匹配的節點的每個可能值構建一個字符串。

這是我使用的XSLT。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"> 
    <xsl:output method="html"/> 
    <xsl:key name="nodes" match="node()" use="local-name()"/> 

    <!-- Variable holding unique node names --> 
    <xsl:variable name="nodeNames"> 
     <xsl:apply-templates select="/*/*" mode="name"/> 
    </xsl:variable> 

    <!-- Use meunchian groupings to build up a node-set of unique node names --> 
    <xsl:template match="*" mode="name"> 
     <xsl:if test="generate-id() = generate-id(key('nodes', local-name())[1])"> 
     <node> 
      <xsl:value-of select="local-name()"/> 
     </node> 
     </xsl:if> 
     <xsl:apply-templates select="*" mode="name"/> 
    </xsl:template> 

    <!-- Match the root to start processing --> 
    <xsl:template match="/"> 
     <xsl:call-template name="recurse"> 
     <xsl:with-param name="nodeIndex">1</xsl:with-param> 
     </xsl:call-template> 
    </xsl:template> 

    <!-- Recursive template to loop through nodes of a given name --> 
    <xsl:template name="recurse"> 
     <xsl:param name="nodeIndex"/> 
     <xsl:param name="outputString"/> 

     <!-- Get the name of the node for the selected index --> 
     <xsl:variable name="nodeName" select="msxsl:node-set($nodeNames)/node[number($nodeIndex)]/text()"/> 

     <!-- Loop through all nodes with this name -->  
     <xsl:for-each select="key('nodes', $nodeName)"> 

     <!-- Build up the output string using the node value --> 
     <xsl:variable name="newOutputString"> 
      <xsl:if test="number($nodeIndex) &gt; 1"> 
       <xsl:value-of select="$outputString"/> 
       <xsl:text>,</xsl:text> 
      </xsl:if> 
      <xsl:value-of select="text()"/> 
     </xsl:variable> 

     <xsl:choose> 
      <!-- If more nodes names are left to process, recursively call the template for the next one --> 
      <xsl:when test="number($nodeIndex) &lt; count(msxsl:node-set($nodeNames)/node)"> 
       <xsl:call-template name="recurse"> 
        <xsl:with-param name="nodeIndex"> 
        <xsl:value-of select="number($nodeIndex) + 1"/> 
        </xsl:with-param> 
        <xsl:with-param name="outputString"> 
        <xsl:value-of select="$newOutputString"/> 
        </xsl:with-param> 
       </xsl:call-template> 
      </xsl:when> 
      <!-- Output the string that has been built up --> 
      <xsl:otherwise> 
       <xsl:value-of select="$newOutputString"/> 
       <xsl:text>&#13;&#10;</xsl:text> 
      </xsl:otherwise> 
     </xsl:choose> 
     </xsl:for-each> 
    </xsl:template> 

</xsl:stylesheet> 

由於涉及遞歸的,這將越來越成爲ineffecient不同的節點名稱,有更大數量。

1

下面是一個XSLT 2.0樣式表,應該完成這項工作,至少當我使用Saxon 9AltovaXML tools針對您發佈的輸入運行它時,我會得到您描述的輸出。 這裏是樣式表:

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    exclude-result-prefixes="xsd" 
    version="2.0"> 

    <xsl:param name="lf" select="'&#10;'"/> 
    <xsl:param name="sep" select="','"/> 

    <xsl:output method="text"/> 

    <xsl:variable name="groups" as="element(group)*"> 
    <xsl:for-each-group select="data/descendant::*" group-by="node-name(.)"> 
     <group name="{current-grouping-key()}"> 
     <xsl:choose> 
      <xsl:when test="*"> 
      <value/> 
      </xsl:when> 
      <xsl:otherwise> 
      <xsl:for-each select="current-group()[not(*)]"> 
       <value><xsl:value-of select="."/></value> 
      </xsl:for-each> 
      </xsl:otherwise> 
     </xsl:choose> 
     </group> 
    </xsl:for-each-group> 
    </xsl:variable> 

    <xsl:template name="cp"> 
    <xsl:param name="groups" as="element(group)*"/> 
    <xsl:param name="row" as="item()*" select="()"/> 
    <xsl:choose> 
     <xsl:when test="not($groups)"> 
     <xsl:value-of select="$row" separator="{$sep}"/> 
     <xsl:value-of select="$lf"/> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:for-each select="$groups[1]/value"> 
      <xsl:call-template name="cp"> 
      <xsl:with-param name="groups" select="$groups[position() gt 1]"/> 
      <xsl:with-param name="row" select="$row, ."/> 
      </xsl:call-template> 
     </xsl:for-each> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:template> 

    <xsl:template match="data"> 
    <xsl:value-of select="$groups/@name" separator="{$sep}"/> 
    <xsl:value-of select="$lf"/> 
    <xsl:call-template name="cp"> 
     <xsl:with-param name="groups" select="$groups"/> 
    </xsl:call-template> 
    </xsl:template> 

</xsl:stylesheet> 

兩個撒克遜9(與它的。NET版本)和AltovaXML工具(通過COM互操作)可以與.NET應用程序一起使用,請參閱Saxon的http://www.saxonica.com/documentation/dotnet/dotnetapi.html和Altova的http://manual.altova.com/AltovaXML/index.html?ax_netinterface.htm