2013-03-05 174 views
2

我正在寻找一种智能高效的XSLT,它将XML文档转换为CSV数据。它应该处理子节点中所有可能的元素。 例如,XML看起来像这样使用XSLT将XML转换为CSV

<?xml version="1.0" encoding="ISO-8859-1"?> 
<sObjects> 
    <sObject> 
    <Name>Raagu</Name> 
    <BillingStreet>Hoskote</BillingStreet> 
    </sObject> 
    <sObject> 
     <Name>Rajath</Name> 
     <BillingStreet>BTM</BillingStreet> 
     <age>25</age> 
    </sObject> 
    <sObject> 
     <Name>Sarath</Name> 
     <BillingStreet>Murgesh</BillingStreet> 
     <location>Bangalore</location> 
    </sObject> 
</sObjects> 

而且我出去把CSV应该是这样的

Name,BillingStreet,age,location 
Raagu,Hoskote,, 
Rajath,BTM,25, 
Sarath,Murgesh,,Bangalore 

所有的行应该在CSV所有键字段,即使如果XML确实有它的价值。

以下是我通过查看这里的不同示例创建的XSLT代码。

这是XSLT我想出了

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="text"/> 
    <xsl:variable name="delimiter" select="','"/> 

    <xsl:key name="field" match="sObject/*" use="name()"/> 

    <xsl:template match="/"> 

     <xsl:for-each select="/*/*/*[generate-id()=generate-id(key('field', name())[1])]"> 
      <xsl:value-of select="name()"/> 

      <xsl:if test="position() != last()"> 
       <xsl:value-of select="$delimiter"/> 
      </xsl:if> 
     </xsl:for-each> 

     <xsl:text>&#xa;</xsl:text> 

     <xsl:for-each select="/*/sObject"> 

      <xsl:variable name="property" select="." /> 
      <xsl:for-each select="$property/*"> 

       <xsl:variable name="value" select="." /> 
       <xsl:value-of select="$value"/> 
       <xsl:if test="position() != last()"> 
        <xsl:value-of select="$delimiter"/> 
       </xsl:if> 
       <xsl:if test="position() = last()"> 
        <xsl:text>&#xa;</xsl:text> 
       </xsl:if> 

      </xsl:for-each> 

     </xsl:for-each> 


    </xsl:template> 
</xsl:stylesheet> 

,并打印出来放在

Name,BillingStreet,age,location 
Raagu,Hoskote 
Rajath,BTM,25 
Sarath,Murgesh,Bangalore 

但我想所有的行应包含那些多次值的所有键在第一行。

你能帮我用XSLT代码实现吗?

+2

我们会提供帮助。当你卡住了。尝试一下,并询问是否有不清楚的地方。 [常见问题] – ppeterka 2013-03-05 14:24:15

+1

可能有几个类似问题的重复。请参阅“相关”下的链接。 – mzjn 2013-03-05 14:27:15

+0

谢谢ppeterka。由于我是XSLT的新手,我一直在XSLT上尝试不同的方式来完成这个任务。我写了示例XSL代码来获取第一行(所有的键),但没有得到逻辑来获取正确位置的值。我看过相关的链接,并没有找到解决我的问题。我现在被困在这一点,现在需要从Stackoverflow的帮助。 – 2013-03-05 14:27:20

回答

6

这个怎么样了两步解决方案

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="text"/> 
    <xsl:variable name="delimiter" select="','"/> 

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

    <!-- variable containing the first occurrence of each field --> 
    <xsl:variable name="allFields" 
     select="/*/*/*[generate-id()=generate-id(key('field', local-name())[1])]" /> 

    <xsl:template match="/"> 
     <xsl:for-each select="$allFields"> 
      <xsl:value-of select="local-name()" /> 
      <xsl:if test="position() &lt; last()"> 
       <xsl:value-of select="$delimiter" /> 
      </xsl:if> 
     </xsl:for-each> 
     <xsl:text>&#10;</xsl:text> 
     <xsl:apply-templates select="*/*" /> 
    </xsl:template> 

    <xsl:template match="*"> 
     <xsl:variable name="this" select="." /> 
     <xsl:for-each select="$allFields"> 
      <xsl:value-of select="$this/*[local-name() = local-name(current())]" /> 
      <xsl:if test="position() &lt; last()"> 
       <xsl:value-of select="$delimiter" /> 
      </xsl:if> 
     </xsl:for-each> 
     <xsl:text>&#10;</xsl:text> 
    </xsl:template> 
</xsl:stylesheet> 

这里的窍门是,allFields变量包含每个名称的一个元素,所以它的这个我们遍历每个节点列表行,而不仅仅是那一行中实际存在的元素。既然你说你想支持任意命名空间的XML等,我已经使用了像/*/*/*这样的模式,而不是对任何特定的元素名称进行硬编码(/*/*/*只是匹配作为文档元素的孙子的任何元素,而不管元素名称) ,并且我使用local-name()而不是name()忽略任何名称空间前缀(它会将<sObject>,<sObject xmlns="foo"><f:sObject xmlns:f="foo">完全相同)。

+0

嗨伊恩 这真的很酷。感谢您的出色解决方案。 我正在平行讨论'stack kind of structure'中的一些问题,它会导致O(N)时间复杂度。 我想了解从您那里生成“allFields”变量的时间复杂度。 一旦我们生成了“allFields”变量,我希望时间复杂度为o(n),其中'n'是allFields中字段的数量。 需要你的帮助。 此外,我想使这个XSLT更通用的地方,如果命名空间来在XML,例如 2013-03-05 15:25:43

+0

我发这样的XML 什么 <的sObject的xmlns = “namespce”> raagu 我们如何处理这个问题? – 2013-03-05 15:29:28

+0

@RaghavendraNilekani,通过询问单独的SO问题并阅读SO问题和答案来处理这个问题 - 类似的问题已经被提出过数千次,而且大多数都有很好的答案。另外,如果“真的很酷”,接受*答案(点击该答案旁边的复选标记)是一种很好的礼仪。 – 2013-03-05 15:34:51