XPath 3.0, XPath 2.0, XPath 1.0 / XPath-Funktionen / XPath 3.1: Map, xsl:map

XPath 3.1: Map, xsl:map

XPath 3.1: Map, xsl:map

➪ Wie Arrays sind auch Maps neue Datentypen in XDM 3.1. Das Datenformat JSON wird in beiden exzessiv genutzt. Spezialfunktionen wie map:size, map:keys, map:find, map:merge, map:get, map:contains, map:put, map:remove, map:entry, map:for-each sowie der Einsatz anonymer Programmierung heben die Möglichkeiten flexibler Implementierungen in XSLT bzw. XQuery auf eine neue Ebene.

Auf dieser Seite:

Eine Map enthält Schlüssel-Wert-Paare, wobei der Schlüssel ein atomic value ist, und der Wert ist eine Sequenz mit den zugeordneten Werten. In einer Map darf derselbe Schlüssel nicht doppelt auftauchen. Das wäre der Fall, wenn die Spezialfunktion op:same-key($key1, $key2) den Wert true ergibt. Weitere Infos zu diesem Thema finden Sie bei den Anmerkungen über map:merge.

Der einzubindende Namespace ist:


xmlns:map="http://www.w3.org/2005/xpath-functions/map"

Nehmen Sie als Beispiel eine XSL-Variable vmap", deren Inhalt eine Map darstellt.


<xsl:variable name="vmap" as="map(xs:integer, xs:string)">
    <xsl:map>
      <xsl:map-entry key="0" select="'Hugo Holzflos'"/>
      <xsl:map-entry key="1" select="'Lotte Rielos'"/>
      <xsl:map-entry key="2" select="'Resi Denzschlos'"/>
    </xsl:map>
</xsl:variable> 

Denselben Effekt erreichen Sie mit


<xsl:variable 
     name="vmap" 
     select="map{
                 0:'Hugo Holzflos', 
                 1:'Lotte Rielos', 
                 2:'Resi Denzschlos'}"/>

Um die Werte direkt aus dem XML-Source-Dokument entnehmen zu können, genügt eine simple xsl:for-each-Schleife:


<xsl:variable name="vmap" as="map(xs:integer, xs:string)">
    <xsl:map>
      <xsl:for-each select="//Mensch">
        <xsl:map-entry
             key="xs:integer(id)" 
             select="concat(vorname, ' ', name)"/>
      </xsl:for-each>      
    </xsl:map>
</xsl:variable> 

Diese XSL-Variable vmap" kann durch diverse map-Funktionen angesprochen (ausgewertet, erweitert, verkürzt, verändert) werden.

XPath: map:size

map:size gibt die Anzahl der Map-Entries zurück.


<xsl:template match="/">
  <root>   
   <size nr="1">
    <xsl:value-of select="map:size($vmap)"/>
   </size>
  </root>
</xsl:template>

Das Ergebnis dieses Aufrufs ist unmittelbar nachvollziehbar:


<root>
 <size nr="1">3</size>
</root>

XPath: map:keys

map:keys gibt eine Sequenz aller Schlüsselwerte zurück.


<xsl:template match="/">
  <root>
   <keys nr="1">
    <xsl:for-each select="map:keys($vmap)">
     <key>
      <xsl:value-of select="."/>
     </key>
    </xsl:for-each>
   </keys>
  </root>
</xsl:template>

Die Einzelwerte der generierten Sequenz lesen Sie hier:


<root>
 <keys nr="1">
  <key>0</key>
  <key>1</key>
  <key>2</key>
 </keys>
</root>

XPath: map:contains

map:contains prüft, ob eine Map einen bestimmten Schlüssel aufweist.


<xsl:template match="/">
  <root>   
   <contains nr="1">
    <xsl:value-of select="map:contains($vmap, 2)"/>
   </contains>
   <contains nr="2">
    <xsl:value-of select="map:contains($vmap, 'Lotte Rielos')"/>
   </contains>
   <contains nr="3">
    <xsl:value-of select="map:contains(map{'Theo':23}, 'Theo')"/>
   </contains>
  </root>
</xsl:template>

