plist 3.1.0 → 4.0.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.
package/README.md CHANGED
@@ -20,33 +20,44 @@ and vice-versa.
20
20
  Install using `npm`:
21
21
 
22
22
  ``` bash
23
- $ npm install --save plist
23
+ $ npm install plist
24
24
  ```
25
25
 
26
- Then `require()` the _plist_ module in your file:
26
+ Then import the `parse()` and `build()` functions:
27
27
 
28
28
  ``` js
29
- var plist = require('plist');
29
+ import { parse, build } from 'plist';
30
30
 
31
- // now use the `parse()` and `build()` functions
32
- var val = plist.parse('<plist><string>Hello World!</string></plist>');
31
+ const val = parse('<plist><string>Hello World!</string></plist>');
33
32
  console.log(val); // "Hello World!"
34
33
  ```
35
34
 
36
35
 
37
36
  ### Browser
38
37
 
39
- Include the `dist/plist.js` in a `<script>` tag in your HTML file:
38
+ Use an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap)
39
+ to resolve the bare module specifiers used by plist and its dependencies,
40
+ then import directly from the source:
40
41
 
41
42
  ``` html
42
- <script src="plist.js"></script>
43
- <script>
44
- // now use the `parse()` and `build()` functions
45
- var val = plist.parse('<plist><string>Hello World!</string></plist>');
43
+ <script type="importmap">
44
+ {
45
+ "imports": {
46
+ "@xmldom/xmldom": "https://esm.sh/@xmldom/xmldom",
47
+ "xmlbuilder": "https://esm.sh/xmlbuilder"
48
+ }
49
+ }
50
+ </script>
51
+ <script type="module">
52
+ import { parse, build } from './node_modules/plist/index.js';
53
+
54
+ const val = parse('<plist><string>Hello World!</string></plist>');
46
55
  console.log(val); // "Hello World!"
47
56
  </script>
48
57
  ```
49
58
 
59
+ See the [browser example](./examples/browser/) for a drag-and-drop demo.
60
+
50
61
 
51
62
  ## API
52
63
 
@@ -54,20 +65,20 @@ Include the `dist/plist.js` in a `<script>` tag in your HTML file:
54
65
 
55
66
  Parsing a plist from filename:
56
67
 
57
- ``` javascript
58
- var fs = require('fs');
59
- var plist = require('plist');
68
+ ``` js
69
+ import { readFileSync } from 'node:fs';
70
+ import { parse } from 'plist';
60
71
 
61
- var obj = plist.parse(fs.readFileSync('myPlist.plist', 'utf8'));
72
+ const obj = parse(readFileSync('myPlist.plist', 'utf8'));
62
73
  console.log(JSON.stringify(obj));
63
74
  ```
64
75
 
65
76
  Parsing a plist from string payload:
66
77
 
67
- ``` javascript
68
- var plist = require('plist');
78
+ ``` js
79
+ import { parse } from 'plist';
69
80
 
70
- var xml =
81
+ const xml =
71
82
  '<?xml version="1.0" encoding="UTF-8"?>' +
72
83
  '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' +
73
84
  '<plist version="1.0">' +
@@ -84,7 +95,7 @@ var xml =
84
95
  '</dict>' +
85
96
  '</plist>';
86
97
 
87
- console.log(plist.parse(xml));
98
+ console.log(parse(xml));
88
99
 
89
100
  // [
90
101
  // "metadata",
@@ -102,10 +113,10 @@ console.log(plist.parse(xml));
102
113
  Given an existing JavaScript Object, you can turn it into an XML document
103
114
  that complies with the plist DTD:
104
115
 
105
- ``` javascript
106
- var plist = require('plist');
116
+ ``` js
117
+ import { build } from 'plist';
107
118
 
108
- var json = [
119
+ const obj = [
109
120
  "metadata",
110
121
  {
111
122
  "bundle-identifier": "com.company.app",
@@ -115,7 +126,7 @@ var json = [
115
126
  }
116
127
  ];
117
128
 
118
- console.log(plist.build(json));
129
+ console.log(build(obj));
119
130
 
120
131
  // <?xml version="1.0" encoding="UTF-8"?>
121
132
  // <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -134,12 +145,6 @@ console.log(plist.build(json));
134
145
  // </plist>
135
146
  ```
