Merge pull request #7184 from keymanapp/feat/developer/7181-use-json-schema-to-verify-source-file

feat(developer): use json schema to verify source file 🙀
This commit is contained in:
Marc Durdin 2022-09-07 11:27:04 +10:00 committed by GitHub
commit 3307dc63a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1149 additions and 13 deletions

View file

@ -46,6 +46,8 @@ fi
if builder_has_action build; then
npm run build
# We need the schema file at runtime and bundled
cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json" "$THIS_SCRIPT_PATH/build/src/"
builder_report success build
fi

View file

@ -9,7 +9,7 @@
],
"scripts": {
"build": "tsc -b",
"test": "cd test && tsc -b && cd .. && mocha ",
"test": "cd test && tsc -b && cd .. && mocha",
"prepublishOnly": "npm run build"
},
"author": "Marc Durdin <marc@keyman.com> (https://github.com/mcdurdin)",
@ -19,6 +19,7 @@
"kmldmlc": "build/src/kmldmlc.js"
},
"dependencies": {
"ajv": "^8.11.0",
"commander": "^3.0.0",
"crc-32": "^1.2.2",
"restructure": "^3.0.0",
@ -37,7 +38,9 @@
"ts-node": "^9.1.1"
},
"mocha": {
"spec": "build/test/test-*.js"
"spec": "build/test/**/test-*.js",
"require": ["source-map-support/register"]
},
"repository": {
"type": "git",

View file

@ -5,5 +5,6 @@ export interface CompilerEvent {
export default interface CompilerCallbacks {
loadFile(baseFilename: string, filename: string): Buffer;
loadLdmlKeyboardSchema(): Buffer;
reportMessage(event: CompilerEvent): void;
};

View file

@ -37,6 +37,10 @@ export class CompilerErrors {
m(this.HINT_OneOrMoreRepeatedLocales, `After minimization, one or more locales is repeated and has been removed`);
static HINT_OneOrMoreRepeatedLocales = SevHint | 0x0006;
static InvalidFile = (errorText: string) =>
m(this.ERROR_InvalidFile, `The source file has an invalid structure: ${errorText}`);
static ERROR_InvalidFile = SevError | 0x0007;
static severityName(code: number): string {
let severity = code & CompilerErrorSeverity.Severity_Mask;
switch(severity) {

View file

@ -1,6 +1,8 @@
import * as xml2js from 'xml2js';
import LDMLKeyboardXMLSourceFile from './ldml-keyboard-xml';
import CompilerCallbacks from '../compiler/callbacks';
import Ajv from 'ajv';
import { CompilerErrors } from '../compiler/errors';
export default class LDMLKeyboardXMLSourceFileReader {
private readonly callbacks: CompilerCallbacks;
@ -16,7 +18,14 @@ export default class LDMLKeyboardXMLSourceFileReader {
*/
private boxArrays(source: any) {
let box = (o: any, x: string) => {
if(typeof o == 'object' && !Array.isArray(o[x])) o[x] = [o[x]];
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');
@ -36,6 +45,16 @@ export default class LDMLKeyboardXMLSourceFileReader {
return source;
}
public validate(source: LDMLKeyboardXMLSourceFile): LDMLKeyboardXMLSourceFile {
const schema = JSON.parse(this.callbacks.loadLdmlKeyboardSchema().toString('utf8'));
const ajv = new Ajv();
if(!ajv.validate(schema, source)) {
this.callbacks.reportMessage(CompilerErrors.InvalidFile(ajv.errorsText()));
return null;
}
return source;
}
public loadFile(filename: string) {
const buf = this.callbacks.loadFile(filename, filename);
return this.load(buf);
@ -47,11 +66,12 @@ export default class LDMLKeyboardXMLSourceFileReader {
let parser = new xml2js.Parser({
explicitArray: false,
mergeAttrs: true,
emptyTag: {}
});
parser.parseString(file, (e: unknown, r: unknown) => { a = r as LDMLKeyboardXMLSourceFile });
return a;
})();
return this.boxArrays(source);
return this.validate(this.boxArrays(source));
}
}

View file

@ -46,6 +46,9 @@ class CompilerCallbacks {
reportMessage(event: CompilerEvent): void {
console.log(CompilerErrors.severityName(event.code) + ' ' + event.code.toString(16) + ': ' + event.message);
}
loadLdmlKeyboardSchema(): Buffer {
return fs.readFileSync(path.join(__dirname, 'ldml-keyboard.schema.json'));
}
}
function compileKeyboard(inputFilename: string): Uint8Array {

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DOCTYPE keyboard SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/ldmlKeyboard.dtd"
Disabling doctype for this invalid file, not used by compiler, and avoids complaints in IDEs etc.
-->
<keyboard locale="mt" conformsTo="nothing-anyone-ever-heard-of">
<info author="srl295" indicator="🙀" layout="qwerty" normalization="NFC" />
<names>
<name value="TestKbd" />
</names>
<keys>
<key id="hmaqtua" to="ħ" />
<key id="that" to="ថា" />
</keys>
</keyboard>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DOCTYPE keyboard SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/ldmlKeyboard.dtd"
Disabling doctype for this invalid file, not used by compiler, and avoids complaints in IDEs etc.
-->
<keyboard locale="mt" conformsTo="techpreview">
</keyboard>

View file

@ -29,6 +29,11 @@ export class CompilerCallbacks {
reportMessage(event: CompilerEvent): void {
this.messages.push(event);
}
loadLdmlKeyboardSchema(): Buffer {
// Relative paths phooey!
return fs.readFileSync(path.join(__dirname, '..', '..', '..', '..', '..', '..',
'resources', 'standards-data', 'ldml-keyboards', 'techpreview', 'ldml-keyboard.schema.json'));
}
}
export function loadSectionFixture(compilerClass: typeof SectionCompiler, filename: string, callbacks: CompilerCallbacks): Section {

View file

@ -0,0 +1,30 @@
import 'mocha';
import {assert} from 'chai';
import {CompilerCallbacks, makePathToFixture} from '../helpers/index';
import LDMLKeyboardXMLSourceFileReader from '../../src/keyman/ldml-keyboard/ldml-keyboard-xml-reader';
import { CompilerErrors } from '../../src/keyman/compiler/errors';
describe('ldml keyboard xml reader tests', function() {
this.slow(500); // 0.5 sec -- json schema validation takes a while
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);
const source = reader.loadFile(inputFilename);
assert.isNull(source);
assert.equal(callbacks.messages.length, 1);
assert.deepEqual(callbacks.messages[0], CompilerErrors.InvalidFile("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);
const source = reader.loadFile(inputFilename);
assert.isNull(source);
assert.equal(callbacks.messages.length, 1);
assert.deepEqual(callbacks.messages[0], CompilerErrors.InvalidFile("data/keyboard/conformsTo must be equal to one of the allowed values"));
});
});

View file

@ -30,6 +30,8 @@ function compileKeyboard(inputFilename: string): Uint8Array {
}
describe('compiler-tests', function() {
this.slow(500); // 0.5 sec -- json schema validation takes a while
it('should-build-fixtures', async function() {
// Let's build basic.xml
// It should match basic.kmx (built from basic.txt)

View file

@ -6,6 +6,8 @@ import { Loca } from '../src/keyman/kmx/kmx-plus';
import { CompilerErrors } from '../src/keyman/compiler/errors';
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;

View file

@ -6,6 +6,8 @@ import { KeyboardSettings, Meta } from '../src/keyman/kmx/kmx-plus';
import { CompilerErrors } from '../src/keyman/compiler/errors';
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;

63
package-lock.json generated
View file

@ -473,6 +473,7 @@
"name": "@keymanapp/ldml-keyboard-compiler",
"license": "MIT",
"dependencies": {
"ajv": "^8.11.0",
"commander": "^3.0.0",
"crc-32": "^1.2.2",
"restructure": "^3.0.0",
@ -506,6 +507,21 @@
"integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
"dev": true
},
"developer/src/kmldmlc/node_modules/ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"developer/src/kmldmlc/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@ -556,6 +572,11 @@
"node": ">=4"
}
},
"developer/src/kmldmlc/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"developer/src/kmldmlc/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -3340,8 +3361,7 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"optional": true
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
@ -5580,7 +5600,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"optional": true,
"engines": {
"node": ">=6"
}
@ -5747,6 +5766,14 @@
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
@ -6600,7 +6627,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"optional": true,
"dependencies": {
"punycode": "^2.1.0"
}
@ -7781,6 +7807,7 @@
"@types/mocha": "^5.2.7",
"@types/node": "^10.14.6",
"@types/xml2js": "^0.4.5",
"ajv": "^8.11.0",
"chai": "^4.3.4",
"chalk": "^2.4.2",
"commander": "^3.0.0",
@ -7804,6 +7831,17 @@
"integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
"dev": true
},
"ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@ -7845,6 +7883,11 @@
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -9981,8 +10024,7 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"optional": true
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
@ -11747,8 +11789,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"optional": true
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"q": {
"version": "1.5.1",
@ -11874,6 +11915,11 @@
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
@ -12520,7 +12566,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"optional": true,
"requires": {
"punycode": "^2.1.0"
}

View file

@ -12,4 +12,13 @@ That will very roughly correspond to <https://github.com/unicode-org/cldr/tree/k
Each directory contains:
- `ldmlKeyboard.dtd` - the DTD file
- `ldmlKeyboard.xsd` - the XSD file, automatically converted from the DTD using
Visual Studio, hand tweaked as necessary
- `ldml-keyboard.schema.json` - the JSON schema file, automatically converted
from the XSD using xsd2json (https://github.com/Mermade/jgeXml), hand tweaked
as necessary:
- change toplevel "id" to "$id"
- add "type": "object" to /keyboard
- change /keyboard/keys/key type to "array"
- `imports/` - the importable data files (TODO-LDML)

View file

@ -0,0 +1,677 @@
{
"title": "techpreview/ldmlKeyboard.xsd",
"$schema": "http://json-schema.org/schema#",
"$id": "http://tempuri.org/ldmlKeyboard",
"type": "object",
"properties": {
"keyboard": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"locales": {
"$ref": "#/definitions/locales"
},
"version": {
"$ref": "#/definitions/version"
},
"info": {
"$ref": "#/definitions/info"
},
"names": {
"$ref": "#/definitions/names"
},
"settings": {
"$ref": "#/definitions/settings"
},
"vkeyMaps": {
"type": "array",
"items": {
"$ref": "#/definitions/vkeyMaps"
}
},
"displayMap": {
"type": "array",
"items": {
"$ref": "#/definitions/displayMap"
}
},
"keys": {
"$ref": "#/definitions/keys"
},
"layerMaps": {
"type": "array",
"items": {
"$ref": "#/definitions/layerMaps"
}
},
"transforms": {
"type": "array",
"items": {
"$ref": "#/definitions/transforms"
}
},
"reorders": {
"$ref": "#/definitions/reorders"
},
"backspaces": {
"$ref": "#/definitions/backspaces"
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
},
"locale": {
"type": "string"
},
"conformsTo": {
"type": "string",
"enum": [
"techpreview"
]
}
},
"required": [
"names",
"keys",
"locale",
"conformsTo"
],
"additionalProperties": false
}
},
"required": [
"keyboard"
],
"additionalProperties": false,
"definitions": {
"import": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"base": {
"type": "string",
"enum": [
"cldr"
]
}
},
"required": [
"path"
],
"additionalProperties": false
},
"locales": {
"type": "object",
"properties": {
"locale": {
"type": "array",
"items": {
"$ref": "#/definitions/locale"
}
}
},
"additionalProperties": false
},
"locale": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"additionalProperties": false
},
"version": {
"type": "object",
"properties": {
"number": {
"type": "string"
},
"cldrVersion": {
"type": "string",
"enum": [
"techpreview"
]
}
},
"additionalProperties": false
},
"info": {
"type": "object",
"properties": {
"author": {
"type": "string"
},
"normalization": {
"type": "string"
},
"layout": {
"type": "string"
},
"indicator": {
"type": "string"
}
},
"additionalProperties": false
},
"names": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"name": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/name"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
}
},
"required": [
"name"
],
"additionalProperties": false
},
"name": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
},
"required": [
"value"
],
"additionalProperties": false
},
"special": {
"type": "string"
},
"settings": {
"type": "object",
"properties": {
"fallback": {
"type": "string",
"enum": [
"omit"
]
},
"transformFailure": {
"type": "string",
"enum": [
"omit"
]
},
"transformPartial": {
"type": "string",
"enum": [
"hide"
]
}
},
"additionalProperties": false
},
"vkeyMaps": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"vkeyMap": {
"type": "array",
"items": {
"$ref": "#/definitions/vkeyMap"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
}
},
"additionalProperties": false
},
"vkeyMap": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"to": {
"type": "string"
}
},
"required": [
"from",
"to"
],
"additionalProperties": false
},
"displayMap": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"display": {
"type": "array",
"items": {
"$ref": "#/definitions/display"
}
},
"displayOptions": {
"type": "array",
"items": {
"$ref": "#/definitions/displayOptions"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
}
},
"additionalProperties": false
},
"display": {
"type": "object",
"properties": {
"to": {
"type": "string"
},
"display": {
"type": "string"
}
},
"required": [
"to",
"display"
],
"additionalProperties": false
},
"displayOptions": {
"type": "object",
"properties": {
"baseCharacter": {
"type": "string"
}
},
"additionalProperties": false
},
"keys": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
},
"key": {
"type": "array",
"items": {
"$ref": "#/definitions/key"
}
},
"flicks": {
"$ref": "#/definitions/flicks"
}
},
"additionalProperties": false
},
"key": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"flicks": {
"type": "string"
},
"gap": {
"type": "string",
"enum": [
"true"
]
},
"to": {
"type": "string"
},
"longPress": {
"type": "string"
},
"longPressDefault": {
"type": "string"
},
"multitap": {
"type": "string"
},
"switch": {
"type": "string"
},
"transform": {
"type": "string",
"enum": [
"no"
]
},
"width": {
"type": "string"
}
},
"required": [
"id"
],
"additionalProperties": false
},
"flicks": {
"type": "object",
"properties": {
"flick": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/flick"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
},
"id": {
"type": "string"
}
},
"required": [
"flick",
"id"
],
"additionalProperties": false
},
"flick": {
"type": "object",
"properties": {
"directions": {
"type": "string"
},
"to": {
"type": "string"
}
},
"required": [
"directions",
"to"
],
"additionalProperties": false
},
"layerMaps": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"layerMap": {
"type": "array",
"items": {
"$ref": "#/definitions/layerMap"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
},
"form": {
"type": "string",
"enum": [
"hardware",
"touch"
]
},
"minDeviceWidthMM": {
"type": "string"
}
},
"required": [
"form"
],
"additionalProperties": false
},
"layerMap": {
"type": "object",
"properties": {
"row": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/row"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
},
"id": {
"type": "string"
},
"modifier": {
"type": "string"
}
},
"required": [
"row"
],
"additionalProperties": false
},
"row": {
"type": "object",
"properties": {
"keys": {
"type": "string"
}
},
"required": [
"keys"
],
"additionalProperties": false
},
"transforms": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"transform": {
"type": "array",
"items": {
"$ref": "#/definitions/transform"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
},
"type": {
"type": "string"
}
},
"required": [
"type"
],
"additionalProperties": false
},
"transform": {
"type": "object",
"properties": {
"before": {
"type": "string"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"error": {
"type": "string",
"enum": [
"fail"
]
}
},
"required": [
"from",
"to"
],
"additionalProperties": false
},
"reorders": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"reorder": {
"type": "array",
"items": {
"$ref": "#/definitions/reorder"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
}
},
"additionalProperties": false
},
"reorder": {
"type": "object",
"properties": {
"before": {
"type": "string"
},
"from": {
"type": "string"
},
"order": {
"type": "string"
},
"tertiary": {
"type": "string"
},
"tertiary_base": {
"type": "string"
},
"prebase": {
"type": "string"
}
},
"required": [
"from"
],
"additionalProperties": false
},
"backspaces": {
"type": "object",
"properties": {
"import": {
"type": "array",
"items": {
"$ref": "#/definitions/import"
}
},
"backspace": {
"type": "array",
"items": {
"$ref": "#/definitions/backspace"
}
},
"special": {
"type": "array",
"items": {
"$ref": "#/definitions/special"
}
}
},
"additionalProperties": false
},
"backspace": {
"type": "object",
"properties": {
"before": {
"type": "string"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"error": {
"type": "string",
"enum": [
"fail"
]
}
},
"required": [
"from"
],
"additionalProperties": false
}
}
}

