XPath 3.0, XPath 2.0, XPath 1.0 / XPath-Funktionen / Zahlenfunktionen / sum()

sum()

sum()

➪ Die sum-Funktion berechnet die Summe der Zahlenwerte in der Sequenz (vorausgesetzt, es sind nur Werte enthalten, die auch als Zahlenwerte gecastet werden können). Die sum()-Funktion steht bereits seit XSLT 1.0/XPath 1.0 zur Verfügung.

Nehmen Sie eine Sequenz von Items, die sich aus einem XML-Dokument auswerten ansprechen lässt. Zum Beispiel:


<Orte>
   <Ort name="Neustadt">
      <Mensch VN="Hugo" NN="Holzflos" Ausgaben="3563.1400000000003">
         <Gehalt>234.56</Gehalt>
      </Mensch>
      <Mensch VN="Nicole" NN="Nixlos" Ausgaben="961.6300000000001">
         <Gehalt>1234.56</Gehalt>
      </Mensch>
      <Mensch VN="Stefan" NN="Sprachlos" Ausgaben="4288.02">
         <Gehalt>5430</Gehalt>
      </Mensch>
      <Mensch VN="Stefan" NN="Sagblos" Ausgaben="2753.9399999999996">
         <Gehalt>321.45</Gehalt>
      </Mensch>
      <Mensch VN="Siggi" NN="Sorglos" Ausgaben="2610.96">
         <Gehalt>987.58</Gehalt>
      </Mensch>
      <Mensch VN="Heini" NN="Herzlos" Ausgaben="415.76">
         <Gehalt>654.21</Gehalt>
      </Mensch>
   </Ort>
   <Ort name="Darmstadt">
      <Mensch VN="Rudi" NN="Rhodos" Ausgaben="740.6999999999999">
         <Gehalt>333.33</Gehalt>
      </Mensch>
      <Mensch VN="Karl" NN="Kolos" Ausgaben="181.92">
         <Gehalt>456</Gehalt>
      </Mensch>
      <Mensch VN="Simone" NN="Sinnlos" Ausgaben="155.94">
         <Gehalt>876.54</Gehalt>
      </Mensch>
      <Mensch VN="Horst" NN="Hirnlos" Ausgaben="38.97">
         <Gehalt>546.77</Gehalt>
      </Mensch>
      <Mensch VN="Werner" NN="Wertlos" Ausgaben="103.96">
         <Gehalt>777.77</Gehalt>
      </Mensch>
      <Mensch VN="Ludwig" NN="Lustlos" Ausgaben="0">
         <Gehalt>357</Gehalt>
      </Mensch>
   </Ort>
   <Ort name="Kapstadt">
      <Mensch VN="Willi" NN="Wasistlos" Ausgaben="831.44">
         <Gehalt>6789</Gehalt>
      </Mensch>
      <Mensch VN="Rita" NN="Ruhelos" Ausgaben="203.19">
         <Gehalt>234</Gehalt>
      </Mensch>
      <Mensch VN="Susi" NN="Schlaflos" Ausgaben="808.13">
         <Gehalt>321</Gehalt>
      </Mensch>
      <Mensch VN="Lotte" NN="Rielos" Ausgaben="1555.98">
         <Gehalt>456</Gehalt>
      </Mensch>
      <Mensch VN="Betty" NN="Bodenlos" Ausgaben="324.81999999999994">
         <Gehalt>3450</Gehalt>
      </Mensch>
      <Mensch VN="Martin" NN="Muehelos" Ausgaben="142.89000000000001">
         <Gehalt>222</Gehalt>
      </Mensch>
      <Mensch VN="Liane" NN="Leinenlos" Ausgaben="964.0699999999999">
         <Gehalt>135</Gehalt>
      </Mensch>
   </Ort>
</Orte>

Dann ergibt ...


<xsl:template match="/">
    <ergebnis>
        <xsl:value-of select="sum(//Gehalt)"/>
    </ergebnis>
</xsl:template>

... dieses Resultat:


<ergebnis>23816.77</ergebnis>

Häufig ist aber auch eine bedingte Summierung erforderlich: Es sollen nur jene Zahlen summiert werden, die einer bestimmten Bedingung entsprechen. Beispiel: Sie suchen nach der Summe aller Gehälter bei jenen Menschen, die in Neustadt wohnen.


<ergebnis>
   <xsl:value-of 
        select="sum(/Orte/Ort[@name='Neustadt']/Mensch/Gehalt)"/>
</ergebnis>