Auch dieses Ergebnis ist nachvollziehbar. Im Fall Nr. 1 ist der Schlüssel "2" enthalten, also true. Die Information (Nr. 2) 'Lotte Rielos' ist zwar als Wert vorhanden, aber nicht als Schlüssel, daher false. Im dritten Fall wird nicht die eingangs erwähnte XSL-Variable verwendet, sondern eine neue Map mit dem Schlüssel 'Theo' generiert und umgehend geprüft, ob 'Theo' auch vorhanden ist: true.


<root>
 <contains nr="1">true</contains>
 <contains nr="2">false</contains>
 <contains nr="3">true</contains>
</root>

XPath: map:find

map:find sucht in einer Sequenz bzw. einem Map-Array nach Map-Entries, die den gesuchten Schlüssel beinhalten, und gibt die jeweiligen Werte zurück.

Die XSL-Variable vmaparray generiert ein Array aus mehreren Maps, die jeweils dieselben Schlüssel (0, 1) beinhalten. Je nach Map haben diese Schlüssel jedoch unterschiedliche Werte. Im Fall "e" gibt es davon eine verkettete Sequenz.


<find>
  <xsl:variable name="vmaparray" 
       select="[
                map{0:'a', 1:'b'}, 
                map{0:'c', 1:'d'}, 
                map{0:'e', 1:('f', 'g')}
               ]"/>				
	<xsl:value-of select="map:find($vmaparray, 1)"/>					
</find>

map:find sucht in diesem "vmaparray" nach dem Schlüssel "1" und gibt dessen Einzelwerte zurück:


<find>b d f g</find>

Das vorige Beispiel können Sie noch etwas komplexer gestalten durch die Einführung einer XML-basierten, union- (oder except- oder intersect-) basierten Sequenz, die zu einem Teil jener Map wird, deren Schlüssel "0" den Inhalt "e" hat.


<find>
 <xsl:variable name="vseq" 
      select="(//Ort[1], //Ort[2]) union (//Ort[2], //Ort[3])"/>
 <xsl:variable name="vmaparray" 
      select="[
               map{0:'a', 1:'b'}, 
               map{0:'c', 1:'d'}, 
               map{0:'e', 1:( $vseq/name )}]"/>    
 <xsl:value-of select="map:find($vmaparray, 1)"/>  
</find>

map:find sucht in diesem "vmaparray" nach dem Schlüssel "1" und gibt auch hier dessen Einzelwerte zurück:


<find>b d Neustadt Darmstadt Kapstadt</find>

XPath: map:put

map:put generiert eine Map, die alle Inhalte der ursprünglichen Map enthält, aber mit einem zusätzlichen Eintrag, der die Inhalte für eventuelle identische Schlüssel ersetzt.


 <xsl:template match="/">
  <root>
   <put>
    <xsl:variable name="vmap3" 
         select="map:put($vmap, 1, 'Zenzi Zwecklos')"/>
    <xsl:for-each select="map:keys($vmap3)">
     <key nr="{.}">
      <xsl:value-of select="map:get($vmap3, .)"/>
     </key>
    </xsl:for-each>
   </put>
  </root>
 </xsl:template>

Das Ergebnis können Sie hier bestaunen:


<root>
 <put>
  <key nr="0">Hugo Holzflos</key>
  <key nr="1">Zenzi Zwecklos</key>
  <key nr="2">Resi Denzschlos</key>
 </put>
</root>

XPath: map:remove

map:remove generiert eine Map, in der alle Inhalte der ursprünglichen Map enthalten sind, jedoch ohne jenen Schlüssel, der mit map:remove entfernt wurde.


 <xsl:template match="/">
  <root>
   <remove>
    <xsl:variable name="vmap4" 
         select="map:remove($vmap, 1)"/>
    <xsl:for-each select="map:keys($vmap4)">
     <key nr="{.}">
      <xsl:value-of select="map:get($vmap4, .)"/>
     </key>
    </xsl:for-each>
   </remove>
  </root>
 </xsl:template>

Auch dieses Ergebnis ist nachvollziehbar:


<root>
 <remove>
  <key nr="0">Hugo Holzflos</key>
  <key nr="2">Resi Denzschlos</key>
 </remove>
