【问题标题】:Better way to convert attributes with XSLT?用 XSLT 转换属性的更好方法?
【发布时间】:2017-03-01 17:29:19
【问题描述】:

必须有更好的方法来做到这一点!我想将 XML 文档中的所有单位转换为基本单位(例如 x value='1.0' units='Mbps'/ 变为 \x value='1000000' units='bps'/)。我还想保留节点中的其他属性。

我知道我可以使用 lxml 遍历文档并更改属性,但我认为这是 XSLT 的完美任务。

这个 XML:

<generator>
    <enable value="0"/>
    <errorPattern value="Off" units=""/>
    <errorMode value="Off" units=""/>
    <errorRate value="1000000"/>
    <bitRate value="1.638" units="Mbps" blah="foo">
        <subelement value="2" units="Kbps"/>
    </bitRate>
</generator>

变成:

<generator>
    <enable value="0"/>
    <errorPattern value="Off" units=""/>
    <errorMode value="Off" units=""/>
    <errorRate value="1000000"/>
    <bitRate value="1638000" units="bps" blah="foo">
        <subelement value="2000" units="bps"/>
    </bitRate>
</generator>

下面的单元测试会这样做,但 xsl:stylesheet 很糟糕。我认为我将能够根据单位设置一个乘数,然后在一些通用代码中使用该乘数。但是,我必须复制“Mbps”和“Kbps”的模板。

一定有更好的方法吧?虽然还在使用 lxml。

import unittest
from lxml import isoschematron
from lxml import etree
from StringIO import StringIO


xml = '''\
<generator>
    <enable value="0"/>
    <errorPattern value="Off" units=""/>
    <errorMode value="Off" units=""/>
    <errorRate value="1000000"/>
    <bitRate value="1.638" units="Mbps" blah='foo'>
        <subelement value='2' units='Kbps'/>
    </bitRate>
</generator>'''

transform_units=etree.XML('''\
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>

    <!-- Convert Mbps to base units --> 
    <xsl:template match="*[@units='Mbps']">
        <xsl:param name='multiplier'>1000000</xsl:param>
        <xsl:copy>
            <xsl:apply-templates select='@*'/>
            <xsl:attribute name='value'><xsl:value-of select='@value * $multiplier'/></xsl:attribute>
            <xsl:attribute name='units'>bps</xsl:attribute>
            <xsl:apply-templates select='node()'/>
        </xsl:copy>
    </xsl:template>

    <!-- Convert Kbps to base units --> 
    <xsl:template match="*[@units='Kbps']">
        <xsl:param name='multiplier'>1000</xsl:param>
        <xsl:copy>
            <xsl:apply-templates select='@*'/> <!-- copy all attributes -->
            <xsl:attribute name='value'><xsl:value-of select='@value * $multiplier'/></xsl:attribute>
            <xsl:attribute name='units'>bps</xsl:attribute>
            <xsl:apply-templates select='node()'/> <!-- process child nodes -->
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>''')

class Test(unittest.TestCase):


    def setUp(self):
        self.ns = namespaces={'svrl':'http://purl.oclc.org/dsdl/svrl'}
        self.transformUnits = etree.XSLT(transform_units)

    def tearDown(self):
        pass

    def test_transformUnits(self):
        doc = etree.fromstring(xml)
        print etree.tostring(doc)
        res = self.transformUnits(doc)
        print etree.tostring(res)



if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.testName']
    unittest.main()

输出:

pydev debugger: starting (pid: 10828)
Finding files... done.
Importing test modules ... done.

<generator>
    <enable value="0"/>
    <errorPattern value="Off" units=""/>
    <errorMode value="Off" units=""/>
    <errorRate value="1000000"/>
    <bitRate value="1.638" units="Mbps" blah="foo">
        <subelement value="2" units="Kbps"/>
    </bitRate>
</generator>
<generator>
    <enable value="0"/>
    <errorPattern value="Off" units=""/>
    <errorMode value="Off" units=""/>
    <errorRate value="1000000"/>
    <bitRate value="1638000" units="bps" blah="foo">
        <subelement value="2000" units="bps"/>
    </bitRate>
</generator>
----------------------------------------------------------------------
Ran 1 test in 0.010s

OK

更新

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

<xsl:template match="node()|@*">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*"></xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<xsl:template match="*[@units='Kbps'] | *[@units='Mbps']">
    <xsl:variable name="multi">
        <xsl:choose>
            <xsl:when test="@units = 'Mbps'">1000000</xsl:when>
            <xsl:when test="@units = 'Kbps'">1000</xsl:when>
        </xsl:choose>
    </xsl:variable>
    <xsl:copy>
        <xsl:apply-templates select='@*'/> <!-- copy all attributes -->
        <xsl:attribute name="units">bps</xsl:attribute>
        <xsl:attribute name="value"><xsl:value-of select="@value * number($multi)"/></xsl:attribute>
        <xsl:apply-templates select='node()'/> <!-- process child nodes -->
    </xsl:copy>
    <units><xsl:value-of select='$multi'/></units>
</xsl:template>
</xsl:stylesheet>

【问题讨论】:

    标签: xml unit-testing xslt lxml


    【解决方案1】:

    你可以用这个

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"></xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="*[matches(@units, '[MK]bps')]">
        <xsl:variable name="multi">
            <xsl:choose>
                <xsl:when test="@units eq 'Mbps'">1000000</xsl:when>
                <xsl:when test="@units eq 'Kbps'">1000</xsl:when>
            </xsl:choose>
        </xsl:variable>
        <xsl:copy>
            <xsl:attribute name="value" select="@value * number($multi)"/>
            <xsl:attribute name="units" select="'bps'"/>
            <xsl:copy-of select="@* except (@value, @units)"></xsl:copy-of>
            <xsl:apply-templates select="node()"></xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    

    【讨论】:

    • 遗憾的是,您的某些语法不适用于 lxml 模块。 “除了()”例如。但是,我确实让 xsl:variable 工作。谢谢。
    • @Shrewmouse 这个答案需要 XSLT 2.0。您显然使用的是 XSLT 1.0。
    • 很遗憾,lxml 只支持 1.0。
    【解决方案2】:

    我必须复制“Mbps”和“Kbps”的模板。

    XSLT(尤其是 XSLT 1.0)自然是冗长的,每个案例都有一个模板并没有错。如果你想消除重复代码,你可以使用类似的东西:

    <xsl:template match="*[contains(@units, 'bps')]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:variable name="prefix" select="substring-before(@units, 'bps')" />
            <xsl:attribute name="value">
                <xsl:choose>
                    <xsl:when test="$prefix='M'">
                        <xsl:value-of select="@value * 1000000"/>
                    </xsl:when>
                    <xsl:when test="$prefix='K'">
                        <xsl:value-of select="@value * 1000"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="@value"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
            <xsl:attribute name="units">bps</xsl:attribute>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    

    【讨论】:

      猜你喜欢
      • 2013-08-15
      • 1970-01-01
      • 2021-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-04
      • 1970-01-01
      相关资源
      最近更新 更多