21 Maps

When XSLT 3.0 is used with XPath 3.0, it extends the type system and data model of XPath 3.0 with an additional datatype: the map. A map is an additional kind of item. Supporting this additional type are additional XPath language constructs, types, and XSLT instructions, all defined in this section.

Note:

The extensions to XPath 3.0 defined in this section have been incorporated into XPath 3.1. Therefore, when an XSLT 3.0 processor implements the XPath 3.1 Feature, the relevant parts of this section can be ignored.

[Definition: A map consists of a set of entries. Each entry comprises a key which is an arbitrary atomic value, and an arbitrary sequence called the associated value.]

[Definition: Within a map, no two entries have the same key. Two atomic values K1 and K2 are the same key for this purpose if the relation op:same-key(K1, K2, $UCC) holds.]

To put it another way, subject to the rule above regarding timezones, the keys are the same if either K1 eq K2 is true under the Unicode codepoint collation, or if both K1 and K2 are NaN. It is not necessary that all the keys should be mutually comparable (for example, they can include a mixture of integers and strings).

The function call map:get($map, $key) can be used to retrieve the value associated with a given key.

A map can also be viewed as a function from keys to associated values. To achieve this, a map is also a function item. The properties of this function are as follows:

Calling the function has the same effect as calling the get function: the expression $map($key) returns the same result as get($map, $key). For example, if $books-by-isbn is a map whose keys are ISBNs and whose associated values are book elements, then the expression $books-by-isbn("0470192747") returns the book element with the given ISBN. The fact that a map is a function item allows it to be passed as an argument to higher-order functions that expect a function item as one of their arguments.

Like all other values, maps are immutable. For example, the map:remove function returns a map that differs from the supplied map by the omission of one entry, but the supplied map is not changed by the operation. Two calls on map:remove with the same arguments will return maps that are indistinguishable from each other; there is no way of asking whether these are “the same map”.

21.1 The Type of a Map

The syntax of ItemTypeXP30 as defined in XPath is extended as follows:

