From c7740a39a2460c13f59999382bccf3cc3a55ed95 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 26 Sep 2022 09:29:38 +1000 Subject: [PATCH 1/4] feat(developer): kvk support Part of #7238. Supports reading and writing .kvk files, and reading .kvks files, to/from an in-memory VisualKeyboard object. TODO: - Full validation of .kvks files - Writing .kvks files - Some cleanup of API surfaces for consistency --- developer/src/kmc-keyboard/package.json | 7 +- developer/src/kmc-keyboard/src/kmx/kmx.ts | 2 + .../kmc-keyboard/src/kvk/kvk-file-reader.ts | 34 +++ .../kmc-keyboard/src/kvk/kvk-file-writer.ts | 73 +++++++ .../src/kmc-keyboard/src/kvk/kvk-file.ts | 122 +++++++++++ .../kmc-keyboard/src/kvk/kvks-file-reader.ts | 145 ++++++++++++ .../src/kmc-keyboard/src/kvk/kvks-file.ts | 86 ++++++++ .../kmc-keyboard/src/kvk/visual-keyboard.ts | 79 +++++++ .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 34 +-- developer/src/kmc-keyboard/src/util/util.ts | 18 ++ .../test/fixtures/kvk/khmer_angkor.kvk | Bin 0 -> 2893 bytes .../test/fixtures/kvk/khmer_angkor.kvks | 206 ++++++++++++++++++ .../kmc-keyboard/test/kvk/test-kvk-file.ts | 15 ++ .../test/kvk/test-kvk-round-trip.ts | 37 ++++ .../kmc-keyboard/test/kvk/test-kvk-utils.ts | 24 ++ .../kmc-keyboard/test/kvk/test-kvks-file.ts | 16 ++ package-lock.json | 30 +-- 17 files changed, 889 insertions(+), 39 deletions(-) create mode 100644 developer/src/kmc-keyboard/src/kvk/kvk-file-reader.ts create mode 100644 developer/src/kmc-keyboard/src/kvk/kvk-file-writer.ts create mode 100644 developer/src/kmc-keyboard/src/kvk/kvk-file.ts create mode 100644 developer/src/kmc-keyboard/src/kvk/kvks-file-reader.ts create mode 100644 developer/src/kmc-keyboard/src/kvk/kvks-file.ts create mode 100644 developer/src/kmc-keyboard/src/kvk/visual-keyboard.ts create mode 100644 developer/src/kmc-keyboard/test/fixtures/kvk/khmer_angkor.kvk create mode 100644 developer/src/kmc-keyboard/test/fixtures/kvk/khmer_angkor.kvks create mode 100644 developer/src/kmc-keyboard/test/kvk/test-kvk-file.ts create mode 100644 developer/src/kmc-keyboard/test/kvk/test-kvk-round-trip.ts create mode 100644 developer/src/kmc-keyboard/test/kvk/test-kvk-utils.ts create mode 100644 developer/src/kmc-keyboard/test/kvk/test-kvks-file.ts diff --git a/developer/src/kmc-keyboard/package.json b/developer/src/kmc-keyboard/package.json index 32884d49c8c..d5ea89d5bda 100644 --- a/developer/src/kmc-keyboard/package.json +++ b/developer/src/kmc-keyboard/package.json @@ -13,7 +13,8 @@ }, "scripts": { "build": "tsc -b", - "test": "cd test && tsc -b && cd .. && c8 mocha", + "test": "cd test && tsc -b && cd .. && mocha", + "coverage": "cd test && tsc -b && cd .. && c8 mocha", "prepublishOnly": "npm run build" }, "author": "Marc Durdin (https://github.com/mcdurdin)", @@ -24,9 +25,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/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..49c0655d4bf --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/kvk-file-writer.ts @@ -0,0 +1,73 @@ +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 { + private source: VisualKeyboard; + + constructor(source: VisualKeyboard) { + this.source = source; + } + + /** + * Fills a kvk string from a source string. Returns byte size of s as UTF-16 + * zero terminated string for KVK format 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; + } + + private build() { + let binary: BUILDER_KVK_FILE = { + header: { + identifier: BUILDER_KVK_HEADER_IDENTIFIER, + version: BUILDER_KVK_HEADER_VERSION, + associatedKeyboard: {len:0,str:''}, + flags: this.source.header.flags, + ansiFont:{ + color: this.source.header.ansiFont.color, + size: this.source.header.ansiFont.size, + name: {len:0,str:''} + }, + unicodeFont:{ + color: this.source.header.unicodeFont.color, + size: this.source.header.unicodeFont.size, + name: {len:0,str:''} + }, + }, + keyCount: this.source.keys.length, + keys:[] + }; + + this.setString(binary.header.associatedKeyboard, this.source.header.associatedKeyboard); + this.setString(binary.header.ansiFont.name, this.source.header.ansiFont.name); + this.setString(binary.header.unicodeFont.name, this.source.header.unicodeFont.name); + + for(let sourceKey of this.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; + } + + compile(): Uint8Array { + const binary = this.build(); + 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; + } +}; \ 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..09bcf6b262a --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/kvks-file-reader.ts @@ -0,0 +1,145 @@ +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 LDMLKeyboardXMLSourceFileReader { + private readonly callbacks: CompilerCallbacks; + + constructor (callbacks: CompilerCallbacks) { + this.callbacks = callbacks; + } + + /** + * 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 validate(source: KVKSourceFile): KVKSourceFile { + /* LDML-TODO: + const schema = JSON.parse(this.callbacks.loadKvksSchema().toString('utf8')); + const ajv = new Ajv(); + if(!ajv.validate(schema, source)) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: ajv.errorsText()})); + return null; + }*/ + return source; + } + + public loadFile(filename: string) { + const buf = this.callbacks.loadFile(filename, filename); + return this.load(buf); + } + + public load(file: Uint8Array): KVKSourceFile { + let source = (() => { + let a: KVKSourceFile; + let 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) => { a = r as KVKSourceFile }); + return a; + })(); + + return this.validate(this.boxArrays(source)); + } + + public loadVisualKeyboard(file: Uint8Array): VisualKeyboard { + let source = this.load(file); + return this.transform(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; + } + + 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; + } +} \ 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..bf046e4b2f2 --- /dev/null +++ b/developer/src/kmc-keyboard/src/kvk/kvks-file.ts @@ -0,0 +1,86 @@ +// .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; +}; + +/* + + + + + + + + + + + + + + + + + + + + + + + +api.keyman.com visualkeyboard.dtd <-- note, this is NOT .kvks dTD +TODO-LDML: document .kvks XML schema + +*/ + 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..601a1ef9974 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; } 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 0000000000000000000000000000000000000000..c64b3a5a544dc64ab3dcfd44a6875ea5f339b878 GIT binary patch literal 2893 zcmYM#XN**36o>IM_k$r3l2uHEZ(|L1?23xBq6nU9y>+!=@;YU>bN$p zj2q&z7#r8cRr${KsmOYBzK+XxuSs=RtJOXc)uw3qe@8h^iV1OJt{NNVeE;&eB3Cx$ zFDu*fwW#EhBBtz~f9Fq8$yG%>Cx1_VCKo2(lIJB~lJk=<%d?Vi$a9i2<;dg=d1i8! zJSF+MJUsdRI&QIclVg$#<=M#vE^beLW3?^$v(=-LzsjSNzsb{+Ke+d_i&QU6UMxG4hsZg}gXM_ifO6eMB~P}91FVit?kh(n2bSwDDsfD5 zf2)TjD{?{d5;-_|vphC=lRP(hkQ|=uk*6ky$d2TJa&~gWo^=w=d1SJ+Tz65)WEU~5t|*#oPh)H2$eM%ycfI5UH%OkOGd%pk~7*SIh4O79lAskm35M+te3RL3Q5{GNXE!ANeI_U=EhRV+*m2;{Y8>a z*)HjnjgsNENz&cjl6kdSGLcqE^8AlvQ2g6q)Izv(x%Ye{II=GrV056dQc&Q}7%On9_E(!1oNq|>MrphWwo>xmkILDi0^t|8 zszFxC`hJoKe(PdFc7jzRxSLhF=qEpyE;`Z0^vOJ{49#8KOUNF#O2{TjLUyktWFNSf zkUeabkUcL6*{70_z3W~=c9&H`cB>?0( + +
+ 10.0 + khmer_angkor + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + ] + [ + / + . + + + + & + + * + @ + \ + } + { + - + ÷ + : + , + + ; + < + # + > + × + $ + +   + + + + + + + + + + + + + ᧿ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + « + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + " + + % + + ( + ) + + = + + + ? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + + » + + + +
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..a520478b9d1 --- /dev/null +++ b/developer/src/kmc-keyboard/test/kvk/test-kvk-round-trip.ts @@ -0,0 +1,37 @@ +import * as fs from 'fs'; +import 'mocha'; +import {assert} from 'chai'; +import { CompilerCallbacks, 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(vk); + const output = writer.compile(); + fs.writeFileSync(path + '.json', JSON.stringify(vk,null,2)); + assert.deepEqual(input, output); + }); +}); + +describe('kvks-file-reader', function () { + it('kvks-file-reader should compile with kvk-file-writer', function() { + const callbacks = new CompilerCallbacks(); + 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(callbacks); + const vk = reader.loadVisualKeyboard(input); + const writer = new KvkFileWriter(vk); + const output = writer.compile(); + fs.writeFileSync(inputPath + '.json', JSON.stringify(vk,null,2)); + fs.writeFileSync(inputPath + '.out', output); + 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..adb98537c6c --- /dev/null +++ b/developer/src/kmc-keyboard/test/kvk/test-kvks-file.ts @@ -0,0 +1,16 @@ +import * as fs from 'fs'; +import 'mocha'; +import { CompilerCallbacks, 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() { + let callbacks = new CompilerCallbacks(); + const path = makePathToFixture('kvk', 'khmer_angkor.kvks'); + const input = fs.readFileSync(path); + const reader = new KvksFileReader(callbacks); + const vk = reader.loadVisualKeyboard(input); + verify_khmer_angkor(vk); + }); +}); 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" From 50db7a2c9b751a179515b9654629d4121965dfd7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 26 Sep 2022 20:05:52 +1000 Subject: [PATCH 2/4] feat(developer): add schemas for .kvk and .kvks Also minor tweaks around reading .xml files. --- common/schemas/kvk/kvk.ksy | 122 ++++++++++++++ common/schemas/kvks/README.md | 14 ++ common/schemas/kvks/kvks.schema.json | 155 ++++++++++++++++++ common/schemas/kvks/kvks.xsd | 92 +++++++++++ developer/src/kmc-keyboard/build.sh | 1 + developer/src/kmc-keyboard/package.json | 3 +- .../kmc-keyboard/src/compiler/callbacks.ts | 1 + .../src/kmc-keyboard/src/compiler/messages.ts | 2 +- .../kmc-keyboard/src/kvk/kvk-file-writer.ts | 67 ++++---- .../kmc-keyboard/src/kvk/kvks-file-reader.ts | 146 ++++++++++------- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 1 + developer/src/kmc/src/kmc.ts | 4 + 12 files changed, 509 insertions(+), 99 deletions(-) create mode 100644 common/schemas/kvk/kvk.ksy create mode 100644 common/schemas/kvks/README.md create mode 100644 common/schemas/kvks/kvks.schema.json create mode 100644 common/schemas/kvks/kvks.xsd 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 d5ea89d5bda..864f6967c86 100644 --- a/developer/src/kmc-keyboard/package.json +++ b/developer/src/kmc-keyboard/package.json @@ -13,8 +13,7 @@ }, "scripts": { "build": "tsc -b", - "test": "cd test && tsc -b && cd .. && mocha", - "coverage": "cd test && tsc -b && cd .. && c8 mocha", + "test": "cd test && tsc -b && cd .. && c8 mocha", "prepublishOnly": "npm run build" }, "author": "Marc Durdin (https://github.com/mcdurdin)", 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/kvk/kvk-file-writer.ts b/developer/src/kmc-keyboard/src/kvk/kvk-file-writer.ts index 49c0655d4bf..78a484284bd 100644 --- a/developer/src/kmc-keyboard/src/kvk/kvk-file-writer.ts +++ b/developer/src/kmc-keyboard/src/kvk/kvk-file-writer.ts @@ -2,52 +2,47 @@ import KVKFile, { BUILDER_KVK_FILE, BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_H import { VisualKeyboard } from "./visual-keyboard.js"; export default class KvkFileWriter { - private source: VisualKeyboard; - - constructor(source: VisualKeyboard) { - this.source = source; - } - /** - * Fills a kvk string from a source string. Returns byte size of s as UTF-16 - * zero terminated string for KVK format Note that the format includes both a - * length word and zero termination. - * @param str - * @param value - * @returns number + * Writes the visual keyboard to a binary .kvk format byte array. + * @param source VisualKeyboard + * @returns Uint8Array, the .kvk file */ - private setString(str: BUILDER_KVK_STRING, value: string): void { - str.len = value.length + 1; - str.str = value; + 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() { - let binary: BUILDER_KVK_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: this.source.header.flags, + flags: source.header.flags, ansiFont:{ - color: this.source.header.ansiFont.color, - size: this.source.header.ansiFont.size, + color: source.header.ansiFont.color, + size: source.header.ansiFont.size, name: {len:0,str:''} }, unicodeFont:{ - color: this.source.header.unicodeFont.color, - size: this.source.header.unicodeFont.size, + color: source.header.unicodeFont.color, + size: source.header.unicodeFont.size, name: {len:0,str:''} }, }, - keyCount: this.source.keys.length, + keyCount: source.keys.length, keys:[] }; - this.setString(binary.header.associatedKeyboard, this.source.header.associatedKeyboard); - this.setString(binary.header.ansiFont.name, this.source.header.ansiFont.name); - this.setString(binary.header.unicodeFont.name, this.source.header.unicodeFont.name); + 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 this.source.keys) { + for(let sourceKey of source.keys) { const binaryKey: BUILDER_KVK_KEY = { flags: sourceKey.flags, vkey: sourceKey.vkey, @@ -62,12 +57,16 @@ export default class KvkFileWriter { return binary; } - compile(): Uint8Array { - const binary = this.build(); - 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; + /** + * 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/kvks-file-reader.ts b/developer/src/kmc-keyboard/src/kvk/kvks-file-reader.ts index 09bcf6b262a..3490c709b59 100644 --- a/developer/src/kmc-keyboard/src/kvk/kvks-file-reader.ts +++ b/developer/src/kmc-keyboard/src/kvk/kvks-file-reader.ts @@ -1,92 +1,85 @@ import * as xml2js from 'xml2js'; import KVKSourceFile from './kvks-file.js'; import CompilerCallbacks from '../compiler/callbacks.js'; -// import Ajv from 'ajv'; +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 LDMLKeyboardXMLSourceFileReader { +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); + } + /** - * xml2js will not place single-entry objects into arrays. - * Easiest way to fix this is to box them ourselves as needed - * @param source KVKSourceFile + * The only element that allows spaces is . Remove + * all other empty whitespace-only values. + * @param root + * @param source */ - 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'); + 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]); } } - return source; } public validate(source: KVKSourceFile): KVKSourceFile { - /* LDML-TODO: - const schema = JSON.parse(this.callbacks.loadKvksSchema().toString('utf8')); + 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 loadFile(filename: string) { - const buf = this.callbacks.loadFile(filename, filename); - return this.load(buf); - } - - public load(file: Uint8Array): KVKSourceFile { - let source = (() => { - let a: KVKSourceFile; - let 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) => { a = r as KVKSourceFile }); - return a; - })(); - - return this.validate(this.boxArrays(source)); - } - - public loadVisualKeyboard(file: Uint8Array): VisualKeyboard { - let source = this.load(file); - return this.transform(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; + return source; } public transform(source: KVKSourceFile): VisualKeyboard { @@ -142,4 +135,33 @@ export default class LDMLKeyboardXMLSourceFileReader { 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/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/kmc-keyboard/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 601a1ef9974..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 @@ -65,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/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 { From 74fc795c1c36585f08299600e2d32e77f44b8e0c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 26 Sep 2022 20:07:00 +1000 Subject: [PATCH 3/4] feat(developer): refactor CompilerCallbacks in tests Now has a shared object for all tests, reset between tests, and if the test fails, prints any reported compiler messages. Also updates kvk and kvks tests with refactored names from previous commit. --- .../src/kmc-keyboard/test/helpers/index.ts | 40 ++++++++++++----- .../test/kvk/test-kvk-round-trip.ts | 18 +++----- .../kmc-keyboard/test/kvk/test-kvks-file.ts | 7 ++- .../test-ldml-keyboard-xml-reader.ts | 16 +++---- developer/src/kmc-keyboard/test/test-bksp.ts | 7 ++- .../kmc-keyboard/test/test-compiler-e2e.ts | 7 ++- developer/src/kmc-keyboard/test/test-finl.ts | 7 ++- developer/src/kmc-keyboard/test/test-keys.ts | 40 +++++++---------- developer/src/kmc-keyboard/test/test-loca.ts | 27 +++++------- developer/src/kmc-keyboard/test/test-meta.ts | 32 ++++++-------- .../test/test-metadata-compiler.ts | 13 +++--- developer/src/kmc-keyboard/test/test-name.ts | 12 +++-- developer/src/kmc-keyboard/test/test-ordr.ts | 7 ++- developer/src/kmc-keyboard/test/test-tran.ts | 7 ++- developer/src/kmc-keyboard/test/test-vkey.ts | 44 ++++++++----------- 15 files changed, 132 insertions(+), 152 deletions(-) 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-round-trip.ts b/developer/src/kmc-keyboard/test/kvk/test-kvk-round-trip.ts index a520478b9d1..a03b34848c6 100644 --- a/developer/src/kmc-keyboard/test/kvk/test-kvk-round-trip.ts +++ b/developer/src/kmc-keyboard/test/kvk/test-kvk-round-trip.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import 'mocha'; import {assert} from 'chai'; -import { CompilerCallbacks, makePathToFixture } from '../helpers/index.js'; +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"; @@ -12,26 +12,22 @@ describe('kvk-file-reader', function () { const input = fs.readFileSync(path); const reader = new KvkFileReader(); const vk = reader.read(input); - const writer = new KvkFileWriter(vk); - const output = writer.compile(); - fs.writeFileSync(path + '.json', JSON.stringify(vk,null,2)); + 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 callbacks = new CompilerCallbacks(); 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(callbacks); - const vk = reader.loadVisualKeyboard(input); - const writer = new KvkFileWriter(vk); - const output = writer.compile(); - fs.writeFileSync(inputPath + '.json', JSON.stringify(vk,null,2)); - fs.writeFileSync(inputPath + '.out', output); + 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-kvks-file.ts b/developer/src/kmc-keyboard/test/kvk/test-kvks-file.ts index adb98537c6c..15f79f6e15d 100644 --- a/developer/src/kmc-keyboard/test/kvk/test-kvks-file.ts +++ b/developer/src/kmc-keyboard/test/kvk/test-kvks-file.ts @@ -1,16 +1,15 @@ import * as fs from 'fs'; import 'mocha'; -import { CompilerCallbacks, makePathToFixture } from '../helpers/index.js'; +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() { - let callbacks = new CompilerCallbacks(); const path = makePathToFixture('kvk', 'khmer_angkor.kvks'); const input = fs.readFileSync(path); - const reader = new KvksFileReader(callbacks); - const vk = reader.loadVisualKeyboard(input); + 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'})); }); }); From 9480d8931e49990b681d7ce1aaa037e863662703 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 26 Sep 2022 20:41:59 +1000 Subject: [PATCH 4/4] chore(developer): remove commented code --- .../src/kmc-keyboard/src/kvk/kvks-file.ts | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/developer/src/kmc-keyboard/src/kvk/kvks-file.ts b/developer/src/kmc-keyboard/src/kvk/kvks-file.ts index bf046e4b2f2..46b860c59bf 100644 --- a/developer/src/kmc-keyboard/src/kvk/kvks-file.ts +++ b/developer/src/kmc-keyboard/src/kvk/kvks-file.ts @@ -46,41 +46,3 @@ export interface KVKSKey { _?: string; }; -/* - - - - - - - - - - - - - - - - - - - - - - - -api.keyman.com visualkeyboard.dtd <-- note, this is NOT .kvks dTD -TODO-LDML: document .kvks XML schema - -*/ -