Dann lautet das Ergebnis:


<ergebnis>8862.36</ergebnis>

Noch ein Beispiel: Sie möchten nur jene Gehalter addieren, die kleiner als die Ausgaben sind.


<ergebnis>
   <xsl:value-of 
        select="sum(//Mensch[Gehalt &lt; @Ausgaben]/Gehalt)"/>
</ergebnis>

In diesem Fall lautet das Resultat:


<ergebnis>9047.45</ergebnis>

sum() ab XSLT 2.0

Ab XSLT 2.0 haben Sie die Möglichkeit, einen optionalen Null-Wert zu definieren, falls die Sequenz der angesprochenen Items leer ist. Da das Element einkommen im XML-Dokument nicht existiert, ist die Sequenz leer: Zurückgegeben wird der Nullwert. Aus


<ergebnis>
   <xsl:value-of 
        select="sum((//Mensch/einkommen), 
                    'leider nichts gefunden')"/>
</ergebnis>

erhalten Sie also das Resultat:


<ergebnis>leider nichts gefunden</ergebnis>

Interessant ist auch der Ansatz, mit der sum-Funktion eine durchzuführen, die in diesem Fall das Resultat P1Y9M ergibt.


<xsl:value-of select="sum( 
    (xs:yearMonthDuration('P7M'), xs:yearMonthDuration('P1Y2M'))
    )"/>

Ab XSLT 2.0 können Sie der sum-Funktion auch eine selbst definierte Sequenz mitgeben.


<ergebnis>
    <xsl:value-of 
         select="sum(
                (7*25.99, 199, 1, (11 to 50)[. mod 9=1])
                )"/>
</ergebnis>

Diese selbst definierte Sequenz besteht dann aus den intern berechneten Werten (181.93, 199, 1, 19, 28, 37, 46), das Ergebnis lautet dann:


<ergebnis>511.93</ergebnis>

Interessant finde ich auch diese Alternative, eine Zahlensequenz automatisch zu berechnen (der Ausdruck (1 to 3)!(.*.) gibt die Zahlenreihe 1, 4, 9) und diese dann zu summieren. (Der !-Operator steht aber erst ab XPath 3.0 zur Verfügung.)


<xsl:value-of 
     select="sum((1 to 3)!(.*.))"/>

Noch eine Alternative (ab XPath 3.0): die Zahlen von 1 bis 10 werden geprüft, ob sie mit Rest durch 2 teilbar sind (ungerade Zahlen). Diese werden einfach berechnet, die anderen Zahlen quadriert.


<xsl:variable 
     name="v" 
     select="(1 to 10)!( if (. mod 2=1) 
                         then (.) 
                         else (.*.))"/>
<xsl:for-each select="$v">
    <wert>
        <xsl:value-of select="."/>
    </wert>
</xsl:for-each>   
<xsl:value-of select="sum($v)"/>

Damit kommen Sie zu den Teilergebnissen (die Summe als Gesamtwert beträgt 245).


   <wert>1</wert>
   <wert>4</wert>
   <wert>3</wert>
   <wert>16</wert>
   <wert>5</wert>
   <wert>36</wert>
   <wert>7</wert>
   <wert>64</wert>
   <wert>9</wert>
   <wert>100</wert

Wichtig ist, dass alle Items der Sequenz als Zahlen gecastet werden können. Das ist bei der folgenden Sequenz (1,2,3,4,5,'Hallo') nicht der Fall:


<ergebnis>
    <xsl:value-of select="sum((1 to 5, 'Hallo'))"/>
</ergebnis>

In diesem Fall ist mit einer Fehlermeldung zu rechnen, z.B.


Input to sum() contains a mix of numeric and non-numeric values;
http://www.w3.org/TR/2005/WD-xpath-functions-20050211/#ERRFORG0006.

Prozessorabhängige Verarbeitung

Beachtenswert ist die prozessorabhängige Verarbeitung der Anweisung.


<xsl:stylesheet 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     version="1.0">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/">
        <ergebnis>
            <xsl:value-of select="sum((7 * 25.99))"/>
        </ergebnis>
    </xsl:template>
</xsl:stylesheet>

Je nach verwendetem erhalten Sie unter xsl:stylesheet version="1.0" eine Fehlermeldung: "The value is not a node-set." oder ein Ergebnis, das auf automatischen Double-Type-Cast des Wertes schließen lässt.


<ergebnis>181.92999999999998</ergebnis>

