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
JavaScript
"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;