MapType
[69]    ItemType    ::=    KindTest | ("item" "(" ")") | FunctionTest | AtomicOrUnionType | ParenthesizedItemType
| MapType
[201]    MapType    ::=    'map' '(' ( '*' | (AtomicOrUnionTypeXP30 ',' SequenceTypeXP30) ')'

The following rules express the matching rules for a map item type and a map, and extend the set of rules given in Section 2.5.5.2 Matching an ItemType and an Item XP30:

A map also acts as a function. This means that maps match certain function item types. Specifically, the following rule extends the list of rules in Section 2.5.5.7 Function Test XP30:

Because of the rules for subtyping of function types according to their signature, it follows that the item type function(A) as item()*, where A is an atomic type, also matches any map, regardless of the type of the keys actually found in the map. For example, a map whose keys are all strings can be supplied where the required type is function(xs:integer) as item()*; a call on the map that treats it as a function with an integer argument will always succeed, and will always return an empty sequence.

The function signature of the map, treated as a function, is always function(xs:anyAtomicType) as item()*, regardless of the actual types of the keys and values in the map. This means that a function item type with a more specific return type, such as function(xs:anyAtomicType) as xs:integer, does not match a map in the sense required to satisfy the instance of operator. However, the rules for function coercion mean that any map can be supplied as a value in a context where such a type is the required type, and a type error will only occur if an actual call on the map (treated as a function) returns a value that is not an instance of the required return type.

Note:

So, given a map $M whose keys are integers and whose results are strings, such as map{0:"no", 1:"yes"}, the following relations hold, among others:

  • $M instance of map(*)

  • $M instance of map(xs:integer, xs:string)

  • $M instance of map(xs:decimal, xs:anyAtomicType)

  • not($M instance of map(xs:int, xs:string))

  • not($M instance of map(xs:integer, xs:token))

  • $M instance of function(*)

  • $M instance of function(xs:anyAtomicType) as item()*

  • $M instance of function(xs:integer) as item()*

  • $M instance of function(xs:int) as item()*

  • $M instance of function(xs:string) as item()*

  • not($M instance of function(xs:integer) as xs:string)

The last case might seem surprising; however, function coercion ensures that $M can be used successfully anywhere that the required type is function(xs:integer) as xs:string.

The rules for judging whether one item type is a subtype of another, given in Section 2.5.6.2 The judgement subtype-itemtype(Ai, Bi) XP30, are extended with some additional rules. The judgement subtype-itemtype(Ai, Bi) is true if:

21.2 Functions that Operate on Maps

XSLT 3.0 provides a number of functions that operate on maps, or that are useful in conjunction with maps. These functions are specified in [Functions and Operators 3.1], but they are available with XSLT 3.0 whether or not the processor offers the XPath 3.1 Feature.

Some of the functions defined in this section use a conventional namespace prefix map, which is assumed to be bound to the namespace URI http://www.w3.org/2005/xpath-functions/map.

Note that there is no operation to atomize a map or convert it to a string.

21.2.1 op:same-key

Summary

Determines whether two atomic values can coexist as separate keys within a map.

Signature
op:same-key( $k1  as xs:anyAtomicType,
$k2  as xs:anyAtomicType) as xs:boolean
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The internal function op:same-key (which is not available at the user level) is used to assess whether two atomic values are considered to be duplicates when used as keys in a map. A map cannot contain two separate entries whose keys are the same as defined by this function. The function is also used when matching keys in functions such as map:get and map:remove.

The function returns true if and only if one of the following conditions is true:

  1. All of the following conditions are true:

    1. $k1 is an instance of xs:string, xs:anyURI, or xs:untypedAtomic

    2. $k2 is an instance of xs:string, xs:anyURI, or xs:untypedAtomic

    3. fn:codepoint-equal($k1, $k2)

    Note:

    Strings are compared without any dependency on collations.

  2. All of the following conditions are true:

    1. $k1 is an instance of xs:decimal, xs:double, or xs:float

    2. $k2 is an instance of xs:decimal, xs:double, or xs:float

    3. One of the following conditions is true:

      1. Both $k1 and $k2 are NaN

        Note:

        xs:double('NaN') is the same key as xs:float('NaN')

      2. Both $k1 and $k2 are positive infinity

        Note:

        xs:double('INF') is the same key as xs:float('INF')

      3. Both $k1 and $k2 are negative infinity

        Note:

        xs:double('-INF') is the same key as xs:float('-INF')

      4. $k1 and $k2 when converted to decimal numbers with no rounding or loss of precision are mathematically equal.

        Note:

        Every instance of xs:double, xs:float, and xs:decimal can be represented exactly as a decimal number provided enough digits are available both before and after the decimal point. Unlike the eq relation, which converts both operands to xs:double values, possibly losing precision in the process, this comparison is transitive.

        Note:

        Positive and negative zero are the same key.

  3. All of the following conditions are true:

    1. $k1 is an instance of xs:date, xs:time, xs:dateTime, xs:gYear, xs:gYearMonth, xs:gMonth, xs:gMonthDay, or xs:gDay

    2. $k2 is an instance of xs:date, xs:time, xs:dateTime, xs:gYear, xs:gYearMonth, xs:gMonth, xs:gMonthDay, or xs:gDay

    3. One of the following conditions is true:

      1. Both $k1 and $k2 have a timezone

      2. Neither $k1 nor $k2 has a timezone

    4. fn:deep-equal($k1, $k2)

      Note:

      The use of deep-equal rather than eq ensures that comparing values of different types yields false rather than an error.

    Note:

    Unlike the eq operator, this comparison has no dependency on the implicit timezone, which means that the question of whether or not a map contains duplicate keys is not dependent on this aspect of the dynamic context.

  4. All of the following conditions are true:

    1. $k1 is an instance of xs:boolean, xs:hexBinary, xs:base64Binary, xs:duration, xs:QName, or xs:NOTATION

    2. $k2 is an instance of xs:boolean, xs:hexBinary, xs:base64Binary, xs:duration, xs:QName, or xs:NOTATION

    3. fn:deep-equal($k1, $k2)

      Note:

      The use of deep-equal rather than eq ensures that comparing values of different types yields false rather than an error.

Notes

The rules for comparing keys in a map are chosen to ensure that the comparison is:

  • Context-free: there is no dependency on the static or dynamic context

  • Error-free: any two atomic values can be compared, and the result is either true or false, never an error

  • Transitive: if A is the same key as B, and B is the same key as C, then A is the same key as C.

As always, any algorithm that delivers the right result is acceptable. For example, when testing whether an xs:double value D is the same key as an xs:decimal value that has N significant digits, it is not necessary to know all the digits in the decimal expansion of D to establish the result: computing the first N+1 significant digits (or indeed, simply knowing that there are more than N significant digits) is sufficient.

21.2.2 map:merge

Summary

Returns a map that combines the entries from a number of existing maps.

Signatures
map:merge($maps as map(*)*) as map(*)
map:merge( $maps  as map(*)*,
$options  as map(*)) as map(*)
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:merge returns a map that is formed by combining the contents of the maps supplied in the $maps argument.

Informally, the supplied maps are combined as follows:

  1. There is one entry in the returned map for each distinct key present in the union of the input maps, where two keys are distinct if they are not the same key.

  2. If there are duplicate keys, that is, if two or more maps contain entries having the same key, then the way this is handled is controlled by the second ($options) argument.

The definitive specification is as follows.

  1. The effect of calling the single-argument function is the same as the effect of calling the two-argument function with an empty map as the value of $options.

  2. The $options argument can be used to control the way in which duplicate keys are handled. The option parameter conventions apply.

  3. The entries that may appear in the $options map are as follows:

    Key Value Meaning
    duplicates Determines the policy for handling duplicate keys: specifically, the action to be taken if two maps in the input sequence $maps contain entries with key values K1 and K2 where K1 and K2 are the same key. The required type is xs:string. The default value is use-first.
    reject An error is raised [ERR FOJS0003] FO31 if duplicate keys are encountered.
    use-first If duplicate keys are present, all but the first of a set of duplicates are ignored, where the ordering is based on the order of maps in the $maps argument.
    use-last If duplicate keys are present, all but the last of a set of duplicates are ignored, where the ordering is based on the order of maps in the $maps argument.
    combine If duplicate keys are present, the result map includes an entry for the key whose associated value is the sequence-concatenation of all the values associated with the key, retaining order based on the order of maps in the $maps argument. The key value in the result map that corresponds to such a set of duplicates must be the same key as each of the duplicates, but it is otherwise unconstrained: for example if the duplicate keys are xs:byte(1) and xs:short(1), the key in the result could legitimately be xs:long(1).
    unspecified If duplicate keys are present, all but one of a set of duplicates are ignored, and it is implementation-dependent which one is retained.

The result of the function call map:merge($MAPS, $OPTIONS) is defined to be consistent with the result of the expression:

let $FOJS0003 := QName("http://www.w3.org/2005/xqt-errors", "FOJS0003"),

$duplicates-handler := map {
  "use-first":   function($a, $b) {$a},
  "use-last":    function($a, $b) {$b},
  "combine":     function($a, $b) {$a, $b},
  "reject":      function($a, $b) {fn:error($FOJS0003)},
  "unspecified": function($a, $b) {fn:random-number-generator()?permute(($a, $b))[1]}
},

$combine-maps := function($A as map(*), $B as map(*), $deduplicator as function(*)) {
    fn:fold-left(map:keys($B), $A, function($z, $k){ 
        if (map:contains($z, $k))
        then map:put($z, $k, $deduplicator($z($k), $B($k)))
        else map:put($z, $k, $B($k))
    })
}
return fn:fold-left($MAPS, map{}, 
    $combine-maps(?, ?, $duplicates-handler(($OPTIONS?duplicates, "use-first")[1]))
            
            

Note:

By way of explanation, $combine-maps is a function that combines two maps by iterating over the keys of the second map, adding each key and its corresponding value to the first map as it proceeds. The second call of fn:fold-left in the return clause then iterates over the maps supplied in the call to map:merge, accumulating a single map that absorbs successive maps in the input sequence by calling $combine-maps.

This algorithm processes the supplied maps in a defined order, but processes the keys within each map in implementation-dependent order.

The use of fn:random-number-generator represents one possible conformant implementation for "duplicates":"unspecified", but it is not the only conformant implementation and is not necessarily a realistic implementation.

Error Conditions

An error is raised [ERR FOJS0003] FO31 if the value of $options indicates that duplicates are to be rejected, and a duplicate key is encountered.

An error is raised [ERR FOJS0005] FO31 if the value of $options includes an entry whose key is defined in this specification, and whose value is not a permitted value for that key.

Notes

If the input is an empty sequence, the result is an empty map.

If the input is a sequence of length one, the result map is indistinguishable from the supplied map.

There is no requirement that the supplied input maps should have the same or compatible types. The type of a map (for example map(xs:integer, xs:string)) is descriptive of the entries it currently contains, but is not a constraint on how the map may be combined with other maps.

Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 
     3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 
     6:"Samstag"}

The expression map:merge(()) returns map{}. (Returns an empty map).

The expression map:merge((map:entry(0, "no"), map:entry(1, "yes"))) returns map{0:"no", 1:"yes"}. (Returns a map with two entries).

The expression map:merge(($week, map{7:"Unbekannt"})) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag", 7:"Unbekannt"}. (The value of the existing map is unchanged; the returned map contains all the entries from $week, supplemented with an additional entry.)

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"use-last"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Sonnabend"}. (The value of the existing map is unchanged; the returned map contains all the entries from $week, with one entry replaced by a new entry. Both input maps contain an entry with the key 6; the one used in the result is the one that comes last in the input sequence.)

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"use-first"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}. (The value of the existing map is unchanged; the returned map contains all the entries from $week, with one entry replaced by a new entry. Both input maps contain an entry with the key 6; the one used in the result is the one that comes first in the input sequence.)

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"combine"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:("Samstag", "Sonnabend")}. (The value of the existing map is unchanged; the returned map contains all the entries from $week, with one entry replaced by a new entry. Both input maps contain an entry with the key 6; the entry that appears in the result is the sequence-concatenation of the entries in the input maps, retaining order.)