Ändern Sie die XSL-Stylesheet-Version auf 2.0 bzw. 3.0, so generieren einige Prozessoren ein Ergebnis, das einen automatischen Decimal-Type-Cast des Wertes vermuten lässt.


<ergebnis>181.93</ergebnis>

Wenn Sie die Typkontrolle nicht dem System überlassen möchten, sind Sie gut beraten, den Type-Cast auf xs:decimal zu stellen.


<xsl:template match="/">
  <ergebnis>
    <xsl:value-of 
       select="sum( 
        for $m in //Kauf return 
         ($m/xs:decimal(preis) * $m/xs:decimal(anzahl))
        )"/>
  </ergebnis>
</xsl:template>

Aufschlussreich ist hier eine Sequenz von Testwerten. Pro test-Element sollen dessen Attribute @p1 und @p2 miteinander multipliziert werden; der jeweils erwartete Wert ist mit dem entsprechenden Attribut vorgegeben. Zusätzlich finden Sie in @real jene Werte, die sich bei automatischem Double-Type-Casting tatsächlich ergeben.


<xsl:variable name="vtestsequenz">
  <test p1="12.99" p2="11" 
        erwartet="142.89" 
        real="142.89000000000001"/>
  <test p1="12.99" p2="22" 
        erwartet="285.78" 
        real="285.78000000000003"/>
  <test p1="12.99" p2="44" 
        erwartet="571.56" 
        real="571.5600000000001"/>
  <test p1="151.23" p2="6" 
        erwartet="907.38" 
        real="907.3799999999999"/>
  <test p1="151.23" p2="3" 
        erwartet="453.69" 
        real="453.68999999999994"/>
  <test p1="25.99" p2="7" 
        erwartet="181.93" 
        real="181.92999999999998"/>
  <test p1="2.1" p2="3" 
        erwartet="6.3" 
        real="6.300000000000001"/>
  <test p1="2.15" p2="3" 
        erwartet="6.45" 
        real="6.449999999999999"/>
  <test p1="2.18" p2="3" 
        erwartet="6.54" 
        real="6.540000000000001"/>
  <test p1="2.2" p2="3" 
        erwartet="6.6" 
        real="6.6000000000000005"/>
  <test p1="2.23" p2="3" 
        erwartet="6.69" 
        real="6.6899999999999995"/>
</xsl:variable>

Das folgende XSL-Stylesheet verfolgt das Ziel, das prozessorabhängige, im Einzelfall möglicherweise unerwartete, Ergebnis sichtbar zu machen.


<xsl:template match="/">
  <ergebnis>
    <xsl:for-each select="$vtestsequenz/test">
      <xsl:if test="@p1 * @p2 != @erwartet">
        <Fehler>
          <xsl:value-of select="@p1 * @p2"/>
        </Fehler>
      </xsl:if>
    </xsl:for-each>
    <summeprod>
      <xsl:value-of 
           select="sum($vtestsequenz/test/(@p1 * @p2))"/>
    </summeprod>
    <summeerwartet>
      <xsl:value-of 
           select="sum($vtestsequenz/test/@erwartet)"/>
    </summeerwartet>
    <summeerwartet_decimal>
      <xsl:value-of 
           select="sum($vtestsequenz/test/xs:decimal(@erwartet))"/>
    </summeerwartet_decimal>
    <summereal>
      <xsl:value-of 
           select="sum($vtestsequenz/test/@real)"/>
    </summereal>
  </ergebnis>
</xsl:template>

<ergebnis>
  <Fehler>142.89000000000001</Fehler>
  <Fehler>285.78000000000003</Fehler>
  <Fehler>571.5600000000001</Fehler>
  <Fehler>907.3799999999999</Fehler>
  <Fehler>453.68999999999994</Fehler>
  <Fehler>181.92999999999998</Fehler>
  <Fehler>6.300000000000001</Fehler>
  <Fehler>6.449999999999999</Fehler>
  <Fehler>6.540000000000001</Fehler>
  <Fehler>6.6000000000000005</Fehler>
  <Fehler>6.6899999999999995</Fehler>
  <summeprod>2575.81</summeprod>
  <summeerwartet>2575.8099999999995</summeerwartet>
  <summeerwartet_decimal>2575.81</summeerwartet_decimal>
  <summereal>2575.81</summereal>
</ergebnis>

wg / 31. Mai 2018



Fragen? Anmerkungen? Tips?

Bitte nehmen Sie Kontakt zu mir auf.






Vielen Dank für Ihr Interesse an meiner Arbeit.


V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/sum.html