2017-03-02 10:07:55 +01:00

242 lines
7.7 KiB
JavaScript

/*jslint node:true */
var sax = require('sax');
var expat /*= require('node-expat');*/ = {on: function () {}, parse: function () {}};
var common = require('./common');
var options;
var pureJsParser = 1; //true;
var currentElement;
function validateOptions (userOptions) {
options = common.copyOptions(userOptions);
common.ensureFlagExists('ignoreDeclaration', options);
common.ensureFlagExists('ignoreAttributes', options);
common.ensureFlagExists('ignoreText', options);
common.ensureFlagExists('ignoreComment', options);
common.ensureFlagExists('ignoreCdata', options);
common.ensureFlagExists('compact', options);
common.ensureFlagExists('alwaysChildren', options);
common.ensureFlagExists('addParent', options);
common.ensureFlagExists('trim', options);
common.ensureFlagExists('nativeType', options);
common.ensureFlagExists('sanitize', options);
common.ensureKeyExists('declaration', options);
common.ensureKeyExists('attributes', options);
common.ensureKeyExists('text', options);
common.ensureKeyExists('comment', options);
common.ensureKeyExists('cdata', options);
common.ensureKeyExists('type', options);
common.ensureKeyExists('name', options);
common.ensureKeyExists('elements', options);
common.ensureKeyExists('parent', options);
return options;
}
function nativeType (value) {
var nValue = Number(value);
if (!isNaN(nValue)) {
return nValue;
}
var bValue = value.toLowerCase();
if (bValue === 'true') {
return true;
} else if (bValue === 'false') {
return false;
}
return value;
}
function addField (type, value, options) {
if (options.compact) {
currentElement[options[type + 'Key']] = (currentElement[options[type + 'Key']] ? currentElement[options[type + 'Key']] + '\n' : '') + value;
} else {
if (!currentElement[options.elementsKey]) {
currentElement[options.elementsKey] = [];
}
var element = {};
element[options.typeKey] = type;
element[options[type + 'Key']] = value;
if (options.addParent) {
element[options.parentKey] = currentElement;
}
currentElement[options.elementsKey].push(element);
}
}
function onDeclaration (declaration) {
if (options.ignoreDeclaration) {
return;
}
if (currentElement[options.declarationKey]) {
return;
}
currentElement[options.declarationKey] = {};
while (declaration.body) {
var attribute = declaration.body.match(/([\w:-]+)\s*=\s*"([^"]*)"|'([^']*)'|(\w+)\s*/);
if (!attribute) {
break;
}
if (!currentElement[options.declarationKey][options.attributesKey]) {
currentElement[options.declarationKey][options.attributesKey] = {};
}
currentElement[options.declarationKey][options.attributesKey][attribute[1]] = attribute[2];
declaration.body = declaration.body.slice(attribute[0].length); // advance the string
}
if (options.addParent && options.compact) {
currentElement[options.declarationKey][options.parentKey] = currentElement;
}
//console.error('result[options.declarationKey]', result[options.declarationKey]);
}
function onStartElement (name, attributes) {
var key, element;
if (typeof name === 'object') {
attributes = name.attributes;
name = name.name;
}
if (options.trim && attributes) {
for (key in attributes) {
if (attributes.hasOwnProperty(key)) {
attributes[key] = attributes[key].trim();
}
}
}
if (options.compact) {
element = {};
if (!options.ignoreAttributes && attributes && Object.keys(attributes).length) {
element[options.attributesKey] = {};
for (key in attributes) {
if (attributes.hasOwnProperty(key)) {
element[options.attributesKey][key] = attributes[key];
}
}
}
element[options.parentKey] = currentElement;
if (!(name in currentElement)) {
currentElement[name] = element;
} else {
if (!(currentElement[name] instanceof Array)) {
currentElement[name] = [currentElement[name]];
}
currentElement[name].push(element);
}
currentElement = element;
} else {
if (!currentElement[options.elementsKey]) {
currentElement[options.elementsKey] = [];
}
element = {};
element[options.typeKey] = 'element';
element[options.nameKey] = name;
if (!options.ignoreAttributes && attributes && Object.keys(attributes).length) {
element[options.attributesKey] = attributes;
}
element[options.parentKey] = currentElement;
if (options.alwaysChildren) {
element[options.elementsKey] = [];
}
currentElement[options.elementsKey].push(element);
currentElement = element;
}
}
function onText (text) {
//console.log('currentElement:', currentElement);
if (options.ignoreText) {
return;
}
if (!text.trim()) {
return;
}
if (options.trim) {
text = text.trim();
}
if (options.nativeType) {
text = nativeType(text);
}
if (options.sanitize) {
text = common.sanitize(text);
}
addField('text', text, options);
}
function onComment (comment) {
if (options.ignoreComment) {
return;
}
if (options.trim) {
comment = comment.trim();
}
if (options.sanitize) {
comment = common.sanitize(comment);
}
addField('comment', comment, options);
}
function onEndElement (name) {
var parentElement = currentElement[options.parentKey];
if (!options.addParent) {
delete currentElement[options.parentKey];
}
currentElement = parentElement;
}
function onCdata (cdata) {
if (options.ignoreCdata) {
return;
}
if (options.trim) {
cdata = cdata.trim();
}
addField('cdata', cdata, options);
}
function onError (error) {
error.note = error; //console.error(error);
}
module.exports = function (xml, userOptions) {
var parser = pureJsParser ? sax.parser(true, {}) : parser = new expat.Parser('UTF-8');
var result = {};
currentElement = result;
options = validateOptions(userOptions);
if (pureJsParser) {
parser.onopentag = onStartElement;
parser.ontext = onText;
parser.oncomment = onComment;
parser.onclosetag = onEndElement;
parser.onerror = onError;
parser.oncdata = onCdata;
parser.onprocessinginstruction = onDeclaration;
} else {
parser.on('startElement', onStartElement);
parser.on('text', onText);
parser.on('comment', onComment);
parser.on('endElement', onEndElement);
parser.on('error', onError);
//parser.on('startCdata', onStartCdata);
//parser.on('endCdata', onEndCdata);
//parser.on('entityDecl', onEntityDecl);
}
if (pureJsParser) {
parser.write(xml).close();
} else {
if (!parser.parse(xml)) {
throw new Error('XML parsing error: ' + parser.getError());
}
}
if (result[options.elementsKey]) {
var temp = result[options.elementsKey];
delete result[options.elementsKey];
result[options.elementsKey] = temp;
delete result.text;
}
return result;
};