21.2.3 map:size

Summary

Returns the number of entries in the supplied map.

Signature
map:size($map as map(*)) as xs:integer
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:size takes any map as its $map argument and returns the number of entries that are present in the map.

Examples

The expression map:size(map{}) returns 0.

The expression map:size(map{"true":1, "false":0}) returns 2.

21.2.4 map:keys

Summary

Returns a sequence containing all the keys present in a map

Signature
map:keys($map as map(*)) as xs:anyAtomicType*
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:keys takes any map as its $map argument and returns the keys that are present in the map as a sequence of atomic values, in implementation-dependent order.

The function is non-deterministic with respect to ordering (see Section 1.7.4 Properties of functions FO31). This means that two calls with the same argument are not guaranteed to produce the results in the same order.

Notes

The number of items in the result will be the same as the number of entries in the map, and the result sequence will contain no duplicate values.

Examples

The expression map:keys(map{1:"yes", 2:"no"}) returns some permutation of (1,2). (The result is in implementation-dependent order.)

21.2.5 map:contains

Summary

Tests whether a supplied map contains an entry for a given key

Signature
map:contains( $map  as map(*),
$key  as xs:anyAtomicType) as xs:boolean
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:contains returns true if the map supplied as $map contains an entry with the same key as the supplied value of $key; otherwise it returns false.

Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 
    3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}

The expression map:contains($week, 2) returns true().

The expression map:contains($week, 9) returns false().

The expression map:contains(map{}, "xyz") returns false().

The expression map:contains(map{"xyz":23}, "xyz") returns true().

The expression map:contains(map{"abc":23, "xyz":()}, "xyz") returns true().

21.2.6 map:get

Summary

Returns the value associated with a supplied key in a given map.

Signature
map:get( $map  as map(*),
$key  as xs:anyAtomicType) as item()*
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:get attempts to find an entry within the map supplied as $map that has the same key as the supplied value of $key. If there is such an entry, it returns the associated value; otherwise it returns an empty sequence.

Notes

A return value of () from map:get could indicate that the key is present in the map with an associated value of (), or it could indicate that the key is not present in the map. The two cases can be distinguished by calling map:contains.

Invoking the map as a function item has the same effect as calling get: that is, when $map is a map, the expression $map($K) is equivalent to map:get($map, $K). Similarly, the expression map:get(map:get(map:get($map, 'employee'), 'name'), 'first') can be written as $map('employee')('name')('first').

Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 
     3:"Mittwoch", 4:"Donnerstag", 5:"Freitag",  
     6:"Samstag"}

The expression map:get($week, 4) returns "Donnerstag".

The expression map:get($week, 9) returns (). (When the key is not present, the function returns an empty sequence.)

The expression map:get(map:entry(7,()), 7) returns (). (An empty sequence as the result can also signify that the key is present and the associated value is an empty sequence.)

21.2.7 map:put

Summary

Returns a map containing all the contents of the supplied map, but with an additional entry, which replaces any existing entry for the same key.

