Skip to content

Commit

Permalink
refactored the build function. Fixes issue #14
Browse files Browse the repository at this point in the history
  • Loading branch information
mreinstein committed Sep 27, 2012
1 parent 96abe31 commit efac250
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 63 deletions.
144 changes: 84 additions & 60 deletions lib/plist.js
Expand Up @@ -208,72 +208,96 @@
var parser = new Parser();
parser.parseFile(filename, callback);
}

exports.build = function (obj) {
var XMLHDR = { 'version': '1.0','encoding': 'UTF-8'}

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'
}

// instanceof is horribly unreliable.
// http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}

function isDate(obj) {
return (toString.call(obj) == '[object Date]');
}

function isBoolean(obj) {
return (obj === true || obj === false || toString.call(obj) == '[object Boolean]');
}

function isNumber(obj) {
return (toString.call(obj) == '[object Number]');
}

function isObject(obj) {
return (toString.call(obj) == '[object Object]');
}

function isString(obj) {
return (toString.call(obj) == '[object String]');
}

exports.build = function(obj) {
var XMLHDR = { 'version': '1.0','encoding': 'UTF-8'}
, XMLDTD = { 'ext': 'PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"'}
, doc = xmlbuilder.create()
, child = doc.begin('plist', XMLHDR, XMLDTD).att('version', '1.0').ele('dict')
, stack = [];
, child = doc.begin('plist', XMLHDR, XMLDTD).att('version', '1.0')
, next, tag_type, i, prop;

reflect(obj);
return child.end({pretty: true });

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'
}
walk_obj(obj, child);
return child.end({pretty: true });
}

function walk_obj(next, next_child) {
var tag_type, i, prop;

function reflect(o) {
prev = null;
var name;
for (name in o) {
if (typeof o[name]!== 'function') {
if (typeof o[name]=== 'object') {
if (o[name] instanceof Date) {
child = child.ele('key').txt(name).up().ele('date').raw(ISODateString(new Date(o[name]))).up();
} else {
if (o[name] instanceof Array) {
prev = 'array';
child = child.ele('key').txt(name).up().ele('array');
} else {
if (stack[stack.length-1] instanceof Array) {
child = child.ele('dict');
} else {
child = child.ele('key').txt(name).up().ele('dict');
}
}
stack.push(o[name]);
reflect(o[name]);
}
} else {
child.ele('key').raw(name);
if ((typeof o[name]) === 'boolean') {
child.ele(o[name]);
} else if ((typeof o[name]) === 'number') {
child.ele((o[name] % 1 === 0) ? 'integer' : 'real').txt(o[name]);
} else {
var tok = o[name];
// TODO this is sloppy base64 detection, use Buffer
var base64Matcher = new RegExp("^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$");
if (base64Matcher.test(tok.replace(/\s/g,'')) && (tok.length>125)) {
child.ele('data').raw(tok);
} else {
child.ele('string').raw(tok);
}
}
}
}
if(isArray(next)) {
next_child = next_child.ele('array');
for(i=0 ;i < next.length;i++) {
walk_obj(next[i], next_child);
}
}
else if(isObject(next)) {
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);
}
stack.pop();
child = child.up();
}
}
}
else if(isNumber(next)) {
// detect if this is an integer or real
tag_type =(next % 1 === 0) ? 'integer' : 'real';
next_child.ele(tag_type).txt(next.toString());
}
else if(isDate(next)) {
next_child.ele('date').raw(ISODateString(new Date(next)));
}
else if(isBoolean(next)) {
val = next ? "true" : "false";
next_child.ele(val);
}
else if(isString(next)) {
//if (str!=obj || str.indexOf("\n")>=0) str = "<![CDATA["+str+"]]>";
// TODO this is sloppy base64 detection, use Buffer
var base64Matcher = new RegExp("^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$");
if (base64Matcher.test(next.replace(/\s/g,'')) && (next.length>125)) {
next_child.ele('data').raw(next);
} else {
next_child.ele('string').raw(next);
}
}
};


})(typeof exports === 'undefined' ? plist = {} : exports, typeof window === "undefined" ? require('sax') : sax, typeof window === "undefined" ? require('xmlbuilder') : xmlbuilder)
//the above line checks for exports (defined in node) and uses it, or creates a global variable and exports to that.
Expand Down
87 changes: 87 additions & 0 deletions tests/Cordova.plist
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
-->
<plist version="1.0">
<dict>
<key>UIWebViewBounce</key>
<true/>
<key>TopActivityIndicator</key>
<string>gray</string>
<key>EnableLocation</key>
<false/>
<key>EnableViewportScale</key>
<false/>
<key>AutoHideSplashScreen</key>
<true/>
<key>ShowSplashScreenSpinner</key>
<true/>
<key>MediaPlaybackRequiresUserAction</key>
<false/>
<key>AllowInlineMediaPlayback</key>
<false/>
<key>OpenAllWhitelistURLsInWebView</key>
<false/>
<key>BackupWebStorage</key>
<true/>
<key>ExternalHosts</key>
<array>
<string>*</string>
</array>
<key>Plugins</key>
<dict>
<key>Device</key>
<string>CDVDevice</string>
<key>Logger</key>
<string>CDVLogger</string>
<key>Compass</key>
<string>CDVLocation</string>
<key>Accelerometer</key>
<string>CDVAccelerometer</string>
<key>Camera</key>
<string>CDVCamera</string>
<key>NetworkStatus</key>
<string>CDVConnection</string>
<key>Contacts</key>
<string>CDVContacts</string>
<key>Debug Console</key>
<string>CDVDebugConsole</string>
<key>Echo</key>
<string>CDVEcho</string>
<key>File</key>
<string>CDVFile</string>
<key>FileTransfer</key>
<string>CDVFileTransfer</string>
<key>Geolocation</key>
<string>CDVLocation</string>
<key>Notification</key>
<string>CDVNotification</string>
<key>Media</key>
<string>CDVSound</string>
<key>Capture</key>
<string>CDVCapture</string>
<key>SplashScreen</key>
<string>CDVSplashScreen</string>
<key>Battery</key>
<string>CDVBattery</string>
</dict>
</dict>
</plist>
25 changes: 22 additions & 3 deletions tests/test-build.js
Expand Up @@ -2,7 +2,6 @@ var path = require('path')
, fs = require('fs')
, plist = require('../');


