Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Harden test-cases and align with other implementations #84

Merged
merged 2 commits into from
Feb 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 80 additions & 35 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ var DOMParser = require('xmldom').DOMParser;

exports.parse = parse;

var TEXT_NODE = 3;
var CDATA_NODE = 4;
var COMMENT_NODE = 8;


/**
* We ignore raw text (usually whitespace), <!-- xml comments -->,
* and raw CDATA nodes.
Expand All @@ -20,9 +25,9 @@ exports.parse = parse;
*/

function shouldIgnoreNode (node) {
return node.nodeType === 3 // text
|| node.nodeType === 8 // comment
|| node.nodeType === 4; // cdata
return node.nodeType === TEXT_NODE
|| node.nodeType === COMMENT_NODE
|| node.nodeType === CDATA_NODE;
}

/**
Expand All @@ -43,6 +48,12 @@ function isEmptyNode(node){
}
}

function invariant(test, message) {
if (!test) {
throw new Error(message);
}
}

/**
* Parses a Plist XML string. Returns an Object.
*
Expand All @@ -53,9 +64,10 @@ function isEmptyNode(node){

function parse (xml) {
var doc = new DOMParser().parseFromString(xml);
if (doc.documentElement.nodeName !== 'plist') {
throw new Error('malformed document. First element should be <plist>');
}
invariant(
doc.documentElement.nodeName === 'plist',
'malformed document. First element should be <plist>'
);
var plist = parsePlistXML(doc.documentElement);

// the root <plist> node gets interpreted as an Array,
Expand All @@ -74,41 +86,59 @@ function parse (xml) {
*/

function parsePlistXML (node) {
var i, new_obj, key, val, new_arr, res, d;
var i, new_obj, key, val, new_arr, res, counter, type;

if (!node)
return null;

if (node.nodeName === 'plist') {
new_arr = [];
if (isEmptyNode(node)) {
return 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;
counter = 0;
if (isEmptyNode(node)) {
return new_obj;
}
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;
}
if (shouldIgnoreNode(node.childNodes[i])) continue;
if (counter % 2 === 0) {
invariant(
node.childNodes[i].nodeName === 'key',
'Missing key while parsing <dict/>.'
);
key = parsePlistXML(node.childNodes[i]);
} else {
invariant(
node.childNodes[i].nodeName !== 'key',
'Unexpected key "'
+ parsePlistXML(node.childNodes[i])
+ '" while parsing <dict/>.'
);
new_obj[key] = parsePlistXML(node.childNodes[i]);
}
counter += 1;
}
if (counter % 2 === 1) {
throw new Error('Missing value for "' + key + '" while parsing <dict/>');
}
return new_obj;

} else if (node.nodeName === 'array') {
new_arr = [];
if (isEmptyNode(node)) {
return 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);
Expand All @@ -120,45 +150,60 @@ function parsePlistXML (node) {
// TODO: what should we do with text types? (CDATA sections)

} else if (node.nodeName === 'key') {
if(isEmptyNode(node)) return null;
res = '';
if(node.childNodes[0]) {
res = node.childNodes[0].nodeValue;
if (isEmptyNode(node)) {
return '';
}
return res;
return node.childNodes[0].nodeValue;
} else if (node.nodeName === 'string') {
res = '';
if(isEmptyNode(node)) return null;
for (d=0; d < node.childNodes.length; d++) {
res += node.childNodes[d].nodeValue;
if (isEmptyNode(node)) {
return res;
}
for (i=0; i < node.childNodes.length; i++) {
var type = node.childNodes[i].nodeType;
if (type === TEXT_NODE || type === CDATA_NODE) {
res += node.childNodes[i].nodeValue;
}
}
return res;

} else if (node.nodeName === 'integer') {
// parse as base 10 integer
invariant(
!isEmptyNode(node),
'Cannot parse "" as integer.'
);
return parseInt(node.childNodes[0].nodeValue, 10);

} else if (node.nodeName === 'real') {
invariant(
!isEmptyNode(node),
'Cannot parse "" as real.'
);
res = '';
for (d=0; d < node.childNodes.length; d++) {
if (node.childNodes[d].nodeType === 3) {
res += node.childNodes[d].nodeValue;
for (i=0; i < node.childNodes.length; i++) {
if (node.childNodes[i].nodeType === TEXT_NODE) {
res += node.childNodes[i].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, '');
if (isEmptyNode(node)) {
return new Buffer(res, 'base64');
}
for (i=0; i < node.childNodes.length; i++) {
if (node.childNodes[i].nodeType === TEXT_NODE) {
res += node.childNodes[i].nodeValue.replace(/\s+/g, '');
}
}

// decode base64 data to a Buffer instance
return new Buffer(res, 'base64');

} else if (node.nodeName === 'date') {
invariant(
!isEmptyNode(node),
'Cannot parse "" as Date.'
)
return new Date(node.childNodes[0].nodeValue);

} else if (node.nodeName === 'true') {
Expand Down
Loading