将嵌套表xml转换为CSV,其中表元素是表名,行和列的连接

时间:2013-12-31 23:47:53

标签: xml xslt csv

我有这种格式的源XML。源的相关部分位于标记下,嵌套约四个级别,并表示一个或多个数据表,其中元素的格式为:name = TableName-Row# .FieldName 和value = fieldValue

<root>
    <someData>1</someData>
    <otherData>free</otherData>
    <parent1>
        <parent2>
            <parent3>
                <values>
                    <data>
                        <name>ComputerInfo-1.CPU</name>
                        <value>4</value>
                    </data>
                    <data>
                        <name>ComputerInfo-1.Memory</name>
                        <value>32</value>
                    </data>
                    <data>
                        <name>ComputerInfo-1.Storage</name>
                        <value>1024</value>
                    </data>
                    <data>
                        <name>ComputerInfo-2.CPU</name>
                        <value>2</value>
                    </data>
                    <data>
                        <name>ComputerInfo-2.Memory</name>
                        <value>64</value>
                    </data>
                    <data>
                        <name>ComputerInfo-2.Storage</name>
                        <value>2048</value>
                    </data>
                    <data>
                        <name>ComputerInfo-3.CPU</name>
                        <value>4</value>
                    </data>
                    <data>
                        <name>ComputerInfo-3.Memory</name>
                        <value>16</value>
                    </data>
                    <data>
                        <name>ComputerInfo-3.Storage</name>
                        <value>512</value>
                    </data>
                    <data>
                        <name>UserInfo-1.firstName</name>
                        <value>Mary</value>
                    </data>
                    <data>
                        <name>UserInfo-1.lastName</name>
                        <value>Jones</value>
                    </data>
                    <data>
                        <name>UserInfo-1.login</name>
                        <value>mjones</value>
                    </data>
                    <data>
                        <name>UserInfo-2.firstName</name>
                        <value>Doctor</value>
                    </data>
                    <data>
                        <name>UserInfo-2.lastName</name>
                        <value>Who</value>
                    </data>
                    <data>
                        <name>UserInfo-2.login</name>
                        <value>dwho</value>
                    </data>
                    <data>
                        <name>UserInfo-3.firstName</name>
                        <value>John</value>
                    </data>
                    <data>
                        <name>UserInfo-3.lastName</name>
                        <value>Mellencamp</value>
                    </data>
                    <data>
                        <name>UserInfo-3.login</name>
                        <value>cougar69</value>
                    </data>
                </values>
            </parent3>
        </parent2>
    </parent1>
</root>

所需的结果是一个CSV文件,标题行位于顶部,后跟相应的行:

CPU,Memory,Storage
4,32,1024
2,64,2048
4,16,512

我还想使用变量来保存我想要处理的特定表格,例如

<xsl:variable name="table" select="/root/tableName"/>

这是因为我有能力通过应用程序在我选择的时候在源XML中包含tableName。但是, xsl:key 不允许我在匹配中使用变量,而且我也被迫使用XSLT 1.0。

我的问题是我可以分步进行,但我真的需要在一次变换中这样做 这是我到目前为止所拥有的。

我可以将Source XML传递给这个转换:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

    <xsl:template match="/root/parent1/parent2/parent3/values/data">
        <xsl:element name="{name}">
            <xsl:value-of select="value" />
        </xsl:element>
    </xsl:template>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="/">
        <values>
            <xsl:apply-templates select="//values/node()"/>
        </values>
    </xsl:template>

</xsl:stylesheet>

它会产生这个结果:

