mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 00:15:32 +00:00
fix(common): legacy .kpj schema
Fixes #9140. Fixes #9148. Keyman Developer 9.0 .kpj files included a lot of additional state metadata. We need a schema which validates these files, as they are otherwise valid to load (we'll never save them any more). Rather than add all the extra metadata to what is otherwise a fairly clean schema, we'll provide a legacy .schema.json. In the future, we may be able to merge these schemas, as we move towards the .kpj 2.0 format which doesn't list files. Ideally, all three formats (Keyman Developer 9.0 schema, call it legacy, 1.0 schema for Keyman Developer 10.0+ which has Options and Files listed, 2.0 schema for capturing just project settings for a folder) will be supported by a single schema file.
This commit is contained in:
parent
f85478ac6f
commit
33affc660f
11 changed files with 171 additions and 9 deletions
11
common/schemas/kpj-9.0/README.md
Normal file
11
common/schemas/kpj-9.0/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# kpj-9.0.schema.json
|
||||
|
||||
**Note:** `KeymanDeveloperProject.Options.Version` is currently implicitly
|
||||
always '1.0'. It will be required for version 2.0 and later of the format.
|
||||
|
||||
This is a schema for for supporting legacy versions of .kpj, from Keyman
|
||||
Developer 9.0 and earlier, is now available. Note that this schema is not fully
|
||||
validating on the legacy fields, but just sufficient to pass a valid
|
||||
file.
|
||||
|
||||
You should be using kpj.schema.json for most purposes
|
||||
134
common/schemas/kpj-9.0/kpj-9.0.schema.json
Normal file
134
common/schemas/kpj-9.0/kpj-9.0.schema.json
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
{
|
||||
"title": "kpj-9.0.xsd",
|
||||
"$schema": "http://json-schema.org/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"KeymanDeveloperProject": {
|
||||
"properties": {
|
||||
"Options": {
|
||||
"$ref": "#/definitions/Options"
|
||||
},
|
||||
"Files": {
|
||||
"$ref": "#/definitions/Files"
|
||||
},
|
||||
"templatepath": { "type":"string", "$comment": "Keyman Developer 9, no longer used in modern .kpj files" },
|
||||
"stringspath": { "type":"string", "$comment": "Keyman Developer 9, no longer used in modern .kpj files" },
|
||||
"state": { "type": "string", "$comment": "Keyman Developer 9, no longer used in modern .kpj files" },
|
||||
"Modules": { "oneOf": [{"type": "object"}, {"type": "string"}], "$comment": "Keyman Developer 9, no longer used in modern .kpj files" },
|
||||
"MRU": { "type": "object", "$comment": "Keyman Developer 9, no longer used in modern .kpj files" }
|
||||
},
|
||||
"required": [
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"KeymanDeveloperProject"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"BuildPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"SourcePath": {
|
||||
"type": "string"
|
||||
},
|
||||
"CompilerWarningsAsErrors": {
|
||||
"type": "string",
|
||||
"pattern": "^(True|False)$"
|
||||
},
|
||||
"WarnDeprecatedCode": {
|
||||
"type": "string",
|
||||
"pattern": "^(True|False)$"
|
||||
},
|
||||
"CheckFilenameConventions": {
|
||||
"type": "string",
|
||||
"pattern": "^(True|False)$"
|
||||
},
|
||||
"ProjectType": {
|
||||
"type": "string",
|
||||
"pattern": "^(keyboard|lexicalmodel)$"
|
||||
},
|
||||
"Version": {
|
||||
"type": "string",
|
||||
"pattern": "^(1\\.0|2\\.0)$"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Files": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"File": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/File"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"File"
|
||||
]
|
||||
},
|
||||
"File": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ID": {
|
||||
"type": "string"
|
||||
},
|
||||
"Filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"Filepath": {
|
||||
"type": "string"
|
||||
},
|
||||
"FileVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"FileType": {
|
||||
"type": "string"
|
||||
},
|
||||
"Details": {
|
||||
"$ref": "#/definitions/FileDetails"
|
||||
},
|
||||
"ParentFileID": {
|
||||
"type": "string"
|
||||
},
|
||||
"FullPath": { "type":"string", "$comment": "Keyman Developer 9, no longer used in modern .kpj files" },
|
||||
"IDEState": { "type":"object", "$comment": "Keyman Developer 9, no longer used in modern .kpj files" }
|
||||
},
|
||||
"required": [
|
||||
"Filename"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"FileDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Name": {
|
||||
"type": "string"
|
||||
},
|
||||
"Copyright": {
|
||||
"type": "string"
|
||||
},
|
||||
"Message": {
|
||||
"type": "string"
|
||||
},
|
||||
"Version": {
|
||||
"type": "string"
|
||||
},
|
||||
"Debug": { "type":"string", "$comment": "Keyman Developer 9, no longer used in modern .kpj files" }
|
||||
},
|
||||
"required": [
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
**Note:** `KeymanDeveloperProject.Options.Version` is currently implicitly
|
||||
always '1.0'. It will be required for version 2.0 and later of the format.
|
||||
|
||||
**Note:** An additional schema file, kpj-9.0.schema.json, for supporting legacy
|
||||
versions of .kpj, from Keyman Developer 9.0 and earlier, is now available.
|
||||
|
||||
## 2023-02-27 2.0
|
||||
* Version 2.0 makes 'Files' optional (internally, Files/File will be ignored,
|
||||
deleted on load and populated from folder structure). Adds Options/SourcePath,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ function copy_schemas() {
|
|||
cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kvks/kvks.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kpj/kpj.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kpj-9.0/kpj-9.0.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/displaymap/displaymap.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
# Store CLDR imports
|
||||
|
|
|
|||
|
|
@ -23,21 +23,27 @@ export class KPJFileReader {
|
|||
});
|
||||
|
||||
parser.parseString(file, (e: unknown, r: unknown) => { data = r as KPJFile });
|
||||
data = this.boxArrays(data);
|
||||
for(let file of data.KeymanDeveloperProject?.Files?.File) {
|
||||
// xml2js imports <Details/> as '' so we will just delete the empty string
|
||||
if(typeof file.Details == 'string') {
|
||||
delete file.Details;
|
||||
}
|
||||
}
|
||||
data = this.boxArrays(data);
|
||||
return data as KPJFile;
|
||||
}
|
||||
|
||||
public validate(source: KPJFile, schemaBuffer: Uint8Array): void {
|
||||
public validate(source: KPJFile, schemaBuffer: Uint8Array, legacySchemaBuffer: Uint8Array): void {
|
||||
const schema = JSON.parse(new TextDecoder().decode(schemaBuffer));
|
||||
const ajv = new Ajv();
|
||||
if(!ajv.validate(schema, source)) {
|
||||
throw new Error(ajv.errorsText());
|
||||
const ajvLegacy = new Ajv();
|
||||
const legacySchema = JSON.parse(new TextDecoder().decode(legacySchemaBuffer));
|
||||
if(!ajvLegacy.validate(legacySchema, source)) {
|
||||
// If the legacy schema also does not validate, then we will only report
|
||||
// the errors against the modern schema
|
||||
throw new Error(ajv.errorsText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ export type CompilerSchema =
|
|||
'ldml-keyboardtest' |
|
||||
'kvks' |
|
||||
'kpj' |
|
||||
'kpj-9.0' |
|
||||
'displaymap';
|
||||
// | 'keyman-touch-layout.clean'; TODO this has the wrong name pattern, .spec.json instead of .schema.json
|
||||
|
||||
|
|
|
|||
|
|
@ -12,15 +12,15 @@ export class TestCompilerCallbacks implements CompilerCallbacks {
|
|||
loadSchema(schema: CompilerSchema): Buffer {
|
||||
switch (schema) {
|
||||
case 'kpj':
|
||||
throw new Error('loadKpjJsonSchema not implemented.'); // not needed for this test
|
||||
case 'kpj-9.0':
|
||||
case 'kvks':
|
||||
throw new Error('loadKvksJsonSchema not implemented.');
|
||||
case 'displaymap':
|
||||
// listing all unimplemented schemas rather than using 'default'
|
||||
throw new Error(`loadSchema(${schema}) not implemented.`); // not needed for this test
|
||||
case 'ldml-keyboard':
|
||||
return loadSchema(schema);
|
||||
case 'ldml-keyboardtest':
|
||||
return loadSchema(schema);
|
||||
case 'displaymap':
|
||||
throw new Error('loadDisplayMapSchema not implemented.'); // not needed for this test
|
||||
}
|
||||
}
|
||||
clear() {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe('kpj-file-reader', function () {
|
|||
const reader = new KPJFileReader(callbacks);
|
||||
const kpj = reader.read(input);
|
||||
assert.doesNotThrow(() => {
|
||||
reader.validate(kpj, loadSchema('kpj'));
|
||||
reader.validate(kpj, loadSchema('kpj'), loadSchema('kpj-9.0'));
|
||||
});
|
||||
assert.equal(kpj.KeymanDeveloperProject.Options.BuildPath, '$PROJECTPATH\\build');
|
||||
assert.equal(kpj.KeymanDeveloperProject.Options.CheckFilenameConventions, 'False');
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ fi
|
|||
SCHEMAS_COPIED=false
|
||||
|
||||
copy_schemas() {
|
||||
# TODO: why do we need a copy of the schemas here? kmc already has a copy of
|
||||
# them; are they used only by tests, in which case, can't we use them in
|
||||
# their source location?
|
||||
if $SCHEMAS_COPIED; then
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -55,6 +58,7 @@ copy_schemas() {
|
|||
cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kvks/kvks.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kpj/kpj.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kpj-9.0/kpj-9.0.schema.json" "$THIS_SCRIPT_PATH/build/src/"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ function copy_schemas() {
|
|||
cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json" "$THIS_SCRIPT_PATH/build/src/util/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kvks/kvks.schema.json" "$THIS_SCRIPT_PATH/build/src/util/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kpj/kpj.schema.json" "$THIS_SCRIPT_PATH/build/src/util/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/kpj-9.0/kpj-9.0.schema.json" "$THIS_SCRIPT_PATH/build/src/util/"
|
||||
cp "$KEYMAN_ROOT/common/schemas/displaymap/displaymap.schema.json" "$THIS_SCRIPT_PATH/build/src/util/"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,8 +89,9 @@ class ProjectBuilder {
|
|||
const reader = new KPJFileReader(this.callbacks);
|
||||
const kpj = reader.read(kpjData);
|
||||
const schema = this.callbacks.loadSchema('kpj');
|
||||
const legacySchema = this.callbacks.loadSchema('kpj-9.0');
|
||||
try {
|
||||
reader.validate(kpj, schema);
|
||||
reader.validate(kpj, schema, legacySchema);
|
||||
} catch(e) {
|
||||
this.callbacks.reportMessage(InfrastructureMessages.Error_InvalidProjectFile({message: (e??'').toString()}));
|
||||
return null;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue