'use strict'; //parse Empty Node as self closing node const buildFromOrderedJs = require('./orderedJs2Xml'); const getIgnoreAttributesFn = require('../ignoreAttributes') const defaultOptions = { attributeNamePrefix: '@_', attributesGroupName: false, textNodeName: '#text', ignoreAttributes: true, cdataPropName: false, format: false, indentBy: ' ', suppressEmptyNode: false, suppressUnpairedNode: true, suppressBooleanAttributes: true, tagValueProcessor: function(key, a) { return a; }, attributeValueProcessor: function(attrName, a) { return a; }, preserveOrder: false, commentPropName: false, unpairedTags: [], entities: [ { regex: new RegExp("&", "g"), val: "&" },//it must be on top { regex: new RegExp(">", "g"), val: ">" }, { regex: new RegExp("<", "g"), val: "<" }, { regex: new RegExp("\'", "g"), val: "'" }, { regex: new RegExp("\"", "g"), val: """ } ], processEntities: true, stopNodes: [], // transformTagName: false, // transformAttributeName: false, oneListGroup: false }; function Builder(options) { this.options = Object.assign({}, defaultOptions, options); if (this.options.ignoreAttributes === true || this.options.attributesGroupName) { this.isAttribute = function(/*a*/) { return false; }; } else { this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes) this.attrPrefixLen = this.options.attributeNamePrefix.length; this.isAttribute = isAttribute; } this.processTextOrObjNode = processTextOrObjNode if (this.options.format) { this.indentate = indentate; this.tagEndChar = '>\n'; this.newLine = '\n'; } else { this.indentate = function() { return ''; }; this.tagEndChar = '>'; this.newLine = ''; } } Builder.prototype.build = function(jObj) { if(this.options.preserveOrder){ return buildFromOrderedJs(jObj, this.options); }else { if(Array.isArray(jObj) && this.options.arrayNodeName && this.options.arrayNodeName.length > 1){ jObj = { [this.options.arrayNodeName] : jObj } } return this.j2x(jObj, 0, []).val; } }; Builder.prototype.j2x = function(jObj, level, ajPath) { let attrStr = ''; let val = ''; const jPath = ajPath.join('.') for (let key in jObj) { if(!Object.prototype.hasOwnProperty.call(jObj, key)) continue; if (typeof jObj[key] === 'undefined') { // supress undefined node only if it is not an attribute if (this.isAttribute(key)) { val += ''; } } else if (jObj[key] === null) { // null attribute should be ignored by the attribute list, but should not cause the tag closing if (this.isAttribute(key)) { val += ''; } else if (key[0] === '?') { val += this.indentate(level) + '<' + key + '?' + this.tagEndChar; } else { val += this.indentate(level) + '<' + key + '/' + this.tagEndChar; } // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar; } else if (jObj[key] instanceof Date) { val += this.buildTextValNode(jObj[key], key, '', level); } else if (typeof jObj[key] !== 'object') { //premitive type const attr = this.isAttribute(key); if (attr && !this.ignoreAttributesFn(attr, jPath)) { attrStr += this.buildAttrPairStr(attr, '' + jObj[key]); } else if (!attr) { //tag value if (key === this.options.textNodeName) { let newval = this.options.tagValueProcessor(key, '' + jObj[key]); val += this.replaceEntitiesValue(newval); } else { val += this.buildTextValNode(jObj[key], key, '', level); } } } else if (Array.isArray(jObj[key])) { //repeated nodes const arrLen = jObj[key].length; let listTagVal = ""; let listTagAttr = ""; for (let j = 0; j < arrLen; j++) { const item = jObj[key][j]; if (typeof item === 'undefined') { // supress undefined node } else if (item === null) { if(key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar; else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar; // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar; } else if (typeof item === 'object') { if(this.options.oneListGroup){ const result = this.j2x(item, level + 1, ajPath.concat(key)); listTagVal += result.val; if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) { listTagAttr += result.attrStr } }else{ listTagVal += this.processTextOrObjNode(item, key, level, ajPath) } } else { if (this.options.oneListGroup) { let textValue = this.options.tagValueProcessor(key, item); textValue = this.replaceEntitiesValue(textValue); listTagVal += textValue; } else { listTagVal += this.buildTextValNode(item, key, '', level); } } } if(this.options.oneListGroup){ listTagVal = this.buildObjectNode(listTagVal, key, listTagAttr, level); } val += listTagVal; } else { //nested node if (this.options.attributesGroupName && key === this.options.attributesGroupName) { const Ks = Object.keys(jObj[key]); const L = Ks.length; for (let j = 0; j < L; j++) { attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]); } } else { val += this.processTextOrObjNode(jObj[key], key, level, ajPath) } } } return {attrStr: attrStr, val: val}; }; Builder.prototype.buildAttrPairStr = function(attrName, val){ val = this.options.attributeValueProcessor(attrName, '' + val); val = this.replaceEntitiesValue(val); if (this.options.suppressBooleanAttributes && val === "true") { return ' ' + attrName; } else return ' ' + attrName + '="' + val + '"'; } function processTextOrObjNode (object, key, level, ajPath) { const result = this.j2x(object, level + 1, ajPath.concat(key)); if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) { return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level); } else { return this.buildObjectNode(result.val, key, result.attrStr, level); } } Builder.prototype.buildObjectNode = function(val, key, attrStr, level) { if(val === ""){ if(key[0] === "?") return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar; else { return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar; } }else{ let tagEndExp = '' + val + tagEndExp ); } else if (this.options.commentPropName !== false && key === this.options.commentPropName && piClosingChar.length === 0) { return this.indentate(level) + `` + this.newLine; }else { return ( this.indentate(level) + '<' + key + attrStr + piClosingChar + this.tagEndChar + val + this.indentate(level) + tagEndExp ); } } } Builder.prototype.closeTag = function(key){ let closeTag = ""; if(this.options.unpairedTags.indexOf(key) !== -1){ //unpaired if(!this.options.suppressUnpairedNode) closeTag = "/" }else if(this.options.suppressEmptyNode){ //empty closeTag = "/"; }else{ closeTag = `>` + this.newLine; }else if (this.options.commentPropName !== false && key === this.options.commentPropName) { return this.indentate(level) + `` + this.newLine; }else if(key[0] === "?") {//PI tag return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar; }else{ let textValue = this.options.tagValueProcessor(key, val); textValue = this.replaceEntitiesValue(textValue); if( textValue === ''){ return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar; }else{ return this.indentate(level) + '<' + key + attrStr + '>' + textValue + ' 0 && this.options.processEntities){ for (let i=0; i