Skip to content

Instantly share code, notes, and snippets.

@jmakeig
Last active April 17, 2018 12:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmakeig/7273340b3065f442030451d04587c466 to your computer and use it in GitHub Desktop.
Save jmakeig/7273340b3065f442030451d04587c466 to your computer and use it in GitHub Desktop.
Recursive descent identity transform in MarkLogic in JavaScript and XQuery
'use strict';
/**
* @param {Node} node
* @param {string} path
* @param {Object} [bindings={}]
* @return {Generator}
*/
function* xpath(node, path, bindings = {}) {
yield* node.xpath(path, bindings);
}
/**
* Force a singleton to be an Iterable of one.
*
* @param {any} value
* @return {Iterable}
*/
function makeIterable(value) {
if (value[Symbol.iterator]) return value;
return [value];
}
/**
* Recrusive descent transformation of one XML tree to another.
*
* @param {Node|Iterable<Node>} node
* @param {function} [visitor=identity]
* @return {Node}
*/
function transform(node, visitor = indentity) {
if (!(node instanceof Node)) return node;
if ('function' !== typeof visitor) throw new TypeError(typeof visitor);
/**
* @private
*
* @param {Node|Iterable<Node>} nodes
* @param {NodeBuilder} [builder=new NodeBuilder]
* @return {Node}
*/
function descend(nodes, builder = new NodeBuilder()) {
nodes = makeIterable(nodes);
const selector = './node()|./attribute()';
for (const nd of nodes) {
visitor(nd, builder, () => descend(xpath(nd, selector), builder));
}
return builder;
}
const trans = descend(node);
if (trans.toNode) return trans.toNode();
return trans;
}
/**
* Indentity transform. To provide your own transfromation logic, implement
* this function signature and pass the function instance as the `visitor`
* parameter of `transform()`.
*
* @param {Node} node - the current node being evaluated
* @param {NodeBuilder} builder - the active `NodeBuilder` instance shared among all nodes
* @param {function} next - process the current nodes’s children (via closure, don’t provide arguments)
* @return {undefined} - the state is maintained within the shared `builder`
*/
function indentity(node, builder, next) {
switch (node.nodeKind) {
case 'element':
builder.startElement(fn.name(node), fn.namespaceUri(node));
next();
builder.endElement();
break;
case 'text':
builder.addText(node.valueOf());
break;
case 'attribute':
builder.addAttribute(
fn.name(node),
node.valueOf(),
fn.namespaceUri(node)
);
break;
case 'document':
builder.startDocument();
next();
builder.endDocument();
break;
case 'comment':
case 'processing-instruction':
break;
default:
throw new TypeError(node.nodeKind);
}
}
const xml = fn.head(
xdmp.unquote(`<a xmlns="a" a="a">
<b>b <c>c</c> d</b>
<!-- comment here -->
<e xmlns="e" e="e"><f a:a="a" xmlns:a="a"/>e</e>
</a>`)
);
Sequence.from([transform(xml), xml]);
'use strict';
/**
* @param {Node} node
* @param {string} path
* @param {Object} [bindings={}]
* @return {Sequence<Node>}
*/
function xpath(node, path, bindings) {
if (undefined === bindings) bindings = {};
return node.xpath(path, bindings);
}
/**
* Force a singleton to be an Iterable of one.
*
* @param {any} value
* @return {Iterable}
*/
function makeIterable(value) {
if (value instanceof ValueIterator) {
return value.toArray();
}
return [].concat(value);
}
/**
* Recrusive descent transformation of one XML tree to another.
*
* @param {Node|Iterable<Node>} node
* @param {function} [visitor=identity]
* @return {Node}
*/
function transform(node, visitor) {
if (undefined === visitor) visitor = identity;
if (!(node instanceof Node)) return node;
if ('function' !== typeof visitor) throw new TypeError(typeof visitor);
/**
* @private
*
* @param {Node|Iterable<Node>} nodes
* @param {NodeBuilder} [builder=new NodeBuilder]
* @return {Node}
*/
function descend(nodes, builder) {
if (undefined === builder) builder = new NodeBuilder();
nodes = makeIterable(nodes);
var selector = './node()|./attribute()';
for (var i = 0; i < nodes.length; i++) {
var nd = nodes[i];
visitor(nd, builder, function() {
return descend(xpath(nd, selector), builder);
});
}
return builder;
}
var trans = descend(node);
if (trans.toNode) return trans.toNode();
return trans;
}
/**
* Indentity transform. To provide your own transfromation logic, implement
* this function signature and pass the function instance as the `visitor`
* parameter of `transform()`.
*
* @param {Node} node - the current node being evaluated
* @param {NodeBuilder} builder - the active `NodeBuilder` instance shared among all nodes
* @param {function} next - process the current nodes’s children (via closure, don’t provide arguments)
* @return {undefined} - the state is maintained within the shared `builder`
*/
function identity(node, builder, next) {
switch (node.nodeKind) {
case 'element':
builder.startElement(fn.name(node), fn.namespaceUri(node));
next();
builder.endElement();
break;
case 'text':
builder.addText(node.valueOf());
break;
case 'attribute':
builder.addAttribute(
fn.name(node),
node.valueOf(),
fn.namespaceUri(node)
);
break;
case 'document':
builder.startDocument();
next();
builder.endDocument();
break;
case 'comment':
case 'processing-instruction':
break;
default:
throw new TypeError(node.nodeKind);
}
}
var xml = fn.head(
xdmp.unquote(
'<a xmlns="a" a="a"><b>b <c>c</c> d</b><!-- comment here --><e xmlns="e" e="e"><f a:a="a" xmlns:a="a"/>e</e></a>'
)
);
xdmp.arrayValues([transform(xml), xml]);
declare namespace local = 'local';
declare function local:transform($nodes as node()*) as node()* {
for $n in $nodes return
typeswitch ($n)
(: case element(order:customer) return <order:customer id="{/cust:customer[cust:name = $n]/cust:id}">{string($n)}</order:customer>:)
case element() return element { fn:node-name($n) } { local:transform($n/node()|$n/attribute()) }
case attribute() return $n
case text() return $n
case comment() return $n
case document-node() return $n
default return local:transform($n/node())
};
let $xml := document {
<a xmlns="a" a="a">
<b>b <c>c</c> d</b>
<!-- comment here -->
<e xmlns="e" e="e"><f a:a="a" xmlns:a="a"/>e</e>
</a>
}
return (
local:transform($xml),
$xml
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment