Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XSLT on-no-match="shallow-copy-all" - revised rules #1238

Open
michaelhkay opened this issue May 23, 2024 · 3 comments
Open

XSLT on-no-match="shallow-copy-all" - revised rules #1238

michaelhkay opened this issue May 23, 2024 · 3 comments
Labels
Enhancement A change or improvement to an existing feature PRG-easy Categorized as "easy" at the Prague f2f, 2024 PRG-required Categorized as "required for 4.0" at the Prague f2f, 2024 XSLT An issue related to XSLT

Comments

@michaelhkay
Copy link
Contributor

michaelhkay commented May 23, 2024

The work on deep lookup with modifiers enables an improved set of rules for processing trees of maps and arrays using a mode with on-no-match="shallow-copy-all".

Recall that the intent is that if the user writes no template rules at all in such a mode, the effect is to recursively copy the entire structure without change. But it should be as easy as possible for the user to add template rules to override this processing for a selected part of the structure.

With this in mind, the proposed built-in rules are as follows:

For an array with no additional information available, we split it up into array members in a way that makes it possible to override
the processing for a specific array member:

<xsl:template match="array(*)">
  <xsl:array use="?member">
     <xsl:apply-templates select="for member $m at $pos in . 
                  return {"array-member":true(), "index": $pos, "member": $m}"
                                          mode="#current"/>
  </xsl:array>
</xsl:template>

The field 'array-member' here is a dummy, provided simply to make it easier to match these records at the next level of processing.

For the array members, when represented in this way, the items in the array member are processed one-by-one to produce
a new array member:

<xsl:template match="record(array-member as xs:boolean, index as xs:integer, member as item()*)">
     <xsl:map:entry key="'member'">
        <xsl:apply-templates select="for $item at $pos in ?member
                                                          return {"array-member-item":true(), 
                                                                       "index": ?index, 
                                                                       "member": ?member, 
                                                                       "item": $item,
                                                                       "position": $pos }"
                                            mode="#current"/>
    </xsl:map:entry>
</xsl:template>

The new array members are delivered as singleton maps, in the form expected by the match="array(*)" template given above.

For the individual items within each array member, the default is simply to apply-templates to the item:

<xsl:template match="record(array-member-item as ..., index as ..., member as ..., item as item(), position as ...)">
   <xsl:apply-templates select="?item" mode="#current"/>
</xsl:template>

Similarly for a map with no additional information, we reconstruct the map by applying templates to its individual entries:

<xsl:template match="map(*)">
  <xsl:map on-duplicates="op(',')">
     <xsl:apply-templates select="for entry ($k, $v) in . 
                                                        return {"map-entry":true(), "key": $k, "value": $v}"
                                          mode="#current"/>
  </xsl:array>
</xsl:template>

The built in processing for a map entry represented in this way is to reconstruct the map entry by applying templates to its individual items:

<xsl:template match="record(map-entry as xs:boolean, key as xs:anyAtomicType, value as item()*)">
     <xsl:map:entry key="$key">
        <xsl:apply-templates select="for $item at $pos in ?value
                                                          return {"map-entry-item":true(), 
                                                                       "key": ?key, 
                                                                       "value": ?value, 
                                                                       "item": $item,
                                                                       "position": $pos }"
                                            mode="#current"/>
    </xsl:map:entry>
</xsl:template>

For the individual items within each map entry, the default is simply to apply-templates to the item:

<xsl:template match="record(map-entry-item as ..., key as ..., value as ..., item as item(), position as ...)">
   <xsl:apply-templates select="?item" mode="#current"/>
</xsl:template>

And of course the fallback processing for items not in the above list is to return them unchanged:

<xsl:template match="item()">
   <xsl:sequence select="."/>
</xsl:template>

This allows user-written template rules to intervene at any of these levels, and to have access to contextual information about the item they are processing. For example to rename map entries with key "comment" to have key "note" instead, use:

<xsl:template match="record(map-entry, *)[?key = 'comment']">
   <xsl:map-entry key="'note'" select="?value"/>
</xsl:template>

There's one more refinement I would like, which is to provide access to the selection path for each map and array entry. I think this can be done by ensuring that within the template, items are labeled so that the function call selection-path(?value) or selection-path(?item) delivers the required result.

@michaelhkay michaelhkay added XSLT An issue related to XSLT Enhancement A change or improvement to an existing feature labels May 23, 2024
@michaelhkay
Copy link
Contributor Author

michaelhkay commented May 23, 2024

Note also the above proposes a change to the handling of any (XML) nodes found in the map/array structure. Currently these are processed as if by the on-no-match="shallow-copy" rule. The proposal changes this to move the nodes directly into the output structure without copying. Of course this can be modified by adding an explicit template rule that matches nodes; but moving them unchanged seems the right default.

This means it may be better to find a different name for this action. Not easy to find something that adequately summarises the behaviour. Perhaps on-no-match="traverse".

@michaelhkay
Copy link
Contributor Author

I also propose that we change the default for the select attribute of xsl:apply-templates. The current default of child::node() makes no sense when processing maps and arrays; and since in 3.0 using the default will always give an error when the context item is a map or array, I think we can safely change it.

The default when the context item is a map or array should be to use the select expressions that appear in the match="map(*)" and match="array(*) template rules shown above.

@Arithmeticus
Copy link
Contributor

I support this improvement. Is there some way we can abbreviate record(map-entry-item as ..., key as ..., value as ..., item as item(), position as ...) and its array counterpart to a reserved keyword (or the like), so we do not have to memorize this record type when writing templates for the contents of the map/array?

@ndw ndw added PRG-easy Categorized as "easy" at the Prague f2f, 2024 PRG-required Categorized as "required for 4.0" at the Prague f2f, 2024 labels Jun 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement A change or improvement to an existing feature PRG-easy Categorized as "easy" at the Prague f2f, 2024 PRG-required Categorized as "required for 4.0" at the Prague f2f, 2024 XSLT An issue related to XSLT
Projects
None yet
Development

No branches or pull requests

3 participants