我一直在进行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() < 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() < 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方式呈现。
知道我做错了吗?
谢谢!
答案 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> </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> </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> </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> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>