多级XML到CSV XSLT转换

时间:2014-09-19 14:42:17

标签: xml csv xslt transform

我一直在进行XML到CSV转换,我一直在使用以下XSLT文档进行转换:

<?xml version="1.0"?>
<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()"/>
  <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/>
    <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/>
  </xsl:template>
</xsl:stylesheet>

如果我在以下xml上运行转换:

<?xml version="1.0"?>
<Calls>
  <Call xmlns="urn:eas-samples:en:xsd:phonecalls.1.0">
    <CustomerID>000391</CustomerID>
    <Filter1>1</Filter1> <Filter2>1</Filter2> 
  </Call>
  <Call xmlns="urn:eas-samples:en:xsd:phonecalls.1.0">
    <CustomerID>000528</CustomerID>
    <Filter1>1</Filter1> <Filter2>1</Filter2> 
  </Call>
</Calls>

转换工作正常,我得到以下结果:

CustomerID,Filter1,Filter2
000391,1,1
000528,1,1

但是,如果我为孩子添加额外的关卡,例如:

<?xml version="1.0"?>
<Calls>
  <Call xmlns="urn:eas-samples:en:xsd:phonecalls.1.0">
    <CustomerID>000391</CustomerID>
    <Filter1><AA>A</AA><BB>B</BB></Filter1>
  </Call>
  <Call xmlns="urn:eas-samples:en:xsd:phonecalls.1.0">
    <CustomerID>000528</CustomerID>        
    <Filter1>1</Filter1> <Filter2>1</Filter2> 
  </Call>
</Calls>

我得到以下输出:

CustomerID,Filter1,Filter2
000391,AB,
000528,1,1

我希望它是:

CustomerID,Filter1,AA,BB,Filter2 
000391,<FILTER1 VALUE>,A,B 
000528,1,<AA VALUE>,<BB VALUE>,1

无论我最终添加多少级别,都会出现问题。我想要的是所有子名称和值以CSV方式呈现。

知道我做错了吗?

谢谢!

1 个答案:

答案 0 :(得分:0)

如果我理解你的要求,这一点都不简单。我建议你放弃尝试采用完全通用的方法,专注于更加量身定制的解决方案 - 尽管仍然足够灵活,可以适应多种级别。

以下样式表:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="urn:eas-samples:en:xsd:phonecalls.1.0"
xmlns:exsl="http://exslt.org/common">
<xsl:strip-space elements="*"/>

<xsl:output method="text" encoding="UTF-8"/>

<xsl:key name="element-by-name" match="/Calls/ns0:Call//*" use="local-name()" />
<xsl:key name="cell" match="/Calls/ns0:Call//*" use="concat(generate-id(ancestor::ns0:Call), '|', local-name())" />
<xsl:variable name="root" select="/" />

<xsl:template match="/">

    <!-- unique column names -->
    <xsl:variable name="columns">
        <xsl:for-each select="Calls/ns0:Call//*[count(. | key('element-by-name', local-name())[1]) = 1]">
            <column><xsl:value-of select="local-name()"/></column>
        </xsl:for-each>
    </xsl:variable>

    <!-- header -->
    <xsl:for-each select="exsl:node-set($columns)/column">
        <xsl:value-of select="."/>
        <xsl:if test="position()!=last()">
            <xsl:text>,</xsl:text>
        </xsl:if>
    </xsl:for-each> 
    <xsl:text>&#10;</xsl:text>

    <!-- rows -->
    <xsl:for-each select="Calls/ns0:Call">
        <xsl:variable name="row-id" select="generate-id()" />
        <xsl:for-each select="exsl:node-set($columns)/column">
            <xsl:variable name="col" select="." />
            <!-- switch context back to document -->
            <xsl:for-each select="$root">
                <xsl:value-of select="key('cell', concat($row-id, '|', $col))/text()"/>
            </xsl:for-each> 
            <xsl:if test="position()!=last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
        </xsl:for-each> 
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
    </xsl:for-each> 
</xsl:template>

</xsl:stylesheet>

应用于以下输入

<?xml version="1.0"?>
<Calls>
    <Call xmlns="urn:eas-samples:en:xsd:phonecalls.1.0">
        <CustomerID>001</CustomerID>
        <Filter1>
            <AA>aa</AA>
            <BB>bb</BB>
        </Filter1>
        <Filter2>f2</Filter2> 
    </Call>
    <Call xmlns="urn:eas-samples:en:xsd:phonecalls.1.0">
        <CustomerID>002</CustomerID>        
        <Filter1>f1</Filter1> 
        <Filter2>f2</Filter2> 
        <Filter3>f3</Filter3> 
    </Call>
</Calls>

将返回:

CustomerID,Filter1,AA,BB,Filter2,Filter3
001,,aa,bb,f2,
002,f1,,,f2,f3

编辑:

如果你不知道元素名称,但确实知道“行”总是在第二级,即/*/*,那么你可以使用:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common">
<xsl:strip-space elements="*"/>

<xsl:output method="text" encoding="UTF-8"/>

<xsl:key name="element-by-name" match="/*/*//*" use="local-name()" />
<xsl:key name="cell" match="/*/*//*" use="concat(generate-id(ancestor::*[last()-1]), '|', local-name())" />
<xsl:variable name="root" select="/" />

<xsl:template match="/">

    <!-- unique column names -->
    <xsl:variable name="columns">
        <xsl:for-each select="*/*//*[count(. | key('element-by-name', local-name())[1]) = 1]">
            <column><xsl:value-of select="local-name()"/></column>
        </xsl:for-each>
    </xsl:variable>

    <!-- header -->
    <xsl:for-each select="exsl:node-set($columns)/column">
        <xsl:value-of select="."/>
        <xsl:if test="position()!=last()">
            <xsl:text>,</xsl:text>
        </xsl:if>
    </xsl:for-each> 
    <xsl:text>&#10;</xsl:text>

    <!-- rows -->
    <xsl:for-each select="*/*">
        <xsl:variable name="row-id" select="generate-id()" />
        <xsl:for-each select="exsl:node-set($columns)/column">
            <xsl:variable name="col" select="." />
            <!-- switch context back to document -->
            <xsl:for-each select="$root">
                <xsl:value-of select="key('cell', concat($row-id, '|', $col))/text()"/>
            </xsl:for-each> 
            <xsl:if test="position()!=last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
        </xsl:for-each> 
        <xsl:if test="position()!=last()">
            <xsl:text>&#10;</xsl:text>
        </xsl:if>
    </xsl:for-each> 
</xsl:template>

</xsl:stylesheet>