feat(developer): replace all any-datatypes

add checkForCorrespondingElements() to validate()
min/maxOccurs added in keylayout.xsd
edited keylayout.dtd
(possible remaining squiggle lines will be addressed in PR#15860
This commit is contained in:
Sabine 2026-04-28 19:27:21 +02:00
parent 9a9d33bb50
commit 461fdd86c4
7 changed files with 194 additions and 134 deletions

View file

@ -158,7 +158,9 @@ export interface KL_When {
* to define which output or next is followed by a state
*/
state?: string;
through?: string;
output?: string;
multiplier?: string;
next?: string;
};

View file

@ -11,6 +11,7 @@ import { CompilerCallbacks, DeveloperUtilsMessages, Keylayout, KeymanXMLReader }
import { util, SchemaValidators } from '@keymanapp/common-types';
import { ConverterMessages } from '../converter-messages.js';
import boxXmlArray = util.boxXmlArray;
import { KL_KeyMapSelect,KL_KeyMap } from "../../../common/web/utils/src/types/keylayout/keylayout-xml.js";
export class KeylayoutFileReader {
@ -23,10 +24,10 @@ export class KeylayoutFileReader {
* @param keyMapSelect the keyMapSelect element to find in keyMapSet
* @return true if the keyMapSet element is found, false if not
*/
public findMapIndexinKeymap(jsonObj: any, keyMapSelect: any): boolean {
public findMapIndexinKeymap(jsonObj: Keylayout.KeylayoutXMLSourceFile, keyMapSelect: KL_KeyMapSelect): boolean {
for (const keyMapSet of jsonObj.keyboard.keyMapSet) {
for (const keyMap of keyMapSet.keyMap) {
if (keyMap['index'] === keyMapSelect) {
if (keyMap['index'] === keyMapSelect.mapIndex) {
return true;
}
}
@ -40,10 +41,10 @@ export class KeylayoutFileReader {
* @param keyMap the keyMap element to find in modifierMap
* @return true if the keyMap element is found, false if not
*/
public findIndexinKeymapSelect(jsonObj: any, keyMap: any): boolean {
public findIndexinKeymapSelect(jsonObj: Keylayout.KeylayoutXMLSourceFile, keyMap: KL_KeyMap): boolean {
for (const modifierMap of jsonObj.keyboard.modifierMap) {
for (const keyMapSelect of modifierMap.keyMapSelect) {
if (keyMapSelect['mapIndex'] === keyMap) {
if (keyMapSelect['mapIndex'] === keyMap.index) {
return true;
}
}
@ -58,19 +59,19 @@ export class KeylayoutFileReader {
* @param jsonObj the read keylayout data to be checked
* @return true if all keyMapSelect elements have a corresponding keyMap element, false if not
*/
public checkForCorrespondingElements(jsonObj: any): boolean {
public checkForCorrespondingElements(jsonObj: Keylayout.KeylayoutXMLSourceFile): boolean {
let available = true;
// check if all keyMapSelect elements have a corresponding keyMap element in the .keylayout file
for (const modifierMap of jsonObj.keyboard.modifierMap) {
for (const keyMapSelect of modifierMap.keyMapSelect) {
available = available && this.findMapIndexinKeymap(jsonObj, keyMapSelect['mapIndex']);
available = available && this.findMapIndexinKeymap(jsonObj, keyMapSelect);
}
}
// check if all keyMap elements have a corresponding keyMapSelect element in the .keylayout file
for (const keyMapSet of jsonObj.keyboard.keyMapSet) {
for (const keyMap of keyMapSet.keyMap) {
available = available && this.findIndexinKeymapSelect(jsonObj, keyMap['index']);
available = available && this.findIndexinKeymapSelect(jsonObj, keyMap);
}
}
return available;

View file

@ -48,7 +48,7 @@ export interface KeylayoutFileData {
actionId?: string;
keyCode?: string;
key?: string;
behavior?: string;
behavior: string;
modifier?: string;
outchar?: string;
};
@ -124,7 +124,7 @@ export class KeylayoutToKmnConverter {
return null;
}
try {
if (!KeylayoutReader.validate(jsonO,inputFilename)) {
if (!KeylayoutReader.validate(jsonO, inputFilename)) {
return null;
}
} catch (e) {
@ -150,7 +150,7 @@ export class KeylayoutToKmnConverter {
* @param jsonObj containing filename, behaviorand rules of a json object
* @return an ProcessedData containing all data ready to print out
*/
private convert(jsonObj: any, inputfilename: string, outputFilename?: string): ProcessedData {
private convert(jsonObj: Keylayout.KeylayoutXMLSourceFile, inputfilename: string, outputFilename?: string): ProcessedData {
// modifiers for each behavior
const modifierBehavior: string[][] = [];
@ -196,7 +196,7 @@ export class KeylayoutToKmnConverter {
* @param jsonObj: json Object containing all data read from a keylayout file
* @return an object containing the name of the input file, an array of behaviors and a populated array of Rules[]
*/
public createRuleData(dataUkelele: ProcessedData, jsonObj: any): ProcessedData {
public createRuleData(dataUkelele: ProcessedData, jsonObj: Keylayout.KeylayoutXMLSourceFile): ProcessedData {
const rules: Rule[] = [];
let dkCounterC3: number = 0;
@ -260,7 +260,7 @@ export class KeylayoutToKmnConverter {
}
else if (jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['action'] !== undefined) {
actionId = jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['action'];
actionId = jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['action'] ?? "";
// ...............................................................................................................................
// case C1: action + state none + output .........................................................................................
// C1 see: https://docs.google.com/document/d/12J3NGO6RxIthCpZDTR8FYSRjiMgXJDLwPY2z9xqKzJ0/edit?tab=t.0#heading=h.g7jwx3lx0ydd ...
@ -317,7 +317,7 @@ export class KeylayoutToKmnConverter {
this.callbacks.reportMessage(ConverterMessages.Error_UndefinedActionDetected({
inputFilename: jsonObj.keyboard['name'] + ".keylayout",
action: actionId,
KeyName: this.mapUkeleleKeycodeToVK(jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['code']),
KeyName: this.mapUkeleleKeycodeToVK(Number(jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['code'])),
keymapIndex: jsonObj.keyboard.keyMapSet[0].keyMap[i]['index']
}));
return null;
@ -325,13 +325,14 @@ export class KeylayoutToKmnConverter {
// with actionId from above loop all 'action' and search for a state(none)-next-pair ............................................................................................................
// e.g. in Block 5: find <when state="none" next="1"/> for action id a18 ......................................................................................................................
for (let l = 0; l < jsonObj.keyboard.actions.action[b1ActionIndex].when.length; l++) {
if ((jsonObj.keyboard.actions.action[b1ActionIndex].when[l]['state'] === "none") // find "none"
&& (jsonObj.keyboard.actions.action[b1ActionIndex].when[l]['next'] !== undefined)) { // find "next"
if (jsonObj.keyboard.actions?.action?.[b1ActionIndex]?.when) {
for (const when of jsonObj.keyboard.actions.action[b1ActionIndex].when) {
if ((when['state'] === "none") // find "none"
&& (when['next'] !== undefined)) { // find "next"
// Data of Block Nr 5 .....................................................................................................................................................................
// of this state(none)-next-pair get value of next (next="1") .............................................................................................................................
/* eg: 1 */ const b5ValueNext: string = jsonObj.keyboard.actions.action[b1ActionIndex].when[l]['next'];
/* eg: 1 */ const b5ValueNext: string = when['next'];
// ........................................................................................................................................................................................
@ -339,7 +340,7 @@ export class KeylayoutToKmnConverter {
// with present actionId (a18) find all keycode-behavior-pairs that use this action (a18) => (keymapIndex 0/keycode 24 and keymapIndex 3/keycode 24) ....................................
// from these create an array of modifier combinations e.g. [['','caps?'], ['Caps']] .....................................................................................................
/* eg: [['24', 0], ['24', 3]] */ const b4DeadkeyObj: KeylayoutFileData[] = this.getKeyModifierArrayFromActionID(jsonObj, actionId);
/* e.g. [['','caps?'], ['Caps']]*/ const b4DeadkeyModifierObj: string[] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b4DeadkeyObj);
/* e.g. [['','caps?'], ['Caps']]*/ const b4DeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b4DeadkeyObj);
// ........................................................................................................................................................................................
@ -353,14 +354,14 @@ export class KeylayoutToKmnConverter {
// create array[Keycode,Keyname,action id,actionIndex,output] and array[Keyname,action id,behavior,modifier,output] ......................................................................
/* eg: ['0','K_A','a9','0','â'] */ const b1KeycodeObj: KeylayoutFileData[] = this.getKeyActionOutputArrayFromActionStateOutputArray(jsonObj, b6ActionIdObj);
/* eg: ['K_A','a9','0','NCAPS','â']*/ const b1ModifierKeyObj: KeylayoutFileData[] = this.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(jsonObj, b1KeycodeObj, isCapsused);
// .......................................................................................................................................................................................
// .......................................................................................................................................................................................
for (let n1 = 0; n1 < b4DeadkeyModifierObj.length; n1++) {
for (let n2 = 0; n2 < b4DeadkeyModifierObj[n1].length; n2++) {
for (let n3 = 0; n3 < b4DeadkeyObj.length; n3++) {
for (let n4 = 0; n4 < b1ModifierKeyObj.length; n4++) {
for (let n1 = 0; n1 < b4DeadkeyModifierObj.length; n1++) {
for (let n2 = 0; n2 < b4DeadkeyModifierObj[n1].length; n2++) {
for (let n3 = 0; n3 < b4DeadkeyObj.length; n3++) {
for (let n4 = 0; n4 < b1ModifierKeyObj.length; n4++) {
ruleObj = new Rule(
ruleObj = new Rule(
/* ruleType */ "C2",
/* modifierPrevDeadkey*/ "",
@ -376,11 +377,12 @@ export class KeylayoutToKmnConverter {
/* modifierKey*/ b1ModifierKeyObj[n4].modifier,
/* key */ b1ModifierKeyObj[n4].key,
/* output */ new TextEncoder().encode(b1ModifierKeyObj[n4].outchar),
);
if ((b1ModifierKeyObj[n4].outchar !== undefined)
&& (b1ModifierKeyObj[n4].outchar !== "undefined")
&& (b1ModifierKeyObj[n4].outchar !== "")) {
rules.push(ruleObj);
);
if ((b1ModifierKeyObj[n4].outchar !== undefined)
&& (b1ModifierKeyObj[n4].outchar !== "undefined")
&& (b1ModifierKeyObj[n4].outchar !== "")) {
rules.push(ruleObj);
}
}
}
}
@ -416,7 +418,7 @@ export class KeylayoutToKmnConverter {
// with present actionId (a16) find all keycode-behavior-pairs that use this action (a16) => (keymapIndex 3/keycode 32) ....................................................................
// from these create an array of modifier combinations e.g. [ [ 'anyOption', 'Caps' ] ] .....................................................................................................
/* e.g. [['32', 3]] */ const b4DeadkeyObj: KeylayoutFileData[] = this.getKeyModifierArrayFromActionID(jsonObj, actionId);
/* e.g. [ [ 'anyOption', 'Caps' ] ]*/ const b4DeadkeyModifierObj: string[] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b4DeadkeyObj);
/* e.g. [ [ 'anyOption', 'Caps' ] ]*/ const b4DeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b4DeadkeyObj);
// ...........................................................................................................................................................................................
// Data of Block Nr 3 ........................................................................................................................................................................
@ -428,7 +430,7 @@ export class KeylayoutToKmnConverter {
// with present actionId (a17) find all key names and behaviors that use this action (a17) => (keymapIndex 3/keycode 28) ....................................................................
// from these create an array of modifier combinations e.g. [ [ 'anyOption', 'Caps' ] ] .....................................................................................................
/* eg: index=3 */ const b2PrevDeadkeyObj: KeylayoutFileData[] = this.getKeyModifierArrayFromActionID(jsonObj, b3ActionId);
/* e.g. [ [ 'anyOption', 'Caps' ] ] */ const b2PrevDeadkeyModifierObj: string[] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b2PrevDeadkeyObj);
/* e.g. [ [ 'anyOption', 'Caps' ] ] */ const b2PrevDeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b2PrevDeadkeyObj);
// ...........................................................................................................................................................................................
// Data of Block Nr 6 ........................................................................................................................................................................
// create an array[action id,state,output] from all state-output-pairs that use state = b5ValueNext (e.g. use 1 in <when state="1" output="â"/> ) .........................................
@ -486,7 +488,7 @@ export class KeylayoutToKmnConverter {
keymapIndex: jsonObj.keyboard.keyMapSet[0].keyMap[i]['index'],
output: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['output'],
key: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['code'],
KeyName: this.mapUkeleleKeycodeToVK(jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['code'])
KeyName: this.mapUkeleleKeycodeToVK(Number(jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['code']))
}));
return null;
}
@ -773,7 +775,7 @@ export class KeylayoutToKmnConverter {
];
if (!(pos >= 0 && pos <= 0x31) || (pos === null) || (pos === undefined)) {
return "";
return "" as string;
} else {
return vk[pos];
}
@ -781,11 +783,14 @@ export class KeylayoutToKmnConverter {
/**
* @brief member function to return an index for a given actionID
* @param data :any - an object containing all data read from a .keylayout file
* @param data an object containing all data read from a .keylayout file
* @param search :string - value 'id' to be found
* @return a number specifying the index of an actionId
*/
public getActionIndexFromActionId(data: any, search: string): number {
public getActionIndexFromActionId(data: Keylayout.KeylayoutXMLSourceFile, search: string): number {
if (!data.keyboard?.actions?.action) {
return -1;
}
for (let i = 0; i < data.keyboard.actions.action.length; i++) {
if (data.keyboard.actions.action[i]['id'] === search) {
return i;
@ -796,53 +801,60 @@ export class KeylayoutToKmnConverter {
/**
* @brief member function to find the actionID of a certain state-next pair
* @param data :any an object containing all data read from a .keylayout file
* @param data an object containing all data read from a .keylayout file
* @param search :string value 'next' to be found
* @return a string containing the actionId of a certain state(none)-next pair
*/
public getActionIdFromActionNext(data: any, search: string): string {
if (search !== "none") {
for (let i = 0; i < data.keyboard.actions.action.length; i++) {
for (let j = 0; j < data.keyboard.actions.action[i].when.length; j++) {
if (data.keyboard.actions.action[i].when[j]['next'] === search) {
return data.keyboard.actions.action[i]['id'];
public getActionIdFromActionNext(data: Keylayout.KeylayoutXMLSourceFile, search: string): string {
if (search !== "none" && data.keyboard?.actions?.action) {
for (const action of data.keyboard.actions.action) {
if (action.when) {
for (const when of action.when) {
if (when['next'] === search) {
return action['id'] as string;
}
}
}
}
}
return "";
return "" as string;
}
/**
* @brief member function to create an array of (modifier) behaviors for a given keycode in [{keycode,modifier}]
* @param data : any - an object containing all data read from a .keylayout file
* @param data an object containing all data read from a .keylayout file
* @param search : KeylayoutFileData[] - an array[{keycode,modifier}] to be found
* @return a string[] containing modifiers
*/
public getModifierArrayFromKeyModifierArray(data: any, search: KeylayoutFileData[]): string[] {
const returnString1D: string[] = [];
public getModifierArrayFromKeyModifierArray(data: ProcessedData["modifiers"], search: KeylayoutFileData[]): string[][] | [null] {
const returnString1D: string[][] = [];
for (let i = 0; i < search.length; i++) {
returnString1D.push(data[search[i].behavior]);
if (search[i].behavior === undefined || search[i].behavior === null) {
return [null];
}
returnString1D.push(data[Number(search[i].behavior)]);
}
return returnString1D;
}
/**
* @brief member function to find the output for a certain actionID for state 'none'
* @param data :any an object containing all data read from a .keylayout file
* @param data an object containing all data read from a .keylayout file
* @param search :string an actionId to be found
* @return a string containing the output character
*/
public getOutputFromActionIdNone(data: any, search: string): string {
public getOutputFromActionIdNone(data: Keylayout.KeylayoutXMLSourceFile, search: string): string {
let OutputValue: string = "";
for (let i = 0; i < data.keyboard.actions.action.length; i++) {
if (data.keyboard.actions.action[i]['id'] === search) {
for (let j = 0; j < data.keyboard.actions.action[i].when.length; j++) {
if (data.keyboard.actions.action[i].when[j]['state'] === "none") {
if (data.keyboard.actions.action[i].when[j]['output'] !== undefined) {
OutputValue = data.keyboard.actions.action[i].when[j]['output'];
}
if (!data.keyboard?.actions?.action) {
return OutputValue;
}
for (const action of data.keyboard.actions.action as Keylayout.KL_Action[]) {
if (action['id'] === search) {
for (const when of action.when as Keylayout.KL_When[]) {
if ((when['state'] === "none") && (when['output'] !== undefined)) {
OutputValue = when['output'];
}
}
}
@ -852,11 +864,11 @@ export class KeylayoutToKmnConverter {
/**
* @brief member function to return array of [Keycode,Keyname,actionId,actionIDIndex, output] for a given actionID in of [ actionID,state,output]
* @param data :any - an object containing all data read from a .keylayout file
* @param data an object containing all data read from a .keylayout file
* @param search :idStateOutputObject[] - array of [{ actionID,state,output }]
* @return a KeylayoutFileData[] containing [{Keycode,Keyname,actionId,actionID, output}]
*/
public getKeyActionOutputArrayFromActionStateOutputArray(data: any, search: ActionStateOutput[]): KeylayoutFileData[] {
public getKeyActionOutputArrayFromActionStateOutputArray(data: Keylayout.KeylayoutXMLSourceFile, search: ActionStateOutput[]): KeylayoutFileData[] {
if ((search === undefined) || (search === null))
return [];
@ -867,7 +879,7 @@ export class KeylayoutToKmnConverter {
for (let i = 0; i < data.keyboard.keyMapSet[0].keyMap.length; i++) {
for (let j = 0; j < data.keyboard.keyMapSet[0].keyMap[i].key.length; j++) {
if (data.keyboard.keyMapSet[0].keyMap[i].key[j]['action'] === search[k].id &&
data.keyboard.keyMapSet[0].keyMap[i].key[j]['code'] <= KeylayoutToKmnConverter.MAX_KEY_IDENTIFIER) {
Number(data.keyboard.keyMapSet[0].keyMap[i].key[j]['code']) <= KeylayoutToKmnConverter.MAX_KEY_IDENTIFIER) {
const singleDataSet = {
keyCode: data.keyboard.keyMapSet[0].keyMap[i].key[j]['code'],
key: this.mapUkeleleKeycodeToVK(Number(data.keyboard.keyMapSet[0].keyMap[i].key[j]['code'])),
@ -885,24 +897,26 @@ export class KeylayoutToKmnConverter {
/**
* @brief member function to get an array of all actionId-output pairs for a certain state
* @param data : any an object containing all data read from a .keylayout file
* @param data an object containing all data read from a .keylayout file
* @param search : string a 'state' to be found
* @return an array: idStateOutputObject[] containing all [{actionId, state, output}] for a certain state
*/
public getActionStateOutputArrayFromActionState(data: any, search: string): ActionStateOutput[] {
public getActionStateOutputArrayFromActionState(data: Keylayout.KeylayoutXMLSourceFile, search: string): ActionStateOutput[] {
const actionStateOutput: ActionStateOutput[] = [];
for (let i = 0; i < data.keyboard.actions.action.length; i++) {
for (let j = 0; j < data.keyboard.actions.action[i].when.length; j++) {
if (data.keyboard.actions.action[i].when[j]['state'] === search) {
if (data.keyboard.actions.action[i].when[j]['output'] !== undefined) {
const singleDataSet = {
id: data.keyboard.actions.action[i]['id'],
state: data.keyboard.actions.action[i].when[j]['state'],
output: data.keyboard.actions.action[i].when[j]['output']
};
actionStateOutput.push(singleDataSet);
if (search !== "none" && data.keyboard?.actions?.action) {
for (const action of data.keyboard.actions.action) {
if (action.when) {
for (const when of action.when) {
if ((when['state'] === search) && (when['output'] !== undefined)) {
const singleDataSet = {
id: action['id'],
state: when['state'],
output: when['output']
};
actionStateOutput.push(singleDataSet as ActionStateOutput);
}
}
}
}
}
@ -911,12 +925,12 @@ export class KeylayoutToKmnConverter {
/**
* @brief member function to create an 2D array of [KeyName,actionId,behavior,modifier,output]
* @param data : any an object containing all data read from a .keylayout file
* @param data an object containing all data read from a .keylayout file
* @param search : array of [{keycode,keyname,actionId,behavior,output}] to be found
* @param isCAPSused : boolean flag to indicate if CAPS is used in a keylayout file or not
* @return an array: KeylayoutFileData[] containing [{KeyName,actionId,behavior,modifier,output}]
*/
public getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(data: any, search: KeylayoutFileData[], isCAPSused: boolean): KeylayoutFileData[] {
public getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(data: Keylayout.KeylayoutXMLSourceFile, search: KeylayoutFileData[], isCAPSused: boolean): KeylayoutFileData[] {
const keyBehaviorModOutput = [];
if (!((search === undefined) || (search === null) || (search.length === 0))) {
for (let i = 0; i < search.length; i++) {
@ -945,20 +959,20 @@ export class KeylayoutToKmnConverter {
unique.push(o);
}
return unique;
}, []);
}, [] as KeylayoutFileData[]);
return uniquekeyBehaviorModOutput;
}
/**
* @brief member function to create an array of [actionID, output, behavior,keyname,modifier] for a given actionId
* @param data : any - an object containing all data read from a .keylayout file
* @param modi : any - an array of modifiers
* @param data an object containing all data read from a .keylayout file
* @param modi an array of modifiers
* @param search : string - an actionId to be found
* @param outchar : string - the output character
* @param isCAPSused : boolean - flag to indicate if CAPS is used in a keylayout file or not
* @return an array: KeylayoutFileData[] containing [{actionID,output, behavior,keyname,modifier}]
*/
public getActionOutputBehaviorKeyModiFromActionIDStateOutput(data: any, modi: string[][], search: string, outchar: string, isCapsused: boolean): KeylayoutFileData[] {
public getActionOutputBehaviorKeyModiFromActionIDStateOutput(data: Keylayout.KeylayoutXMLSourceFile, modi: string[][], search: string, outchar: string, isCapsused: boolean): KeylayoutFileData[] {
const actionOutputBehaviorKeyModi = [];
if ((!modi) || (search === "") || (search === undefined)) {
return [];
@ -967,8 +981,8 @@ export class KeylayoutToKmnConverter {
for (let i = 0; i < data.keyboard.keyMapSet[0].keyMap.length; i++) {
for (let j = 0; j < data.keyboard.keyMapSet[0].keyMap[i].key.length; j++) {
if (data.keyboard.keyMapSet[0].keyMap[i].key[j]['action'] === search) {
for (let k = 0; k < modi[data.keyboard.keyMapSet[0].keyMap[i]['index']].length; k++) {
const behaviorIdx: number = data.keyboard.keyMapSet[0].keyMap[i]['index'];
for (let k = 0; k < modi[Number(data.keyboard.keyMapSet[0].keyMap[i]['index'])].length; k++) {
const behaviorIdx: number = Number(data.keyboard.keyMapSet[0].keyMap[i]['index']);
const singleDataSet = {
outchar: outchar,
actionId: data.keyboard.keyMapSet[0].keyMap[i].key[j]['action'],
@ -996,18 +1010,18 @@ export class KeylayoutToKmnConverter {
unique.push(o);
}
return unique;
}, []);
}, [] as KeylayoutFileData[]);
return uniqueactionOutputBehaviorKey;
}
/**
* @brief member function to create an array of [{keycode,behavior}] for a given actionId
* @param data : any - an object containing all data read from a .keylayout file
* @param data an object containing all data read from a .keylayout file
* @param search : string - an actionId to be found
* @return an array: KeylayoutFileData[] containing [{keycode,behavior}]
*/
public getKeyModifierArrayFromActionID(data: any, search: string): KeylayoutFileData[] {
public getKeyModifierArrayFromActionID(data: Keylayout.KeylayoutXMLSourceFile, search: string): KeylayoutFileData[] {
const mapIndexObject1D: KeylayoutFileData[] = [];
for (let i = 0; i < data.keyboard.keyMapSet[0].keyMap.length; i++) {
for (let j = 0; j < data.keyboard.keyMapSet[0].keyMap[i].key.length; j++) {

View file

@ -4,29 +4,86 @@
* Created by S. Schmitt on 2025-07-14
*
* This DTD describes a technical preview of Keylayout Data
-->
*
* Unless otherwise specified we used TN 2056:
* https://developer.apple.com/library/archive/technotes/tn2056/_index.html#//apple_ref/doc/uid/DTS10003085-CH1-SUBSECTION7
*
* kmc-convert does not need and therefore does not process the following attributes:
* group, id, maxout,
* first, last, mapSet, modifiers,
* defaultIndex,
* baseMapSet, baseIndex,
* through, multiplier
* kmc-convert does not need and therefore does not process the following elements:
* terminators
* kmc-convert does not need and therefore does not process 'anonymous actions'
* -->
<!ELEMENT keyboard (layouts, keyMapSet) >
<!ELEMENT keyboard (modifierMap)* >
<!ATTLIST keyboard (group, id, name, maxout) #REQUIRED >
<!ELEMENT layouts ( layout)* >
<!ATTLIST layout (first, last, mapSet, modifiers) #REQUIRED >
<!-- Overall structure -->
<!-- from TN 2056:
<!ELEMENT keyboard (layouts+, modifierMap+, keyMapSet+, actions*, terminators*)>
but kmc-convert uses instead: -->
<!ELEMENT keyboard layouts>
<!ELEMENT keyboard modifierMap+>
<!ELEMENT keyboard keyMapSet+>
<!ELEMENT keyboard actions*>
<!ELEMENT keyboard terminators*>
<!ELEMENT modifierMap (keyMapSelect)*>
<!ATTLIST modifierMap (id, defaultIndex) #REQUIRED >
<!ATTLIST keyboard group NMTOKEN #REQUIRED >
<!ATTLIST keyboard id NMTOKEN #REQUIRED >
<!ATTLIST keyboard name CDATA #REQUIRED >
<!ATTLIST keyboard maxout NMTOKEN #IMPLIED >
<!ELEMENT keyMapSelect (modifier)*>
<!ATTLIST keyMapSelect (mapIndex) #REQUIRED >
<!-- Hardware layout elements -->
<!ELEMENT layouts (layout+) >
<!ELEMENT layout EMPTY >
<!ATTLIST layout first NMTOKEN #REQUIRED >
<!ATTLIST layout last NMTOKEN #REQUIRED >
<!ATTLIST layout mapSet IDREF #REQUIRED >
<!ATTLIST layout modifiers IDREF #REQUIRED >
<!ATTLIST modifier (keys) #REQUIRED >
<!-- Modifier descriptions -->
<!ELEMENT modifierMap (keyMapSelect+) >
<!ATTLIST modifierMap id ID #REQUIRED >
<!ATTLIST modifierMap defaultIndex NMTOKEN #REQUIRED >
<!ELEMENT keyMapSet ( keyMap)* >
<!ATTLIST keyMapSet (id) #REQUIRED >
<!ELEMENT keyMapSelect (modifier+) >
<!ATTLIST keyMapSelect mapIndex NMTOKEN #REQUIRED >
<!ELEMENT keyMap (key)* >
<!ATTLIST keyMap (index) #REQUIRED >
<!ELEMENT modifier EMPTY >
<!ATTLIST modifier keys CDATA #REQUIRED >
<!ATTLIST key (code) #REQUIRED >
<!-- Keyboard mapping most keyboards use ANSI only but some use JIS also -->
<!ELEMENT keyMapSet (keyMap+) >
<!ATTLIST keyMapSet id ID #REQUIRED >
<!ELEMENT keyMap (key+) >
<!ATTLIST keyMap index NMTOKEN #REQUIRED >
<!ATTLIST keyMap baseMapSet IDREF #IMPLIED >
<!ATTLIST keyMap baseIndex NMTOKEN #IMPLIED >
<!ELEMENT key (action*) >
<!ATTLIST key code NMTOKEN #REQUIRED >
<!-- from TN 2056:
<!ATTLIST key output CDATA #IMPLIED >
<!ATTLIST key action IDREF #IMPLIED >
since output or action needs to be present kmc-convert uses instead: -->
<!ATTLIST key (action #REQUIRED output #IMPLIED | output #REQUIRED action #IMPLIED)>
<!-- Actions (state records) -->
<!ELEMENT actions (action+) >
<!ELEMENT action (when+) >
<!ATTLIST action id ID #IMPLIED >
<!ELEMENT when EMPTY >
<!ATTLIST when state NMTOKEN #REQUIRED >
<!ATTLIST when through NMTOKEN #IMPLIED >
<!ATTLIST when output CDATA #IMPLIED >
<!ATTLIST when multiplier NMTOKEN #IMPLIED >
<!ATTLIST when next NMTOKEN #IMPLIED >
<!-- Terminators -->
<!-- kmc-convert does not use terminators -->
<!ELEMENT terminators (when+)>

View file

@ -186,24 +186,6 @@ describe('KeylayoutToKmnConverter', function () {
assert.isNull(convertedEmpty);
});
it('should return empty on only modifiers as input', async function () {
const convertedMod = sut.convertBound.convert({
keylayoutFilename: '',
modifiers: [['caps'], ['Shift'], ['command']],
rules: []
}, '');
assert.isNull(convertedMod);
});
it('should return empty on only rules as input', async function () {
const convertedRule = sut.convertBound.convert({
keylayoutFilename: '',
modifiers: [],
rules: [['C0', '', '', 0, 0, '', '', 0, 0, 'CAPS', 'K_A', 'A']]
}, '');
assert.isNull(convertedRule);
});
it('should return empty array of rules on null input', async function () {
const convertedRule = sut.convertBound.convert(null, 'ABC.kmn');
assert.isNull(convertedRule);
@ -350,13 +332,14 @@ describe('KeylayoutToKmnConverter', function () {
[[{ key: '999', behavior: null }], [null]],
[[{ key: '0', behavior: -999 }], [null]],
[[{ key: '0', behavior: null }], [null]],
[[{ key: '0', behavior: undefined }], [null]],
[[], []],
].forEach(function (values) {
it((values[1] !== null) ?
("getModifierArrayFromKeyModifierArray('" + JSON.stringify(values[0]) + "')").padEnd(68, " ") + " should return '" + JSON.stringify(values[1]) + "'" :
("getModifierArrayFromKeyModifierArray('" + JSON.stringify(values[0]) + "')").padEnd(68, " ") + " should return '" + "null" + "'", async function () {
const result = sut.getModifierArrayFromKeyModifierArray(converted.modifiers, values[0] as KeylayoutFileData[]);
const result = sut.getModifierArrayFromKeyModifierArray(converted.modifiers, values[0] as unknown as KeylayoutFileData[]);
assert.deepStrictEqual(JSON.stringify(result), JSON.stringify(values[1]));
});
});

View file

@ -74,7 +74,7 @@ describe('KmnFileWriter', function () {
["&#x1234;", 'ሴ'],
["&#x1F60E;", '😎'],
["&#x0002;", '\u0002'],
["&#x1000000;",undefined ],
["&#x1000000;", undefined],
["&#97;", 'a'],
["&#4660;", 'ሴ'],
["&#128518;", '😆'],
@ -148,9 +148,9 @@ describe('KmnFileWriter', function () {
['c WARNING: unavailable modifier : here: '],
['c WARNING: unavailable superior rule ( [Y K_Y] > dk(B0) ) : here: ']],
].forEach(function (values: any, index: number) {
it(('rule " ' + values[0][0].ruleType + ' "') + 'should create "' + values[1] + ' | ' + values[2] + ' | ' + values[3] + '"', async function () {
const result: string[] = sutW.reviewRules(values[0], 0);
].forEach(function (values: (string[] | Rule[])[], index: number) {
it(('rule " ' + (values[0][0] as Rule).ruleType as string + ' "') + 'should create "' + values[1] + ' | ' + values[2] + ' | ' + values[3] + '"', async function () {
const result: string[] = sutW.reviewRules(values[0] as Rule[], 0);
assert.equal(result[0], values[1][0]);
assert.equal(result[1], values[2][0]);
assert.equal(result[2], values[3][0]);
@ -313,9 +313,9 @@ describe('KmnFileWriter', function () {
[''],
["c WARNING: duplicate rule: earlier: [CAPS K_C] > 'X' here: "]],
].forEach(function (values: any, index: number) {
it('rule ' + values[0][0].ruleType + ' should create " ' + ' "' + values[1] + ' | ' + values[2] + ' | ' + values[3] + '"', async function () {
const result: string[] = sutW.reviewRules(values[0], 1);
].forEach(function (values: (string[] | Rule[])[], index: number) {
it('rule ' + (values[0][0] as Rule).ruleType as string + ' should create " ' + ' "' + values[1] + ' | ' + values[2] + ' | ' + values[3] + '"', async function () {
const result: string[] = sutW.reviewRules(values[0] as Rule[], 1);
assert.equal(result[0], values[1][0]);
assert.equal(result[1], values[2][0]);
assert.equal(result[2], values[3][0]);
@ -333,9 +333,9 @@ describe('KmnFileWriter', function () {
[''],
[''],
["c WARNING: ambiguous rule: later: [RALT K_B] > dk(A0) ambiguous rule: earlier: [RALT K_B] > 'X' here: PLEASE CHECK THE FOLLOWING RULE AS IT WILL NOT BE WRITTEN ! "]],
].forEach(function (values: any, index: number) {
it(('rule ' + values[0][0].ruleType + ' should create " ' + ' "') + values[1] + ' | ' + values[2] + ' | ' + values[3] + '"', async function () {
const result: string[] = sutW.reviewRules(values[0], 2);
].forEach(function (values: (string[] | Rule[])[], index: number) {
it(('rule ' + (values[0][0]as Rule).ruleType as string + ' should create " ' + ' "') + values[1] + ' | ' + values[2] + ' | ' + values[3] + '"', async function () {
const result: string[] = sutW.reviewRules(values[0]as Rule[], 2);
assert.equal(result[0], values[1][0]);
assert.equal(result[1], values[2][0]);
assert.equal(result[2], values[3][0]);
@ -432,13 +432,13 @@ describe('KmnFileWriter', function () {
"dk(B2) + [NCAPS RALT K_A] > 'â'\n\n"
]
],
].forEach(function (values: any) {
].forEach(function (values: (string[] | Rule[])[], index: number) {
it(('an array of Rules should create a set of kmn rules '), async function () {
const data: ProcessedData = {
keylayoutFilename: "",
kmnFilename: "",
modifiers: [[]],
rules: values[0]
rules: values[0] as Rule[]
};
const result1 = sutW.writeDataRules(data);
assert.isTrue(result1 === values[1][0]);

View file

@ -8,7 +8,10 @@
<xs:complexType>
<xs:sequence>
<xs:element name="layouts">
<!-- in TN 2056, the 'layouts' element is defined as maxOccurs="unbounded",
but in practice, only one 'layouts' element is used per .keylayout file.
<xs:element name="layouts" minOccurs="1" maxOccurs="unbounded"> -->
<xs:element name="layouts" minOccurs="1" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="layout" minOccurs="1" maxOccurs="unbounded">
@ -63,7 +66,7 @@
</xs:complexType>
</xs:element>
<xs:element name="actions">
<xs:element name="actions" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="action" minOccurs="1" maxOccurs="unbounded">
@ -83,7 +86,7 @@
</xs:complexType>
</xs:element>
<xs:element name="terminators">
<xs:element name="terminators" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="when" minOccurs="1" maxOccurs="unbounded">