Skip to content
Ozgur Ozcitak edited this page Mar 26, 2020 · 63 revisions

Initialization

There are a few ways to start building the XML document, each with their own strengths and weaknesses. The most common API is the create function which provides some common defaults (XML declaration and the root node) and uses the full range of operations that is exposed by xmlbuilder. However, it keeps the entire XML tree in memory.

The second function is begin which is almost identical to create except it creates an empty document without any defaults.

The real advantage of begin arises when it is used with a callback function. In that case, XML nodes are written as they are created without keeping the XML tree in memory. However, it is not possible the traverse the document tree or modify already written nodes in this mode.

create()

An XML document is created by calling the create function of the module. All arguments and their default values are shown below.

var xmlbuilder = require('xmlbuilder');

var root = xmlbuilder.create('root',
                     {version: '1.0', encoding: 'UTF-8', standalone: true},
                     {pubID: null, sysID: null},
                     {keepNullNodes: false, keepNullAttributes: false, 
                      headless: false, ignoreDecorators: false,
                      separateArrayItems: false, noDoubleEncoding: false,
                      noValidation: false, invalidCharReplacement: undefined,
                      stringify: {}});

See this page for information on builder options.

The create function requires the name of the root node. It is not possible to add attributes to the root node here, but create can be chained with att to set root node properties. There are also a number of optional arguments for creating the prolog and document type declaration. XML version number will default to 1.0 if omitted. Prolog and document type declaration can be suppressed with the headless option. It is also possible to define the internal subset of the DTD. See this wiki page for details.

stringify provides a set of functions which tell XMLBuilder how to convert values into strings. For example, the following will prepend myns: to all node names:

var root = xmlbuilder.create('root', {
  stringify: {
    name: function(val) {
      return 'myns:' + val;
    }
  }
});

See this page for conversion details.

The create function will return the new root node.

begin()

A second export function begin is also available to start the XML document. With begin, an empty document is created without the root element or the XML prolog. It accepts the same options as create although options related the XML prolog are ignored.

var root = xmlbuilder.begin().ele('root', { 'rootAttribute': 'value' });

The begin function will return the new document node.

begin() with callback

begin can also be used with a callback function. In callback mode, the XML document will be generated in chunks and each chunk will be passed to the supplied function. In this mode, begin uses a different code path and the builder should use much less memory since the entire XML tree is not kept. There are a few drawbacks though. For example, traversing the document tree or adding attributes to a node after it is written is not possible. It is also not possible to remove nodes or attributes.

xmlbuilder.begin(function(chunk) { process.stdout.write(chunk); })
  .dec()
  .ele('root')
    .ele('xmlbuilder').up()
  .end();

IMPORTANT: This version of begin returns a callback document object where create (and the non-callback begin) would return XML node objects. Although the methods of this object uses the same method signatures, the objects returned are not XML nodes but the same callback document object instance whose state is altered with each call. Use this version of begin only if you are interested in the final XML document string. If you need to process intermediate nodes, use the create functions or the non-callback version of begin above.

Creating Child Nodes

Child nodes are created with the element function (can also be abbreviated to ele or e).

var ele = root.ele('child',
                   {'attribute': 'value',
                    'attribute2': 'value2'},
                   'node text');

Node attributes and text are optional and can be specified later with the att and txt functions.

There are also the insertBefore and insertAfter functions for creating child nodes. They accept the same arguments as the element function.

var ele = root.ele('third')
              .insertBefore('first')  // Insert the first node before third
              .insertAfter('second'); // Insert the second node after first

element, insertBefore and insertAfter will return the newly created child node.

Child nodes can also be created by passing an object to the element function. Here is an example:

var obj = {
  person: {
    name: "John",
    '@age': 35,
    address: {
      city: "Istanbul"
    },
    phone: [
      { '#text': "555-1234", '@type': 'home' }, 
      { '#text': "555-1235", '@type': 'mobile' }
    ],
    id: function() {
      return 42;
    }
  }
};

var ele = root.ele(obj);

See this wiki page for conversion details. When called with an object, element, insertBefore and insertAfter will return the last top level child node.

Child nodes can also be created with the node function (can also be abbreviated to nod or n). The difference between node and element is that node does not expand objects (i.e. its first argument must be a string).

Deleting Nodes

Child nodes are deleted with the remove function.

var ele = root.ele('delete me')
              .remove()
              .ele('child'));

remove will return its parent node.

Attributes

Node attributes are created with the attribute function (can also be abbreviated to att or a). attribute will create a new attribute or overwrite the attribute value if it already exists.

ele.att('attribute', 'value');

Attributes can be deleted with the removeAttribute function.

ele.removeAttribute('attribute');

att and removeAttribute will return the parent node.

Text Nodes

Text nodes are created with the text function (can also be abbreviated to txt or t).

ele.txt('node text');

txt will return its parent node.

Raw Text Nodes

Raw text nodes are created with the raw function (can also be abbreviated to r).

ele.raw('this will not be escaped');

raw will return its parent node.

CDATA Nodes

