plist 0.4.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>plist.js browser example</title>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6
+ </head>
7
+ <body>
8
+ <script src="../../dist/plist.js"></script>
9
+ <script>
10
+ // TODO: add <input type=file> drag and drop example
11
+ console.log(plist);
12
+ </script>
13
+ </body>
14
+ </html>
package/lib/build.js ADDED
@@ -0,0 +1,136 @@
1
+
2
+ /**
3
+ * Module dependencies.
4
+ */
5
+
6
+ var base64 = require('base64-js');
7
+ var xmlbuilder = require('xmlbuilder');
8
+
9
+ /**
10
+ * Module exports.
11
+ */
12
+
13
+ exports.build = build;
14
+
15
+ /**
16
+ * Accepts a `Date` instance and returns an ISO date string.
17
+ *
18
+ * @param {Date} d - Date instance to serialize
19
+ * @returns {String} ISO date string representation of `d`
20
+ * @api private
21
+ */
22
+
23
+ function ISODateString(d){
24
+ function pad(n){
25
+ return n < 10 ? '0' + n : n;
26
+ }
27
+ return d.getUTCFullYear()+'-'
28
+ + pad(d.getUTCMonth()+1)+'-'
29
+ + pad(d.getUTCDate())+'T'
30
+ + pad(d.getUTCHours())+':'
31
+ + pad(d.getUTCMinutes())+':'
32
+ + pad(d.getUTCSeconds())+'Z';
33
+ }
34
+
35
+ /**
36
+ * Returns the internal "type" of `obj` via the
37
+ * `Object.prototype.toString()` trick.
38
+ *
39
+ * @param {Mixed} obj - any value
40
+ * @returns {String} the internal "type" name
41
+ * @api private
42
+ */
43
+
44
+ var toString = Object.prototype.toString;
45
+ function type (obj) {
46
+ var m = toString.call(obj).match(/\[object (.*)\]/);
47
+ return m ? m[1] : m;
48
+ }
49
+
50
+ /**
51
+ * Generate an XML plist string from the input object `obj`.
52
+ *
53
+ * @param {Object} obj - the object to convert
54
+ * @param {Object} [opts] - optional options object
55
+ * @returns {String} converted plist XML string
56
+ * @api public
57
+ */
58
+
59
+ function build (obj, opts) {
60
+ var XMLHDR = {
61
+ version: '1.0',
62
+ encoding: 'UTF-8'
63
+ };
64
+
65
+ var XMLDTD = {
66
+ pubid: '-//Apple//DTD PLIST 1.0//EN',
67
+ sysid: 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'
68
+ };
69
+
70
+ var doc = xmlbuilder.create('plist');
71
+
72
+ doc.dec(XMLHDR.version, XMLHDR.encoding, XMLHDR.standalone);
73
+ doc.dtd(XMLDTD.pubid, XMLDTD.sysid);
74
+ doc.att('version', '1.0');
75
+
76
+ walk_obj(obj, doc);
77
+
78
+ if (!opts) opts = {};
79
+ // default `pretty` to `true`
80
+ opts.pretty = opts.pretty !== false;
81
+ return doc.end(opts);
82
+ }
83
+
84
+ /**
85
+ * depth first, recursive traversal of a javascript object. when complete,
86
+ * next_child contains a reference to the build XML object.
87
+ *
88
+ * @api private
89
+ */
90
+
91
+ function walk_obj(next, next_child) {
92
+ var tag_type, i, prop;
93
+ var name = type(next);
94
+
95
+ if (Array.isArray(next)) {
96
+ next_child = next_child.ele('array');
97
+ for (i = 0; i < next.length; i++) {
98
+ walk_obj(next[i], next_child);
99
+ }
100
+
101
+ } else if (Buffer.isBuffer(next)) {
102
+ next_child.ele('data').raw(next.toString('base64'));
103
+
104
+ } else if ('Object' == name) {
105
+ next_child = next_child.ele('dict');
106
+ for (prop in next) {
107
+ if (next.hasOwnProperty(prop)) {
108
+ next_child.ele('key').txt(prop);
109
+ walk_obj(next[prop], next_child);
110
+ }
111
+ }
112
+
113
+ } else if ('Number' == name) {
114
+ // detect if this is an integer or real
115
+ // TODO: add an ability to force one way or another via a "cast"
116
+ tag_type = (next % 1 === 0) ? 'integer' : 'real';
117
+ next_child.ele(tag_type).txt(next.toString());
118
+
119
+ } else if ('Date' == name) {
120
+ next_child.ele('date').txt(ISODateString(new Date(next)));
121
+
122
+ } else if ('Boolean' == name) {
123
+ next_child.ele(next ? 'true' : 'false');
124
+
125
+ } else if ('String' == name) {
126
+ next_child.ele('string').txt(next);
127
+
128
+ } else if ('ArrayBuffer' == name) {
129
+ next_child.ele('data').raw(base64.fromByteArray(next));
130
+
131
+ } else if (next.buffer && 'ArrayBuffer' == type(next.buffer)) {
132
+ // a typed array
133
+ next_child.ele('data').raw(base64.fromByteArray(new Uint8Array(next.buffer), next_child));
134
+
135
+ }
136
+ }
package/lib/node.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Module dependencies.
3
+ */
4
+
5
+ var fs = require('fs');
6
+ var parse = require('./parse');
7
+ var deprecate = require('util-deprecate');
8
+
9
+ /**
10
+ * Module exports.
11
+ */
12
+
13
+ exports.parseFile = deprecate(parseFile, '`parseFile()` is deprecated. ' +
14
+ 'Use `parseString()` instead.');
15
+ exports.parseFileSync = deprecate(parseFileSync, '`parseFileSync()` is deprecated. ' +
16
+ 'Use `parseStringSync()` instead.');
17
+
18
+ /**
19
+ * Parses file `filename` as a .plist file.
20
+ * Invokes `fn` callback function when done.
21
+ *
22
+ * @param {String} filename - name of the file to read
23
+ * @param {Function} fn - callback function
24
+ * @api public
25
+ * @deprecated use parseString() instead
26
+ */
27
+
28
+ function parseFile (filename, fn) {
29
+ fs.readFile(filename, { encoding: 'utf8' }, onread);
30
+ function onread (err, inxml) {
31
+ if (err) return fn(err);
32
+ parse.parseString(inxml, fn);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Parses file `filename` as a .plist file.
38
+ * Returns a when done.
39
+ *
40
+ * @param {String} filename - name of the file to read
41
+ * @param {Function} fn - callback function
42
+ * @api public
43
+ * @deprecated use parseStringSync() instead
44
+ */
45
+
46
+ function parseFileSync (filename) {
47
+ var inxml = fs.readFileSync(filename, 'utf8');
48
+ return parse.parseStringSync(inxml);
49
+ }
package/lib/parse.js ADDED
@@ -0,0 +1,200 @@
1
+
2
+ /**
3
+ * Module dependencies.
4
+ */
5
+
6
+ var deprecate = require('util-deprecate');
7
+ var DOMParser = require('xmldom').DOMParser;
8
+
9
+ /**
10
+ * Module exports.
11
+ */
12
+
13
+ exports.parse = parse;
14
+ exports.parseString = deprecate(parseString, '`parseString()` is deprecated. ' +
15
+ 'It\'s not actually async. Use `parse()` instead.');
16
+ exports.parseStringSync = deprecate(parseStringSync, '`parseStringSync()` is ' +
17
+ 'deprecated. Use `parse()` instead.');
18
+
19
+ /**
20
+ * We ignore raw text (usually whitespace), <!-- xml comments -->,
21
+ * and raw CDATA nodes.
22
+ *
23
+ * @param {Element} node
24
+ * @returns {Boolean}
25
+ * @api private
26
+ */
27
+
28
+ function shouldIgnoreNode (node) {
29
+ return node.nodeType === 3 // text
30
+ || node.nodeType === 8 // comment
31
+ || node.nodeType === 4; // cdata
32
+ }
33
+
34
+
35
+ /**
36
+ * Parses a Plist XML string. Returns an Object.
37
+ *
38
+ * @param {String} xml - the XML String to decode
39
+ * @returns {Mixed} the decoded value from the Plist XML
40
+ * @api public
41
+ */
42
+
43
+ function parse (xml) {
44
+ var doc = new DOMParser().parseFromString(xml);
45
+ if (doc.documentElement.nodeName !== 'plist') {
46
+ throw new Error('malformed document. First element should be <plist>');
47
+ }
48
+ var plist = parsePlistXML(doc.documentElement);
49
+
50
+ // the root <plist> node gets interpreted as an Array,
51
+ // so pull out the inner data first
52
+ if (plist.length == 1) plist = plist[0];
53
+
54
+ return plist;
55
+ }
56
+
57
+ /**
58
+ * Parses a Plist XML string. Returns an Object. Takes a `callback` function.
59
+ *
60
+ * @param {String} xml - the XML String to decode
61
+ * @param {Function} callback - callback function
62
+ * @returns {Mixed} the decoded value from the Plist XML
63
+ * @api public
64
+ * @deprecated not actually async. use parse() instead
65
+ */
66
+
67
+ function parseString (xml, callback) {
68
+ var doc, error, plist;
69
+ try {
70
+ doc = new DOMParser().parseFromString(xml);
71
+ plist = parsePlistXML(doc.documentElement);
72
+ } catch(e) {
73
+ error = e;
74
+ }
75
+ callback(error, plist);
76
+ }
77
+
78
+ /**
79
+ * Parses a Plist XML string. Returns an Object.
80
+ *
81
+ * @param {String} xml - the XML String to decode
82
+ * @param {Function} callback - callback function
83
+ * @returns {Mixed} the decoded value from the Plist XML
84
+ * @api public
85
+ * @deprecated use parse() instead
86
+ */
87
+
88
+ function parseStringSync (xml) {
89
+ var doc = new DOMParser().parseFromString(xml);
90
+ var plist;
91
+ if (doc.documentElement.nodeName !== 'plist') {
92
+ throw new Error('malformed document. First element should be <plist>');
93
+ }
94
+ plist = parsePlistXML(doc.documentElement);
95
+
96
+ // if the plist is an array with 1 element, pull it out of the array
97
+ if (plist.length == 1) {
98
+ plist = plist[0];
99
+ }
100
+ return plist;
101
+ }
102
+
103
+ /**
104
+ * Convert an XML based plist document into a JSON representation.
105
+ *
106
+ * @param {Object} xml_node - current XML node in the plist
107
+ * @returns {Mixed} built up JSON object
108
+ * @api private
109
+ */
110
+
111
+ function parsePlistXML (node) {
112
+ var i, new_obj, key, val, new_arr, res, d;
113
+
114
+ if (!node)
115
+ return null;
116
+
117
+ if (node.nodeName === 'plist') {
118
+ new_arr = [];
119
+ for (i=0; i < node.childNodes.length; i++) {
120
+ // ignore comment nodes (text)
121
+ if (!shouldIgnoreNode(node.childNodes[i])) {
122
+ new_arr.push( parsePlistXML(node.childNodes[i]));
123
+ }
124
+ }
125
+ return new_arr;
126
+
127
+ } else if (node.nodeName === 'dict') {
128
+ new_obj = {};
129
+ key = null;
130
+ for (i=0; i < node.childNodes.length; i++) {
131
+ // ignore comment nodes (text)
132
+ if (!shouldIgnoreNode(node.childNodes[i])) {
133
+ if (key === null) {
134
+ key = parsePlistXML(node.childNodes[i]);
135
+ } else {
136
+ new_obj[key] = parsePlistXML(node.childNodes[i]);
137
+ key = null;
138
+ }
139
+ }
140
+ }
141
+ return new_obj;
142
+
143
+ } else if (node.nodeName === 'array') {
144
+ new_arr = [];
145
+ for (i=0; i < node.childNodes.length; i++) {
146
+ // ignore comment nodes (text)
147
+ if (!shouldIgnoreNode(node.childNodes[i])) {
148
+ res = parsePlistXML(node.childNodes[i]);
149
+ if (null != res) new_arr.push(res);
150
+ }
151
+ }
152
+ return new_arr;
153
+
154
+ } else if (node.nodeName === '#text') {
155
+ // TODO: what should we do with text types? (CDATA sections)
156
+
157
+ } else if (node.nodeName === 'key') {
158
+ return node.childNodes[0].nodeValue;
159
+
160
+ } else if (node.nodeName === 'string') {
161
+ res = '';
162
+ for (d=0; d < node.childNodes.length; d++) {
163
+ res += node.childNodes[d].nodeValue;
164
+ }
165
+ return res;
166
+
167
+ } else if (node.nodeName === 'integer') {
168
+ // parse as base 10 integer
169
+ return parseInt(node.childNodes[0].nodeValue, 10);
170
+
171
+ } else if (node.nodeName === 'real') {
172
+ res = '';
173
+ for (d=0; d < node.childNodes.length; d++) {
174
+ if (node.childNodes[d].nodeType === 3) {
175
+ res += node.childNodes[d].nodeValue;
176
+ }
177
+ }
178
+ return parseFloat(res);
179
+
180
+ } else if (node.nodeName === 'data') {
181
+ res = '';
182
+ for (d=0; d < node.childNodes.length; d++) {
183
+ if (node.childNodes[d].nodeType === 3) {
184
+ res += node.childNodes[d].nodeValue.replace(/\s+/g, '');
185
+ }
186
+ }
187
+
188
+ // decode base64 data to a Buffer instance
189
+ return new Buffer(res, 'base64');
190
+
191
+ } else if (node.nodeName === 'date') {
192
+ return new Date(node.childNodes[0].nodeValue);
193
+
194
+ } else if (node.nodeName === 'true') {
195
+ return true;
196
+
197
+ } else if (node.nodeName === 'false') {
198
+ return false;
199
+ }
200
+ }