/*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; };