简体   繁体   English

使用XSLT将XML转换为CSV

[英]XML to CSV using XSLT

I am looking for an intelligent efficient XSLT which does convert an XML document to CSV data. 我正在寻找一种智能高效的XSLT,它确实可以将XML文档转换为CSV数据。 It should take care of all possible elements in the child nodes. 它应该照顾子节点中所有可能的元素。 For example, the XML looks like this 例如,XML看起来像这样

<?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>

And my out put CSV should look like this 而我的输出CSV应该是这样的

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

All the rows should have fields for all the keys in the CSV even though if the XML does have a value for it. 所有行都应包含CSV中所有键的字段,即使XML确实具有值。

Following is the XSLT code I came up with by looking at different examples over here. 以下是通过查看此处的不同示例得出的XSLT代码。

This is the XSLT I have come up with 这是我提出的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>

and it print this out put 并打印出来

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

But I wanted all the rows should contain values for those many times for all the keys on the first row. 但是我希望所有行都应包含第一行所有键多次的值。

Could you please help me achieve this using XSLT code ? 你能用XSLT代码帮助我实现这个目的吗?

How about this for a two-step solution 这对于两步解决方案来说如何呢?

<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>

The trick here is that the allFields variable will contain one element with each name, so it's this list of nodes that we iterate over for each row, not simply the elements that actually exist in that row. 这里的诀窍是, allFields变量包含每个名称的一个元素,所以这就是我们遍历每一行,不能简单地认为实际存在该行的元素节点的这个列表。 Since you say that you want to support XML in arbitrary namespaces etc. I've used patterns like /*/*/* rather than hard-coding any particular element names ( /*/*/* simply matches any element that is a grandchild of the document element, regardless of element names), and I'm using local-name() instead of name() to ignore any namespace prefixes (it would treat <sObject> , <sObject xmlns="foo"> and <f:sObject xmlns:f="foo"> exactly the same). 因为你说你想在任意命名空间等中支持XML。我使用了像/*/*/*而不是硬编码任何特定的元素名称( /*/*/*只是匹配任何元素是孙子文档元素的大小,无论元素名称是什么),我都使用local-name()而不是name()来忽略任何名称空间前缀(它将处理<sObject><sObject xmlns="foo"><f:sObject xmlns:f="foo">完全相同)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM