From 03a24a150619f1677133240faef45eec9ec389f9 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Thu, 3 Oct 2024 12:41:08 -0500 Subject: [PATCH] feat(developer): fast-xml-parser: fix for ldml empty attributes - because of the form to="" in ldml, we need to distinguish attributes and sub-elements in the ldml xml parsing - use an attributePrefix, and fixup the object tree afterwards Fixes: #12208 --- .../src/common/web/utils/src/xml-utils.ts | 42 ++++++++++++------- .../xml/tran_fail-matches-nothing-1.xml | 13 ++++++ .../xml/tran_fail-matches-nothing-1.xml.json | 19 +++++++++ .../common/web/utils/test/test-xml-utils.ts | 1 + 4 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml create mode 100644 developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml.json diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts index 02d28dfc90c..9ed252256e6 100644 --- a/developer/src/common/web/utils/src/xml-utils.ts +++ b/developer/src/common/web/utils/src/xml-utils.ts @@ -25,7 +25,7 @@ type KemanXMLOptionsBag = { const PARSER_OPTIONS: KemanXMLOptionsBag = { 'keyboard3': { ignoreAttributes: false, // We'd like attributes, please - attributeNamePrefix: '', // to avoid '@_' prefixes + attributeNamePrefix: '@__', // We'll use this to convert attributes to strings and subobjects to arrays, when empty. trimValues: false, // preserve spaces, but: htmlEntities: true, tagValueProcessor: (tagName: string, tagValue: string /*, jPath, hasAttributes, isLeafNode*/) => { @@ -133,19 +133,35 @@ export class KeymanXMLReader { } } - /** replace any empty string "" with an empty object {} */ + /** + * Requires attribute prefix @__ (double underscore) + * For attributes, just remove @__ and continue. + * For objects, replace any empty string "" with an empty object {} */ private static fixupEmptyStringToEmptyObject(data: any) : any { - if (data === "") { - // this is the core feature here. - return {}; - } else if (typeof data === 'object') { + if (typeof data === 'object') { + // For arrays of objects, we map "" to {} + // "" means an empty object if (Array.isArray(data)) { - return data.map(v => KeymanXMLReader.fixupEmptyStringToEmptyObject(v)); + return data.map(v => { + if (v === '') { + return {}; + } else { + return KeymanXMLReader.fixupEmptyStringToEmptyObject(v); + } + }); } - // object - const e : any = []; + // otherwise: remove @__ for attributes, remap objects + const e: any = []; Object.entries(data).forEach(([k, v]) => { - e.push([k, KeymanXMLReader.fixupEmptyStringToEmptyObject(v)]); + if (k.startsWith('@__')) { + e.push([k.substring(3), KeymanXMLReader.fixupEmptyStringToEmptyObject(v)]); + } else { + if (v === '') { + e.push([k, {}]); + } else { + e.push([k, KeymanXMLReader.fixupEmptyStringToEmptyObject(v)]); + } + } }); return Object.fromEntries(e); } else { @@ -217,11 +233,9 @@ export class KeymanXMLReader { let result = parser.parse(data, true); if (PARSER_OPTIONS[this.type].attributeNamePrefix === '$') { result = KeymanXMLReader.fixupDollarAttributes(result); - } - if (this.type === 'keyboard3') { + } else if (PARSER_OPTIONS[this.type].attributeNamePrefix === '@__') { result = KeymanXMLReader.fixupEmptyStringToEmptyObject(result); - } - if (PARSER_OPTIONS[this.type].preserveOrder) { + } else if (PARSER_OPTIONS[this.type].preserveOrder) { result = KeymanXMLReader.fixupPreserveOrder(result); } delete result['?xml']; diff --git a/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml new file mode 100644 index 00000000000..5b5e2bba5bd --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml.json b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml.json new file mode 100644 index 00000000000..477499477d8 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-matches-nothing-1.xml.json @@ -0,0 +1,19 @@ +{ + "keyboard3": { + "conformsTo": "45", + "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3", + "locale": "mt", + "info": { + "name": "fail-matches-nothing" + }, + "keys": {}, + "transforms": { + "type": "simple", + "transformGroup": { + "transform": { + "from": "" + } + } + } + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/test-xml-utils.ts b/developer/src/common/web/utils/test/test-xml-utils.ts index fbe32f8da4f..1cc9724b278 100644 --- a/developer/src/common/web/utils/test/test-xml-utils.ts +++ b/developer/src/common/web/utils/test/test-xml-utils.ts @@ -32,6 +32,7 @@ const read_cases: Case[] = [ 'k_020_fr.xml', 'strs_invalid-illegal.xml', 'tran_fail-empty.xml', + 'tran_fail-matches-nothing-1.xml', ], }, { type: 'keyboardTest3',