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

Add an option on xsl:copy-of to copy a subtree with a change of namespace #266

Closed
michaelhkay opened this issue Nov 23, 2022 · 9 comments
Closed
Labels
Enhancement A change or improvement to an existing feature Propose Closing with No Action The WG should consider closing this issue with no action XSLT An issue related to XSLT

Comments

@michaelhkay
Copy link
Contributor

It's a common requirement to copy a subtree with a change of namespace.

It can be done easily enough in XSLT with apply-templates in a custom mode, but an option on xsl:copy-of could make it a lot easier. It could also potentially be a lot more efficient.

Alternatively, this could be provided as a function, or an option on the copy-of function.

Or it could be a new higher order function copy-renaming($node, function($name){ xs:QName('new uri', local-name-from-QName($name) }).

There's a danger of course of packing in too much functionality and making it just as complex/inefficient as using a custom mode.

@dnovatchev
Copy link
Contributor

dnovatchev commented Nov 24, 2022

1, It seems that something like a

fn:extract-to-no-namespace($subtree as element() ) as element() (: in no namespace :)
could be used as a common base of a chain of copying/renaming operations.

  1. Both this and the original proposal are meaningful if it is desired to put all elements from the given subtree in a single new namespace.

  2. We will need more sophisticated mechanism if the subtree contains elements in different namespaces and we only want to rename some (but not all) of the nodes that are in these namespaces.

4, Another issue is whether this function should be deterministic, or not.

Based on my memories from the period I was using XSLT more extensively.

@michaelhkay
Copy link
Contributor Author

This is what we used to call a "stereotype": we have a completely general mechanism with rich functionality that can do this job, but it takes 10 lines of code; we're looking for a one-liner to handle a common case. The tricky part is deciding how much power to pack into the one-liner short cut before people have to fall back to the more general mechanism. The idea of course is to make easy things dead simple while making harder things possible.

I think the sweet spot is probably a function that copies a subtree putting all elements into a defined namespace (or the null namespace) regardless of their original namespace. And I think I would go for an XSLT solution: an attribute xsl:copy-of/@new-namespace which, if present, puts all elements into the new namespace specified. But how to control both the namespace and the prefix?

@dnovatchev
Copy link
Contributor

dnovatchev commented Nov 24, 2022

And I think I would go for an XSLT solution: an attribute /@new-namespace which, if present, puts all elements into the new namespace specified. But how to control both the namespace and the prefix?

Maybe something like:

<xsl:copy-of select  ="." new-namespace="my-prefix:Q{WhateverMyNamespace}"/>

@ndw
Copy link
Contributor

ndw commented Nov 24, 2022

I think the common case is definitely "all elements into the new (maybe null) namespace". I assume we'd leave namespace qualified attributes alone.

I wonder if @new-prefix to go along with @new-namespace would be the best solution for the prefix. You could then use it just to change the prefix, I suppose, which might be just useful enough to justify two attributes.

@ndw ndw added XSLT An issue related to XSLT Enhancement A change or improvement to an existing feature labels Nov 26, 2022
@michaelhkay
Copy link
Contributor Author

By analogy with xsl:namespace-alias, perhaps

Perhaps <xsl:copy-of select ="." result-prefix="pfx"/> where pfx is either a prefix that is bound in the static context, or #default indicating the default namespace in the static context - which can of course be bound in the xsl:copy-of instruction itself.

I wouldn't have chosen to do xsl:namespace-alias this way, but it works, and consistency is valuable.

@michaelhkay
Copy link
Contributor Author

Perhaps a simpler and more powerful mechanism would be a rename-elements callback

<xsl:copy-of select  ="." 
                      rename-elements="function($name){QName(local-name-from-QName($name))}"/>

The function takes the source element name as input and returns the new element name to be used, both as xs:QName values. A rule will be needed for handling of any attributes that use the new prefix.

@michaelhkay
Copy link
Contributor Author

Rather than adding ad-hoc capability to xsl:copy-of, I'm wondering about doing something more radical.

We introduce an xsl:modify instruction, similar in concept to the copy-modify expression of XQuery Update, but with semantics defined entirely in terms of template rules. The xsl:modify instruction has as its children a set of modification rules; semantically these are abbreviated template rules, and the effect of xsl:modify is to apply-templates to its input, using a mode constructed from these rules.

So to take the original problem:

<xsl:modify select="/">
  <rename match="my:*" prefix="your" namespace="http://your-namespace/"/>
</xsl:modify>

has the effect of renaming all elements matching my:* to change their prefix and namespace (they keep the same local name because the local-name attribute is omitted). The semantics of rename are defined to be equivalent to a template rule something like:

<xsl:template match="my:*">
  <xsl:element name="{$prefix}:{$local-name}" namespace="{$namespace}">
    <xsl:apply-templates mode="#current"/>
  </xsl:element>
</xsl:template>

Other modification rules that can appear as children of xsl:modify include

<skip match="..."/> - does a shallow skip
<delete match="..."/> - does a deep skip
<copy-of match="..."/> - does a deep copy
<copy match="..."/> - does a shallow copy
<add-attributes match="..." select="..."/>
<insert match="..." position="before|after|first|last" select="..."/>

The default action is defined by an xsl:modify/@on-no-match attribute which takes the same values as on xsl:mode, but defaulting to shallow-copy. (Perhaps the rule names should be aligned with the on-no-match keywords.)

A sequence constructor can be used in place of the select attribute.

In this initial design, for simplicity, only one rule can fire for any node, and the priority order of the rules is order of appearance (last one wins).

The rules for simplified stylesheets are extended to allow xsl:modify as the root element of a stylesheet module.

We could extend the idea to allow modification of JSON (that is map/array) structures.

One potential problem with "stereotype" instructions like this is that you have to rewrite a lot of code when you step over the boundary of the functionality available within the stereotype. We can avoid that problem by allowing a modification rule that invokes another mode:

<apply-templates match="..." mode="mode-name"/>

Although I'm thinking of this feature primarily for usability benefits (it can make simple transformations a lot more concise and readable at the same time), there are also potential optimisation benefits because the entire set of rules is much more amenable to static analysis.

@michaelhkay
Copy link
Contributor Author

I believe this design could also solve the problem of deep update of maps/arrays - see issue #77 and various others.

<xsl:modify select="$json">
   <replace match="record(longitude, latitude)" select="map:put(., 'altitude', 0)"/>
</xsl:modify>

has the effect of adding an altitude field to every map (at any depth of the hierarchy) that matches record(longitude, latitude)

@ndw ndw added the Propose Closing with No Action The WG should consider closing this issue with no action label Jun 4, 2024
@ndw
Copy link
Contributor

ndw commented Jun 11, 2024

The CG agreed to close this issue without further action at meeting 081

@ndw ndw closed this as completed Jun 11, 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 Propose Closing with No Action The WG should consider closing this issue with no action XSLT An issue related to XSLT
Projects
None yet
Development

No branches or pull requests

3 participants