diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 726ab28df9..4999336cee 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -289,11 +289,15 @@ export class KmpCompiler implements KeymanCompiler { // if(kps.Keyboards?.Keyboard?.length) { + if(!this.validateKeyboards(kps.Keyboards.Keyboard)) { + // error reported by validateKeyboards + return null; + } kmp.keyboards = kps.Keyboards.Keyboard.map((keyboard: KpsFile.KpsFileKeyboard) => ({ displayFont: keyboard.DisplayFont ? this.callbacks.path.basename(this.normalizePath(keyboard.DisplayFont)) : undefined, oskFont: keyboard.OSKFont ? this.callbacks.path.basename(this.normalizePath(keyboard.OSKFont)) : undefined, name: '', // filled by PackageMetadataUpdater - id:keyboard.ID?.trim(), + id:keyboard.ID.trim(), version: DEFAULT_KEYBOARD_VERSION, // filled by PackageMetadataUpdater rtl: false, // filled by PackageMetadataUpdater languages: keyboard.Languages?.Language?.length ? @@ -322,6 +326,10 @@ export class KmpCompiler implements KeymanCompiler { // if(kps.LexicalModels?.LexicalModel?.length) { + if(!this.validateLexicalModels(kps.LexicalModels.LexicalModel)) { + // error reported by validateLexicalModels + return null; + } kmp.lexicalModels = kps.LexicalModels.LexicalModel.map((model: KpsFile.KpsFileLexicalModel) => ({ name:model.ID.trim(), id:model.ID.trim(), @@ -453,6 +461,30 @@ export class KmpCompiler implements KeymanCompiler { return o; } + private validateKeyboards(keyboards: KpsFile.KpsFileKeyboard[]): boolean { + let index = 1; + for(const keyboard of keyboards) { + if(!keyboard.ID || keyboard.ID.trim() == '') { + this.callbacks.reportMessage(PackageCompilerMessages.Error_MissingKeyboardId({index})); + return false; + } + index++; + } + return true; + } + + private validateLexicalModels(models: KpsFile.KpsFileLexicalModel[]): boolean { + let index = 1; + for(const model of models) { + if(!model.ID || model.ID.trim() == '') { + this.callbacks.reportMessage(PackageCompilerMessages.Error_MissingModelId({index})); + return false; + } + index++; + } + return true; + } + /** * @internal * Returns a Promise to the serialized data which can then be written to a .kmp file. diff --git a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts index d98ff747e0..82c2d0b023 100644 --- a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts +++ b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts @@ -207,6 +207,22 @@ export class PackageCompilerMessages { may violate this rule transitively. `); + static ERROR_MissingModelId = SevError | 0x0029; + static Error_MissingModelId = (o:{index:number}) => m( + this.ERROR_MissingModelId, `The lexical model at index ${def(o.index)} has a missing or empty ID field.`, ` + Each LexicalModel element must have an ID sub-element and a Languages + sub-element. The content of the ID sub-element must correspond to the basename + of a .model.js file inside the Files element. + `); + + static ERROR_MissingKeyboardId = SevError | 0x002A; + static Error_MissingKeyboardId = (o:{index:number}) => m( + this.ERROR_MissingKeyboardId, `The keyboard at index ${def(o.index)} has a missing or empty ID field.`, ` + Each Keyboard element must have an ID sub-element sub-element. The content of + the ID sub-element must correspond to the basename of a keyboard .kmx or .js + file inside the Files element. + `); + //------------------------------------------------------------------------------| // max length of detail message lines (checked by verifyCompilerMessagesObject) | //------------------------------------------------------------------------------| diff --git a/developer/src/kmc-package/test/fixtures/invalid/missing_model_id.kps b/developer/src/kmc-package/test/fixtures/invalid/missing_model_id.kps new file mode 100644 index 0000000000..ef84326634 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/missing_model_id.kps @@ -0,0 +1,30 @@ + + + + 12.0.1500.0 + 12.0 + + + SENĆOŦEN (Saanich Dialect) Lexical Model + © 2019 National Research Council Canada + Eddie Antonio Santos + 1.0.3 + + + + example.qaa.sencoten.model.js + Lexical model example.qaa.sencoten.model.js + 0 + .model.js + + + + + + + North Straits Salish + SENĆOŦEN + + + + diff --git a/developer/src/kmc-package/test/package-compiler.tests.ts b/developer/src/kmc-package/test/package-compiler.tests.ts index 76d00d99be..33b00e6f00 100644 --- a/developer/src/kmc-package/test/package-compiler.tests.ts +++ b/developer/src/kmc-package/test/package-compiler.tests.ts @@ -86,7 +86,7 @@ describe('KmpCompiler', function () { assert.deepEqual(kmpJsonOutput, kmpJsonZippedFixture); }), zipFile.file(`${modelID}.model.js`).async('uint8array').then(modelJsFile => { - assert.deepEqual(modelJsFile, fs.readFileSync(jsPath)); + assert.deepEqual(modelJsFile, new Uint8Array(fs.readFileSync(jsPath))); }) ]); }); @@ -278,8 +278,13 @@ describe('KmpCompiler', function () { it(`should load a package with missing keyboard ID metadata`, function () { const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_keyboard_id.kps')); assert.isNull(kmpJson); // with a missing keyboard_id, the package shouldn't load, but it shouldn't crash either - assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.ERROR_KeyboardContentFileNotFound); + assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.ERROR_MissingKeyboardId); + }); + it(`should load a package with missing model ID metadata`, function () { + const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_model_id.kps')); + assert.isNull(kmpJson); // with a missing model_id, the package shouldn't load, but it shouldn't crash either + assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.ERROR_MissingModelId); }); it(`should load a package with missing keyboard name metadata`, function () {