如何在使用函数

时间:2015-05-14 08:58:53

标签: python xml xslt saxon

TL; DR:

似乎比运行eXSLT的速度慢于XSLT2中的对应物。 (7分钟vs 18小时)

下面我解释我的问题,在eXSLT和XSLT2中写下同一转换的两个实现。

当然,引擎是不同的,因为XSLT2我使用SaxonHE,而对于eXSLT我使用python和lxml。

最后我请求帮助提高eXSLT部分的速度,因为我更喜欢使用python而不是Java。

我必须将大型(~200k第1层元素)XML转换为csv。

我有2个实现:

  • 一个使用python,所以下面是libxml,我使用的是eXSL。
  • 另一个使用SaxonHE,所以我使用XSL2转换。

由于编写CSV时,即使元素没有值,也必须打印分隔符,我采用了这种方法:

我创建了2个功能:

myf:printElement接收一个Element和一个数字,表示如果元素为空则必须写入的分隔符数。

myf:printAttr接收属性,并将其打印到分隔符。

如果我还将分隔符定义为:

<xsl:param name="delim" select="','"/>

函数在每个文件中声明如下:

XSLT2

<!-- Shortcut function to print an attribute plus a delimiter -->
<xsl:function name="myf:printAttr" as="xs:string">
    <xsl:param name="pAttr" as="attribute()*"/>
    <xsl:value-of select="concat($pAttr,$delim)"/>
</xsl:function>

<!-- This function will call the apply templates if the given elements exist. Else, it will return as many delimiters as the number given as second parameter -->
<xsl:function name="myf:printElement" as="item()*">
    <xsl:param name="pElement" as="element()*"/>
    <xsl:param name="pCount" as="xs:integer"/>
    <xsl:choose>
        <xsl:when test="$pElement">
            <xsl:apply-templates select="$pElement"/>
        </xsl:when>
        <xsl:otherwise>
            <!-- explicit void separator or will add an space -->
            <xsl:value-of select="for $i in 1 to $pCount return $delim" separator=""/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

EXSLT

<!-- Shortcut function to print an attribute plus a delimiter -->
<func:function name="myf:printAttr">
    <xsl:param name="pAttr"/>
    <func:result select="concat($pAttr,$delim)"/>
</func:function>
<!-- This function will call the apply templates if the given elements exist. Else, it will return as many delimiters as the number given as second parameter -->
<func:function name="myf:printElement" as="item()*">
    <xsl:param name="pElement" as="element()*"/>
    <xsl:param name="pCount" as="xs:integer"/>
    <xsl:choose>
        <xsl:when test="$pElement">
            <func:result>
                <xsl:apply-templates select="$pElement"/>
            </func:result>
        </xsl:when>
        <xsl:otherwise>
            <!-- explicit void separator or will add an space -->
            <func:result select="str:padding($pCount,$delim)"/>
        </xsl:otherwise>
    </xsl:choose>
</func:function>

其余文件是相同的。

所以,假设我有这样的XML:

<root>
  <Tier1 attr1="A" attr2="B"/>
  <Tier1 attr1="C" attr2="D">
    <Child2 type="1" val="ABC"/>
    <Child2 type="3" val="123"/>
  </Tier1>
  <Tier1 attr1="E" attr2="F">
    <Child2 type="2" val="pancakes"/>
    <Child2 type="1" val="42"/>
    <Child3 a="H">
        <Child4 Month="JUN"/>
    </Child3>
  </Tier1>
</root>

使用:

