Home
Über mich
Blog
Veröffentlichungen
IT-Trainings
Impressum


XPath 3.1: Map

Zusammenfassung:

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 auf eine neue Ebene.

XPath 3.1: Map

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)" true ergibt. Weitere Infos zu diesem Thema finden wir bei den Anmerkungen über map:merge.

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

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

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="/" name="xsl:initial-template">
  <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 geben eine Sequenz aller Schlüsselwerte zurück.

<xsl:template match="/" name="xsl:initial-template">
  <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 wir 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="/" name="xsl:initial-template">
  <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. Map array nach Map-Entries, die den gesuchten Schlüssel beinhalten, und geben 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 deren Einzelwerte zurück:

<find>b d f g</find>

Das vorige Beispiel können wir noch etwas komplexer gestalten durch die Einführung einer XML-basierten, union- (oder except- oder intersect-) basierten Sequenz, die zum 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 deren 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="/" name="xsl:initial-template">
  <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 wir 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 "remove" entfernt wurde.

 <xsl:template match="/" name="xsl:initial-template">
  <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="/" name="xsl:initial-template">
  <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 wir 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>

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="/" name="xsl:initial-template">
  <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 sollten wir auf den Konfliktfall hinweisen, daß beim Mergen zweier Maps ein Schlüssel doppelt auftauchen könnte. Dieser Fall wird in der XSL-Variable "vmap2" abgebildet.

<xsl:template match="/" name="xsl:initial-template">
  <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 muß 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" sind im Web einige Bedingungen zu finden, die bei der Prüfung der Map-Schlüssel zu beachten sind, u.a., daß beide Schlüssel den Anforderungen von fn:deep-equal entsprechen. Darüberhinaus können wir 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 Hilfe von 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>

XPath: map:for-each

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

<xsl:template match="/" name="xsl:initial-template">
 <root>
  <fn_for-each nr="map:1">
    <xsl:for-each select="map:for-each($vmap, function ($k, $v){$v})">
      <wert>
        <xsl:value-of select="."/>
      </wert>
    </xsl:for-each>
  </fn_for-each>
  </root>
</xsl:template>
<root>
  <fn_for-each nr="map:1">
    <wert>Hugo Holzflos</wert>
    <wert>Lotte Rielos</wert>
    <wert>Resi Denzschlos</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="/" name="xsl:initial-template">
  <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>

qrpic/map.jpg

wg / 10. Oktober 2017




Fragen? Anmerkungen? Tips?

Bitte nehmen Sie Kontakt zu mir auf (info10@wilfried-grupe.de).



Vielen Dank für Ihr Interesse an meiner Arbeit.


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

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