@fuman/plist 0.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.
- package/LICENSE +8 -0
- package/README.md +41 -0
- package/_constants.cjs +11 -0
- package/_constants.d.cts +4 -0
- package/_constants.d.ts +4 -0
- package/_constants.js +11 -0
- package/_utils.cjs +12 -0
- package/_utils.d.cts +1 -0
- package/_utils.d.ts +1 -0
- package/_utils.js +12 -0
- package/bplist-reader.cjs +210 -0
- package/bplist-reader.d.cts +14 -0
- package/bplist-reader.d.ts +14 -0
- package/bplist-reader.js +210 -0
- package/bplist-writer.cjs +344 -0
- package/bplist-writer.d.cts +1 -0
- package/bplist-writer.d.ts +1 -0
- package/bplist-writer.js +344 -0
- package/index.cjs +17 -0
- package/index.d.cts +7 -0
- package/index.d.ts +7 -0
- package/index.js +17 -0
- package/ns-keyed-archiver.cjs +129 -0
- package/ns-keyed-archiver.d.cts +1 -0
- package/ns-keyed-archiver.d.ts +1 -0
- package/ns-keyed-archiver.js +129 -0
- package/ns-keyed-unarchiver.cjs +155 -0
- package/ns-keyed-unarchiver.d.cts +9 -0
- package/ns-keyed-unarchiver.d.ts +9 -0
- package/ns-keyed-unarchiver.js +155 -0
- package/package.json +30 -0
- package/plist-reader.cjs +79 -0
- package/plist-reader.d.cts +15 -0
- package/plist-reader.d.ts +15 -0
- package/plist-reader.js +79 -0
- package/plist-writer.cjs +120 -0
- package/plist-writer.d.cts +27 -0
- package/plist-writer.d.ts +27 -0
- package/plist-writer.js +120 -0
- package/types.cjs +25 -0
- package/types.d.cts +27 -0
- package/types.d.ts +27 -0
- package/types.js +25 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const io = require("@fuman/io");
|
|
4
|
+
const _constants = require("./_constants.cjs");
|
|
5
|
+
const types = require("./types.cjs");
|
|
6
|
+
function determineUintSize(value) {
|
|
7
|
+
if (value < 0) {
|
|
8
|
+
throw new Error(`unexpected negative value: ${value}`);
|
|
9
|
+
}
|
|
10
|
+
if (value < 256) {
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
if (value < 65536) {
|
|
14
|
+
return 2;
|
|
15
|
+
}
|
|
16
|
+
if (value < 4294967296) {
|
|
17
|
+
return 4;
|
|
18
|
+
}
|
|
19
|
+
return 8;
|
|
20
|
+
}
|
|
21
|
+
function writeUint(bytes, value, size) {
|
|
22
|
+
switch (size) {
|
|
23
|
+
case 1:
|
|
24
|
+
io.write.uint8(bytes, value);
|
|
25
|
+
break;
|
|
26
|
+
case 2:
|
|
27
|
+
io.write.uint16be(bytes, value);
|
|
28
|
+
break;
|
|
29
|
+
case 4:
|
|
30
|
+
io.write.uint32be(bytes, value);
|
|
31
|
+
break;
|
|
32
|
+
case 8:
|
|
33
|
+
io.write.uint64be(bytes, value);
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
throw new Error(`invalid uint size: ${size}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function writeUintWithHeader(bytes, value, type = 16) {
|
|
40
|
+
const size = determineUintSize(value);
|
|
41
|
+
let headerSize;
|
|
42
|
+
if (size === 1) headerSize = 0;
|
|
43
|
+
else if (size === 2) headerSize = 1;
|
|
44
|
+
else if (size === 4) headerSize = 2;
|
|
45
|
+
else headerSize = 3;
|
|
46
|
+
io.write.uint8(bytes, type | headerSize);
|
|
47
|
+
writeUint(bytes, value, size);
|
|
48
|
+
}
|
|
49
|
+
function writeSizeHeader(bytes, type, size) {
|
|
50
|
+
if (size < 15) {
|
|
51
|
+
io.write.uint8(bytes, type | size);
|
|
52
|
+
} else {
|
|
53
|
+
io.write.uint8(bytes, type | 15);
|
|
54
|
+
writeUintWithHeader(bytes, size);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isAscii(value) {
|
|
58
|
+
return /^[\x00-\x7F]*$/.test(value);
|
|
59
|
+
}
|
|
60
|
+
const INT32_MAX = 2 ** 31 - 1;
|
|
61
|
+
const INT64_MAX = 2n ** 63n - 1n;
|
|
62
|
+
const INT64_MIN = -(2n ** 63n);
|
|
63
|
+
const INT128_MAX = 2n ** 127n - 1n;
|
|
64
|
+
const INT128_MIN = -(2n ** 127n);
|
|
65
|
+
function handleInt(id, value) {
|
|
66
|
+
if (value < 0 || value > INT32_MAX) {
|
|
67
|
+
value = BigInt(value);
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === "number") {
|
|
70
|
+
return { id, type: "int", value, size: determineUintSize(value) };
|
|
71
|
+
}
|
|
72
|
+
const value_ = value;
|
|
73
|
+
if (value_ < INT64_MIN || value_ > INT64_MAX) {
|
|
74
|
+
if (value_ < INT128_MIN || value_ > INT128_MAX) {
|
|
75
|
+
throw new Error(`value is too large: ${value_}`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
id,
|
|
79
|
+
type: "int",
|
|
80
|
+
value: value_,
|
|
81
|
+
size: 16
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
id,
|
|
86
|
+
type: "int",
|
|
87
|
+
value: value_,
|
|
88
|
+
size: 8
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
class BinaryPlistWriter {
|
|
92
|
+
#nextId = 0;
|
|
93
|
+
#values = /* @__PURE__ */ new Map();
|
|
94
|
+
constructor() {
|
|
95
|
+
this.toPlistEntry = this.toPlistEntry.bind(this);
|
|
96
|
+
}
|
|
97
|
+
toPlistEntry(value) {
|
|
98
|
+
switch (typeof value) {
|
|
99
|
+
case "string": {
|
|
100
|
+
const existingId = this.#values.get(value);
|
|
101
|
+
if (existingId !== void 0) {
|
|
102
|
+
return existingId;
|
|
103
|
+
}
|
|
104
|
+
const id = this.#nextId++;
|
|
105
|
+
this.#values.set(value, id);
|
|
106
|
+
return {
|
|
107
|
+
id,
|
|
108
|
+
type: "string",
|
|
109
|
+
value,
|
|
110
|
+
format: isAscii(value) ? "ascii" : "utf16"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
case "number":
|
|
114
|
+
case "bigint": {
|
|
115
|
+
const existingId = this.#values.get(value);
|
|
116
|
+
if (existingId !== void 0) {
|
|
117
|
+
return existingId;
|
|
118
|
+
}
|
|
119
|
+
const id = this.#nextId++;
|
|
120
|
+
this.#values.set(value, id);
|
|
121
|
+
if (typeof value === "number") {
|
|
122
|
+
if (value % 1 !== 0) {
|
|
123
|
+
return { id, type: "real", value, size: 8 };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return handleInt(id, value);
|
|
127
|
+
}
|
|
128
|
+
case "boolean": {
|
|
129
|
+
const existingId = this.#values.get(value);
|
|
130
|
+
if (existingId !== void 0) {
|
|
131
|
+
return existingId;
|
|
132
|
+
}
|
|
133
|
+
const id = this.#nextId++;
|
|
134
|
+
this.#values.set(value, id);
|
|
135
|
+
return { id, type: "bool", value };
|
|
136
|
+
}
|
|
137
|
+
case "object": {
|
|
138
|
+
if (value === null) {
|
|
139
|
+
const existingId = this.#values.get(value);
|
|
140
|
+
if (existingId !== void 0) {
|
|
141
|
+
return existingId;
|
|
142
|
+
}
|
|
143
|
+
const id2 = this.#nextId++;
|
|
144
|
+
this.#values.set(value, id2);
|
|
145
|
+
return { id: id2, type: "null" };
|
|
146
|
+
}
|
|
147
|
+
const id = this.#nextId++;
|
|
148
|
+
if (value instanceof Date) return { id, type: "date", value };
|
|
149
|
+
if (value instanceof Uint8Array) return { id, type: "data", value };
|
|
150
|
+
if (Array.isArray(value)) return { id, type: "array", items: value.map(this.toPlistEntry) };
|
|
151
|
+
if (value instanceof Set) return { id, type: "set", items: Array.from(value).map(this.toPlistEntry) };
|
|
152
|
+
if (value instanceof types.PlistValue) {
|
|
153
|
+
switch (value.type) {
|
|
154
|
+
case "float32":
|
|
155
|
+
return { id, type: "real", value: value.value, size: 4 };
|
|
156
|
+
case "float64":
|
|
157
|
+
return { id, type: "real", value: value.value, size: 8 };
|
|
158
|
+
case "int":
|
|
159
|
+
return handleInt(id, value.value);
|
|
160
|
+
case "uid":
|
|
161
|
+
return { id, type: "uid", value: value.value, size: determineUintSize(value.value) };
|
|
162
|
+
case "ascii":
|
|
163
|
+
return { id, type: "string", value: value.value, format: "ascii" };
|
|
164
|
+
case "utf16":
|
|
165
|
+
return { id, type: "string", value: value.value, format: "utf16" };
|
|
166
|
+
case "utf8":
|
|
167
|
+
return { id, type: "string", value: value.value, format: "utf8" };
|
|
168
|
+
default:
|
|
169
|
+
throw new Error(`unsupported plist value type: ${value.type}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const objEntries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value);
|
|
173
|
+
const entries = [];
|
|
174
|
+
for (let i = 0; i < objEntries.length; i++) {
|
|
175
|
+
const [key, value2] = objEntries[i];
|
|
176
|
+
entries.push({ key: this.toPlistEntry(key), value: this.toPlistEntry(value2) });
|
|
177
|
+
}
|
|
178
|
+
return { id: this.#nextId++, type: "dict", entries };
|
|
179
|
+
}
|
|
180
|
+
default:
|
|
181
|
+
throw new Error(`unexpected value type: ${typeof value}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
#offsetTable;
|
|
185
|
+
#objectRefSize;
|
|
186
|
+
writeRef(bytes, id) {
|
|
187
|
+
writeUint(bytes, id, this.#objectRefSize);
|
|
188
|
+
}
|
|
189
|
+
writeEntry(bytes, entry) {
|
|
190
|
+
if (typeof entry === "number") return entry;
|
|
191
|
+
switch (entry.type) {
|
|
192
|
+
case "null":
|
|
193
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
194
|
+
io.write.uint8(bytes, 0);
|
|
195
|
+
break;
|
|
196
|
+
case "bool":
|
|
197
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
198
|
+
io.write.uint8(bytes, entry.value ? 9 : 8);
|
|
199
|
+
break;
|
|
200
|
+
case "int": {
|
|
201
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
202
|
+
switch (entry.size) {
|
|
203
|
+
case 1: {
|
|
204
|
+
io.write.uint8(bytes, 16);
|
|
205
|
+
io.write.uint8(bytes, entry.value);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 2: {
|
|
209
|
+
io.write.uint8(bytes, 17);
|
|
210
|
+
io.write.uint16be(bytes, entry.value);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 4: {
|
|
214
|
+
io.write.uint8(bytes, 18);
|
|
215
|
+
io.write.uint32be(bytes, entry.value);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case 8: {
|
|
219
|
+
io.write.uint8(bytes, 19);
|
|
220
|
+
io.write.int64be(bytes, entry.value);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case 16: {
|
|
224
|
+
io.write.uint8(bytes, 20);
|
|
225
|
+
io.write.intbe(bytes, 16, entry.value);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
default:
|
|
229
|
+
throw new Error(`invalid int size: ${entry.size}`);
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "real": {
|
|
234
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
235
|
+
switch (entry.size) {
|
|
236
|
+
case 4: {
|
|
237
|
+
io.write.uint8(bytes, 34);
|
|
238
|
+
io.write.float32be(bytes, entry.value);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case 8: {
|
|
242
|
+
io.write.uint8(bytes, 35);
|
|
243
|
+
io.write.float64be(bytes, entry.value);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
default:
|
|
247
|
+
throw new Error(`invalid real size: ${entry.size}`);
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case "date": {
|
|
252
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
253
|
+
io.write.uint8(bytes, 51);
|
|
254
|
+
const seconds = (entry.value.getTime() - _constants.CORE_DATA_EPOCH) / 1e3;
|
|
255
|
+
io.write.float64be(bytes, seconds);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case "data":
|
|
259
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
260
|
+
writeSizeHeader(bytes, 64, entry.value.length);
|
|
261
|
+
io.write.bytes(bytes, entry.value);
|
|
262
|
+
break;
|
|
263
|
+
case "string": {
|
|
264
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
265
|
+
switch (entry.format) {
|
|
266
|
+
case "ascii":
|
|
267
|
+
writeSizeHeader(bytes, 80, entry.value.length);
|
|
268
|
+
io.write.rawString(bytes, entry.value);
|
|
269
|
+
break;
|
|
270
|
+
case "utf16":
|
|
271
|
+
writeSizeHeader(bytes, 96, entry.value.length);
|
|
272
|
+
io.write.utf16beString(bytes, entry.value);
|
|
273
|
+
break;
|
|
274
|
+
case "utf8":
|
|
275
|
+
writeSizeHeader(bytes, 112, entry.value.length);
|
|
276
|
+
io.write.utf8String(bytes, entry.value);
|
|
277
|
+
break;
|
|
278
|
+
default:
|
|
279
|
+
throw new Error(`invalid string format: ${entry.format}`);
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "uid":
|
|
284
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
285
|
+
writeUintWithHeader(bytes, entry.value, 128);
|
|
286
|
+
break;
|
|
287
|
+
case "array":
|
|
288
|
+
case "set": {
|
|
289
|
+
const type = entry.type === "array" ? 160 : 176;
|
|
290
|
+
const refs = [];
|
|
291
|
+
for (const item of entry.items) {
|
|
292
|
+
refs.push(this.writeEntry(bytes, item));
|
|
293
|
+
}
|
|
294
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
295
|
+
writeSizeHeader(bytes, type, refs.length);
|
|
296
|
+
refs.forEach((ref) => this.writeRef(bytes, ref));
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case "dict": {
|
|
300
|
+
const keyRefs = [];
|
|
301
|
+
const valueRefs = [];
|
|
302
|
+
for (const { key, value } of entry.entries) {
|
|
303
|
+
keyRefs.push(this.writeEntry(bytes, key));
|
|
304
|
+
valueRefs.push(this.writeEntry(bytes, value));
|
|
305
|
+
}
|
|
306
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
307
|
+
writeSizeHeader(bytes, 208, entry.entries.length);
|
|
308
|
+
keyRefs.forEach((ref) => this.writeRef(bytes, ref));
|
|
309
|
+
valueRefs.forEach((ref) => this.writeRef(bytes, ref));
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return entry.id;
|
|
314
|
+
}
|
|
315
|
+
writeTop(data) {
|
|
316
|
+
const topEntry = this.toPlistEntry(data);
|
|
317
|
+
const numObjects = this.#nextId;
|
|
318
|
+
if (numObjects > _constants.MAX_OBJECT_COUNT) {
|
|
319
|
+
throw new Error("MAX_OBJECT_COUNT exceeded");
|
|
320
|
+
}
|
|
321
|
+
this.#objectRefSize = determineUintSize(numObjects);
|
|
322
|
+
this.#offsetTable = Array.from({ length: numObjects });
|
|
323
|
+
const bytes = io.Bytes.alloc();
|
|
324
|
+
io.write.bytes(bytes, _constants.BPLIST_MAGIC);
|
|
325
|
+
io.write.int16be(bytes, 12336);
|
|
326
|
+
this.writeEntry(bytes, topEntry);
|
|
327
|
+
const offsetTableOffset = bytes.written;
|
|
328
|
+
const offsetSize = determineUintSize(bytes.written);
|
|
329
|
+
for (let i = 0; i < this.#offsetTable.length; i++) {
|
|
330
|
+
writeUint(bytes, this.#offsetTable[i], offsetSize);
|
|
331
|
+
}
|
|
332
|
+
io.write.bytes(bytes, new Uint8Array([0, 0, 0, 0, 0, 0]));
|
|
333
|
+
io.write.uint8(bytes, offsetSize);
|
|
334
|
+
io.write.uint8(bytes, this.#objectRefSize);
|
|
335
|
+
io.write.uint64be(bytes, BigInt(numObjects));
|
|
336
|
+
io.write.uint64be(bytes, BigInt(topEntry.id));
|
|
337
|
+
io.write.uint64be(bytes, BigInt(offsetTableOffset));
|
|
338
|
+
return bytes.result();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function writeBinaryPlist(data) {
|
|
342
|
+
return new BinaryPlistWriter().writeTop(data);
|
|
343
|
+
}
|
|
344
|
+
exports.writeBinaryPlist = writeBinaryPlist;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function writeBinaryPlist(data: unknown): Uint8Array;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function writeBinaryPlist(data: unknown): Uint8Array;
|
package/bplist-writer.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { write, Bytes } from "@fuman/io";
|
|
2
|
+
import { CORE_DATA_EPOCH, MAX_OBJECT_COUNT, BPLIST_MAGIC } from "./_constants.js";
|
|
3
|
+
import { PlistValue } from "./types.js";
|
|
4
|
+
function determineUintSize(value) {
|
|
5
|
+
if (value < 0) {
|
|
6
|
+
throw new Error(`unexpected negative value: ${value}`);
|
|
7
|
+
}
|
|
8
|
+
if (value < 256) {
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
if (value < 65536) {
|
|
12
|
+
return 2;
|
|
13
|
+
}
|
|
14
|
+
if (value < 4294967296) {
|
|
15
|
+
return 4;
|
|
16
|
+
}
|
|
17
|
+
return 8;
|
|
18
|
+
}
|
|
19
|
+
function writeUint(bytes, value, size) {
|
|
20
|
+
switch (size) {
|
|
21
|
+
case 1:
|
|
22
|
+
write.uint8(bytes, value);
|
|
23
|
+
break;
|
|
24
|
+
case 2:
|
|
25
|
+
write.uint16be(bytes, value);
|
|
26
|
+
break;
|
|
27
|
+
case 4:
|
|
28
|
+
write.uint32be(bytes, value);
|
|
29
|
+
break;
|
|
30
|
+
case 8:
|
|
31
|
+
write.uint64be(bytes, value);
|
|
32
|
+
break;
|
|
33
|
+
default:
|
|
34
|
+
throw new Error(`invalid uint size: ${size}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function writeUintWithHeader(bytes, value, type = 16) {
|
|
38
|
+
const size = determineUintSize(value);
|
|
39
|
+
let headerSize;
|
|
40
|
+
if (size === 1) headerSize = 0;
|
|
41
|
+
else if (size === 2) headerSize = 1;
|
|
42
|
+
else if (size === 4) headerSize = 2;
|
|
43
|
+
else headerSize = 3;
|
|
44
|
+
write.uint8(bytes, type | headerSize);
|
|
45
|
+
writeUint(bytes, value, size);
|
|
46
|
+
}
|
|
47
|
+
function writeSizeHeader(bytes, type, size) {
|
|
48
|
+
if (size < 15) {
|
|
49
|
+
write.uint8(bytes, type | size);
|
|
50
|
+
} else {
|
|
51
|
+
write.uint8(bytes, type | 15);
|
|
52
|
+
writeUintWithHeader(bytes, size);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isAscii(value) {
|
|
56
|
+
return /^[\x00-\x7F]*$/.test(value);
|
|
57
|
+
}
|
|
58
|
+
const INT32_MAX = 2 ** 31 - 1;
|
|
59
|
+
const INT64_MAX = 2n ** 63n - 1n;
|
|
60
|
+
const INT64_MIN = -(2n ** 63n);
|
|
61
|
+
const INT128_MAX = 2n ** 127n - 1n;
|
|
62
|
+
const INT128_MIN = -(2n ** 127n);
|
|
63
|
+
function handleInt(id, value) {
|
|
64
|
+
if (value < 0 || value > INT32_MAX) {
|
|
65
|
+
value = BigInt(value);
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === "number") {
|
|
68
|
+
return { id, type: "int", value, size: determineUintSize(value) };
|
|
69
|
+
}
|
|
70
|
+
const value_ = value;
|
|
71
|
+
if (value_ < INT64_MIN || value_ > INT64_MAX) {
|
|
72
|
+
if (value_ < INT128_MIN || value_ > INT128_MAX) {
|
|
73
|
+
throw new Error(`value is too large: ${value_}`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
type: "int",
|
|
78
|
+
value: value_,
|
|
79
|
+
size: 16
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
id,
|
|
84
|
+
type: "int",
|
|
85
|
+
value: value_,
|
|
86
|
+
size: 8
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
class BinaryPlistWriter {
|
|
90
|
+
#nextId = 0;
|
|
91
|
+
#values = /* @__PURE__ */ new Map();
|
|
92
|
+
constructor() {
|
|
93
|
+
this.toPlistEntry = this.toPlistEntry.bind(this);
|
|
94
|
+
}
|
|
95
|
+
toPlistEntry(value) {
|
|
96
|
+
switch (typeof value) {
|
|
97
|
+
case "string": {
|
|
98
|
+
const existingId = this.#values.get(value);
|
|
99
|
+
if (existingId !== void 0) {
|
|
100
|
+
return existingId;
|
|
101
|
+
}
|
|
102
|
+
const id = this.#nextId++;
|
|
103
|
+
this.#values.set(value, id);
|
|
104
|
+
return {
|
|
105
|
+
id,
|
|
106
|
+
type: "string",
|
|
107
|
+
value,
|
|
108
|
+
format: isAscii(value) ? "ascii" : "utf16"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
case "number":
|
|
112
|
+
case "bigint": {
|
|
113
|
+
const existingId = this.#values.get(value);
|
|
114
|
+
if (existingId !== void 0) {
|
|
115
|
+
return existingId;
|
|
116
|
+
}
|
|
117
|
+
const id = this.#nextId++;
|
|
118
|
+
this.#values.set(value, id);
|
|
119
|
+
if (typeof value === "number") {
|
|
120
|
+
if (value % 1 !== 0) {
|
|
121
|
+
return { id, type: "real", value, size: 8 };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return handleInt(id, value);
|
|
125
|
+
}
|
|
126
|
+
case "boolean": {
|
|
127
|
+
const existingId = this.#values.get(value);
|
|
128
|
+
if (existingId !== void 0) {
|
|
129
|
+
return existingId;
|
|
130
|
+
}
|
|
131
|
+
const id = this.#nextId++;
|
|
132
|
+
this.#values.set(value, id);
|
|
133
|
+
return { id, type: "bool", value };
|
|
134
|
+
}
|
|
135
|
+
case "object": {
|
|
136
|
+
if (value === null) {
|
|
137
|
+
const existingId = this.#values.get(value);
|
|
138
|
+
if (existingId !== void 0) {
|
|
139
|
+
return existingId;
|
|
140
|
+
}
|
|
141
|
+
const id2 = this.#nextId++;
|
|
142
|
+
this.#values.set(value, id2);
|
|
143
|
+
return { id: id2, type: "null" };
|
|
144
|
+
}
|
|
145
|
+
const id = this.#nextId++;
|
|
146
|
+
if (value instanceof Date) return { id, type: "date", value };
|
|
147
|
+
if (value instanceof Uint8Array) return { id, type: "data", value };
|
|
148
|
+
if (Array.isArray(value)) return { id, type: "array", items: value.map(this.toPlistEntry) };
|
|
149
|
+
if (value instanceof Set) return { id, type: "set", items: Array.from(value).map(this.toPlistEntry) };
|
|
150
|
+
if (value instanceof PlistValue) {
|
|
151
|
+
switch (value.type) {
|
|
152
|
+
case "float32":
|
|
153
|
+
return { id, type: "real", value: value.value, size: 4 };
|
|
154
|
+
case "float64":
|
|
155
|
+
return { id, type: "real", value: value.value, size: 8 };
|
|
156
|
+
case "int":
|
|
157
|
+
return handleInt(id, value.value);
|
|
158
|
+
case "uid":
|
|
159
|
+
return { id, type: "uid", value: value.value, size: determineUintSize(value.value) };
|
|
160
|
+
case "ascii":
|
|
161
|
+
return { id, type: "string", value: value.value, format: "ascii" };
|
|
162
|
+
case "utf16":
|
|
163
|
+
return { id, type: "string", value: value.value, format: "utf16" };
|
|
164
|
+
case "utf8":
|
|
165
|
+
return { id, type: "string", value: value.value, format: "utf8" };
|
|
166
|
+
default:
|
|
167
|
+
throw new Error(`unsupported plist value type: ${value.type}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const objEntries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value);
|
|
171
|
+
const entries = [];
|
|
172
|
+
for (let i = 0; i < objEntries.length; i++) {
|
|
173
|
+
const [key, value2] = objEntries[i];
|
|
174
|
+
entries.push({ key: this.toPlistEntry(key), value: this.toPlistEntry(value2) });
|
|
175
|
+
}
|
|
176
|
+
return { id: this.#nextId++, type: "dict", entries };
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
throw new Error(`unexpected value type: ${typeof value}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
#offsetTable;
|
|
183
|
+
#objectRefSize;
|
|
184
|
+
writeRef(bytes, id) {
|
|
185
|
+
writeUint(bytes, id, this.#objectRefSize);
|
|
186
|
+
}
|
|
187
|
+
writeEntry(bytes, entry) {
|
|
188
|
+
if (typeof entry === "number") return entry;
|
|
189
|
+
switch (entry.type) {
|
|
190
|
+
case "null":
|
|
191
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
192
|
+
write.uint8(bytes, 0);
|
|
193
|
+
break;
|
|
194
|
+
case "bool":
|
|
195
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
196
|
+
write.uint8(bytes, entry.value ? 9 : 8);
|
|
197
|
+
break;
|
|
198
|
+
case "int": {
|
|
199
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
200
|
+
switch (entry.size) {
|
|
201
|
+
case 1: {
|
|
202
|
+
write.uint8(bytes, 16);
|
|
203
|
+
write.uint8(bytes, entry.value);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case 2: {
|
|
207
|
+
write.uint8(bytes, 17);
|
|
208
|
+
write.uint16be(bytes, entry.value);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case 4: {
|
|
212
|
+
write.uint8(bytes, 18);
|
|
213
|
+
write.uint32be(bytes, entry.value);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case 8: {
|
|
217
|
+
write.uint8(bytes, 19);
|
|
218
|
+
write.int64be(bytes, entry.value);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case 16: {
|
|
222
|
+
write.uint8(bytes, 20);
|
|
223
|
+
write.intbe(bytes, 16, entry.value);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
default:
|
|
227
|
+
throw new Error(`invalid int size: ${entry.size}`);
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case "real": {
|
|
232
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
233
|
+
switch (entry.size) {
|
|
234
|
+
case 4: {
|
|
235
|
+
write.uint8(bytes, 34);
|
|
236
|
+
write.float32be(bytes, entry.value);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case 8: {
|
|
240
|
+
write.uint8(bytes, 35);
|
|
241
|
+
write.float64be(bytes, entry.value);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
default:
|
|
245
|
+
throw new Error(`invalid real size: ${entry.size}`);
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "date": {
|
|
250
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
251
|
+
write.uint8(bytes, 51);
|
|
252
|
+
const seconds = (entry.value.getTime() - CORE_DATA_EPOCH) / 1e3;
|
|
253
|
+
write.float64be(bytes, seconds);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case "data":
|
|
257
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
258
|
+
writeSizeHeader(bytes, 64, entry.value.length);
|
|
259
|
+
write.bytes(bytes, entry.value);
|
|
260
|
+
break;
|
|
261
|
+
case "string": {
|
|
262
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
263
|
+
switch (entry.format) {
|
|
264
|
+
case "ascii":
|
|
265
|
+
writeSizeHeader(bytes, 80, entry.value.length);
|
|
266
|
+
write.rawString(bytes, entry.value);
|
|
267
|
+
break;
|
|
268
|
+
case "utf16":
|
|
269
|
+
writeSizeHeader(bytes, 96, entry.value.length);
|
|
270
|
+
write.utf16beString(bytes, entry.value);
|
|
271
|
+
break;
|
|
272
|
+
case "utf8":
|
|
273
|
+
writeSizeHeader(bytes, 112, entry.value.length);
|
|
274
|
+
write.utf8String(bytes, entry.value);
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
throw new Error(`invalid string format: ${entry.format}`);
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case "uid":
|
|
282
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
283
|
+
writeUintWithHeader(bytes, entry.value, 128);
|
|
284
|
+
break;
|
|
285
|
+
case "array":
|
|
286
|
+
case "set": {
|
|
287
|
+
const type = entry.type === "array" ? 160 : 176;
|
|
288
|
+
const refs = [];
|
|
289
|
+
for (const item of entry.items) {
|
|
290
|
+
refs.push(this.writeEntry(bytes, item));
|
|
291
|
+
}
|
|
292
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
293
|
+
writeSizeHeader(bytes, type, refs.length);
|
|
294
|
+
refs.forEach((ref) => this.writeRef(bytes, ref));
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "dict": {
|
|
298
|
+
const keyRefs = [];
|
|
299
|
+
const valueRefs = [];
|
|
300
|
+
for (const { key, value } of entry.entries) {
|
|
301
|
+
keyRefs.push(this.writeEntry(bytes, key));
|
|
302
|
+
valueRefs.push(this.writeEntry(bytes, value));
|
|
303
|
+
}
|
|
304
|
+
this.#offsetTable[entry.id] = bytes.written;
|
|
305
|
+
writeSizeHeader(bytes, 208, entry.entries.length);
|
|
306
|
+
keyRefs.forEach((ref) => this.writeRef(bytes, ref));
|
|
307
|
+
valueRefs.forEach((ref) => this.writeRef(bytes, ref));
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return entry.id;
|
|
312
|
+
}
|
|
313
|
+
writeTop(data) {
|
|
314
|
+
const topEntry = this.toPlistEntry(data);
|
|
315
|
+
const numObjects = this.#nextId;
|
|
316
|
+
if (numObjects > MAX_OBJECT_COUNT) {
|
|
317
|
+
throw new Error("MAX_OBJECT_COUNT exceeded");
|
|
318
|
+
}
|
|
319
|
+
this.#objectRefSize = determineUintSize(numObjects);
|
|
320
|
+
this.#offsetTable = Array.from({ length: numObjects });
|
|
321
|
+
const bytes = Bytes.alloc();
|
|
322
|
+
write.bytes(bytes, BPLIST_MAGIC);
|
|
323
|
+
write.int16be(bytes, 12336);
|
|
324
|
+
this.writeEntry(bytes, topEntry);
|
|
325
|
+
const offsetTableOffset = bytes.written;
|
|
326
|
+
const offsetSize = determineUintSize(bytes.written);
|
|
327
|
+
for (let i = 0; i < this.#offsetTable.length; i++) {
|
|
328
|
+
writeUint(bytes, this.#offsetTable[i], offsetSize);
|
|
329
|
+
}
|
|
330
|
+
write.bytes(bytes, new Uint8Array([0, 0, 0, 0, 0, 0]));
|
|
331
|
+
write.uint8(bytes, offsetSize);
|
|
332
|
+
write.uint8(bytes, this.#objectRefSize);
|
|
333
|
+
write.uint64be(bytes, BigInt(numObjects));
|
|
334
|
+
write.uint64be(bytes, BigInt(topEntry.id));
|
|
335
|
+
write.uint64be(bytes, BigInt(offsetTableOffset));
|
|
336
|
+
return bytes.result();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function writeBinaryPlist(data) {
|
|
340
|
+
return new BinaryPlistWriter().writeTop(data);
|
|
341
|
+
}
|
|
342
|
+
export {
|
|
343
|
+
writeBinaryPlist
|
|
344
|
+
};
|
package/index.cjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const bplistReader = require("./bplist-reader.cjs");
|
|
4
|
+
const bplistWriter = require("./bplist-writer.cjs");
|
|
5
|
+
const nsKeyedArchiver = require("./ns-keyed-archiver.cjs");
|
|
6
|
+
const nsKeyedUnarchiver = require("./ns-keyed-unarchiver.cjs");
|
|
7
|
+
const plistReader = require("./plist-reader.cjs");
|
|
8
|
+
const plistWriter = require("./plist-writer.cjs");
|
|
9
|
+
const types = require("./types.cjs");
|
|
10
|
+
exports.readBinaryPlist = bplistReader.readBinaryPlist;
|
|
11
|
+
exports.writeBinaryPlist = bplistWriter.writeBinaryPlist;
|
|
12
|
+
exports.nsKeyedArchive = nsKeyedArchiver.nsKeyedArchive;
|
|
13
|
+
exports.nsKeyedUnarchive = nsKeyedUnarchiver.nsKeyedUnarchive;
|
|
14
|
+
exports.readXmlPlist = plistReader.readXmlPlist;
|
|
15
|
+
exports.writeXmlPlist = plistWriter.writeXmlPlist;
|
|
16
|
+
exports.KeyedArchiverValue = types.KeyedArchiverValue;
|
|
17
|
+
exports.PlistValue = types.PlistValue;
|