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 = '\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}'`); } } }