XSLT更快(=更有效)的行分组

时间:2016-04-26 08:55:17

标签: xml csv xslt xslt-1.0 grouping

使用XSLT分两步将CSV输入转换为XML。在第一次CSV-XML转换csv2xml之后,我想将属于同一行的所有元素分组到一个元素中,其中从第一行(标题)读取属性,即。

初始(CSV)输入

<signature>
stroke,x,y,t
1,0.585,0.226,1460579160811
1,0.653,0.231,1460579160812
1,1.251,1.125,1460579160813
1,2.224,1.672,1460579160814
1,2.716,1.761,1460579160815
1,3.675,1.877,1460579160816
1,4.008,1.984,1460579160817
1,4.888,2.81,1460579160818
</signature>

输出1(=输入2)

  <column row="1" col="1">stroke</column>
  <column row="1" col="2">x</column>
  <column row="1" col="3">y</column>
  <column row="1" col="4">t</column>
  <column row="2" col="1">1</column>
  <column row="2" col="2">0.585</column>
  <column row="2" col="3">0.226</column>
  <column row="2" col="4">1460579160811</column>
  <column row="3" col="1">1</column>
  <column row="3" col="2">0.653</column>
  <column row="3" col="3">0.231</column>
  <column row="3" col="4">1460579160812</column>
  <column row="4" col="1">1</column>
  <column row="4" col="2">1.251</column>
  <column row="4" col="3">1.125</column>
  <column row="4" col="4">1460579160813</column>

期望的最终输出

<?xml version="1.0"?>
<signature>
  <row stroke="1" x="0.585" y="0.226" t="1460579160811"/>
  <row stroke="1" x="0.653" y="0.231" t="1460579160812"/>
  <row stroke="1" x="1.251" y="1.125" t="1460579160813"/>
  <row stroke="1" x="2.224" y="1.672" t="1460579160814"/>

我使用以下XSLT模板实现:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet
  xmlns:exsl="http://exslt.org/common"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  extension-element-prefixes="exsl"
  version="1.0">
<xsl:strip-space elements="root"/>
<xsl:output method="xml" omit-xml-declaration="no"/>
<!-- Version -->
<xsl:variable name="xslver" select='1' />

<xsl:template name="csv2xml">
  <xsl:param name="input" select="''"/>
  <xsl:param name="column" select="1"/>
  <xsl:param name="row" select="1"/>

  <xsl:choose>
    <xsl:when test="contains(substring-before($input,','),'&#10;')">
      <xsl:element name="column">
        <xsl:attribute name="row">
          <xsl:value-of select="$row"/>
        </xsl:attribute>
        <xsl:attribute name="col">
          <xsl:value-of select="$column"/>
        </xsl:attribute>
        <xsl:value-of select="substring-before($input,'&#10;')"/>
      </xsl:element>
      <xsl:call-template name="csv2xml">
        <xsl:with-param name="input" select="substring-after($input,'&#10;')" />
        <xsl:with-param name="row" select="$row+1"/>
        <xsl:with-param name="column" select="1"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="contains($input,',')">
      <xsl:element name="column">
        <xsl:attribute name="row">
          <xsl:value-of select="$row"/>
        </xsl:attribute>
        <xsl:attribute name="col">
          <xsl:value-of select="$column"/>
        </xsl:attribute>
        <xsl:value-of select="substring-before($input,',')"/>
      </xsl:element>
      <xsl:call-template name="csv2xml">
        <xsl:with-param name="input" select="substring-after($input,',')" />
        <xsl:with-param name="row" select="$row"/>
        <xsl:with-param name="column" select="$column + 1" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:element name="column">
        <xsl:attribute name="row">
          <xsl:value-of select="$row"/>
        </xsl:attribute>
        <xsl:attribute name="col">
          <xsl:value-of select="$column"/>
        </xsl:attribute>
        <xsl:value-of select="$input" />
      </xsl:element>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="remap">
  <xsl:param name="input"/>
  <xsl:variable name="output">
    <xsl:call-template name="csv2xml">
      <xsl:with-param name="input" select="$input"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:variable name="csv" select="exsl:node-set($output)"/>

  <xsl:variable name="headers" select="$csv/column[@row=1]"/>
  <xsl:for-each select="$csv/column[not(@row = following-sibling::column/@row)]">
    <xsl:variable name="pos" select="position()"/>
    <xsl:variable name="rows" select="$csv/column[@row=$pos]"/>
    <xsl:element name="row">
      <xsl:for-each select="$headers">
        <xsl:variable name="attr" select="text()"/>
        <xsl:variable name="pos2" select="position()"/>
        <xsl:attribute name="{$attr}"><xsl:value-of select="$rows[@col=$pos2]/text()"/></xsl:attribute>
      </xsl:for-each>
    </xsl:element>
  </xsl:for-each>
