【问题标题】:Sum up or increment a variable in XSLT using a function使用函数对 XSLT 中的变量求和或递增
【发布时间】:2017-04-22 14:08:21
【问题描述】:

我有一个使用 Saxon9.7 处理的 .GPX 文件和以下 .xsl 文件。我尝试通过计算两个轨迹点之间的距离(使用 @lon 和 @lat 值)来总结不同轨迹点之间的距离。

使用这个函数,我尝试增加(或累加)这些值。

  • gDistance 是当前求和的距离
  • 距离是应该添加到 gDistance 的数字

函数应该返回增加的距离

<xsl:function name="of:gesDistance">
    <xsl:param name="gDistance"/>
    <xsl:param name="distance"/>
    <xsl:value-of select="($gDistance + $distance)"/>
</xsl:function>

我的 .xsl 文件如下所示:

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.topografix.com/GPX/1/1"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:of ="http://lul.org">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:function name="of:gesDistance">
    <xsl:param name="gDistance"/>
    <xsl:param name="distance"/>
    <xsl:value-of select="($gDistance + $distance)"/>
</xsl:function>

<xsl:template match="trkseg">
    <xsl:variable name="gDistance" select="0"/>
    <xsl:for-each select="trkpt">
        <xsl:variable name="dLat" select="(@lat - preceding-sibling::*[1]/@lat)"/>
        <xsl:variable name="dLon" select="(@lon - preceding-sibling::*[1]/@lon)"/>
        <xsl:variable name="cLon" select="(@lon)"/>
        <xsl:variable name="cLat" select="(@lat)"/>
        <xsl:variable name="pLon" select="(preceding-sibling::*[1]/@lon)"/>
        <xsl:variable name="pLat" select="(preceding-sibling::*[1]/@lat)"/>
        <xsl:variable name="distance" select="6378.388 * math:acos(math:sin($cLat)*math:sin($pLat) + math:cos($cLat) * math:cos($pLat) * math:cos($dLon))"/>
        <xsl:variable name="gDistance" select="of:gesDistance($gDistance, $distance)"/>

        <xsl:text>newNode&#xA;dLat: </xsl:text>
        <xsl:value-of select="$dLat"/>
        <xsl:text>&#xA;dLon: </xsl:text>
        <xsl:value-of select="$dLon"/>
        <xsl:text>&#xA;Distance: </xsl:text>
        <xsl:value-of select="$distance"/>
        <xsl:text> km &#xA;Length: </xsl:text>
        <xsl:value-of select="$gDistance"/>

    <xsl:text>&#xA;</xsl:text>
</xsl:for-each>
</xsl:template>

在 for-each 部分中,我总是计算 dLondLat 到前一个跟踪点的距离。稍后计算以公里为单位的距离。

xsl:variable gDistance 在 for-each 语句之前设置为 0

        <xsl:variable name="gDistance" select="0"/>

以下行描述了使用旧 gDistance 值和与前一个兄弟的当前距离调用函数的部分。

<xsl:variable name="gDistance" select="of:gesDistance($gDistance, $distance)"/>

使用 Saxon9 运行它会得到以下输出:

dLat: -0.0001660000000001105
dLon: -0.0004770000000000607
Distance: 1.1425320012289337 km 
Length: 1.1425320012289337
newNode
dLat: -0.00023200000000400678
dLon: -0.0006450000000004508
Distance: 1.5892769562498525 km 
Length: 1.5892769562498525
newNode
dLat: -0.00023799999999596366
dLon: -0.0004939999999997724
Distance: 1.5814420943745287 km 
Length: 1.5814420943745287

如您所见,总和的距离始终与到前一个兄弟的距离相同。但为什么?有没有办法解决我的问题?

我使用不同的撒克逊命令尝试了不同的方法来解决我的问题,但也没有用。

是否有准备好的 xml-namespace 以及我需要的功能?

...给你我所有的一切——我的 GPX 文件的一部分

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" creator="" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
 <trk>
  <name>ACTIVE LOG133212</name>
  <trkseg>
   <trkpt lat="48.813909" lon="9.249088">
    <ele>-21.666</ele>
    <time>2008-05-25T13:32:07Z</time>
   </trkpt>
   <trkpt lat="48.814533" lon="9.248918">
    <ele>49.192</ele>
    <time>2008-05-25T13:32:14Z</time>
   </trkpt>
...

【问题讨论】:

  • 变量在 XSLT 中不是变量。你必须编写一个表达式来计算你想要的总数,你不能像在过程语言中那样零碎地累积值。 XSLT 是功能性的,而不是程序性的。
  • 如果您真的想在 Saxon 9.7 中使用这种命令式编程方法,那么您需要 PE 或 EE 并使用 XSLT 3.0 和 xsl:iterate。但正如 Jim 所说,在 XSLT 2.0 中,您可以编写表达式和/或递归函数/模板来添加/累积值。
  • @JimGarrison 所以我必须创建一个表达式来计算未知数量节点的距离并将它们相加。我对吗?没有其他方法可以使这样的事情发挥作用吗?
  • 一种“增加变量”(或者更准确地说是累积余额)的简单方法是采用一种称为兄弟递归的技术。参见,例如:stackoverflow.com/a/21809106/3016153
  • P.S.你确定你计算距离的公式是正确的吗?

标签: xslt increment saxon


【解决方案1】:

我认为如果我们简化示例会更容易。我使用的典型示例是“银行对账单”问题:给定一系列资金进出账户的交易,显示所有交易的银行对账单加上账户中的总金额。

最简单的解决方案是计算

$balance = $initial-balance + sum(preceding-sibling::transaction/value)

但计算每笔交易的成本是 O(n^2),因此除非交易数量非常少,否则您不想使用此解决方案。

在函数式编程中,有两种主要方法可以解决这个问题。一种是递归:您可以使用递归函数将余额序列计算为交易序列的函数:

<xsl:function name="f:balances" as="xs:decimal*">
   <xsl:param name="initial-balance" as="xs:decimal*"/>
   <xsl:param name="transactions" as="element(transaction)*"/>
   <xsl:variable name="first-balance" select="initial-balance + head(transactions)/value"/>
   <xsl:sequence select="$first-balance"/>
   <xsl:sequence select="f:balances($first-balance, tail($transactions)"/>
</xsl:function>

此函数在第一次交易后计算余额,然后调用自身计算剩余交易的余额 - 经典的头/尾递归。

另一种方法是使用折叠操作。折叠操作接受一个初始值和一个序列,并将提供的函数依次应用于序列中的每个项目,每次都有一个新的初始值。所以

fn:fold-left($transactions, $initial-balance, function($balance, $transaction) {$balance + $transaction/value})

XSLT 3.0 还引入了 xsl:iterate 作为折叠操作的语法糖:

<xsl:iterate select="$transactions">
  <xsl:param name="initial-balance"/>
  <xsl:variable name="balance" select="./value + $initial-balance"/>
  <xsl:sequence select="$balance"/>
  <xsl:next-iteration>
    <xsl:with-param name="initial-balance" select="$balance"/>
  </xsl:next-iteration>
</xsl:iterate>

如果你觉得这些概念有点吓人,那是因为命令式编程已经扭曲了你的大脑,以至于你认为语句像

i = i + 1

正常且可接受。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-07
    • 2013-05-05
    • 2015-03-14
    • 1970-01-01
    • 2019-04-08
    相关资源
    最近更新 更多