使用XSLT计算同级元素的值之和

问题描述 投票:1回答:2

我正在使用XSLT进行测量的自动转换。从一个系统(例如英制)到另一个系统(例如公制)的单次测量转换工作正常。但是英制尺寸可以采用“ 5 ft 10 in”的形式,我想将其转换为单个度量值。

在我的XML模型中,我允许单个值或多个子节点来容纳这样的组合度量。因此,当我发现自己有子节点时,需要将这些子元素中的每一个转换为度量单位,然后将这些值相加以获得一个度量标准结果。

我正在努力找到处理多个子节点并累加结果值的最佳方法。用迭代语言,我只会从第一个处理到下一个,并更新一个全局变量,但是在XSLT中,我不知道是否存在可以从后续调用同一模板进行更新的全局变量之类的东西。

这里是一个(简化的)变换-这个仅处理[ft_i]和[in_i]到m。

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

<xsl:output method="xml" encoding="UTF-8"/>

<xsl:template match="/">
    <xsl:apply-templates/>
</xsl:template>

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

<xsl:template match="measurement">
    <xsl:copy>
        <xsl:choose>
            <xsl:when test="measurement">
                <xsl:apply-templates select="*"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="normalise">
                    <xsl:with-param name="val" as="xs:double" select="number(text())"/>
                    <xsl:with-param name="unitin" select="@ucum"/>
                    <xsl:with-param name="count" as="xs:integer" select="1"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

<xsl:template name="normalise">
    <xsl:param name="val" as="xs:double"/>
    <xsl:param name="unitin"/>
    <xsl:param name="count" as="xs:integer"/>
    <xsl:choose>
        <xsl:when test="$unitin eq '[ft_i]'">
            <xsl:attribute name="ucum">
                <xsl:value-of select="'m'"/>
            </xsl:attribute>
            <xsl:attribute name="unit">
                <xsl:value-of select="'m'"/>
            </xsl:attribute>
            <xsl:value-of select="$val * 0.3048"/>
        </xsl:when>
        <xsl:when test="$unitin eq '[in_i]'">
            <xsl:attribute name="ucum">
                <xsl:value-of select="'m'"/>
            </xsl:attribute>
            <xsl:attribute name="unit">
                <xsl:value-of select="'m'"/>
            </xsl:attribute>
            <xsl:value-of select="$val * 0.0254"/>
        </xsl:when>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

一个简单的测试文件:

<topic>
    <p>This piece is 
        <measurement>
            <measurement unit="ft" 
                ucum=" [ft_i]">10</measurement>
            <measurement unit="in" 
                ucum="[in_i]">2</measurement>
        </measurement> 
        long
    </p>        
</topic>

转换给出了这个:

<topic>
    <p>This piece is 
        <measurement>
            <measurement ucum="m"
                unit="m">3.048</measurement>
            <measurement ucum="m" 
                unit="m">0.0508</measurement>
        </measurement> 
        long
    </p>        
</topic>

显然,我希望看到这个:

<topic>
    <p>This piece is 
        <measurement ucum="m" 
            unit="m">3.0988</measurement>
        long
    </p>        
</topic>

我可以在子度量节点上使用xsl:for-each,但是如何将单独的值添加到可以从主模板输出的全局值中?

loops xslt calculation
2个回答
0
投票

假定某些值和属性是恒定的,最简单的方法是使用复杂的XPath-2.0表达式。然后,您可以将measurement模板简化为:

<xsl:template match="measurement">
    <measurement ucum="m" unit="m">
        <xsl:copy-of select="sum(for $x in measurement return if (normalize-space($x/@ucum)= '[ft_i]') then xs:double(normalize-space($x))*0.3048 else xs:double(normalize-space($x))*0.0254)" />
    </measurement>
</xsl:template>

它假定属性保持不变,并且只有两个单位。但是您可以轻松扩展它。


根据您的方法构建模板,以下解决方案也是可能的:

<xsl:template match="measurement">
    <xsl:copy>
        <xsl:variable name="summary">
            <xsl:for-each select="measurement">
                <val>
                    <xsl:call-template name="normalise">
                        <xsl:with-param name="val" as="xs:double" select="number(.)"/>
                        <xsl:with-param name="unitin" select="@ucum"/>
                        <xsl:with-param name="count" as="xs:integer" select="1"/>
                    </xsl:call-template>
                </val>
            </xsl:for-each>
        </xsl:variable>
        <xsl:copy-of select="$summary/val[1]/@*" />
        <xsl:copy-of select="sum($summary/val)" />
    </xsl:copy>
</xsl:template>

<xsl:template name="normalise">
    <xsl:param name="val" as="xs:double"/>
    <xsl:param name="unitin"/>
    <xsl:param name="count" as="xs:integer"/>
    <xsl:choose>
        <xsl:when test="normalize-space($unitin) = '[ft_i]'">
            <xsl:attribute name="ucum">
                <xsl:value-of select="'m'"/>
            </xsl:attribute>
            <xsl:attribute name="unit">
                <xsl:value-of select="'m'"/>
            </xsl:attribute>
            <xsl:value-of select="$val * 0.3048"/>
        </xsl:when>
        <xsl:when test="normalize-space($unitin) = '[in_i]'">
            <xsl:attribute name="ucum">
                <xsl:value-of select="'m'"/>
            </xsl:attribute>
            <xsl:attribute name="unit">
                <xsl:value-of select="'m'"/>
            </xsl:attribute>
            <xsl:value-of select="$val * 0.0254"/>
        </xsl:when>
    </xsl:choose>
</xsl:template>

它更灵活,并且对变量使用两阶段方法。结果是一样的。我想如果将两者结合起来,就会找到适合您需求的好方法。


0
投票

无需多次处理-XPath 2.0足以解决此类问题:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

  <xsl:template match="measurement[*]">
    <measurement ucum="m" unit="m">
      <xsl:value-of select=
                   "sum(measurement/(. * (if(@ucum eq '[ft_i]') then 0.3048 else 0.0254)))"/>
    </measurement>
  </xsl:template>
</xsl:stylesheet>

当对提供的XML文档应用此转换时:

<topic>
    <p>This piece is
        <measurement>
            <measurement unit="ft"
                ucum="[ft_i]">10</measurement>
            <measurement unit="in"
                ucum="[in_i]">2</measurement>
        </measurement>
        long
    </p>
</topic>

产生想要的正确结果:

<topic>
    <p>This piece is
        <measurement ucum="m" unit="m">3.0988</measurement>
        long
    </p>
</topic>
© www.soinside.com 2019 - 2024. All rights reserved.