diff --git a/common/web/types/build.sh b/common/web/types/build.sh index 90432e16de..a4fac777d4 100755 --- a/common/web/types/build.sh +++ b/common/web/types/build.sh @@ -41,6 +41,16 @@ else cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json" "$THIS_SCRIPT_PATH/build/src/" cp "$KEYMAN_ROOT/common/schemas/kvks/kvks.schema.json" "$THIS_SCRIPT_PATH/build/src/" cp "$KEYMAN_ROOT/common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json" "$THIS_SCRIPT_PATH/build/src/" + # Store CLDR imports + # load all versions that have a cldr_info.json + for CLDR_INFO_PATH in "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/"*/cldr_info.json + do + CLDR_PATH=$(dirname "$CLDR_INFO_PATH") + CLDR_VER=$(basename "$CLDR_PATH") + mkdir -p "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER" + # TODO-LDML: When these are copied, the DOCTYPE will break due to the wrong path. We don't use the DTD so it should be OK. + cp "$CLDR_INFO_PATH" "$CLDR_PATH/import/"*.xml "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER/" + done fi #------------------------------------------------------------------------------------------------------------------- diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 33c30e8bfc..5558fd7199 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -1,15 +1,41 @@ import * as xml2js from 'xml2js'; -import { LDMLKeyboardXMLSourceFile } from './ldml-keyboard-xml.js'; +import { LDMLKeyboardXMLSourceFile, LKImport } from './ldml-keyboard-xml.js'; import Ajv from 'ajv'; import { boxXmlArray } from '../util/util.js'; +import { CompilerCallbacks } from 'src/util/compiler.js'; +import { constants } from '@keymanapp/ldml-keyboard-constants'; export default class LDMLKeyboardXMLSourceFileReader { + callbacks: CompilerCallbacks; + + constructor(callbacks : CompilerCallbacks) { + this.callbacks = callbacks; + } + + readImportFile(version: string, subpath: string): Buffer { + // TODO-LDML: sanitize input string + let importPath = new URL(`../import/${version}/${subpath}`, import.meta.url); + // TODO-LDML: support baseFileName? + return this.callbacks.loadFile(importPath.pathname, importPath); + } + /** * xml2js will not place single-entry objects into arrays. * Easiest way to fix this is to box them ourselves as needed * @param source any */ private boxArrays(source: any) { + if (source?.keyboard) { + if (!source.keyboard.keys) { + source.keyboard.keys = { + key: [], + flicks: [], + }; + } + if (!source.keyboard.keys?.import) { + source.keyboard.keys.import = []; + } + } boxXmlArray(source?.keyboard, 'layers'); boxXmlArray(source?.keyboard?.displays, 'display'); boxXmlArray(source?.keyboard?.names, 'name'); @@ -39,31 +65,90 @@ export default class LDMLKeyboardXMLSourceFileReader { } } boxXmlArray(source?.keyboard?.reorders, 'reorder'); - this.boxImportsAndSpecials(source); + this.boxImportsAndSpecials(source, 'keyboard'); return source; } /** * Recurse over object, boxing up any specials or imports * @param obj any object to be traversed + * @param subtag the leafmost enclosing tag such as 'keyboard' */ - private boxImportsAndSpecials(obj: any) { + private boxImportsAndSpecials(obj: any, subtag: string) { if (!obj) return; if (Array.isArray(obj)) { for (const sub of obj) { - this.boxImportsAndSpecials(sub); + // retain the same subtag + this.boxImportsAndSpecials(sub, subtag); } } else if(typeof obj === 'object') { for (const key of Object.keys(obj)) { - if (key === 'special' || key === 'import') { + if (key === 'special') { boxXmlArray(obj, key); + } else if(key === 'import') { + // Need to 'box it up' first for processing + boxXmlArray(obj, key); + // Now, resolve the import + this.resolveImports(obj, subtag); + // now delete the import array we so carefully constructed, the caller does not + // want to see it. + delete obj['import']; } else { - this.boxImportsAndSpecials(obj[key]); + this.boxImportsAndSpecials(obj[key], key); } } } } + private resolveImports(obj: any, subtag: string) { + // first, the implied imports + if (subtag === 'keys') { + // + this.resolveOneImport(obj, subtag, { + base: constants.cldr_import_base, + path: constants.cldr_implied_keys_import + }); + } + + // now, the explicit imports + for (const asImport of (obj['import'] as LKImport[])) { + this.resolveOneImport(obj, subtag, asImport); + } + } + + private resolveOneImport(obj: any, subtag: string, asImport: LKImport) { + if (asImport.base !== constants.cldr_import_base) { + // TODO-LDML: use custom error class CompilerImportResolveError + throw new Error(`import element with base ${asImport.base} is unsupported, only ${constants.cldr_import_base} is supported.`); + } + const paths = asImport.path.split('/'); + if (paths[0] == '' || paths[1] == '' || paths.length !== 2) { + throw new Error(`import element with invalid path ${asImport.path}: expect the form '${constants.cldr_version_latest}./*.xml'`); + } + const importData: Uint8Array = this.readImportFile(paths[0], paths[1]); + if (!importData || !importData.length) { + throw new Error(`could not read data with path ${asImport.path}: expect the form '${constants.cldr_version_latest}./*.xml'`); + } + const importXml: any = this.loadUnboxed(importData); // TODO-LDML: have to load as any because it is an arbitrary part + const importRootNode = importXml[subtag]; // e.g. + + // importXml will have one property: the root element. + if (!importRootNode) { + throw new Error(`Invalid import file ${asImport.path}: expected ${subtag} as root element`); + } + // pull all children of importXml[subtag] into obj + for (const subsubtag of Object.keys(importRootNode)) { // e.g. + const subsubval = importRootNode[subsubtag]; + if (!Array.isArray(subsubval)) { + throw new Error(`Problem importing ${asImport.path}: not sure how to handle non-array ${subtag}.${subsubtag}`); + } + if (!obj[subsubtag]) { + obj[subsubtag] = []; // start with empty array + } + obj[subsubtag] = [...subsubval, ...obj[subsubtag]]; + } + } + public validate(source: LDMLKeyboardXMLSourceFile, schemaSource: Buffer): void { const schema = JSON.parse(schemaSource.toString('utf8')); const ajv = new Ajv(); @@ -81,7 +166,7 @@ export default class LDMLKeyboardXMLSourceFileReader { } } - public load(file: Uint8Array): LDMLKeyboardXMLSourceFile { + loadUnboxed(file: Uint8Array): LDMLKeyboardXMLSourceFile { let source = (() => { let a: LDMLKeyboardXMLSourceFile; let parser = new xml2js.Parser({ @@ -101,7 +186,11 @@ export default class LDMLKeyboardXMLSourceFileReader { parser.parseString(file, (e: unknown, r: unknown) => { a = r as LDMLKeyboardXMLSourceFile }); return a; })(); + return source; + } + public load(file: Uint8Array): LDMLKeyboardXMLSourceFile { + const source = this.loadUnboxed(file); return this.boxArrays(source); } } diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts index 9f83ad17d5..e9710700bd 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts @@ -31,6 +31,20 @@ export interface LKKeyboard { reorders?: LKReorders; }; +/** + * This is defined as an interface, but actually is resolved during the reading phase + */ +export interface LKImport { + /** + * import base, currently `cldr` is supported + */ + base: string; + /** + * path to imported resource, of the form `techpreview/*.xml` + */ + path: string; +} + export interface LKLocales { locale: LKLocale[]; }; diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts index a2c651731f..de83086f37 100644 --- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -2,16 +2,33 @@ import 'mocha'; import {assert} from 'chai'; import {loadLdmlKeyboardSchema, loadFile, makePathToFixture} from '../helpers/index.js'; import LDMLKeyboardXMLSourceFileReader from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; +import { CompilerCallbacks, CompilerEvent } from '../../src/util/compiler.js'; describe('ldml keyboard xml reader tests', function() { this.slow(500); // 0.5 sec -- json schema validation takes a while + const callbacks : CompilerCallbacks = { + loadFile: function (baseFilename: string, filename: string | URL): Buffer { + throw new Error('Function not implemented.'); + }, + loadLdmlKeyboardSchema: function (): Buffer { + throw new Error('Function not implemented.'); + }, + reportMessage: function (event: CompilerEvent): void { + throw new Error('Function not implemented.'); + }, + loadKvksJsonSchema: function (): Buffer { + throw new Error('Function not implemented.'); + } + }; + it("should fail to load files that don't conform to DTD", function() { const inputFilename = makePathToFixture('invalid-structure-per-dtd.xml'); - let reader = new LDMLKeyboardXMLSourceFileReader(); + let reader = new LDMLKeyboardXMLSourceFileReader(callbacks); const data = loadFile(inputFilename, inputFilename); const source = reader.load(data); assert.isNotNull(source); + // TODO-LDML: could report a messge instead of throw assert.throws(() => { reader.validate(source, loadLdmlKeyboardSchema()); }, `/keyboard: required: must have required property 'names' {"missingProperty":"names"}`); @@ -19,10 +36,11 @@ describe('ldml keyboard xml reader tests', function() { it("should fail to load files with an invalid conformsTo", function() { const inputFilename = makePathToFixture('invalid-conforms-to.xml'); - let reader = new LDMLKeyboardXMLSourceFileReader(); + let reader = new LDMLKeyboardXMLSourceFileReader(callbacks); const data = loadFile(inputFilename, inputFilename); const source = reader.load(data); assert.isNotNull(source); + // TODO-LDML: could report a messge instead of throw assert.throws(() => { reader.validate(source, loadLdmlKeyboardSchema()); }, `/keyboard/conformsTo: enum: must be equal to one of the allowed values {"allowedValues":["techpreview"]}`); diff --git a/common/web/types/tsconfig.json b/common/web/types/tsconfig.json index 9600270371..a32b8e1ed7 100644 --- a/common/web/types/tsconfig.json +++ b/common/web/types/tsconfig.json @@ -12,6 +12,6 @@ ], "references": [ { "path": "../keyman-version/tsconfig.esm.json" }, - { "path": "../../../core/include/ldml/"}, + { "path": "../../../core/include/ldml/tsconfig.json"}, ] } diff --git a/core/include/ldml/keyboardprocessor_ldml.h b/core/include/ldml/keyboardprocessor_ldml.h index 5bab7018f4..ec08b4f234 100644 --- a/core/include/ldml/keyboardprocessor_ldml.h +++ b/core/include/ldml/keyboardprocessor_ldml.h @@ -1,6 +1,6 @@ /* - Copyright: Copyright (C) 2022 SIL International. + Copyright: Copyright (C) 2022-2023 SIL International. Authors: srl295 This file provides constants for the KMX Plus (LDML support) binary format, to be shared between TypeScript and C++ via the generator (below) @@ -16,6 +16,10 @@ #pragma once #define LDML_BKSP_FLAGS_ERROR 0x1 +#define LDML_CLDR_IMPLIED_KEYS_IMPORT "techpreview/keys-Latn-implied.xml" +#define LDML_CLDR_IMPORT_BASE "cldr" +#define LDML_CLDR_VERSION_LATEST "techpreview" +#define LDML_CLDR_VERSION_TECHPREVIEW "techpreview" #define LDML_ELEM_FLAGS_ORDER_BITSHIFT 0x10 #define LDML_ELEM_FLAGS_ORDER_MASK 0xFF0000 #define LDML_ELEM_FLAGS_PREBASE 0x4 diff --git a/core/include/ldml/keyboardprocessor_ldml.ts b/core/include/ldml/keyboardprocessor_ldml.ts index 08fc02036e..bc9d8635ae 100644 --- a/core/include/ldml/keyboardprocessor_ldml.ts +++ b/core/include/ldml/keyboardprocessor_ldml.ts @@ -59,6 +59,22 @@ class Constants { * The version of the LDML processor */ readonly version = '1.0'; + /** + * The techpreview CLDR version + */ + readonly cldr_version_techpreview = 'techpreview'; + /** + * The latest CLDR version + */ + readonly cldr_version_latest = this.cldr_version_techpreview; + /** + * import base + */ + readonly cldr_import_base = 'cldr'; + /** + * implied keys file + */ + readonly cldr_implied_keys_import = `${this.cldr_version_techpreview}/keys-Latn-implied.xml`; /** * Length of a raw section header, in bytes */ diff --git a/core/include/ldml/ldml-const-builder.ts b/core/include/ldml/ldml-const-builder.ts index ad68daeda7..56bdf19c82 100644 --- a/core/include/ldml/ldml-const-builder.ts +++ b/core/include/ldml/ldml-const-builder.ts @@ -10,7 +10,7 @@ const keys = Object.keys(constants); keys.sort(); console.log(` /* - Copyright: Copyright (C) 2022 SIL International. + Copyright: Copyright (C) 2022-2023 SIL International. Authors: srl295 This file provides constants for the KMX Plus (LDML support) binary format, to be shared between TypeScript and C++ via the generator (below) @@ -29,35 +29,37 @@ console.log(` let errs = 0; for (const key of keys) { - const value : any = constants[key as keyof typeof constants]; - const upkey = key.toUpperCase(); - const type = typeof value; - if (type === 'number') { - console.log(`#define LDML_${upkey} 0x${value.toString(16).toUpperCase()}`); - } else if (type === 'string') { - console.log(`#define LDML_${upkey} "${value}"`); - } else if (key === 'section') { - // handle section table - const subkeys = Object.keys(value); - subkeys.sort(); - for (const subkey of subkeys) { - const upsubkey = subkey.toUpperCase(); - const subvalue = value[subkey]; - if (subvalue !== subkey) { - // "can't happen" because tsc would complain - console.error(`In the SectionMap: ${subkey}: '${subvalue}' - expected key and value to match.`); - errs++; - } - const asnum = constants.hex_section_id(subkey); - console.log(`#define LDML_${upkey}ID_${upsubkey} 0x${asnum.toString(16).toUpperCase()} /* "${subkey}" */`); - console.log(`#define LDML_${upkey}NAME_${upsubkey} "${subkey}"`); - } - } else if (type !== 'function') { - console.error(`Don’t know what to do with constants[${key}] of type ${type}`); + const value: any = constants[key as keyof typeof constants]; + const upkey = key.toUpperCase(); + const type = typeof value; + if (type === 'number') { + console.log(`#define LDML_${upkey} 0x${value.toString(16).toUpperCase()}`); + } else if (type === 'string') { + console.log(`#define LDML_${upkey} "${value}"`); + } else if (key === 'section') { + // handle section table + const subkeys = Object.keys(value); + subkeys.sort(); + for (const subkey of subkeys) { + const upsubkey = subkey.toUpperCase(); + const subvalue = value[subkey]; + if (subvalue !== subkey) { + // "can't happen" because tsc would complain + console.error(`In the SectionMap: ${subkey}: '${subvalue}' - expected key and value to match.`); errs++; + } + const asnum = constants.hex_section_id(subkey); + console.log(`#define LDML_${upkey}ID_${upsubkey} 0x${asnum.toString(16).toUpperCase()} /* "${subkey}" */`); + console.log(`#define LDML_${upkey}NAME_${upsubkey} "${subkey}"`); } + } else if (key === 'layr_list_hardware_map' || type === 'function') { + // ignored + } else { + console.error(`Don’t know what to do with constants[${key}] of type ${type}`); + errs++; + } } if (errs != 0) { - throw Error(`Fail: ${errs} error(s), see above.`); + throw Error(`Fail: ${errs} error(s), see above.`); } diff --git a/core/tests/unit/ldml/keyboards/k_003_transform.xml b/core/tests/unit/ldml/keyboards/k_003_transform.xml index decb9375a8..4002fa2c92 100644 --- a/core/tests/unit/ldml/keyboards/k_003_transform.xml +++ b/core/tests/unit/ldml/keyboards/k_003_transform.xml @@ -18,37 +18,10 @@ Note: keys should start with [K_A][K_B][K_C] not [K_Y] when transforms are funct - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/core/tests/unit/ldml/keyboards/k_010_mt.xml b/core/tests/unit/ldml/keyboards/k_010_mt.xml index e123d12a7d..cedc65f554 100644 --- a/core/tests/unit/ldml/keyboards/k_010_mt.xml +++ b/core/tests/unit/ldml/keyboards/k_010_mt.xml @@ -25,12 +25,9 @@ Gets part of the way, - - - - - + + @@ -56,9 +53,6 @@ Gets part of the way, - - - diff --git a/core/tests/unit/ldml/keyboards/k_100_keytest.xml b/core/tests/unit/ldml/keyboards/k_100_keytest.xml new file mode 100644 index 0000000000..d185035c94 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_100_keytest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_101_keytest.xml b/core/tests/unit/ldml/keyboards/k_101_keytest.xml new file mode 100644 index 0000000000..5fb2b7311a --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_101_keytest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_102_keytest.xml b/core/tests/unit/ldml/keyboards/k_102_keytest.xml new file mode 100644 index 0000000000..45ab4b71c0 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_102_keytest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index 0561133199..80e2fa8edc 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -10,8 +10,10 @@ tests = [ 'k_001_tiny', 'k_002_tinyu32', 'k_003_transform', -# TODO-LDML: depends on implied keys -# 'k_010_mt', + 'k_010_mt', + 'k_100_keytest', + 'k_101_keytest', + 'k_102_keytest', ] # Setup kmc diff --git a/core/tools/ldml-const-builder/build.sh b/core/tools/ldml-const-builder/build.sh index ce25033998..ad49783b61 100755 --- a/core/tools/ldml-const-builder/build.sh +++ b/core/tools/ldml-const-builder/build.sh @@ -42,7 +42,7 @@ if builder_start_action build; then fi if builder_start_action run; then - node ../../include/ldml/ldml-const-builder/ldml-const-builder.js > ${KBP_LDML_H_FILE} + node --enable-source-maps ../../include/ldml/ldml-const-builder/ldml-const-builder.js > ${KBP_LDML_H_FILE} echo "Updated ${KBP_LDML_H_FILE}" builder_finish_action success run diff --git a/developer/src/kmc-keyboard/src/compiler/compiler.ts b/developer/src/kmc-keyboard/src/compiler/compiler.ts index 450eb70048..f6a8423c84 100644 --- a/developer/src/kmc-keyboard/src/compiler/compiler.ts +++ b/developer/src/kmc-keyboard/src/compiler/compiler.ts @@ -56,7 +56,7 @@ export default class Compiler { * @returns */ public load(filename: string): LDMLKeyboardXMLSourceFile { - const reader = new LDMLKeyboardXMLSourceFileReader(); + const reader = new LDMLKeyboardXMLSourceFileReader(this.callbacks); const data = this.callbacks.loadFile(filename, filename); const source = reader.load(data); if(!source) { diff --git a/developer/src/kmc-keyboard/src/compiler/key2.ts b/developer/src/kmc-keyboard/src/compiler/key2.ts index 5ede6ce99d..824c2e848f 100644 --- a/developer/src/kmc-keyboard/src/compiler/key2.ts +++ b/developer/src/kmc-keyboard/src/compiler/key2.ts @@ -20,9 +20,30 @@ export class Key2Compiler extends SectionCompiler { return valid; } + /** + * Return a set of all used keys + * public for test + */ + public allUsedKeyIds(): Set { + const s = new Set(); + if (this.keyboard?.layers) { + for (const layers of this.keyboard.layers || []) { + for (const layer of layers.layer || []) { + for (const row of layer.row || []) { + if (row.keys) { + for (const k of row.keys.split(" ")) { + s.add(k); + } + } + } + } + } + } + return s; + } public compile(sections: GlobalSections): Key2 { - if (!this.keyboard.keys.key && !this.keyboard.keys.flicks) { + if (!this.keyboard?.keys?.key && !this.keyboard?.keys?.flicks) { // short-circuit if no keys or flicks return null; } @@ -60,7 +81,26 @@ export class Key2Compiler extends SectionCompiler { } public loadKeys(sections: GlobalSections, sect: Key2) { - for (let key of this.keyboard.keys.key) { + const usedKeys = this.allUsedKeyIds(); + + // TODO-LDML factor common code + // Need 'newer' (later) keys to override older ones. + const reverseKeys = [...this.keyboard.keys.key].reverse(); // newest to oldest + const alreadySeen = new Set(); + // filter out only the keys that haven't already been seen + const uniqueKeys = reverseKeys.filter(({id}) => { + if (!alreadySeen.has(id)) { + alreadySeen.add(id); + return true; + } + return false; + }); + + for (let key of uniqueKeys) { + if (!usedKeys.has(key.id)) { + // TODO-LDML: linting for unused keys + continue; // unused key, skip + } let flags = 0; const flicks = key.flicks; // TODO-LDML: verify that this flick id exists diff --git a/developer/src/kmc-keyboard/src/compiler/keys.ts b/developer/src/kmc-keyboard/src/compiler/keys.ts index 837d28bd26..b50b42521e 100644 --- a/developer/src/kmc-keyboard/src/compiler/keys.ts +++ b/developer/src/kmc-keyboard/src/compiler/keys.ts @@ -14,6 +14,19 @@ export class KeysCompiler extends SectionCompiler { } private validateHardwareLayer(layer: LDMLKeyboard.LKLayer) { + // TODO-LDML factor common code + // Need 'newer' (later) keys to override older ones. + const reverseKeys = [...this.keyboard.keys?.key].reverse(); // newest to oldest + const alreadySeen = new Set(); + // filter out only the keys that haven't already been seen + const uniqueKeys = reverseKeys.filter(({id}) => { + if (!alreadySeen.has(id)) { + alreadySeen.add(id); + return true; + } + return false; + }); + let valid = true; if(layer.row.length > USVirtualKeyMap.length) { this.callbacks.reportMessage(CompilerMessages.Error_HardwareLayerHasTooManyRows()); @@ -32,7 +45,7 @@ export class KeysCompiler extends SectionCompiler { for(let key of keys) { x++; - let keydef = this.keyboard.keys?.key?.find(x => x.id == key); + let keydef = uniqueKeys.find(x => x.id == key); if(!keydef) { this.callbacks.reportMessage(CompilerMessages.Error_KeyNotFoundInKeyBag({keyId: key, col: x+1, row: y+1, layer: layer.id, form: 'hardware'})); valid = false; diff --git a/developer/src/kmc-keyboard/test/helpers/index.ts b/developer/src/kmc-keyboard/test/helpers/index.ts index 210ed90312..73b93cd473 100644 --- a/developer/src/kmc-keyboard/test/helpers/index.ts +++ b/developer/src/kmc-keyboard/test/helpers/index.ts @@ -71,7 +71,7 @@ export function loadSectionFixture(compilerClass: typeof SectionCompiler, filena const data = callbacks.loadFile(inputFilename, inputFilename); assert.isNotNull(data); - const reader = new LDMLKeyboardXMLSourceFileReader(); + const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); const source = reader.load(data); assert.isNotNull(source); assert.doesNotThrow(() => { diff --git a/developer/src/kmc-keyboard/test/test-key2.ts b/developer/src/kmc-keyboard/test/test-key2.ts index 4ed28b0f9a..cb3e254aac 100644 --- a/developer/src/kmc-keyboard/test/test-key2.ts +++ b/developer/src/kmc-keyboard/test/test-key2.ts @@ -29,7 +29,7 @@ describe('key2', function () { const [q] = key2.keys.filter(({ id }) => id.value === 'q'); assert.ok(q); assert.isFalse(!!(q.flags & constants.key2_key_flags_gap)); - assert.equal(q.width, 32); // ceil(3.14159 * 10.0) + assert.equal(q.width, 32, 'q\'s width'); // ceil(3.14159 * 10.0) assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem assert.equal(q.longPress.toString(), 'á é í'); assert.equal(q.longPressDefault.value, 'é'); @@ -85,7 +85,7 @@ describe('key2', function () { const [Qgap] = key2.keys.filter(({ id }) => id.value === 'Q'); assert.ok(Qgap); - assert.isTrue(!!(Qgap.flags & constants.key2_key_flags_gap)); + assert.isTrue(!!(Qgap.flags & constants.key2_key_flags_gap), 'Q’s gap='); const [Wshift] = key2.keys.filter(({ id }) => id.value === 'W'); assert.isNotNull(Wshift); diff --git a/developer/src/kmc-keyboard/test/test-keys.ts b/developer/src/kmc-keyboard/test/test-keys.ts index e38a9205dc..eef14fc0b8 100644 --- a/developer/src/kmc-keyboard/test/test-keys.ts +++ b/developer/src/kmc-keyboard/test/test-keys.ts @@ -67,7 +67,6 @@ describe('keys', function () { let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-key-missing-attrs.xml', compilerTestCallbacks) as Keys; assert.isNull(keys); assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_KeyMissingToGapOrSwitch({keyId: 'Q'})); }); it('should accept layouts with gap/switch keys', function() { diff --git a/package-lock.json b/package-lock.json index cff877e90e..a6cc4380f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,8 @@ "dependencies": { "@keymanapp/common-types": "file:common/web/types", "@keymanapp/hextobin": "file:common/tools/hextobin", - "@keymanapp/keyman-version": "file:common/web/keyman-version" + "@keymanapp/keyman-version": "file:common/web/keyman-version", + "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" }, "devDependencies": { "chai": "^4.3.4", diff --git a/package.json b/package.json index 09c73d7a69..4f33d63962 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "@keymanapp/hextobin": "file:common/tools/hextobin", "@keymanapp/keyman-version": "file:common/web/keyman-version", - "@keymanapp/common-types": "file:common/web/types" + "@keymanapp/common-types": "file:common/web/types", + "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" } } diff --git a/resources/standards-data/ldml-keyboards/techpreview/3.0/mt.xml b/resources/standards-data/ldml-keyboards/techpreview/3.0/mt.xml index 04ac9a9e12..2c5e6a880c 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/3.0/mt.xml +++ b/resources/standards-data/ldml-keyboards/techpreview/3.0/mt.xml @@ -14,7 +14,7 @@ - + @@ -33,22 +33,19 @@ - - + + - - - - - + + @@ -56,7 +53,7 @@ - + diff --git a/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json b/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json index 3e8c33a252..10924e1ec9 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json +++ b/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json @@ -1,5 +1,5 @@ { - "sha": "e906a2e9a628e7339e4fbc68f8007747d5f5374d", - "description": "release-42-301-ge906a2e9a6", - "date": "Sat, 29 Oct 2022 21:03:08 +0000" + "sha": "d0bdd8868a502afb6c1c1a3e2bf7c3404620f23b", + "description": "release-42-m1-67-gd0bdd8868a", + "date": "Wed, 25 Jan 2023 21:03:15 +0000" } diff --git a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd index b1a9f305ff..8240be771f 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd +++ b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd @@ -1,5 +1,5 @@ + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.xsd b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.xsd index 4250f7f877..5f6020f51f 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.xsd +++ b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.xsd @@ -1,6 +1,6 @@ + diff --git a/resources/standards-data/ldml-keyboards/techpreview/import/keys-Latn-implied.xml b/resources/standards-data/ldml-keyboards/techpreview/import/keys-Latn-implied.xml new file mode 100644 index 0000000000..25f9f0183d --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/import/keys-Latn-implied.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/import/keys-Zyyy-punctuation.xml b/resources/standards-data/ldml-keyboards/techpreview/import/keys-Zyyy-punctuation.xml index 38062c5c26..eadad84b53 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/import/keys-Zyyy-punctuation.xml +++ b/resources/standards-data/ldml-keyboards/techpreview/import/keys-Zyyy-punctuation.xml @@ -2,36 +2,41 @@ + - - - - - - - - - + + + + + + + + + + + - + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json b/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json index 8e29081c3d..93fcc00cfa 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json +++ b/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json @@ -159,6 +159,12 @@ "multiTap": { "type": "string" }, + "stretch": { + "enum": [ + "true" + ], + "type": "string" + }, "switch": { "type": "string" },