首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用XSLT计算同级元素的值之和

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

Stack Overflow用户
提问于 2019-10-12 16:00:01
回答 3查看 504关注 0票数 1

我正在使用XSLT进行测量的自动转换。单个测量系统的转换(e.q. )另一种(例如公制)的效果很好。但帝国测量的形式可能是'5英尺10英寸‘,我想把它转换成一个单一的度量值。

在我的XML模型中,通过允许单个值或多个子节点来适应这种组合度量。因此,当我发现我有子节点时,我需要将每个子元素转换为度量单位,然后将值相加得到一个度量结果。

我正在努力寻找处理多个子节点并将结果值相加的最佳方法。在迭代语言中,我只需要从第一个处理到下一个,并更新一个全局变量,但在XSLT中,我不知道是否有全局变量可以从后续调用更新到同一个模板。

这里是一个(简化的)转换--这个转换只处理ft_i和in_i到m。

代码语言:javascript
复制
<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>

一个简单的测试文件:

代码语言:javascript
复制
<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>

变换的结果如下:

代码语言:javascript
复制
<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>

很明显,我想看到的是:

代码语言:javascript
复制
<topic>
    <p>This piece is 
        <measurement ucum="m" 
            unit="m">3.0988</measurement>
        long
    </p>        
</topic>

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-10-12 16:32:10

假设某些值和属性是常数,最简单的方法是使用复杂的XPath2.0表达式。然后,可以将measurement模板缩减为:

代码语言:javascript
复制
<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>

它假设属性保持不变,并且只有两个单元。但你可以很容易地扩展它。

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

代码语言:javascript
复制
<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
EN

Stack Overflow用户

发布于 2019-10-13 17:53:40

不需要多通道处理-- XPath 2.0足以解决这种问题:

代码语言:javascript
复制
<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文档时:

代码语言:javascript
复制
<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>

想要的,正确的结果产生:

代码语言:javascript
复制
<topic>
    <p>This piece is
        <measurement ucum="m" unit="m">3.0988</measurement>
        long
    </p>
</topic>

II.更通用的解决方案

您可以生成更通用的解决方案,其中将转换隔离到单独的XSLT <xsl:function>中。请注意,这个解决方案即使在转换逻辑非常复杂以至于不能像上面的解决方案那样在单个XPath表达式中表示的情况下也能工作。如果转换不仅仅是一个简单的乘法,那么通常不可能将它表示为一个表并使用xsl:key

代码语言:javascript
复制
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:f="my:f" exclude-result-prefixes="f">
 <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/f:convert(@ucum, .))"/>
    </measurement>
  </xsl:template>

  <xsl:function name="f:convert">
    <xsl:param name="pFromUnit"/>
    <xsl:param name="pValue"/>

    <xsl:value-of select=
    "$pValue * (if($pFromUnit eq '[ft_i]') then 0.3048 else 0.0254)"/>
  </xsl:function>
</xsl:stylesheet>

当将此转换应用于同一个XML文档(上面)时,将再次生成相同的正确、所需的结果()。

代码语言:javascript
复制
<topic>
    <p>This piece is
        <measurement ucum="m" unit="m">3.0988</measurement>
        long
    </p>
</topic>
票数 0
EN

Stack Overflow用户

发布于 2019-10-13 18:42:58

假设您可以有两个以上的单元,那么将它们存储在一个变量中并使用一个钥匙检索相应的转换因子是很方便的。

XSLT2.0

代码语言:javascript
复制
<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="unit" match="unit" use="@name" />

<xsl:variable name="units">
    <unit name="ft" factor=".3048"/>
    <unit name="in" factor=".0254"/>
    <!-- add more units here ... -->
</xsl:variable>

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

<xsl:template match="measurement[measurement]">
    <measurement ucum="m" unit="m">
        <xsl:value-of select="sum(measurement/(. * key('unit', @unit, $units)/@factor))"/>    
    </measurement>
</xsl:template>

</xsl:stylesheet>

Demohttps://xsltfiddle.liberty-development.net/6rewNxT

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58355927

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档