【问题标题】:convert xml document to comma delimited (CSV) file using xslt stylesheet使用 xslt 样式表将 xml 文档转换为逗号分隔 (CSV) 文件
【发布时间】:2011-03-04 15:36:01
【问题描述】:

我需要一些帮助,使用 xslt 样式表将 xml 文档转换为 CSV 文件。我正在尝试使用以下 xsl,但似乎无法正确使用。我希望我的逗号分隔文件包含列标题,然后是数据。我最大的问题是删除最后一项后的最后一个逗号并插入一个回车符,以便每组数据出现在单独的行上。我一直在使用 XML 记事本。

  <xsl:template match="/">
        <xsl:element name="table">
              <xsl:apply-templates select="/*/*[1]" mode="header" />
              <xsl:apply-templates select="/*/*" mode="row" />
        </xsl:element>
  </xsl:template>

  <xsl:template match="*" mode="header">
        <xsl:element name="tr">
              <xsl:apply-templates select="./*" mode="column" />
        </xsl:element>
  </xsl:template>

  <xsl:template match="*" mode="row">
        <xsl:element name="tr">
              <xsl:apply-templates select="./*" mode="node" />
        </xsl:element>
  </xsl:template>

  <xsl:template match="*" mode="column">
        <xsl:element name="th">
              <xsl:value-of select="translate(name(.),'qwertyuiopasdfghjklzxcvbnm_','QWERTYUIOPASDFGHJKLZXCVBNM ')" />
        </xsl:element>,
  </xsl:template>

  <xsl:template match="*" mode="node">
        <xsl:element name="td">
              <xsl:value-of select="." />
        </xsl:element>,
  </xsl:template> 

【问题讨论】:

  • 我检查了上面给出的另一个问题的链接,但这对我没有帮助。谢谢
  • 请提供一个示例 XML 文档以及您希望从中获得的确切结果。
  • 另请注意,您正在结果树中创建元素。我无法想象如何将其解释为 CSV。

标签: xml xslt csv


【解决方案1】:

我使用它来简单的 XSLT 将 XML 转换为 CSV;它假定根节点的所有子节点都是 CSV 中的行,将根节点的第一个子节点的元素名称作为字段名称。

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

  <xsl:template match="/">
    <xsl:for-each select="*/*[1]/*">
      <xsl:value-of select="name()" />
      <xsl:if test="not(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:apply-templates select="*" mode="data" />
    <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>

所以这个 XML:

<csv>
  <row>
    <field1>foo</field1>
    <field2>ba"r</field2>
  </row>
  <row>
    <field1>foo,2</field1>
    <field2>bar,"2</field2>
  </row>
</csv>

转换为:

field1,field2
foo,ba"r
"foo,2","bar,""2"

不确定这是否有帮助,这取决于您的 XML 的布局方式。

编辑:这是一个更彻底的转换:

<?xml version="1.0" encoding="utf-8"?>
<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>

这将在您的 CSV 中为所有“行”中存在的所有标签名称创建一列,并在每一行中填充相应的列。

【讨论】:

  • 完美!非常感谢您的帮助!
  • 您写道:它假定根节点的所有子节点都是 CSV 中的行。我想你假设更多。以您的示例输入文档为例:您在样式表中假设第一行中的字段是所有可以存在的字段,并且进一步假设各个行的字段顺序相同,并且没有丢失。跨度>
  • 当然,这是一个相当简单的例程。我最初编写它是为了将 xhtml 中的 标记转换为 CSV,除非你有 colspan 属性,否则它工作得很好。 XML 显然是一种比 CSV 灵活得多的格式,因此如果不符合这些假设,它就不会很好地转换。当然,您始终可以先使用 XSD 验证它。
  • 想一想,调整转换以正确执行此操作并不难;我在没有做出这些假设的更彻底的 XSLT 中进行了编辑;它仍然假设您的 XML 只有两层深(在根之下),并且每一层深的元素都打算作为一行。它确实具有的一个限制是“行”具有相同的标签名称,具有讽刺意味的是,就像
  • 标签一样。我的原始转换实际上将
    标记的内容作为列名而不是节点名;我上面的第一个转换是一个更通用的版本。
【解决方案2】:

我正在使用来自@flynn1179 的答案的 xslt 的“更彻底的转换”.. 但发现当行数增加时需要花费大量时间。 (这是使用 oracle dbms_xmlgen xslt 转换)

我发现,因为我知道我的 2 级数据被称为什么(在这种情况下为 ROWSET 和 ROW),所以我能够优化 xslt 并获得显着的性能提升。

我的修改版本(专门针对 oracle 的 dbms_xmlgen 包)如下。它还包括我在互联网上找到的其他 csv 转换的位。

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

 <!-- some variables for unprintable charcaters -->
<xsl:variable name="CRLF">  
  <xsl:text>&#13;&#10;</xsl:text>  
</xsl:variable>  
<xsl:variable name="CR">  
  <xsl:text>&#13;</xsl:text>  
</xsl:variable>  
<xsl:variable name="LF">  
  <xsl:text>&#10;</xsl:text>  
</xsl:variable>  
<xsl:variable name="apos">'</xsl:variable>

<xsl:template match="/ROWSET">
<xsl:for-each select="ROW[1]/*">
  <xsl:value-of select="local-name()" />
  <xsl:if test="position() != last()">,</xsl:if>
</xsl:for-each>
<xsl:value-of select="$LF"/>
<xsl:apply-templates />
</xsl:template>

<xsl:template match="ROW">
  <xsl:apply-templates />
  <xsl:value-of select="$LF"/>
</xsl:template>

<xsl:template match="ROW/*">
  <xsl:choose>
  <xsl:when test="contains( text(), ',' ) or   
                  contains( text(), $apos ) or  
                  contains( text(), $CRLF ) or  
                  contains( text(), $CR ) or  
                  contains( text(), $LF )">
    <!-- Field contains a comma, apostrophe and/or a linefeed, so quote --> 
    <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;')">
    <!-- recursive call -->
    <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>

【讨论】:

    猜你喜欢
    相关资源
    最近更新 更多
    热门标签