Its a crux!

fix: fix bug in integer support and fix cyclic references

Signed-off-by: Jonathan Basniak <740416+gm112@users.noreply.github.com>

+13 -17
+3
libraries/plist-parser/src/plist-parser.test.ts
··· 291 <array> 292 <string>test.test</string> 293 </array> 294 </dict> 295 </plist> 296 `.trim() ··· 341 UISupportedInterfaceOrientations: ['UIInterfaceOrientationPortrait'], 342 UIViewControllerBasedStatusBarAppearance: true, 343 WKAppBoundDomains: ['test.test'], 344 }
··· 291 <array> 292 <string>test.test</string> 293 </array> 294 + <key>TestNumber</key> 295 + <integer>123</integer> 296 </dict> 297 </plist> 298 `.trim() ··· 343 UISupportedInterfaceOrientations: ['UIInterfaceOrientationPortrait'], 344 UIViewControllerBasedStatusBarAppearance: true, 345 WKAppBoundDomains: ['test.test'], 346 + TestNumber: 123, 347 }
+10 -17
libraries/plist-parser/src/plist-parser.ts
··· 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. ··· 88 89 // Supporting real values is not supported by this parser, we only support numbers. 90 function serialize_number(value: number, indent: string) { 91 - return `${indent}<number>${value}</number>` 92 } 93 94 function serialize_boolean(value: boolean, indent: string) { ··· 129 } 130 131 /* Deserialization */ 132 - const plist_parser_regex = /<(dict|array|string|number)>([\s\S]*?)<\/\1>/ 133 function 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' 156 157 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] } }) 172 173 const value_part = parts[index + 1] ··· 180 return result 181 } 182 183 - const plist_parser_array_self_closing_tag_regex = /<(dict|array|string|true|false)\/>/ 184 function deserialize_plist_array_to_object(xml_fragment: string) { 185 const result: plist_value[] = [] 186 let remaining = xml_fragment.trim() 187 188 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 } }) 202 203 - 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. ··· 88 89 // Supporting real values is not supported by this parser, we only support numbers. 90 function serialize_number(value: number, indent: string) { 91 + return `${indent}<integer>${value}</integer>` 92 } 93 94 function serialize_boolean(value: boolean, indent: string) { ··· 129 } 130 131 /* Deserialization */ 132 + const plist_parser_regex = /<(dict|array|string|integer)>([\s\S]*?)<\/\1>/ 133 function 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 === '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 + } 160 161 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] } }) 176 177 const value_part = parts[index + 1] ··· 184 return result 185 } 186 187 function deserialize_plist_array_to_object(xml_fragment: string) { 188 const result: plist_value[] = [] 189 let remaining = xml_fragment.trim() 190 191 while (remaining) { 192 const xml_part_length = remaining.length 193 if (!plist_parser_regex.test(remaining)) 194 throw new Error('unsupported_tag' as plist_parser_error_types, { cause: { xml: xml_fragment, remaining } }) 195 196 + 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