使用XSLT将XML转换为CSV - 动态列

时间:2012-11-08 11:09:50

标签: xml xslt csv

我必须将XML文件转换为CSV文件。 输入XML文件是这样的:

<Person>
    <Name>John</Name>
    <FamilyMembers>
        <FamilyMember>
            <Name>Lisa</Name>
            <Type>Sister</Type>
        </FamilyMember>
        <FamilyMember>
            <Name>Tom</Name>
            <Type>Brother</Type>
        </FamilyMember>
    </FamilyMembers>
</Person>
<Person>
    <Name>Daniel</Name>
    <FamilyMembers>
        <FamilyMember>
            <Name>Peter</Name>
            <Type>Father</Type>
        </FamilyMember>
    </FamilyMembers>
</Person>

最终的CSV文件应如下所示:

Name;Sister;Brother;Father
John;Lisa;Tom
Daniel;;;Peter

我基本上想要的是每个具有不同内容的“Type”节点的一列。 “类型”没有限制。

编辑:我的实际XSLT将其解析为CSV,如下所示:

Name;Name;Type
John;Lisa;Sister
John;Tom;Brother
Daniel;Peter;Father 

有谁知道如何解决我的问题?

安德烈

3 个答案:

答案 0 :(得分:2)

这是一个XSLT1.0解决方案(感谢Martin!),它使用 xsl:key ,这通常是解决问题的最有效方法。基本上你试图按类型进行分组,因此要获得不同的家庭成员类型,你可以定义一个类似的键

<xsl:key name="Type" match="Type" use="." />

然后,对于标题行,要实际获取不同类型,您将迭代所有类型,但只选择首先出现在键中的给定值的记录

<xsl:apply-templates 
   select="//Type[generate-id() = generate-id(key('Type', .)[1])]" 
   mode="header" />

(标题的模式是因为类型记录会在一个单独的位置为家庭成员匹配,所以你需要区分匹配的模板)

接下来,您将选择每个记录,并且对于每个此类记录,您将再次选择不同的类型,但这次将当前的人员记录传递为参数,以便您可以提取相关的家庭成员

<xsl:apply-templates 
   select="//Type[generate-id() = generate-id(key('Type', .)[1])]" 
   mode="family">
   <xsl:with-param name="Person" select="." />
 </xsl:apply-templates>

在匹配的模板中(使用family的模式),然后您可以输出类型的相关家庭成员

             

这是完整的XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>
   <xsl:key name="Type" match="Type" use="." />

   <xsl:template match="/*">
      <xsl:text>Name</xsl:text>
      <xsl:apply-templates select="//Type[generate-id() = generate-id(key('Type', .)[1])]" mode="header" />
      <xsl:text>&#10;</xsl:text>
      <xsl:apply-templates select="Person" />
   </xsl:template>

   <xsl:template match="Person">
      <xsl:value-of select="Name" />
      <xsl:apply-templates select="//Type[generate-id() = generate-id(key('Type', .)[1])]" mode="family">
         <xsl:with-param name="Person" select="." />
      </xsl:apply-templates>
      <xsl:text>&#10;</xsl:text>
   </xsl:template>

   <xsl:template match="Type" mode="header">
      <xsl:text>;</xsl:text>
      <xsl:value-of select="." />
   </xsl:template>

   <xsl:template match="Type" mode="family">
      <xsl:param name="Person" />
      <xsl:text>;</xsl:text>
      <xsl:value-of select="$Person/FamilyMembers/FamilyMember[Type=current()]/Name" />
   </xsl:template>
</xsl:stylesheet>

当应用于XML(假设单个根元素)时,输出以下内容

Name;Sister;Brother;Father
John;Lisa;Tom;
Daniel;;;Peter

这确实假设每个人不能拥有一个以上的兄弟或姐妹等。

答案 1 :(得分:0)

样式表

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

<xsl:output method="text"/>

<xsl:template match="/">
    <xsl:text>Name;Sister;Brother;Father&#xd;</xsl:text>
    <xsl:for-each select="Persons/Person">
        <xsl:variable name="name" select="Name"/>
        <xsl:variable name="others">
            <xsl:value-of select="FamilyMembers/FamilyMember[Type/text()='Sister']/Name/text()"/>
            <xsl:text>;</xsl:text>
            <xsl:value-of select="FamilyMembers/FamilyMember[Type/text()='Brother']/Name/text()"/>
            <xsl:text>;</xsl:text>
            <xsl:value-of select="FamilyMembers/FamilyMember[Type/text()='Father']/Name/text()"/>
            <xsl:text>&#xd;</xsl:text>          
        </xsl:variable>
        <xsl:value-of select="concat($name,';',$others)"/>
    </xsl:for-each> 
</xsl:template>

</xsl:stylesheet>

应用于XML(修改为格式良好)时:

<Persons>
    <Person>
        <Name>John</Name>
        <FamilyMembers>
            <FamilyMember>
                <Name>Lisa</Name>
                <Type>Sister</Type>
            </FamilyMember>
            <FamilyMember>
                <Name>Tom</Name>
                <Type>Brother</Type>
            </FamilyMember>
        </FamilyMembers>
    </Person>
    <Person>
        <Name>Daniel</Name>
        <FamilyMembers>
            <FamilyMember>
                <Name>Peter</Name>
                <Type>Father</Type>
            </FamilyMember>
        </FamilyMembers>
    </Person>
</Persons>

<强>结果

Name;Sister;Brother;Father
John;Lisa;Tom;
Daniel;;;Peter

希望这会有所帮助。

答案 2 :(得分:0)

我使用XSLT 2.0:

<xsl:stylesheet 
  version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">

<xsl:param name="sep" as="xs:string" select="';'"/>

<xsl:key name="k1" match="FamilyMember" use="Type"/>

<xsl:output method="text"/>

<xsl:variable name="cols" as="xs:string*" select="('Name', distinct-values(//Person/FamilyMembers/FamilyMember/Type))"/>

<xsl:template match="/">
  <xsl:value-of select="$cols" separator="{$sep}"/>
  <xsl:text>&#10;</xsl:text>
  <xsl:apply-templates select="//Person"/>
</xsl:template>

<xsl:template match="Person">
  <xsl:value-of select="Name"/>
  <xsl:variable name="cells" as="xs:string*" select="
    for $col in $cols[position() gt 1] return (key('k1', $col, current())/Name, '')[1]"/>
  <xsl:sequence select="if (not(empty($cells))) then concat($sep, string-join($cells, $sep)) else ()"/>  
  <xsl:text>&#10;</xsl:text>
</xsl:template>

</xsl:stylesheet>

变换

<Persons>
<Person>
    <Name>John</Name>
    <FamilyMembers>
        <FamilyMember>
            <Name>Lisa</Name>
            <Type>Sister</Type>
        </FamilyMember>
        <FamilyMember>
            <Name>Tom</Name>
            <Type>Brother</Type>
        </FamilyMember>
    </FamilyMembers>
</Person>
<Person>
    <Name>Daniel</Name>
    <FamilyMembers>
        <FamilyMember>
            <Name>Peter</Name>
            <Type>Father</Type>
        </FamilyMember>
    </FamilyMembers>
</Person>
</Persons>

Name;Sister;Brother;Father
John;Lisa;Tom;
Daniel;;;Peter

我可能稍后尝试将其转换为XSLT 1.0解决方案,但我想如果Tim C已经尝试在XSLT 1.0中解决它,他会更快。