View file

@ -0,0 +1,304 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- generated via Visual Studio from ldmlKeyboard.dtd -->
<xs:schema xmlns="http://tempuri.org/ldmlKeyboard" elementFormDefault="qualified" targetNamespace="http://tempuri.org/ldmlKeyboard" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="keyboard">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:element minOccurs="0" maxOccurs="1" ref="locales" />
<xs:element minOccurs="0" maxOccurs="1" ref="version" />
<xs:element minOccurs="0" maxOccurs="1" ref="info" />
<xs:element ref="names" />
<xs:element minOccurs="0" maxOccurs="1" ref="settings" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="vkeyMaps" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="displayMap" />
<xs:element ref="keys" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="layerMaps" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="transforms" />
<xs:element minOccurs="0" maxOccurs="1" ref="reorders" />
<xs:element minOccurs="0" maxOccurs="1" ref="backspaces" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
<xs:attribute name="locale" type="xs:string" use="required" />
<xs:attribute name="conformsTo" use="required">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="techpreview" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="import">
<xs:complexType>
<xs:attribute name="path" type="xs:string" use="required" />
<xs:attribute name="base">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="cldr" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="locales">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="locale" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="locale">
<xs:complexType>
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="version">
<xs:complexType>
<xs:attribute name="number" type="xs:string" />
<xs:attribute fixed="techpreview" name="cldrVersion" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="info">
<xs:complexType>
<xs:attribute name="author" type="xs:string" />
<xs:attribute name="normalization" type="xs:string" />
<xs:attribute name="layout" type="xs:string" />
<xs:attribute name="indicator" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="names">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:element minOccurs="1" maxOccurs="unbounded" ref="name" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="name">
<xs:complexType>
<xs:attribute name="value" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="special">
<xs:complexType mixed="true">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##any" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="settings">
<xs:complexType>
<xs:attribute name="fallback">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="omit" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="transformFailure">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="omit" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="transformPartial">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="hide" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="vkeyMaps">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="vkeyMap" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="vkeyMap">
<xs:complexType>
<xs:attribute name="from" type="xs:string" use="required" />
<xs:attribute name="to" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="displayMap">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="display" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="displayOptions" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="display">
<xs:complexType>
<xs:attribute name="to" type="xs:string" use="required" />
<xs:attribute name="display" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="displayOptions">
<xs:complexType>
<xs:attribute name="baseCharacter" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="keys">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="key" />
<xs:element ref="flicks" />
</xs:choice>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="key">
<xs:complexType>
<xs:attribute name="id" type="xs:string" use="required" />
<xs:attribute name="flicks" type="xs:NMTOKEN" />
<xs:attribute name="gap">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="true" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="to" type="xs:string" />
<xs:attribute name="longPress" type="xs:string" />
<xs:attribute name="longPressDefault" type="xs:string" />
<xs:attribute name="multitap" type="xs:string" />
<xs:attribute name="switch" type="xs:NMTOKEN" />
<xs:attribute name="transform">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="no" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="width" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="flicks">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="flick" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
<xs:attribute name="id" type="xs:NMTOKEN" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="flick">
<xs:complexType>
<xs:attribute name="directions" type="xs:NMTOKENS" use="required" />
<xs:attribute name="to" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="layerMaps">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="layerMap" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
<xs:attribute name="form" use="required">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="hardware" />
<xs:enumeration value="touch" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="minDeviceWidthMM" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="layerMap">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="row" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
<xs:attribute name="id" type="xs:NMTOKEN" />
<xs:attribute name="modifier" type="xs:NMTOKEN" />
</xs:complexType>
</xs:element>
<xs:element name="row">
<xs:complexType>
<xs:attribute name="keys" type="xs:NMTOKENS" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="transforms">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="transform" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="transform">
<xs:complexType>
<xs:attribute name="before" type="xs:string" />
<xs:attribute name="from" type="xs:string" use="required" />
<xs:attribute name="to" type="xs:string" use="required" />
<xs:attribute name="error">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="fail" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="reorders">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="reorder" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="reorder">
<xs:complexType>
<xs:attribute name="before" type="xs:string" />
<xs:attribute name="from" type="xs:string" use="required" />
<xs:attribute name="order" type="xs:string" />
<xs:attribute name="tertiary" type="xs:string" />
<xs:attribute name="tertiary_base" type="xs:string" />
<xs:attribute name="prebase" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="backspaces">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="import" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="backspace" />
<xs:element minOccurs="0" maxOccurs="unbounded" ref="special" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="backspace">
<xs:complexType>
<xs:attribute name="before" type="xs:string" />
<xs:attribute name="from" type="xs:string" use="required" />
<xs:attribute name="to" type="xs:string" />
<xs:attribute name="error">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="fail" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>