<values>
    <ComputerInfo-1.CPU>4</ComputerInfo-1.CPU>
    <ComputerInfo-1.Memory>32</ComputerInfo-1.Memory>
    <ComputerInfo-1.Storage>1024</ComputerInfo-1.Storage>
    <ComputerInfo-2.CPU>2</ComputerInfo-2.CPU>
    <ComputerInfo-2.Memory>64</ComputerInfo-2.Memory>
    <ComputerInfo-2.Storage>2048</ComputerInfo-2.Storage>
    <ComputerInfo-3.CPU>4</ComputerInfo-3.CPU>
    <ComputerInfo-3.Memory>16</ComputerInfo-3.Memory>
    <ComputerInfo-3.Storage>512</ComputerInfo-3.Storage>
    <UserInfo-1.firstName>Mary</UserInfo-1.firstName>
    <UserInfo-1.lastName>Jones</UserInfo-1.lastName>
    <UserInfo-1.login>mjones</UserInfo-1.login>
    <UserInfo-2.firstName>Doctor</UserInfo-2.firstName>
    <UserInfo-2.lastName>Who</UserInfo-2.lastName>
    <UserInfo-2.login>dwho</UserInfo-2.login>
    <UserInfo-3.firstName>John</UserInfo-3.firstName>
    <UserInfo-3.lastName>Mellencamp</UserInfo-3.lastName>
    <UserInfo-3.login>cougar69</UserInfo-3.login>
</values>

然后我可以获取此结果并应用以下转换:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

    <xsl:key name="elementByRow" match="/*/*[contains(name(), 'ComputerInfo')]" use="substring-before(name(), '.')"/>

    <xsl:template match="/*">
        <values>
            <xsl:apply-templates select="*[generate-id() = generate-id(key('elementByRow', substring-before(name(), '.'))[1])]" />
        </values>
    </xsl:template>

    <xsl:template match="*">
        <Row>
            <xsl:for-each select="key('elementByRow', substring-before(name(), '.'))">
                <xsl:element name="{substring-after(name(), '.')}">
                    <xsl:value-of select="." />
                </xsl:element>
            </xsl:for-each>
        </Row>
    </xsl:template>

</xsl:stylesheet>

将产生这个结果:

<values>
    <Row>
        <CPU>4</CPU>
        <Memory>32</Memory>
        <Storage>1024</Storage>
    </Row>
    <Row>
        <CPU>2</CPU>
        <Memory>64</Memory>
        <Storage>2048</Storage>
    </Row>
    <Row>
        <CPU>4</CPU>
        <Memory>16</Memory>
        <Storage>512</Storage>
    </Row>
</values>

我专门针对这个以前的格式,因为我在这里发现了另一个帖子(convert xml document to comma delimited (CSV) file using xslt stylesheet),它允许我获取此结果并应用上述帖子中的变换来获得所需的结果。以下是该帖子的转换参考:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="field" match="/*/*/*" use="name()" />
    <xsl:output method="text"/>

    <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:if>
        </xsl:for-each>
        <xsl:text>&#10;</xsl:text>
        <xsl:apply-templates select="*/*" mode="row"/>
    </xsl:template>

    <xsl:template match="*" mode="row">
        <xsl:variable name="row" select="*" />
        <xsl:for-each select="/*/*/*[generate-id() = generate-id(key('field',name())[1])]">
            <xsl:variable name="name" select="name()" />
            <xsl:apply-templates select="$row[name()=$name]" mode="data" />
            <xsl:if test="position() != last()">,</xsl:if>
        </xsl:for-each>
        <xsl:text>&#10;</xsl:text>
    </xsl:template>

    <xsl:template match="*" mode="data">
        <xsl:choose>
            <xsl:when test="contains(text(),',')">
                <xsl:text>&quot;</xsl:text>
                <xsl:call-template name="doublequotes">
                    <xsl:with-param name="text" select="text()" />
                </xsl:call-template>
                <xsl:text>&quot;</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="." />
            </xsl:otherwise>
        </xsl:choose>
        <xsl:if test="position() != last()">,</xsl:if>
    </xsl:template>

    <xsl:template name="doublequotes">
        <xsl:param name="text" />
        <xsl:choose>
            <xsl:when test="contains($text,'&quot;')">
                <xsl:value-of select="concat(substring-before($text,'&quot;'),'&quot;&quot;')" />
                <xsl:call-template name="doublequotes">
                    <xsl:with-param name="text" select="substring-after($text,'&quot;')" />
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$text" />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

1 个答案:

答案 0 :(得分:2)

你不能简单地这样做:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output method="text" encoding="utf-8"/>

<xsl:template match="/">
    <xsl:text>CPU,Memory,Storage&#10;</xsl:text>
    <xsl:for-each select="root/parent1/parent2/parent3/values/data[starts-with(./name, 'ComputerInfo')]">
        <xsl:value-of select="value" />
        <xsl:choose>
            <xsl:when test="position() mod 3">
                <xsl:value-of select="','" />
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="'&#10;'" />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each>
</xsl:template>
</xsl:stylesheet>  

