138 lines
3.3 KiB
JavaScript
138 lines
3.3 KiB
JavaScript
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var base64 = require('base64-js');
|
|
var xmlbuilder = require('xmlbuilder');
|
|
|
|
/**
|
|
* Module exports.
|
|
*/
|
|
|
|
exports.build = build;
|
|
|
|
/**
|
|
* Accepts a `Date` instance and returns an ISO date string.
|
|
*
|
|
* @param {Date} d - Date instance to serialize
|
|
* @returns {String} ISO date string representation of `d`
|
|
* @api private
|
|
*/
|
|
|
|
function ISODateString(d){
|
|
function pad(n){
|
|
return n < 10 ? '0' + n : n;
|
|
}
|
|
return d.getUTCFullYear()+'-'
|
|
+ pad(d.getUTCMonth()+1)+'-'
|
|
+ pad(d.getUTCDate())+'T'
|
|
+ pad(d.getUTCHours())+':'
|
|
+ pad(d.getUTCMinutes())+':'
|
|
+ pad(d.getUTCSeconds())+'Z';
|
|
}
|
|
|
|
/**
|
|
* Returns the internal "type" of `obj` via the
|
|
* `Object.prototype.toString()` trick.
|
|
*
|
|
* @param {Mixed} obj - any value
|
|
* @returns {String} the internal "type" name
|
|
* @api private
|
|
*/
|
|
|
|
var toString = Object.prototype.toString;
|
|
function type (obj) {
|
|
var m = toString.call(obj).match(/\[object (.*)\]/);
|
|
return m ? m[1] : m;
|
|
}
|
|
|
|
/**
|
|
* Generate an XML plist string from the input object `obj`.
|
|
*
|
|
* @param {Object} obj - the object to convert
|
|
* @param {Object} [opts] - optional options object
|
|
* @returns {String} converted plist XML string
|
|
* @api public
|
|
*/
|
|
|
|
function build (obj, opts) {
|
|
var XMLHDR = {
|
|
version: '1.0',
|
|
encoding: 'UTF-8'
|
|
};
|
|
|
|
var XMLDTD = {
|
|
pubid: '-//Apple//DTD PLIST 1.0//EN',
|
|
sysid: 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'
|
|
};
|
|
|
|
var doc = xmlbuilder.create('plist');
|
|
|
|
doc.dec(XMLHDR.version, XMLHDR.encoding, XMLHDR.standalone);
|
|
doc.dtd(XMLDTD.pubid, XMLDTD.sysid);
|
|
doc.att('version', '1.0');
|
|
|
|
walk_obj(obj, doc);
|
|
|
|
if (!opts) opts = {};
|
|
// default `pretty` to `true`
|
|
opts.pretty = opts.pretty !== false;
|
|
return doc.end(opts);
|
|
}
|
|
|
|
/**
|
|
* depth first, recursive traversal of a javascript object. when complete,
|
|
* next_child contains a reference to the build XML object.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
function walk_obj(next, next_child) {
|
|
var tag_type, i, prop;
|
|
var name = type(next);
|
|
|
|
if ('Undefined' == name) {
|
|
return;
|
|
} else if (Array.isArray(next)) {
|
|
next_child = next_child.ele('array');
|
|
for (i = 0; i < next.length; i++) {
|
|
walk_obj(next[i], next_child);
|
|
}
|
|
|
|
} else if (Buffer.isBuffer(next)) {
|
|
next_child.ele('data').raw(next.toString('base64'));
|
|
|
|
} else if ('Object' == name) {
|
|
next_child = next_child.ele('dict');
|
|
for (prop in next) {
|
|
if (next.hasOwnProperty(prop)) {
|
|
next_child.ele('key').txt(prop);
|
|
walk_obj(next[prop], next_child);
|
|
}
|
|
}
|
|
|
|
} else if ('Number' == name) {
|
|
// detect if this is an integer or real
|
|
// TODO: add an ability to force one way or another via a "cast"
|
|
tag_type = (next % 1 === 0) ? 'integer' : 'real';
|
|
next_child.ele(tag_type).txt(next.toString());
|
|
|
|
} else if ('Date' == name) {
|
|
next_child.ele('date').txt(ISODateString(new Date(next)));
|
|
|
|
} else if ('Boolean' == name) {
|
|
next_child.ele(next ? 'true' : 'false');
|
|
|
|
} else if ('String' == name) {
|
|
next_child.ele('string').txt(next);
|
|
|
|
} else if ('ArrayBuffer' == name) {
|
|
next_child.ele('data').raw(base64.fromByteArray(next));
|
|
|
|
} else if (next && next.buffer && 'ArrayBuffer' == type(next.buffer)) {
|
|
// a typed array
|
|
next_child.ele('data').raw(base64.fromByteArray(new Uint8Array(next.buffer), next_child));
|
|
|
|
}
|
|
}
|