apple_configurator/src/plist.ts

86 lines
2.3 KiB
TypeScript

const DTYPE_QUALIFIED = "plist";
const DTYPE_PUBLIC = "-//Apple Inc//DTD PLIST 1.0//EN";
const DTYPE_SYSTEM = "http://www.apple.com/DTDs/PropertyList-1.0.dtd";
const PLIST_VERSION = "1.0";
const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n';
export class Plist {
static stringify(value: unknown) {
const root = document.implementation.createDocument(
null,
"plist",
document.implementation.createDocumentType(
DTYPE_QUALIFIED,
DTYPE_PUBLIC,
DTYPE_SYSTEM,
),
);
root.lastElementChild!.setAttribute("version", PLIST_VERSION);
new Serializer(root)
.insert(root.lastElementChild!, value);
return XML_HEADER + new XMLSerializer().serializeToString(root);
}
}
class Serializer {
#doc: XMLDocument;
constructor(doc: XMLDocument) {
this.#doc = doc;
}
insert(cur: Element, value: unknown) {
switch (typeof value) {
case "bigint": {
const int = this.#doc.createElement("integer");
int.textContent = value.toString();
cur.append(int);
break;
}
case "number": {
const real = this.#doc.createElement("real");
real.textContent = value.toString();
cur.append(real);
break;
}
case "object":
if (Array.isArray(value)) {
const arr = this.#doc.createElement("array");
cur.append(arr);
for (const val of value) {
this.insert(arr, val);
}
} else if (value instanceof Uint8Array) {
const data = this.#doc.createElement("data");
data.textContent = btoa(String.fromCharCode(...value));
cur.append(data);
} else if (value !== null) {
const dict = this.#doc.createElement("dict");
cur.append(dict);
for (const [key, val] of Object.entries(value)) {
const pKey = this.#doc.createElement("key");
pKey.textContent = key;
dict.append(pKey);
this.insert(dict, val);
}
} else {
throw new TypeError(`Unsupported plist type 'null'`);
}
break;
case "string": {
const str = this.#doc.createElement("string");
str.textContent = value;
cur.append(str);
break;
}
default:
throw new TypeError(`Unsupported plist type '${typeof value}'`);
}
}
}