diff --git a/common/schemas/kvk/kvk.ksy b/common/schemas/kvk/kvk.ksy new file mode 100644 index 00000000000..11d8f2faa77 --- /dev/null +++ b/common/schemas/kvk/kvk.ksy @@ -0,0 +1,122 @@ +meta: + id: kvk + title: Keyman Visual Keyboard + file-extension: kvk + license: MIT + ks-version: 0.9 + endian: le + bit-endian: le +doc: | + KVK is the binary file format for Keyman Visual Keyboard + files. KVKS is the equivalent XML source file format +doc-ref: + - https://github.com/keymanapp/keyman/ +seq: + - id: header + type: header + - id: keys + type: keys +types: + header: + seq: + - id: identifier + contents: 'KVKF' + doc: Magic file identifier, always KVKF + - id: version + contents: [0, 6, 0, 0] + doc: Version number of KVK file format, always 0x00000600 + - id: flag + type: header_flags + - id: associated_keyboard + type: string + - id: ansi_font + type: font + - id: unicode_font + type: font + + header_flags: + seq: + - id: display_102 + type: b1 + doc: kvkh102, Keyboard should display 102nd key + - id: display_underlying + type: b1 + doc: kvkhDisplayUnderlying, Keyboard should display underlying characters + - id: use_underlying + type: b1 + doc: kvkhUseUnderlying, + - id: altgr + type: b1 + doc: kvkhAltGr, Keyboard should treat left/right Ctrl and Alt separately + + keys: + seq: + - id: count + type: u4 + - id: key + type: key + repeat: expr + repeat-expr: count + key: + seq: + - id: flags + type: key_flags + - id: modifiers + type: key_modifiers + - id: vkey + type: u2 + - id: text + type: string + - id: bitmap + type: u4 + + key_modifiers: + seq: + - id: shift + type: b1 + - id: ctrl + type: b1 + - id: alt + type: b1 + - id: lctrl + type: b1 + - id: rctrl + type: b1 + - id: lalt + type: b1 + - id: ralt + type: b1 + - id: padding + type: b1 + doc: reserved, + - id: zeropad + contents: [0] + + key_flags: + seq: + - id: bitmap + type: b1 + - id: unicode + type: b1 + - id: padding + type: b6 + + font: + seq: + - id: name + type: string + - id: size + type: u4 + - id: color + type: u4 + + string: + seq: + - id: len + type: u2 + - id: str + type: str + size: len*2 - 2 + encoding: utf-16 + - id: zero_terminator + contents: [0,0] diff --git a/common/schemas/kvks/README.md b/common/schemas/kvks/README.md new file mode 100644 index 00000000000..fb4f2a8a687 --- /dev/null +++ b/common/schemas/kvks/README.md @@ -0,0 +1,14 @@ +# .kvks schema + +This schema validates .kvks files, according to the reference implementation +from VisualKeyboardLoaderXML.pas. + +## Notes on conversion from xsd to json-schema + +Converted using xsd2json. Following structural changes: + +* kvk-version base type from km-version to string, copy km-version pattern in +* remove xs:all bracketing +* remove format:double from fontsize, change type to string +* encoding property changed type to array +* kvk-key added _ property for base text value \ No newline at end of file diff --git a/common/schemas/kvks/kvks.schema.json b/common/schemas/kvks/kvks.schema.json new file mode 100644 index 00000000000..fd58f56e380 --- /dev/null +++ b/common/schemas/kvks/kvks.schema.json @@ -0,0 +1,155 @@ +{ + "title": "kvks.xsd", + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "visualkeyboard": { + "properties": { + "header": { + "$ref": "#/definitions/kvk-header" + }, + "encoding": { + "type": "array", + "items": { + "$ref": "#/definitions/kvk-encoding" + } + } + }, + "required": [ + "header" + ], + "additionalProperties": false, + "type": "object" + } + }, + "required": [ + "visualkeyboard" + ], + "additionalProperties": false, + "definitions": { + "kvk-header": { + "type": "object", + "properties": { + "version": { + "$ref": "#/definitions/kvk-version" + }, + "kbdname": { + "type": "string" + }, + "flags": { + "$ref": "#/definitions/kvk-header-flags" + }, + "layout": { + "type": "string" + } + }, + "required": [ + "version" + ], + "additionalProperties": false + }, + "kvk-header-flags": { + "type": "object", + "properties": { + "key102": { + "$ref": "#/definitions/km-empty" + }, + "displayunderlying": { + "$ref": "#/definitions/km-empty" + }, + "usealtgr": { + "$ref": "#/definitions/km-empty" + }, + "useunderlying": { + "$ref": "#/definitions/km-empty" + } + }, + "additionalProperties": false + }, + "kvk-encoding": { + "type": "object", + "properties": { + "layer": { + "type": "array", + "items": { + "$ref": "#/definitions/kvk-layer" + } + }, + "name": { + "$ref": "#/definitions/kvk-encoding-name" + }, + "fontname": { + "type": "string" + }, + "fontsize": { + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "kvk-layer": { + "type": "object", + "properties": { + "key": { + "type": "array", + "items": { + "$ref": "#/definitions/kvk-key" + } + }, + "shift": { + "$ref": "#/definitions/kvk-layer-shift" + } + }, + "required": [ + "shift" + ], + "additionalProperties": false + }, + "kvk-key": { + "type": "object", + "properties": { + "bitmap": { + "type": "string" + }, + "vkey": { + "type": "string" + }, + "_": { + "type": "string" + } + }, + "required": [ + "vkey" + ], + "additionalProperties": false + }, + "km-empty": { + "type": "string" + }, + "kvk-encoding-name": { + "type": "string", + "enum": [ + "ansi", + "unicode" + ] + }, + "kvk-layer-shift": { + "type": "string", + "pattern": "S?(C|LC|RC)?(A|LA|RA)?" + }, + "kvk-version": { + "type": "string", + "pattern": "(\\d+\\.)+(\\d+)", + "enum": [ + "10.0" + ] + }, + "km-version": { + "type": "string", + "pattern": "(\\d+\\.)+(\\d+)" + } + } +} \ No newline at end of file diff --git a/common/schemas/kvks/kvks.xsd b/common/schemas/kvks/kvks.xsd new file mode 100644 index 00000000000..17056a5a57b --- /dev/null +++ b/common/schemas/kvks/kvks.xsd @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/developer/src/kmc-keyboard/build.sh b/developer/src/kmc-keyboard/build.sh index 27aeccf27ee..938e0e1ecaa 100755 --- a/developer/src/kmc-keyboard/build.sh +++ b/developer/src/kmc-keyboard/build.sh @@ -36,6 +36,7 @@ else # We need the schema file at runtime and bundled, so always copy it for all actions except `clean` mkdir -p "$THIS_SCRIPT_PATH/build/src/" 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/" fi #------------------------------------------------------------------------------------------------------------------- diff --git a/developer/src/kmc-keyboard/package.json b/developer/src/kmc-keyboard/package.json index 32884d49c8c..864f6967c86 100644 --- a/developer/src/kmc-keyboard/package.json +++ b/developer/src/kmc-keyboard/package.json @@ -24,9 +24,9 @@ "dependencies": { "@keymanapp/keyman-version": "*", "ajv": "^8.11.0", - "restructure": "^3.0.0", + "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", "semver": "^7.3.7", - "xml2js": "^0.4.19" + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" }, "devDependencies": { "@types/chai": "^4.1.7", diff --git a/developer/src/kmc-keyboard/src/compiler/callbacks.ts b/developer/src/kmc-keyboard/src/compiler/callbacks.ts index 8d32f5e8c4a..f6cd673b562 100644 --- a/developer/src/kmc-keyboard/src/compiler/callbacks.ts +++ b/developer/src/kmc-keyboard/src/compiler/callbacks.ts @@ -6,5 +6,6 @@ export interface CompilerEvent { export default interface CompilerCallbacks { loadFile(baseFilename: string, filename: string): Buffer; loadLdmlKeyboardSchema(): Buffer; + loadKvksJsonSchema(): Buffer; reportMessage(event: CompilerEvent): void; }; diff --git a/developer/src/kmc-keyboard/src/compiler/messages.ts b/developer/src/kmc-keyboard/src/compiler/messages.ts index 24c4370767a..409b37acab9 100644 --- a/developer/src/kmc-keyboard/src/compiler/messages.ts +++ b/developer/src/kmc-keyboard/src/compiler/messages.ts @@ -20,7 +20,7 @@ export class CompilerMessages { static Error_InvalidNormalization = (o:{form: string}) => m(this.ERROR_InvalidNormalization, `Invalid normalization form '${o.form}`); static ERROR_InvalidNormalization = SevError | 0x0001; - static Error_InvalidLocale = (o:{tag: string}) => m(this.ERROR_InvalidLocale, `Invalid BCP 47 locale form '${o.tag}`); + static Error_InvalidLocale = (o:{tag: string}) => m(this.ERROR_InvalidLocale, `Invalid BCP 47 locale form '${o.tag}'`); static ERROR_InvalidLocale = SevError | 0x0002; static Error_HardwareLayerHasTooManyRows = () => m(this.ERROR_HardwareLayerHasTooManyRows, `'hardware' layer has too many rows`); diff --git a/developer/src/kmc-keyboard/src/kmx/kmx.ts b/developer/src/kmc-keyboard/src/kmx/kmx.ts index 9583d9a889e..b5cf2e1df2d 100644 --- a/developer/src/kmc-keyboard/src/kmx/kmx.ts +++ b/developer/src/kmc-keyboard/src/kmx/kmx.ts @@ -2,6 +2,8 @@ import * as r from 'restructure'; /* Definitions from kmx_file.h. Must be kept in sync */ +// TODO: split kmx-file from kmx in-memory, similar to what I've done for kvk (keep restructure decl + BUILDER_ interfaces together) + // In memory representations of KMX structures // kmx-builder will transform these to the corresponding COMP_xxxx diff --git a/developer/src/kmc-keyboard/src/kvk/kvk-file-reader.ts b/developer/src/kmc-keyboard/src/kvk/kvk-file-reader.ts new file mode 100644 index 00000000000..c3ee8924506 --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/kvk-file-reader.ts @@ -0,0 +1,34 @@ +import KVKFile, { BUILDER_KVK_FILE, BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_HEADER_VERSION } from "./kvk-file.js"; +import { VisualKeyboard, VisualKeyboardKey } from "./visual-keyboard.js"; + +export default class KvkFileReader { + public read(source: Uint8Array): VisualKeyboard { + let binary: BUILDER_KVK_FILE; + let kvk = new KVKFile(); + binary = kvk.KVK_FILE.fromBuffer(source); + if(binary.header.identifier != BUILDER_KVK_HEADER_IDENTIFIER || + binary.header.version != BUILDER_KVK_HEADER_VERSION) { + return null; + } + let result = new VisualKeyboard(); + result.header.version = binary.header.version; + result.header.flags = binary.header.flags; + result.header.associatedKeyboard = binary.header.associatedKeyboard.str; + result.header.ansiFont.color = binary.header.ansiFont.color; + result.header.ansiFont.name = binary.header.ansiFont.name.str; + result.header.ansiFont.size = binary.header.ansiFont.size; + result.header.unicodeFont.color = binary.header.unicodeFont.color; + result.header.unicodeFont.name = binary.header.unicodeFont.name.str; + result.header.unicodeFont.size = binary.header.unicodeFont.size; + for(let binaryKey of binary.keys) { + let key: VisualKeyboardKey = { + flags: binaryKey.flags, + vkey: binaryKey.vkey, + shift: binaryKey.shift, + text: binaryKey.text.str + }; + result.keys.push(key); + } + return result; + } +}; \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/kvk/kvk-file-writer.ts b/developer/src/kmc-keyboard/src/kvk/kvk-file-writer.ts new file mode 100644 index 00000000000..78a484284bd --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/kvk-file-writer.ts @@ -0,0 +1,72 @@ +import KVKFile, { BUILDER_KVK_FILE, BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_HEADER_VERSION, BUILDER_KVK_KEY, BUILDER_KVK_STRING } from "./kvk-file.js"; +import { VisualKeyboard } from "./visual-keyboard.js"; + +export default class KvkFileWriter { + /** + * Writes the visual keyboard to a binary .kvk format byte array. + * @param source VisualKeyboard + * @returns Uint8Array, the .kvk file + */ + write(source: VisualKeyboard): Uint8Array { + const binary = this.build(source); + const kvk = new KVKFile(); + const file: Uint8Array = new Uint8Array(kvk.KVK_FILE.size(binary)); + const data = kvk.KVK_FILE.toBuffer(binary); + file.set(data, 0); + return file; + } + + private build(source: VisualKeyboard) { + const binary: BUILDER_KVK_FILE = { + header: { + identifier: BUILDER_KVK_HEADER_IDENTIFIER, + version: BUILDER_KVK_HEADER_VERSION, + associatedKeyboard: {len:0,str:''}, + flags: source.header.flags, + ansiFont:{ + color: source.header.ansiFont.color, + size: source.header.ansiFont.size, + name: {len:0,str:''} + }, + unicodeFont:{ + color: source.header.unicodeFont.color, + size: source.header.unicodeFont.size, + name: {len:0,str:''} + }, + }, + keyCount: source.keys.length, + keys:[] + }; + + this.setString(binary.header.associatedKeyboard, source.header.associatedKeyboard); + this.setString(binary.header.ansiFont.name, source.header.ansiFont.name); + this.setString(binary.header.unicodeFont.name, source.header.unicodeFont.name); + + for(let sourceKey of source.keys) { + const binaryKey: BUILDER_KVK_KEY = { + flags: sourceKey.flags, + vkey: sourceKey.vkey, + shift: sourceKey.shift, + text: { len: 0, str: '' }, + bitmap: 0 + }; + this.setString(binaryKey.text, sourceKey.text); + binary.keys.push(binaryKey); + } + + return binary; + } + + /** + * Fills a kvk string from a source string. Note that the format includes both + * a length word and zero termination. + * + * @param str + * @param value + * @returns number + */ + private setString(str: BUILDER_KVK_STRING, value: string): void { + str.len = value.length + 1; + str.str = value; + } +}; \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/kvk/kvk-file.ts b/developer/src/kmc-keyboard/src/kvk/kvk-file.ts new file mode 100644 index 00000000000..b33117e0f0f --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/kvk-file.ts @@ -0,0 +1,122 @@ +import * as r from 'restructure'; +// +// Binary backing structures for .kvk format +// matching VisualKeyboardSaverBinary.pas +// + +export const BUILDER_KVK_HEADER_IDENTIFIER = 0x464B564B; // 'KVKF', little-endian +export const BUILDER_KVK_HEADER_VERSION = 0x0600; // Keyman 6.0 + +export interface BUILDER_KVK_STRING { + len: number; + str: string; +}; + +export interface BUILDER_KVK_FONT { + name: BUILDER_KVK_STRING; // 0000, variable length + size: number; // 4 bytes + color: number; // 4 bytes +}; + +export const BUILDER_KVK_FONT_Size = 8; // size of fixed elements of BUILDER_KVK_FONT + +export const enum BUILDER_KVK_KEY_FLAGS { + kvkkBitmap = 0x01, + kvkkUnicode = 0x02 +}; + +export const enum BUILDER_KVK_SHIFT_STATE { + KVKS_NORMAL = 0, + KVKS_SHIFT = 1, + KVKS_CTRL = 2, + KVKS_ALT = 4, + KVKS_LCTRL = 8, + KVKS_RCTRL = 16, + KVKS_LALT = 32, + KVKS_RALT = 64 +}; + +export interface BUILDER_KVK_KEY { + flags: BUILDER_KVK_KEY_FLAGS; // 0000, 1 byte + shift: number; // 0001, 2 bytes + vkey: number; // 0003, 2 bytes + text: BUILDER_KVK_STRING; // 0005, variable length + bitmap: number; // always 0 // 4 bytes +}; + +export const BUILDER_KVK_KEY_Size = 9; // size of fixed elements of BUILDER_KVK_KEY + +export const enum BUILDER_KVK_HEADER_FLAGS { + kvkh102 = 0x01, + kvkhDisplayUnderlying = 0x02, + kvkhUseUnderlying = 0x04, + kvkhAltGr = 0x08 +}; + +export interface BUILDER_KVK_HEADER { + identifier: number; // 0000, 4 bytes + version: number; // 0004, 4 bytes + flags: BUILDER_KVK_HEADER_FLAGS; // 0008, 1 byte + associatedKeyboard: BUILDER_KVK_STRING; // 0009, variable length + ansiFont: BUILDER_KVK_FONT; // variable length + unicodeFont: BUILDER_KVK_FONT; // variable length +}; + +export const BUILDER_KVK_HEADER_Size = 9 + BUILDER_KVK_FONT_Size + BUILDER_KVK_FONT_Size; // size of fixed elements of BUILDER_KVK_HEADER + +export interface BUILDER_KVK_FILE { + header: BUILDER_KVK_HEADER; // variable length + keyCount: number; // 4 bytes + keys: BUILDER_KVK_KEY[]; // variable length +}; + +export default class KVKFile { + public KVK_HEADER: any; + public KVK_KEY: any; + public KVK_KEYS: any; + public KVK_FONT: any; + public KVK_FILE: any; + public KVK_STRING: any; + + constructor() { + // + // Binary restructure definitions matching VisualKeyboardSaverBinary.pas + // TODO: move binaries to separate kvk-file.ts + // + + this.KVK_STRING = new r.Struct({ + len: r.int16le, + str: new r.String(null, 'utf16le') + }); + + this.KVK_FONT = new r.Struct({ + name: this.KVK_STRING, + size: r.int32le, + color: r.uint32le + }); + + this.KVK_KEY = new r.Struct({ + flags: r.uint8, + shift: r.uint16le, + vkey: r.uint16le, + text: this.KVK_STRING, + bitmap: r.uint32le // always 00 00 00 00 for now + }); + + this.KVK_HEADER = new r.Struct({ + identifier: r.uint32le, // KVKF + version: r.uint32le, // 0x0600 + flags: r.uint8, + associatedKeyboard: this.KVK_STRING, + ansiFont: this.KVK_FONT, + unicodeFont: this.KVK_FONT + }); + + this.KVK_FILE = new r.Struct({ + header: this.KVK_HEADER, + keyCount: r.uint32le, + keys: new r.Array(this.KVK_KEY, 'keyCount') + }); + + } +}; \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/kvk/kvks-file-reader.ts b/developer/src/kmc-keyboard/src/kvk/kvks-file-reader.ts new file mode 100644 index 00000000000..3490c709b59 --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/kvks-file-reader.ts @@ -0,0 +1,167 @@ +import * as xml2js from 'xml2js'; +import KVKSourceFile from './kvks-file.js'; +import CompilerCallbacks from '../compiler/callbacks.js'; +import Ajv from 'ajv'; +import { CompilerMessages } from '../compiler/messages.js'; +import { boxXmlArray } from '../util/util.js'; +import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKey, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js'; +import { USVirtualKeyCodes } from '../ldml-keyboard/virtual-key-constants.js'; +import { BUILDER_KVK_HEADER_VERSION } from './kvk-file.js'; + +export default class KVKSFileReader { + private readonly callbacks: CompilerCallbacks; + + constructor (callbacks: CompilerCallbacks) { + this.callbacks = callbacks; + } + + public read(file: Uint8Array): VisualKeyboard { + let source = this.internalRead(file); + return this.transform(source); + } + + public internalRead(file: Uint8Array): KVKSourceFile { + let source: KVKSourceFile; + + const parser = new xml2js.Parser({ + explicitArray: false, + mergeAttrs: true, + 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. + }); + + parser.parseString(file, (e: unknown, r: unknown) => { source = r as KVKSourceFile }); + source = this.boxArrays(source); + this.cleanupUnderscore('visualkeyboard', source.visualkeyboard); + return this.validate(source); + } + + /** + * The only element that allows spaces is . Remove + * all other empty whitespace-only values. + * @param root + * @param source + */ + private cleanupUnderscore(root: string, source: any) { + if(root != 'key') { + if(source?.['_']?.trim() === '') { + delete source['_']; + } + } + + for(let key of Object.keys(source)) { + if(Array.isArray(source[key])) { + for(let item of source[key]) { + if(typeof(item) === 'object') { + this.cleanupUnderscore(key, item); + } + } + } else if(typeof source[key] === 'object') { + this.cleanupUnderscore(key, source[key]); + } + } + } + + public validate(source: KVKSourceFile): KVKSourceFile { + const schema = JSON.parse(this.callbacks.loadKvksJsonSchema().toString('utf8')); + const ajv = new Ajv(); + if(!ajv.validate(schema, source)) { + console.dir(source, {depth:8}); + this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: ajv.errorsText()})); + return null; + } + return source; + } + + public transform(source: KVKSourceFile): VisualKeyboard { + // NOTE: at this point, the xml should have been validated + // and matched the schema result so we can assume properties exist + let result: VisualKeyboard = { + header: { + version: BUILDER_KVK_HEADER_VERSION, + flags: 0, + ansiFont: { name: "Arial", size: -12, color: 0xFF000008 }, // TODO-LDML: consider defaults + unicodeFont: { name: "Arial", size: -12, color: 0xFF000008 }, // TODO-LDML: consider defaults + associatedKeyboard: source.visualkeyboard?.header?.kbdname + }, + keys: [] + }; + + if(source.visualkeyboard?.header?.flags?.displayunderlying !== undefined) { + result.header.flags |= VisualKeyboardHeaderFlags.kvkhDisplayUnderlying; + } + if(source.visualkeyboard?.header?.flags?.key102 !== undefined) { + result.header.flags |= VisualKeyboardHeaderFlags.kvkh102; + } + if(source.visualkeyboard?.header?.flags?.usealtgr !== undefined) { + result.header.flags |= VisualKeyboardHeaderFlags.kvkhAltGr; + } + if(source.visualkeyboard?.header?.flags?.useunderlying !== undefined) { + result.header.flags |= VisualKeyboardHeaderFlags.kvkhUseUnderlying; + } + + for(let encoding of source.visualkeyboard.encoding) { + let isUnicode = (encoding.name == 'unicode'), + font = isUnicode ? result.header.unicodeFont : result.header.ansiFont; + font.name = encoding.fontname; + font.size = parseInt(encoding.fontsize,10); + for(let layer of encoding.layer) { + let shift = this.kvksShiftToKvkShift(layer.shift); + for(let sourceKey of layer.key) { + let vkey = (USVirtualKeyCodes as any)[sourceKey.vkey]; + if(!vkey) { + this.callbacks.reportMessage(CompilerMessages.Error_VkeyIsNotValid({vkey: sourceKey.vkey})); + continue; + } + let key: VisualKeyboardKey = { + flags: isUnicode ? VisualKeyboardKeyFlags.kvkkUnicode : 0, // TODO-LDML: bitmap support + shift: shift, + text: sourceKey._ ?? '', + vkey: vkey + } + result.keys.push(key); + } + } + } + + return result; + } + + /** + * xml2js will not place single-entry objects into arrays. + * Easiest way to fix this is to box them ourselves as needed + * @param source KVKSourceFile + */ + private boxArrays(source: KVKSourceFile) { + boxXmlArray(source.visualkeyboard, 'encoding'); + for(let encoding of source.visualkeyboard.encoding) { + boxXmlArray(encoding, 'layer'); + for(let layer of encoding.layer) { + boxXmlArray(layer, 'key'); + } + } + return source; + } + + + public kvksShiftToKvkShift(shift: string): VisualKeyboardShiftState { + shift = shift.toUpperCase(); + + // TODO-LDML(lowpri): make a map of this? + for(let state of VisualKeyboardLegalShiftStates) { + if(state.name == shift) { + return state.shift; + } + } + return 0; + } +} \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/kvk/kvks-file.ts b/developer/src/kmc-keyboard/src/kvk/kvks-file.ts new file mode 100644 index 00000000000..46b860c59bf --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/kvks-file.ts @@ -0,0 +1,48 @@ +// .kvks xml format +// See VisualKeyboardLoaderXML.pas, not the same as VisualKeyboardImportXML.pas! + +export default interface KVKSourceFile { + /** + * -- the root element. + */ + visualkeyboard: KVKSVisualKeyboard; +} + +export interface KVKSVisualKeyboard { + header?: KVKSHeader; + encoding?: KVKSEncoding[]; +}; + +export interface KVKSHeader { + version?: string; + kbdname?: string; + flags?: KVKSFlags; + keybitmap?: string; + layout?: string; +}; + +export interface KVKSFlags { + key102?: string; + displayunderlying?: string; + useunderlying?: string; + usealtgr?: string; +}; + +export interface KVKSEncoding { + name?: string; + fontname?: string; + fontsize?: string; + layer?: KVKSLayer[]; +}; + +export interface KVKSLayer { + shift?: string; + key?: KVKSKey[]; +}; + +export interface KVKSKey { + vkey?: string; + bitmap?: string; + _?: string; +}; + diff --git a/developer/src/kmc-keyboard/src/kvk/visual-keyboard.ts b/developer/src/kmc-keyboard/src/kvk/visual-keyboard.ts new file mode 100644 index 00000000000..c50011ca46d --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/visual-keyboard.ts @@ -0,0 +1,79 @@ +// +// Visual Keyboard in-memory data +// +// Corresponds to .kvk / .kvks file data +// + +import { BUILDER_KVK_SHIFT_STATE, BUILDER_KVK_HEADER_FLAGS, BUILDER_KVK_KEY_FLAGS } from "./kvk-file"; + +export class VisualKeyboard { + header: VisualKeyboardHeader = {flags: 0, ansiFont:{}, unicodeFont:{}}; + keys: VisualKeyboardKey[] = []; +}; + +export { BUILDER_KVK_HEADER_FLAGS as VisualKeyboardHeaderFlags } from "./kvk-file"; + +export class VisualKeyboardHeader { + version?: number; // 0x0600 + flags: BUILDER_KVK_HEADER_FLAGS; + associatedKeyboard?: string; + ansiFont: VisualKeyboardFont; // generally unused + unicodeFont: VisualKeyboardFont; +}; + +export class VisualKeyboardFont { + name?: string; + size?: number; + color?: number; // unused +}; + +export { BUILDER_KVK_KEY_FLAGS as VisualKeyboardKeyFlags } from "./kvk-file.js"; + +export { BUILDER_KVK_SHIFT_STATE as VisualKeyboardShiftState } from "./kvk-file.js"; + +export class VisualKeyboardKey { + flags?: BUILDER_KVK_KEY_FLAGS; + shift?: BUILDER_KVK_SHIFT_STATE; + vkey?: number; + text?: string; + //bitmap: bitmap-image, unsupported in ldml keyboards; may include for round-tripabilty? +}; + +interface VisualKeyboardLegalShiftState { + desc: string; + name: string; + shift: number; + vkeys: number[]; +}; + +import { BUILDER_KVK_SHIFT_STATE as ss } from "./kvk-file.js"; +import { USVirtualKeyCodes as vk } from "../ldml-keyboard/virtual-key-constants.js"; + +export const VisualKeyboardLegalShiftStates: VisualKeyboardLegalShiftState[] = [ + {desc: 'Unshifted', name: '', shift: ss.KVKS_NORMAL, vkeys: []}, //1 + + {desc: 'Shift', name: 'S', shift: ss.KVKS_SHIFT, vkeys: [vk.K_SHIFT]}, + {desc: 'Ctrl', name: 'C', shift: ss.KVKS_CTRL, vkeys: [vk.K_CONTROL]}, + {desc: 'Alt', name: 'A', shift: ss.KVKS_ALT, vkeys: [vk.K_ALT]}, + {desc: 'Shift+Ctrl', name: 'SC', shift: ss.KVKS_SHIFT | ss.KVKS_CTRL, vkeys: [vk.K_SHIFT,vk.K_CONTROL]}, + {desc: 'Shift+Alt', name: 'SA', shift: ss.KVKS_SHIFT | ss.KVKS_ALT, vkeys: [vk.K_SHIFT,vk.K_ALT]}, + {desc: 'Ctrl+Alt', name: 'CA', shift: ss.KVKS_CTRL | ss.KVKS_ALT, vkeys: [vk.K_CONTROL,vk.K_ALT]}, + {desc: 'Shift+Ctrl+Alt', name: 'SCA', shift: ss.KVKS_SHIFT | ss.KVKS_CTRL | ss.KVKS_ALT, vkeys: [vk.K_SHIFT,vk.K_CONTROL,vk.K_ALT]}, //7 + + {desc: 'Left Ctrl', name: 'LC', shift: ss.KVKS_LCTRL, vkeys: [vk.K_LCONTROL]}, + {desc: 'Right Ctrl', name: 'RC', shift: ss.KVKS_RCTRL, vkeys: [vk.K_RCONTROL]}, + {desc: 'Left Alt', name: 'LA', shift: ss.KVKS_LALT, vkeys: [vk.K_LALT]}, + {desc: 'Right Alt', name: 'RA', shift: ss.KVKS_RALT, vkeys: [vk.K_RALT]}, + {desc: 'Shift+Left Ctrl', name: 'SLC', shift: ss.KVKS_SHIFT | ss.KVKS_LCTRL, vkeys: [vk.K_SHIFT,vk.K_LCONTROL]}, + {desc: 'Shift+Right Ctrl', name: 'SRC', shift: ss.KVKS_SHIFT | ss.KVKS_RCTRL, vkeys: [vk.K_SHIFT,vk.K_RCONTROL]}, + {desc: 'Shift+Left Alt', name: 'SLA', shift: ss.KVKS_SHIFT | ss.KVKS_LALT, vkeys: [vk.K_SHIFT,vk.K_LALT]}, + {desc: 'Shift+Right Alt', name: 'SRA', shift: ss.KVKS_SHIFT | ss.KVKS_RALT, vkeys: [vk.K_SHIFT,vk.K_RALT]}, + {desc: 'Left Ctrl+Left Alt', name: 'LCLA', shift: ss.KVKS_LCTRL | ss.KVKS_LALT, vkeys: [vk.K_LCONTROL,vk.K_LALT]}, + {desc: 'Left Ctrl+Right Alt', name: 'LCRA', shift: ss.KVKS_LCTRL | ss.KVKS_RALT, vkeys: [vk.K_LCONTROL,vk.K_RALT]}, + {desc: 'Right Ctrl+Left Alt', name: 'RCLA', shift: ss.KVKS_RCTRL | ss.KVKS_LALT, vkeys: [vk.K_RCONTROL,vk.K_LALT]}, + {desc: 'Right Ctrl+Right Alt', name: 'RCRA', shift: ss.KVKS_RCTRL | ss.KVKS_RALT, vkeys: [vk.K_RCONTROL,vk.K_RALT]}, + {desc: 'Shift+Left Ctrl+Left Alt', name: 'SLCLA', shift: ss.KVKS_SHIFT | ss.KVKS_LCTRL | ss.KVKS_LALT, vkeys: [vk.K_SHIFT, vk.K_LCONTROL,vk.K_LALT]}, + {desc: 'Shift+Left Ctrl+Right Alt', name: 'SLCRA', shift: ss.KVKS_SHIFT | ss.KVKS_LCTRL | ss.KVKS_RALT, vkeys: [vk.K_SHIFT, vk.K_LCONTROL,vk.K_RALT]}, + {desc: 'Shift+Right Ctrl+Left Alt', name: 'SRCLA', shift: ss.KVKS_SHIFT | ss.KVKS_RCTRL | ss.KVKS_LALT, vkeys: [vk.K_SHIFT, vk.K_RCONTROL,vk.K_LALT]}, + {desc: 'Shift+Right Ctrl+Right Alt', name: 'SRCRA', shift: ss.KVKS_SHIFT | ss.KVKS_RCTRL | ss.KVKS_RALT, vkeys: [vk.K_SHIFT, vk.K_RCONTROL,vk.K_RALT]}, //16 +]; diff --git a/developer/src/kmc-keyboard/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/kmc-keyboard/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 2014b8c58a4..f8202715670 100644 --- a/developer/src/kmc-keyboard/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/kmc-keyboard/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -3,6 +3,7 @@ import LDMLKeyboardXMLSourceFile from './ldml-keyboard-xml.js'; import CompilerCallbacks from '../compiler/callbacks.js'; import Ajv from 'ajv'; import { CompilerMessages } from '../compiler/messages.js'; +import { boxXmlArray } from '../util/util.js'; export default class LDMLKeyboardXMLSourceFileReader { private readonly callbacks: CompilerCallbacks; @@ -17,40 +18,29 @@ export default class LDMLKeyboardXMLSourceFileReader { * @param source any */ private boxArrays(source: any) { - let box = (o: any, x: string) => { - if(typeof o == 'object' && !Array.isArray(o[x])) { - if(o[x] === null || o[x] === undefined) { - o[x] = []; - } - else { - o[x] = [o[x]]; - } - } - } - - box(source?.keyboard, 'layerMaps'); - box(source?.keyboard?.names, 'name'); - box(source?.keyboard?.vkeyMaps, 'vkeyMap'); - box(source?.keyboard?.keys, 'key'); - box(source?.keyboard?.locales, 'locale'); - box(source?.keyboard, 'transforms'); + boxXmlArray(source?.keyboard, 'layerMaps'); + boxXmlArray(source?.keyboard?.names, 'name'); + boxXmlArray(source?.keyboard?.vkeyMaps, 'vkeyMap'); + boxXmlArray(source?.keyboard?.keys, 'key'); + boxXmlArray(source?.keyboard?.locales, 'locale'); + boxXmlArray(source?.keyboard, 'transforms'); if(source?.keyboard?.layerMaps) { for(let layerMaps of source?.keyboard?.layerMaps) { - box(layerMaps, 'layerMap'); + boxXmlArray(layerMaps, 'layerMap'); if(layerMaps?.layerMap) { for(let layerMap of layerMaps?.layerMap) { - box(layerMap, 'row'); + boxXmlArray(layerMap, 'row'); } } } } if(source?.keyboard?.transforms) { for(let transform of source.keyboard.transforms) { - box(transform, 'transform'); + boxXmlArray(transform, 'transform'); } } - box(source?.keyboard?.reorders, 'reorder'); - box(source?.keyboard?.backspaces, 'backspace'); + boxXmlArray(source?.keyboard?.reorders, 'reorder'); + boxXmlArray(source?.keyboard?.backspaces, 'backspace'); return source; } @@ -75,6 +65,7 @@ export default class LDMLKeyboardXMLSourceFileReader { let parser = 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 diff --git a/developer/src/kmc-keyboard/src/util/util.ts b/developer/src/kmc-keyboard/src/util/util.ts index d663fc490bb..520de398b2e 100644 --- a/developer/src/kmc-keyboard/src/util/util.ts +++ b/developer/src/kmc-keyboard/src/util/util.ts @@ -5,3 +5,21 @@ export function isValidEnumValue(enu: T, value: string) { return (Object.values(enu) as string[]).includes(value); } + +/** + * xml2js will not place single-entry objects into arrays. Easiest way to fix + * this is to box them ourselves as needed. Ensures that o.x is an array. + * + * @param o Object with property to box + * @param x Name of element to box + */ +export function boxXmlArray(o: any, x: string): void { + if(typeof o == 'object' && !Array.isArray(o[x])) { + if(o[x] === null || o[x] === undefined) { + o[x] = []; + } + else { + o[x] = [o[x]]; + } + } +} diff --git a/developer/src/kmc-keyboard/test/fixtures/kvk/khmer_angkor.kvk b/developer/src/kmc-keyboard/test/fixtures/kvk/khmer_angkor.kvk new file mode 100644 index 00000000000..c64b3a5a544 Binary files /dev/null and b/developer/src/kmc-keyboard/test/fixtures/kvk/khmer_angkor.kvk differ diff --git a/developer/src/kmc-keyboard/test/fixtures/kvk/khmer_angkor.kvks b/developer/src/kmc-keyboard/test/fixtures/kvk/khmer_angkor.kvks new file mode 100644 index 00000000000..e9c38a464dd --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/kvk/khmer_angkor.kvks @@ -0,0 +1,206 @@ + + +
+ 10.0 + khmer_angkor + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + ] + [ + / + . + + + + & + + * + @ + \ + } + { + - + ÷ + : + , + + ; + < + # + > + × + $ + +   + + + + + + + + + + + + + ᧿ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + « + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + " + + % + + ( + ) + + = + + + ? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + + » + + + +
diff --git a/developer/src/kmc-keyboard/test/helpers/index.ts b/developer/src/kmc-keyboard/test/helpers/index.ts index 0b4e0dc740d..f7636fae185 100644 --- a/developer/src/kmc-keyboard/test/helpers/index.ts +++ b/developer/src/kmc-keyboard/test/helpers/index.ts @@ -24,20 +24,36 @@ export function makePathToFixture(...components: string[]): string { return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url)); } - -export class CompilerCallbacks { +class CompilerCallbacks { messages: CompilerEvent[] = []; loadFile(baseFilename: string, filename:string): Buffer { // TODO: translate filename based on the baseFilename return fs.readFileSync(filename); } reportMessage(event: CompilerEvent): void { + // console.log(event.message); this.messages.push(event); } loadLdmlKeyboardSchema(): Buffer { return fs.readFileSync(new URL(path.join('..', '..', 'src', 'ldml-keyboard.schema.json'), import.meta.url)); } -} + loadKvksJsonSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'kvks.schema.json'), import.meta.url)); + } +}; + +export const compilerTestCallbacks = new CompilerCallbacks(); + +beforeEach(function() { + compilerTestCallbacks.messages = []; +}); + +afterEach(function() { + if (this.currentTest.state !== 'passed') { + compilerTestCallbacks.messages.forEach(message => console.log(message.message)); + } +}); + export function loadSectionFixture(compilerClass: typeof SectionCompiler, filename: string, callbacks: CompilerCallbacks): Section { callbacks.messages = []; @@ -57,18 +73,18 @@ export function loadSectionFixture(compilerClass: typeof SectionCompiler, filena return compiler.compile(globalSections); } -export function compileKeyboard(inputFilename: string, callbacks: CompilerCallbacks, options: CompilerOptions): KMXPlusFile { - const k = new Compiler(callbacks, options); +export function compileKeyboard(inputFilename: string, options: CompilerOptions): KMXPlusFile { + const k = new Compiler(compilerTestCallbacks, options); const source = k.load(inputFilename); - checkMessages(callbacks); + checkMessages(); assert.isNotNull(source, 'k.load should not have returned null'); const valid = k.validate(source); - checkMessages(callbacks); + checkMessages(); assert.isTrue(valid, 'k.validate should not have failed'); const kmx = k.compile(source); - checkMessages(callbacks); + checkMessages(); assert.isNotNull(kmx, 'k.compile should not have returned null'); // In order for the KMX file to be loaded by non-KMXPlus components, it is helpful @@ -78,9 +94,9 @@ export function compileKeyboard(inputFilename: string, callbacks: CompilerCallba return kmx; } -export function checkMessages(callbacks: CompilerCallbacks) { - if(callbacks.messages.length > 0) { - console.log(callbacks.messages); +export function checkMessages() { + if(compilerTestCallbacks.messages.length > 0) { + console.log(compilerTestCallbacks.messages); } - assert.isEmpty(callbacks.messages); + assert.isEmpty(compilerTestCallbacks.messages); } diff --git a/developer/src/kmc-keyboard/test/kvk/test-kvk-file.ts b/developer/src/kmc-keyboard/test/kvk/test-kvk-file.ts new file mode 100644 index 00000000000..7578169eea7 --- /dev/null +++ b/developer/src/kmc-keyboard/test/kvk/test-kvk-file.ts @@ -0,0 +1,15 @@ +import * as fs from 'fs'; +import 'mocha'; +import { makePathToFixture } from '../helpers/index.js'; +import KvkFileReader from "../../src/kvk/kvk-file-reader.js"; +import { verify_khmer_angkor } from './test-kvk-utils.js'; + +describe('kvk-file-reader', function () { + it('kvk-file-reader should read a valid file', function() { + const path = makePathToFixture('kvk', 'khmer_angkor.kvk'); + const input = fs.readFileSync(path); + const reader = new KvkFileReader(); + const vk = reader.read(input); + verify_khmer_angkor(vk); + }); +}); diff --git a/developer/src/kmc-keyboard/test/kvk/test-kvk-round-trip.ts b/developer/src/kmc-keyboard/test/kvk/test-kvk-round-trip.ts new file mode 100644 index 00000000000..a03b34848c6 --- /dev/null +++ b/developer/src/kmc-keyboard/test/kvk/test-kvk-round-trip.ts @@ -0,0 +1,33 @@ +import * as fs from 'fs'; +import 'mocha'; +import {assert} from 'chai'; +import { compilerTestCallbacks, makePathToFixture } from '../helpers/index.js'; +import KvksFileReader from "../../src/kvk/kvks-file-reader.js"; +import KvkFileReader from "../../src/kvk/kvk-file-reader.js"; +import KvkFileWriter from "../../src/kvk/kvk-file-writer.js"; + +describe('kvk-file-reader', function () { + it('kvk-file-reader should round-trip with kvk-file-writer', function() { + const path = makePathToFixture('kvk', 'khmer_angkor.kvk'); + const input = fs.readFileSync(path); + const reader = new KvkFileReader(); + const vk = reader.read(input); + const writer = new KvkFileWriter(); + const output = writer.write(vk); + assert.deepEqual(input, output); + }); +}); + +describe('kvks-file-reader', function () { + it('kvks-file-reader should compile with kvk-file-writer', function() { + const inputPath = makePathToFixture('kvk', 'khmer_angkor.kvks'); + const compiledPath = makePathToFixture('kvk', 'khmer_angkor.kvk'); + const input = fs.readFileSync(inputPath); + const compiled = fs.readFileSync(compiledPath); + const reader = new KvksFileReader(compilerTestCallbacks); + const vk = reader.read(input); + const writer = new KvkFileWriter(); + const output = writer.write(vk); + assert.deepEqual(output, compiled); + }); +}); diff --git a/developer/src/kmc-keyboard/test/kvk/test-kvk-utils.ts b/developer/src/kmc-keyboard/test/kvk/test-kvk-utils.ts new file mode 100644 index 00000000000..99e5c05fc77 --- /dev/null +++ b/developer/src/kmc-keyboard/test/kvk/test-kvk-utils.ts @@ -0,0 +1,24 @@ +import 'mocha'; +import {assert} from 'chai'; +import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKeyFlags, VisualKeyboardShiftState } from "./visual-keyboard.js"; +import { USVirtualKeyCodes } from '../../src/ldml-keyboard/virtual-key-constants.js'; + +export function verify_khmer_angkor(vk: VisualKeyboard) { + assert.equal(vk.header.flags, VisualKeyboardHeaderFlags.kvkhAltGr); + assert.equal(vk.header.associatedKeyboard, 'khmer_angkor'); + assert.equal(vk.header.ansiFont.name, 'Arial'); + assert.equal(vk.header.ansiFont.size, -12); + assert.equal(vk.header.ansiFont.color, 0xFF000008); + assert.equal(vk.header.unicodeFont.name, 'Khmer Busra Kbd'); + assert.equal(vk.header.unicodeFont.size, 16); + assert.equal(vk.header.unicodeFont.color, 0xFF000008); + assert.equal(vk.keys.length, 186); + assert.equal(vk.keys[0].flags, VisualKeyboardKeyFlags.kvkkUnicode); + assert.equal(vk.keys[0].vkey, USVirtualKeyCodes.K_B); + assert.equal(vk.keys[0].shift, VisualKeyboardShiftState.KVKS_RALT); + assert.equal(vk.keys[0].text, 'ឞ'); + assert.equal(vk.keys[185].flags, VisualKeyboardKeyFlags.kvkkUnicode); + assert.equal(vk.keys[185].vkey, USVirtualKeyCodes.K_COMMA); + assert.equal(vk.keys[185].shift, VisualKeyboardShiftState.KVKS_SHIFT); + assert.equal(vk.keys[185].text, ''); +} diff --git a/developer/src/kmc-keyboard/test/kvk/test-kvks-file.ts b/developer/src/kmc-keyboard/test/kvk/test-kvks-file.ts new file mode 100644 index 00000000000..15f79f6e15d --- /dev/null +++ b/developer/src/kmc-keyboard/test/kvk/test-kvks-file.ts @@ -0,0 +1,15 @@ +import * as fs from 'fs'; +import 'mocha'; +import { compilerTestCallbacks, makePathToFixture } from '../helpers/index.js'; +import KvksFileReader from "../../src/kvk/kvks-file-reader.js"; +import { verify_khmer_angkor } from './test-kvk-utils.js'; + +describe('kvks-file-reader', function() { + it('kvks-file-reader should read a valid file', function() { + const path = makePathToFixture('kvk', 'khmer_angkor.kvks'); + const input = fs.readFileSync(path); + const reader = new KvksFileReader(compilerTestCallbacks); + const vk = reader.read(input); + verify_khmer_angkor(vk); + }); +}); diff --git a/developer/src/kmc-keyboard/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/developer/src/kmc-keyboard/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts index ecd04762baf..2a287273458 100644 --- a/developer/src/kmc-keyboard/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts +++ b/developer/src/kmc-keyboard/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -1,6 +1,6 @@ import 'mocha'; import {assert} from 'chai'; -import {CompilerCallbacks, makePathToFixture} from '../helpers/index.js'; +import {compilerTestCallbacks, makePathToFixture} from '../helpers/index.js'; import LDMLKeyboardXMLSourceFileReader from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; import { CompilerMessages } from '../../src/compiler/messages.js'; @@ -9,22 +9,20 @@ describe('ldml keyboard xml reader tests', function() { it("should fail to load files that don't conform to DTD", function() { const inputFilename = makePathToFixture('invalid-structure-per-dtd.xml'); - const callbacks = new CompilerCallbacks(); - let reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + let reader = new LDMLKeyboardXMLSourceFileReader(compilerTestCallbacks); const source = reader.loadFile(inputFilename); assert.isNull(source); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_InvalidFile({errorText: "data/keyboard must have required property 'names'"})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidFile({errorText: "data/keyboard must have required property 'names'"})); }); it("should fail to load files with an invalid conformsTo", function() { const inputFilename = makePathToFixture('invalid-conforms-to.xml'); - const callbacks = new CompilerCallbacks(); - let reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + let reader = new LDMLKeyboardXMLSourceFileReader(compilerTestCallbacks); const source = reader.loadFile(inputFilename); assert.isNull(source); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_InvalidFile({errorText: "data/keyboard/conformsTo must be equal to one of the allowed values"})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidFile({errorText: "data/keyboard/conformsTo must be equal to one of the allowed values"})); }); }); \ No newline at end of file diff --git a/developer/src/kmc-keyboard/test/test-bksp.ts b/developer/src/kmc-keyboard/test/test-bksp.ts index d6b234f3a2f..05a16b7a0e1 100644 --- a/developer/src/kmc-keyboard/test/test-bksp.ts +++ b/developer/src/kmc-keyboard/test/test-bksp.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { BkspCompiler } from '../src/compiler/bksp.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { Bksp, BkspItemFlags } from '../src/kmx/kmx-plus.js'; //import { CompilerMessages } from './keyman/compiler/messages'; @@ -9,9 +9,8 @@ describe('bksp', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal bksp data', function() { - const callbacks = new CompilerCallbacks(); - let bksp = loadSectionFixture(BkspCompiler, 'sections/bksp/minimal.xml', callbacks) as Bksp; - assert.lengthOf(callbacks.messages, 0); + let bksp = loadSectionFixture(BkspCompiler, 'sections/bksp/minimal.xml', compilerTestCallbacks) as Bksp; + assert.lengthOf(compilerTestCallbacks.messages, 0); assert.lengthOf(bksp.items, 1); assert.lengthOf(bksp.items[0].from, 2); diff --git a/developer/src/kmc-keyboard/test/test-compiler-e2e.ts b/developer/src/kmc-keyboard/test/test-compiler-e2e.ts index 91508ee986d..c3babba4c61 100644 --- a/developer/src/kmc-keyboard/test/test-compiler-e2e.ts +++ b/developer/src/kmc-keyboard/test/test-compiler-e2e.ts @@ -2,7 +2,7 @@ import 'mocha'; import {assert} from 'chai'; import x_hextobin from '@keymanapp/hextobin'; import KMXBuilder from '../src/kmx/kmx-builder.js'; -import {checkMessages, compileKeyboard, CompilerCallbacks, makePathToFixture} from './helpers/index.js'; +import {checkMessages, compileKeyboard, makePathToFixture} from './helpers/index.js'; // const __filename = fileURLToPath(import.meta.url); const hextobin = (x_hextobin as any).default; @@ -18,14 +18,13 @@ describe('compiler-tests', function() { const binaryFilename = makePathToFixture('basic.txt'); // Compile the keyboard - const callbacks = new CompilerCallbacks(); - const kmx = compileKeyboard(inputFilename, callbacks, {debug: true, addCompilerVersion: false}); + const kmx = compileKeyboard(inputFilename, {debug: true, addCompilerVersion: false}); assert.isNotNull(kmx); // Use the builder to generate the binary output file const builder = new KMXBuilder(kmx, true); const code = builder.compile(); - checkMessages(callbacks); + checkMessages(); assert.isNotNull(code); // Compare output diff --git a/developer/src/kmc-keyboard/test/test-finl.ts b/developer/src/kmc-keyboard/test/test-finl.ts index ad4749e5746..fb63a28100c 100644 --- a/developer/src/kmc-keyboard/test/test-finl.ts +++ b/developer/src/kmc-keyboard/test/test-finl.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { FinlCompiler } from '../src/compiler/tran.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { Finl, FinlItemFlags } from '../src/kmx/kmx-plus.js'; //import { CompilerMessages } from './keyman/compiler/messages'; @@ -9,9 +9,8 @@ describe('finl', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal finl data', function() { - const callbacks = new CompilerCallbacks(); - let finl = loadSectionFixture(FinlCompiler, 'sections/finl/minimal.xml', callbacks) as Finl; - assert.lengthOf(callbacks.messages, 0); + let finl = loadSectionFixture(FinlCompiler, 'sections/finl/minimal.xml', compilerTestCallbacks) as Finl; + assert.lengthOf(compilerTestCallbacks.messages, 0); assert.lengthOf(finl.items, 1); assert.lengthOf(finl.items[0].from, 2); diff --git a/developer/src/kmc-keyboard/test/test-keys.ts b/developer/src/kmc-keyboard/test/test-keys.ts index 90d8c06a414..ae40eb051b9 100644 --- a/developer/src/kmc-keyboard/test/test-keys.ts +++ b/developer/src/kmc-keyboard/test/test-keys.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { KeysCompiler } from '../src/compiler/keys.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { Keys } from '../src/kmx/kmx-plus.js'; import { CompilerMessages } from '../src/compiler/messages.js'; @@ -9,54 +9,48 @@ describe('keys', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal keys data', function() { - const callbacks = new CompilerCallbacks(); - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/minimal.xml', callbacks) as Keys; + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/minimal.xml', compilerTestCallbacks) as Keys; assert.isNotNull(keys); - assert.equal(callbacks.messages.length, 0); + assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(keys.keys.length, 1); }); it('should compile a hardware layer', function() { - const callbacks = new CompilerCallbacks(); - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/hardware.xml', callbacks) as Keys; + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/hardware.xml', compilerTestCallbacks) as Keys; assert.isNotNull(keys); - assert.equal(callbacks.messages.length, 0); + assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(keys.keys.length, 2); }); it('should reject structurally invalid layers', function() { - const callbacks = new CompilerCallbacks(); - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-missing-layer.xml', callbacks) as Keys; + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-missing-layer.xml', compilerTestCallbacks) as Keys; assert.isNull(keys); - assert.equal(callbacks.messages.length, 1); + assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_MustBeAtLeastOneLayerElement()); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_MustBeAtLeastOneLayerElement()); }); it('should reject layouts with too many hardware rows', function() { - const callbacks = new CompilerCallbacks(); - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-rows.xml', callbacks) as Keys; + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-rows.xml', compilerTestCallbacks) as Keys; assert.isNull(keys); - assert.equal(callbacks.messages.length, 1); + assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_HardwareLayerHasTooManyRows()); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_HardwareLayerHasTooManyRows()); }); it('should reject layouts with too many hardware keys', function() { - const callbacks = new CompilerCallbacks(); - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-keys.xml', callbacks) as Keys; + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-keys.xml', compilerTestCallbacks) as Keys; assert.isNull(keys); - assert.equal(callbacks.messages.length, 1); + assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_RowOnHardwareLayerHasTooManyKeys({row: 1})); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_RowOnHardwareLayerHasTooManyKeys({row: 1})); }); it('should reject layouts with undefined keys', function() { - const callbacks = new CompilerCallbacks(); - let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-undefined-key.xml', callbacks) as Keys; + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-undefined-key.xml', compilerTestCallbacks) as Keys; assert.isNull(keys); - assert.equal(callbacks.messages.length, 1); + assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_KeyNotFoundInKeyBag({col: 1, form: 'hardware', keyId: 'foo', layer: 'base', row: 1})); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_KeyNotFoundInKeyBag({col: 1, form: 'hardware', keyId: 'foo', layer: 'base', row: 1})); }); }); diff --git a/developer/src/kmc-keyboard/test/test-loca.ts b/developer/src/kmc-keyboard/test/test-loca.ts index 5baa3347189..d1f259427bd 100644 --- a/developer/src/kmc-keyboard/test/test-loca.ts +++ b/developer/src/kmc-keyboard/test/test-loca.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { LocaCompiler } from '../src/compiler/loca.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { Loca } from '../src/kmx/kmx-plus.js'; import { CompilerMessages } from '../src/compiler/messages.js'; @@ -9,27 +9,25 @@ describe('loca', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal loca data', function() { - const callbacks = new CompilerCallbacks(); - let loca = loadSectionFixture(LocaCompiler, 'sections/loca/minimal.xml', callbacks) as Loca; + let loca = loadSectionFixture(LocaCompiler, 'sections/loca/minimal.xml', compilerTestCallbacks) as Loca; assert.isObject(loca); - assert.equal(callbacks.messages.length, 0); + assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(loca.locales.length, 1); assert.equal(loca.locales[0].value.toLowerCase(), 'mt'); }); it('should compile multiple locales', function() { - const callbacks = new CompilerCallbacks(); - let loca = loadSectionFixture(LocaCompiler, 'sections/loca/multiple.xml', callbacks) as Loca; + let loca = loadSectionFixture(LocaCompiler, 'sections/loca/multiple.xml', compilerTestCallbacks) as Loca; assert.isObject(loca); // Note: multiple.xml includes fr-FR twice, with differing case, which should be canonicalized - assert.equal(callbacks.messages.length, 4); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-FR', locale: 'fr'})); - assert.deepEqual(callbacks.messages[1], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'km-khmr-kh', locale: 'km'})); - assert.deepEqual(callbacks.messages[2], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-fr', locale: 'fr'})); - assert.deepEqual(callbacks.messages[3], CompilerMessages.Hint_OneOrMoreRepeatedLocales()); + assert.equal(compilerTestCallbacks.messages.length, 4); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-FR', locale: 'fr'})); + assert.deepEqual(compilerTestCallbacks.messages[1], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'km-khmr-kh', locale: 'km'})); + assert.deepEqual(compilerTestCallbacks.messages[2], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-fr', locale: 'fr'})); + assert.deepEqual(compilerTestCallbacks.messages[3], CompilerMessages.Hint_OneOrMoreRepeatedLocales()); // Original is 6 locales, now five minimized in the results assert.equal(loca.locales.length, 5); @@ -41,12 +39,11 @@ describe('loca', function () { }); it('should reject structurally invalid locales', function() { - const callbacks = new CompilerCallbacks(); - let loca = loadSectionFixture(LocaCompiler, 'sections/loca/invalid-locale.xml', callbacks) as Loca; + let loca = loadSectionFixture(LocaCompiler, 'sections/loca/invalid-locale.xml', compilerTestCallbacks) as Loca; assert.isNull(loca); - assert.equal(callbacks.messages.length, 1); + assert.equal(compilerTestCallbacks.messages.length, 1); // We'll only test one invalid BCP 47 tag to verify that we are properly calling BCP 47 validation routines. // Furthermore, we are testing BCP 47 structure, not the validity of each subtag -- we must assume the author knows of new subtags! - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_InvalidLocale({tag:'en-*'})); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidLocale({tag:'en-*'})); }) }); diff --git a/developer/src/kmc-keyboard/test/test-meta.ts b/developer/src/kmc-keyboard/test/test-meta.ts index ffbc0c1dab7..d2417a0458f 100644 --- a/developer/src/kmc-keyboard/test/test-meta.ts +++ b/developer/src/kmc-keyboard/test/test-meta.ts @@ -1,7 +1,7 @@ import 'mocha'; import {assert} from 'chai'; import { MetaCompiler } from '../src/compiler/meta.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { KeyboardSettings, Meta } from '../src/kmx/kmx-plus.js'; import { CompilerMessages } from '../src/compiler/messages.js'; @@ -9,9 +9,8 @@ describe('meta', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal metadata', function() { - const callbacks = new CompilerCallbacks(); - let meta = loadSectionFixture(MetaCompiler, 'sections/meta/minimal.xml', callbacks) as Meta; - assert.equal(callbacks.messages.length, 0); + let meta = loadSectionFixture(MetaCompiler, 'sections/meta/minimal.xml', compilerTestCallbacks) as Meta; + assert.equal(compilerTestCallbacks.messages.length, 0); assert.isEmpty(meta.author.value); // TODO-LDML: default author string "unknown"? assert.equal(meta.conform.value, 'techpreview'); @@ -22,9 +21,8 @@ describe('meta', function () { }); it('should compile maximal metadata', function() { - const callbacks = new CompilerCallbacks(); - let meta = loadSectionFixture(MetaCompiler, 'sections/meta/maximal.xml', callbacks) as Meta; - assert.equal(callbacks.messages.length, 0); + let meta = loadSectionFixture(MetaCompiler, 'sections/meta/maximal.xml', compilerTestCallbacks) as Meta; + assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(meta.author.value, 'The Keyman Team'); assert.equal(meta.conform.value, 'techpreview'); @@ -36,24 +34,22 @@ describe('meta', function () { }); it('should reject invalid normalization', function() { - const callbacks = new CompilerCallbacks(); - let meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-normalization.xml', callbacks) as Meta; + let meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-normalization.xml', compilerTestCallbacks) as Meta; assert.isNull(meta); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_InvalidNormalization({form:'NFQ'})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidNormalization({form:'NFQ'})); }); it('should reject invalid version', function() { - const callbacks = new CompilerCallbacks(); - let meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-1.0.xml', callbacks) as Meta; + let meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-1.0.xml', compilerTestCallbacks) as Meta; assert.isNull(meta); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_InvalidVersion({version:'1.0'})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidVersion({version:'1.0'})); - meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-v1.0.3.xml', callbacks) as Meta; + meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-v1.0.3.xml', compilerTestCallbacks) as Meta; assert.isNull(meta); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_InvalidVersion({version:'v1.0.3'})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidVersion({version:'v1.0.3'})); }); }); diff --git a/developer/src/kmc-keyboard/test/test-metadata-compiler.ts b/developer/src/kmc-keyboard/test/test-metadata-compiler.ts index 5cfb9678e38..55a3df83344 100644 --- a/developer/src/kmc-keyboard/test/test-metadata-compiler.ts +++ b/developer/src/kmc-keyboard/test/test-metadata-compiler.ts @@ -1,6 +1,6 @@ import 'mocha'; import {assert} from 'chai'; -import { checkMessages, compileKeyboard, CompilerCallbacks, makePathToFixture } from './helpers/index.js'; +import { checkMessages, compileKeyboard, makePathToFixture } from './helpers/index.js'; import KMXFile from '../src/kmx/kmx.js'; import KEYMAN_VERSION from '@keymanapp/keyman-version/keyman-version.mjs'; @@ -8,13 +8,11 @@ describe('kmx metadata compiler', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile metadata with debug and compiler version', function() { - debugger - const callbacks = new CompilerCallbacks(); const inputFilename = makePathToFixture('basic.xml'); // Compile the keyboard - const kmx = compileKeyboard(inputFilename, callbacks, {debug:true, addCompilerVersion:true}); - checkMessages(callbacks); + const kmx = compileKeyboard(inputFilename, {debug:true, addCompilerVersion:true}); + checkMessages(); assert.isNotNull(kmx); // Order of stores is not significant in kmx spec, but kmxplus compiler will @@ -45,12 +43,11 @@ describe('kmx metadata compiler', function () { }); it('should compile metadata with no compiler version', function() { - const callbacks = new CompilerCallbacks(); const inputFilename = makePathToFixture('basic.xml'); // Compile the keyboard - const kmx = compileKeyboard(inputFilename, callbacks, {debug:true, addCompilerVersion:false}); - checkMessages(callbacks); + const kmx = compileKeyboard(inputFilename, {debug:true, addCompilerVersion:false}); + checkMessages(); assert.isNotNull(kmx); // TSS_NAME = 7 diff --git a/developer/src/kmc-keyboard/test/test-name.ts b/developer/src/kmc-keyboard/test/test-name.ts index e3c80b0022a..f1c1a780a45 100644 --- a/developer/src/kmc-keyboard/test/test-name.ts +++ b/developer/src/kmc-keyboard/test/test-name.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { NameCompiler } from '../src/compiler/name.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { Name } from '../src/kmx/kmx-plus.js'; //import { CompilerMessages } from './keyman/compiler/messages'; @@ -9,18 +9,16 @@ describe('name', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal name data', function() { - const callbacks = new CompilerCallbacks(); - let name = loadSectionFixture(NameCompiler, 'sections/name/minimal.xml', callbacks) as Name; - assert.equal(callbacks.messages.length, 0); + let name = loadSectionFixture(NameCompiler, 'sections/name/minimal.xml', compilerTestCallbacks) as Name; + assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(name.names.length, 1); assert.equal(name.names[0].value, 'My First Keyboard'); }); it('should compile multiple names', function() { - const callbacks = new CompilerCallbacks(); - let name = loadSectionFixture(NameCompiler, 'sections/name/multiple.xml', callbacks) as Name; - assert.equal(callbacks.messages.length, 0); + let name = loadSectionFixture(NameCompiler, 'sections/name/multiple.xml', compilerTestCallbacks) as Name; + assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(name.names.length, 5); assert.equal(name.names[0].value, 'My Second Keyboard'); diff --git a/developer/src/kmc-keyboard/test/test-ordr.ts b/developer/src/kmc-keyboard/test/test-ordr.ts index 9e54d06e4bd..ff7bd34d402 100644 --- a/developer/src/kmc-keyboard/test/test-ordr.ts +++ b/developer/src/kmc-keyboard/test/test-ordr.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { OrdrCompiler } from '../src/compiler/ordr.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { Ordr } from '../src/kmx/kmx-plus.js'; //import { CompilerMessages } from './keyman/compiler/messages'; @@ -9,9 +9,8 @@ describe('ordr', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal ordr data', function() { - const callbacks = new CompilerCallbacks(); - let ordr = loadSectionFixture(OrdrCompiler, 'sections/ordr/minimal.xml', callbacks) as Ordr; - assert.lengthOf(callbacks.messages, 0); + let ordr = loadSectionFixture(OrdrCompiler, 'sections/ordr/minimal.xml', compilerTestCallbacks) as Ordr; + assert.lengthOf(compilerTestCallbacks.messages, 0); assert.lengthOf(ordr.items, 1); assert.lengthOf(ordr.items[0].elements, 4); diff --git a/developer/src/kmc-keyboard/test/test-tran.ts b/developer/src/kmc-keyboard/test/test-tran.ts index 5004d5e876c..7e617711058 100644 --- a/developer/src/kmc-keyboard/test/test-tran.ts +++ b/developer/src/kmc-keyboard/test/test-tran.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { TranCompiler } from '../src/compiler/tran.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { Tran, TranItemFlags } from '../src/kmx/kmx-plus.js'; //import { CompilerMessages } from './keyman/compiler/messages'; @@ -9,9 +9,8 @@ describe('tran', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal tran data', function() { - const callbacks = new CompilerCallbacks(); - let tran = loadSectionFixture(TranCompiler, 'sections/tran/minimal.xml', callbacks) as Tran; - assert.lengthOf(callbacks.messages, 0); + let tran = loadSectionFixture(TranCompiler, 'sections/tran/minimal.xml', compilerTestCallbacks) as Tran; + assert.lengthOf(compilerTestCallbacks.messages, 0); assert.lengthOf(tran.items, 1); assert.lengthOf(tran.items[0].from, 2); diff --git a/developer/src/kmc-keyboard/test/test-vkey.ts b/developer/src/kmc-keyboard/test/test-vkey.ts index 9c8dd49ffa2..0812b49648c 100644 --- a/developer/src/kmc-keyboard/test/test-vkey.ts +++ b/developer/src/kmc-keyboard/test/test-vkey.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { VkeyCompiler } from '../src/compiler/vkey.js'; -import { CompilerCallbacks, loadSectionFixture } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { Vkey } from '../src/kmx/kmx-plus.js'; import { CompilerMessages } from '../src/compiler/messages.js'; import { USVirtualKeyCodes } from '../src/ldml-keyboard/virtual-key-constants.js'; @@ -10,9 +10,8 @@ describe('vkey compiler', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while it('should compile minimal vkey data', function() { - const callbacks = new CompilerCallbacks(); - let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/minimal.xml', callbacks) as Vkey; - assert.equal(callbacks.messages.length, 0); + let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/minimal.xml', compilerTestCallbacks) as Vkey; + assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(vkey.vkeys.length, 4); // Note, final order is sorted by `vkey` member @@ -23,44 +22,39 @@ describe('vkey compiler', function () { }); it('should hint on redundant data', function() { - const callbacks = new CompilerCallbacks(); - let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/redundant.xml', callbacks) as Vkey; + let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/redundant.xml', compilerTestCallbacks) as Vkey; assert.isNotNull(vkey); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Hint_VkeyMapIsRedundant({vkey: 'A'})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Hint_VkeyMapIsRedundant({vkey: 'A'})); }); it('should report an info message if same target found', function() { - const callbacks = new CompilerCallbacks(); - let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/same-target.xml', callbacks) as Vkey; + let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/same-target.xml', compilerTestCallbacks) as Vkey; assert.isNotNull(vkey); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Info_MultipleVkeyMapsHaveSameTarget({vkey: 'Q'})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Info_MultipleVkeyMapsHaveSameTarget({vkey: 'Q'})); }); it('should error on invalid "from" vkey', function() { - const callbacks = new CompilerCallbacks(); - let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-from-vkey.xml', callbacks) as Vkey; + let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-from-vkey.xml', compilerTestCallbacks) as Vkey; assert.isNull(vkey); - assert.equal(callbacks.messages.length, 2); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_VkeyIsNotValid({vkey: 'q'})); - assert.deepEqual(callbacks.messages[1], CompilerMessages.Error_VkeyIsNotValid({vkey: 'HYFEN'})); + assert.equal(compilerTestCallbacks.messages.length, 2); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_VkeyIsNotValid({vkey: 'q'})); + assert.deepEqual(compilerTestCallbacks.messages[1], CompilerMessages.Error_VkeyIsNotValid({vkey: 'HYFEN'})); }); it('should error on invalid "to" vkey', function() { - const callbacks = new CompilerCallbacks(); - let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-to-vkey.xml', callbacks) as Vkey; + let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-to-vkey.xml', compilerTestCallbacks) as Vkey; assert.isNull(vkey); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_VkeyIsNotValid({vkey: 'A-ACUTE'})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_VkeyIsNotValid({vkey: 'A-ACUTE'})); }); it('should error on repeated vkeys', function() { - const callbacks = new CompilerCallbacks(); - let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-repeated-vkey.xml', callbacks) as Vkey; + let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-repeated-vkey.xml', compilerTestCallbacks) as Vkey; assert.isNull(vkey); - assert.equal(callbacks.messages.length, 1); - assert.deepEqual(callbacks.messages[0], CompilerMessages.Error_VkeyMapIsRepeated({vkey: 'A'})); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_VkeyMapIsRepeated({vkey: 'A'})); }); }); diff --git a/developer/src/kmc/src/kmc.ts b/developer/src/kmc/src/kmc.ts index 9c2e8a8cf37..9606e15b829 100644 --- a/developer/src/kmc/src/kmc.ts +++ b/developer/src/kmc/src/kmc.ts @@ -49,6 +49,10 @@ class CompilerCallbacks { let schemaPath = new URL('ldml-keyboard.schema.json', import.meta.url); return fs.readFileSync(schemaPath); } + loadKvksJsonSchema(): Buffer { + let schemaPath = new URL('kvks.schema.json', import.meta.url); + return fs.readFileSync(schemaPath); + } } function compileKeyboard(inputFilename: string, options: kmc.CompilerOptions): Uint8Array { diff --git a/package-lock.json b/package-lock.json index d4a546c677e..8de1536f4d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -338,9 +338,9 @@ "dependencies": { "@keymanapp/keyman-version": "*", "ajv": "^8.11.0", - "restructure": "^3.0.0", + "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", "semver": "^7.3.7", - "xml2js": "^0.4.19" + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" }, "devDependencies": { "@types/chai": "^4.1.7", @@ -6631,8 +6631,9 @@ }, "node_modules/restructure": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.0.tgz", - "integrity": "sha512-Xj8/MEIhhfj9X2rmD9iJ4Gga9EFqVlpMj3vfLnV2r/Mh5jRMryNV+6lWh9GdJtDBcBSPIqzRdfBQ3wDtNFv/uw==" + "resolved": "git+ssh://git@github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", + "integrity": "sha512-2vBFOLc0+dkUb3Pnpl87o+B4SIc3MAm3P8PXT8YFkP51URFGsgTgm3gAnwTX2jXSEd/5eT9n7C733loG6OaVgw==", + "license": "MIT" }, "node_modules/retry": { "version": "0.12.0", @@ -7718,8 +7719,9 @@ }, "node_modules/xml2js": { "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "resolved": "git+ssh://git@github.com/keymanapp/dependency-node-xml2js.git#535fe732dc408d697e0f847c944cc45f0baf0829", + "integrity": "sha512-5CS+yWxp0qg8zO7ng/iXrBZm2FXgpiJ+RJ3E0LzRxDXChESqzazRwzl8sXYe8/7je/NfwG4EXcClaRKUkcIvtQ==", + "license": "MIT", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -8687,11 +8689,11 @@ "chai": "^4.3.4", "chalk": "^2.4.2", "mocha": "^8.4.0", - "restructure": "^3.0.0", + "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", "semver": "^7.3.7", "ts-node": "^9.1.1", "typescript": "^4.5.4", - "xml2js": "^0.4.19" + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" }, "dependencies": { "@types/mocha": { @@ -13419,9 +13421,9 @@ } }, "restructure": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.0.tgz", - "integrity": "sha512-Xj8/MEIhhfj9X2rmD9iJ4Gga9EFqVlpMj3vfLnV2r/Mh5jRMryNV+6lWh9GdJtDBcBSPIqzRdfBQ3wDtNFv/uw==" + "version": "git+ssh://git@github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", + "integrity": "sha512-2vBFOLc0+dkUb3Pnpl87o+B4SIc3MAm3P8PXT8YFkP51URFGsgTgm3gAnwTX2jXSEd/5eT9n7C733loG6OaVgw==", + "from": "restructure@git+https://github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50" }, "retry": { "version": "0.12.0", @@ -14241,9 +14243,9 @@ "requires": {} }, "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "version": "git+ssh://git@github.com/keymanapp/dependency-node-xml2js.git#535fe732dc408d697e0f847c944cc45f0baf0829", + "integrity": "sha512-5CS+yWxp0qg8zO7ng/iXrBZm2FXgpiJ+RJ3E0LzRxDXChESqzazRwzl8sXYe8/7je/NfwG4EXcClaRKUkcIvtQ==", + "from": "xml2js@git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0"