</root>

XPath: map:get

map:get gibt den Wert zurück, der einem Schlüssel zugeordnet ist. Ist der Schlüssel nicht vorhanden, gibt map:get eine leere Sequenz zurück.


<xsl:template match="/">
  <root>
   <get nr="1">
    <xsl:value-of select="map:get($vmap, 2)"/>
   </get>
   <get nr="2">
    <xsl:value-of select="map:get($vmap, 5)"/>
   </get>
   <get nr="3">
    <xsl:for-each select="map:keys($vmap)">
     <wert key="{.}">
      <xsl:value-of select="map:get($vmap, .)"/>
     </wert>
    </xsl:for-each>
   </get>
  </root>
</xsl:template>

Der Wert, der vmap mit dem Schlüssel "2" zugeordnet ist, ist 'Resi Denzschlos'. Einen Schlüssel "5" gibt es in vmap nicht, daher finden Sie hier eine leere Sequenz. Und die Schleife, die über alle keys von vmap läuft, gibt via map:get jeden Einzelwert zurück.


<root>
 <get nr="1">Resi Denzschlos</get>
 <get nr="2"/>
 <get nr="3">
  <wert key="0">Hugo Holzflos</key>
  <wert key="1">Lotte Rielos</key>
  <wert key="2">Resi Denzschlos</key>
 </get>
</root>

Einen vergleichbaren Ansatz finden Sie in XQuery:


declare namespace map = "http://www.w3.org/2005/xpath-functions/map";
<erg>
  {
    let $vvar := map{
         0:'Hugo Holzflos', 
         1:'Lotte Rielos', 
         2:'Resi Denzschlos'}
    for $x in map:keys($vvar) return
      <WERT key="{$x}">  
        {map:get($vvar, $x)}
      </WERT>
  }
</erg>

Hier sieht das Ergebnis so aus:


<erg>
  <WERT key="0">Hugo Holzflos</WERT>
  <WERT key="1">Lotte Rielos</WERT>
  <WERT key="2">Resi Denzschlos</WERT>
</erg

XPath: map:entry

map:entry generiert eine Map mit einem einzelnen Schlüssel-Wert-Paar. map:entry('A', 'Achtungfertiglos') erzeugt also map{'A':'Achtungfertiglos'}.


<entry>
 <xsl:value-of select="
      map:get(
             map:entry('A', 'Achtungfertiglos')
             , 'A')"/>
</entry>

Die Auswertung dieser map:entry mit map:get liefert verständlicherweise:


<entry>Achtungfertiglos</entry>

XPath: map:merge

map:merge kombiniert die Inhalte mehrerer Maps in eine neue Map, die wiederum ausgewertet werden kann.


<xsl:template match="/">
  <root>
   <size nr="2">
    <xsl:value-of 
         select="map:size(
                     map:merge(
                          ($vmap, map{3:'Zenzi Zwecklos'})
                     ))"/>
   </size>
   <keys nr="2">
    <xsl:for-each 
         select="map:keys(
                     map:merge(
                         ($vmap, map{3:'Zenzi Zwecklos'})
                     ))">
     <key>
      <xsl:value-of select="."/>
     </key>
    </xsl:for-each>
   </keys>
  </root>
</xsl:template>

Die merged map ist durch map:size bzw. map:keys auswertbar:


<root>
 <size nr="2">4</size>
 <keys nr="2">
  <key>0</key>
  <key>1</key>
  <key>2</key>
  <key>3</key>
 </keys>
</root>

Hier möchte ich auf den Konfliktfall hinweisen, dass beim Mergen zweier Maps ein Schlüssel doppelt auftauchen könnte. Dieser Fall wird in der XSL-Variablen vmap2 abgebildet.


<xsl:template match="/">
  <root>
   <merge nr="2">
    <xsl:variable name="vmap2" 
         select="map:merge(($vmap, map{1:'Zenzi Zwecklos'}))"/>
    <xsl:for-each select="map:keys($vmap2)">
     <key nr="{.}">
      <xsl:value-of select="map:get($vmap2, .)"/>
     </key>
    </xsl:for-each>   
   </merge>
  </root>