</xsl:template>

<xsl:param name="root" value="'root'"/>
<xsl:template match="/">
  <xsl:variable name="trimmed">
    <xsl:call-template name="trim">
      <xsl:with-param name="input" select="*[local-name() = $root]"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:element name="{$root}">
    <xsl:call-template name="remap">
      <xsl:with-param name="input" select="$trimmed"/>
    </xsl:call-template>
  </xsl:element>
</xsl:template>

<xsl:template name="trim">
  <xsl:param name="input" select="''"/>
  <xsl:variable name="out-rtrim">
    <xsl:call-template name="rtrim">
      <xsl:with-param name="input" select="$input"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:variable name="out-ltrim">
    <xsl:call-template name="ltrim">
      <xsl:with-param name="input" select="$out-rtrim"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:value-of select="$out-ltrim"/>
</xsl:template>

<xsl:template name="rtrim">
  <xsl:param name="input">&quot;</xsl:param>
  <xsl:choose>
    <xsl:when test="string-length($input) &gt; 0">
      <xsl:variable name="first" select="substring($input,1,1)"/>
      <xsl:variable name="remainder" select="substring($input,2)"/>
      <xsl:choose>
        <xsl:when test="string-length(normalize-space($first)) &gt; 0">
          <xsl:value-of select="$input"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="rtrim">
            <xsl:with-param name="input" select="$remainder"/>
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise/>
  </xsl:choose>
</xsl:template>

<xsl:template name="ltrim">
  <xsl:param name="input">&quot;</xsl:param>
  <xsl:choose>
    <xsl:when test="string-length($input) &gt; 0">
      <xsl:variable name="head" select="substring($input,1,string-length($input)-1)"/>
      <xsl:variable name="tail" select="substring($input,string-length($input),1)"/>
      <xsl:choose>
        <xsl:when test="string-length(normalize-space($tail)) &gt; 0">
          <xsl:value-of select="$input"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="ltrim">
            <xsl:with-param name="input" select="$head"/>
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise/>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

按预期工作,但速度不快:输入500行的xsltproc大约需要1000ms,其中98%用于上面的转换,即。第2步。

有没有办法改善这个?也许通过展平两个xsl:for-each

1 个答案:

答案 0 :(得分:1)

- 根据您的澄清进行编辑 -

您的单元格已经分组为输入中的行。为什么你不做:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="exsl str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/signature">
    <!-- first pass: convert CSV to XML -->
    <xsl:variable name="table-rtf">
        <xsl:for-each select="str:tokenize(., '&#10;')">
            <row>
                <xsl:for-each select="str:tokenize(., ',')">
                    <col>
                        <xsl:value-of select="."/>
                    </col>
                </xsl:for-each>
            </row>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="table" select="exsl:node-set($table-rtf)"/>
    <!-- output -->
    <xsl:variable name="header" select="$table/row[1]" />
    <xsl:copy>
        <xsl:for-each select="$table/row[position() > 1]">
            <row> 
                <xsl:for-each select="col">
                    <xsl:variable name="i" select="position()" />
                    <xsl:attribute name="{$header/col[$i]}">
                        <xsl:value-of select="."/>
                    </xsl:attribute>
                </xsl:for-each>
            </row> 
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

这假设您的处理器支持str:tokenize() - 正如libxslt那样。