Hi list --
apologies if you've seen this message in other mailing lists or IRC. This isn't BaseX-specific, and so I hope that's not troublesome.
I'm trying to develop a better understanding of how to leverage the identity transform pattern (or whatever the best term would be) in XQuery. Based on some web searching, typeswitch seemed to be the recommended method; e.g. [1]. But I would run into problems with using `case element($node[@name='first') return local:do-something-specific-with-this($node)`. After some chat with Liam on IRC yesterday, he gave me the idea of trying a plain(er) `switch` but I couldn't quite work out the comparison of a local-name($node) to the atomized value, especially when I needed to evaluted an XPath expression as part of the `case`. Eventually I settled on if-then-else; e.g. [2]. It feels a little unwieldy.
Note that in my (typical) usecases I'm transforming entire documents, see [3] for example output.
I can't help but wonder though: it there a better way to handle this? Am I missing something obvious with the typeswitch method? I started with XSLT and I feel like maybe I'm doing something wrong :).
Thanks for any suggestions or pointers you can offer. Best, Bridger
PS Again, hopefully this isn't too off-topic for the list.
(: [1] identity transform with typeswitch :)
declare variable $input := <docs> <doc> <title>ABC: The Alphabet</title> <author>Some Person</author> <fields> <field name="first">First Field</field> <field name="second">Second Field</field> </fields> </doc> </docs>;
declare function local:passthru( $node as node()* ){ local:dispatch($node/node()) };
declare function local:dispatch( $nodes as node()* ) as item()* { for $node in $nodes return typeswitch($node) case text() return $node case element(docs) return local:passthru($node) case element(doc) return local:docf($node) case element(title) return local:title($node) case element(author) return local:author($node) case element(fields) return local:passthru($node) case element(field) return () (:return empty sequence:) default return local:passthru($node) };
declare function local:docf($node) { <new>{local:dispatch($node/node())}</new> };
declare function local:title($node) { <new-title>{local:dispatch($node/node())}</new-title> };
declare function local:author($node) { <new-author>{local:dispatch($node/node())}</new-author> };
local:dispatch($input)
(: [2] identity transform with if-then-else :)
declare function local:dispatch( $nodes as node()* ) as item()* { for $node in $nodes return( if ($node instance of text()) then $node/data() else (), if (fn:local-name($node) = 'docs') then local:passthru($node) else (), if (fn:local-name($node) = 'doc') then local:docf($node) else (), if (fn:local-name($node) = 'title') then local:title($node) else (), if (fn:local-name($node) = 'author') then local:author($node) else (), if (fn:local-name($node) = 'fields') then local:passthru($node) else (), if (fn:local-name($node) = 'field' and $node/@name='first') then local:field-1($node) else (), if (fn:local-name($node) = 'field' and $node/@name='second') then local:field-2($node) else () ) };
(: [3] sample output :) <new> <new-title>ABC: The Alphabet</new-title> <new-author>Some Person</new-author> <new-subject>First Field</new-subject> <new-section> <new-entry>Second Field</new-entry> </new-section> </new>
Hi Bridger,
Questions like this are welcome!
A switch approach, as suggested by Liam, might look as follows:
declare function local:dispatch( $nodes as node()* ) as item()* { for $node in $nodes return if (not($node instance of element())) then ( $node ) else switch(name($node)) case 'docs' return local:passthru($node) case 'doc' return local:docf($node) case 'title' return local:title($node) case 'author' return local:author($node) case 'fields' return local:passthru($node) case 'field' return ( if($node/@name='first') then local:field-1($node) else if($node/@name='second') then local:field-2($node) else () ) default return () };
The nested if conditions for the 'field' element could be rewritten to a switch clause as well.
In most cases, I tend to use XQuery for transforming existing XML nodes. The 'update' keyword gives you a concise syntax for updating existing nodes [1]:
declare %updating function local:rename($node, $name) { rename node $node as $name }; $input/* update { local:rename(., 'new'), .//(title, author) ! (rename node . as 'new-' || name()), insert node element new-subject { .//field[@name = 'first']/text() } into ., insert node element new-section { element new-entry { .//field[@name = 'second']/text() } } into ., delete node .//fields }
In this little example, I have accommodated various different ways to modify existing nodes (calling a function, using paths and the simple map expression, …). As you can easily see, the approach is pretty different to the identity transformation in XSLT: Instead of building a new document, you are updating existing nodes. This can be both very powerful and concise, because you can address and update descendant nodes with a simple path expression, but it is mostly appropriate if your old and new documents look similar.
If you want to stick with the official XQuery Update 1.0 syntax, you can also use the more verbose copy/modiy/return statements (see the same link).
Hope this helps, feel free to ask for more details, Christian
Hi Christian and all,
Something interesting about this is that I think I saw that a direct recursion like this in BaseX or Saxon is not suitable for tail call optimization. Or, I ran out of memory trying this once. I wonder if something like SAX for XQuery implemented in XQuery is a useful idea for an extreme case of needing an identity transform.
Kendall
On 2/16/18, 8:56 AM, "basex-talk-bounces@mailman.uni-konstanz.de on behalf of Christian Grün" <basex-talk-bounces@mailman.uni-konstanz.de on behalf of christian.gruen@gmail.com> wrote:
Hi Bridger,
Questions like this are welcome!
A switch approach, as suggested by Liam, might look as follows:
declare function local:dispatch( $nodes as node()* ) as item()* { for $node in $nodes return if (not($node instance of element())) then ( $node ) else switch(name($node)) case 'docs' return local:passthru($node) case 'doc' return local:docf($node) case 'title' return local:title($node) case 'author' return local:author($node) case 'fields' return local:passthru($node) case 'field' return ( if($node/@name='first') then local:field-1($node) else if($node/@name='second') then local:field-2($node) else () ) default return () };
The nested if conditions for the 'field' element could be rewritten to a switch clause as well.
In most cases, I tend to use XQuery for transforming existing XML nodes. The 'update' keyword gives you a concise syntax for updating existing nodes [1]:
declare %updating function local:rename($node, $name) { rename node $node as $name }; $input/* update { local:rename(., 'new'), .//(title, author) ! (rename node . as 'new-' || name()), insert node element new-subject { .//field[@name = 'first']/text() } into ., insert node element new-section { element new-entry { .//field[@name = 'second']/text() } } into ., delete node .//fields }
In this little example, I have accommodated various different ways to modify existing nodes (calling a function, using paths and the simple map expression, …). As you can easily see, the approach is pretty different to the identity transformation in XSLT: Instead of building a new document, you are updating existing nodes. This can be both very powerful and concise, because you can address and update descendant nodes with a simple path expression, but it is mostly appropriate if your old and new documents look similar.
If you want to stick with the official XQuery Update 1.0 syntax, you can also use the more verbose copy/modiy/return statements (see the same link).
Hope this helps, feel free to ask for more details, Christian
[1] https://urldefense.proofpoint.com/v2/url?u=http-3A__docs.basex.org_wiki_XQue...
Hi Kendall,
I saw that conversation over on talk@x-query; I'm not sure if I'll have a problem here - all of the documents in this case are short, and we'll be iterating over them individually. I wonder about longer documents, but I haven't had to deal with anything like that (yet).
Best, Bridger
On Fri, Feb 16, 2018 at 4:35 PM, Kendall Shaw kendall.shaw@workday.com wrote:
Hi Christian and all,
Something interesting about this is that I think I saw that a direct recursion like this in BaseX or Saxon is not suitable for tail call optimization. Or, I ran out of memory trying this once. I wonder if something like SAX for XQuery implemented in XQuery is a useful idea for an extreme case of needing an identity transform.
Kendall
On 2/16/18, 8:56 AM, "basex-talk-bounces@mailman.uni-konstanz.de on behalf of Christian Grün" <basex-talk-bounces@mailman.uni-konstanz.de on behalf of christian.gruen@gmail.com> wrote:
Hi Bridger, Questions like this are welcome! A switch approach, as suggested by Liam, might look as follows: declare function local:dispatch( $nodes as node()* ) as item()* { for $node in $nodes return if (not($node instance of element())) then ( $node ) else switch(name($node)) case 'docs' return local:passthru($node) case 'doc' return local:docf($node) case 'title' return local:title($node) case 'author' return local:author($node) case 'fields' return local:passthru($node) case 'field' return ( if($node/@name='first') then local:field-1($node) else if($node/@name='second') then local:field-2($node) else () ) default return () }; The nested if conditions for the 'field' element could be rewritten to a switch clause as well. In most cases, I tend to use XQuery for transforming existing XML nodes. The 'update' keyword gives you a concise syntax for updating existing nodes [1]: declare %updating function local:rename($node, $name) { rename node $node as $name }; $input/* update { local:rename(., 'new'), .//(title, author) ! (rename node . as 'new-' || name()), insert node element new-subject { .//field[@name = 'first']/text() } into ., insert node element new-section { element new-entry { .//field[@name = 'second']/text() } } into ., delete node .//fields } In this little example, I have accommodated various different ways to modify existing nodes (calling a function, using paths and the simple map expression, …). As you can easily see, the approach is pretty different to the identity transformation in XSLT: Instead of building a new document, you are updating existing nodes. This can be both very powerful and concise, because you can address and update descendant nodes with a simple path expression, but it is mostly appropriate if your old and new documents look similar. If you want to stick with the official XQuery Update 1.0 syntax, you can also use the more verbose copy/modiy/return statements (see the same link). Hope this helps, feel free to ask for more details, Christian [1] https://urldefense.proofpoint.com/v2/url?u=http-3A__docs.
basex.org_wiki_XQuery-5FUpdate-23update&d=DwIFaQ&c=DS6PUFBBr_ KiLo7Sjt3ljp5jaW5k2i9ijVXllEdOozc&r=JgwnBEpN1c-DDmq- Up2QMq9rrGyfWK0KtSpT7dxRglA&m=CoL2BpluuEvyW-WdbNgt2gSoIHO77nRzlVsE_agjRO4& s=zf5U1x9A37aU0FMwZDLFgizGEI8ALjn0uGST2cZGCdw&e=
Hi Christian -
belatedly, thank you very much! Your example helped me get on the right track, I think.
Cheers, Bridger
On Fri, Feb 16, 2018 at 11:56 AM, Christian Grün christian.gruen@gmail.com wrote:
Hi Bridger,
Questions like this are welcome!
A switch approach, as suggested by Liam, might look as follows:
declare function local:dispatch( $nodes as node()* ) as item()* { for $node in $nodes return if (not($node instance of element())) then ( $node ) else switch(name($node)) case 'docs' return local:passthru($node) case 'doc' return local:docf($node) case 'title' return local:title($node) case 'author' return local:author($node) case 'fields' return local:passthru($node) case 'field' return ( if($node/@name='first') then local:field-1($node) else if($node/@name='second') then local:field-2($node) else () ) default return () };
The nested if conditions for the 'field' element could be rewritten to a switch clause as well.
In most cases, I tend to use XQuery for transforming existing XML nodes. The 'update' keyword gives you a concise syntax for updating existing nodes [1]:
declare %updating function local:rename($node, $name) { rename node $node as $name }; $input/* update { local:rename(., 'new'), .//(title, author) ! (rename node . as 'new-' || name()), insert node element new-subject { .//field[@name = 'first']/text() } into ., insert node element new-section { element new-entry { .//field[@name = 'second']/text() } } into ., delete node .//fields }
In this little example, I have accommodated various different ways to modify existing nodes (calling a function, using paths and the simple map expression, …). As you can easily see, the approach is pretty different to the identity transformation in XSLT: Instead of building a new document, you are updating existing nodes. This can be both very powerful and concise, because you can address and update descendant nodes with a simple path expression, but it is mostly appropriate if your old and new documents look similar.
If you want to stick with the official XQuery Update 1.0 syntax, you can also use the more verbose copy/modiy/return statements (see the same link).
Hope this helps, feel free to ask for more details, Christian
basex-talk@mailman.uni-konstanz.de