plist 3.1.1 → 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 +33 -28
- package/index.js +2 -13
- package/lib/build.js +32 -28
- package/lib/parse.js +38 -33
- package/package.json +9 -10
- package/.github/workflows/ci.yml +0 -24
- package/.jshintrc +0 -4
- package/History.md +0 -201
- package/Makefile +0 -75
- package/dist/plist-build.js +0 -5348
- package/dist/plist-parse.js +0 -10709
- package/dist/plist.js +0 -15918
- package/examples/browser/index.html +0 -14
package/README.md
CHANGED
|
@@ -20,33 +20,44 @@ and vice-versa.
|
|
|
20
20
|
Install using `npm`:
|
|
21
21
|
|
|
22
22
|
``` bash
|
|
23
|
-
$ npm install
|
|
23
|
+
$ npm install plist
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Then `
|
|
26
|
+
Then import the `parse()` and `build()` functions:
|
|
27
27
|
|
|
28
28
|
``` js
|
|
29
|
-
|
|
29
|
+
import { parse, build } from 'plist';
|
|
30
30
|
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
```
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
``` js
|
|
69
|
+
import { readFileSync } from 'node:fs';
|
|
70
|
+
import { parse } from 'plist';
|
|
60
71
|
|
|
61
|
-
|
|
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
|
-
```
|
|
68
|
-
|
|
78
|
+
``` js
|
|
79
|
+
import { parse } from 'plist';
|
|
69
80
|
|
|
70
|
-
|
|
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(
|
|
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
|
-
```
|
|
106
|
-
|
|
116
|
+
``` js
|
|
117
|
+
import { build } from 'plist';
|
|
107
118
|
|
|
108
|
-
|
|
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(
|
|
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
|
-
[](https://saucelabs.com)
|
|
142
|
-
|
|
143
148
|
|
|
144
149
|
## License
|
|
145
150
|
|
package/index.js
CHANGED
|
@@ -1,13 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
48
|
+
const toString = Object.prototype.toString;
|
|
44
49
|
function type (obj) {
|
|
45
|
-
|
|
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
|
-
|
|
63
|
+
export function build (obj, opts) {
|
|
64
|
+
const XMLHDR = {
|
|
60
65
|
version: '1.0',
|
|
61
66
|
encoding: 'UTF-8'
|
|
62
67
|
};
|
|
63
68
|
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
103
|
-
next_child.ele('data').raw(next
|
|
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,18 +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
|
-
*
|
|
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
|
-
|
|
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
|
+
}
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
const TEXT_NODE = 3;
|
|
21
|
+
const CDATA_NODE = 4;
|
|
22
|
+
const COMMENT_NODE = 8;
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
25
|
* We ignore raw text (usually whitespace), <!-- xml comments -->,
|
|
@@ -63,13 +70,13 @@ function invariant(test, message) {
|
|
|
63
70
|
* @api public
|
|
64
71
|
*/
|
|
65
72
|
|
|
66
|
-
function parse(xml) {
|
|
67
|
-
|
|
73
|
+
export function parse(xml) {
|
|
74
|
+
const doc = new DOMParser().parseFromString(xml, "text/xml");
|
|
68
75
|
invariant(
|
|
69
76
|
doc.documentElement.nodeName === "plist",
|
|
70
77
|
"malformed document. First element should be <plist>",
|
|
71
78
|
);
|
|
72
|
-
|
|
79
|
+
let plist = parsePlistXML(doc.documentElement);
|
|
73
80
|
|
|
74
81
|
// the root <plist> node gets interpreted as an Array,
|
|
75
82
|
// so pull out the inner data first
|
|
@@ -87,29 +94,27 @@ function parse(xml) {
|
|
|
87
94
|
*/
|
|
88
95
|
|
|
89
96
|
function parsePlistXML(node) {
|
|
90
|
-
var i, new_obj, key, val, new_arr, res, counter, type;
|
|
91
|
-
|
|
92
97
|
if (!node) return null;
|
|
93
98
|
|
|
94
99
|
if (node.nodeName === "plist") {
|
|
95
|
-
new_arr = [];
|
|
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
106
|
new_arr.push(parsePlistXML(node.childNodes[i]));
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
return new_arr;
|
|
105
110
|
} else if (node.nodeName === "dict") {
|
|
106
|
-
new_obj = {};
|
|
107
|
-
key = null;
|
|
108
|
-
counter = 0;
|
|
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(
|
|
@@ -134,13 +139,13 @@ function parsePlistXML(node) {
|
|
|
134
139
|
|
|
135
140
|
return new_obj;
|
|
136
141
|
} else if (node.nodeName === "array") {
|
|
137
|
-
new_arr = [];
|
|
142
|
+
const new_arr = [];
|
|
138
143
|
if (isEmptyNode(node)) {
|
|
139
144
|
return new_arr;
|
|
140
145
|
}
|
|
141
|
-
for (i = 0; i < node.childNodes.length; i++) {
|
|
146
|
+
for (let i = 0; i < node.childNodes.length; i++) {
|
|
142
147
|
if (!shouldIgnoreNode(node.childNodes[i])) {
|
|
143
|
-
res = parsePlistXML(node.childNodes[i]);
|
|
148
|
+
const res = parsePlistXML(node.childNodes[i]);
|
|
144
149
|
if (null != res) new_arr.push(res);
|
|
145
150
|
}
|
|
146
151
|
}
|
|
@@ -159,12 +164,12 @@ function parsePlistXML(node) {
|
|
|
159
164
|
|
|
160
165
|
return node.childNodes[0].nodeValue;
|
|
161
166
|
} else if (node.nodeName === "string") {
|
|
162
|
-
res = "";
|
|
167
|
+
let res = "";
|
|
163
168
|
if (isEmptyNode(node)) {
|
|
164
169
|
return res;
|
|
165
170
|
}
|
|
166
|
-
for (i = 0; i < node.childNodes.length; i++) {
|
|
167
|
-
|
|
171
|
+
for (let i = 0; i < node.childNodes.length; i++) {
|
|
172
|
+
const type = node.childNodes[i].nodeType;
|
|
168
173
|
if (type === TEXT_NODE || type === CDATA_NODE) {
|
|
169
174
|
res += node.childNodes[i].nodeValue;
|
|
170
175
|
}
|
|
@@ -175,24 +180,24 @@ function parsePlistXML(node) {
|
|
|
175
180
|
return parseInt(node.childNodes[0].nodeValue, 10);
|
|
176
181
|
} else if (node.nodeName === "real") {
|
|
177
182
|
invariant(!isEmptyNode(node), 'Cannot parse "" as real.');
|
|
178
|
-
res = "";
|
|
179
|
-
for (i = 0; i < node.childNodes.length; i++) {
|
|
183
|
+
let res = "";
|
|
184
|
+
for (let i = 0; i < node.childNodes.length; i++) {
|
|
180
185
|
if (node.childNodes[i].nodeType === TEXT_NODE) {
|
|
181
186
|
res += node.childNodes[i].nodeValue;
|
|
182
187
|
}
|
|
183
188
|
}
|
|
184
189
|
return parseFloat(res);
|
|
185
190
|
} else if (node.nodeName === "data") {
|
|
186
|
-
res = "";
|
|
191
|
+
let res = "";
|
|
187
192
|
if (isEmptyNode(node)) {
|
|
188
|
-
return
|
|
193
|
+
return base64ToUint8Array(res);
|
|
189
194
|
}
|
|
190
|
-
for (i = 0; i < node.childNodes.length; i++) {
|
|
195
|
+
for (let i = 0; i < node.childNodes.length; i++) {
|
|
191
196
|
if (node.childNodes[i].nodeType === TEXT_NODE) {
|
|
192
197
|
res += node.childNodes[i].nodeValue.replace(/\s+/g, "");
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
|
-
return
|
|
200
|
+
return base64ToUint8Array(res);
|
|
196
201
|
} else if (node.nodeName === "date") {
|
|
197
202
|
invariant(!isEmptyNode(node), 'Cannot parse "" as Date.');
|
|
198
203
|
return new Date(node.childNodes[0].nodeValue);
|
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": "
|
|
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,21 +24,19 @@
|
|
|
23
24
|
"parser",
|
|
24
25
|
"xml"
|
|
25
26
|
],
|
|
26
|
-
"
|
|
27
|
+
"exports": "./index.js",
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"lib"
|
|
31
|
+
],
|
|
27
32
|
"dependencies": {
|
|
28
33
|
"@xmldom/xmldom": "^0.9.10",
|
|
29
|
-
"base64-js": "^1.5.1",
|
|
30
34
|
"xmlbuilder": "^15.1.1"
|
|
31
35
|
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"browserify": "^17.0.0",
|
|
34
|
-
"mocha": "^10.2.0",
|
|
35
|
-
"zuul": "3.12.0"
|
|
36
|
-
},
|
|
37
36
|
"scripts": {
|
|
38
|
-
"test": "
|
|
37
|
+
"test": "node --test test/*.js"
|
|
39
38
|
},
|
|
40
39
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
40
|
+
"node": ">=18"
|
|
42
41
|
}
|
|
43
42
|
}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -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