</xsl:template>

Der verwendete XSL-Prozessor hat den ursprünglichen Wert beibehalten:


<root>
 <merge nr="2">
  <key nr="0">Hugo Holzflos</key>
  <key nr="1">Lotte Rielos</key>
  <key nr="2">Resi Denzschlos</key>
 </merge>
</root>

Bei der Verarbeitung dieser Fälle muss betrachtet werden, inwieweit die Schlüssel wirklich gleich, d.h. auch von demselben Datentyp sind. Das ist beispielsweise nicht der Fall, wenn der Schlüssel "1" als xs:string gecastet wird.


<xsl:variable 
     name="vmap2" 
     select="map:merge(($vmap, 
                 map{xs:string(1):'Zenzi Zwecklos'}))"/>

Unter dem Stichwort op:same-key können Sie im Web einige Bedingungen finden, die bei der Prüfung der Map-Schlüssel zu beachten sind, u.a., dass beide Schlüssel den Anforderungen von fn:deep-equal entsprechen. Darüber hinaus können Sie spezielle Duplikat-Handler in Form von (auch anonymen) Funktionen implementieren, in denen das nähere Verfahren hinsichtlich use-first, use-last, combine, reject oder use-any festgelegt wird.

Im Übrigen ist die automatische Generierung komplexer Maps mit map:merge, map:entry, fn:for-each und function sehr effizient:


<fn_for-each>
  <xsl:variable name="vmap5" 
        select="map:merge(($vmap, 
              fn:for-each(
                 (10 to 20)[. mod 2 = 1], 
                 function($p1){map:entry($p1, $p1*100)}
              )
        ))"/>
        <xsl:for-each select="map:keys($vmap5)">
    <key nr="{.}">
      <xsl:value-of select="map:get($vmap5, .)"/>
    </key>
  </xsl:for-each>
</fn_for-each>

Das Ergebnis lautet:


<root>
  <fn_for-each>
    <key nr="0">Hugo Holzflos</key>
    <key nr="1">Lotte Rielos</key>
    <key nr="2">Resi Denzschlos</key>
    <key nr="11">1100</key>
    <key nr="13">1300</key>
    <key nr="15">1500</key>
    <key nr="17">1700</key>
    <key nr="19">1900</key>
  </fn_for-each>
</root>

Der Vollständigkeit halber finden Sie hier noch eine XQuery-Variante, in der die ursprüngliche map "$vvar" um mehrere Maps ergänzt wurde. Damit das problemlos klappt, muss jede Map ihren eigenen Schlüssel haben.


declare namespace map = "http://www.w3.org/2005/xpath-functions/map";
<erg>
  {
    let $vvar := map{
         0:'Hugo Holzflos', 
         1:'Lotte Rielos', 
         2:'Resi Denzschlos'}
    let $vmerged := map:merge(($vvar, 
        map{3:'Zenzi Zwecklos'}, 
        map{4:'Wilma Wunschlos'}, 
        map{5:'Theo Traumlos'}))
    for $x in map:keys($vmerged)
    return
      <WERT
        key="{$x}">  
        {map:get($vmerged, $x)}
      </WERT>
  }
</erg>

Das Ergebnis dieses Aufrufs sehen Sie hier:


<erg>
  <WERT key="0">Hugo Holzflos</WERT>
  <WERT key="1">Lotte Rielos</WERT>
  <WERT key="2">Resi Denzschlos</WERT>
  <WERT key="3">Zenzi Zwecklos</WERT>
  <WERT key="4">Wilma Wunschlos</WERT>
  <WERT key="5">Theo Traumlos</WERT>
</erg

XPath: map:for-each

Neben steht noch map:for-each zur Verfügung. map:for-each benötigt eine zusätzliche Funktion, die die Ergebnisse verkettet.

Nehmen Sie zur Abwechslung mal eine andere map-Variable:


<xsl:variable 
    name="vmapTTWWGG" 
    select="map{
    'TT':'Theo Traumlos', 
    'WW':'Willi Wunschlos', 
    'GG':'Gustav Grundlos'}"/>

"vmapTTWWGG" lässt sich so auswerten:


