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:
The name of the function is absent.
The arity of the function is 1 (one).
The parameter names comprise a sequence of one QName, conventionally
$key
, though the choice of name has no observable
consequences.
The signature is function($key as xs:anyAtomicValue) as
item()*
(with no annotations).
The implementation is the expression map:get($self,
$key)
The non-local-variable-bindings comprise a single variable,
$self
, whose value is the map itself.
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”.
The syntax of ItemTypeXP30 as defined in XPath is extended as follows:
[69] | ItemType |
::= | KindTest | ("item" "(" ")") | FunctionTest | AtomicOrUnionType |
ParenthesizedItemType |
|
[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:
The ItemType
map(K, V)
matches an item M if (a) M is a
map, and (b) every entry in M has
a key that matches K and an associated value that matches
V. For example, map(xs:integer, element(employee))
matches a map if all the keys in the map are integers, and all the associated
values are employee
elements. Note that a map (like a sequence)
carries no intrinsic type information separate from the types of its entries,
and the type of existing entries in a map does not constrain the type of new
entries that can be added to the map.
Note:
In consequence, map(K, V)
matches an empty
map, whatever the types K and V might be.
The ItemType
map(*)
matches any map regardless of its contents. It is
equivalent to map(xs:anyAtomicType, item()*)
.
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:
function(*)
matches any map.
function(xs:anyAtomicType) as item()*
matches any map.
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:
Ai
is map(K, V)
and Bi
is
map(*)
, for any K
and V
.
Ai
is map(Ka, Va)
and Bi
is
map(Kb, Vb)
, where subtype-itemtype(Ka, Kb)
and
subtype(Va, Vb)
.
Ai
is map(*)
(or, because of the transitivity rules,
any other map type) and Bi
is function(*)
.
Ai
is map(*)
, (or, because of the transitivity rules,
any other map type) and Bi
is function(xs:anyAtomicType) as
item()*
.
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.
Determines whether two atomic values can coexist as separate keys within a map.
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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:
All of the following conditions are true:
$k1
is an instance of xs:string
, xs:anyURI
, or xs:untypedAtomic
$k2
is an instance of xs:string
, xs:anyURI
, or xs:untypedAtomic
fn:codepoint-equal($k1, $k2)
Note:
Strings are compared without any dependency on collations.
All of the following conditions are true:
$k1
is an instance of xs:decimal
, xs:double
, or xs:float
$k2
is an instance of xs:decimal
, xs:double
, or xs:float
One of the following conditions is true:
Both $k1
and $k2
are NaN
Note:
xs:double('NaN')
is the same key as xs:float('NaN')
Both $k1
and $k2
are positive infinity
Note:
xs:double('INF')
is the same key as xs:float('INF')
Both $k1
and $k2
are negative infinity
Note:
xs:double('-INF')
is the same key as xs:float('-INF')
$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.
All of the following conditions are true:
$k1
is an instance of xs:date
, xs:time
, xs:dateTime
,
xs:gYear
, xs:gYearMonth
, xs:gMonth
, xs:gMonthDay
, or xs:gDay
$k2
is an instance of xs:date
, xs:time
, xs:dateTime
,
xs:gYear
, xs:gYearMonth
, xs:gMonth
, xs:gMonthDay
, or xs:gDay
One of the following conditions is true:
Both $k1
and $k2
have a timezone
Neither $k1
nor $k2
has a timezone
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.
All of the following conditions are true:
$k1
is an instance of xs:boolean
, xs:hexBinary
, xs:base64Binary
,
xs:duration
, xs:QName
, or xs:NOTATION
$k2
is an instance of xs:boolean
, xs:hexBinary
, xs:base64Binary
,
xs:duration
, xs:QName
, or xs:NOTATION
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.
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.
Returns a map that combines the entries from a number of existing maps.
map:merge (
|
$maps |
as map(*)* ,
|
$options |
as map(*) ) as map(*) |
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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:
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.
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.
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
.
The $options
argument can be used to control the way in which duplicate keys are handled.
The option parameter conventions apply.
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.
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.
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.
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.)
Returns the number of entries in the supplied map.
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
The function map:size
takes any map
as its $map
argument and returns the number of entries that are present
in the map.
The expression map:size(map{})
returns 0
.
The expression map:size(map{"true":1, "false":0})
returns 2
.
Returns a sequence containing all the keys present in a map
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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.
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.
The expression map:keys(map{1:"yes", 2:"no"})
returns some permutation of (1,2)
. (The result is in implementation-dependent order.)
Tests whether a supplied map contains an entry for a given key
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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.
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()
.
Returns the value associated with a supplied key in a given map.
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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.
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')
.
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.)
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.
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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:
let $MAP2 := map:remove($MAP, $KEY)
This returns a map in which all entries with the same key as $KEY
have been removed.
Construct and return a map containing:
All the entries (key/value pairs) in $MAP2
, and
The entry map:entry($KEY, $VALUE)
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.
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"}
.
Returns a map that contains a single entry (a key-value pair).
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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
.
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))
The expression map:entry("M", "Monday")
returns {"M":"Monday"}
.
Returns a map containing all the entries from a supplied map, except those having a specified key.
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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) } ) )
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"}
.
Applies a supplied function to every entry in a map, returning the concatenation of the results.
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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.
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.)
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"/>
.
Searches the supplied input sequence and any contained maps and arrays for a map entry with the supplied key, and returns the corresponding values.
This function is deterministicFO30, context-independentFO30, and focus-independentFO30.
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):
To process a sequence, process each of its items in order.
To process an item that is an array, process each of the array's members in order (each member is, in general, a sequence).
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:
If K is the same key as $key
,
then add V as a new member to the end of the result array.
Process V (which is, in general, a sequence).
To process an item that is neither a map nor an array, do nothing. (Such items are ignored).
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.
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":[]}], []]
.
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.
fn:collation-key (
|
$key |
as xs:string ,
|
$collation |
as xs:string ) as xs:base64Binary |
This function is deterministicFO30, context-dependentFO30, and focus-independentFO30. It depends on collations.
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.
An error is raised [ERR FOCH0004] FO31 if the specified collation does not support the generation of collation keys.
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.
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)}
.
fn:deep-equal
The deep-equal
FO30 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.
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.
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>
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>
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.
[52] | PrimaryExpr |
::= | Literal | VarRef | ParenthesizedExpr | ContextItemExpr | FunctionCall |
FunctionItemExpr |
|
[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.
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.
[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:
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']
.
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]
.
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]
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.
[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.
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")
.
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.
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.
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.
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:
Because the return
clause is motionless, the sweep of the let
expression is the sweep of the map
expression (the expression in curly brackets).
The sweep of a map expression is the maximum sweep of its key/value pairs.
For both key/value pairs, the key is motionless and the value is consuming.
The expression carefully atomizes both values, because retaining references to streamed nodes in a map is not permitted.
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
This section gives some examples of where maps can be useful.
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>
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>
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)">
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:
Flat structure:
<customer id="c123">...</customer> <product id="p789">...</product> <order customer="c123" product="p789">...</order>
Orders within customer elements:
<customer id="c123"> <order product="p789">...</order> </customer> <product id="p789">...</product>
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")(.))]