// Parses and generates Apple Info.plist files // Example file: /* BuildMachineOSBuild 20F71 CFBundleDevelopmentRegion en CFBundleExecutable WebView CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier org.turbowarp.webviews.mac CFBundleInfoDictionaryVersion 6.0 CFBundleName WebView CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSupportedPlatforms MacOSX CFBundleVersion 1 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild 12E507 DTPlatformName macosx DTPlatformVersion 11.3 DTSDKBuild 20E214 DTSDKName macosx11.3 DTXcode 1251 DTXcodeBuild 12E507 LSApplicationCategoryType public.app-category.games LSMinimumSystemVersion 10.12 NSMainStoryboardFile Main NSPrincipalClass NSApplication ExampleBooleanTrue ExampleBooleanFalse */ const xmlToValue = (node) => { if (node.tagName === 'dict') { const result = {}; for (const child of node.children) { if (child.tagName === 'key') { result[child.textContent] = xmlToValue(child.nextElementSibling); } } return result; } else if (node.tagName === 'array') { return Array.from(node.children).map(xmlToValue); } else if (node.tagName === 'string') { return node.textContent; } else if (node.tagName === 'true') { return true; } else if (node.tagName === 'false') { return false; } console.warn('unknown plist xml', node); return null; }; const valueToXml = (doc, value) => { if (Array.isArray(value)) { const node = doc.createElement('array'); for (const listItem of value) { node.appendChild(valueToXml(doc, listItem)); } return node; } else if (typeof value === 'object') { const node = doc.createElement('dict'); for (const [key, keyValue] of Object.entries(value)) { const keyNode = doc.createElement('key'); keyNode.textContent = key; const valueNode = valueToXml(doc, keyValue); node.appendChild(keyNode); node.appendChild(valueNode); } return node; } else if (typeof value === 'string') { const node = doc.createElement('string'); node.textContent = value; return node; } else if (typeof value === 'boolean') { const node = doc.createElement(value.toString()); return node; } console.warn('unknown plist value', value); return valueToXml(doc, `${value}`); }; export const parsePlist = (string) => { const xml = new DOMParser().parseFromString(string, 'text/xml'); const rootNode = xml.children[0]; const rootDict = rootNode.children[0]; return xmlToValue(rootDict); }; export const generatePlist = (values) => { const xml = document.implementation.createDocument(null, "plist"); const rootNode = xml.documentElement; rootNode.setAttribute('version', '1.0'); rootNode.appendChild(valueToXml(xml, values)); const serialized = new XMLSerializer().serializeToString(xml); return ` ${serialized}`; };