136
147
 
137
- ## Cross Platform Testing Credits
138
-
139
- Much thanks to Sauce Labs for providing free resources that enable cross-browser testing on this project!
140
-
141
- [![Testing Powered By SauceLabs](https://opensource.saucelabs.com/images/opensauce/powered-by-saucelabs-badge-red.png?sanitize=true "Testing Powered By SauceLabs")](https://saucelabs.com)
142
-
143
148
 
144
149
  ## License
145
150
 
package/index.js CHANGED
@@ -1,13 +1,2 @@
1
- /**
2
- * Parser functions.
3
- */
4
-
5
- var parserFunctions = require('./lib/parse');
6
- Object.keys(parserFunctions).forEach(function (k) { exports[k] = parserFunctions[k]; });
7
-
8
- /**
9
- * Builder functions.
10
- */
11
-
12
- var builderFunctions = require('./lib/build');
13
- Object.keys(builderFunctions).forEach(function (k) { exports[k] = builderFunctions[k]; });
1
+ export { parse } from './lib/parse.js';
2
+ export { build } from './lib/build.js';
package/lib/build.js CHANGED
@@ -1,15 +1,20 @@
1
- /**
2
- * Module dependencies.
3
- */
4
-
5
- var base64 = require('base64-js');
6
- var xmlbuilder = require('xmlbuilder');
1
+ import xmlbuilder from 'xmlbuilder';
7
2
 
8
3
  /**
9
- * Module exports.
4
+ * Encode a Uint8Array into a base64 string.
5
+ *
6
+ * @param {Uint8Array} bytes
7
+ * @returns {String} base64 encoded string
8
+ * @api private
10
9
  */
11
10
 
12
- exports.build = build;
11
+ function uint8ArrayToBase64(bytes) {
12
+ let binary = '';
13
+ for (let i = 0; i < bytes.length; i++) {
14
+ binary += String.fromCharCode(bytes[i]);
15
+ }
16
+ return btoa(binary);
17
+ }
13
18
 
14
19
  /**
15
20
  * Accepts a `Date` instance and returns an ISO date string.
@@ -40,9 +45,9 @@ function ISODateString(d){
40
45
  * @api private
41
46
  */
42
47
 
43
- var toString = Object.prototype.toString;
48
+ const toString = Object.prototype.toString;
44
49
  function type (obj) {
45
- var m = toString.call(obj).match(/\[object (.*)\]/);
50
+ const m = toString.call(obj).match(/\[object (.*)\]/);
46
51
  return m ? m[1] : m;
47
52
  }
48
53
 
@@ -55,18 +60,18 @@ function type (obj) {
55
60
  * @api public
56
61
  */
57
62
 
58
- function build (obj, opts) {
59
- var XMLHDR = {
63
+ export function build (obj, opts) {
64
+ const XMLHDR = {
60
65
  version: '1.0',
61
66
  encoding: 'UTF-8'
62
67
  };
63
68
 
64
- var XMLDTD = {
69
+ const XMLDTD = {
65
70
  pubid: '-//Apple//DTD PLIST 1.0//EN',
66
71
  sysid: 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'
67
72
  };
68
73
 
69
- var doc = xmlbuilder.create('plist');
74
+ const doc = xmlbuilder.create('plist');
70
75
 
71
76
  doc.dec(XMLHDR.version, XMLHDR.encoding, XMLHDR.standalone);
72
77
  doc.dtd(XMLDTD.pubid, XMLDTD.sysid);
@@ -88,23 +93,29 @@ function build (obj, opts) {
88
93
  */
89
94
 
90
95
  function walk_obj(next, next_child) {
91
- var tag_type, i, prop;
92
- var name = type(next);
96
+ const name = type(next);
93
97
 
94
98
  if ('Undefined' == name) {
95
99
  return;
96
100
  } else if (Array.isArray(next)) {
97
101
  next_child = next_child.ele('array');
98
- for (i = 0; i < next.length; i++) {
102
+ for (let i = 0; i < next.length; i++) {
99
103
  walk_obj(next[i], next_child);
100
104
  }
101
105
 
102
- } else if (Buffer.isBuffer(next)) {
103
- next_child.ele('data').raw(next.toString('base64'));
106
+ } else if (next instanceof ArrayBuffer) {
107
+ next_child.ele('data').raw(uint8ArrayToBase64(new Uint8Array(next)));
108
+
109
+ } else if (ArrayBuffer.isView(next)) {
110
+ // Handles Uint8Array, Buffer, and all typed arrays
111
+ const bytes = next instanceof Uint8Array
112
+ ? next
113
+ : new Uint8Array(next.buffer, next.byteOffset, next.byteLength);
114
+ next_child.ele('data').raw(uint8ArrayToBase64(bytes));
104
115
 
105
116
  } else if ('Object' == name) {
106
117
  next_child = next_child.ele('dict');
107
- for (prop in next) {
118
+ for (const prop in next) {
108
119
  if (next.hasOwnProperty(prop)) {
109
120
  next_child.ele('key').txt(prop);
110
121
  walk_obj(next[prop], next_child);
@@ -114,7 +125,7 @@ function walk_obj(next, next_child) {
114
125
  } else if ('Number' == name) {
115
126
  // detect if this is an integer or real
116
127
  // TODO: add an ability to force one way or another via a "cast"
117
- tag_type = (next % 1 === 0) ? 'integer' : 'real';
128
+ const tag_type = (next % 1 === 0) ? 'integer' : 'real';
118
129
  next_child.ele(tag_type).txt(next.toString());
119
130
 
120
131
  } else if ('BigInt' == name) {
@@ -129,13 +140,6 @@ function walk_obj(next, next_child) {
129
140
  } else if ('String' == name) {
130
141
  next_child.ele('string').txt(next);
131
142
 
132
- } else if ('ArrayBuffer' == name) {
133
- next_child.ele('data').raw(base64.fromByteArray(next));
134
-
135
- } else if (next && next.buffer && 'ArrayBuffer' == type(next.buffer)) {
136
- // a typed array
137
- next_child.ele('data').raw(base64.fromByteArray(new Uint8Array(next.buffer), next_child));
138
-
139
143
  } else if ('Null' === name) {
140
144
  next_child.ele('null').txt('');
141
145
 
package/lib/parse.js CHANGED
@@ -1,19 +1,25 @@
1
- /**
2
- * Module dependencies.
3
- */
4
-
5
- const { DOMParser } = require('@xmldom/xmldom');
1
+ import { DOMParser } from '@xmldom/xmldom';
6
2
 
7
3
  /**
8
- * Module exports.
4
+ * Decode a base64 string into a Uint8Array.
5
+ *
6
+ * @param {String} base64 - base64 encoded string
7
+ * @returns {Uint8Array}
8
+ * @api private
9
9
  */
10
10
 
11
- exports.parse = parse;
12
-
13
- var TEXT_NODE = 3;
14
- var CDATA_NODE = 4;
15
- var COMMENT_NODE = 8;
11
+ function base64ToUint8Array(base64) {
12
+ const binary = atob(base64);
13
+ const bytes = new Uint8Array(binary.length);
14
+ for (let i = 0; i < binary.length; i++) {
15
+ bytes[i] = binary.charCodeAt(i);
16
+ }
17
+ return bytes;
18
+ }
16
19
 
20
+ const TEXT_NODE = 3;
21
+ const CDATA_NODE = 4;
22
+ const COMMENT_NODE = 8;
17
23
 
18
24
  /**
19
25
  * We ignore raw text (usually whitespace), <!-- xml comments -->,
@@ -24,10 +30,12 @@ var COMMENT_NODE = 8;
24
30
  * @api private
25
31
  */
26
32
 
27
- function shouldIgnoreNode (node) {
28
- return node.nodeType === TEXT_NODE
29
- || node.nodeType === COMMENT_NODE
30
- || node.nodeType === CDATA_NODE;
33
+ function shouldIgnoreNode(node) {
34
+ return (
35
+ node.nodeType === TEXT_NODE ||
36
+ node.nodeType === COMMENT_NODE ||
37
+ node.nodeType === CDATA_NODE
38
+ );
31
39
  }
32
40
 
33
41
  /**
@@ -40,8 +48,8 @@ function shouldIgnoreNode (node) {
40
48
  * @returns {Boolean}
41
49
  * @api private
42
50
  */
43
- function isEmptyNode(node){
44
- if(!node.childNodes || node.childNodes.length === 0) {
51
+ function isEmptyNode(node) {
52
+ if (!node.childNodes || node.childNodes.length === 0) {
45
53
  return true;
46
54
  } else {
47
55
  return false;
@@ -62,13 +70,13 @@ function invariant(test, message) {
62
70
  * @api public
63
71
  */
64
72
 
65
- function parse (xml) {
66
- var doc = new DOMParser().parseFromString(xml);
73
+ export function parse(xml) {
74
+ const doc = new DOMParser().parseFromString(xml, "text/xml");
67
75
  invariant(
68
- doc.documentElement.nodeName === 'plist',
69
- 'malformed document. First element should be <plist>'
76
+ doc.documentElement.nodeName === "plist",
77
+ "malformed document. First element should be <plist>",
70
78
  );
71
- var plist = parsePlistXML(doc.documentElement);
79
+ let plist = parsePlistXML(doc.documentElement);
72
80
 
73
81
  // the root <plist> node gets interpreted as an Array,
74
82
  // so pull out the inner data first
@@ -85,143 +93,121 @@ function parse (xml) {
85
93
  * @api private
86
94
  */
87
95
 
88
- function parsePlistXML (node) {
89
- var i, new_obj, key, val, new_arr, res, counter, type;
90
-
91
- if (!node)
92
- return null;
96
+ function parsePlistXML(node) {
97
+ if (!node) return null;
93
98
 
94
- if (node.nodeName === 'plist') {
95
- new_arr = [];
99
+ if (node.nodeName === "plist") {
100
+ const new_arr = [];
96
101
  if (isEmptyNode(node)) {
97
102
  return new_arr;
98
103
  }
99
- for (i=0; i < node.childNodes.length; i++) {
104
+ for (let i = 0; i < node.childNodes.length; i++) {
100
105
  if (!shouldIgnoreNode(node.childNodes[i])) {
101
- new_arr.push( parsePlistXML(node.childNodes[i]));
106
+ new_arr.push(parsePlistXML(node.childNodes[i]));
102
107
  }
103
108
  }
104
109
  return new_arr;
105
- } else if (node.nodeName === 'dict') {
106
- new_obj = {};
107
- key = null;
108
- counter = 0;
110
+ } else if (node.nodeName === "dict") {
111
+ const new_obj = {};
112
+ let key = null;
113
+ let counter = 0;
109
114
  if (isEmptyNode(node)) {
110
115
  return new_obj;
111
116
  }
112
- for (i=0; i < node.childNodes.length; i++) {
117
+ for (let i = 0; i < node.childNodes.length; i++) {
113
118
  if (shouldIgnoreNode(node.childNodes[i])) continue;
114
119
  if (counter % 2 === 0) {
115
120
  invariant(
116
- node.childNodes[i].nodeName === 'key',
117
- 'Missing key while parsing <dict/>.'
121
+ node.childNodes[i].nodeName === "key",
122
+ "Missing key while parsing <dict/>.",
118
123
  );
119
124
  key = parsePlistXML(node.childNodes[i]);
120
125
  } else {
121
126
  invariant(
122
- node.childNodes[i].nodeName !== 'key',
123
- 'Unexpected key "'
124
- + parsePlistXML(node.childNodes[i])
125
- + '" while parsing <dict/>.'
127
+ node.childNodes[i].nodeName !== "key",
128
+ 'Unexpected key "' +
129
+ parsePlistXML(node.childNodes[i]) +
130
+ '" while parsing <dict/>.',
126
131
  );
127
132
  new_obj[key] = parsePlistXML(node.childNodes[i]);
128
133
  }
129
134
  counter += 1;
130
135
  }
131
136
  if (counter % 2 === 1) {
132
- new_obj[key] = '';
137
+ new_obj[key] = "";
133
138
  }
134
-
135
- return new_obj;
136
139
 
137
- } else if (node.nodeName === 'array') {
138
- new_arr = [];
140
+ return new_obj;
141
+ } else if (node.nodeName === "array") {
142
+ const new_arr = [];
139
143
  if (isEmptyNode(node)) {
140
144
  return new_arr;
141
145
  }
142
- for (i=0; i < node.childNodes.length; i++) {
146
+ for (let i = 0; i < node.childNodes.length; i++) {
143
147
  if (!shouldIgnoreNode(node.childNodes[i])) {
144
- res = parsePlistXML(node.childNodes[i]);
148
+ const res = parsePlistXML(node.childNodes[i]);
145
149
  if (null != res) new_arr.push(res);
146
150
  }
147
151
  }
148
152
  return new_arr;
149
-
150
- } else if (node.nodeName === '#text') {
153
+ } else if (node.nodeName === "#text") {
151
154
  // TODO: what should we do with text types? (CDATA sections)
152
-
153
- } else if (node.nodeName === 'key') {
155
+ } else if (node.nodeName === "key") {
154
156
  if (isEmptyNode(node)) {
155
- return '';
157
+ return "";
156
158
  }
157
159
 
158
160
  invariant(
159
- node.childNodes[0].nodeValue !== '__proto__',
160
- '__proto__ keys can lead to prototype pollution. More details on CVE-2022-22912'
161
+ node.childNodes[0].nodeValue !== "__proto__",
162
+ "__proto__ keys can lead to prototype pollution. More details on CVE-2022-22912",
161
163
  );
162
164
 
163
165
  return node.childNodes[0].nodeValue;
164
- } else if (node.nodeName === 'string') {
165
- res = '';
166
+ } else if (node.nodeName === "string") {
167
+ let res = "";
166
168
  if (isEmptyNode(node)) {
167
169
  return res;
168
170
  }
169
- for (i=0; i < node.childNodes.length; i++) {
170
- var type = node.childNodes[i].nodeType;
171
+ for (let i = 0; i < node.childNodes.length; i++) {
172
+ const type = node.childNodes[i].nodeType;
171
173
  if (type === TEXT_NODE || type === CDATA_NODE) {
172
174
  res += node.childNodes[i].nodeValue;
173
175
  }
174
176
  }
175
177
  return res;
176
-
177
- } else if (node.nodeName === 'integer') {
178
- invariant(
179
- !isEmptyNode(node),
180
- 'Cannot parse "" as integer.'
181
- );
178
+ } else if (node.nodeName === "integer") {
179
+ invariant(!isEmptyNode(node), 'Cannot parse "" as integer.');
182
180
  return parseInt(node.childNodes[0].nodeValue, 10);
183
-
184
- } else if (node.nodeName === 'real') {
185
- invariant(
186
- !isEmptyNode(node),
187
- 'Cannot parse "" as real.'
188
- );
189
- res = '';
190
- for (i=0; i < node.childNodes.length; i++) {
181
+ } else if (node.nodeName === "real") {
182
+ invariant(!isEmptyNode(node), 'Cannot parse "" as real.');
183
+ let res = "";
184
+ for (let i = 0; i < node.childNodes.length; i++) {
191
185
  if (node.childNodes[i].nodeType === TEXT_NODE) {
192
186
  res += node.childNodes[i].nodeValue;
193
187
  }
194
188
  }
195
189
  return parseFloat(res);
196
-
197
- } else if (node.nodeName === 'data') {
198
- res = '';
190
+ } else if (node.nodeName === "data") {
191
+ let res = "";
199
192
  if (isEmptyNode(node)) {
200
- return Buffer.from(res, 'base64');
193
+ return base64ToUint8Array(res);
201
194
  }
202
- for (i=0; i < node.childNodes.length; i++) {
195
+ for (let i = 0; i < node.childNodes.length; i++) {
203
196
  if (node.childNodes[i].nodeType === TEXT_NODE) {
204
- res += node.childNodes[i].nodeValue.replace(/\s+/g, '');
197
+ res += node.childNodes[i].nodeValue.replace(/\s+/g, "");
205
198
  }
206
199
  }
207
- return Buffer.from(res, 'base64');
208
-
209
- } else if (node.nodeName === 'date') {
210
- invariant(
211
- !isEmptyNode(node),
212
- 'Cannot parse "" as Date.'
213
- )
200
+ return base64ToUint8Array(res);
201
+ } else if (node.nodeName === "date") {
202
+ invariant(!isEmptyNode(node), 'Cannot parse "" as Date.');
214
203
  return new Date(node.childNodes[0].nodeValue);
215
-
216
- } else if (node.nodeName === 'null') {
204
+ } else if (node.nodeName === "null") {
217
205
  return null;
218
-
219
- } else if (node.nodeName === 'true') {
206
+ } else if (node.nodeName === "true") {
220
207
  return true;
221
-
222
- } else if (node.nodeName === 'false') {
208
+ } else if (node.nodeName === "false") {
223
209
  return false;
224
210
  } else {
225
- throw new Error('Invalid PLIST tag ' + node.nodeName);
211
+ throw new Error("Invalid PLIST tag " + node.nodeName);
226
212
  }
227
213
  }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "plist",
3
3
  "description": "Apple's property list parser/builder for Node.js and browsers",
4
- "version": "3.1.0",
4
+ "version": "4.0.0",
5
+ "type": "module",
5
6
  "author": "Nathan Rajlich <nathan@tootallnate.net>",
6
7
  "contributors": [
7
8
  "Hans Huebner <hans.huebner@gmail.com>",
@@ -23,22 +24,19 @@
23
24
  "parser",
24
25
  "xml"
25
26
  ],
26
- "main": "index.js",
27
+ "exports": "./index.js",
28
+ "files": [
29
+ "index.js",
30
+ "lib"
31
+ ],
27
32
  "dependencies": {
28
- "@xmldom/xmldom": "^0.8.8",
29
- "base64-js": "^1.5.1",
33
+ "@xmldom/xmldom": "^0.9.10",
30
34
  "xmlbuilder": "^15.1.1"
31
35
  },
32
- "devDependencies": {
33
- "browserify": "^17.0.0",
34
- "mocha": "^9.2.2",
35
- "multiline": "^2.0.0",
36
- "zuul": "3.12.0"
37
- },
38
36
  "scripts": {
39
- "test": "make test"
37
+ "test": "node --test test/*.js"
40
38
  },
41
39
  "engines": {
42
- "node": ">=10.4.0"
40
+ "node": ">=18"
43
41
  }
44
42
  }
@@ -1,24 +0,0 @@
1
- name: ci
2
-
3
- on:
4
- push:
5
- pull_request:
6
-
7
- jobs:
8
- build:
9
- runs-on: ubuntu-latest
10
-
11
- strategy:
12
- matrix:
13
- node-version: [ '14', '16', '18' ]
14
-
15
- steps:
16
- - uses: actions/checkout@v2
17
-
18
- - name: Use Node.js ${{ matrix.node-version }}
19
- uses: actions/setup-node@v1
20
- with:
21
- node-version: ${{ matrix.node-version }}
22
-
23
- - run: npm install
24
- - run: npm test
package/.jshintrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "laxbreak": true,
3
- "laxcomma": true
4
- }