UNPKG

easyxml

Version:

A configurable object to XML converter

257 lines (226 loc) 7.23 kB
'use strict'; var et = require('elementtree'); var inflect = require('inflect'); var ElementTree = et.ElementTree; var element = et.Element; var subElement = et.SubElement; /** * Instantiate a new EasyXml instance * * @param {Object} config * @constructor */ var EasyXml = function(config) { this.config = EasyXml.merge({ attributePrefix: '_', dateFormat: 'ISO', // ISO = ISO8601, SQL = MySQL Timestamp, JS = (new Date).toString() filterNulls: false, indent: 2, manifest: false, rootArray: 'items', rootElement: 'response', singularize: true, unwrapArrays: false }, config); }; EasyXml.ISO = 'ISO'; EasyXml.JS = 'JS'; EasyXml.SQL = 'SQL'; /** * Merges two objects and returns the result * * @param {Object} obj1 * @param {Object} obj2 * @returns {Object} Properties from obj1 and obj2 * @static */ EasyXml.merge = function(obj1, obj2) { var obj3 = {}; for (var attr1 in obj1) { if (Object.prototype.hasOwnProperty.call(obj1, attr1)) { obj3[attr1] = obj1[attr1]; } } for (var attr2 in obj2) { if (Object.prototype.hasOwnProperty.call(obj2, attr2)) { obj3[attr2] = obj2[attr2]; } } return obj3; }; /** * Pads a number so that it is two digits * * @param {Number} val * @returns {String} * @static */ EasyXml.zeroPadTen = function(val) { if (val < 10) { return "0" + val; } return val.toString(); }; /** * Should we bother parsing this child attribute * @param child * @returns {Boolean} */ EasyXml.isChildKeyParsed = function(child) { switch(typeof child) { case 'number': case 'string': case 'boolean': return false; default: // null, undefined, objects, functions return true; } }; /** * Takes an object and returns an XML string * * @param {Object|Array} object * @param {String} [rootElementOverride] * @returns {String} XML Document */ EasyXml.prototype.render = function(object, rootElementOverride) { var root; if (rootElementOverride) { root = rootElementOverride; } else if (object instanceof Array) { root = this.config.rootArray; } else { root = this.config.rootElement; } var xml = element(root); this.parseChildElement(xml, object); return new ElementTree(xml).write({ xml_declaration: this.config.manifest, indent: this.config.indent }); }; /** * Check if this item doesn't exist and if we should not render it * * @param child Attribute being checked * @returns {Boolean} */ EasyXml.prototype.filterNull = function(child) { return (child === null || child === undefined) && this.config.filterNulls === true; }; /** * Checks to see if the given key should be rendered as an attribute * * @param {String} key * @returns {Boolean} */ EasyXml.prototype.isAttribute = function(key) { return this.config.attributePrefix && key[0] === this.config.attributePrefix; }; /** * Takes an object and attaches it to the XML doc * * @param {Element} parentXmlNode * @param {Element} parentObjectNode * @retursive */ EasyXml.prototype.parseChildElement = function(parentXmlNode, parentObjectNode) { for (var key in parentObjectNode) { if (Object.prototype.hasOwnProperty.call(parentObjectNode, key)) { var child = parentObjectNode[key]; var el = null; if (this.filterNull(child)) { // no element if we are skipping nulls and undefined continue; } if (!isNaN(key)) { key = inflect.singularize(this.config.rootArray); } if (!this.isAttribute(key)) { el = subElement(parentXmlNode, key); } if (child === null || child === undefined) { // allow for both null child and undefined child el.text = ""; } else if (!this.config.singularize && typeof parentXmlNode === 'object' && typeof child === 'object') { for (var subkey in child) { if (Object.prototype.hasOwnProperty.call(child, subkey)) { if (EasyXml.isChildKeyParsed(child[subkey])) { this.parseChildElement(el, child[subkey]); } else { subElement(el, subkey).text = child[subkey].toString(); } } } } else if (this.isAttribute(key)) { if (typeof child === 'string' || typeof child === 'number') { if (key === this.config.attributePrefix) { parentXmlNode.text = child; } else { parentXmlNode.set(key.substring(1), child); } } else { throw new Error(key + "contained non_string_attribute"); } } else if (child instanceof Date) { // Date if (this.config.dateFormat === EasyXml.ISO) { // ISO: YYYY-MM-DDTHH:MM:SS.mmmZ el.text = child.toISOString(); } else if (this.config.dateFormat === EasyXml.SQL) { // SQL: YYYY-MM-DD HH:MM:SS var yyyy = child.getFullYear(); var mm = EasyXml.zeroPadTen(child.getMonth() + 1); var dd = EasyXml.zeroPadTen(child.getDate()); var hh = EasyXml.zeroPadTen(child.getHours()); var min = EasyXml.zeroPadTen(child.getMinutes()); var ss = EasyXml.zeroPadTen(child.getSeconds()); el.text = [yyyy, '-', mm, '-', dd, ' ', hh, ':', min, ':', ss].join(""); } else if (this.config.dateFormat === EasyXml.JS) { // JavaScript date format el.text = child.toString(); } else { throw new Error(key + "contained unknown_date_format"); } } else if (child instanceof Array) { // Array var subElementName = inflect.singularize(key); for (var key2 in child) { if (Object.prototype.hasOwnProperty.call(child, key2)) { if (this.filterNull(child[key2])) { continue; } // If unwrap arrays, make new subelements on the parent var el2 = this.config.unwrapArrays ? (el || subElement(parentXmlNode, key)) : (subElement(el, subElementName)); // Check type of child element if (Object.prototype.hasOwnProperty.call(child, key2) && EasyXml.isChildKeyParsed(child[key2])) { this.parseChildElement(el2, child[key2]); } else { // Just add element directly without parsing el2.text = child[key2].toString(); } // If unwrap arrays, the initial child element has been consumed if (this.config.unwrapArrays) el = undefined; } } } else if (typeof child === 'object') { if (typeof child.toJSON === 'function') { // .toJSON() is a common JS convention for serializing an object el.text = child.toJSON(); } else { // Object, go deeper this.parseChildElement(el, child); } } else if (typeof child === 'number' || typeof child === 'boolean') { el.text = child.toString(); /* istanbul ignore else */ } else if (typeof child === 'string') { el.text = child; } else { throw new Error(key + " contained unknown_data_type: " + typeof child); } } } }; module.exports = EasyXml;