假设数据的结构是不变的。


<强>增加:

现在,如果您想概括上述内容,可以执行以下操作:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output method="text" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/">
<xsl:variable name="tableName" select="root/tableName"/>
<xsl:variable name="tableData" select="root/parent1/parent2/parent3/values/data[substring-before(./name, '-')=$tableName]" />
<xsl:variable name="firstRow" select="$tableData[substring-before(substring-after(./name, '-'), '.')='1']" />
<xsl:variable name="countCols" select="count($firstRow)" />

    <xsl:for-each select="$firstRow">
    <xsl:value-of select="substring-after(./name, '-1.')" />
            <xsl:choose>
                <xsl:when test="position() mod $countCols">
                    <xsl:value-of select="','" />
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="'&#10;'" />
                </xsl:otherwise>
            </xsl:choose>
    </xsl:for-each> 

    <xsl:for-each select="$tableData">
        <xsl:value-of select="value" />
        <xsl:choose>
            <xsl:when test="position() mod $countCols">
                <xsl:value-of select="','" />
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="'&#10;'" />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each>

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

给出输入:

<root>
    <tableName>ComputerInfo</tableName>
    <someData>1</someData>
    <otherData>free</otherData>
    <parent1>
        <parent2>
            <parent3>
                <values>
                    <data>
                        <name>ComputerInfo-1.CPU</name>
                        <value>4</value>
                    </data>
                    <data>
                        <name>ComputerInfo-1.Memory</name>
                        <value>32</value>
                    </data>
                    <data>
                        <name>ComputerInfo-1.Storage</name>
                        <value>1024</value>
                    </data>
                    <data>
                        <name>ComputerInfo-1.Age</name>
                        <value>11</value>
                    </data>
                    <data>
                        <name>ComputerInfo-2.CPU</name>
                        <value>2</value>
                    </data>
                    <data>
                        <name>ComputerInfo-2.Memory</name>
                        <value>64</value>
                    </data>
                    <data>
                        <name>ComputerInfo-2.Storage</name>
                        <value>2048</value>
                    </data>
                    <data>
                        <name>ComputerInfo-2.Age</name>
                        <value>22</value>
                    </data>
                    <data>
                        <name>ComputerInfo-3.CPU</name>
                        <value>4</value>
                    </data>
                    <data>
                        <name>ComputerInfo-3.Memory</name>
                        <value>16</value>
                    </data>
                    <data>
                        <name>ComputerInfo-3.Storage</name>
                        <value>512</value>
                    </data>
                    <data>
                        <name>ComputerInfo-3.Age</name>
                        <value>33</value>
                    </data>
                    <data>
                        <name>UserInfo-1.firstName</name>
                        <value>Mary</value>
                    </data>
                    <data>
                        <name>UserInfo-1.lastName</name>
                        <value>Jones</value>
                    </data>
                    <data>
                        <name>UserInfo-1.login</name>
                        <value>mjones</value>
                    </data>
                    <data>
                        <name>UserInfo-2.firstName</name>
                        <value>Doctor</value>
                    </data>
                    <data>
                        <name>UserInfo-2.lastName</name>
                        <value>Who</value>
                    </data>
                    <data>
                        <name>UserInfo-2.login</name>
                        <value>dwho</value>
                    </data>
                    <data>
                        <name>UserInfo-3.firstName</name>
                        <value>John</value>
                    </data>
                    <data>
                        <name>UserInfo-3.lastName</name>
                        <value>Mellencamp</value>
                    </data>
                    <data>
                        <name>UserInfo-3.login</name>
                        <value>cougar69</value>
                    </data>
                </values>
            </parent3>
        </parent2>
    </parent1>
</root>

它会返回:

CPU,Memory,Storage,Age
4,32,1024,11
2,64,2048,22
4,16,512,33

但如果您将<tableName>更改为“UserInfo”,则现在会生成相同的样式表:

firstName,lastName,login
Mary,Jones,mjones
Doctor,Who,dwho
John,Mellencamp,cougar69