From 609faf80475864aced1fbe4ee187082e0705b3aa Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 27 Sep 2024 16:52:21 -0500 Subject: [PATCH] feat(common): unified xml parser reader test Fixes: #12208 --- developer/src/common/web/utils/src/index.ts | 2 +- .../src/common/web/utils/src/xml-utils.ts | 94 +- .../utils/test/fixtures/xml/disp_maximal.xml | 16 + .../test/fixtures/xml/disp_maximal.xml.json | 35 + .../xml/error_invalid_package_file.kps | 32 + .../utils/test/fixtures/xml/k_020_fr-test.xml | 23 + .../test/fixtures/xml/k_020_fr-test.xml.json | 538 ++++++++ .../web/utils/test/fixtures/xml/k_020_fr.xml | 200 +++ .../utils/test/fixtures/xml/k_020_fr.xml.json | 509 +++++++ .../utils/test/fixtures/xml/khmer_angkor.kpj | 187 +++ .../test/fixtures/xml/khmer_angkor.kpj.json | 190 +++ .../utils/test/fixtures/xml/khmer_angkor.kvks | 206 +++ .../test/fixtures/xml/khmer_angkor.kvks.json | 1172 +++++++++++++++++ .../fixtures/xml/strs_invalid-illegal.xml | 58 + .../xml/strs_invalid-illegal.xml.json | 113 ++ .../utils/test/fixtures/xml/test_valid.kps | 46 + .../test/fixtures/xml/test_valid.kps.json | 64 + .../test/fixtures/xml/tran_fail-empty.xml | 19 + .../fixtures/xml/tran_fail-empty.xml.json | 28 + .../common/web/utils/test/test-xml-utils.ts | 109 +- 20 files changed, 3632 insertions(+), 9 deletions(-) create mode 100644 developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml create mode 100644 developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml.json create mode 100644 developer/src/common/web/utils/test/fixtures/xml/error_invalid_package_file.kps create mode 100644 developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml create mode 100644 developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml.json create mode 100644 developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml create mode 100644 developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml.json create mode 100644 developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj create mode 100644 developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj.json create mode 100644 developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks create mode 100644 developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks.json create mode 100644 developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml create mode 100644 developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml.json create mode 100644 developer/src/common/web/utils/test/fixtures/xml/test_valid.kps create mode 100644 developer/src/common/web/utils/test/fixtures/xml/test_valid.kps.json create mode 100644 developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml create mode 100644 developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml.json diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index 4a748bc354a..7b83f682dff 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -46,4 +46,4 @@ export { CommonTypesMessages } from './common-messages.js'; export * as xml2js from './deps/xml2js/xml2js.js'; -export { KeymanXMLParser, KeymanXMLGenerator } from './xml-utils.js'; +export { KeymanXMLOptions, KeymanXMLWriter, KeymanXMLReader } from './xml-utils.js'; diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts index fa71dcf9656..319b607fe73 100644 --- a/developer/src/common/web/utils/src/xml-utils.ts +++ b/developer/src/common/web/utils/src/xml-utils.ts @@ -1,11 +1,99 @@ +import { xml2js } from "./index.js"; + +export class KeymanXMLOptions { + type: 'keyboard3' // LDML + | 'keyboard3-test' // LDML + | 'kps' // + | 'kvks' // + | 'kpj' // // + ; +} /** wrapper for XML parsing support */ -export class KeymanXMLParser { +export class KeymanXMLReader { + public constructor(public options: KeymanXMLOptions) { + } + public parse(data: string): Object { + const parser = this.parser(); + let a: any; + parser.parseString(data, (e: unknown, r: unknown) => { if (e) throw e; a = r; }); + return a; + } + + public parser() { + const { type } = this.options; + switch (type) { + case 'keyboard3': + return new xml2js.Parser({ + explicitArray: false, + mergeAttrs: true, + includeWhiteChars: false, + emptyTag: {} as any + // Why "as any"? xml2js is broken: + // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means + // that an old version of `emptyTag` is used which doesn't support + // functions, but DefinitelyTyped is requiring use of function or a + // string. See also notes at + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470 + // An alternative fix would be to pull xml2js directly from github + // rather than using the version tagged on npmjs.com. + }); + case 'keyboard3-test': + return new xml2js.Parser({ + // explicitArray: false, + preserveChildrenOrder: true, // needed for test data + explicitChildren: true, // needed for test data + // mergeAttrs: true, + // includeWhiteChars: false, + // emptyTag: {} as any + // Why "as any"? xml2js is broken: + // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means + // that an old version of `emptyTag` is used which doesn't support + // functions, but DefinitelyTyped is requiring use of function or a + // string. See also notes at + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470 + // An alternative fix would be to pull xml2js directly from github + // rather than using the version tagged on npmjs.com. + }); + case 'kps': + return new xml2js.Parser({ + explicitArray: false + }); + case 'kpj': + return new xml2js.Parser({ + explicitArray: false, + mergeAttrs: false, + includeWhiteChars: false, + normalize: false, + emptyTag: '' + }); + case 'kvks': + return new xml2js.Parser({ + explicitArray: false, + mergeAttrs: false, + includeWhiteChars: true, + normalize: false, + emptyTag: {} as any + // Why "as any"? xml2js is broken: + // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means + // that an old version of `emptyTag` is used which doesn't support + // functions, but DefinitelyTyped is requiring use of function or a + // string. See also notes at + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470 + // An alternative fix would be to pull xml2js directly from github + // rather than using the version tagged on npmjs.com. + }); + default: + /* c8 ignore next 1 */ + throw Error(`Internal error: unhandled XML type ${type}`); + } + } } /** wrapper for XML generation support */ -export class KeymanXMLGenerator { - +export class KeymanXMLWriter { + constructor(public options: KeymanXMLOptions) { + } } diff --git a/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml b/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml new file mode 100644 index 00000000000..aeab3a71a5c --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml.json b/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml.json new file mode 100644 index 00000000000..55ea878c833 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/disp_maximal.xml.json @@ -0,0 +1,35 @@ +{ + "keyboard3": { + "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3", + "locale": "mt", + "conformsTo": "45", + "info": { + "name": "disp-maximal" + }, + "displays": { + "display": [ + { + "keyId": "g", + "display": "(g)" + }, + { + "output": "f", + "display": "(f)" + }, + { + "output": "${eee}", + "display": "(${eee})" + } + ], + "displayOptions": { + "baseCharacter": "x" + } + }, + "variables": { + "string": { + "id": "eee", + "value": "e" + } + } + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/fixtures/xml/error_invalid_package_file.kps b/developer/src/common/web/utils/test/fixtures/xml/error_invalid_package_file.kps new file mode 100644 index 00000000000..e18eaa26a06 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/error_invalid_package_file.kps @@ -0,0 +1,32 @@ + + + + 15.0.266.0 + 7.0 + + + SENĆOŦEN (Saanich Dialect) Keyboard + + © 2019 National Research Council Canada & this test + Eddie Antonio Santos + 1.0 + + + + basic.kmx + Keyboard Basic + 0 + .kmx + + + + + Basic + basic + 1.0 + + Khmer + + + + diff --git a/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml new file mode 100644 index 00000000000..22fce785389 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml.json b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml.json new file mode 100644 index 00000000000..ab530dd0b4a --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr-test.xml.json @@ -0,0 +1,538 @@ +{ + "keyboardTest3": { + "$": { + "conformsTo": "techpreview" + }, + "#name": "keyboardTest3", + "$$": [ + { + "$": { + "keyboard": "k_020_fr.xml", + "author": "Team Keyboard", + "name": "fr-test-updated" + }, + "#name": "info" + }, + { + "$": { + "name": "simple-repertoire", + "chars": "[a b c d e \\u0022]", + "type": "simple" + }, + "#name": "repertoire" + }, + { + "$": { + "name": "chars-repertoire", + "chars": "[á é ó]", + "type": "gesture" + }, + "#name": "repertoire" + }, + { + "$": { + "name": "key-tests-updated" + }, + "#name": "tests", + "$$": [ + { + "$": { + "name": "key-test" + }, + "#name": "test", + "$$": [ + { + "$": { + "to": "abc\\u0022..." + }, + "#name": "startContext" + }, + { + "$": { + "key": "s" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...s" + }, + "#name": "check" + }, + { + "$": { + "key": "t" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...st" + }, + "#name": "check" + }, + { + "$": { + "key": "u" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...stu" + }, + "#name": "check" + }, + { + "$": { + "to": "v" + }, + "#name": "emit" + }, + { + "$": { + "result": "abc\\u0022...stuv" + }, + "#name": "check" + } + ], + "startContext": [ + { + "$": { + "to": "abc\\u0022..." + } + } + ], + "keystroke": [ + { + "$": { + "key": "s" + } + }, + { + "$": { + "key": "t" + } + }, + { + "$": { + "key": "u" + } + } + ], + "check": [ + { + "$": { + "result": "abc\\u0022...s" + } + }, + { + "$": { + "result": "abc\\u0022...st" + } + }, + { + "$": { + "result": "abc\\u0022...stu" + } + }, + { + "$": { + "result": "abc\\u0022...stuv" + } + } + ], + "emit": [ + { + "$": { + "to": "v" + } + } + ] + } + ], + "test": [ + { + "$": { + "name": "key-test" + }, + "$$": [ + { + "$": { + "to": "abc\\u0022..." + }, + "#name": "startContext" + }, + { + "$": { + "key": "s" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...s" + }, + "#name": "check" + }, + { + "$": { + "key": "t" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...st" + }, + "#name": "check" + }, + { + "$": { + "key": "u" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...stu" + }, + "#name": "check" + }, + { + "$": { + "to": "v" + }, + "#name": "emit" + }, + { + "$": { + "result": "abc\\u0022...stuv" + }, + "#name": "check" + } + ], + "startContext": [ + { + "$": { + "to": "abc\\u0022..." + } + } + ], + "keystroke": [ + { + "$": { + "key": "s" + } + }, + { + "$": { + "key": "t" + } + }, + { + "$": { + "key": "u" + } + } + ], + "check": [ + { + "$": { + "result": "abc\\u0022...s" + } + }, + { + "$": { + "result": "abc\\u0022...st" + } + }, + { + "$": { + "result": "abc\\u0022...stu" + } + }, + { + "$": { + "result": "abc\\u0022...stuv" + } + } + ], + "emit": [ + { + "$": { + "to": "v" + } + } + ] + } + ] + } + ], + "info": [ + { + "$": { + "keyboard": "k_020_fr.xml", + "author": "Team Keyboard", + "name": "fr-test-updated" + } + } + ], + "repertoire": [ + { + "$": { + "name": "simple-repertoire", + "chars": "[a b c d e \\u0022]", + "type": "simple" + } + }, + { + "$": { + "name": "chars-repertoire", + "chars": "[á é ó]", + "type": "gesture" + } + } + ], + "tests": [ + { + "$": { + "name": "key-tests-updated" + }, + "$$": [ + { + "$": { + "name": "key-test" + }, + "#name": "test", + "$$": [ + { + "$": { + "to": "abc\\u0022..." + }, + "#name": "startContext" + }, + { + "$": { + "key": "s" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...s" + }, + "#name": "check" + }, + { + "$": { + "key": "t" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...st" + }, + "#name": "check" + }, + { + "$": { + "key": "u" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...stu" + }, + "#name": "check" + }, + { + "$": { + "to": "v" + }, + "#name": "emit" + }, + { + "$": { + "result": "abc\\u0022...stuv" + }, + "#name": "check" + } + ], + "startContext": [ + { + "$": { + "to": "abc\\u0022..." + } + } + ], + "keystroke": [ + { + "$": { + "key": "s" + } + }, + { + "$": { + "key": "t" + } + }, + { + "$": { + "key": "u" + } + } + ], + "check": [ + { + "$": { + "result": "abc\\u0022...s" + } + }, + { + "$": { + "result": "abc\\u0022...st" + } + }, + { + "$": { + "result": "abc\\u0022...stu" + } + }, + { + "$": { + "result": "abc\\u0022...stuv" + } + } + ], + "emit": [ + { + "$": { + "to": "v" + } + } + ] + } + ], + "test": [ + { + "$": { + "name": "key-test" + }, + "$$": [ + { + "$": { + "to": "abc\\u0022..." + }, + "#name": "startContext" + }, + { + "$": { + "key": "s" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...s" + }, + "#name": "check" + }, + { + "$": { + "key": "t" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...st" + }, + "#name": "check" + }, + { + "$": { + "key": "u" + }, + "#name": "keystroke" + }, + { + "$": { + "result": "abc\\u0022...stu" + }, + "#name": "check" + }, + { + "$": { + "to": "v" + }, + "#name": "emit" + }, + { + "$": { + "result": "abc\\u0022...stuv" + }, + "#name": "check" + } + ], + "startContext": [ + { + "$": { + "to": "abc\\u0022..." + } + } + ], + "keystroke": [ + { + "$": { + "key": "s" + } + }, + { + "$": { + "key": "t" + } + }, + { + "$": { + "key": "u" + } + } + ], + "check": [ + { + "$": { + "result": "abc\\u0022...s" + } + }, + { + "$": { + "result": "abc\\u0022...st" + } + }, + { + "$": { + "result": "abc\\u0022...stu" + } + }, + { + "$": { + "result": "abc\\u0022...stuv" + } + } + ], + "emit": [ + { + "$": { + "to": "v" + } + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml new file mode 100644 index 00000000000..671f1b8b9b6 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml.json b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml.json new file mode 100644 index 00000000000..1e23ec4bdbd --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/k_020_fr.xml.json @@ -0,0 +1,509 @@ +{ + "keyboard3": { + "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3", + "locale": "fr-t-k0-azerty", + "conformsTo": "45", + "locales": { + "locale": { + "id": "br" + } + }, + "version": { + "number": "1.0.1" + }, + "info": { + "author": "Team Keyboard", + "layout": "AZERTY", + "indicator": "FR", + "name": "French Test Updated" + }, + "displays": { + "display": { + "output": "\\u{0300}", + "display": "\\u{02CB}" + }, + "displayOptions": { + "baseCharacter": "x" + } + }, + "keys": { + "import": [ + { + "base": "cldr", + "path": "45/keys-Zyyy-punctuation.xml" + }, + { + "base": "cldr", + "path": "45/keys-Zyyy-currency.xml" + } + ], + "key": [ + { + "id": "shift", + "layerId": "shift" + }, + { + "id": "numeric", + "layerId": "numeric" + }, + { + "id": "symbol", + "layerId": "symbol" + }, + { + "id": "base", + "layerId": "base" + }, + { + "id": "bksp", + "gap": "true" + }, + { + "id": "extra", + "gap": "true" + }, + { + "id": "enter", + "output": "\\u{000A}" + }, + { + "id": "u-grave", + "output": "ü" + }, + { + "id": "e-grave", + "output": "é" + }, + { + "id": "e-acute", + "output": "è" + }, + { + "id": "c-cedilla", + "output": "ç" + }, + { + "id": "a-grave", + "output": "à" + }, + { + "id": "a-acute", + "output": "á" + }, + { + "id": "a-caret", + "output": "â" + }, + { + "id": "a-umlaut", + "output": "ä" + }, + { + "id": "a-tilde", + "output": "ã" + }, + { + "id": "a-ring", + "output": "å" + }, + { + "id": "a-caron", + "output": "ā" + }, + { + "id": "A-grave", + "output": "À" + }, + { + "id": "A-acute", + "output": "Á" + }, + { + "id": "A-caret", + "output": "Â" + }, + { + "id": "A-umlaut", + "output": "Ä" + }, + { + "id": "A-tilde", + "output": "Ã" + }, + { + "id": "A-ring", + "output": "Å" + }, + { + "id": "A-caron", + "output": "Ā" + }, + { + "id": "bullet", + "output": "•" + }, + { + "id": "umlaut", + "output": "¨" + }, + { + "id": "sub-2", + "output": "₂" + }, + { + "id": "super-2", + "output": "²", + "longPressKeyIds": "sub-2" + }, + { + "id": "a", + "flickId": "a", + "output": "a", + "longPressKeyIds": "a-grave a-caret a-acute a-umlaut a-tilde a-ring a-caron", + "longPressDefaultKeyId": "a-caret" + }, + { + "id": "A", + "flickId": "b", + "output": "A", + "longPressKeyIds": "A-grave A-caret A-acute A-umlaut a-tilde A-ring A-caron", + "longPressDefaultKeyId": "A-caret" + } + ] + }, + "flicks": { + "flick": [ + { + "id": "b", + "flickSegment": [ + { + "directions": "nw", + "keyId": "A-grave" + }, + { + "directions": "nw se", + "keyId": "A-acute" + }, + { + "directions": "e", + "keyId": "A-caron" + }, + { + "directions": "s", + "keyId": "numeric" + } + ] + }, + { + "id": "a", + "flickSegment": [ + { + "directions": "nw", + "keyId": "a-grave" + }, + { + "directions": "nw se", + "keyId": "a-acute" + }, + { + "directions": "e", + "keyId": "a-caron" + } + ] + } + ] + }, + "layers": [ + { + "formId": "iso", + "layer": [ + { + "modifiers": "none", + "row": [ + { + "keys": "super-2 amp e-grave double-quote apos open-paren hyphen e-acute underscore c-cedilla a-acute close-paren equal" + }, + { + "keys": "a z e r t y u i o p caret dollar" + }, + { + "keys": "q s d f g h j k l m u-grave asterisk" + }, + { + "keys": "open-angle w x c v b n comma semi-colon colon bang" + }, + { + "keys": "space" + } + ] + }, + { + "modifiers": "shift", + "row": [ + { + "keys": "1 2 3 4 5 6 7 8 9 0 degree plus" + }, + { + "keys": "A Z E R T Y U I O P umlaut pound" + }, + { + "keys": "Q S D F G H J K L M percent micro" + }, + { + "keys": "close-angle W X C V B N question period slash section" + }, + { + "keys": "space" + } + ] + } + ] + }, + { + "formId": "touch", + "minDeviceWidth": "150", + "layer": [ + { + "id": "base", + "row": [ + { + "keys": "a z e r t y u i o p" + }, + { + "keys": "q s d f g h j k l m" + }, + { + "keys": "shift gap w x c v b n gap" + }, + { + "keys": "numeric extra space enter" + } + ] + }, + { + "id": "shift", + "row": [ + { + "keys": "A Z E R T Y U I O P" + }, + { + "keys": "Q S D F G H J K L M" + }, + { + "keys": "base W X C V B N" + }, + { + "keys": "numeric extra space enter" + } + ] + }, + { + "id": "numeric", + "row": [ + { + "keys": "1 2 3 4 5 6 7 8 9 0" + }, + { + "keys": "hyphen slash colon semi-colon open-paren close-paren dollar amp at double-quote" + }, + { + "keys": "symbol period comma question bang double-quote" + }, + { + "keys": "base extra space enter" + } + ] + }, + { + "id": "symbol", + "row": [ + { + "keys": "open-square close-square open-curly close-curly hash percent caret asterisk plus equal" + }, + { + "keys": "underscore backslash pipe tilde open-angle close-angle euro pound yen bullet" + }, + { + "keys": "numeric period comma question bang double-quote" + }, + { + "keys": "base extra space enter" + } + ] + } + ] + } + ], + "transforms": { + "type": "simple", + "transformGroup": { + "transform": [ + { + "from": "` ", + "to": "`" + }, + { + "from": "`a", + "to": "à" + }, + { + "from": "`A", + "to": "À" + }, + { + "from": "`e", + "to": "è" + }, + { + "from": "`E", + "to": "È" + }, + { + "from": "`i", + "to": "ì" + }, + { + "from": "`I", + "to": "Ì" + }, + { + "from": "`o", + "to": "ò" + }, + { + "from": "`O", + "to": "Ò" + }, + { + "from": "`u", + "to": "ù" + }, + { + "from": "`U", + "to": "Ù" + }, + { + "from": "^ ", + "to": "^" + }, + { + "from": "^a", + "to": "â" + }, + { + "from": "^A", + "to": " " + }, + { + "from": "^e", + "to": "ê" + }, + { + "from": "^E", + "to": "Ê" + }, + { + "from": "^i", + "to": "î" + }, + { + "from": "^I", + "to": "Î" + }, + { + "from": "^o", + "to": "ô" + }, + { + "from": "^O", + "to": "Ô" + }, + { + "from": "^u", + "to": "û" + }, + { + "from": "^U", + "to": "Û" + }, + { + "from": "¨ ", + "to": "¨" + }, + { + "from": "¨a", + "to": "ä" + }, + { + "from": "¨A", + "to": "Ä" + }, + { + "from": "¨e", + "to": "ë" + }, + { + "from": "¨E", + "to": "Ë" + }, + { + "from": "¨i", + "to": "ï" + }, + { + "from": "¨I", + "to": "Ï" + }, + { + "from": "¨o", + "to": "ö" + }, + { + "from": "¨O", + "to": "Ö" + }, + { + "from": "¨u", + "to": "ü" + }, + { + "from": "¨U", + "to": "Ü" + }, + { + "from": "¨y", + "to": "ÿ" + }, + { + "from": "~ ", + "to": "~" + }, + { + "from": "~a", + "to": "ã" + }, + { + "from": "~A", + "to": "Ã" + }, + { + "from": "~n", + "to": "ñ" + }, + { + "from": "~N", + "to": "Ñ" + }, + { + "from": "~o", + "to": "õ" + }, + { + "from": "~O", + "to": "Õ" + } + ] + } + } + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj new file mode 100644 index 00000000000..4b239df20ce --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj @@ -0,0 +1,187 @@ + + + + $PROJECTPATH\build + True + True + False + keyboard + + + + id_f347675c33d2e6b1c705c787fad4941a + khmer_angkor.kmn + source\khmer_angkor.kmn + 1.3 + .kmn +
+ Khmer Angkor + © 2015-2022 SIL International + More than just a Khmer Unicode keyboard. +
+
+ + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + khmer_angkor.kps + source\khmer_angkor.kps + + .kps +
+ Khmer Angkor + © 2015-2022 SIL International +
+
+ + id_8a1efc7c4ab7cfece8aedd847679ca27 + khmer_angkor.ico + source\khmer_angkor.ico + + .ico + id_f347675c33d2e6b1c705c787fad4941a + + + id_8dc195db32d1fd0514de0ad51fff5df0 + khmer_angkor.js + source\..\build\khmer_angkor.js + + .js + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_10596632fcbf4138d24bcccf53e6ae01 + khmer_angkor.kvk + source\..\build\khmer_angkor.kvk + + .kvk + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_0a851f95ce553ecd62cbee6c32ced68f + khmer_angkor.kmx + source\..\build\khmer_angkor.kmx + + .kmx + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_d8b6eb05f4b7e2945c10e04c1f49e4c8 + keyboard_layout.png + source\welcome\keyboard_layout.png + + .png + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_724e5b4c63f10bc0abf7077f7c3172fc + welcome.htm + source\welcome\welcome.htm + + .htm + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_35857cb2b54f123612735ec948400082 + FONTLOG.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\FONTLOG.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_7e3afe5bb59b888b08b48cd5817d8de4 + Mondulkiri-B.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-B.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_b9734e80f86c69ea5ae4dfa9f0083d09 + Mondulkiri-BI.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-BI.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_25abe4d2b0abc03a5be5b666a8de776e + Mondulkiri-I.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-I.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_b766568498108eee46ed1601ff69c47d + Mondulkiri-R.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_84544d04133cab3dbfc86b91ad1a4e17 + OFL.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\OFL.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_0c33fbefd1c20f487b1bea2343b3bb2c + OFL-FAQ.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\OFL-FAQ.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_a59d89fca36a310147645fa2604e521b + KAK_Documentation_EN.pdf + source\welcome\KAK_Documentation_EN.pdf + + .pdf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_5643c4cd3933b3ada0b4af6579305ec4 + KAK_Documentation_KH.pdf + source\welcome\KAK_Documentation_KH.pdf + + .pdf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_acb0dd94c60e345d999670e999cbd159 + image002.png + source\welcome\image002.png + + .png + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_4edf70bc019f05b5ad39a2ea727ad547 + khmer_busra_kbd.ttf + source\..\..\..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_bc823844e4399751e1867016801f7327 + splash.gif + source\splash.gif + + .gif + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + +
+
diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj.json b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj.json new file mode 100644 index 00000000000..8f2310f821c --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kpj.json @@ -0,0 +1,190 @@ +{ + "KeymanDeveloperProject": { + "Options": { + "BuildPath": "$PROJECTPATH\\build", + "CompilerWarningsAsErrors": "True", + "WarnDeprecatedCode": "True", + "CheckFilenameConventions": "False", + "ProjectType": "keyboard" + }, + "Files": { + "File": [ + { + "ID": "id_f347675c33d2e6b1c705c787fad4941a", + "Filename": "khmer_angkor.kmn", + "Filepath": "source\\khmer_angkor.kmn", + "FileVersion": "1.3", + "FileType": ".kmn", + "Details": { + "Name": "Khmer Angkor", + "Copyright": "© 2015-2022 SIL International", + "Message": "More than just a Khmer Unicode keyboard." + } + }, + { + "ID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456", + "Filename": "khmer_angkor.kps", + "Filepath": "source\\khmer_angkor.kps", + "FileVersion": "", + "FileType": ".kps", + "Details": { + "Name": "Khmer Angkor", + "Copyright": "© 2015-2022 SIL International" + } + }, + { + "ID": "id_8a1efc7c4ab7cfece8aedd847679ca27", + "Filename": "khmer_angkor.ico", + "Filepath": "source\\khmer_angkor.ico", + "FileVersion": "", + "FileType": ".ico", + "ParentFileID": "id_f347675c33d2e6b1c705c787fad4941a" + }, + { + "ID": "id_8dc195db32d1fd0514de0ad51fff5df0", + "Filename": "khmer_angkor.js", + "Filepath": "source\\..\\build\\khmer_angkor.js", + "FileVersion": "", + "FileType": ".js", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_10596632fcbf4138d24bcccf53e6ae01", + "Filename": "khmer_angkor.kvk", + "Filepath": "source\\..\\build\\khmer_angkor.kvk", + "FileVersion": "", + "FileType": ".kvk", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_0a851f95ce553ecd62cbee6c32ced68f", + "Filename": "khmer_angkor.kmx", + "Filepath": "source\\..\\build\\khmer_angkor.kmx", + "FileVersion": "", + "FileType": ".kmx", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_d8b6eb05f4b7e2945c10e04c1f49e4c8", + "Filename": "keyboard_layout.png", + "Filepath": "source\\welcome\\keyboard_layout.png", + "FileVersion": "", + "FileType": ".png", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_724e5b4c63f10bc0abf7077f7c3172fc", + "Filename": "welcome.htm", + "Filepath": "source\\welcome\\welcome.htm", + "FileVersion": "", + "FileType": ".htm", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_35857cb2b54f123612735ec948400082", + "Filename": "FONTLOG.txt", + "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\FONTLOG.txt", + "FileVersion": "", + "FileType": ".txt", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_7e3afe5bb59b888b08b48cd5817d8de4", + "Filename": "Mondulkiri-B.ttf", + "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\Mondulkiri-B.ttf", + "FileVersion": "", + "FileType": ".ttf", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_b9734e80f86c69ea5ae4dfa9f0083d09", + "Filename": "Mondulkiri-BI.ttf", + "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\Mondulkiri-BI.ttf", + "FileVersion": "", + "FileType": ".ttf", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_25abe4d2b0abc03a5be5b666a8de776e", + "Filename": "Mondulkiri-I.ttf", + "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\Mondulkiri-I.ttf", + "FileVersion": "", + "FileType": ".ttf", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_b766568498108eee46ed1601ff69c47d", + "Filename": "Mondulkiri-R.ttf", + "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\Mondulkiri-R.ttf", + "FileVersion": "", + "FileType": ".ttf", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_84544d04133cab3dbfc86b91ad1a4e17", + "Filename": "OFL.txt", + "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\OFL.txt", + "FileVersion": "", + "FileType": ".txt", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_0c33fbefd1c20f487b1bea2343b3bb2c", + "Filename": "OFL-FAQ.txt", + "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\mondulkiri\\OFL-FAQ.txt", + "FileVersion": "", + "FileType": ".txt", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_a59d89fca36a310147645fa2604e521b", + "Filename": "KAK_Documentation_EN.pdf", + "Filepath": "source\\welcome\\KAK_Documentation_EN.pdf", + "FileVersion": "", + "FileType": ".pdf", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_5643c4cd3933b3ada0b4af6579305ec4", + "Filename": "KAK_Documentation_KH.pdf", + "Filepath": "source\\welcome\\KAK_Documentation_KH.pdf", + "FileVersion": "", + "FileType": ".pdf", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_8da344c4cea6f467013357fe099006f5", + "Filename": "readme.htm", + "Filepath": "source\\readme.htm", + "FileVersion": "", + "FileType": ".htm", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_acb0dd94c60e345d999670e999cbd159", + "Filename": "image002.png", + "Filepath": "source\\welcome\\image002.png", + "FileVersion": "", + "FileType": ".png", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_4edf70bc019f05b5ad39a2ea727ad547", + "Filename": "khmer_busra_kbd.ttf", + "Filepath": "source\\..\\..\\..\\shared\\fonts\\khmer\\busrakbd\\khmer_busra_kbd.ttf", + "FileVersion": "", + "FileType": ".ttf", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + }, + { + "ID": "id_bc823844e4399751e1867016801f7327", + "Filename": "splash.gif", + "Filepath": "source\\splash.gif", + "FileVersion": "", + "FileType": ".gif", + "ParentFileID": "id_8d4eb765f80c9f2b0f769cf4e4aaa456" + } + ] + } + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks new file mode 100644 index 00000000000..e9c38a464dd --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks @@ -0,0 +1,206 @@ + + +
+ 10.0 + khmer_angkor + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + ] + [ + / + . + + + + & + + * + @ + \ + } + { + - + ÷ + : + , + + ; + < + # + > + × + $ + +   + + + + + + + + + + + + + ᧿ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + « + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + " + + % + + ( + ) + + = + + + ? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + + » + + + +
diff --git a/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks.json b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks.json new file mode 100644 index 00000000000..6775beb1605 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/khmer_angkor.kvks.json @@ -0,0 +1,1172 @@ +{ + "visualkeyboard": { + "_": "\n \n \n", + "header": { + "_": "\n \n \n \n ", + "version": "10.0", + "kbdname": "khmer_angkor", + "flags": { + "_": "\n \n ", + "usealtgr": "" + } + }, + "encoding": { + "_": "\n \n \n \n \n ", + "$": { + "name": "unicode", + "fontname": "Khmer Busra Kbd", + "fontsize": "16" + }, + "layer": [ + { + "_": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ", + "$": { + "shift": "RA" + }, + "key": [ + { + "_": "ឞ", + "$": { + "vkey": "K_B" + } + }, + { + "_": "ឝ", + "$": { + "vkey": "K_K" + } + }, + { + "_": "ៈ", + "$": { + "vkey": "K_QUOTE" + } + }, + { + "_": "ឳ", + "$": { + "vkey": "K_RBRKT" + } + }, + { + "_": "ឨ", + "$": { + "vkey": "K_T" + } + }, + { + "_": "ឩ", + "$": { + "vkey": "K_LBRKT" + } + }, + { + "_": "ឰ", + "$": { + "vkey": "K_P" + } + }, + { + "_": "ឫ", + "$": { + "vkey": "K_R" + } + }, + { + "_": "ឦ", + "$": { + "vkey": "K_I" + } + }, + { + "_": "ឱ", + "$": { + "vkey": "K_O" + } + }, + { + "_": "ឯ", + "$": { + "vkey": "K_E" + } + }, + { + "_": "", + "$": { + "vkey": "K_3" + } + }, + { + "_": "", + "$": { + "vkey": "K_W" + } + }, + { + "_": "ៜ", + "$": { + "vkey": "K_Q" + } + }, + { + "_": "", + "$": { + "vkey": "K_EQUAL" + } + }, + { + "_": "៖", + "$": { + "vkey": "K_COLON" + } + }, + { + "_": "៙", + "$": { + "vkey": "K_6" + } + }, + { + "_": "៚", + "$": { + "vkey": "K_7" + } + }, + { + "_": "", + "$": { + "vkey": "K_M" + } + }, + { + "_": "៘", + "$": { + "vkey": "K_L" + } + }, + { + "_": "‌", + "$": { + "vkey": "K_1" + } + }, + { + "_": "‍", + "$": { + "vkey": "K_BKQUOTE" + } + }, + { + "_": "]", + "$": { + "vkey": "K_U" + } + }, + { + "_": "[", + "$": { + "vkey": "K_Y" + } + }, + { + "_": "/", + "$": { + "vkey": "K_SLASH" + } + }, + { + "_": ".", + "$": { + "vkey": "K_PERIOD" + } + }, + { + "_": "‘", + "$": { + "vkey": "K_H" + } + }, + { + "_": "+", + "$": { + "vkey": "K_A" + } + }, + { + "_": "&", + "$": { + "vkey": "K_V" + } + }, + { + "_": "’", + "$": { + "vkey": "K_J" + } + }, + { + "_": "*", + "$": { + "vkey": "K_8" + } + }, + { + "_": "@", + "$": { + "vkey": "K_2" + } + }, + { + "_": "\\", + "$": { + "vkey": "K_BKSLASH" + } + }, + { + "_": "}", + "$": { + "vkey": "K_0" + } + }, + { + "_": "{", + "$": { + "vkey": "K_9" + } + }, + { + "_": "-", + "$": { + "vkey": "K_S" + } + }, + { + "_": "÷", + "$": { + "vkey": "K_F" + } + }, + { + "_": ":", + "$": { + "vkey": "K_G" + } + }, + { + "_": ",", + "$": { + "vkey": "K_COMMA" + } + }, + { + "_": "≈", + "$": { + "vkey": "K_HYPHEN" + } + }, + { + "_": ";", + "$": { + "vkey": "K_N" + } + }, + { + "_": "<", + "$": { + "vkey": "K_Z" + } + }, + { + "_": "#", + "$": { + "vkey": "K_C" + } + }, + { + "_": ">", + "$": { + "vkey": "K_X" + } + }, + { + "_": "×", + "$": { + "vkey": "K_D" + } + }, + { + "_": "$", + "$": { + "vkey": "K_4" + } + }, + { + "_": "€", + "$": { + "vkey": "K_5" + } + }, + { + "_": " ", + "$": { + "vkey": "K_SPACE" + } + } + ] + }, + { + "_": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ", + "$": { + "shift": "SRA" + }, + "key": [ + { + "_": "៸", + "$": { + "vkey": "K_8" + } + }, + { + "_": "៰", + "$": { + "vkey": "K_0" + } + }, + { + "_": "៱", + "$": { + "vkey": "K_1" + } + }, + { + "_": "៲", + "$": { + "vkey": "K_2" + } + }, + { + "_": "៳", + "$": { + "vkey": "K_3" + } + }, + { + "_": "៴", + "$": { + "vkey": "K_4" + } + }, + { + "_": "៵", + "$": { + "vkey": "K_5" + } + }, + { + "_": "៶", + "$": { + "vkey": "K_6" + } + }, + { + "_": "៷", + "$": { + "vkey": "K_7" + } + }, + { + "_": "៹", + "$": { + "vkey": "K_9" + } + }, + { + "_": "᧿", + "$": { + "vkey": "K_PERIOD" + } + }, + { + "_": "᧾", + "$": { + "vkey": "K_COMMA" + } + }, + { + "_": "᧪", + "$": { + "vkey": "K_LBRKT" + } + }, + { + "_": "᧫", + "$": { + "vkey": "K_RBRKT" + } + }, + { + "_": "᧶", + "$": { + "vkey": "K_QUOTE" + } + }, + { + "_": "᧵", + "$": { + "vkey": "K_COLON" + } + }, + { + "_": "᧬", + "$": { + "vkey": "K_A" + } + }, + { + "_": "᧷", + "$": { + "vkey": "K_Z" + } + }, + { + "_": "᧥", + "$": { + "vkey": "K_Y" + } + }, + { + "_": "᧸", + "$": { + "vkey": "K_X" + } + }, + { + "_": "᧡", + "$": { + "vkey": "K_W" + } + }, + { + "_": "᧻", + "$": { + "vkey": "K_B" + } + }, + { + "_": "᧹", + "$": { + "vkey": "K_C" + } + }, + { + "_": "᧮", + "$": { + "vkey": "K_D" + } + }, + { + "_": "᧢", + "$": { + "vkey": "K_E" + } + }, + { + "_": "᧯", + "$": { + "vkey": "K_F" + } + }, + { + "_": "᧰", + "$": { + "vkey": "K_G" + } + }, + { + "_": "᧦", + "$": { + "vkey": "K_U" + } + }, + { + "_": "᧱", + "$": { + "vkey": "K_H" + } + }, + { + "_": "᧤", + "$": { + "vkey": "K_T" + } + }, + { + "_": "᧭", + "$": { + "vkey": "K_S" + } + }, + { + "_": "᧣", + "$": { + "vkey": "K_R" + } + }, + { + "_": "᧧", + "$": { + "vkey": "K_I" + } + }, + { + "_": "᧠", + "$": { + "vkey": "K_Q" + } + }, + { + "_": "᧺", + "$": { + "vkey": "K_V" + } + }, + { + "_": "᧲", + "$": { + "vkey": "K_J" + } + }, + { + "_": "᧳", + "$": { + "vkey": "K_K" + } + }, + { + "_": "᧴", + "$": { + "vkey": "K_L" + } + }, + { + "_": "᧩", + "$": { + "vkey": "K_P" + } + }, + { + "_": "᧨", + "$": { + "vkey": "K_O" + } + }, + { + "_": "᧼", + "$": { + "vkey": "K_N" + } + }, + { + "_": "᧽", + "$": { + "vkey": "K_M" + } + } + ] + }, + { + "_": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ", + "$": { + "shift": "" + }, + "key": [ + { + "_": "​", + "$": { + "vkey": "K_SPACE" + } + }, + { + "_": "", + "$": { + "vkey": "K_QUOTE" + } + }, + { + "_": "", + "$": { + "vkey": "K_COMMA" + } + }, + { + "_": "ឥ", + "$": { + "vkey": "K_HYPHEN" + } + }, + { + "_": "។", + "$": { + "vkey": "K_PERIOD" + } + }, + { + "_": "", + "$": { + "vkey": "K_SLASH" + } + }, + { + "_": "០", + "$": { + "vkey": "K_0" + } + }, + { + "_": "១", + "$": { + "vkey": "K_1" + } + }, + { + "_": "២", + "$": { + "vkey": "K_2" + } + }, + { + "_": "៣", + "$": { + "vkey": "K_3" + } + }, + { + "_": "៤", + "$": { + "vkey": "K_4" + } + }, + { + "_": "៥", + "$": { + "vkey": "K_5" + } + }, + { + "_": "៦", + "$": { + "vkey": "K_6" + } + }, + { + "_": "៧", + "$": { + "vkey": "K_7" + } + }, + { + "_": "៨", + "$": { + "vkey": "K_8" + } + }, + { + "_": "៩", + "$": { + "vkey": "K_9" + } + }, + { + "_": "", + "$": { + "vkey": "K_COLON" + } + }, + { + "_": "ឲ", + "$": { + "vkey": "K_EQUAL" + } + }, + { + "_": "", + "$": { + "vkey": "K_LBRKT" + } + }, + { + "_": "ឮ", + "$": { + "vkey": "K_BKSLASH" + } + }, + { + "_": "ឪ", + "$": { + "vkey": "K_RBRKT" + } + }, + { + "_": "«", + "$": { + "vkey": "K_BKQUOTE" + } + }, + { + "_": "", + "$": { + "vkey": "K_A" + } + }, + { + "_": "ប", + "$": { + "vkey": "K_B" + } + }, + { + "_": "ច", + "$": { + "vkey": "K_C" + } + }, + { + "_": "ដ", + "$": { + "vkey": "K_D" + } + }, + { + "_": "", + "$": { + "vkey": "K_E" + } + }, + { + "_": "ថ", + "$": { + "vkey": "K_F" + } + }, + { + "_": "ង", + "$": { + "vkey": "K_G" + } + }, + { + "_": "ហ", + "$": { + "vkey": "K_H" + } + }, + { + "_": "", + "$": { + "vkey": "K_I" + } + }, + { + "_": "", + "$": { + "vkey": "K_J" + } + }, + { + "_": "ក", + "$": { + "vkey": "K_K" + } + }, + { + "_": "ល", + "$": { + "vkey": "K_L" + } + }, + { + "_": "ម", + "$": { + "vkey": "K_M" + } + }, + { + "_": "ន", + "$": { + "vkey": "K_N" + } + }, + { + "_": "", + "$": { + "vkey": "K_O" + } + }, + { + "_": "ផ", + "$": { + "vkey": "K_P" + } + }, + { + "_": "ឆ", + "$": { + "vkey": "K_Q" + } + }, + { + "_": "រ", + "$": { + "vkey": "K_R" + } + }, + { + "_": "ស", + "$": { + "vkey": "K_S" + } + }, + { + "_": "ត", + "$": { + "vkey": "K_T" + } + }, + { + "_": "", + "$": { + "vkey": "K_U" + } + }, + { + "_": "វ", + "$": { + "vkey": "K_V" + } + }, + { + "_": "", + "$": { + "vkey": "K_W" + } + }, + { + "_": "ខ", + "$": { + "vkey": "K_X" + } + }, + { + "_": "យ", + "$": { + "vkey": "K_Y" + } + }, + { + "_": "ឋ", + "$": { + "vkey": "K_Z" + } + } + ] + }, + { + "_": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ", + "$": { + "shift": "S" + }, + "key": [ + { + "_": "", + "$": { + "vkey": "K_SPACE" + } + }, + { + "_": "!", + "$": { + "vkey": "K_1" + } + }, + { + "_": "", + "$": { + "vkey": "K_QUOTE" + } + }, + { + "_": "\"", + "$": { + "vkey": "K_3" + } + }, + { + "_": "៛", + "$": { + "vkey": "K_4" + } + }, + { + "_": "%", + "$": { + "vkey": "K_5" + } + }, + { + "_": "", + "$": { + "vkey": "K_7" + } + }, + { + "_": "(", + "$": { + "vkey": "K_9" + } + }, + { + "_": ")", + "$": { + "vkey": "K_0" + } + }, + { + "_": "", + "$": { + "vkey": "K_8" + } + }, + { + "_": "=", + "$": { + "vkey": "K_EQUAL" + } + }, + { + "_": "", + "$": { + "vkey": "K_COLON" + } + }, + { + "_": "៕", + "$": { + "vkey": "K_PERIOD" + } + }, + { + "_": "?", + "$": { + "vkey": "K_SLASH" + } + }, + { + "_": "ៗ", + "$": { + "vkey": "K_2" + } + }, + { + "_": "", + "$": { + "vkey": "K_A" + } + }, + { + "_": "ព", + "$": { + "vkey": "K_B" + } + }, + { + "_": "ជ", + "$": { + "vkey": "K_C" + } + }, + { + "_": "ឌ", + "$": { + "vkey": "K_D" + } + }, + { + "_": "", + "$": { + "vkey": "K_E" + } + }, + { + "_": "ធ", + "$": { + "vkey": "K_F" + } + }, + { + "_": "អ", + "$": { + "vkey": "K_G" + } + }, + { + "_": "ះ", + "$": { + "vkey": "K_H" + } + }, + { + "_": "", + "$": { + "vkey": "K_I" + } + }, + { + "_": "ញ", + "$": { + "vkey": "K_J" + } + }, + { + "_": "គ", + "$": { + "vkey": "K_K" + } + }, + { + "_": "ឡ", + "$": { + "vkey": "K_L" + } + }, + { + "_": "", + "$": { + "vkey": "K_M" + } + }, + { + "_": "ណ", + "$": { + "vkey": "K_N" + } + }, + { + "_": "", + "$": { + "vkey": "K_O" + } + }, + { + "_": "ភ", + "$": { + "vkey": "K_P" + } + }, + { + "_": "ឈ", + "$": { + "vkey": "K_Q" + } + }, + { + "_": "ឬ", + "$": { + "vkey": "K_R" + } + }, + { + "_": "", + "$": { + "vkey": "K_S" + } + }, + { + "_": "ទ", + "$": { + "vkey": "K_T" + } + }, + { + "_": "", + "$": { + "vkey": "K_U" + } + }, + { + "_": "", + "$": { + "vkey": "K_V" + } + }, + { + "_": "", + "$": { + "vkey": "K_W" + } + }, + { + "_": "ឃ", + "$": { + "vkey": "K_X" + } + }, + { + "_": "", + "$": { + "vkey": "K_Y" + } + }, + { + "_": "ឍ", + "$": { + "vkey": "K_Z" + } + }, + { + "_": "", + "$": { + "vkey": "K_6" + } + }, + { + "_": "", + "$": { + "vkey": "K_HYPHEN" + } + }, + { + "_": "", + "$": { + "vkey": "K_LBRKT" + } + }, + { + "_": "ឭ", + "$": { + "vkey": "K_BKSLASH" + } + }, + { + "_": "ឧ", + "$": { + "vkey": "K_RBRKT" + } + }, + { + "_": "»", + "$": { + "vkey": "K_BKQUOTE" + } + }, + { + "_": "", + "$": { + "vkey": "K_COMMA" + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml b/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml new file mode 100644 index 00000000000..3eddaffdeea --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml.json b/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml.json new file mode 100644 index 00000000000..e26bf18696d --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/strs_invalid-illegal.xml.json @@ -0,0 +1,113 @@ +{ + "keyboard3": { + "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3", + "locale": "mt", + "conformsTo": "45", + "version": { + "number": "1.0.0" + }, + "info": { + "author": "srl295", + "indicator": "🙀", + "layout": "qwerty", + "name": "TestKbd" + }, + "displays": { + "display": [ + { + "output": "a", + "display": "^" + }, + { + "keyId": "e", + "display": "^e" + } + ], + "displayOptions": { + "baseCharacter": "e" + } + }, + "keys": { + "key": [ + { + "id": "hmaqtugha", + "output": "h\\u{FDD0}", + "longPressKeyIds": "a e" + }, + { + "id": "that", + "output": "￿" + } + ] + }, + "layers": { + "formId": "us", + "minDeviceWidth": "123", + "layer": { + "id": "￾", + "row": { + "keys": "hmaqtugha that" + } + } + }, + "variables": { + "string": [ + { + "id": "a", + "value": "\\m{a}" + }, + { + "id": "vst", + "value": "abc pua:\\u{E010}" + } + ], + "set": { + "id": "vse", + "value": "a b \\u{04FFFE} \\u{5FFFF}" + }, + "uset": { + "id": "vus", + "value": "[abc]" + } + }, + "transforms": [ + { + "type": "simple", + "transformGroup": [ + { + "transform": [ + { + "from": "^a", + "to": "\\u{FDD0}" + }, + { + "from": "a", + "to": "\\m{a}\\u{E020}" + } + ] + }, + { + "transform": { + "from": "\\m{a}" + } + }, + { + "reorder": { + "before": "\\u{1A6B}", + "from": "\\u{1A60}[\\u{1A75}-\\u{1A79}]\\u{1A45}", + "order": "10 55 10" + } + } + ] + }, + { + "type": "backspace", + "transformGroup": { + "transform": { + "from": "^e" + } + } + } + ] + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps b/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps new file mode 100644 index 00000000000..8877759a281 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps @@ -0,0 +1,46 @@ + + + + 10.0.1024.0 + 7.0 + + + + + + + + + + + + 1.0 + Test Valid + + + + test_valid.kmx + Keyboard Test Valid + 0 + .kmx + + + test_valid.js + Keyboard Test Valid + 0 + .js + + + + + Test Valid + test_valid + 1.0 + True + + English + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps.json b/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps.json new file mode 100644 index 00000000000..276554d09c3 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/test_valid.kps.json @@ -0,0 +1,64 @@ +{ + "Package": { + "System": { + "KeymanDeveloperVersion": "10.0.1024.0", + "FileVersion": "7.0" + }, + "Options": { + "ExecuteProgram": "", + "MSIFileName": "", + "MSIOptions": "" + }, + "StartMenu": { + "Folder": "", + "Items": "" + }, + "Info": { + "Version": { + "_": "1.0", + "$": { + "URL": "" + } + }, + "Name": { + "_": "Test Valid", + "$": { + "URL": "" + } + } + }, + "Files": { + "File": [ + { + "Name": "test_valid.kmx", + "Description": "Keyboard Test Valid", + "CopyLocation": "0", + "FileType": ".kmx" + }, + { + "Name": "test_valid.js", + "Description": "Keyboard Test Valid", + "CopyLocation": "0", + "FileType": ".js" + } + ] + }, + "Keyboards": { + "Keyboard": { + "Name": "Test Valid", + "ID": "test_valid", + "Version": "1.0", + "RTL": "True", + "Languages": { + "Language": { + "_": "English", + "$": { + "ID": "en" + } + } + } + } + }, + "Strings": "" + } +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml new file mode 100644 index 00000000000..6c28d3f00d7 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml.json b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml.json new file mode 100644 index 00000000000..3308970fdd8 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/xml/tran_fail-empty.xml.json @@ -0,0 +1,28 @@ +{ + "keyboard3": { + "xmlns": "https://schemas.unicode.org/cldr/45/keyboard3", + "locale": "mt", + "conformsTo": "45", + "info": { + "name": "tran-mixed" + }, + "transforms": { + "type": "simple", + "transformGroup": [ + { + "transform": { + "from": "xx", + "to": "x" + } + }, + { + "reorder": { + "from": "ខែ្ម", + "order": "1 3 4 2" + } + }, + {} + ] + } + } +} \ 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 756855e6b9f..b06da2cc034 100644 --- a/developer/src/common/web/utils/test/test-xml-utils.ts +++ b/developer/src/common/web/utils/test/test-xml-utils.ts @@ -1,13 +1,112 @@ import { assert } from 'chai'; import 'mocha'; +import { env } from 'node:process'; +import { readFileSync, writeFileSync } from 'node:fs'; -import { KeymanXMLParser, KeymanXMLGenerator } from '../src/xml-utils.js'; -describe('XML Parser Test', () => { - it('null test', () => assert.ok(new KeymanXMLParser())); +import { KeymanXMLOptions, KeymanXMLReader, KeymanXMLWriter } from '../src/xml-utils.js'; +import { makePathToFixture } from './helpers/index.js'; + +// if true, attempt to WRITE the fixtures +const { GEN_XML_FIXTURES } = env; + +class Case { + options: KeymanXMLOptions; + paths: string[]; +}; + +const read_cases : Case[] = [ + { + options: { type: 'keyboard3' }, + paths: [ + // keyboards + 'disp_maximal.xml', + 'k_020_fr.xml', + 'strs_invalid-illegal.xml', + 'tran_fail-empty.xml', + ], + }, { + options: { type: 'keyboard3-test' }, + paths: [ + // keyboard test + 'k_020_fr-test.xml', + ], + }, { + options: { type: 'kvks' }, + paths: [ + // kvks + 'khmer_angkor.kvks', + ], + }, { + options: { type: 'kps' }, + paths: [ + // kps + 'test_valid.kps', + // 'error_invalid_package_file.kps', + ], + }, { + options: { type: 'kpj' }, + paths: [ + // kpj + 'khmer_angkor.kpj', + ], + }, +]; + +/** read data, or null */ +function readData(path: string) : string | null { + try { + return readFileSync(path, 'utf-8'); + } catch(e) { + if (e?.code !== 'ENOENT') console.error(`reading ${path}`, e); + return null; + } +} + +function readJson(path: string) : any | null { + const data = readData(path); + if(data === null) return null; + return JSON.parse(data); +} + +function writeJson(path: string, data: any) { + writeFileSync(path, JSON.stringify(data, null, ' ')); +} + +describe(`XML Reader Test ${GEN_XML_FIXTURES && '(update mode!)' || ''}`, () => { + for (const c of read_cases) { + const {options, paths} = c; + describe(`test reading ${JSON.stringify(options)}`, () => { + const reader = new KeymanXMLReader(options); + assert.ok(reader); + for (const path of paths) { + const xmlPath = makePathToFixture('xml', `${path}`); + const jsonPath = makePathToFixture('xml', `${path}.json`); + it(`read: ${xmlPath}`, () => { + // get the string data + const xml = readData(xmlPath); + assert.ok(xml, `Could not read ${xmlPath}`); + + // now, parse + const actual = reader.parse(xml); + assert.ok(actual, `Parser failed on ${xmlPath}`); + + // get the expected + const expect = readJson(jsonPath); + if (GEN_XML_FIXTURES) { + console.log(`GEN_XML_FIXTURES: writing ${jsonPath} from actual`); + writeJson(jsonPath, actual); + } else { + assert.ok(expect, `Could not read ${jsonPath} - run with env GEN_XML_FIXTURES=1 to update.`); + assert.deepEqual(actual, expect, `Mismatch of ${xmlPath} vs ${jsonPath}`); + } + }); + } + }); + } }); -describe('XML Generator Test', () => { - it('null test', () => assert.ok(new KeymanXMLGenerator())); +describe('XML Writer Test', () => { + it('null test', () => assert.ok(new KeymanXMLWriter({type: 'kpj'}))); });