@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
package/index.d.cts
ADDED
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { readBinaryPlist } from "./bplist-reader.js";
|
|
2
|
+
import { writeBinaryPlist } from "./bplist-writer.js";
|
|
3
|
+
import { nsKeyedArchive } from "./ns-keyed-archiver.js";
|
|
4
|
+
import { nsKeyedUnarchive } from "./ns-keyed-unarchiver.js";
|
|
5
|
+
import { readXmlPlist } from "./plist-reader.js";
|
|
6
|
+
import { writeXmlPlist } from "./plist-writer.js";
|
|
7
|
+
import { KeyedArchiverValue, PlistValue } from "./types.js";
|
|
8
|
+
export {
|
|
9
|
+
KeyedArchiverValue,
|
|
10
|
+
PlistValue,
|
|
11
|
+
nsKeyedArchive,
|
|
12
|
+
nsKeyedUnarchive,
|
|
13
|
+
readBinaryPlist,
|
|
14
|
+
readXmlPlist,
|
|
15
|
+
writeBinaryPlist,
|
|
16
|
+
writeXmlPlist
|
|
17
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const utils = require("@fuman/utils");
|
|
4
|
+
const _constants = require("./_constants.cjs");
|
|
5
|
+
const _utils = require("./_utils.cjs");
|
|
6
|
+
const types = require("./types.cjs");
|
|
7
|
+
const NsDictionaryHeader = {
|
|
8
|
+
$classname: "NSDictionary",
|
|
9
|
+
$classes: ["NSDictionary", "NSObject"]
|
|
10
|
+
};
|
|
11
|
+
const NsArrayHeader = {
|
|
12
|
+
$classname: "NSArray",
|
|
13
|
+
$classes: ["NSArray", "NSObject"]
|
|
14
|
+
};
|
|
15
|
+
const NsDateHeader = {
|
|
16
|
+
$classname: "NSDate",
|
|
17
|
+
$classes: ["NSDate", "NSObject"]
|
|
18
|
+
};
|
|
19
|
+
class NsKeyedArchiver {
|
|
20
|
+
#values = /* @__PURE__ */ new Map();
|
|
21
|
+
#objects = [];
|
|
22
|
+
#nextUid = 0;
|
|
23
|
+
#allocateUid() {
|
|
24
|
+
return new types.PlistValue("uid", this.#nextUid++);
|
|
25
|
+
}
|
|
26
|
+
#serializeObject(uid, header, object) {
|
|
27
|
+
let headerUid = this.#values.get(header);
|
|
28
|
+
if (!headerUid) {
|
|
29
|
+
headerUid = this.#allocateUid();
|
|
30
|
+
this.#values.set(header, headerUid);
|
|
31
|
+
this.#objects[_utils.safeToNumber(headerUid.value)] = header;
|
|
32
|
+
}
|
|
33
|
+
object.$class = headerUid;
|
|
34
|
+
this.#objects[_utils.safeToNumber(uid.value)] = object;
|
|
35
|
+
return uid;
|
|
36
|
+
}
|
|
37
|
+
#serializeValue(value) {
|
|
38
|
+
const existing = this.#values.get(value);
|
|
39
|
+
if (existing) return existing;
|
|
40
|
+
const uid = this.#allocateUid();
|
|
41
|
+
const uidValue = _utils.safeToNumber(uid.value);
|
|
42
|
+
this.#values.set(value, uid);
|
|
43
|
+
switch (typeof value) {
|
|
44
|
+
case "string":
|
|
45
|
+
case "number":
|
|
46
|
+
case "bigint":
|
|
47
|
+
case "boolean":
|
|
48
|
+
this.#objects[uidValue] = value;
|
|
49
|
+
return uid;
|
|
50
|
+
case "object": {
|
|
51
|
+
if (value === null) utils.unreachable();
|
|
52
|
+
let overrideHeader;
|
|
53
|
+
if (value instanceof types.KeyedArchiverValue) {
|
|
54
|
+
overrideHeader = value.header;
|
|
55
|
+
value = value.value;
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
return this.#serializeObject(uid, overrideHeader, {
|
|
58
|
+
"NS.string": value
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (value instanceof Uint8Array) {
|
|
62
|
+
return this.#serializeObject(uid, overrideHeader, {
|
|
63
|
+
"NS.data": value
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (value == null || typeof value !== "object") {
|
|
67
|
+
throw new Error("invalid value in KeyedArchiverValue", { cause: value });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (value instanceof Uint8Array) {
|
|
71
|
+
this.#objects[uidValue] = value;
|
|
72
|
+
return uid;
|
|
73
|
+
}
|
|
74
|
+
if (value instanceof Date) {
|
|
75
|
+
return this.#serializeObject(uid, overrideHeader ?? NsDateHeader, {
|
|
76
|
+
"NS.time": (value.getTime() - _constants.CORE_DATA_EPOCH) / 1e3
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (value instanceof Set) value = Array.from(value);
|
|
80
|
+
if (value instanceof Map) value = Object.fromEntries(value);
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
const uids = value.map((item) => this.#serializeValue(item));
|
|
83
|
+
return this.#serializeObject(uid, overrideHeader ?? NsArrayHeader, {
|
|
84
|
+
"NS.objects": uids
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const keyUids = [];
|
|
88
|
+
const valueUids = [];
|
|
89
|
+
for (const [key, dictValue] of Object.entries(value)) {
|
|
90
|
+
keyUids.push(this.#serializeValue(key));
|
|
91
|
+
valueUids.push(this.#serializeValue(dictValue));
|
|
92
|
+
}
|
|
93
|
+
return this.#serializeObject(uid, overrideHeader ?? NsDictionaryHeader, {
|
|
94
|
+
"NS.keys": keyUids,
|
|
95
|
+
"NS.objects": valueUids
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
throw new Error(`invalid value: ${JSON.stringify(value)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
writeTop(value) {
|
|
103
|
+
this.#objects.push("$null");
|
|
104
|
+
this.#values.set(null, this.#allocateUid());
|
|
105
|
+
let top;
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
const obj = {};
|
|
108
|
+
for (let i = 0; i < value.length; i++) {
|
|
109
|
+
obj[`$${i}`] = this.#serializeValue(value[i]);
|
|
110
|
+
}
|
|
111
|
+
top = obj;
|
|
112
|
+
} else {
|
|
113
|
+
top = {
|
|
114
|
+
root: this.#serializeValue(value)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
$version: _constants.NS_KEYED_ARCHIVER_VERSION,
|
|
119
|
+
$archiver: "NSKeyedArchiver",
|
|
120
|
+
$top: top,
|
|
121
|
+
$objects: this.#objects
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function nsKeyedArchive(data) {
|
|
126
|
+
const archiver = new NsKeyedArchiver();
|
|
127
|
+
return archiver.writeTop(data);
|
|
128
|
+
}
|
|
129
|
+
exports.nsKeyedArchive = nsKeyedArchive;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function nsKeyedArchive(data: unknown[] | Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function nsKeyedArchive(data: unknown[] | Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { unreachable } from "@fuman/utils";
|
|
2
|
+
import { CORE_DATA_EPOCH, NS_KEYED_ARCHIVER_VERSION } from "./_constants.js";
|
|
3
|
+
import { safeToNumber } from "./_utils.js";
|
|
4
|
+
import { PlistValue, KeyedArchiverValue } from "./types.js";
|
|
5
|
+
const NsDictionaryHeader = {
|
|
6
|
+
$classname: "NSDictionary",
|
|
7
|
+
$classes: ["NSDictionary", "NSObject"]
|
|
8
|
+
};
|
|
9
|
+
const NsArrayHeader = {
|
|
10
|
+
$classname: "NSArray",
|
|
11
|
+
$classes: ["NSArray", "NSObject"]
|
|
12
|
+
};
|
|
13
|
+
const NsDateHeader = {
|
|
14
|
+
$classname: "NSDate",
|
|
15
|
+
$classes: ["NSDate", "NSObject"]
|
|
16
|
+
};
|
|
17
|
+
class NsKeyedArchiver {
|
|
18
|
+
#values = /* @__PURE__ */ new Map();
|
|
19
|
+
#objects = [];
|
|
20
|
+
#nextUid = 0;
|
|
21
|
+
#allocateUid() {
|
|
22
|
+
return new PlistValue("uid", this.#nextUid++);
|
|
23
|
+
}
|
|
24
|
+
#serializeObject(uid, header, object) {
|
|
25
|
+
let headerUid = this.#values.get(header);
|
|
26
|
+
if (!headerUid) {
|
|
27
|
+
headerUid = this.#allocateUid();
|
|
28
|
+
this.#values.set(header, headerUid);
|
|
29
|
+
this.#objects[safeToNumber(headerUid.value)] = header;
|
|
30
|
+
}
|
|
31
|
+
object.$class = headerUid;
|
|
32
|
+
this.#objects[safeToNumber(uid.value)] = object;
|
|
33
|
+
return uid;
|
|
34
|
+
}
|
|
35
|
+
#serializeValue(value) {
|
|
36
|
+
const existing = this.#values.get(value);
|
|
37
|
+
if (existing) return existing;
|
|
38
|
+
const uid = this.#allocateUid();
|
|
39
|
+
const uidValue = safeToNumber(uid.value);
|
|
40
|
+
this.#values.set(value, uid);
|
|
41
|
+
switch (typeof value) {
|
|
42
|
+
case "string":
|
|
43
|
+
case "number":
|
|
44
|
+
case "bigint":
|
|
45
|
+
case "boolean":
|
|
46
|
+
this.#objects[uidValue] = value;
|
|
47
|
+
return uid;
|
|
48
|
+
case "object": {
|
|
49
|
+
if (value === null) unreachable();
|
|
50
|
+
let overrideHeader;
|
|
51
|
+
if (value instanceof KeyedArchiverValue) {
|
|
52
|
+
overrideHeader = value.header;
|
|
53
|
+
value = value.value;
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
return this.#serializeObject(uid, overrideHeader, {
|
|
56
|
+
"NS.string": value
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (value instanceof Uint8Array) {
|
|
60
|
+
return this.#serializeObject(uid, overrideHeader, {
|
|
61
|
+
"NS.data": value
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (value == null || typeof value !== "object") {
|
|
65
|
+
throw new Error("invalid value in KeyedArchiverValue", { cause: value });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (value instanceof Uint8Array) {
|
|
69
|
+
this.#objects[uidValue] = value;
|
|
70
|
+
return uid;
|
|
71
|
+
}
|
|
72
|
+
if (value instanceof Date) {
|
|
73
|
+
return this.#serializeObject(uid, overrideHeader ?? NsDateHeader, {
|
|
74
|
+
"NS.time": (value.getTime() - CORE_DATA_EPOCH) / 1e3
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (value instanceof Set) value = Array.from(value);
|
|
78
|
+
if (value instanceof Map) value = Object.fromEntries(value);
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
const uids = value.map((item) => this.#serializeValue(item));
|
|
81
|
+
return this.#serializeObject(uid, overrideHeader ?? NsArrayHeader, {
|
|
82
|
+
"NS.objects": uids
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const keyUids = [];
|
|
86
|
+
const valueUids = [];
|
|
87
|
+
for (const [key, dictValue] of Object.entries(value)) {
|
|
88
|
+
keyUids.push(this.#serializeValue(key));
|
|
89
|
+
valueUids.push(this.#serializeValue(dictValue));
|
|
90
|
+
}
|
|
91
|
+
return this.#serializeObject(uid, overrideHeader ?? NsDictionaryHeader, {
|
|
92
|
+
"NS.keys": keyUids,
|
|
93
|
+
"NS.objects": valueUids
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
throw new Error(`invalid value: ${JSON.stringify(value)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
writeTop(value) {
|
|
101
|
+
this.#objects.push("$null");
|
|
102
|
+
this.#values.set(null, this.#allocateUid());
|
|
103
|
+
let top;
|
|
104
|
+
if (Array.isArray(value)) {
|
|
105
|
+
const obj = {};
|
|
106
|
+
for (let i = 0; i < value.length; i++) {
|
|
107
|
+
obj[`$${i}`] = this.#serializeValue(value[i]);
|
|
108
|
+
}
|
|
109
|
+
top = obj;
|
|
110
|
+
} else {
|
|
111
|
+
top = {
|
|
112
|
+
root: this.#serializeValue(value)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
$version: NS_KEYED_ARCHIVER_VERSION,
|
|
117
|
+
$archiver: "NSKeyedArchiver",
|
|
118
|
+
$top: top,
|
|
119
|
+
$objects: this.#objects
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function nsKeyedArchive(data) {
|
|
124
|
+
const archiver = new NsKeyedArchiver();
|
|
125
|
+
return archiver.writeTop(data);
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
nsKeyedArchive
|
|
129
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const utils = require("@fuman/utils");
|
|
4
|
+
const _constants = require("./_constants.cjs");
|
|
5
|
+
const _utils = require("./_utils.cjs");
|
|
6
|
+
const types = require("./types.cjs");
|
|
7
|
+
function isUid(value) {
|
|
8
|
+
return value instanceof types.PlistValue && value.type === "uid";
|
|
9
|
+
}
|
|
10
|
+
function nsKeyedUnarchive(data, params) {
|
|
11
|
+
const { preserveType = false, extraHandlers } = params ?? {};
|
|
12
|
+
const extraHandlersEntries = extraHandlers ? utils.objectEntries(extraHandlers) : void 0;
|
|
13
|
+
utils.assert(typeof data === "object" && data !== null, "data is not an object");
|
|
14
|
+
utils.unsafeCastType(data);
|
|
15
|
+
if (data.$version !== _constants.NS_KEYED_ARCHIVER_VERSION) {
|
|
16
|
+
throw new Error("unsupported NSKeyedArchiver version");
|
|
17
|
+
}
|
|
18
|
+
if (data.$archiver !== "NSKeyedArchiver") {
|
|
19
|
+
throw new Error("invalid or missing $archiver");
|
|
20
|
+
}
|
|
21
|
+
utils.assertHasKey(data, "$objects");
|
|
22
|
+
utils.assert(Array.isArray(data.$objects), "$objects is not an array");
|
|
23
|
+
utils.assertHasKey(data, "$top");
|
|
24
|
+
utils.assert(typeof data.$top === "object" && data.$top !== null, "$top is not an object");
|
|
25
|
+
const objects = data.$objects;
|
|
26
|
+
const pending = /* @__PURE__ */ new Map();
|
|
27
|
+
function parseByUid(uid) {
|
|
28
|
+
const obj = objects[uid];
|
|
29
|
+
if (obj === "$null") return null;
|
|
30
|
+
if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean" || obj instanceof Uint8Array) return obj;
|
|
31
|
+
if (typeof obj === "object" && obj !== null) {
|
|
32
|
+
utils.assertHasKey(obj, "$class");
|
|
33
|
+
utils.assert(isUid(obj.$class), "$class is not a UID");
|
|
34
|
+
const header = objects[_utils.safeToNumber(obj.$class.value)];
|
|
35
|
+
utils.assert(typeof header === "object" && header !== null, "referenced $class is not an object");
|
|
36
|
+
utils.assertHasKey(header, "$classes");
|
|
37
|
+
utils.assert(Array.isArray(header.$classes), "$classes is not an array");
|
|
38
|
+
utils.unsafeCastType(header);
|
|
39
|
+
const classes = header.$classes;
|
|
40
|
+
if (extraHandlersEntries) {
|
|
41
|
+
for (const [key, handler] of extraHandlersEntries) {
|
|
42
|
+
if (classes.includes(key)) {
|
|
43
|
+
return handler(obj, header, objects);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (classes.includes("NSDictionary")) {
|
|
48
|
+
utils.assertHasKey(obj, "NS.keys");
|
|
49
|
+
utils.assertHasKey(obj, "NS.objects");
|
|
50
|
+
utils.assert(Array.isArray(obj["NS.keys"]), "NS.keys is not an array");
|
|
51
|
+
utils.assert(Array.isArray(obj["NS.objects"]), "NS.objects is not an array");
|
|
52
|
+
const keys = obj["NS.keys"];
|
|
53
|
+
const values = obj["NS.objects"];
|
|
54
|
+
if (keys.length !== values.length) {
|
|
55
|
+
throw new Error("invalid NSDictionary");
|
|
56
|
+
}
|
|
57
|
+
const result2 = {};
|
|
58
|
+
pending.set(uid, result2);
|
|
59
|
+
for (let i = 0; i < keys.length; i++) {
|
|
60
|
+
utils.assert(isUid(keys[i]), "key is not a UID");
|
|
61
|
+
utils.assert(isUid(values[i]), "value is not a UID");
|
|
62
|
+
const keyUid = _utils.safeToNumber(keys[i].value);
|
|
63
|
+
const valueUid = _utils.safeToNumber(values[i].value);
|
|
64
|
+
let parsedKey = parseByUid(keyUid);
|
|
65
|
+
if (preserveType && parsedKey instanceof types.KeyedArchiverValue) {
|
|
66
|
+
parsedKey = parsedKey.value;
|
|
67
|
+
}
|
|
68
|
+
utils.assert(typeof parsedKey === "string" || typeof parsedKey === "number", "key is not a string or number");
|
|
69
|
+
if (pending.has(valueUid)) {
|
|
70
|
+
result2[parsedKey] = pending.get(valueUid);
|
|
71
|
+
} else {
|
|
72
|
+
result2[parsedKey] = parseByUid(valueUid);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
pending.delete(uid);
|
|
76
|
+
return preserveType ? new types.KeyedArchiverValue(header, result2) : result2;
|
|
77
|
+
}
|
|
78
|
+
if (classes.includes("NSDate")) {
|
|
79
|
+
utils.assertHasKey(obj, "NS.time");
|
|
80
|
+
utils.assert(typeof obj["NS.time"] === "number", "NS.time is not a number");
|
|
81
|
+
const val = new Date(obj["NS.time"] * 1e3 + _constants.CORE_DATA_EPOCH);
|
|
82
|
+
return preserveType ? new types.KeyedArchiverValue(header, val) : val;
|
|
83
|
+
}
|
|
84
|
+
const isSet = classes.includes("NSSet");
|
|
85
|
+
if (classes.includes("NSArray") || isSet) {
|
|
86
|
+
utils.assertHasKey(obj, "NS.objects");
|
|
87
|
+
utils.assert(Array.isArray(obj["NS.objects"]));
|
|
88
|
+
const result2 = [];
|
|
89
|
+
pending.set(uid, result2);
|
|
90
|
+
for (const value of obj["NS.objects"]) {
|
|
91
|
+
const valueUid = _utils.safeToNumber(value.value);
|
|
92
|
+
if (pending.has(valueUid)) {
|
|
93
|
+
result2.push(pending.get(valueUid));
|
|
94
|
+
} else {
|
|
95
|
+
result2.push(parseByUid(valueUid));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
pending.delete(uid);
|
|
99
|
+
if (isSet) {
|
|
100
|
+
const set = new Set(result2);
|
|
101
|
+
return preserveType ? new types.KeyedArchiverValue(header, set) : set;
|
|
102
|
+
}
|
|
103
|
+
return preserveType ? new types.KeyedArchiverValue(header, result2) : result2;
|
|
104
|
+
}
|
|
105
|
+
if (classes.includes("NSString")) {
|
|
106
|
+
utils.assertHasKey(obj, "NS.string");
|
|
107
|
+
utils.assert(typeof obj["NS.string"] === "string");
|
|
108
|
+
const val = obj["NS.string"];
|
|
109
|
+
return preserveType ? new types.KeyedArchiverValue(header, val) : val;
|
|
110
|
+
}
|
|
111
|
+
if (classes.includes("NSData")) {
|
|
112
|
+
utils.assertHasKey(obj, "NS.data");
|
|
113
|
+
utils.assert(obj["NS.data"] instanceof Uint8Array);
|
|
114
|
+
const val = obj["NS.data"];
|
|
115
|
+
return preserveType ? new types.KeyedArchiverValue(header, val) : val;
|
|
116
|
+
}
|
|
117
|
+
const result = {};
|
|
118
|
+
pending.set(uid, result);
|
|
119
|
+
for (const key in obj) {
|
|
120
|
+
if (key === "$class") continue;
|
|
121
|
+
const value = obj[key];
|
|
122
|
+
if (isUid(value)) {
|
|
123
|
+
const valueUid = _utils.safeToNumber(value.value);
|
|
124
|
+
if (pending.has(valueUid)) {
|
|
125
|
+
result[key] = pending.get(valueUid);
|
|
126
|
+
} else {
|
|
127
|
+
result[key] = parseByUid(valueUid);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
result[key] = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
pending.delete(uid);
|
|
134
|
+
return new types.KeyedArchiverValue(header, result);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!("root" in data.$top)) {
|
|
138
|
+
const count = Object.keys(data.$top).length;
|
|
139
|
+
const array = [];
|
|
140
|
+
for (let i = 0; i < count; i++) {
|
|
141
|
+
const key = `$${i}`;
|
|
142
|
+
utils.assertHasKey(data.$top, key);
|
|
143
|
+
utils.assert(isUid(data.$top[key]), `${key} is not a UID`);
|
|
144
|
+
array.push(parseByUid(_utils.safeToNumber(data.$top[key].value)));
|
|
145
|
+
}
|
|
146
|
+
return array;
|
|
147
|
+
}
|
|
148
|
+
utils.assert(isUid(data.$top.root), "$top.root is not a UID");
|
|
149
|
+
const topObject = parseByUid(_utils.safeToNumber(data.$top.root.value));
|
|
150
|
+
if (typeof topObject !== "object" || topObject === null) {
|
|
151
|
+
throw new Error("top object is not an object");
|
|
152
|
+
}
|
|
153
|
+
return topObject;
|
|
154
|
+
}
|
|
155
|
+
exports.nsKeyedUnarchive = nsKeyedUnarchive;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { KeyedArchiverValueHeader } from './types.js';
|
|
2
|
+
export declare function nsKeyedUnarchive(data: unknown, params?: {
|
|
3
|
+
/**
|
|
4
|
+
* if true, every value will be wrapped in {@link KeyedArchiverValue},
|
|
5
|
+
* allowing for lossless operation. otherwise, only unknown objects will be wrapped
|
|
6
|
+
*/
|
|
7
|
+
preserveType?: boolean;
|
|
8
|
+
extraHandlers?: Record<string, (value: unknown, header: KeyedArchiverValueHeader, objects: unknown[]) => unknown>;
|
|
9
|
+
}): unknown[] | Record<string, unknown>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { KeyedArchiverValueHeader } from './types.js';
|
|
2
|
+
export declare function nsKeyedUnarchive(data: unknown, params?: {
|
|
3
|
+
/**
|
|
4
|
+
* if true, every value will be wrapped in {@link KeyedArchiverValue},
|
|
5
|
+
* allowing for lossless operation. otherwise, only unknown objects will be wrapped
|
|
6
|
+
*/
|
|
7
|
+
preserveType?: boolean;
|
|
8
|
+
extraHandlers?: Record<string, (value: unknown, header: KeyedArchiverValueHeader, objects: unknown[]) => unknown>;
|
|
9
|
+
}): unknown[] | Record<string, unknown>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { objectEntries, assert, unsafeCastType, assertHasKey } from "@fuman/utils";
|
|
2
|
+
import { NS_KEYED_ARCHIVER_VERSION, CORE_DATA_EPOCH } from "./_constants.js";
|
|
3
|
+
import { safeToNumber } from "./_utils.js";
|
|
4
|
+
import { PlistValue, KeyedArchiverValue } from "./types.js";
|
|
5
|
+
function isUid(value) {
|
|
6
|
+
return value instanceof PlistValue && value.type === "uid";
|
|
7
|
+
}
|
|
8
|
+
function nsKeyedUnarchive(data, params) {
|
|
9
|
+
const { preserveType = false, extraHandlers } = params ?? {};
|
|
10
|
+
const extraHandlersEntries = extraHandlers ? objectEntries(extraHandlers) : void 0;
|
|
11
|
+
assert(typeof data === "object" && data !== null, "data is not an object");
|
|
12
|
+
unsafeCastType(data);
|
|
13
|
+
if (data.$version !== NS_KEYED_ARCHIVER_VERSION) {
|
|
14
|
+
throw new Error("unsupported NSKeyedArchiver version");
|
|
15
|
+
}
|
|
16
|
+
if (data.$archiver !== "NSKeyedArchiver") {
|
|
17
|
+
throw new Error("invalid or missing $archiver");
|
|
18
|
+
}
|
|
19
|
+
assertHasKey(data, "$objects");
|
|
20
|
+
assert(Array.isArray(data.$objects), "$objects is not an array");
|
|
21
|
+
assertHasKey(data, "$top");
|
|
22
|
+
assert(typeof data.$top === "object" && data.$top !== null, "$top is not an object");
|
|
23
|
+
const objects = data.$objects;
|
|
24
|
+
const pending = /* @__PURE__ */ new Map();
|
|
25
|
+
function parseByUid(uid) {
|
|
26
|
+
const obj = objects[uid];
|
|
27
|
+
if (obj === "$null") return null;
|
|
28
|
+
if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean" || obj instanceof Uint8Array) return obj;
|
|
29
|
+
if (typeof obj === "object" && obj !== null) {
|
|
30
|
+
assertHasKey(obj, "$class");
|
|
31
|
+
assert(isUid(obj.$class), "$class is not a UID");
|
|
32
|
+
const header = objects[safeToNumber(obj.$class.value)];
|
|
33
|
+
assert(typeof header === "object" && header !== null, "referenced $class is not an object");
|
|
34
|
+
assertHasKey(header, "$classes");
|
|
35
|
+
assert(Array.isArray(header.$classes), "$classes is not an array");
|
|
36
|
+
unsafeCastType(header);
|
|
37
|
+
const classes = header.$classes;
|
|
38
|
+
if (extraHandlersEntries) {
|
|
39
|
+
for (const [key, handler] of extraHandlersEntries) {
|
|
40
|
+
if (classes.includes(key)) {
|
|
41
|
+
return handler(obj, header, objects);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (classes.includes("NSDictionary")) {
|
|
46
|
+
assertHasKey(obj, "NS.keys");
|
|
47
|
+
assertHasKey(obj, "NS.objects");
|
|
48
|
+
assert(Array.isArray(obj["NS.keys"]), "NS.keys is not an array");
|
|
49
|
+
assert(Array.isArray(obj["NS.objects"]), "NS.objects is not an array");
|
|
50
|
+
const keys = obj["NS.keys"];
|
|
51
|
+
const values = obj["NS.objects"];
|
|
52
|
+
if (keys.length !== values.length) {
|
|
53
|
+
throw new Error("invalid NSDictionary");
|
|
54
|
+
}
|
|
55
|
+
const result2 = {};
|
|
56
|
+
pending.set(uid, result2);
|
|
57
|
+
for (let i = 0; i < keys.length; i++) {
|
|
58
|
+
assert(isUid(keys[i]), "key is not a UID");
|
|
59
|
+
assert(isUid(values[i]), "value is not a UID");
|
|
60
|
+
const keyUid = safeToNumber(keys[i].value);
|
|
61
|
+
const valueUid = safeToNumber(values[i].value);
|
|
62
|
+
let parsedKey = parseByUid(keyUid);
|
|
63
|
+
if (preserveType && parsedKey instanceof KeyedArchiverValue) {
|
|
64
|
+
parsedKey = parsedKey.value;
|
|
65
|
+
}
|
|
66
|
+
assert(typeof parsedKey === "string" || typeof parsedKey === "number", "key is not a string or number");
|
|
67
|
+
if (pending.has(valueUid)) {
|
|
68
|
+
result2[parsedKey] = pending.get(valueUid);
|
|
69
|
+
} else {
|
|
70
|
+
result2[parsedKey] = parseByUid(valueUid);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
pending.delete(uid);
|
|
74
|
+
return preserveType ? new KeyedArchiverValue(header, result2) : result2;
|
|
75
|
+
}
|
|
76
|
+
if (classes.includes("NSDate")) {
|
|
77
|
+
assertHasKey(obj, "NS.time");
|
|
78
|
+
assert(typeof obj["NS.time"] === "number", "NS.time is not a number");
|
|
79
|
+
const val = new Date(obj["NS.time"] * 1e3 + CORE_DATA_EPOCH);
|
|
80
|
+
return preserveType ? new KeyedArchiverValue(header, val) : val;
|
|
81
|
+
}
|
|
82
|
+
const isSet = classes.includes("NSSet");
|
|
83
|
+
if (classes.includes("NSArray") || isSet) {
|
|
84
|
+
assertHasKey(obj, "NS.objects");
|
|
85
|
+
assert(Array.isArray(obj["NS.objects"]));
|
|
86
|
+
const result2 = [];
|
|
87
|
+
pending.set(uid, result2);
|
|
88
|
+
for (const value of obj["NS.objects"]) {
|
|
89
|
+
const valueUid = safeToNumber(value.value);
|
|
90
|
+
if (pending.has(valueUid)) {
|
|
91
|
+
result2.push(pending.get(valueUid));
|
|
92
|
+
} else {
|
|
93
|
+
result2.push(parseByUid(valueUid));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
pending.delete(uid);
|
|
97
|
+
if (isSet) {
|
|
98
|
+
const set = new Set(result2);
|
|
99
|
+
return preserveType ? new KeyedArchiverValue(header, set) : set;
|
|
100
|
+
}
|
|
101
|
+
return preserveType ? new KeyedArchiverValue(header, result2) : result2;
|
|
102
|
+
}
|
|
103
|
+
if (classes.includes("NSString")) {
|
|
104
|
+
assertHasKey(obj, "NS.string");
|
|
105
|
+
assert(typeof obj["NS.string"] === "string");
|
|
106
|
+
const val = obj["NS.string"];
|
|
107
|
+
return preserveType ? new KeyedArchiverValue(header, val) : val;
|
|
108
|
+
}
|
|
109
|
+
if (classes.includes("NSData")) {
|
|
110
|
+
assertHasKey(obj, "NS.data");
|
|
111
|
+
assert(obj["NS.data"] instanceof Uint8Array);
|
|
112
|
+
const val = obj["NS.data"];
|
|
113
|
+
return preserveType ? new KeyedArchiverValue(header, val) : val;
|
|
114
|
+
}
|
|
115
|
+
const result = {};
|
|
116
|
+
pending.set(uid, result);
|
|
117
|
+
for (const key in obj) {
|
|
118
|
+
if (key === "$class") continue;
|
|
119
|
+
const value = obj[key];
|
|
120
|
+
if (isUid(value)) {
|
|
121
|
+
const valueUid = safeToNumber(value.value);
|
|
122
|
+
if (pending.has(valueUid)) {
|
|
123
|
+
result[key] = pending.get(valueUid);
|
|
124
|
+
} else {
|
|
125
|
+
result[key] = parseByUid(valueUid);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
result[key] = value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
pending.delete(uid);
|
|
132
|
+
return new KeyedArchiverValue(header, result);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!("root" in data.$top)) {
|
|
136
|
+
const count = Object.keys(data.$top).length;
|
|
137
|
+
const array = [];
|
|
138
|
+
for (let i = 0; i < count; i++) {
|
|
139
|
+
const key = `$${i}`;
|
|
140
|
+
assertHasKey(data.$top, key);
|
|
141
|
+
assert(isUid(data.$top[key]), `${key} is not a UID`);
|
|
142
|
+
array.push(parseByUid(safeToNumber(data.$top[key].value)));
|
|
143
|
+
}
|
|
144
|
+
return array;
|
|
145
|
+
}
|
|
146
|
+
assert(isUid(data.$top.root), "$top.root is not a UID");
|
|
147
|
+
const topObject = parseByUid(safeToNumber(data.$top.root.value));
|
|
148
|
+
if (typeof topObject !== "object" || topObject === null) {
|
|
149
|
+
throw new Error("top object is not an object");
|
|
150
|
+
}
|
|
151
|
+
return topObject;
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
nsKeyedUnarchive
|
|
155
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fuman/plist",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "apple property list reader and writer",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@fuman/io": "^0.0.14",
|
|
9
|
+
"@fuman/utils": "^0.0.14"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./index.d.ts",
|
|
15
|
+
"default": "./index.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./index.d.cts",
|
|
19
|
+
"default": "./index.cjs"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"author": "",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/teidesu/fuman.git"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {}
|
|
30
|
+
}
|