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 291 <array> 292 292 <string>test.test</string> 293 293 </array> 294 + <key>TestNumber</key> 295 + <integer>123</integer> 294 296 </dict> 295 297 </plist> 296 298 `.trim() ··· 341 343 UISupportedInterfaceOrientations: ['UIInterfaceOrientationPortrait'], 342 344 UIViewControllerBasedStatusBarAppearance: true, 343 345 WKAppBoundDomains: ['test.test'], 346 + TestNumber: 123, 344 347 }
+10 -17
libraries/plist-parser/src/plist-parser.ts
··· 5 5 * @see {serialize_xml_to_plist_object} 6 6 * @see {deserialize_plist_xml_to_plist_object} 7 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. 8 + * @description A single-file zero dependency parser for serializing and deserializing Apple Info.plist files. Supprts only XML formatted plists. 9 9 * Only supports plists with a single root element, that contain 10 10 * only string, number, boolean, array(also nested), and dictionary(also nested) values. Attempts to conform to 11 11 * Apple's formatting and specifications for plists in the parts of the spec that are supported. ··· 88 88 89 89 // Supporting real values is not supported by this parser, we only support numbers. 90 90 function serialize_number(value: number, indent: string) { 91 - return `${indent}<number>${value}</number>` 91 + return `${indent}<integer>${value}</integer>` 92 92 } 93 93 94 94 function serialize_boolean(value: boolean, indent: string) { ··· 129 129 } 130 130 131 131 /* Deserialization */ 132 - const plist_parser_regex = /<(dict|array|string|number)>([\s\S]*?)<\/\1>/ 132 + const plist_parser_regex = /<(dict|array|string|integer)>([\s\S]*?)<\/\1>/ 133 133 function naive_ends_with_closing_tag(xml_fragment: string, _element_name: string) { 134 134 const open = xml_fragment.lastIndexOf('<') 135 135 return open !== -1 && xml_fragment[open + 1] === '/' && xml_fragment[xml_fragment.length - 1] === '>' ··· 151 151 if (tag === 'string') return unescape_xml(content!) 152 152 else if (tag === 'dict') return deserialize_plist_dict_to_object(content!) 153 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 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 + } 156 160 157 161 throw new Error('invalid_xml' as plist_parser_error_types, { 158 162 cause: { xml: xml_fragment }, ··· 167 171 // Starting the loop at 1 because we do not want to look at the first element. 168 172 for (let index = 1; index + 1 < parts.length; index += 2) { 169 173 const key_part = parts[index] 170 - if (typeof key_part !== 'string') 174 + if (typeof key_part !== 'string' || result[key_part]) 171 175 throw new Error('invalid_xml' as plist_parser_error_types, { cause: { xml: xml_fragment, key_part, value_part: parts[index + 1] } }) 172 176 173 177 const value_part = parts[index + 1] ··· 180 184 return result 181 185 } 182 186 183 - const plist_parser_array_self_closing_tag_regex = /<(dict|array|string|true|false)\/>/ 184 187 function deserialize_plist_array_to_object(xml_fragment: string) { 185 188 const result: plist_value[] = [] 186 189 let remaining = xml_fragment.trim() 187 190 188 191 while (remaining) { 189 192 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 193 if (!plist_parser_regex.test(remaining)) 201 194 throw new Error('unsupported_tag' as plist_parser_error_types, { cause: { xml: xml_fragment, remaining } }) 202 195 203 - const [item] = remaining.match(plist_parser_regex)! // arrays only support booleans, strings, dicts, and arrays. 196 + const [item] = remaining.match(plist_parser_regex)! 204 197 result.push(deserialize_xml_fragment_to_plist_value_object(item)) 205 198 remaining = remaining.substring(item.length).trim() 206 199 // sanity check: ensure we made progress