<xsl:template match="/">
 <root>
  <fn_for-each nr="map:1">
    <xsl:for-each 
       select="map:for-each($vmapTTWWGG, 
               function ($k, $v){$k})">
      <wert key="{.}">
        <xsl:value-of select="map:get($vmapTTWWGG, .)"/>
      </wert>
    </xsl:for-each>
  </root>
</xsl:template>

... und das Ergebnis ist:


<root>
  <fn_for-each nr="map:1">
   <wert key="GG">Gustav Grundlos</wert>
   <wert key="TT">Theo Traumlos</wert>
   <wert key="WW">Willi Wunschlos</wert>
  </fn_for-each>
</root>

Mit den enormen Möglichkeiten anonymer Funktionsaufrufe steht die Implementierung beliebiger Logiken frei. Viel Spaß beim Experimentieren!


<xsl:template match="/">
  <root>
  <fn_for-each nr="map:2">
    <xsl:for-each 
         select="map:for-each($vmap, 
                 function ($k, $v){ 
                   if ($k mod 2 = 1) then ($v) 
                   else ('kein Ergebnis')
                 })">
      <wert>
        <xsl:value-of select="."/>
      </wert>
    </xsl:for-each>
  </fn_for-each>
  </root>
</xsl:template>

Ich habe dieses Ergebnis erwartet, und Sie?


<root>
  <fn_for-each nr="map:2">
    <wert>kein Ergebnis</wert>
    <wert>Lotte Rielos</wert>
    <wert>kein Ergebnis</wert>
  </fn_for-each>
</root>

In XQuery funktioniert das Ganze selbstverständlich auch. Wie vorher, habe ich eine map-Variable "$vvar" mit Inhalten gefüllt und via map:merge weitere hinzugefügt; die neue Variable heißt "$vmerged".

Mittels map:for-each wird aus "$vmerged" jeder zweite Key herausgefiltert:


declare namespace map = "http://www.w3.org/2005/xpath-functions/map";
<erg>
  {
    let $vvar := map{
         0:'Hugo Holzflos', 
         1:'Lotte Rielos', 
         2:'Resi Denzschlos'}
    let $vmerged := map:merge(($vvar, 
        map{3:'Zenzi Zwecklos'}, 
        map{4:'Wilma Wunschlos'}, 
        map{5:'Theo Traumlos'}))
    for $x in map:for-each($vmerged, 
       function ($k, $v){ 
        if ($k mod 2 = 1) then ($k) 
        else ()
       })
       return
    <WERT
      key="{$x}">  
      {map:get($vmerged, $x)}
    </WERT>
  }
</erg>

Übrig bleiben die Maps mit den Keys 1, 3, 5 und deren zugehörige Werte:


<erg>
  <WERT key="1">Lotte Rielos</WERT>
  <WERT key="3">Zenzi Zwecklos</WERT>
  <WERT key="5">Theo Traumlos</WERT>
</erg

random-number-generator

Ein Spezialthema ist der random-number-generator. Dieser generiert eine Map, deren Schlüssel ein xs:string ist: Die zulässigen Werte dieses Schlüssels sind "number", "next" und "permute".


<xsl:variable name="vngen" 
     as="map(xs:string, item())" 
     select="random-number-generator()"/>

Rufen Sie die Map-Variable vngen über den Schlüssel "number" auf,


<number>
  <xsl:value-of select="map:get($vngen, 'number')"/>
</number>

so generiert das System eine Zufallszahl, die zwischen 0 und 1 liegt.


<number>0.020134109105953035</number>

Rufen Sie die map-Variable vngen über den Schlüssel permute auf und übergeben eine Zahlensequenz, z.B. 1 to 5,


<permute>
  <xsl:for-each select="map:get($vngen, 'permute')(1 to 5)">
    <nr>
      <xsl:value-of select="."/>
    </nr>
  </xsl:for-each>
</permute>

so generiert das System eine zufällige Folge der vorgegebenen Zahlen.


  <permute>
   <nr>3</nr>
   <nr>5</nr>
   <nr>1</nr>
   <nr>4</nr>
   <nr>2</nr>
  </permute>

wg / 13. April 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/map.html