diff --git a/HISTORY.md b/HISTORY.md index 55a6f985e63..a779c84f558 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 17.0.199 alpha 2023-10-26 + +* fix(developer): handle xml errors in package compiler (#9821) +* fix(developer): server download Keyman link (#9822) +* chore(common): handle invalid XML in kpj-file-reader (#9824) +* fix(developer): reduce confusion in Unicode fields in touch layout editor (#9839) + ## 17.0.198 alpha 2023-10-25 * chore(common): Add entries from 16.0 HISTORY.md (#9826) diff --git a/VERSION.md b/VERSION.md index a2ed5131d52..0de5a09ae58 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.199 \ No newline at end of file +17.0.200 \ No newline at end of file diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index cffa2069c64..4457992e227 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -21,7 +21,12 @@ export class KPJFileReader { emptyTag: '' }); - parser.parseString(file, (e: unknown, r: unknown) => { data = r as KPJFile }); + parser.parseString(file, (e: unknown, r: unknown) => { + if(e) { + throw e; + } + data = r as KPJFile; + }); data = this.boxArrays(data); for(let file of data.KeymanDeveloperProject?.Files?.File) { // xml2js imports
as '' so we will just delete the empty string diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index b26643e1781..f2df4c881fc 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -29,6 +29,7 @@ export class KmpCompiler { public transformKpsToKmpObject(kpsFilename: string): KmpJsonFile.KmpJsonFile { const kps = this.loadKpsFile(kpsFilename); if(!kps) { + // errors will already have been reported by loadKpsFile return null; } return this.transformKpsFileToKmpObject(kpsFilename, kps); @@ -48,11 +49,19 @@ export class KmpCompiler { let parser = new xml2js.Parser({ explicitArray: false }); - // TODO: add unit test for xml errors parsing .kps file - parser.parseString(data, (e: unknown, r: unknown) => { if(e) throw e; a = r as KpsFile.KpsPackage }); + + try { + parser.parseString(data, (e: unknown, r: unknown) => { if(e) throw e; a = r as KpsFile.KpsPackage }); + } catch(e) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidPackageFile({e})); + } return a; })(); + if(!kpsPackage) { + return null; + } + const kps: KpsFile.KpsFile = kpsPackage.Package; return kps; } diff --git a/developer/src/kmc-package/src/compiler/messages.ts b/developer/src/kmc-package/src/compiler/messages.ts index 2eeaa6e0618..cec733d3d84 100644 --- a/developer/src/kmc-package/src/compiler/messages.ts +++ b/developer/src/kmc-package/src/compiler/messages.ts @@ -123,5 +123,9 @@ export class CompilerMessages { static Hint_PackageContainsSourceFile = (o:{filename:string}) => m(this.HINT_PackageContainsSourceFile, `The source file ${o.filename} should not be included in the package; instead include the compiled result.`); static HINT_PackageContainsSourceFile = SevHint | 0x001D; + + static Error_InvalidPackageFile = (o:{e:any}) => m(this.ERROR_InvalidPackageFile, + `Package source file is invalid: ${(o.e ?? 'unknown error').toString()}`); + static ERROR_InvalidPackageFile = SevError | 0x001E; } diff --git a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts index 8f920535bbf..1a5f4ebe6b8 100644 --- a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts +++ b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts @@ -39,6 +39,10 @@ export class WindowsPackageInstallerCompiler { public async compile(kpsFilename: string, sources: WindowsPackageInstallerSources): Promise { const kps = this.kmpCompiler.loadKpsFile(kpsFilename); + if(!kps) { + // errors will already have been reported by loadKpsFile + return null; + } // Check existence of required files for(const filename of [sources.licenseFilename, sources.msiFilename, sources.setupExeFilename]) { diff --git a/developer/src/kmc-package/test/fixtures/invalid/error_invalid_package_file.kps b/developer/src/kmc-package/test/fixtures/invalid/error_invalid_package_file.kps new file mode 100644 index 00000000000..e18eaa26a06 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/error_invalid_package_file.kps @@ -0,0 +1,32 @@ + + + + 15.0.266.0 + 7.0 + + + SENĆOŦEN (Saanich Dialect) Keyboard + + © 2019 National Research Council Canada & this test + Eddie Antonio Santos + 1.0 + + + + basic.kmx + Keyboard Basic + 0 + .kmx + + + + + Basic + basic + 1.0 + + Khmer + + + + diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index f9649e9c090..24fb658b599 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -226,4 +226,11 @@ describe('CompilerMessages', function () { CompilerMessages.HINT_PackageContainsSourceFile); }); + // ERROR_InvalidPackageFile + + it('should generate ERROR_InvalidPackageFile if package source file contains invalid XML', async function() { + testForMessage(this, ['invalid', 'error_invalid_package_file.kps'], + CompilerMessages.ERROR_InvalidPackageFile); + }); + }); diff --git a/developer/src/kmc/src/util/projectLoader.ts b/developer/src/kmc/src/util/projectLoader.ts index 078f2e31ded..10cd58fc818 100644 --- a/developer/src/kmc/src/util/projectLoader.ts +++ b/developer/src/kmc/src/util/projectLoader.ts @@ -44,8 +44,9 @@ function loadDefaultProjectFromFolder(infile: string, callbacks: CompilerCallbac function loadProjectFromFile(infile: string, callbacks: CompilerCallbacks): KeymanDeveloperProject { const kpjData = callbacks.loadFile(infile); const reader = new KPJFileReader(callbacks); - const kpj = reader.read(kpjData); + let kpj = null; try { + kpj = reader.read(kpjData); reader.validate(kpj); } catch(e) { callbacks.reportMessage(InfrastructureMessages.Error_InvalidProjectFile({message: (e??'').toString()})); diff --git a/developer/src/server/src/site/packages.js b/developer/src/server/src/site/packages.js index 9a13b1afacc..2255fe9ebd6 100644 --- a/developer/src/server/src/site/packages.js +++ b/developer/src/server/src/site/packages.js @@ -11,12 +11,12 @@ menuDropdown.onclick = (value) => { menuDropdown.set(''); // we never show an 'active' package if(value == '#install-keyman') { let href = ''; - switch(keyman.util.device.OS) { - case 'iOS': href = 'https://keyman.com/go/developer/'+versionMajor+'/ios-app'; break; - case 'Android': href = 'https://keyman.com/go/developer/'+versionMajor+'/android-app'; break; - case 'Linux': href = 'https://keyman.com/linux/download'; break; - case 'Windows': href = 'https://keyman.com/go/download/keyman-windows'; break; - case 'MacOSX': href = 'https://keyman.com/go/download/keyman-mac'; break; + switch(keyman.config.hostDevice.OS) { // note: KeymanWeb internal API + case 'ios': href = 'https://keyman.com/go/developer/'+versionMajor+'/ios-app'; break; + case 'android': href = 'https://keyman.com/go/developer/'+versionMajor+'/android-app'; break; + case 'linux': href = 'https://keyman.com/linux/download'; break; + case 'windows': href = 'https://keyman.com/go/download/keyman-windows'; break; + case 'macosx': href = 'https://keyman.com/go/download/keyman-mac'; break; default: href = 'https://keyman.com/downloads'; break; } location.href = href; diff --git a/developer/src/server/src/site/test.js b/developer/src/server/src/site/test.js index 60a27cc484f..6014c8ab264 100644 --- a/developer/src/server/src/site/test.js +++ b/developer/src/server/src/site/test.js @@ -216,14 +216,14 @@ window.onload = function() { if(newOSK) { document.getElementById('osk-host').removeChild(newOSK.element); - keyman.osk = null; + keyman.osk = null; // Note: undocumented KeymanWeb API } // Create a new on screen keyboard view and tell KeymanWeb that // we are using the targetDevice for context input. - newOSK = new keyman.views.InlinedOSKView(keyman, { device: targetDevice }); - keyman.core.contextDevice = targetDevice; - keyman.osk = newOSK; + newOSK = new keyman.views.InlinedOSKView(keyman, { device: targetDevice }); // Note: KeymanWeb internal API + keyman.core.contextDevice = targetDevice; // Note: KeymanWeb internal API + keyman.osk = newOSK; // Note: undocumented KeymanWeb API if(document.body.offsetWidth < targetDevice.dimensions[0]) { newOSK.setSize('320px', '200px'); @@ -237,8 +237,8 @@ window.onload = function() { keyman.addEventListener('keyboardchange', function(keyboardProperties) { if(newOSK) { - keyman.osk = newOSK; - newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; // Private API refs on both sides + keyman.osk = newOSK; // Note: undocumented KeymanWeb API + newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; // Note: undocumented KeymanWeb API refs on both sides } keyboardDropdown.set(keyboardProperties.internalName); window.sessionStorage.setItem('current-keyboard', keyboardProperties.internalName); @@ -284,7 +284,7 @@ function unloadKeyboardsAndModels() { const lastModel = keyman.core.activeModel; if(lastModel) { console.log('Unregistering model '+lastModel.id); - keyman.removeModel(lastModel.id); + keyman.removeModel(lastModel.id); // Note: undocumented KeymanWeb API } modelDropdown.removeAll(); diff --git a/developer/src/tike/xml/layoutbuilder/builder.xsl b/developer/src/tike/xml/layoutbuilder/builder.xsl index 2d6f5299cd1..3b75cd81a3e 100644 --- a/developer/src/tike/xml/layoutbuilder/builder.xsl +++ b/developer/src/tike/xml/layoutbuilder/builder.xsl @@ -108,7 +108,7 @@
- +
@@ -118,7 +118,7 @@
- +
@@ -221,7 +221,7 @@
- +