UNPKG

fast-xml-parser

Version:

Validate XML or Parse XML to JS/JSON very fast without C/C++ based libraries

215 lines (194 loc) 6.98 kB
"use strict"; //parse Empty Node as self closing node const he = require("he"); const defaultOptions = { attributeNamePrefix: "@_", attrNodeName: false, textNodeName: "#text", ignoreAttributes: true, encodeHTMLchar: false, cdataTagName: false, cdataPositionChar: "\\c", format: false, indentBy: " ", supressEmptyNode: false }; function Parser(options) { this.options = Object.assign({}, defaultOptions, options); if (this.options.ignoreAttributes) { this.isAttribute = function(/*a*/) { return false;}; } else { this.attrPrefixLen = this.options.attributeNamePrefix.length; this.isAttribute = isAttribute; } if (this.options.cdataTagName) { this.isCDATA = isCDATA; } else { this.isCDATA = function(/*a*/) { return false;}; } this.replaceCDATAstr = replaceCDATAstr; this.replaceCDATAarr = replaceCDATAarr; if (this.options.encodeHTMLchar) { this.encodeHTMLchar = encodeHTMLchar; } else { this.encodeHTMLchar = function(a) { return a;}; } if (this.options.format) { this.indentate = indentate; this.tagEndChar = ">\n"; this.newLine = "\n"; } else { this.indentate = function() { return "";}; this.tagEndChar = ">"; this.newLine = ""; } if (this.options.supressEmptyNode) { this.buildTextNode = buildEmptyTextNode; this.buildObjNode = buildEmptyObjNode; } else { this.buildTextNode = buildTextValNode; this.buildObjNode = buildObjectNode; } this.buildTextValNode = buildTextValNode; this.buildObjectNode = buildObjectNode; } Parser.prototype.parse = function(jObj) { return this.j2x(jObj, 0).val; }; Parser.prototype.j2x = function(jObj, level) { let attrStr = ""; let val = ""; const keys = Object.keys(jObj); const len = keys.length; for (let i = 0; i < len; i++) { const key = keys[i]; if (typeof jObj[key] === "undefined") { // supress undefined node } else if (typeof jObj[key] !== "object") {//premitive type const attr = this.isAttribute(key); if (attr) { attrStr += " " + attr + "=\"" + this.encodeHTMLchar(jObj[key], true) + "\""; } else if (this.isCDATA(key)) { if (jObj[this.options.textNodeName]) { val += this.replaceCDATAstr(jObj[this.options.textNodeName], jObj[key]); } else { val += this.replaceCDATAstr("", jObj[key]); } } else {//tag value if (key === this.options.textNodeName) { if (jObj[this.options.cdataTagName]) { //value will added while processing cdata } else { val += this.encodeHTMLchar(jObj[key]); } } else { val += this.buildTextNode(jObj[key], key, "", level); } } } else if (Array.isArray(jObj[key])) {//repeated nodes if (this.isCDATA(key)) { if (jObj[this.options.textNodeName]) { val += this.replaceCDATAarr(jObj[this.options.textNodeName], jObj[key]); } else { val += this.replaceCDATAarr("", jObj[key]); } } else {//nested nodes const arrLen = jObj[key].length; for (let j = 0; j < arrLen; j++) { const item = jObj[key][j]; if (typeof item === "undefined") { // supress undefined node } else if (typeof item === "object") { const result = this.j2x(item, level + 1); val += this.buildObjNode(result.val, key, result.attrStr, level); } else { val += this.buildTextNode(item, key, "", level); } } } } else { if (this.options.attrNodeName && key === this.options.attrNodeName) { const Ks = Object.keys(jObj[key]); const L = Ks.length; for (let j = 0; j < L; j++) { attrStr += " " + Ks[j] + "=\"" + this.encodeHTMLchar(jObj[key][Ks[j]]) + "\""; } } else { const result = this.j2x(jObj[key], level + 1); val += this.buildObjNode(result.val, key, result.attrStr, level); } } } return {attrStr: attrStr, val: val}; }; function replaceCDATAstr(str, cdata) { str = this.encodeHTMLchar(str); if (this.options.cdataPositionChar === "" || str === "") { return str + "<![CDATA[" + cdata + "]]>"; } else { return str.replace(this.options.cdataPositionChar, "<![CDATA[" + cdata + "]]>"); } } function replaceCDATAarr(str, cdata) { str = this.encodeHTMLchar(str); if (this.options.cdataPositionChar === "" || str === "") { return str + "<![CDATA[" + cdata.join("]]><![CDATA[") + "]]>"; } else { for (const v in cdata) { str = str.replace(this.options.cdataPositionChar, "<![CDATA[" + cdata[v] + "]]>"); } return str; } } function buildObjectNode(val, key, attrStr, level) { return this.indentate(level) + "<" + key + attrStr + this.tagEndChar + val //+ this.newLine + this.indentate(level) + "</" + key + this.tagEndChar; } function buildEmptyObjNode(val, key, attrStr, level) { if (val !== "") { return this.buildObjectNode(val, key, attrStr, level); } else { return this.indentate(level) + "<" + key + attrStr + "/" + this.tagEndChar; //+ this.newLine } } function buildTextValNode(val, key, attrStr, level) { return this.indentate(level) + "<" + key + attrStr + ">" + this.encodeHTMLchar(val) + "</" + key + this.tagEndChar; } function buildEmptyTextNode(val, key, attrStr, level) { if (val !== "") { return this.buildTextValNode(val, key, attrStr, level); } else { return this.indentate(level) + "<" + key + attrStr + "/" + this.tagEndChar; } } function indentate(level) { return this.options.indentBy.repeat(level); } function encodeHTMLchar(val, isAttribute) { return he.encode("" + val, {isAttributeValue: isAttribute, useNamedReferences: true}); } function isAttribute(name/*, options*/) { if (name.startsWith(this.options.attributeNamePrefix)) { return name.substr(this.attrPrefixLen); } else { return false; } } function isCDATA(name) { return name === this.options.cdataTagName; } //formatting //indentation //\n after each closing or self closing tag module.exports = Parser;