/*
// TODO These assertions fail because CDATA entities get converted in the process
exports.testBuildFromPlistFile = function(test) {
Expand Down Expand Up @@ -39,7 +38,6 @@ exports.testBuildFromSmallItunesXML = function(test) {
});
}


exports.testBuildAirplayXML = function(test) {
var file = path.join(__dirname, 'airplay.xml');

Expand All @@ -56,16 +54,36 @@ exports.testBuildAirplayXML = function(test) {
});
}

exports.testCordovaPlist = function(test) {
var file = path.join(__dirname, 'Cordova.plist');

plist.parseFile(file, function(err, dicts) {
var dict = dicts[0];
test.ifError(err);
test.equal(dict['TopActivityIndicator'], 'gray');
test.equal(dict['Plugins']['Device'], 'CDVDevice');

// Try re-stringifying and re-parsing
plist.parseString(plist.build(dict), function(err, dicts2) {
test.ifError(err);
test.deepEqual(dicts,dicts2);
test.done();
});
});
}

exports.testBuildPhoneGapPlist = function(test) {
var file = path.join(__dirname, 'Xcode-PhoneGap.plist');

plist.parseFile(file, function(err, dicts) {
var dict = dicts[0];
test.ifError(err);

test.equal(dict['ExternalHosts'][0], "*");
test.equal(dict['Plugins']['com.phonegap.accelerometer'], "PGAccelerometer");

//console.log('like they were', dict);
//console.log('hmm', plist.build(dict));
// Try re-stringifying and re-parsing
plist.parseString(plist.build(dict), function(err, dicts2) {
test.ifError(err);
Expand Down Expand Up @@ -95,6 +113,7 @@ exports.testBuildXcodeInfoPlist = function(test) {
});
}


// this code does a string to string comparison. It's not very useful right
// now because CDATA sections arent supported. save for later I guess
/*
Expand Down

0 comments on commit efac250

Please sign in to comment.