Reputation: 1
<Expression>
<description/>
<minus>
<plus>
<int value="2"/>
<int value="3"/>
<times>
<int value="5"/>
<minus>
<int value="6"/>
<int value="3"/>
</minus>
</times>
</plus>
<int value="4"/>
</minus>
</Expression>
I want to get expression like this and output the result:(2+3+(6-3)*5)-4=16
Upvotes: 0
Views: 335
Reputation: 163342
This is a good use case for higher-order functions:
declare variable $evaluators := map {
'int' : function($e){xs:integer($e/@value)},
'plus' : function($e){sum($e/*/local:eval(.)},
'minus' : function($e){local:eval($e/*[1] - local:eval($e/*[2])},
'times' : function($e){local:eval($e/*[1] * local:eval($e/*[2])},
...
}
declare function local:eval($e as element(*)) {
$evaluators(name($e))($e)
}
local:eval(Expression/(* except description))
The details depend a bit on aspects of the specification that you haven't shared with us, for example whether description
can appear anywhere, whether times
(like plus
) can take more than two operands, etc.
Acknowledgements: I saw this problem solved in 1972 using Algol 68 by my late friend and colleague Andrew Birrell (https://cacm.acm.org/news/210689-in-memoriam-andrew-birrell-1951-2016/fulltext) and it was this problem and solution that convinced me of the value of treating functions as first-class objects.
Upvotes: 2
Reputation: 167571
Here is an XSLT 4 example as runnable by SaxonCS from Saxonica:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="4.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="text"/>
<xsl:mode on-no-match="shallow-skip"/>
<xsl:strip-space elements="*"/>
<xsl:param name="operator-map" as="map(xs:string, xs:string)"
select="map { 'minus' : ' - ',
'plus' : ' + ',
'times' : ' * ' }"/>
<xsl:template match="Expression">
<xsl:variable name="expression">
<xsl:apply-templates/>
</xsl:variable>
<xsl:variable name="result" as="xs:decimal">
<xsl:evaluate xpath="$expression"/>
</xsl:variable>
<xsl:value-of select="$expression, $result" separator=" = "/>
</xsl:template>
<xsl:template match="minus | plus | times">
<xsl:text>(</xsl:text>
<xsl:apply-templates separator="{$operator-map(local-name())}"/>
<xsl:text>)</xsl:text>
</xsl:template>
<xsl:template match="int[@value]">
<xsl:value-of select="@value"/>
</xsl:template>
</xsl:stylesheet>
Or as XSLT 3:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="text"/>
<xsl:mode on-no-match="shallow-skip"/>
<xsl:strip-space elements="*"/>
<xsl:param name="operator-map" as="map(xs:string, xs:string)"
select="map { 'minus' : ' - ',
'plus' : ' + ',
'times' : ' * ' }"/>
<xsl:template match="Expression">
<xsl:variable name="expression">
<xsl:apply-templates/>
</xsl:variable>
<xsl:variable name="result" as="xs:decimal">
<xsl:evaluate xpath="$expression"/>
</xsl:variable>
<xsl:value-of select="$expression, $result" separator=" = "/>
</xsl:template>
<xsl:template match="minus | plus | times">
<xsl:if test="position() gt 1"> {$operator-map(local-name(..))} </xsl:if>
<xsl:text>(</xsl:text>
<xsl:apply-templates/>
<xsl:text>)</xsl:text>
</xsl:template>
<xsl:template match="int[@value]">
<xsl:if test="position() gt 1"> {$operator-map(local-name(..))} </xsl:if>
<xsl:value-of select="@value"/>
</xsl:template>
</xsl:stylesheet>
Solely evaluation in XQuery (3) perhaps as
declare function local:evaluate($expression)
{
local:evaluate-operator($expression/(* except description))
};
declare function local:evaluate-operator($operator)
{
typeswitch ($operator)
case element(minus)
return fold-left(tail($operator/*), local:evaluate-operator(head($operator/*)), function($a, $op) { $a - local:evaluate-operator($op) })
case element(plus)
return sum($operator/*!local:evaluate-operator(.))
case element(times)
return fold-left($operator/*, 1, function($a, $op) { $a * local:evaluate-operator($op) })
case element(int)
return xs:integer($operator/@value)
default return ()
};
local:evaluate(Expression)
Upvotes: 1