diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index 4f066313f6..05cba553bb 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -12,7 +12,7 @@ "@keymanapp/common-types": "*", "@sentry/node": "^7.57.0", "eventemitter3": "^5.0.0", - "fast-xml-parser": "^4.5.0", + "fast-xml-parser": "^5.0.9", "path-browserify": "^1.0.1", "restructure": "^3.0.1", "sax": ">=0.6.0", diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts index 022328ff94..5ef3b55ec9 100644 --- a/developer/src/common/web/utils/src/xml-utils.ts +++ b/developer/src/common/web/utils/src/xml-utils.ts @@ -6,7 +6,7 @@ * Abstraction for XML reading and writing */ -import { XMLParser, XMLBuilder } from 'fast-xml-parser'; +import { XMLParser, XMLBuilder, XmlBuilderOptions, X2jOptions } from 'fast-xml-parser'; export type KeymanXMLType = 'keyboard3' // LDML @@ -17,102 +17,87 @@ export type KeymanXMLType = ; /** Bag of options, maximally one for each KeymanXMLType */ -type KeymanXMLOptionsBag = { - [key in KeymanXMLType]?: any +type KeymanXMLParserOptionsBag = { + [key in KeymanXMLType]?: X2jOptions; +}; + +const PARSER_COMMON_OPTIONS: X2jOptions = { + attributeNamePrefix: '$', // causes remapping into $: { … } objects + htmlEntities: true, + ignoreAttributes: false, + ignorePiTags: true, + numberParseOptions: { // TODO: query is this option really necessary? + eNotation: null, + hex: null, + leadingZeros: null, + skipLike: /(?:)/, // parse numbers as strings + }, + textNodeName: '_', }; /** map of options for the XML parser */ -const PARSER_OPTIONS: KeymanXMLOptionsBag = { +const PARSER_OPTIONS: KeymanXMLParserOptionsBag = { 'keyboard3': { - ignoreAttributes: false, // We'd like attributes, please 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*/) => { + ignoreAttributes: false, // We'd like attributes, please + tagValueProcessor: (_tagName: string, tagValue: string /*, jPath, hasAttributes, isLeafNode*/) => { // since trimValues: false, we need to zap any element values that would be trimmed. // currently, the LDML spec doesn't have any element values, but this // future-proofs us a little in that element values are allowed, just trimmed. // if we do need elements in the future, we'd check the preserve-space attribute here. return tagValue?.trim(); }, + trimValues: false, // preserve spaces, but see tagValueProcessor }, 'keyboardTest3': { - ignorePiTags: true, + attributeNamePrefix: '', // avoid @_ htmlEntities: true, ignoreAttributes: false, // We'd like attributes, please - attributeNamePrefix: '', // avoid @_ + ignorePiTags: true, preserveOrder: true, // Gives us a 'special' format }, 'kps': { - ignorePiTags: true, - ignoreAttributes: false, - htmlEntities: true, - attributeNamePrefix: '$', // causes remapping into $: { … } objects - textNodeName: '_', - numberParseOptions: { - skipLike: /(?:)/, // parse numbers as strings - hex: null, - leadingZeros: null, - eNotation: null, - }, + ...PARSER_COMMON_OPTIONS, }, 'kpj': { - ignorePiTags: true, - textNodeName: '_', - htmlEntities: true, - ignoreAttributes: false, // We'd like attributes, please + ...PARSER_COMMON_OPTIONS, attributeNamePrefix: '', // to avoid '@_' prefixes - numberParseOptions: { - skipLike: /(?:)/, // parse numbers as strings - hex: null, - leadingZeros: null, - eNotation: null, - }, }, 'kvks': { - ignorePiTags: true, - textNodeName: '_', - htmlEntities: true, - ignoreAttributes: false, // We'd like attributes, please - attributeNamePrefix: '$', // causes remapping into $: { … } objects - numberParseOptions: { - skipLike: /(?:)/, // parse numbers as strings - hex: null, - leadingZeros: null, - eNotation: null, - }, - trimValues: false, // preserve spaces, but: - tagValueProcessor: (tagName: string, tagValue: string, jPath: string, hasAttributes: string, isLeafNode: boolean) : string | undefined => { + ...PARSER_COMMON_OPTIONS, + tagValueProcessor: (_tagName: string, tagValue: string, _jPath: string, _hasAttributes: boolean, isLeafNode: boolean) : string | undefined => { if (!isLeafNode) { return tagValue?.trim(); // trimmed value } else { return null; // no change to leaf nodes } }, + trimValues: false, // preserve spaces }, }; -const GENERATOR_OPTIONS: KeymanXMLOptionsBag = { +type KeymanXMLGeneratorOptionsBag = { + [key in KeymanXMLType]?: XmlBuilderOptions +}; + +const GENERATOR_COMMON_OPTIONS: XmlBuilderOptions = { + attributeNamePrefix: '$', + ignoreAttributes: false, + format: true, + textNodeName: '_', + suppressEmptyNode: true, +}; + +const GENERATOR_OPTIONS: KeymanXMLGeneratorOptionsBag = { kvks: { - attributeNamePrefix: '$', - ignoreAttributes: false, - format: true, - textNodeName: '_', - suppressEmptyNode: true, + ...GENERATOR_COMMON_OPTIONS, }, kpj: { - attributeNamePrefix: '$', - ignoreAttributes: false, - format: true, - textNodeName: '_', - suppressEmptyNode: true, + ...GENERATOR_COMMON_OPTIONS, }, kps: { - attributeNamePrefix: '$', - ignoreAttributes: false, - format: true, - textNodeName: '_', - suppressEmptyNode: true, + ...GENERATOR_COMMON_OPTIONS, }, }; @@ -263,9 +248,6 @@ export class KeymanXMLReader { throw Error(`Internal error: unhandled XML type ${this.type}`); } options = Object.assign({}, options); // TODO: xml2js likes to mutate the options here. Shallow clone the object. - if (options.emptyTag) { - options.emptyTag = {}; // TODO: xml2js likes to mutate the options here. Reset it. - } return new XMLParser(options); } } diff --git a/developer/src/common/web/utils/test/fixtures/kvks/hex_escape.kvks b/developer/src/common/web/utils/test/fixtures/kvks/hex_escape.kvks new file mode 100644 index 0000000000..74c10b2e71 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/kvks/hex_escape.kvks @@ -0,0 +1,16 @@ + + +
+ 10.0 + hex_escape + +
+ + + + Ā + 𐀀 + 𒍅 + + +
diff --git a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/numeric-id.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/numeric-id.xml new file mode 100644 index 0000000000..404ae55d99 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/numeric-id.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/test-fr.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/test-fr.xml index fc0224fcb8..a352e437a6 100644 --- a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/test-fr.xml +++ b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/test-fr.xml @@ -1,5 +1,5 @@ - +