<xsl:param name="break" select="'&#xA;'"/>
<xsl:template match="/">
     <xsl:apply-templates select="root/Tier1"/>`
</xsl:template>
<xsl:template match="Tier1">
    <xsl:value-of select="myf:printAttr(@attr1)"/>
    <xsl:value-of select="myf:printAttr(@attr2)"/>
    <xsl:value-of select="myf:printAttr(Child2[@type='1']/@val)"/>
    <xsl:value-of select="myf:printAttr(Child2[@type='2']/@val)"/>
    <xsl:value-of select="myf:printAttr(Child2[@type='3']/@val)"/>
    <xsl:apply-templates/>
    <!-- line break after each Tier1 -->
    <xsl:if test="following-sibling::*">
        <xsl:value-of select="$break"/>
    </xsl:if>
</xsl:template>
<xsl:template match="Child3">
    <xsl:value-of select="myf:printAttr(@a)"/>
    <xsl:value-of select="ama:printElement(Child4,3)"/>
</xsl:template>
<xsl:template match="Child4">
    <xsl:value-of select="myf:printAttr(@Day)"/>
    <xsl:value-of select="myf:printAttr(@Month)"/>
    <!-- We dont want comma after last element-->
    <xsl:value-of select=@Average/>
</xsl:template>

我会得到所需的csv输出:

T1_attr1, T1_attr2, C2_t1, C2_t2, C2_t3, C3_a, C4_Mont, C4_Day, C4_Average
A,B,,,,,,,
C,D,ABC,,123,,,,
E,F,42,pancakes,,H,JUN,3,1200

关于上述内容的一些注意事项:

  • 可以在Tier1下重复Child2,但只有一组给定的值类型,而不是重复。

  • 此外,元素中没有文字,这使得这两种功能的方法涵盖了我可能遇到的所有可能情况。虽然printAttr也可能适用于文本节点。

  • 我添加了列名,以便于阅读。在代码中我在start时添加它,一个用eXSLT设置的内部节点,一个带XSLT2的简单字符串数组。

所以,现在,问题是:

正如我在开始时所说的,我必须将变换运行到一个巨大的文件,其中包含超过20万个Tier1元素。

  • 使用SaxonHE需要7分钟
  • 使用Python,需要 18小时

两个转换脚本/程序都这样做:

  1. 打开文件
  2. 打开XSLT
  3. 将后者应用于前者
  4. 保存结果
  5. 我知道我在谈论变换引擎的不同实现,但是由于这个原因,这种差异太明显了。 测试相同引擎的唯一方法是使用Saxon-PE或Saxon-EE下的eXSLT,因为它在Saxon-HE中不可用。 当然,python中没有XSLT2实现。

    我想知道为什么python版本需要太长时间。这是使用eXSLT固有的吗?或有没有办法改善这个?

    当然这是一个示例XML,真正的XML有很多元素,而且它实际上更复杂。

    这是一个较大项目的一部分,我只想依赖于JVM,但是,差异是如此之大,以至于现在,Python不是一个选项。

    谢谢!

1 个答案:

答案 0 :(得分:2)

对我来说,看起来好像你正在大量过度设计这个问题。

以下简单的XSLT 1.0转换

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

  <xsl:template match="/root">
    <xsl:text>T1_attr1,T1_attr2,C2_t1,C2_t2,C2_t3,C3_a,C4_Month,C4_Day,C4_Average</xsl:text>
    <xsl:apply-templates select="Tier1" />
  </xsl:template>

  <xsl:template match="Tier1">
    <xsl:text>&#xA;</xsl:text>
    <xsl:value-of select="@attr1" />                   <xsl:text>,</xsl:text>
    <xsl:value-of select="@attr2" />                   <xsl:text>,</xsl:text>
    <xsl:value-of select="Child2[@type = '1']/@val" /> <xsl:text>,</xsl:text>
    <xsl:value-of select="Child2[@type = '2']/@val" /> <xsl:text>,</xsl:text>
    <xsl:value-of select="Child2[@type = '3']/@val" /> <xsl:text>,</xsl:text>
    <xsl:value-of select="Child3/@a" />                <xsl:text>,</xsl:text>
    <xsl:value-of select="Child3/Child4/@Month" />     <xsl:text>,</xsl:text>
    <xsl:value-of select="Child3/Child4/@Day" />       <xsl:text>,</xsl:text>
    <xsl:value-of select="Child3/Child4/@Average" />
  </xsl:template>
</xsl:transform>

应用于

<root>
  <Tier1 attr1="A" attr2="B">
  </Tier1>
  <Tier1 attr1="C" attr2="D">
    <Child2 type="1" val="ABC" />
    <Child2 type="3" val="123" />
  </Tier1>
  <Tier1 attr1="E" attr2="F">
    <Child2 type="2" val="pancakes" />
    <Child2 type="1" val="42" />
    <Child3 a="H">
        <Child4 Month="JUN" Day="3" Average="1200" />
    </Child3>
  </Tier1>
</root>

产生

T1_attr1,T1_attr2,C2_t1,C2_t2,C2_t3,C3_a,C4_Month,C4_Day,C4_Average
A,B,,,,,,,
C,D,ABC,,123,,,,
E,F,42,pancakes,,H,JUN,3,1200