Signature
map:put( $map  as map(*),
$key  as xs:anyAtomicType,
$value  as item()*) as map(*)
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:put returns a map that contains all entries from the supplied $map, with the exception of any entry whose key is the same key as $key, together with a new entry whose key is $key and whose associated value is $value.

The effect of the function call map:put($MAP, $KEY, $VALUE) is equivalent to the result of the following steps:

  1. let $MAP2 := map:remove($MAP, $KEY)

    This returns a map in which all entries with the same key as $KEY have been removed.

  2. Construct and return a map containing:

    1. All the entries (key/value pairs) in $MAP2, and

    2. The entry map:entry($KEY, $VALUE)

Notes

There is no requirement that the type of $key and $value be consistent with the types of any existing keys and values in the supplied map.

Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 
     3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 
     6:"Samstag"}

The expression map:put($week, 6, "Sonnabend") returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Sonnabend"}.

The expression map:put($week, -1, "Unbekannt") returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag", -1:"Unbekannt"}.

21.2.8 map:entry

Summary

Returns a map that contains a single entry (a key-value pair).

Signature
map:entry( $key  as xs:anyAtomicType,
$value  as item()*) as map(*)
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:entry returns a map which contains a single entry. The key of the entry in the new map is $key, and its associated value is $value.

Notes

The function map:entry is intended primarily for use in conjunction with the function map:merge. For example, a map containing seven entries may be constructed like this:

map:merge((
   map:entry("Su", "Sunday"),
   map:entry("Mo", "Monday"),
   map:entry("Tu", "Tuesday"),
   map:entry("We", "Wednesday"),
   map:entry("Th", "Thursday"),
   map:entry("Fr", "Friday"),
   map:entry("Sa", "Saturday")
   ))

Unlike the map expression (map{...}), this technique can be used to construct a map with a variable number of entries, for example:

map:merge(for $b in //book return map:entry($b/isbn, $b))
Examples

The expression map:entry("M", "Monday") returns {"M":"Monday"}.

21.2.9 map:remove

Summary

Returns a map containing all the entries from a supplied map, except those having a specified key.

Signature
map:remove( $map  as map(*),
$keys  as xs:anyAtomicType*) as map(*)
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:remove returns a map containing all the entries in $map except for any entry whose key is the same key as an item in $keys.

No failure occurs if an item in $keys does not correspond to any entry in $map; that key value is simply ignored.

The effect of the function call map:remove($MAP, $KEY) can be described more formally as the result of the expression below:

map:merge (
    map:for-each (
       $MAP, function($k, $v) { 
               if (some $key in $KEY satisfies op:same-key($k, $key)) 
               then () 
               else map:entry($k, $v)
             } ) ) 
Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag",
     3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}

The expression map:remove($week, 4) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 5:"Freitag", 6:"Samstag"}.

The expression map:remove($week, 23) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}.

The expression map:remove($week, (0, 6 to 7)) returns map{1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag"}.

The expression map:remove($week, ()) returns >map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}.

21.2.10 map:for-each

Summary

Applies a supplied function to every entry in a map, returning the concatenation of the results.

Signature
map:for-each( $map  as map(*),
$action  as function(xs:anyAtomicType, item()*) as item()*) as item()*
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:for-each takes any map as its $map argument and applies the supplied function to each entry in the map, in implementation-dependent order; the result is the sequence obtained by concatenating the results of these function calls.

The function is non-deterministic with respect to ordering (see Section 1.7.4 Properties of functions FO31). This means that two calls with the same arguments are not guaranteed to process the map entries in the same order.

The function supplied as $action takes two arguments. It is called supplying the key of the map entry as the first argument, and the associated value as the second argument.

Examples

The expression map:for-each(map{1:"yes", 2:"no"}, function($k, $v){$k}) returns some permutation of (1,2). (This function call is equivalent to calling map:keys. The result is in implementation-dependent order.)

The expression distinct-values(map:for-each(map{1:"yes", 2:"no"}, function($k, $v){$v})) returns some permutation of ("yes", "no"). (This function call returns the distinct values present in the map, in implementation-dependent order.)

The expression map:merge(map:for-each(map{"a":1, "b":2}, function($k, $v){map:entry($k, $v+1)})) returns map{"a":2, "b":3}. (This function call returns a map with the same keys as the input map, with the value of each entry increased by one.)

Example: Converting a Map to an Element Node

This XQuery example converts the entries in a map to attributes on a newly constructed element node.

let
  $dimensions := map{'height': 3, 'width': 4, 'depth': 5};
return
  <box>{
     map:for-each($dimensions, function ($k, $v) { attribute {$k} {$v} })
  }</box>

The result is the element <box height="3" width="4" depth="5"/>.

21.2.11 map:find

Summary

Searches the supplied input sequence and any contained maps and arrays for a map entry with the supplied key, and returns the corresponding values.

Signature
map:find( $input  as item()*,
$key  as xs:anyAtomicType) as array(*)
Properties

This function is deterministicFO30, context-independentFO30, and focus-independentFO30.

Rules

The function map:find searches the sequence supplied as $input looking for map entries whose key is the same key as $key. The associated value in any such map entry (each being in general a sequence) is returned as a member of the result array.

The search processes the $input sequence using the following recursively-defined rules (any equivalent algorithm may be used provided it delivers the same result, respecting those rules that constrain the order of the result):

  1. To process a sequence, process each of its items in order.

  2. To process an item that is an array, process each of the array's members in order (each member is, in general, a sequence).

  3. To process an item that is a map, then for each key-value entry (K, V) in the map (in implementation-dependent order) perform both of the following steps, in order:

    1. If K is the same key as $key, then add V as a new member to the end of the result array.

    2. Process V (which is, in general, a sequence).

  4. To process an item that is neither a map nor an array, do nothing. (Such items are ignored).

Notes

