593 lines
20 KiB
Plaintext
593 lines
20 KiB
Plaintext
|
'use strict';
|
||
|
///@ts-check
|
||
|
|
||
|
const util = require('../util');
|
||
|
const xmlNode = require('./xmlNode');
|
||
|
const readDocType = require("./DocTypeReader");
|
||
|
const toNumber = require("strnum");
|
||
|
|
||
|
// const regx =
|
||
|
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
|
||
|
// .replace(/NAME/g, util.nameRegexp);
|
||
|
|
||
|
//const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
|
||
|
//const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
|
||
|
|
||
|
class OrderedObjParser{
|
||
|
constructor(options){
|
||
|
this.options = options;
|
||
|
this.currentNode = null;
|
||
|
this.tagsNodeStack = [];
|
||
|
this.docTypeEntities = {};
|
||
|
this.lastEntities = {
|
||
|
"apos" : { regex: /&(apos|#39|#x27);/g, val : "'"},
|
||
|
"gt" : { regex: /&(gt|#62|#x3E);/g, val : ">"},
|
||
|
"lt" : { regex: /&(lt|#60|#x3C);/g, val : "<"},
|
||
|
"quot" : { regex: /&(quot|#34|#x22);/g, val : "\""},
|
||
|
};
|
||
|
this.ampEntity = { regex: /&(amp|#38|#x26);/g, val : "&"};
|
||
|
this.htmlEntities = {
|
||
|
"space": { regex: /&(nbsp|#160);/g, val: " " },
|
||
|
// "lt" : { regex: /&(lt|#60);/g, val: "<" },
|
||
|
// "gt" : { regex: /&(gt|#62);/g, val: ">" },
|
||
|
// "amp" : { regex: /&(amp|#38);/g, val: "&" },
|
||
|
// "quot" : { regex: /&(quot|#34);/g, val: "\"" },
|
||
|
// "apos" : { regex: /&(apos|#39);/g, val: "'" },
|
||
|
"cent" : { regex: /&(cent|#162);/g, val: "¢" },
|
||
|
"pound" : { regex: /&(pound|#163);/g, val: "£" },
|
||
|
"yen" : { regex: /&(yen|#165);/g, val: "¥" },
|
||
|
"euro" : { regex: /&(euro|#8364);/g, val: "€" },
|
||
|
"copyright" : { regex: /&(copy|#169);/g, val: "©" },
|
||
|
"reg" : { regex: /&(reg|#174);/g, val: "®" },
|
||
|
"inr" : { regex: /&(inr|#8377);/g, val: "₹" },
|
||
|
};
|
||
|
this.addExternalEntities = addExternalEntities;
|
||
|
this.parseXml = parseXml;
|
||
|
this.parseTextData = parseTextData;
|
||
|
this.resolveNameSpace = resolveNameSpace;
|
||
|
this.buildAttributesMap = buildAttributesMap;
|
||
|
this.isItStopNode = isItStopNode;
|
||
|
this.replaceEntitiesValue = replaceEntitiesValue;
|
||
|
this.readStopNodeData = readStopNodeData;
|
||
|
this.saveTextToParentTag = saveTextToParentTag;
|
||
|
this.addChild = addChild;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function addExternalEntities(externalEntities){
|
||
|
const entKeys = Object.keys(externalEntities);
|
||
|
for (let i = 0; i < entKeys.length; i++) {
|
||
|
const ent = entKeys[i];
|
||
|
this.lastEntities[ent] = {
|
||
|
regex: new RegExp("&"+ent+";","g"),
|
||
|
val : externalEntities[ent]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {string} val
|
||
|
* @param {string} tagName
|
||
|
* @param {string} jPath
|
||
|
* @param {boolean} dontTrim
|
||
|
* @param {boolean} hasAttributes
|
||
|
* @param {boolean} isLeafNode
|
||
|
* @param {boolean} escapeEntities
|
||
|
*/
|
||
|
function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, escapeEntities) {
|
||
|
if (val !== undefined) {
|
||
|
if (this.options.trimValues && !dontTrim) {
|
||
|
val = val.trim();
|
||
|
}
|
||
|
if(val.length > 0){
|
||
|
if(!escapeEntities) val = this.replaceEntitiesValue(val);
|
||
|
|
||
|
const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
|
||
|
if(newval === null || newval === undefined){
|
||
|
//don't parse
|
||
|
return val;
|
||
|
}else if(typeof newval !== typeof val || newval !== val){
|
||
|
//overwrite
|
||
|
return newval;
|
||
|
}else if(this.options.trimValues){
|
||
|
return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
|
||
|
}else{
|
||
|
const trimmedVal = val.trim();
|
||
|
if(trimmedVal === val){
|
||
|
return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
|
||
|
}else{
|
||
|
return val;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function resolveNameSpace(tagname) {
|
||
|
if (this.options.removeNSPrefix) {
|
||
|
const tags = tagname.split(':');
|
||
|
const prefix = tagname.charAt(0) === '/' ? '/' : '';
|
||
|
if (tags[0] === 'xmlns') {
|
||
|
return '';
|
||
|
}
|
||
|
if (tags.length === 2) {
|
||
|
tagname = prefix + tags[1];
|
||
|
}
|
||
|
}
|
||
|
return tagname;
|
||
|
}
|
||
|
|
||
|
//TODO: change regex to capture NS
|
||
|
//const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
|
||
|
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
|
||
|
|
||
|
function buildAttributesMap(attrStr, jPath, tagName) {
|
||
|
if (!this.options.ignoreAttributes && typeof attrStr === 'string') {
|
||
|
// attrStr = attrStr.replace(/\r?\n/g, ' ');
|
||
|
//attrStr = attrStr || attrStr.trim();
|
||
|
|
||
|
const matches = util.getAllMatches(attrStr, attrsRegx);
|
||
|
const len = matches.length; //don't make it inline
|
||
|
const attrs = {};
|
||
|
for (let i = 0; i < len; i++) {
|
||
|
const attrName = this.resolveNameSpace(matches[i][1]);
|
||
|
let oldVal = matches[i][4];
|
||
|
let aName = this.options.attributeNamePrefix + attrName;
|
||
|
if (attrName.length) {
|
||
|
if (this.options.transformAttributeName) {
|
||
|
aName = this.options.transformAttributeName(aName);
|
||
|
}
|
||
|
if(aName === "__proto__") aName = "#__proto__";
|
||
|
if (oldVal !== undefined) {
|
||
|
if (this.options.trimValues) {
|
||
|
oldVal = oldVal.trim();
|
||
|
}
|
||
|
oldVal = this.replaceEntitiesValue(oldVal);
|
||
|
const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath);
|
||
|
if(newVal === null || newVal === undefined){
|
||
|
//don't parse
|
||
|
attrs[aName] = oldVal;
|
||
|
}else if(typeof newVal !== typeof oldVal || newVal !== oldVal){
|
||
|
//overwrite
|
||
|
attrs[aName] = newVal;
|
||
|
}else{
|
||
|
//parse
|
||
|
attrs[aName] = parseValue(
|
||
|
oldVal,
|
||
|
this.options.parseAttributeValue,
|
||
|
this.options.numberParseOptions
|
||
|
);
|
||
|
}
|
||
|
} else if (this.options.allowBooleanAttributes) {
|
||
|
attrs[aName] = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!Object.keys(attrs).length) {
|
||
|
return;
|
||
|
}
|
||
|
if (this.options.attributesGroupName) {
|
||
|
const attrCollection = {};
|
||
|
attrCollection[this.options.attributesGroupName] = attrs;
|
||
|
return attrCollection;
|
||
|
}
|
||
|
return attrs
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const parseXml = function(xmlData) {
|
||
|
xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
|
||
|
const xmlObj = new xmlNode('!xml');
|
||
|
let currentNode = xmlObj;
|
||
|
let textData = "";
|
||
|
let jPath = "";
|
||
|
for(let i=0; i< xmlData.length; i++){//for each char in XML data
|
||
|
const ch = xmlData[i];
|
||
|
if(ch === '<'){
|
||
|
// const nextIndex = i+1;
|
||
|
// const _2ndChar = xmlData[nextIndex];
|
||
|
if( xmlData[i+1] === '/') {//Closing Tag
|
||
|
const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
|
||
|
let tagName = xmlData.substring(i+2,closeIndex).trim();
|
||
|
|
||
|
if(this.options.removeNSPrefix){
|
||
|
const colonIndex = tagName.indexOf(":");
|
||
|
if(colonIndex !== -1){
|
||
|
tagName = tagName.substr(colonIndex+1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(this.options.transformTagName) {
|
||
|
tagName = this.options.transformTagName(tagName);
|
||
|
}
|
||
|
|
||
|
if(currentNode){
|
||
|
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
||
|
}
|
||
|
|
||
|
//check if last tag of nested tag was unpaired tag
|
||
|
const lastTagName = jPath.substring(jPath.lastIndexOf(".")+1);
|
||
|
if(tagName && this.options.unpairedTags.indexOf(tagName) !== -1 ){
|
||
|
throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
|
||
|
}
|
||
|
let propIndex = 0
|
||
|
if(lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1 ){
|
||
|
propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.')-1)
|
||
|
this.tagsNodeStack.pop();
|
||
|
}else{
|
||
|
propIndex = jPath.lastIndexOf(".");
|
||
|
}
|
||
|
jPath = jPath.substring(0, propIndex);
|
||
|
|
||
|
currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
|
||
|
textData = "";
|
||
|
i = closeIndex;
|
||
|
} else if( xmlData[i+1] === '?') {
|
||
|
|
||
|
let tagData = readTagExp(xmlData,i, false, "?>");
|
||
|
if(!tagData) throw new Error("Pi Tag is not closed.");
|
||
|
|
||
|
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
||
|
if( (this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags){
|
||
|
|
||
|
}else{
|
||
|
|
||
|
const childNode = new xmlNode(tagData.tagName);
|
||
|
childNode.add(this.options.textNodeName, "");
|
||
|
|
||
|
if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
|
||
|
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
|
||
|
}
|
||
|
this.addChild(currentNode, childNode, jPath)
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
i = tagData.closeIndex + 1;
|
||
|
} else if(xmlData.substr(i + 1, 3) === '!--') {
|
||
|
const endIndex = findClosingIndex(xmlData, "-->", i+4, "Comment is not closed.")
|
||
|
if(this.options.commentPropName){
|
||
|
const comment = xmlData.substring(i + 4, endIndex - 2);
|
||
|
|
||
|
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
||
|
|
||
|
currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
|
||
|
}
|
||
|
i = endIndex;
|
||
|
} else if( xmlData.substr(i + 1, 2) === '!D') {
|
||
|
const result = readDocType(xmlData, i);
|
||
|
this.docTypeEntities = result.entities;
|
||
|
i = result.i;
|
||
|
}else if(xmlData.substr(i + 1, 2) === '![') {
|
||
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
|
||
|
const tagExp = xmlData.substring(i + 9,closeIndex);
|
||
|
|
||
|
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
||
|
|
||
|
//cdata should be set even if it is 0 length string
|
||
|
if(this.options.cdataPropName){
|
||
|
// let val = this.parseTextData(tagExp, this.options.cdataPropName, jPath + "." + this.options.cdataPropName, true, false, true);
|
||
|
// if(!val) val = "";
|
||
|
currentNode.add(this.options.cdataPropName, [ { [this.options.textNodeName] : tagExp } ]);
|
||
|
}else{
|
||
|
let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true);
|
||
|
if(val == undefined) val = "";
|
||
|
currentNode.add(this.options.textNodeName, val);
|
||
|
}
|
||
|
|
||
|
i = closeIndex + 2;
|
||
|
}else {//Opening tag
|
||
|
let result = readTagExp(xmlData,i, this.options.removeNSPrefix);
|
||
|
let tagName= result.tagName;
|
||
|
const rawTagName = result.rawTagName;
|
||
|
let tagExp = result.tagExp;
|
||
|
let attrExpPresent = result.attrExpPresent;
|
||
|
let closeIndex = result.closeIndex;
|
||
|
|
||
|
if (this.options.transformTagName) {
|
||
|
tagName = this.options.transformTagName(tagName);
|
||
|
}
|
||
|
|
||
|
//save text as child node
|
||
|
if (currentNode && textData) {
|
||
|
if(currentNode.tagname !== '!xml'){
|
||
|
//when nested tag is found
|
||
|
textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//check if last tag was unpaired tag
|
||
|
const lastTag = currentNode;
|
||
|
if(lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1 ){
|
||
|
currentNode = this.tagsNodeStack.pop();
|
||
|
jPath = jPath.substring(0, jPath.lastIndexOf("."));
|
||
|
}
|
||
|
if(tagName !== xmlObj.tagname){
|
||
|
jPath += jPath ? "." + tagName : tagName;
|
||
|
}
|
||
|
if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
|
||
|
let tagContent = "";
|
||
|
//self-closing tag
|
||
|
if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
|
||
|
i = result.closeIndex;
|
||
|
}
|
||
|
//unpaired tag
|
||
|
else if(this.options.unpairedTags.indexOf(tagName) !== -1){
|
||
|
i = result.closeIndex;
|
||
|
}
|
||
|
//normal tag
|
||
|
else{
|
||
|
//read until closing tag is found
|
||
|
const result = this.readStopNodeData(xmlData, rawTagName, closeIndex + 1);
|
||
|
if(!result) throw new Error(`Unexpected end of ${rawTagName}`);
|
||
|
i = result.i;
|
||
|
tagContent = result.tagContent;
|
||
|
}
|
||
|
|
||
|
const childNode = new xmlNode(tagName);
|
||
|
if(tagName !== tagExp && attrExpPresent){
|
||
|
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
|
||
|
}
|
||
|
if(tagContent) {
|
||
|
tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
|
||
|
}
|
||
|
|
||
|
jPath = jPath.substr(0, jPath.lastIndexOf("."));
|
||
|
childNode.add(this.options.textNodeName, tagContent);
|
||
|
|
||
|
this.addChild(currentNode, childNode, jPath)
|
||
|
}else{
|
||
|
//selfClosing tag
|
||
|
if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
|
||
|
if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
|
||
|
tagName = tagName.substr(0, tagName.length - 1);
|
||
|
jPath = jPath.substr(0, jPath.length - 1);
|
||
|
tagExp = tagName;
|
||
|
}else{
|
||
|
tagExp = tagExp.substr(0, tagExp.length - 1);
|
||
|
}
|
||
|
|
||
|
if(this.options.transformTagName) {
|
||
|
tagName = this.options.transformTagName(tagName);
|
||
|
}
|
||
|
|
||
|
const childNode = new xmlNode(tagName);
|
||
|
if(tagName !== tagExp && attrExpPresent){
|
||
|
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
|
||
|
}
|
||
|
this.addChild(currentNode, childNode, jPath)
|
||
|
jPath = jPath.substr(0, jPath.lastIndexOf("."));
|
||
|
}
|
||
|
//opening tag
|
||
|
else{
|
||
|
const childNode = new xmlNode( tagName);
|
||
|
this.tagsNodeStack.push(currentNode);
|
||
|
|
||
|
if(tagName !== tagExp && attrExpPresent){
|
||
|
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
|
||
|
}
|
||
|
this.addChild(currentNode, childNode, jPath)
|
||
|
currentNode = childNode;
|
||
|
}
|
||
|
textData = "";
|
||
|
i = closeIndex;
|
||
|
}
|
||
|
}
|
||
|
}else{
|
||
|
textData += xmlData[i];
|
||
|
}
|
||
|
}
|
||
|
return xmlObj.child;
|
||
|
}
|
||
|
|
||
|
function addChild(currentNode, childNode, jPath){
|
||
|
const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
|
||
|
if(result === false){
|
||
|
}else if(typeof result === "string"){
|
||
|
childNode.tagname = result
|
||
|
currentNode.addChild(childNode);
|
||
|
}else{
|
||
|
currentNode.addChild(childNode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const replaceEntitiesValue = function(val){
|
||
|
|
||
|
if(this.options.processEntities){
|
||
|
for(let entityName in this.docTypeEntities){
|
||
|
const entity = this.docTypeEntities[entityName];
|
||
|
val = val.replace( entity.regx, entity.val);
|
||
|
}
|
||
|
for(let entityName in this.lastEntities){
|
||
|
const entity = this.lastEntities[entityName];
|
||
|
val = val.replace( entity.regex, entity.val);
|
||
|
}
|
||
|
if(this.options.htmlEntities){
|
||
|
for(let entityName in this.htmlEntities){
|
||
|
const entity = this.htmlEntities[entityName];
|
||
|
val = val.replace( entity.regex, entity.val);
|
||
|
}
|
||
|
}
|
||
|
val = val.replace( this.ampEntity.regex, this.ampEntity.val);
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
|
||
|
if (textData) { //store previously collected data as textNode
|
||
|
if(isLeafNode === undefined) isLeafNode = Object.keys(currentNode.child).length === 0
|
||
|
|
||
|
textData = this.parseTextData(textData,
|
||
|
currentNode.tagname,
|
||
|
jPath,
|
||
|
false,
|
||
|
currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false,
|
||
|
isLeafNode);
|
||
|
|
||
|
if (textData !== undefined && textData !== "")
|
||
|
currentNode.add(this.options.textNodeName, textData);
|
||
|
textData = "";
|
||
|
}
|
||
|
return textData;
|
||
|
}
|
||
|
|
||
|
//TODO: use jPath to simplify the logic
|
||
|
/**
|
||
|
*
|
||
|
* @param {string[]} stopNodes
|
||
|
* @param {string} jPath
|
||
|
* @param {string} currentTagName
|
||
|
*/
|
||
|
function isItStopNode(stopNodes, jPath, currentTagName){
|
||
|
const allNodesExp = "*." + currentTagName;
|
||
|
for (const stopNodePath in stopNodes) {
|
||
|
const stopNodeExp = stopNodes[stopNodePath];
|
||
|
if( allNodesExp === stopNodeExp || jPath === stopNodeExp ) return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the tag Expression and where it is ending handling single-double quotes situation
|
||
|
* @param {string} xmlData
|
||
|
* @param {number} i starting index
|
||
|
* @returns
|
||
|
*/
|
||
|
function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
|
||
|
let attrBoundary;
|
||
|
let tagExp = "";
|
||
|
for (let index = i; index < xmlData.length; index++) {
|
||
|
let ch = xmlData[index];
|
||
|
if (attrBoundary) {
|
||
|
if (ch === attrBoundary) attrBoundary = "";//reset
|
||
|
} else if (ch === '"' || ch === "'") {
|
||
|
attrBoundary = ch;
|
||
|
} else if (ch === closingChar[0]) {
|
||
|
if(closingChar[1]){
|
||
|
if(xmlData[index + 1] === closingChar[1]){
|
||
|
return {
|
||
|
data: tagExp,
|
||
|
index: index
|
||
|
}
|
||
|
}
|
||
|
}else{
|
||
|
return {
|
||
|
data: tagExp,
|
||
|
index: index
|
||
|
}
|
||
|
}
|
||
|
} else if (ch === '\t') {
|
||
|
ch = " "
|
||
|
}
|
||
|
tagExp += ch;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function findClosingIndex(xmlData, str, i, errMsg){
|
||
|
const closingIndex = xmlData.indexOf(str, i);
|
||
|
if(closingIndex === -1){
|
||
|
throw new Error(errMsg)
|
||
|
}else{
|
||
|
return closingIndex + str.length - 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
|
||
|
const result = tagExpWithClosingIndex(xmlData, i+1, closingChar);
|
||
|
if(!result) return;
|
||
|
let tagExp = result.data;
|
||
|
const closeIndex = result.index;
|
||
|
const separatorIndex = tagExp.search(/\s/);
|
||
|
let tagName = tagExp;
|
||
|
let attrExpPresent = true;
|
||
|
if(separatorIndex !== -1){//separate tag name and attributes expression
|
||
|
tagName = tagExp.substring(0, separatorIndex);
|
||
|
tagExp = tagExp.substring(separatorIndex + 1).trimStart();
|
||
|
}
|
||
|
|
||
|
const rawTagName = tagName;
|
||
|
if(removeNSPrefix){
|
||
|
const colonIndex = tagName.indexOf(":");
|
||
|
if(colonIndex !== -1){
|
||
|
tagName = tagName.substr(colonIndex+1);
|
||
|
attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
tagName: tagName,
|
||
|
tagExp: tagExp,
|
||
|
closeIndex: closeIndex,
|
||
|
attrExpPresent: attrExpPresent,
|
||
|
rawTagName: rawTagName,
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* find paired tag for a stop node
|
||
|
* @param {string} xmlData
|
||
|
* @param {string} tagName
|
||
|
* @param {number} i
|
||
|
*/
|
||
|
function readStopNodeData(xmlData, tagName, i){
|
||
|
const startIndex = i;
|
||
|
// Starting at 1 since we already have an open tag
|
||
|
let openTagCount = 1;
|
||
|
|
||
|
for (; i < xmlData.length; i++) {
|
||
|
if( xmlData[i] === "<"){
|
||
|
if (xmlData[i+1] === "/") {//close tag
|
||
|
const closeIndex = findClosingIndex(xmlData, ">", i, `${tagName} is not closed`);
|
||
|
let closeTagName = xmlData.substring(i+2,closeIndex).trim();
|
||
|
if(closeTagName === tagName){
|
||
|
openTagCount--;
|
||
|
if (openTagCount === 0) {
|
||
|
return {
|
||
|
tagContent: xmlData.substring(startIndex, i),
|
||
|
i : closeIndex
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
i=closeIndex;
|
||
|
} else if(xmlData[i+1] === '?') {
|
||
|
const closeIndex = findClosingIndex(xmlData, "?>", i+1, "StopNode is not closed.")
|
||
|
i=closeIndex;
|
||
|
} else if(xmlData.substr(i + 1, 3) === '!--') {
|
||
|
const closeIndex = findClosingIndex(xmlData, "-->", i+3, "StopNode is not closed.")
|
||
|
i=closeIndex;
|
||
|
} else if(xmlData.substr(i + 1, 2) === '![') {
|
||
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
|
||
|
i=closeIndex;
|
||
|
} else {
|
||
|
const tagData = readTagExp(xmlData, i, '>')
|
||
|
|
||
|
if (tagData) {
|
||
|
const openTagName = tagData && tagData.tagName;
|
||
|
if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
|
||
|
openTagCount++;
|
||
|
}
|
||
|
i=tagData.closeIndex;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}//end for loop
|
||
|
}
|
||
|
|
||
|
function parseValue(val, shouldParse, options) {
|
||
|
if (shouldParse && typeof val === 'string') {
|
||
|
//console.log(options)
|
||
|
const newval = val.trim();
|
||
|
if(newval === 'true' ) return true;
|
||
|
else if(newval === 'false' ) return false;
|
||
|
else return toNumber(val, options);
|
||
|
} else {
|
||
|
if (util.isExist(val)) {
|
||
|
return val;
|
||
|
} else {
|
||
|
return '';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
module.exports = OrderedObjParser;
|