4.2.5.3  Funktionen und Module

4.2.5.3.1  Funktionen

Um bestimmte Abschnitte des XQuery Programm wiederverwendbar zu machen, stehen Funktionsdeklarationen zur Verfügung. Eine einfache Funktion wäre z.B. diese hier:
declare function local:wrap-header($json) {
  xdmp:add-response-header("Pragma", "no-cache"),
  xdmp:add-response-header("Cache-Control", "no-cache"),
  xdmp:add-response-header("Expires", "0"),
  xdmp:set-response-content-type('text/json; charset=utf-8'),
  $json
};
Sie wickelt um einen JSON String eine passende Header Information.
Damit die Funktion eingebunden werden kann, muss ein passender Namespace deklariert werden:
declare namespace local = 'local:';
Nicht i do my change her nur bzgl. Wiederverwendbarkeit sind Funktionen praktisch, sondern auch um ganz elementare Konstrukte, wie while...do Schleifen, zu realisieren.
Dazu nutzt man, wie in der funktionalen Programmierung üblich, die Rekursion:
declare function local:ist-letzter-wert-in-kette($glied) {
  let $wert := local:komplizierte-berechnung($glied),
    $naechstes-glied := local:komplizierte-berechnung-der-position($glied),
  return
    if ($naechstes-glied and not($wert = 'foobar')) then
      local:durchlaufe-kette($naechstes-glied)
    else
      $wert = 'foobar'
};
In diesem kleinen Schnippsel sind schon einige Besonderheiten von XQuery zu sehen. Variablenzuweisungen geschehen mit einem Doppelpunkt, Vergleiche dagegen nur mit einem einfachen "=". Statements werden mit einem Komma getrennt.

4.2.5.3.2  Funktionsaufrufe im XPath

Wenn eine Funktion auf einer Kontenmenge operiert, dann kann der Funktionsaufruf auch an einen Pfadselektor gehangen werden, bspw. so:
<xsl:value-of select="sum($current/betrag[xs:decimal(.) gt 0]/xs:decimal(.))"/>
Hier werden die betrag -Knoten eines zuvor selektierten Teilbaums, der in der Variablen $current abgespeichert ist, aufsummiert - aber nur wenn der Wert größer als 0 ist.
Der Funktionsaufruf ist hier ein Type-Cast auf einen Dezimalwert, um eine gewisse Rechengenauigkeit zu gewährleisten.
Die Filterung auf positive Werte ist dabei noch gewöhnlich formuliert:
[xs:decimal(.) gt 0]
xs:decimal nimmt den aktuell ausgwählten Knoten und macht einen Dezimalwert daraus, um ihn mit 0 zu vergleichen.
Falls hier an den Typkonstruktor xs:decimal ein nicht-unterstütztes Format übergeben wird, bspw. ein String, dann wird ein fatalen Fehler geworfen und das Programm bricht ab.
Der Funktionsaufruf kann aber auch als Pfadselektion an einem XPath angebracht werden:
/xs:decimal(.)
Im Fehlerfall wird der fehlerhafte Wert nicht summiert und das Programm läuft weiter.
$current/betrag[xs:decimal(.) gt 0]
Auf herkömmlichen Weg würde man eine Schleife verwenden, die alle Werte auf deren Dezimalwert abbildet:
sum(for $x in $current/betrag[xs:decimal(.) gt 0]
return xs:decimal($x))
Das ist ein bisschen komplizerter, gewährleistet aber eine bessere Robustheit der Programmierung.
ACHTUNG!
Funktionsaufrufe als Pfadselektoren brechen bei einem Fehler in der Funktion - ohne explizite Ausnahmebehandlung - nicht ab.
Falls mehr Robustheit gefordert ist, sollte man über Ergbnisknotenmengen iterieren und Funktionsaufrufe auf herkömmlichem Weg absetzen.
Betrachten wir folgendes XQuery-Schnippsel:
collection("/abrechnung")[vorgangsnummer[.=(3, 8,9,10)]/xdmp:node-collections(.)
                         [starts-with(., '/buchung')]/xdmp:collection-delete(.)
Hier sind alle Abrechnungen in einer Collection /abrechnung gespeichert. Die Abrechungen mit den Vorgangsnummern 3,8,9 und 10 sollen herausgefischt werden. Diese Abrechungen können auch in verschiedenen Collections verwaltet werden, bspw. mittels eines Collection-Typs "Buchung". Eine Buchung-Collection sammelt alle Abrechungen, die an einem bestimmten Buchungstag getätigt wurden. Wir gehen jetzt davon aus, dass alle Abrechungen 3,8,9,10 an einem bestimmten Buchungstag getätigt wurden - und nur diese. Aus irgendeinem Grund wollen wir diese Buchung nun löschen. Das macht genau der obige Einzeiler. Der Filter:
collection("/abrechnung")[vorgangsnummer[.=(3, 8,9,10)]
gibt eine Knotenmenge zurück. Das gefilterte Funktionsergebnis
xdmp:node-collections(.)[starts-with(., '/buchung')]
ist auch eine Knotenmenge. Normalerweise bräuchten wir also Schleifen, um über diese Mengen zu iterieren. Das würde irgendwie so aussehen
let 
  $filtered-collection := collection("/abrechnung")[vorgangsnummer[.=(3, 8,9,10)],
  $collections-to-be-deleted :=
  distinct-values(
    for $x in $collection
       return (
         for $y in xdmp:document-get-collections(fn:document-uri($x))[starts-with(., '/buchung')]
           return 
             $y
       )
    )
return (
      for $culprit in $collections-to-be-deleted
        return xdmp:collection-delete($culprit)
    )
Der Quelltext ist zwar so wesentlich länger, aber auch weniger geübte XPath-Experten erkennen leicht, um was es geht.

4.2.5.3.3  Module

Um eine XQuery Anwendung zu modularisieren, können einzelne Skripte in Module ausgelagert werden. Ein Modul, z.B. common.xqy , wird dabei über einen eigenen Namespace deklariert:
module namespace common = "https://www.tekturcms.de/common";
Dieses Modul kann dann in anderen Skripten eingebunden werden:
import module namespace common = "https://www.tekturcms.de/common" at "common.xqy";
Funktionen und Variablen werden dann mir dem Namespace geprefixt aufgerufen:
Funktionsaufruf: common:wrap-response-header(...)
Variablenauswertung: $common:collection-books
Previous Page Next Page
Version: 93
Jan 25 2021