Skip to content

Instantly share code, notes, and snippets.

@grtjn
Last active May 21, 2021 14:32
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 grtjn/0caa73d041cb30d12ca3022b11443beb to your computer and use it in GitHub Desktop.
Save grtjn/0caa73d041cb30d12ca3022b11443beb to your computer and use it in GitHub Desktop.
Fluent interface for MarkLogic NodeBuilder
'use strict';
// Fluent interface functions
function document(root) {
return function(b) {
return (b || new NodeBuilder()).addDocument(root);
}
}
function element(name, contents) {
return function(b) {
return (b || new NodeBuilder()).addElement(name, cb => {
if (!Array.isArray(contents)) {
contents = [contents];
}
return contents.reduce((cb, child) => child(cb), cb);
});
};
}
function attribute(name, contents) {
return function(b) {
return (b || new NodeBuilder()).addAttribute(name, '' + contents);
};
}
function text(contents) {
return function(b) {
return (b || new NodeBuilder()).addText(contents);
}
}
// Straight-forward method looks like this:
var b = new NodeBuilder();
b.startDocument();
b.startElement('names');
b.startElement('name');
b.addAttribute('id', '123');
b.addText('John');
b.endElement();
b.startElement('name');
b.addAttribute('id', '234');
b.addText('Patrick');
b.endElement();
b.startElement('name');
b.addAttribute('id', '345');
b.addText('Chris');
b.endElement();
b.endElement();
b.endDocument();
b.toNode();
// Original NodeBuilder combined with arrow-notation
new NodeBuilder()
.addDocument(b => {
b.addElement('names', b => {
b.addElement('name', b => {
b.addAttribute('id', '123')
.addText('John');
})
.addElement('name', b => {
b.addAttribute('id', '234')
.addText('Patrick');
})
.addElement('name', b => {
b.addAttribute('id', '345')
.addText('Chris');
});
});
})
.toNode();
// Fluent-notation approach:
document(
element('names', [
element('name', [
attribute('id', 123),
text('John')
]),
element('name', [
attribute('id', 234),
text('Patrick')
]),
element('name', [
attribute('id', 345),
text('Chris')
])
])
)().toNode();
@grtjn
Copy link
Author

grtjn commented Oct 4, 2019

This could be further extended by adding specializing element functions, for instance:

// Using specialized element functions

function names(contents) {
  return element('names', contents);
}

function name(id, contents) {
  return element('name', [attribute('id', id)].concat(contents));
}

document(
  names([
    name(123, text('John')),
    name(234, text('Patrick')),
    name(345, text('Chris'))
  ])
)().toNode();

@rbay85
Copy link

rbay85 commented May 21, 2021

What if I need

b.startElement('ex:example', 'http://example.com/ns1');

at line 37 above as in example https://docs.marklogic.com/NodeBuilder.startElement to get the node starting with

<ex:example xmlns:ex="http://example.com/ns1">
    Some element content here
</ex:example>

How to call the functions or edit them?
I tried like an attribute but didn't succeed. The error is:

[javascript] XDMP-UNBPRFX: return (b || new NodeBuilder()).addElement(name, cb => { -- Prefix tns has no namespace binding
Stack Trace
In /gi/dataservices/transform/transformPdiHeaderImpl.sjs on line 47 
In return (b || new NodeBuilder()).addElement(name, cb => {

links to line 13 of your example.

@grtjn
Copy link
Author

grtjn commented May 21, 2021

Yeah, you cannot insert attributes with xmlns prefix, those are reserved. addElement, and addAttribute allow a optional 3rd param that can be exposed on the fluent methods as well with only minor changes. You end up with something like:

'use strict';

// Fluent interface functions

function document(root) {
  return function(b) {
    return (b || new NodeBuilder()).addDocument(root);
  }
}

function element(name, contents, ns) {
  return function(b) {
    return (b || new NodeBuilder()).addElement(name, cb => {
      if (!Array.isArray(contents)) {
        contents = [contents];
      }
      return contents.reduce((cb, child) => child(cb), cb);
    }, ns);
  };
}

function attribute(name, contents, ns) {
  return function(b) {
    return (b || new NodeBuilder()).addAttribute(name, '' + contents, ns);
  };
}

function text(contents) {
  return function(b) {
    return (b || new NodeBuilder()).addText(contents);
  }
}

// Straight-forward method looks like this:
var b = new NodeBuilder();
b.startDocument();
b.startElement('names');
b.startElement('name');
b.addAttribute('id', '123');
b.addText('John');
b.endElement();
b.startElement('name');
b.addAttribute('id', '234');
b.addText('Patrick');
b.endElement();
b.startElement('name');
b.addAttribute('id', '345');
b.addText('Chris');
b.endElement();
b.endElement();
b.endDocument();
b.toNode();

// Original NodeBuilder combined with arrow-notation
new NodeBuilder()
 .addDocument(b => {
   b.addElement('names', b => {
     b.addElement('name', b => {
       b.addAttribute('id', '123')
         .addText('John');
     })
     .addElement('name', b => {
       b.addAttribute('id', '234')
         .addText('Patrick');
     })
     .addElement('name', b => {
       b.addAttribute('id', '345')
         .addText('Chris');
     });
   });
 })
 .toNode();

// Fluent-notation approach:
let ns = 'https://example.com/'
document(
  element('ex:names', [
    element('ex:name', [
      attribute('ex:id', 123, ns),
      text('John')
    ], ns),
    element('ex:name', [
      attribute('ex:id', 234, ns),
      text('Patrick')
    ], ns),
    element('ex:name', [
      attribute('ex:id', 345, ns),
      text('Chris')
    ], ns)
  ], ns)
)().toNode();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment