saxon:deep-update
The saxon:deep-update
instruction is used to make changes to the content of a structure
of maps and arrays (which will typically have been obtained by applying the parse-json
or json-doc
function to JSON input).
Category: instruction
Content: none
Permitted parent elements:
any XSLT element whose content model is
sequence-constructor; any literal result element
Attributes
|
| An expression to select the root(s) of the tree(s) to be updated. |
|
| An expression to select the maps or arrays that are to be directly updated. |
|
| An expression to perform the update on the selected maps or arrays. |
Saxon availability
Requires Saxon-PE or Saxon-EE. Implemented since Saxon 9.9. Available for all platforms.
Details
The effect, in outline, is to return a value that is the same as the value selected by the root
expression, except that in the maps or arrays selected by the select
expression, the
specified action
has been applied.
More detailed rules:
The
root
attribute is an XPath expression that selects a map or array (call this the root item). It can also select a sequence of maps or arrays, in which case the same processing is applied to each of them, and the result is a new sequence that corresponds one-for-one with the old.The
select
attribute is an XPath expression that selects the maps or arrays to which updates are to be applied (call these the leaves: though there is no restriction on what they can contain).This expression must follow certain rules. It is evaluated with the root item as context item, and it must only select downwards from the root item using a defined set of operators. If the expression doesn't follow these rules, a dynamic error results indicating that something has been selected which does not have the root item in its pedigree.
The functions and operators that can be used to select downwards within this expression include:
map:get($M, $K)
and its equivalents$M($K)
or$M?($K)
array:get($A, $I)
and its equivalents$A($I)
or$A?($I)
$V?*
where $V is either a map or an arrayarray:subarray()
,array:remove()
,array:head()
,array:tail()
map:find()
The
action
attribute is an expression that is evaluated with each selected (leaf) map or array as context item, and it returns a new value (usually, but not necessarily, an updated map or array). The resulting map/array is then substituted for the original in its containing map/array, and so on recursively up to the root. The item that results from substituting at the root level is then returned in the result of thesaxon:deep-update
instruction.
Internally, the way this works is that the root item is marked as a map/array that retains pedigree information. When downwards selections are made from a map/array with pedigree information, any maps or arrays that are selected in the content will also be marked as having pedigree information: specifically, this information holds the item's containing map or array, and its key within this map or array. At the leaf level, when a map/array with pedigree information is replaced, its known container can be updated to hold the new item in place of the old. This updating then percolates upwards to the root of the pedigree, and the updated root is returned in the result of the instruction.
Examples
Consider a JSON text holding details of customers, orders, and order-lines like this:
[ { "customer": "Jones the Baker", "orders" : [{ "order-no": 123, "date": "2017-02-03", "order-lines": [ { "product": "flour, 25Kg" "quantity": 1 }, { "product": "salt, 1Kg" "quantity": 3 } ] }, { "order-no": 456, "date": "2017-05-12", "order-lines": [ { "product": "butter, 10Kg" "quantity": 2 }, { "product": "yeast, 100g" "quantity": 5 } ] }] }, { "customer": "Evans the Butcher", .... } ]And suppose we want to double the quantity of flour ordered by Jones. This can be done as follows:
<xsl:variable name="in" select="json-doc('customers.json')"/> <xsl:variable name="updated" as="array(*)"> <saxon:deep-update root = "$in" select = "?*[?customer = 'Jones the Baker'] ? orders?*[?order-no = 123] ? order-lines?*[contains(?product, 'flour')]" action = "map:put(., 'quantity', ?quantity * 2)"/> </xsl:variable> <xsl:value-of select="serialize($updated, map{'method':'json', 'indent':true()})"/>