If $input is an empty sequence, map, or array, or if the requested $key is not found, the result will be a zero-length array.

Examples
let $responses := [map{0:'no', 1:'yes'},   
     map{0:'non', 1:'oui'},   
     map{0:'nein', 1:('ja', 'doch')}]

The expression map:find($responses, 0) returns ['no', 'non', 'nein'].

The expression map:find($responses, 1) returns ['yes', 'oui', ('ja', 'doch')].

The expression map:find($responses, 2) returns [].

let $inventory := map{"name":"car", "id":"QZ123",   
     "parts": [map{name":"engine", "id":"YW678", "parts":[]}]}

The expression map:find($inventory, "parts") returns [[map{name":"engine", "id":"YW678", "parts":[]}], []].

21.2.12 fn:collation-key

Summary

Given a string value and a collation, generates an internal value called a collation key, with the property that the matching and ordering of collation keys reflects the matching and ordering of strings under the specified collation.

Signatures
fn:collation-key($key as xs:string) as xs:base64Binary
fn:collation-key( $key  as xs:string,
$collation  as xs:string) as xs:base64Binary
Properties

This function is deterministicFO30, context-dependentFO30, and focus-independentFO30. It depends on collations.

Rules

Calling the one-argument version of this function is equivalent to calling the two-argument version supplying the default collation as the second argument.

The function returns an implementation-dependent value with the property that, for any two strings $K1 and $K2:

  • collation-key($K1, $C) eq collation-key($K2, $C) if and only if compare($K1, $K2, $C) eq 0

  • collation-key($K1, $C) lt collation-key($K2, $C) if and only if compare($K1, $K2, $C) lt 0

The collation used by this function is determined in the same way as for other functions accepting a collation URI argument. Collation keys are defined as xs:base64Binary values to ensure unambiguous and context-free comparison semantics.

An implementation is free to generate a collation key in any convenient way provided that it always generates the same collation key for two strings that are equal under the collation, and different collation keys for strings that are not equal. This holds only within a single execution scopeFO30; an implementation is under no obligation to generate the same collation keys during a subsequent unrelated query or transformation.

It is possible to define collations that do not have the ability to generate collation keys. Supplying such a collation will cause the function to fail. The ability to generate collation keys is an implementation-defined property of the collation.

Error Conditions

An error is raised [ERR FOCH0004] FO31 if the specified collation does not support the generation of collation keys.

Notes

The function is provided primarily for use with maps. If a map is required where codepoint equality is inappropriate for comparing keys, then a common technique is to normalize the key so that equality matching becomes feasible. There are many ways keys can be normalized, for example by use of functions such as fn:upper-case, fn:lower-case, fn:normalize-space, or fn:normalize-unicode, but this function provides a way of normalizing them according to the rules of a specified collation. For example, if the collation ignores accents, then the function will generate the same collation key for two input strings that differ only in their use of accents.

The result of the function is defined to be an xs:base64Binary value. Binary values are chosen because they have unambiguous and context-free comparison semantics, because the value space is unbounded, and because the ordering rules are such that between any two values in the ordered value space, an arbitrary number of further values can be interpolated. The choice between xs:base64Binary and xs:hexBinary is arbitrary; the only operation that behaves differently between the two binary data types is conversion to/from a string, and this operation is not one that is normally required for effective use of collation keys.

For collations based on the Unicode Collation Algorithm, an algorithm for computing collation keys is provided in [UNICODE TR10]. Implementations are not required to use this algorithm.

This specification does not mandate that collation keys should retain ordering. This is partly because the primary use case is for maps, where only equality comparisons are required, and partly to allow the use of binary data types (which are currently unordered types) for the result. The specification may be revised in a future release to specify that ordering is preserved.

The fact that collation keys are ordered can be exploited in XQuery, whose order by clause does not allow the collation to be selected dynamically. This restriction can be circumvented by rewriting the clause order by $e/@key collation "URI" as order by fn:collation-key($e/@key, $collation), where $collation allows the collation to be chosen dynamically.

Note that xs:base64Binary becomes an ordered type in XPath 3.1, making binary collation keys possible. In an implementation that adheres strictly to XPath 3.0, collation keys can be used only for equality matching, not for ordering operations.

Examples
let $C := 'http://www.w3.org/2013/collation/UCA?strength=primary'

The expression map:merge((map{collation-key("A", $C):1}, map{collation-key("a", $C):2}), map{"duplicates":"use-last"})(collation-key("A", $C)) returns 2. (Given that the keys of the two entries are equal under the rules of the chosen collation, only one of the entries can appear in the result; the one that is chosen is the one from the last map in the input sequence.)

The expression let $M := map{collation-key("A", $C):1, collation-key("B", $C):2} return $M(collation-key("a", $C)) returns 1. (The strings "A" and "a" have the same collation key under this collation.)

As the above examples illustrate, it is important that when the collation-key function is used to add entries to a map, then it must also be used when retrieving entries from the map. This process can be made less error-prone by encapsulating the map within a function: function($k) {$M(collation-key($k, $collation)}.

21.2.13 fn:deep-equal

The deep-equalFO30 function, when used with XSLT 3.0, is defined to handle maps in the way that is defined in the [Functions and Operators 3.1] specification of the function.

Specifically, two maps are deep-equal if they have the same number of entries, and if there is a one-to-one correspondence in the sense that for every entry E1in the first map, there is a corresponding entry E2 in the second map, such that the keys of E1 and E2 are equal under the op:same-key relation (see 21.2.1 op:same-key), and the corresponding values are equal under the deep-equal relation, invoked recursively using the collation supplied as argument to the original deep-equal call, or its default. Note that collations are not used for comparing keys.

21.3 Map Instructions

Two instructions are added to XSLT to facilitate the construction of maps.

<!-- Category: instruction -->
<xsl:map>
  <!-- Content: sequence-constructor -->
</xsl:map>

The instruction xsl:map constructs and returns a new map.

The contained sequence constructor must evaluate to a sequence of maps: call this $maps.

The result of the instruction (other than the choice of error code) is then given by the XPath 3.1 expression:

   
map:merge($maps, map{"duplicates":"reject"}) 

Note:

Informally: if there are duplicate keys among the sequence of maps, a dynamic error occurs. Otherwise, the resulting map contains the union of the map entries from the supplied sequence of maps.

[ERR XTDE3365] A dynamic error occurs if the set of keys in the maps resulting from evaluating the sequence constructor contains duplicates.

There is no requirement that the supplied input maps should have the same or compatible types. The type of a map (for example map(xs:integer, xs:string)) is descriptive of the entries it currently contains, but is not a constraint on how the map may be combined with other maps.

[ERR XTTE3375] A type error occurs if the result of evaluating the sequence constructor is not an instance of the required type map(*)*.

Note:

In practice, the effect of this rule is that the sequence constructor contained in the xsl:map instruction is severely constrained: it doesn’t make sense, for example, for it to contain instructions such as xsl:element that create new nodes. As with other type errors, processors are free to signal the error statically if they are able to determine that the sequence constructor would always fail when evaluated.

<!-- Category: instruction -->
<xsl:map-entry
  key = expression
  select? = expression >
  <!-- Content: sequence-constructor -->
</xsl:map-entry>

The instruction xsl:map-entry constructs and returns a singleton map: that is, a map which contains one key and one value. Such a map is primarily used as a building block when constructing maps using the xsl:map instruction.

The select attribute and the contained sequence constructor are mutually exclusive: if a select attribute is present, then the content must be empty except optionally for xsl:fallback instructions.

[ERR XTSE3280] It is a static error if the select attribute of the xsl:map-entry element is present unless the element has no children other than xsl:fallback elements.

The key of the entry in the new map is the value obtained by evaluating the expression in the key attribute, converted to the required type xs:anyAtomicType by applying the function conversion rules. If the supplied key (after conversion) is of type xs:untypedAtomic, it is cast to xs:string.

The associated value is the value obtained by evaluating the expression in the select attribute, or the contained sequence constructor, with no conversion. If there is no select attribute and the sequence constructor is empty, the associated value is the empty sequence.

Example: Using XSLT instructions to create a fixed map

The following example binds a variable to a map whose content is statically known:

<xsl:variable name="week" as="map(xs:string, xs:string)">
  <xsl:map>
    <xsl:map-entry key="'Mo'" select="'Monday'"/>
    <xsl:map-entry key="'Tu'" select="'Tuesday'"/>
    <xsl:map-entry key="'We'" select="'Wednesday'"/>
    <xsl:map-entry key="'Th'" select="'Thursday'"/>
    <xsl:map-entry key="'Fr'" select="'Friday'"/>
    <xsl:map-entry key="'Sa'" select="'Saturday'"/>
    <xsl:map-entry key="'Su'" select="'Sunday'"/>
  </xsl:map>
</xsl:variable>  

 

Example: Using XSLT instructions to create a computed map

The following example binds a variable to a map acting as an index into a source document:

<xsl:variable name="index" as="map(xs:string, element(employee))">
  <xsl:map>
    <xsl:for-each select="//employee">
      <xsl:map-entry key="@empNr" select="."/>
    </xsl:for-each>
  </xsl:map>
</xsl:variable>  

21.4 Map Constructors

A Map Constructor is a new kind of expression added to the syntax of XPath.

Note:

Map Constructors are defined in XPath 3.1. They are available in XSLT 3.0 whether or not XPath 3.1 is supported. The specification given here is intended to be identical to the specification in XPath 3.1.

The syntax of PrimaryExprXP30 is extended to permit MapConstructor as an additional alternative.

MapConstructor
[52]    PrimaryExpr    ::=    Literal | VarRef | ParenthesizedExpr | ContextItemExpr | FunctionCall | FunctionItemExpr
| MapConstructor
[202]    MapConstructor    ::=    "map" "{" (MapConstructorEntry ("," MapConstructorEntry )*)? "}"
[203]    MapConstructorEntry    ::=    MapKeyExpr ":" MapValueExpr
[204]    MapKeyExpr    ::=    ExprSingleXP30
[205]    MapValueExpr    ::=    ExprSingleXP30

Note:

In some circumstances, it is necessary to include whitespace before or after the colon to ensure that this grammar is correctly parsed; this arises for example when the KeyExpr ends with a name and the ValueExpr starts with a name.

The value of the expression is a map whose entries correspond to the key-value pairs obtained by evaluating the successive KeyExpr and ValueExpr expressions.

Each KeyExpr expression is evaluated and atomized; a type error [ERR XPTY0004] XP30 occurs if the result is not a single atomic value. If the key is of type xs:untypedAtomic it is converted to xs:string. The associated value is the result of evaluating the corresponding ValueExpr. If two or more entries have the same key then a dynamic error occurs [see ERR XTDE3365].

For example, the following expression constructs a map with seven entries:

map {
  "Su" : "Sunday",
  "Mo" : "Monday",
  "Tu" : "Tuesday",
  "We" : "Wednesday",
  "Th" : "Thursday",
  "Fr" : "Friday",
  "Sa" : "Saturday"
}

Note:

Unlike the map:merge function, the number of entries in a map that is constructed using a map expression is known statically.

21.5 The Map Lookup Operator

A new operator is introduced into XPath to allow convenient lookup of entries in a map (or a sequence of maps), knowing the key.

Note:

Map Lookup Expressions are defined in XPath 3.1. They are available in XSLT 3.0 whether or not XPath 3.1 is supported. The specification given here is intended to be identical to the specification in XPath 3.1.

The operator is available in two forms: as a unary (prefix) operator, and as a postfix operator.

21.5.1 The Unary Lookup Operator

[76] UnaryLookup ::= "?" KeySpecifier [54] KeySpecifier ::= NCName | IntegerLiteral | ParenthesizedExpr | "*"

A UnaryLookup expression returns a sequence of values selected from the map that is the context item. If the context item is not a map or an array, a type error is raised [err:XPTY0004]

The semantics are as follows:

  1. If the KeySpecifier is an NCName, the UnaryLookup expression ?KS is equivalent to .("KS"). For example, $emp[?name='Jim'] is shorthand for $emp[.("name")='Jim'].

  2. If the KeySpecifier is an IntegerLiteral, the UnaryLookup expression ?N is equivalent to .(N). This form is only useful for maps whose keys are numeric. For example, $temp[?7 > 30] is shorthand for $temp[.(7) > 30].

  3. If the KeySpecifier is a ParenthesizedExpr, the UnaryLookup expression ?(EXP) is equivalent to .(EXP). This form allows arbitrary keys, including keys computed dynamically. For example, $emp[?('Year of Birth') > 1980] is shorthand for $emp[.("Year of Birth") > 1980]

  4. If the KeySpecifier is a wildcard ("*") the UnaryLookup expression ?* is equivalent to the expression for $k in map:keys(.) return .($k). That is, it returns the sequence-concatenation of all the values in the map; since the order of keys is implementation-dependent, so is the order of these values.

21.5.2 The Postfix Lookup Operator

[53] Lookup ::= "?" KeySpecifier

The semantics of the postfix lookup operator are defined in terms of the unary lookup operator. The left-hand operand must be a map, or a sequence of maps. The KeySpecifier is applied to each of these maps, in order, and the results are sequence-concatenated.

  1. If the KeySpecifier is an NCName, the Postfix Lookup expression E?KS is equivalent to E!?KS. For example, if $emps is a sequence of maps containing information about employees, then $emps?name selects the names of the employees: it is equivalent to $emps!map:get(., "name").

  2. If the KeySpecifier is an IntegerLiteral, the Postfix Lookup expression E?N is equivalent to E!?N. This form is only useful for maps whose keys are numeric.

    For example, if $emps is a sequence of maps containing information about employees, and if one of the entries in this map represents the salary history, as a map whose keys are the relevant year and whose associated value is the salary, then $emps[?name='John']?2012 returns the value of John’s salary in the year 2012.

  3. If the KeySpecifier is a ParenthesizedExpr, the Postfix Lookup expression E?(EXP) is equivalent to for $m in E, $k in EXP return $m!?($k). This form allows arbitrary keys, including keys computed dynamically. It also allows multiple keys.

    For example, if $emps is a sequence of maps containing information about employees, and if one of the entries in this map represents the salary history, as a map whose keys are the relevant year and whose associated value is the salary, then $emps[?name='John']?(2012 to 2015) returns the value of John’s salary in the years 2012 through 2015.

  4. If the KeySpecifier is a wildcard ("*") the Postfix Lookup expression E?* is equivalent to the expression E!?*. That is, it returns the sequence-concatenation of all the values in all the maps; since the order of keys within each map is implementation-dependent, so is the order of these values.

21.6 Maps and Streaming

Maps have many uses, but their introduction to XSLT 3.0 was strongly motivated by streaming use cases. In essence, when a source document is processed in streaming mode, data that is encountered in the course of processing may need to be retained in variables for subsequent use, because the nodes cannot be revisited. This creates a need for a flexible data structure to accommodate such temporary data, and maps were designed to fulfil this need.

The entries in a map are not allowed to contain references to streamed nodes. This is achieved by ensuring that for all constructs that supply content to be included in a map (for example the third argument of map:put, and the select attribute of xsl:map-entry), the relevant operand is defined to have operand usage navigation. Because maps cannot contain references to streamed nodes, they are effectively grounded, and can therefore be used freely in contexts (such as parameters to functions or templates) where only grounded operands are permitted.

The xsl:map instruction, and the XPath MapConstructor construct, are exceptions to the general rule that during streaming, only one downward selection (one consuming subexpression) is permitted. They share this characteristic with xsl:fork. As with xsl:fork, a streaming processor is expected to be able to construct the map during a single pass of the streamed input document, which may require multiple expressions to be evaluated in parallel.

In the case of the xsl:map instruction, this exemption applies only in the case where the instruction consists exclusively of xsl:map-entry (and xsl:fallback) children, and not in more complex cases where the map entries are constructed dynamically (for example using a control flow implemented using xsl:choose, xsl:for-each, or xsl:call-template). Such cases may, of course, be streamable if they only have a single consuming subexpression.

For example, the following XPath expression is streamable, despite making two downward selections:

let $m := map{'price':xs:decimal(price), 'discount':xs:decimal(discount)} 
return ($m?price - $m?discount)

Analysis:

  1. Because the return clause is motionless, the sweep of the let expression is the sweep of the map expression (the expression in curly brackets).

  2. The sweep of a map expression is the maximum sweep of its key/value pairs.

  3. For both key/value pairs, the key is motionless and the value is consuming.

  4. The expression carefully atomizes both values, because retaining references to streamed nodes in a map is not permitted.

  5. Therefore the map expression, and hence the expression as a whole, is grounded and consuming.

See also: 19.8.8.17 Streamability of Map Constructors, 19.8.4.23 Streamability of xsl:map, 19.8.4.24 Streamability of xsl:map-entry

21.7 Examples using Maps

This section gives some examples of where maps can be useful.

Example: Using Maps with xsl:iterate

This example uses maps in conjunction with the xsl:iterate instruction to find the highest-earning employee in each department, in a single streaming pass of an input document containing employee records.

<xsl:source-document streamable="yes" href="employees.xml">
  <xsl:iterate select="*/employee">
    <xsl:param name="highest-earners" 
               as="map(xs:string, element(employee))" 
               select="map{}"/>
    <xsl:on-completion>
      <xsl:for-each select="map:keys($highest-earners)">
        <department name="{.}">
          <xsl:copy-of select="$highest-earners(.)"/>
        </department>
      </xsl:for-each>
    </xsl:on-completion>           
    <xsl:variable name="this" select="copy-of(.)" as="element(employee)"/> 
    <xsl:next-iteration>
      <xsl:with-param name="highest-earners"
          select="let $existing := $highest-earners($this/department)
                  return if ($existing/salary gt $this/salary)
                         then $highest-earners
                         else map:put($highest-earners, $this/department, $this)"/>
    </xsl:next-iteration>
  </xsl:iterate>
</xsl:source-document>

 

Example: Using Maps to Implement Complex Numbers

A complex number might be represented as a map with two entries, the keys being the xs:boolean value true for the real part, and the xs:boolean value false for the imaginary part. A library for manipulation of complex numbers might include functions such as the following:

<xsl:variable name="REAL" static="yes" as="xs:int" select="0"/> 
<xsl:variable name="IMAG" static="yes" as="xs:int" select="1"/> 
                     
<xsl:function name="i:complex" as="map(xs:int, xs:double)">
  <xsl:param name="real" as="xs:double"/>
  <xsl:param name="imaginary" as="xs:double"/>
  <xsl:sequence select="map{ $REAL : $real, $IMAG : $imaginary }"/>
</xsl:function>

<xsl:function name="i:real" as="xs:double">
  <xsl:param name="complex" as="map(xs:int, xs:double)"/>
  <xsl:sequence select="$complex($REAL)"/>
</xsl:function>

<xsl:function name="i:imaginary" as="xs:double">
  <xsl:param name="complex" as="map(xs:int, xs:double)"/>
  <xsl:sequence select="$complex($IMAG)"/>
</xsl:function>

<xsl:function name="i:add" as="map(xs:int, xs:double)">
  <xsl:param name="arg1" as="map(xs:int, xs:double)"/>
  <xsl:param name="arg2" as="map(xs:int, xs:double)"/>
  <xsl:sequence select="i:complex(i:real($arg1)+i:real($arg2), 
                                  i:imaginary($arg1)+i:imaginary($arg2)"/>
</xsl:function>

<xsl:function name="i:multiply" as="map(xs:boolean, xs:double)">
  <xsl:param name="arg1" as="map(xs:boolean, xs:double)"/>
  <xsl:param name="arg2" as="map(xs:boolean, xs:double)"/>
  <xsl:sequence select="i:complex(
      i:real($arg1)*i:real($arg2) - i:imaginary($arg1)*i:imaginary($arg2),
      i:real($arg1)*i:imaginary($arg2) + i:imaginary($arg1)*i:real($arg2))"/>
</xsl:function>

 

Example: Using a Map as an Index

Given a set of book elements, it is possible to construct an index in the form of a map allowing the books to be retrieved by ISBN number.

Assume the book elements have the form:

<book>
  <isbn>0470192747</isbn>
  <author>Michael H. Kay</author>
  <publisher>Wiley</publisher>
  <title>XSLT 2.0 and XPath 2.0 Programmer's Reference</title>
</book>

An index may be constructed as follows:

<xsl:variable name="isbn-index" as="map(xs:string, element(book))"
    select="map:merge(for $b in //book return map{$b/isbn : $b})"/>

This index may then be used to retrieve the book for a given ISBN using either of the expressions map:get($isbn-index, "0470192747") or $isbn-index("0470192747").

In this simple form, this replicates the functionality available using xsl:key and the key function. However, it also provides capabilities not directly available using the key function: for example, the index can include book elements in multiple source documents. It also allows processing of all the books using a construct such as <xsl:for-each select="map:keys($isbn-index)">

 

Example: A Map containing Named Functions

As in JavaScript, a map whose keys are strings and whose associated values are function items can be used in a similar way to a class in object-oriented programming languages.

Suppose an application needs to handle customer order information that may arrive in three different formats, with different hierarchic arrangements:

  1. Flat structure:

    <customer id="c123">...</customer>
    <product id="p789">...</product>
    <order customer="c123" product="p789">...</order>
  2. Orders within customer elements:

    <customer id="c123">
       <order product="p789">...</order>
    </customer>
    <product id="p789">...</product>
  3. Orders within product elements:

    <customer id="c123">...</customer>
    <product id="p789">
      <order customer="c123">...</order>
    </product>

An application can isolate itself from these differences by defining a set of functions to navigate the relationships between customers, orders, and products: orders-for-customer, orders-for-product, customer-for-order, product-for-order. These functions can be implemented in different ways for the three different input formats. For example, with the first format the implementation might be:

<xsl:variable name="flat-input-functions" as="map(xs:string, function(*))*"
  select="map{
            'orders-for-customer' : 
                 function($c as element(customer)) as element(order)* 
                    {$c/../order[@customer=$c/@id]},
            'orders-for-product' : 
                 function($p as element(product)) as element(order)* 
                    {$p/../order[@product=$p/@id]},
            'customer-for-order' : 
                 function($o as element(order)) as element(customer) 
                    {$o/../customer[@id=$o/@customer]},
            'product-for-order' : 
                 function($o as element(order)) as element(product) 
                    {$o/../product[@id=$o/@product]} }                    
         "/>

Having established which input format is in use, the application can bind the appropriate implementation of these functions to a variable such as $input-navigator, and can then process the input using XPath expressions such as the following, which selects all products for which there is no order: //product[empty($input-navigator("orders-for-product")(.))]