/** * Module dependencies. */ var deprecate = require('util-deprecate'); var DOMParser = require('xmldom').DOMParser; /** * Module exports. */ exports.parse = parse; exports.parseString = deprecate(parseString, '`parseString()` is deprecated. ' + 'It\'s not actually async. Use `parse()` instead.'); exports.parseStringSync = deprecate(parseStringSync, '`parseStringSync()` is ' + 'deprecated. Use `parse()` instead.'); /** * We ignore raw text (usually whitespace), , * and raw CDATA nodes. * * @param {Element} node * @returns {Boolean} * @api private */ function shouldIgnoreNode (node) { return node.nodeType === 3 // text || node.nodeType === 8 // comment || node.nodeType === 4; // cdata } /** * Parses a Plist XML string. Returns an Object. * * @param {String} xml - the XML String to decode * @returns {Mixed} the decoded value from the Plist XML * @api public */ function parse (xml) { var doc = new DOMParser().parseFromString(xml); if (doc.documentElement.nodeName !== 'plist') { throw new Error('malformed document. First element should be '); } var plist = parsePlistXML(doc.documentElement); // the root node gets interpreted as an Array, // so pull out the inner data first if (plist.length == 1) plist = plist[0]; return plist; } /** * Parses a Plist XML string. Returns an Object. Takes a `callback` function. * * @param {String} xml - the XML String to decode * @param {Function} callback - callback function * @returns {Mixed} the decoded value from the Plist XML * @api public * @deprecated not actually async. use parse() instead */ function parseString (xml, callback) { var doc, error, plist; try { doc = new DOMParser().parseFromString(xml); plist = parsePlistXML(doc.documentElement); } catch(e) { error = e; } callback(error, plist); } /** * Parses a Plist XML string. Returns an Object. * * @param {String} xml - the XML String to decode * @param {Function} callback - callback function * @returns {Mixed} the decoded value from the Plist XML * @api public * @deprecated use parse() instead */ function parseStringSync (xml) { var doc = new DOMParser().parseFromString(xml); var plist; if (doc.documentElement.nodeName !== 'plist') { throw new Error('malformed document. First element should be '); } plist = parsePlistXML(doc.documentElement); // if the plist is an array with 1 element, pull it out of the array if (plist.length == 1) { plist = plist[0]; } return plist; } /** * Convert an XML based plist document into a JSON representation. * * @param {Object} xml_node - current XML node in the plist * @returns {Mixed} built up JSON object * @api private */ function parsePlistXML (node) { var i, new_obj, key, val, new_arr, res, d; if (!node) return null; if (node.nodeName === 'plist') { new_arr = []; for (i=0; i < node.childNodes.length; i++) { // ignore comment nodes (text) if (!shouldIgnoreNode(node.childNodes[i])) { new_arr.push( parsePlistXML(node.childNodes[i])); } } return new_arr; } else if (node.nodeName === 'dict') { new_obj = {}; key = null; for (i=0; i < node.childNodes.length; i++) { // ignore comment nodes (text) if (!shouldIgnoreNode(node.childNodes[i])) { if (key === null) { key = parsePlistXML(node.childNodes[i]); } else { new_obj[key] = parsePlistXML(node.childNodes[i]); key = null; } } } return new_obj; } else if (node.nodeName === 'array') { new_arr = []; for (i=0; i < node.childNodes.length; i++) { // ignore comment nodes (text) if (!shouldIgnoreNode(node.childNodes[i])) { res = parsePlistXML(node.childNodes[i]); if (null != res) new_arr.push(res); } } return new_arr; } else if (node.nodeName === '#text') { // TODO: what should we do with text types? (CDATA sections) } else if (node.nodeName === 'key') { return node.childNodes[0].nodeValue; } else if (node.nodeName === 'string') { res = ''; for (d=0; d < node.childNodes.length; d++) { res += node.childNodes[d].nodeValue; } return res; } else if (node.nodeName === 'integer') { // parse as base 10 integer return parseInt(node.childNodes[0].nodeValue, 10); } else if (node.nodeName === 'real') { res = ''; for (d=0; d < node.childNodes.length; d++) { if (node.childNodes[d].nodeType === 3) { res += node.childNodes[d].nodeValue; } } return parseFloat(res); } else if (node.nodeName === 'data') { res = ''; for (d=0; d < node.childNodes.length; d++) { if (node.childNodes[d].nodeType === 3) { res += node.childNodes[d].nodeValue.replace(/\s+/g, ''); } } // decode base64 data to a Buffer instance return new Buffer(res, 'base64'); } else if (node.nodeName === 'date') { return new Date(node.childNodes[0].nodeValue); } else if (node.nodeName === 'true') { return true; } else if (node.nodeName === 'false') { return false; } }