···5 * @see {serialize_xml_to_plist_object}
6 * @see {deserialize_plist_xml_to_plist_object}
7 * @link https://code.google.com/archive/p/networkpx/wikis/PlistSpec.wiki
8- * @description A single-file zero dependency parser for serializing and deserializing Apple Info.plist files.
9 * Only supports plists with a single root element, that contain
10 * only string, number, boolean, array(also nested), and dictionary(also nested) values. Attempts to conform to
11 * Apple's formatting and specifications for plists in the parts of the spec that are supported.
···8889// Supporting real values is not supported by this parser, we only support numbers.
90function serialize_number(value: number, indent: string) {
91- return `${indent}<number>${value}</number>`
92}
9394function serialize_boolean(value: boolean, indent: string) {
···129}
130131/* Deserialization */
132-const plist_parser_regex = /<(dict|array|string|number)>([\s\S]*?)<\/\1>/
133function naive_ends_with_closing_tag(xml_fragment: string, _element_name: string) {
134 const open = xml_fragment.lastIndexOf('<')
135 return open !== -1 && xml_fragment[open + 1] === '/' && xml_fragment[xml_fragment.length - 1] === '>'
···151 if (tag === 'string') return unescape_xml(content!)
152 else if (tag === 'dict') return deserialize_plist_dict_to_object(content!)
153 else if (tag === 'array') return deserialize_plist_array_to_object(content!)
154- else if (tag === 'number' && Number.isSafeInteger(Number(content))) return parseInt(content!) // Real's probably could be supported if we added a check for if the value is a fixed int or not.
155 else if (tag === 'true' || tag === 'false') return tag === 'true'
00000156157 throw new Error('invalid_xml' as plist_parser_error_types, {
158 cause: { xml: xml_fragment },
···167 // Starting the loop at 1 because we do not want to look at the first element.
168 for (let index = 1; index + 1 < parts.length; index += 2) {
169 const key_part = parts[index]
170- if (typeof key_part !== 'string')
171 throw new Error('invalid_xml' as plist_parser_error_types, { cause: { xml: xml_fragment, key_part, value_part: parts[index + 1] } })
172173 const value_part = parts[index + 1]
···180 return result
181}
182183-const plist_parser_array_self_closing_tag_regex = /<(dict|array|string|true|false)\/>/
184function deserialize_plist_array_to_object(xml_fragment: string) {
185 const result: plist_value[] = []
186 let remaining = xml_fragment.trim()
187188 while (remaining) {
189 const xml_part_length = remaining.length
190- const [content, tag] = remaining.match(plist_parser_array_self_closing_tag_regex) ?? []
191-192- if (content) {
193- if (tag === 'array') result.push([])
194- else if (tag === 'dict') result.push({})
195-196- remaining = remaining.substring(content.length).trim()
197- continue
198- }
199-200 if (!plist_parser_regex.test(remaining))
201 throw new Error('unsupported_tag' as plist_parser_error_types, { cause: { xml: xml_fragment, remaining } })
202203- const [item] = remaining.match(plist_parser_regex)! // arrays only support booleans, strings, dicts, and arrays.
204 result.push(deserialize_xml_fragment_to_plist_value_object(item))
205 remaining = remaining.substring(item.length).trim()
206 // sanity check: ensure we made progress
···5 * @see {serialize_xml_to_plist_object}
6 * @see {deserialize_plist_xml_to_plist_object}
7 * @link https://code.google.com/archive/p/networkpx/wikis/PlistSpec.wiki
8+ * @description A single-file zero dependency parser for serializing and deserializing Apple Info.plist files. Supprts only XML formatted plists.
9 * Only supports plists with a single root element, that contain
10 * only string, number, boolean, array(also nested), and dictionary(also nested) values. Attempts to conform to
11 * Apple's formatting and specifications for plists in the parts of the spec that are supported.
···8889// Supporting real values is not supported by this parser, we only support numbers.
90function serialize_number(value: number, indent: string) {
91+ return `${indent}<integer>${value}</integer>`
92}
9394function serialize_boolean(value: boolean, indent: string) {
···129}
130131/* Deserialization */
132+const plist_parser_regex = /<(dict|array|string|integer)>([\s\S]*?)<\/\1>/
133function naive_ends_with_closing_tag(xml_fragment: string, _element_name: string) {
134 const open = xml_fragment.lastIndexOf('<')
135 return open !== -1 && xml_fragment[open + 1] === '/' && xml_fragment[xml_fragment.length - 1] === '>'
···151 if (tag === 'string') return unescape_xml(content!)
152 else if (tag === 'dict') return deserialize_plist_dict_to_object(content!)
153 else if (tag === 'array') return deserialize_plist_array_to_object(content!)
0154 else if (tag === 'true' || tag === 'false') return tag === 'true'
155+ else if (tag === 'integer') {
156+ const number_value = Number(content!)
157+ if (Number.isSafeInteger(number_value))
158+ return parseInt(content!) // Real's probably could be supported if we added a check for if the value is a fixed int or not.
159+ }
160161 throw new Error('invalid_xml' as plist_parser_error_types, {
162 cause: { xml: xml_fragment },
···171 // Starting the loop at 1 because we do not want to look at the first element.
172 for (let index = 1; index + 1 < parts.length; index += 2) {
173 const key_part = parts[index]
174+ if (typeof key_part !== 'string' || result[key_part])
175 throw new Error('invalid_xml' as plist_parser_error_types, { cause: { xml: xml_fragment, key_part, value_part: parts[index + 1] } })
176177 const value_part = parts[index + 1]
···184 return result
185}
1860187function deserialize_plist_array_to_object(xml_fragment: string) {
188 const result: plist_value[] = []
189 let remaining = xml_fragment.trim()
190191 while (remaining) {
192 const xml_part_length = remaining.length
0000000000193 if (!plist_parser_regex.test(remaining))
194 throw new Error('unsupported_tag' as plist_parser_error_types, { cause: { xml: xml_fragment, remaining } })
195196+ const [item] = remaining.match(plist_parser_regex)!
197 result.push(deserialize_xml_fragment_to_plist_value_object(item))
198 remaining = remaining.substring(item.length).trim()
199 // sanity check: ensure we made progress