CDATA nodes are created with the cdata function (can also be abbreviated to dat or d). The value should not include CDATA delimiters

ele.dat('this will be surrounded by CDATA delimiters');

dat will return its parent node.

Comment Nodes

XML comments are created with the comment function (can also be abbreviated to com or c).

ele.com('some comment here');

There are also the commentBefore and commentAfter functions for creating comments. They accept the same arguments as the comment function.

com, commentBefore and commentAfter will return their parent node.

Processing instructions

XML processing instructions are created with the instruction function (can also be abbreviated to ins or i).

ele.ins('xml-stylesheet', 'type="text/xsl" href="style.xsl"');

There are also the instructionBefore and instructionAfter functions for processing instructions. They accept the same arguments as the instruction function.

ins, instructionBefore and instructionAfter will return their parent node.

instruction

instruction will append an instruction as a child node under its parent element node.

var foo = xmlbuilder.create('foo').instruction('bar', 'version="13.0"');
var xml = foo.end({ 'pretty': true });
<?xml version="1.0" encoding="utf-8"?>
<foo>
  <?bar version="13.0"?>
</foo>

instructionBefore

instructionBefore will add an instruction before its parent element node.

var foo = xmlbuilder.create('foo').instructionBefore('bar', 'version="13.0"');
var xml = foo.end({ 'pretty': true });
<?xml version="1.0" encoding="utf-8"?>
<?bar version="13.0"?>
<foo/>

instructionAfter

instructionAfter will add an instruction after its parent element node.

var foo = xmlbuilder.create('foo').instructionAfter('bar', 'version="13.0"');
var xml = foo.end({ 'pretty': true });
<?xml version="1.0" encoding="utf-8"?>
<foo/>
<?bar version="13.0"?>

Importing documents

Sometimes it is useful to generate XML fragments and later import them into a main document. The importDocument function takes the root node of an XML document and inserts it after the current node.

var peopleNode = xmlbuilder.create('roster').ele('people');

for (var i = 1; i <= 3; i++) {
  // Create an XML fragment
  person = xmlbuilder.create('person').att('id', i);
  // Import the root node of the fragment after
  // the people node of the main XML document
  peopleNode.importDocument(person);
}
<?xml version="1.0"?>
<roster>
  <people>
    <person id="1"/>
    <person id="2"/>
    <person id="3"/>
  </people>
</roster>

Traversing the Document Tree

Each call to ele will return the newly created child node. The up function provides a means to return back to the parent node after creating the child node (can also be abbreviated to u).

root.ele('child')
  .up() // we are back at the root node
  .txt('root node text');

There are also the prev and next functions which return the node before and after the current node. The root function can be used to get to the document's root node from anywhere in the document.

root.ele('first')
    .up()
    .ele('second')
    .prev() // returns first
    .next() // returns second
    .root(); // returns root

The doc function will return the XML document itself. doc can be called from anywhere in the document.

root.doc().end(); // Convert the entire document to string

Converting to String

Once the XML document is created, it can be converted to a string by calling the end function from anywhere in the document.

var xmlString = root.end({ 
  pretty: true,
  indent: '  ',
  newline: '\n',
  width: 0,
  allowEmpty: false,
  spacebeforeslash: ''
});

If allowEmpty is set, empty nodes will not be self-closed, for example:

root.end({ allowEmpty: true });
// with allowEmpty: true
<?xml version="1.0"?><root></root>
// with allowEmpty: false
<?xml version="1.0"?><root/>

If spacebeforeslash is set, the given space character(s) will be inserted before closing tags, for example:

root.end({ spacebeforeslash: ' ' });
// with spacebeforeslash set to a space char
<?xml version="1.0" ?><root />
// without spacebeforeslash
<?xml version="1.0"?><root/>

If width is set to a number greater than zero, element attributes are divided into multiple lines so that lines are not longer than width characters.

Instead of the entire XML file, fragments can be created by calling the toString function of a child node.

root.ele('child')
  .toString({ 
    pretty: true,
    indent: '  ',
    offset: 1,
    newline: '\n',
    spacebeforeslash: ''
  }); // convert only the child node to string

XML Writers

The end function uses the built-in XML writer for converting the XML document to string. Converter functions can be customized while calling end:

root.end({
  writer: {
    text: function(node, options, level) { /* customize the text function */ }
  }
});

See this page for more information on customizing the writer.

There is also a built-in stream writer that can be used as follows:

var xmlbuilder = require('xmlbuilder');
var writer = xmlbuilder.streamWriter(process.stdout); // pass a writable stream
var root = xmlbuilder.create('root'); // build the XML document
// ...
// ...
root.end(writer); // call end to pass the XML document to the stream writer

If you need to write your own XML writer, you can do so by creating an object that contains a function property named document which will receive the XML document when the end function is called.

function MyWriter() {
  this.document = function(doc) {
    // doc contains the XML document object.
    // Built-in writers contain functions to write the 
    // XML declaration, document type definition and
    // the root node which in turn writes child nodes
    // recursively.
  }
}

// Use the custom writer
var writer = new MyWriter();
var doc = xmlbuilder.create('root').end(writer);