使用XSLT将XML转换为CSV [英] XML to CSV using XSLT

查看:182
本文介绍了使用XSLT将XML转换为CSV的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

 <?xml version =1.0encoding = 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>班加罗尔< / location>
< / sObject>
< / sObjects>

而我的CSV应该像这样

 姓名,BillingStreet,年龄,地点
Raagu,Hoskote ,,
Rajath,BTM,25,
Sarath,Murgesh ,, Bangalore

所有行都应该具有CSV中所有键的字段,即使XML有一个值



以下是我在这里查看不同示例时提供的XSLT代码。



是XSLT我想出了

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

< xsl:key name =fieldmatch =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 =propertyselect =。 />
< xsl:for-each select =$ property / *>

< xsl:variable name =valueselect =。 />
< 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>

并打印出来

 姓名,BillingStreet,年龄,位置
Raagu,Hoskote
Rajath,BTM,25
Sarath,Murgesh,Bangalore

但我想所有的行应该包含第一行上所有键的多次的值。


解决方案

步骤解决方案

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

< xsl:key name =fieldmatch =/ * / * / *use =local-name()/>

<! - 包含每个字段的第一次出现的变量 - >
< 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 =thisselect =。 />
< 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> / code>完全相同)。


I am looking for an intelligent efficient XSLT which does convert an XML document to CSV data. It should take care of all possible elements in the child nodes. For example, the XML looks like this

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

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.

Following is the XSLT code I came up with by looking at different examples over here.

This is the XSLT I have come up with

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

解决方案

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

这篇关于使用XSLT将XML转换为CSV的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