Skip to content

Commit

Permalink
map:put may use either the new or the old key
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelhkay committed Feb 4, 2025
1 parent 79052d6 commit 39fa65b
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 65 deletions.
57 changes: 32 additions & 25 deletions specifications/xpath-functions-40/src/function-catalog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23048,8 +23048,9 @@ xs:QName('xs:double')</eg></fos:result>
<item><p>The position of that entry in the <xtermref spec="DM40"
ref="dt-entry-order"/> of the result map will correspond to the
position of the first of the duplicates.</p></item>
<item><p>The key of that entry will be the key used
in the <emph>last</emph> of the duplicates. (Keys may be
<item><p>The key of the combined entry
will correspond to the key of one of the duplicates: it is
<termref def="implementation-dependent"/> which one is chosen. (Keys may be
duplicates even though they differ: for example, they may have
different type annotations, or they may be <code>xs:dateTime</code>
values in different timezones.)</p></item>
Expand All @@ -23062,7 +23063,7 @@ xs:QName('xs:double')</eg></fos:result>
</fos:rules>
<fos:equivalent style="xpath-expression">
map:of-pairs($maps =!> map:pairs(),
$options[exists((?duplicates, ?combine))]
$options[exists(?duplicates)]
otherwise { "duplicates": "use-first" });
</fos:equivalent>

Expand Down Expand Up @@ -23214,7 +23215,7 @@ map:of-pairs($maps =!> map:pairs(),
<fos:rules>

<p>The function <function>map:of-pairs</function>
<phrase>returns a map</phrase> that
<phrase>returns a map</phrase> which
is formed by combining <termref def="dt-key-value-pair-map">key-value pair maps</termref> supplied in the
<code>$input</code>
argument.</p>
Expand Down Expand Up @@ -23343,8 +23344,10 @@ return fold-left( $input, {},
of the result map, the position of the entry containing the result
of combining a set of entries with duplicate keys corresponds to
the position of the first of the duplicates in the input sequence.</p></item>
<item><p>The key of the entry containing the combined value is the <emph>last</emph> of
the several duplicates. (Keys may be duplicates even though they differ:
<item><p>The key of the combined entry
will correspond to the key of one of the duplicates: it is
<termref def="implementation-dependent"/> which one is chosen.
(Keys may be duplicates even though they differ:
for example they may have different type annotations, or they might be
<code>xs:dateTime</code> values in different timezones.)</p></item>
</ulist>
Expand Down Expand Up @@ -24120,20 +24123,18 @@ declare function map:find($input as item()*,
any existing entry for the same key.</p>
</fos:summary>
<fos:rules>
<p>The function <function>map:put</function> returns <phrase>a <termref def="dt-map"
>map</termref> that</phrase> contains all entries from the supplied <code>$map</code>,
with the exception of any entry whose key is the <termref
def="dt-same-key"
>same key</termref> as <code>$key</code>, together with a new
entry whose key is <code>$key</code> and whose associated value is <code>$value</code>.</p>
<p>If <code>$map</code> contains an entry whose key is the <termref
def="dt-same-key">same key</termref> as <code>$key</code>, the function returns
a map in which that entry is replaced (at the same relative position)
with a new entry whose value is <code>$value</code>. It is
<termref def="implementation-dependent"/> whether the key in the new entry
takes its original value or is replaced by the supplied <code>$key</code>.
All other entries in the map are unchanged, and retain their relative order.</p>

<p>The <xtermref spec="DM40" ref="dt-entry-order">entry order</xtermref>
of the entries in the returned map is as follows:
if <code>$map</code> contains an entry whose key is <code>$key</code>,
then the new value replaces the old value and the position of the entry is not changed;
otherwise, the new entry is added after all existing entries.</p>


<p>Otherwise, when <code>$map</code> contains no such entry, the function
returns a map containing all entries from the supplied <code>$map</code>
(retaining their relative position) followed by a new entry whose key
is <code>$key</code> and whose associated value is <code>$value</code>.</p>

</fos:rules>

Expand All @@ -24154,8 +24155,9 @@ declare function map:find($input as item()*,
as some existing key present in <code>$map</code>, but nevertheless
differs from the existing key in some way:
for example, it might have a different type annotation, or it might be an <code>xs:dateTime</code>
value in a different timezone. In this situation the key that appears in the result map
is always the supplied <code>$key</code>, not the existing key.</p>
value in a different timezone. In this situation it is
<termref def="implementation-dependent"/> whether the key that appears in the result map
is the supplied <code>$key</code> or the existing key.</p>

</fos:notes>

Expand Down Expand Up @@ -24191,7 +24193,12 @@ declare function map:find($input as item()*,
</fos:example>
</fos:examples>
<fos:changes>
<fos:change issue="1651" PR="1703" date="2025-01-14"><p>Enhanced to allow for ordered maps.</p></fos:change>
<fos:change issue="1651" PR="1703" date="2025-01-14">
<p>Enhanced to allow for ordered maps.</p>
</fos:change>
<fos:change issue="1725">
<p>It is no longer guaranteed that the new key replaces the existing key.</p>
</fos:change>
</fos:changes>
</fos:function>

Expand Down Expand Up @@ -24639,9 +24646,9 @@ else map:put($map, $key, $action(()))
<item><p>The position of the combined entry in the
<xtermref spec="DM40" ref="dt-entry-order"/> of the result map
will correspond to the position of the first of the duplicates.</p></item>
<item><p>The key of the combined entry in the
<xtermref spec="DM40" ref="dt-entry-order"/> of the result map
will correspond to the key of the <emph>last</emph> of the duplicates.
<item><p>The key of the combined entry
will correspond to the key of one of the duplicates: it is
<termref def="implementation-dependent"/> which one is chosen.
(It is possible for two keys to be considered duplicates even if they differ:
for example, they may have different type annotations, or they may
be <code>xs:dateTime</code> values in different timezones.) </p></item>
Expand Down
6 changes: 6 additions & 0 deletions specifications/xpath-functions-40/src/xpath-functions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13816,6 +13816,12 @@ ISBN 0 521 77752 6.</bibl>
longer possible to supply an instance of <code>xs:anyURI</code> or (when XPath 1.0 compatibility
mode is in force) an instance of <code>xs:boolean</code> or <code>xs:duration</code>.</p>
</item>
<item diff="add" at="issue1725">
<p>When <function>fn:put</function> replaces an entry in a map with a new value for an
existing key, in the case where the existing key and the new key differ (for example,
if they have different type annotations), it is no longer guaranteed that the new
entry includes the new key rather than the existing key.</p>
</item>
</olist>

<p>For compatibility issues regarding earlier versions, see the 3.1 version of this specification.</p>
Expand Down
2 changes: 1 addition & 1 deletion specifications/xslt-40/src/element-catalog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1702,7 +1702,7 @@
<e:attribute name="select">
<e:data-type name="expression"/>
</e:attribute>
<e:attribute name="on-duplicates" required="no" default="fn($a, $b) { error(xs:QName(err:XTDE3365)) }">
<e:attribute name="duplicates" required="no" default="fn($a, $b) { error(xs:QName(err:XTDE3365)) }">
<e:data-type name="expression"/>
</e:attribute>
<!--<e:attribute name="ordered" default="no">
Expand Down
5 changes: 2 additions & 3 deletions specifications/xslt-40/src/schema-for-xslt40.rnc
Original file line number Diff line number Diff line change
Expand Up @@ -1125,10 +1125,9 @@ map.element =
element map {
extension.atts,
global.atts,
sequence-constructor.model
select-or-sequence-constructor.model
}
# TODO: add @on-duplicates
# TODO: add @ordering
# TODO: add @duplicates

map-entry.element =
element map-entry {
Expand Down
8 changes: 3 additions & 5 deletions specifications/xslt-40/src/schema-for-xslt40.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -1128,11 +1128,9 @@ of problems processing the schema using various tools
substitutionGroup="xsl:instruction">
<xs:complexType>
<xs:complexContent mixed="true">
<xs:extension base="xsl:sequence-constructor">
<xs:attribute name="on-duplicates" type="xsl:expression"/>
<xs:attribute name="ordering" type="xsl:avt"/>
<xs:attribute name="_on-duplicates" type="xs:string"/>
<xs:attribute name="_ordering" type="xs:string"/>
<xs:extension base="xsl:sequence-constructor-and-select">
<xs:attribute name="duplicates" type="xsl:expression"/>
<xs:attribute name="_duplicates" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
Expand Down
71 changes: 40 additions & 31 deletions specifications/xslt-40/src/xslt.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36127,7 +36127,7 @@ the same group, and the-->

<changes>
<change issue="169" date="2023-11-28">
A new attribute <code>xsl:map/@on-duplicates</code> is available,
A new attribute <code>xsl:map/@duplicates</code> is available,
allowing control over how duplicate keys are handled by the <elcode>xsl:map</elcode>
instruction.
</change>
Expand All @@ -36139,25 +36139,26 @@ the same group, and the-->
and <code>fn:atomic-equal(<var>K</var>, <var>L</var>)</code> returns <code>true</code>.</p>

<p><error spec="XT" class="DE" code="3365" type="dynamic">
<p>In the absence of the <code>on-duplicates</code> attribute,
<p>In the absence of the <code>duplicates</code> attribute,
a <termref def="dt-dynamic-error">dynamic error</termref> occurs if the set of
keys in the maps making up the input sequence
<error.extra>of an <elcode>xsl:map</elcode> instruction</error.extra>
contains duplicates.</p>
</error></p>

<p>The result of evaluating the <code>on-duplicates</code> attribute, if present, <rfc2119>must</rfc2119>
be a function with arity 2. When the <elcode>xsl:map</elcode> instruction encounters two
map entries having the same key, the two values associated with this key are passed as
arguments to this function, and the function returns the value that should be associated
with this key in the final map.</p>

<p>More formally, the result of the <elcode>xsl:map</elcode> instruction is defined by reference to
the function <xfunction>map:merge</xfunction>. Specifically, if <code>$maps</code>
is the input sequence to <elcode>xsl:map</elcode>, and <code>$combine</code>
is the <termref def="dt-effective-value"/> of the <code>on-duplicates</code>
<p>The result of evaluating the <code>duplicates</code> attribute, if present, <rfc2119>must</rfc2119>
be either one of the strings <code>"use-first"</code>, <code>"use-last"</code>,
<code>"use-any"</code>, <code>"combine"</code>, or <code>"reject"</code>,
or a function with arity 2. These values correspond to the permitted
values of the <code>duplicates</code> option of the
<xfunction>map:of-pairs</xfunction> function.</p>

<p>The result of the <elcode>xsl:map</elcode> instruction is defined by reference to
the function <xfunction>map:of-pairs</xfunction>. Specifically, if <code>$maps</code>
is the input sequence to <elcode>xsl:map</elcode>, and <code>$duplicates</code>
is the <termref def="dt-effective-value"/> of the <code>duplicates</code>
attribute, then the result of the instruction is the result of the function
call <code>map:merge($maps, { "combine": $combine })</code>.</p>
call <code>map:of-pairs(map:pairs($maps), { "duplicates": $duplicates })</code>.</p>

<!--

Expand All @@ -36183,8 +36184,8 @@ the same group, and the-->
<p>Thus, if the values are all singleton items (which is not necessarily the case), and if the sequence
of values is <var>S</var>, then the final result is <code>fold-left(tail(S), head(S), F)</code>.</p>
-->
<p>For example, the following table shows some useful callback functions that might be supplied
as the value of the <code>on-duplicates</code> attribute, and explains their effect:</p>
<p>The following table shows some possible values
of the <code>duplicates</code> attribute, and explains their effect:</p>

<table>
<thead>
Expand All @@ -36195,41 +36196,47 @@ the same group, and the-->
</thead>
<tbody>
<tr>
<td><code>fn($a, $b) { $a }</code></td>
<td><code>duplicates="use-first"</code></td>
<td>The first of the duplicate values is used.</td>
</tr>
<tr>
<td><code>fn($a, $b) { $b }</code></td>
<td><code>duplicates="use-last"</code></td>
<td>The last of the duplicate values is used.</td>
</tr>
<tr>
<td><code>fn($a, $b) { $a, $b }</code></td>
<td><code>duplicates="combine"</code></td>
<td>The <xtermref spec="XP40" ref="dt-sequence-concatenation"/>
of the duplicate values is used. This could
also be expressed as <code>on-duplicates="op(',')"</code>.</td>
</tr>
<tr>
<td><code>fn($a, $b) { max(($a, $b)) }</code></td>
<td><code>duplicates="fn($a, $b) { max(($a, $b)) }"</code></td>
<td>The highest of the duplicate values is used.</td>
</tr>
<tr>
<td><code>fn($a, $b) { min(($a, $b)) }</code></td>
<td><code>duplicates="fn($a, $b) { min(($a, $b)) }"</code></td>
<td>The lowest of the duplicate values is used.</td>
</tr>
<tr>
<td><code>concat(?, ', ', ?) }</code></td>
<td><code>duplicates="concat(?, ', ', ?) }"</code></td>
<td>The comma-separated string concatenation of the duplicate values is used.</td>
</tr>
<tr diff="add" at="2023-04-04">
<td><code>fn($a, $b) { $a + $b }</code></td>
<td>The sum of the duplicate values is used.
This could also be expressed as <code>on-duplicates="op('+')"</code>
<tr>
<td><code>duplicates="op('+')"</code></td>
<td>The sum of the duplicate values is used.</td>
</tr>
<tr>
<td><code>duplicates="fn($a, $b) { subsequence(($a, $b), 1, 4) }"</code></td>
<td>The first four of the duplicates are retained; any further duplicates
are discarded.
</td>
</tr>
<tr>
<td><code>fn($a, $b) { error() }</code></td>
<td>Duplicates are rejected as an error (this is the default in the absence of the
<code>on-duplicates</code> attribute).</td>
<td><code>duplicates="fn($a, $b) { distinct-values(($a, $b)) }"</code></td>
<td>When multiple entries have the same key, the corresponding values
are retained only if they are distinct from other values having the
same key.
</td>
</tr>
</tbody>
</table>
Expand All @@ -36247,7 +36254,7 @@ the same group, and the-->
<eg><![CDATA[{ "A23": [ 12, 2 ], "A24": [ 5 ], "A23": [ 9 ] }]]></eg>
<p>The logic is:</p>
<eg><![CDATA[<xsl:template match="data">
<xsl:map on-duplicates="fn($a, $b) { array:join(($a, $b)) }">
<xsl:map duplicates="fn($a, $b) { array:join(($a, $b)) }">
<xsl:for-each select="event">
<xsl:map-entry key="@id" select="[xs:integer(@value)]"/>
</xsl:for-each>
Expand All @@ -36256,14 +36263,16 @@ the same group, and the-->
</example>

<note>
<p>Specifying the effect by reference to <xfunction>map:merge</xfunction> has
<p>Specifying the effect by reference to <xfunction>map:of-pairs</xfunction> has
the following consequences when duplicates are combined
into a merged entry:</p>
<ulist>
<item><p>The position of the merged entry in the result corresponds
to the position of the first of the duplicate keys in the input.</p></item>
<item><p>The key used for the merged entry in the result corresponds
to the last of the duplicate keys in the input. This is relevant when
to one of the duplicate keys in the input: it is
<termref def="dt-implementation-dependent"/> which one is chosen.
This is relevant when
the duplicate keys differ in some way, for example when they have
different type annotations, or when they are <code>xs:dateTime</code>
values in different timezones.</p></item>
Expand Down

0 comments on commit 39fa65b

Please sign in to comment.