Merge pull request #7252 from keymanapp/chore/developer/7235-kmc-sort-meta-and-strs

refactor(developer): kmc - encapsulate strs and elem items for sort 🙀
This commit is contained in:
Marc Durdin 2022-09-11 11:35:27 +10:00 committed by GitHub
commit ef86410562
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 337 additions and 280 deletions

View file

@ -1,6 +1,5 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { Bksp, BkspItem, BkspItemFlags } from "../kmx/kmx-plus";
import { ElementString } from "../kmx/element-string";
import { Bksp, BkspItem, BkspItemFlags, GlobalSections } from "../kmx/kmx-plus";
import { LKBackspace, LKBackspaces } from "../ldml-keyboard/ldml-keyboard-xml";
import { SectionCompiler } from "./section-compiler";
@ -17,28 +16,28 @@ export class BkspCompiler extends SectionCompiler {
return valid;
}
private compileBackspace(backspace: LKBackspace): BkspItem {
private compileBackspace(sections: GlobalSections, backspace: LKBackspace): BkspItem {
let result = new BkspItem();
result.from = new ElementString(backspace.from);
result.to = backspace.to;
result.before = new ElementString(backspace.before);
result.from = sections.elem.allocElementString(sections.strs, backspace.from);
result.to = sections.strs.allocString(backspace.to);
result.before = sections.elem.allocElementString(sections.strs, backspace.before);
result.flags = backspace.error == 'fail' ? BkspItemFlags.error : BkspItemFlags.none;
return result;
}
private compileBackspaces(backspaces: LKBackspaces): Bksp {
private compileBackspaces(sections: GlobalSections, backspaces: LKBackspaces): Bksp {
let result = new Bksp();
if(backspaces?.backspace) {
for(let backspace of backspaces.backspace) {
result.items.push(this.compileBackspace(backspace));
result.items.push(this.compileBackspace(sections, backspace));
}
}
return result;
}
public compile(): Bksp {
return this.compileBackspaces(this.keyboard.backspaces);
public compile(sections: GlobalSections): Bksp {
return this.compileBackspaces(sections, this.keyboard.backspaces);
}
}

View file

@ -1,4 +1,4 @@
import KMXPlusFile from '../kmx/kmx-plus';
import KMXPlusFile, { Elem, Strs } from '../kmx/kmx-plus';
import LDMLKeyboardXMLSourceFile from '../ldml-keyboard/ldml-keyboard-xml';
import LDMLKeyboardXMLSourceFileReader from '../ldml-keyboard/ldml-keyboard-xml-reader';
import { BkspCompiler } from './bksp';
@ -65,20 +65,26 @@ export default class Compiler {
public compile(source: LDMLKeyboardXMLSourceFile): KMXPlusFile {
const sections = this.buildSections(source);
let passed = true;
const kmx = new KMXPlusFile();
// These two sections are required by other sections
kmx.kmxplus.strs = new Strs();
kmx.kmxplus.elem = new Elem(kmx.kmxplus.strs);
for(let section of sections) {
if(!section.validate()) {
//console.log(`failed to validate ${section.id}`);
passed = false;
// We'll keep validating other sections anyway, so we get a full set of
// errors for the keyboard developer.
continue;
}
const sect = section.compile();
const sect = section.compile({strs: kmx.kmxplus.strs, elem: kmx.kmxplus.elem});
/* istanbul ignore if */
if(!sect) {
// This should not really happen -- validate() should be telling us
// if something is going to fail to compiler
//console.log(`failed to compile ${section.id}`);
passed = false;
continue;
}

View file

@ -1,5 +1,5 @@
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { Keys } from '../kmx/kmx-plus';
import { GlobalSections, Keys } from '../kmx/kmx-plus';
import * as LDMLKeyboard from '../ldml-keyboard/ldml-keyboard-xml';
import { USVirtualKeyMap } from "../ldml-keyboard/virtual-key-constants";
import { CompilerMessages } from './messages';
@ -12,12 +12,12 @@ export class KeysCompiler extends SectionCompiler {
return constants.section.keys;
}
public compile(): Keys {
public compile(sections: GlobalSections): Keys {
// Use LayerMap + keys to generate compiled keys for hardware
if(this.keyboard.layerMaps?.[0]?.form == 'hardware') {
for(let layer of this.keyboard.layerMaps[0].layerMap) {
let sect = this.compileHardwareLayer(layer);
let sect = this.compileHardwareLayer(sections, layer);
return sect;
}
}
@ -28,6 +28,7 @@ export class KeysCompiler extends SectionCompiler {
}
private compileHardwareLayer(
sections: GlobalSections,
layer: LDMLKeyboard.LKLayerMap
): Keys {
let result = new Keys();
@ -58,7 +59,7 @@ export class KeysCompiler extends SectionCompiler {
result.keys.push({
vkey: USVirtualKeyMap[y][x],
mod: mod,
to: keydef.to,
to: sections.strs.allocString(keydef.to),
flags: 0 // Note: 'expand' is never set here, only by the .kmx builder
});
}

View file

@ -1,5 +1,5 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { Loca } from "../kmx/kmx-plus";
import { GlobalSections, Loca } from "../kmx/kmx-plus";
import { SectionCompiler } from "./section-compiler";
import { CompilerMessages } from "./messages";
import { LKKeyboard } from "../ldml-keyboard/ldml-keyboard-xml";
@ -37,7 +37,7 @@ export class LocaCompiler extends SectionCompiler {
return valid;
}
public compile(): Loca {
public compile(sections: GlobalSections): Loca {
let result = new Loca();
// This also minimizes locales according to Remove Likely Subtags algorithm:
@ -54,7 +54,8 @@ export class LocaCompiler extends SectionCompiler {
// TODO: remove `as any` cast: (Intl as any): ts lib version we have doesn't
// yet include `getCanonicalLocales` but node 16 does include it so we can
// safely use it. Also well supported in modern browsers.
result.locales = (Intl as any).getCanonicalLocales(locales);
const canonicalLocales = (Intl as any).getCanonicalLocales(locales) as string[];
result.locales = canonicalLocales.map(locale => sections.strs.allocString(locale));
if(result.locales.length < locales.length) {
this.callbacks.reportMessage(CompilerMessages.Hint_OneOrMoreRepeatedLocales());

View file

@ -1,5 +1,5 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { KeyboardSettings, Meta, Meta_NormalizationForm } from "../kmx/kmx-plus";
import { GlobalSections, KeyboardSettings, Meta, Meta_NormalizationForm } from "../kmx/kmx-plus";
import { isValidEnumValue } from "../util/util";
import { CompilerMessages } from "./messages";
import { SectionCompiler } from "./section-compiler";
@ -41,14 +41,14 @@ export class MetaCompiler extends SectionCompiler {
return true;
}
public compile(): Meta {
public compile(sections: GlobalSections): Meta {
let result = new Meta();
result.author = this.keyboard.info?.author;
result.conform = this.keyboard.conformsTo;
result.layout = this.keyboard.info?.layout;
result.normalization = this.keyboard.info?.normalization as Meta_NormalizationForm;
result.indicator = this.keyboard.info?.indicator;
result.version = this.keyboard.version?.number ?? "0";
result.author = sections.strs.allocString(this.keyboard.info?.author);
result.conform = sections.strs.allocString(this.keyboard.conformsTo);
result.layout = sections.strs.allocString(this.keyboard.info?.layout);
result.normalization = sections.strs.allocString(this.keyboard.info?.normalization);
result.indicator = sections.strs.allocString(this.keyboard.info?.indicator);
result.version = sections.strs.allocString(this.keyboard.version?.number ?? "0");
result.settings =
(this.keyboard.settings?.fallback == "omit" ? KeyboardSettings.fallback : 0) |
(this.keyboard.settings?.transformFailure == "omit" ? KeyboardSettings.transformFailure : 0) |

View file

@ -1,5 +1,5 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { Name } from "../kmx/kmx-plus";
import { GlobalSections, Name } from "../kmx/kmx-plus";
import { SectionCompiler } from "./section-compiler";
export class NameCompiler extends SectionCompiler {
@ -14,9 +14,9 @@ export class NameCompiler extends SectionCompiler {
return valid;
}
public compile(): Name {
public compile(sections: GlobalSections): Name {
let result = new Name();
result.names = this.keyboard.names?.name?.map(v => v.value) ?? [];
result.names = this.keyboard.names?.name?.map(v => sections.strs.allocString(v.value)) ?? [];
return result;
}
}

View file

@ -1,6 +1,5 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { Ordr, OrdrItem } from "../kmx/kmx-plus";
import { ElementString } from "../kmx/element-string";
import { GlobalSections, Ordr, OrdrItem } from "../kmx/kmx-plus";
import { LKReorder, LKReorders } from "../ldml-keyboard/ldml-keyboard-xml";
import { SectionCompiler } from "./section-compiler";
@ -17,26 +16,26 @@ export class OrdrCompiler extends SectionCompiler {
return valid;
}
private compileReorder(reorder: LKReorder): OrdrItem {
private compileReorder(sections: GlobalSections, reorder: LKReorder): OrdrItem {
let result = new OrdrItem();
result.elements = new ElementString(reorder.from, reorder.order, reorder.tertiary, reorder.tertiary_base, reorder.prebase);
result.before = new ElementString(reorder.before);
result.elements = sections.elem.allocElementString(sections.strs, reorder.from, reorder.order, reorder.tertiary, reorder.tertiary_base, reorder.prebase);
result.before = sections.elem.allocElementString(sections.strs, reorder.before);
return result;
}
private compileReorders(reorders: LKReorders): Ordr {
private compileReorders(sections: GlobalSections, reorders: LKReorders): Ordr {
let result = new Ordr();
if(reorders?.reorder) {
for(let reorder of reorders.reorder) {
result.items.push(this.compileReorder(reorder));
result.items.push(this.compileReorder(sections, reorder));
}
}
return result;
}
public compile(): Ordr {
return this.compileReorders(this.keyboard.reorders);
public compile(sections: GlobalSections): Ordr {
return this.compileReorders(sections, this.keyboard.reorders);
}
}

View file

@ -1,4 +1,4 @@
import { Section } from "../kmx/kmx-plus";
import { GlobalSections, Section } from "../kmx/kmx-plus";
import LDMLKeyboardXMLSourceFile, { LKKeyboard } from "../ldml-keyboard/ldml-keyboard-xml";
import CompilerCallbacks from "./callbacks";
import { SectionIdent } from '@keymanapp/ldml-keyboard-constants';
@ -12,15 +12,18 @@ export class SectionCompiler {
this.callbacks = callbacks;
}
/* istanbul ignore next */
public get id(): SectionIdent {
return null;
}
/* istanbul ignore next */
public get required(): boolean {
return true;
}
public compile(): Section {
/* istanbul ignore next */
public compile(sections: GlobalSections): Section {
return null;
}

View file

@ -1,6 +1,5 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { Finl, FinlItem, Tran, TranItem, TranItemFlags } from "../kmx/kmx-plus";
import { ElementString } from "../kmx/element-string";
import { Finl, FinlItem, GlobalSections, Tran, TranItem, TranItemFlags } from "../kmx/kmx-plus";
import LDMLKeyboardXMLSourceFile, { LKTransform, LKTransforms } from "../ldml-keyboard/ldml-keyboard-xml";
import CompilerCallbacks from "./callbacks";
import { SectionCompiler } from "./section-compiler";
@ -30,31 +29,31 @@ class TransformCompiler<T extends TransformCompilerType, TranBase extends Tran,
return null;
}
private compileTransform(transform: LKTransform): TranItemBase {
private compileTransform(sections: GlobalSections, transform: LKTransform): TranItemBase {
let result = this.newTranItem();
result.from = new ElementString(transform.from);
result.to = transform.to;
result.before = new ElementString(transform.before);
result.from = sections.elem.allocElementString(sections.strs, transform.from);
result.to = sections.strs.allocString(transform.to);
result.before = sections.elem.allocElementString(sections.strs, transform.before);
result.flags = transform.error == 'fail' ? TranItemFlags.error : TranItemFlags.none;
return result;
}
private compileTransforms(transforms: LKTransforms): TranBase {
private compileTransforms(sections: GlobalSections, transforms: LKTransforms): TranBase {
let result = this.newTran();
if(transforms?.transform) {
for(let transform of transforms.transform) {
result.items.push(this.compileTransform(transform));
result.items.push(this.compileTransform(sections, transform));
}
}
return result;
}
public compile(): TranBase {
public compile(sections: GlobalSections): TranBase {
for(let t of this.keyboard.transforms) {
if(t.type == this.type) {
return this.compileTransforms(t);
return this.compileTransforms(sections, t);
}
}
return this.newTran();

View file

@ -1,4 +1,5 @@
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { Strs, StrsItem } from './kmx-plus';
export enum ElemElementFlags {
none = 0,
@ -9,7 +10,7 @@ export enum ElemElementFlags {
;
export class ElemElement {
value: string; // UnicodeSet or UCS32LE character
value: StrsItem; // UnicodeSet or UCS32LE character
order: number; // -128 to +127; used only by reorder element values
tertiary: number; // -128 to +127; used only by reorder element values
flags: ElemElementFlags;
@ -23,7 +24,7 @@ export class ElemElement {
;
export class ElementString extends Array<ElemElement> {
constructor(source: string, order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) {
constructor(strs: Strs, source: string, order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) {
super();
//TODO-LDML: full UnicodeSet and parsing
if(!source) {
@ -54,7 +55,7 @@ export class ElementString extends Array<ElemElement> {
for(let i = 0; i < items.length; i++) {
let elem = new ElemElement();
elem.value = items[i];
elem.value = strs.allocString(items[i]);
elem.order = orders.length ? parseInt(orders[i], 10) : 0;
elem.tertiary = tertiaries.length ? parseInt(tertiaries[i], 10) : 0;
elem.flags = ElemElementFlags.none |

View file

@ -1,6 +1,7 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { ElementString } from "../element-string";
import { alloc_string, BUILDER_STRS } from "./build-strs";
import { Elem } from "../kmx-plus";
import { build_strs_index, BUILDER_STRS } from "./build-strs";
import { BUILDER_SECTION } from "./builder-section";
/* ------------------------------------------------------------------
@ -10,6 +11,7 @@ import { BUILDER_SECTION } from "./builder-section";
interface BUILDER_ELEM_ELEMENT {
element: number; // str | UTF-32 char
flags: number;
_value: string;
};
interface BUILDER_ELEM_STRING {
@ -28,68 +30,71 @@ export interface BUILDER_ELEM extends BUILDER_SECTION {
strings: BUILDER_ELEM_STRING[];
};
export function build_elem(): BUILDER_ELEM {
return {
function binaryElemCompare(a: BUILDER_ELEM_STRING, b: BUILDER_ELEM_STRING): number {
for(let i = 0; i < a.items.length && i < b.items.length; i++) {
if(a.items[i]._value < b.items[i]._value) return -1;
if(a.items[i]._value > b.items[i]._value) return 1;
if(a.items[i].flags < b.items[i].flags) return -1;
if(a.items[i].flags > b.items[i].flags) return 1;
}
if(a.items.length < b.items.length) return -1;
if(a.items.length > b.items.length) return 1;
return 0;
}
export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS): BUILDER_ELEM {
let result: BUILDER_ELEM = {
ident: constants.hex_section_id(constants.section.elem),
size: 0, // finalized later
size: 0, // finalized below
_offset: 0,
count: 0, // finalized later
count: source_elem.strings.length,
reserved: 0,
strings: [], // finalized later
strings: [], // finalized below
};
}
/**
*
* @param sect_elem
* @returns false if the section should be omitted from the file
*/
export function finalize_elem(sect_elem: BUILDER_ELEM): boolean {
if(sect_elem.strings.length == 1) {
// If we have only the 'null' element string, then we don't include the
// section at all.
return false;
}
sect_elem.count = sect_elem.strings.length;
let offset = constants.length_elem + constants.length_elem_item * sect_elem.count;
// TODO: consider padding
for(let string of sect_elem.strings) {
string.offset = offset;
offset += string.length * constants.length_elem_item_element;
}
sect_elem.size = offset;
return true;
}
result.strings = source_elem.strings.map(item => {
let res: BUILDER_ELEM_STRING = {
offset: 0, // finalized below
length: item.length,
items: [],
_value: item
};
export function alloc_element_string(sect_strs: BUILDER_STRS, sect_elem: BUILDER_ELEM, value: ElementString) {
if(value === undefined || value === null) {
// the "null" element string
value = new ElementString('');
}
const idx = sect_elem.strings.findIndex(v => value.isEqual(v._value));
if(idx >= 0) {
return idx;
}
let str: BUILDER_ELEM_STRING = {
_value: value,
items: [],
length: value.length,
offset: 0 // will be filled in later
};
for(let v of value) {
str.items.push({
// TODO: support UTF-32 char (!UnicodeSet)
// TODO: support UnicodeSet properly
element: alloc_string(sect_strs, v.value),
flags: constants.elem_flags_unicode_set |
v.flags | //
((v.order ?? 0) << constants.elem_flags_order_bitshift) | // -128 to +127; used only by reorder element values
((v.tertiary ?? 0) << constants.elem_flags_tertiary_bitshift) // -128 to +127; used only by reorder element values
res.items = item.map(v => {
return {
element: build_strs_index(sect_strs, v.value),
flags: constants.elem_flags_unicode_set |
v.flags | //
((v.order ?? 0) << constants.elem_flags_order_bitshift) | // -128 to +127; used only by reorder element values
((v.tertiary ?? 0) << constants.elem_flags_tertiary_bitshift), // -128 to +127; used only by reorder element values
_value: v.value.value
};
});
return res;
});
result.strings.sort((a,b) => binaryElemCompare(a, b));
/* Calculate offsets and total size */
let offset = constants.length_elem + constants.length_elem_item * result.count;
for(let item of result.strings) {
item.offset = offset;
offset += item.length * constants.length_elem_item_element;
}
return sect_elem.strings.push(str) - 1;
result.size = offset;
return result;
}
export function build_elem_index(sect_elem: BUILDER_ELEM, value: ElementString) {
if(!(value instanceof ElementString)) {
throw new Error('unexpected value '+value);
}
const result = sect_elem.strings.findIndex(v => value.isEqual(v._value));
if(result < 0) {
throw new Error('unexpectedly missing StrsItem '+value);
}
return result;
}

View file

@ -1,7 +1,7 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { KeyFlags, KMXPlusData } from "../kmx-plus";
import { alloc_string, BUILDER_STRS } from "./build-strs";
import { build_strs_index, BUILDER_STRS } from "./build-strs";
import { BUILDER_SECTION } from "./builder-section";
/* ------------------------------------------------------------------
@ -45,7 +45,7 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD
vkey: item.vkey,
mod: item.mod,
// todo: support 'extend'
to: alloc_string(sect_strs, item.to),
to: build_strs_index(sect_strs, item.to),
flags: KeyFlags.extend // todo: support non-extended
});
}

View file

@ -5,7 +5,7 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { KMXPlusData } from "../kmx-plus";
import { alloc_string, BUILDER_STRS } from "./build-strs";
import { build_strs_index, BUILDER_STRS } from "./build-strs";
import { BUILDER_SECTION } from "./builder-section";
/**
@ -28,7 +28,7 @@ export function build_loca(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD
};
for(let item of kmxplus.loca.locales) {
loca.items.push(alloc_string(sect_strs, item));
loca.items.push(build_strs_index(sect_strs, item));
}
return loca;

View file

@ -5,7 +5,7 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { KMXPlusData } from "../kmx-plus";
import { alloc_string, BUILDER_STRS } from "./build-strs";
import { build_strs_index, BUILDER_STRS } from "./build-strs";
import { BUILDER_SECTION } from "./builder-section";
/**
@ -26,12 +26,12 @@ export function build_meta(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD
ident: constants.hex_section_id(constants.section.meta),
size: constants.length_meta,
_offset: 0,
author: alloc_string(sect_strs, kmxplus.meta.author),
conform: alloc_string(sect_strs, kmxplus.meta.conform),
layout: alloc_string(sect_strs, kmxplus.meta.layout),
normalization: alloc_string(sect_strs, kmxplus.meta.normalization),
indicator: alloc_string(sect_strs, kmxplus.meta.indicator),
version: alloc_string(sect_strs, kmxplus.meta.version),
author: build_strs_index(sect_strs, kmxplus.meta.author),
conform: build_strs_index(sect_strs, kmxplus.meta.conform),
layout: build_strs_index(sect_strs, kmxplus.meta.layout),
normalization: build_strs_index(sect_strs, kmxplus.meta.normalization),
indicator: build_strs_index(sect_strs, kmxplus.meta.indicator),
version: build_strs_index(sect_strs, kmxplus.meta.version),
settings: kmxplus.meta.settings ?? 0,
};
}

View file

@ -5,7 +5,7 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { KMXPlusData } from "../kmx-plus";
import { alloc_string, BUILDER_STRS } from "./build-strs";
import { build_strs_index, BUILDER_STRS } from "./build-strs";
import { BUILDER_SECTION } from "./builder-section";
/**
@ -32,7 +32,7 @@ export function build_name(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD
};
for(let item of kmxplus.name.names) {
name.items.push(alloc_string(sect_strs, item));
name.items.push(build_strs_index(sect_strs, item));
}
return name;

View file

@ -1,6 +1,6 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { KMXPlusData } from "../kmx-plus";
import { alloc_element_string, BUILDER_ELEM } from "./build-elem";
import { build_elem_index, BUILDER_ELEM } from "./build-elem";
import { BUILDER_STRS } from "./build-strs";
import { BUILDER_SECTION } from "./builder-section";
@ -38,8 +38,8 @@ export function build_ordr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_e
for(let item of kmxplus.ordr.items) {
ordr.items.push({
elements: alloc_element_string(sect_strs, sect_elem, item.elements),
before: alloc_element_string(sect_strs, sect_elem, item.before)
elements: build_elem_index(sect_elem, item.elements),
before: build_elem_index(sect_elem, item.before)
});
}

View file

@ -1,4 +1,5 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { Strs, StrsItem } from "../kmx-plus";
import { BUILDER_SECTION } from "./builder-section";
/* ------------------------------------------------------------------
@ -22,47 +23,48 @@ export interface BUILDER_STRS extends BUILDER_SECTION {
items: BUILDER_STRS_ITEM[];
};
export function build_strs(): BUILDER_STRS {
return {
function binaryStringCompare(a: string, b: string): number {
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islessthan
if(typeof a != 'string' || typeof b != 'string') {
throw new Error('binaryStringCompare: inputs must be strings');
}
if(a < b) return -1;
if(a > b) return 1;
return 0;
}
export function build_strs(source_strs: Strs): BUILDER_STRS {
let result: BUILDER_STRS = {
ident: constants.hex_section_id(constants.section.strs),
size: 0, // finalized later
_offset: 0,
count: 0, // finalized later
count: source_strs.strings.length,
reserved: 0,
items: [], // finalized later
items: [], // filled below
};
}
export function finalize_strs(sect_strs: BUILDER_STRS): void {
sect_strs.count = sect_strs.items.length;
let offset = constants.length_strs + constants.length_strs_item * sect_strs.count;
result.items = source_strs.strings.map(item => { return {_value: item.value, length: item.value.length, offset: 0}; });
result.items.sort((a,b) => binaryStringCompare(a._value, b._value));
let offset = constants.length_strs + constants.length_strs_item * result.count;
// TODO: consider padding
for(let item of sect_strs.items) {
for(let item of result.items) {
item.offset = offset;
offset += item.length * 2 + 2; /* UTF-16 code units + sizeof null terminator */
}
sect_strs.size = offset;
result.size = offset;
return result;
}
export function alloc_string(sect_strs: BUILDER_STRS, value: string) {
if(typeof value != 'string') {
if(value === undefined || value === null) {
value = '';
} else {
throw 'unexpected value '+value;
}
export function build_strs_index(sect_strs: BUILDER_STRS, value: StrsItem) {
if(!(value instanceof StrsItem)) {
throw new Error('unexpected value '+ value);
}
let idx = sect_strs.items.findIndex(v => v._value === value);
if(idx >= 0) {
return idx;
let result = sect_strs.items.findIndex(v => v._value === value.value);
if(result < 0) {
throw new Error('unexpectedly missing StrsItem '+value.value);
}
let item: BUILDER_STRS_ITEM = {
_value: value,
length: value.length,
offset: 0 // will be filled in later
};
return sect_strs.items.push(item) - 1;
return result;
}

View file

@ -1,8 +1,8 @@
import { constants } from "@keymanapp/ldml-keyboard-constants";
import { Bksp, Finl, Tran } from "../kmx-plus";
import { alloc_element_string, BUILDER_ELEM } from "./build-elem";
import { alloc_string, BUILDER_STRS } from "./build-strs";
import { build_elem_index, BUILDER_ELEM } from "./build-elem";
import { build_strs_index, BUILDER_STRS } from "./build-strs";
import { BUILDER_SECTION } from "./builder-section";
/* ------------------------------------------------------------------
@ -41,9 +41,9 @@ export function build_tran(source_tran: Tran|Finl|Bksp, sect_strs: BUILDER_STRS,
for(let item of source_tran.items) {
tran.items.push({
from: alloc_element_string(sect_strs, sect_elem, item.from),
to: alloc_string(sect_strs, item.to),
before: alloc_element_string(sect_strs, sect_elem, item.before),
from: build_elem_index(sect_elem, item.from),
to: build_strs_index(sect_strs, item.to),
before: build_elem_index(sect_elem, item.before),
flags: item.flags
});
}

View file

@ -7,10 +7,10 @@ import { BUILDER_KEYS, build_keys } from './build-keys';
import { BUILDER_LOCA, build_loca } from './build-loca';
import { BUILDER_META, build_meta } from './build-meta';
import { BUILDER_NAME, build_name } from './build-name';
import { alloc_string, BUILDER_STRS, build_strs, finalize_strs } from './build-strs';
import { BUILDER_STRS, build_strs } from './build-strs';
import { BUILDER_VKEY, build_vkey } from './build-vkey';
import { BUILDER_TRAN, build_tran } from './build-tran';
import { alloc_element_string, BUILDER_ELEM, build_elem, finalize_elem } from './build-elem';
import { BUILDER_ELEM, build_elem } from './build-elem';
import { BUILDER_ORDR, build_ordr } from './build-ordr';
type BUILDER_BKSP = BUILDER_TRAN;
@ -63,19 +63,10 @@ export default class KMXPlusBuilder {
private build() {
// Required sections: sect, strs, loca, meta
this.sect_sect = build_sect();
// We must prepare the strs section early so that other sections can
// reference it. However, it will be emitted in alpha order.
this.sect_strs = build_strs();
// per C7043, the first string in sect_strs MUST be the zero-length string.
alloc_string(this.sect_strs, '');
// We must prepare the elem section early so that other sections can
// reference it. However, it will be emitted in alpha order.
this.sect_elem = build_elem();
// per C7043, the first element string in sect_elem MUST be the zero-length string.
alloc_element_string(this.sect_strs, this.sect_elem, null);
// We must prepare the strs and elem sections early so that other sections can
// reference them. However, they will be emitted in alpha order.
this.sect_strs = build_strs(this.file.kmxplus.strs);
this.sect_elem = build_elem(this.file.kmxplus.elem, this.sect_strs);
const build_bksp = build_tran;
const build_finl = build_tran;
@ -90,16 +81,10 @@ export default class KMXPlusBuilder {
this.sect_tran = build_tran(this.file.kmxplus.tran, this.sect_strs, this.sect_elem);
this.sect_vkey = build_vkey(this.file.kmxplus);
// Finalize all sections
if(!finalize_elem(this.sect_elem)) { // must be done after all element strings allocated
this.sect_elem = null;
}
finalize_strs(this.sect_strs); // must be done after all strings allocated
// Finalize the sect (index) section
this.sect_sect = build_sect();
this.finalize_sect(); // must be done last
return this.sect_sect.total;
}

View file

@ -11,14 +11,36 @@ import KMXFile from './kmx';
export class Section {
}
export class GlobalSections {
// These sections are used by other sections during compilation
strs: Strs;
elem: Elem;
}
// 'sect'
export class Sect extends Section {};
// 'bksp' -- see 'tran'
// 'elem'
export class Elem extends Section {};
export class Elem extends Section {
strings: ElementString[] = [];
constructor(strs: Strs) {
super();
this.strings.push(new ElementString(strs, '')); // C7043: null element string
}
allocElementString(strs: Strs, source: string, order?: string, tertiary?: string, tertiary_base?: string, prebase?: string): ElementString {
let s = new ElementString(strs, source, order, tertiary, tertiary_base, prebase);
let result = this.strings.find(item => item.isEqual(s));
if(result === undefined) {
result = s;
this.strings.push(result);
}
return result;
}
};
// 'finl' -- see 'tran'
@ -33,7 +55,7 @@ export enum KeyFlags {
export class KeysItem {
vkey: number;
mod: number;
to: string;
to: StrsItem;
flags: KeyFlags;
};
@ -44,7 +66,7 @@ export class Keys extends Section {
// 'loca'
export class Loca extends Section {
locales: string[] = [];
locales: StrsItem[] = [];
};
// 'meta'
@ -59,19 +81,19 @@ export enum KeyboardSettings {
export enum Meta_NormalizationForm { NFC='NFC', NFD='NFD', other='other' };
export class Meta extends Section {
author: string;
conform: string;
layout: string;
normalization: Meta_NormalizationForm;
indicator: string;
version: string; // semver version string, defaults to "0"
author: StrsItem;
conform: StrsItem;
layout: StrsItem;
normalization: StrsItem;
indicator: StrsItem;
version: StrsItem; // semver version string, defaults to "0"
settings: KeyboardSettings;
};
// 'name'
export class Name extends Section {
names: string[] = [];
names: StrsItem[] = [];
};
// 'ordr'
@ -87,7 +109,34 @@ export class Ordr extends Section {
// 'strs'
export type Strs = Section;
export class StrsItem {
readonly value: string;
constructor(value: string) {
this.value = value;
}
}
export class Strs extends Section {
strings: StrsItem[] = [ new StrsItem('') ]; // C7043: The null string is always requierd
allocString(s?: string): StrsItem {
if(s === undefined || s === null) {
// undefined or null are always equivalent to empty string, see C7043
s = '';
}
if(typeof s !== 'string') {
throw new Error('alloc_string: s must be a string, undefined, or null.');
}
let result = this.strings.find(item => item.value === s);
if(result === undefined) {
result = new StrsItem(s);
this.strings.push(result);
}
return result;
}
};
// 'tran'
@ -98,7 +147,7 @@ export enum TranItemFlags {
export class TranItem extends Section {
from: ElementString;
to: string;
to: StrsItem;
before: ElementString;
flags: TranItemFlags;
};

View file

@ -21,7 +21,7 @@ block(kmxheader) # struct COMP_KEYBOARD {
00 10 00 00 # KMX_DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400
8f 97 57 27 # KMX_DWORD dwCheckSum; // 0008 As stored in keyboard
26 d2 24 03 # KMX_DWORD dwCheckSum; // 0008 As stored in keyboard
00 00 00 00 # KMX_DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts
01 00 00 00 # KMX_DWORD IsRegistered; // 0010
00 00 00 00 # KMX_DWORD version; // 0014 keyboard version
@ -128,35 +128,42 @@ block(elem) # struct COMP_KMXPLUS_ELEM {
diff(elem,elemNull) # KMX_DWORD offset; // 0010+ offset from this blob
sizeof(elemNull,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units)
diff(elem,elemBkspFrom) # KMX_DWORD offset; // 0010+ offset from this blob
sizeof(elemBkspFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units)
diff(elem,elemFinlFrom) # KMX_DWORD offset; // 0010+ offset from this blob
sizeof(elemFinlFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units)
diff(elem,elemTranFrom) # KMX_DWORD offset; // 0010+ offset from this blob
sizeof(elemTranFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units)
diff(elem,elemBkspFrom) # KMX_DWORD offset; // 0010+ offset from this blob
sizeof(elemBkspFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units)
diff(elem,elemOrdrFrom) # KMX_DWORD offset; // 0010+ offset from this blob
sizeof(elemOrdrFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units)
diff(elem,elemOrdrBefore) # KMX_DWORD offset; // 0010+ offset from this blob
sizeof(elemOrdrBefore,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units)
diff(elem,elemTranFrom) # KMX_DWORD offset; // 0010+ offset from this blob
sizeof(elemTranFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units)
block(elemNull)
block(elemBkspFrom)
index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
index(strNull,strElemBkspFrom2,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
block(elemFinlFrom)
index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
block(elemTranFrom)
index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
index(strNull,strElemTranFrom2,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
block(elemBkspFrom)
index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
index(strNull,strElemBkspFrom2,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
block(elemOrdrFrom)
# <reorder before="ᩫ" from="᩠᩵ᩅ" order="10 55 10" />
index(strNull,strElemOrdrFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
@ -170,12 +177,6 @@ block(elemOrdrBefore)
index(strNull,strElemOrdrBefore,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
block(elemTranFrom)
index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
index(strNull,strElemTranFrom2,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint
01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset
block(endelem)
# ----------------------------------------------------------------------------------------------------
@ -271,54 +272,53 @@ block(strs) # struct COMP_KMXPLUS_STRS {
# KMX_DWORD length; // 0014+ str length (UTF-16LE units)
diff(strs,strNull) sizeof(strNull,2)
diff(strs,strVersion) sizeof(strVersion,2)
diff(strs,strNorm) sizeof(strNorm,2)
diff(strs,strName) sizeof(strName,2)
diff(strs,strElemTranFrom1) sizeof(strElemTranFrom1,2)
diff(strs,strElemTranFrom2) sizeof(strElemTranFrom2,2)
diff(strs,strElemBkspFrom2) sizeof(strElemBkspFrom2,2)
diff(strs,strKey1) sizeof(strKey1,2)
diff(strs,strKey2) sizeof(strKey2,2)
diff(strs,strLocale) sizeof(strLocale,2)
diff(strs,strLayout) sizeof(strLayout,2)
diff(strs,strAuthor) sizeof(strAuthor,2)
diff(strs,strConformsTo) sizeof(strConformsTo,2)
diff(strs,strLayout) sizeof(strLayout,2)
diff(strs,strNorm) sizeof(strNorm,2)
diff(strs,strIndicator) sizeof(strIndicator,2)
diff(strs,strVersion) sizeof(strVersion,2)
diff(strs,strName) sizeof(strName,2)
diff(strs,strElemOrdrFrom1) sizeof(strElemOrdrFrom1,2)
diff(strs,strElemOrdrFrom2) sizeof(strElemOrdrFrom2,2)
diff(strs,strElemOrdrFrom3) sizeof(strElemOrdrFrom3,2)
diff(strs,strElemOrdrBefore) sizeof(strElemOrdrBefore,2)
diff(strs,strElemTranFrom2) sizeof(strElemTranFrom2,2)
diff(strs,strTranTo) sizeof(strTranTo,2)
diff(strs,strKey1) sizeof(strKey1,2)
diff(strs,strKey2) sizeof(strKey2,2)
diff(strs,strElemOrdrFrom3) sizeof(strElemOrdrFrom3,2)
diff(strs,strElemOrdrFrom1) sizeof(strElemOrdrFrom1,2)
diff(strs,strElemOrdrBefore) sizeof(strElemOrdrBefore,2)
diff(strs,strElemOrdrFrom2) sizeof(strElemOrdrFrom2,2)
diff(strs,strIndicator) sizeof(strIndicator,2)
# String table -- block(x) is used to store the null u16char at end of each string
# without interfering with sizeof() calculation above
block(strNull) block(x) 00 00 # the zero-length string
block(strVersion) 30 00 block(x) 00 00 # '0'
block(strNorm) 4e 00 46 00 43 00 block(x) 00 00 # 'NFC'
block(strName) 54 00 65 00 73 00 74 00 4b 00 62 00 64 00 block(x) 00 00 # 'TestKbd'
block(strElemTranFrom1) 5E 00 block(x) 00 00 # '^'
block(strElemTranFrom2) 61 00 block(x) 00 00 # 'a'
block(strElemBkspFrom2) 65 00 block(x) 00 00 # 'e'
block(strKey1) 27 01 block(x) 00 00 # 'ħ'
block(strKey2) 90 17 b6 17 block(x) 00 00 # 'ថា'
block(strLocale) 6d 00 74 00 block(x) 00 00 # 'mt'
block(strLayout) 71 00 77 00 65 00 72 00 74 00 79 00 block(x) 00 00 # 'qwerty'
block(strAuthor) 73 00 72 00 6c 00 32 00 39 00 35 00 block(x) 00 00 # 'srl295'
block(strConformsTo) 74 00 65 00 63 00 68 00 70 00 72 00 65 00 76 00
69 00 65 00 77 00 block(x) 00 00 # 'techpreview'
block(strLayout) 71 00 77 00 65 00 72 00 74 00 79 00 block(x) 00 00 # 'qwerty'
block(strNorm) 4e 00 46 00 43 00 block(x) 00 00 # 'NFC'
block(strIndicator) 3d d8 40 de block(x) 00 00 # '🙀'
block(strVersion) 30 00 block(x) 00 00 # '0'
block(strName) 54 00 65 00 73 00 74 00 4b 00 62 00 64 00 block(x) 00 00 # 'TestKbd'
# <reorder before="ᩫ" from="᩠᩵ᩅ" order="10 55 10" />
block(strElemOrdrFrom1) 60 1a block(x) 00 00 # '᩠'
block(strElemOrdrFrom2) 75 1a block(x) 00 00 # '᩵'
block(strElemOrdrFrom3) 45 1a block(x) 00 00 # 'ᩅ'
block(strElemOrdrBefore) 6b 1a block(x) 00 00 # 'ᩫ'
block(strElemTranFrom2) 61 00 block(x) 00 00 # 'a'
block(strTranTo) E2 00 block(x) 00 00 # 'â'
block(strKey1) 27 01 block(x) 00 00 # 'ħ'
block(strKey2) 90 17 b6 17 block(x) 00 00 # 'ថា'
# <reorder before="ᩫ" from="᩠᩵ᩅ" order="10 55 10" />
block(strElemOrdrFrom3) 45 1a block(x) 00 00 # 'ᩅ'
block(strElemOrdrFrom1) 60 1a block(x) 00 00 # '᩠'
block(strElemOrdrBefore) 6b 1a block(x) 00 00 # 'ᩫ'
block(strElemOrdrFrom2) 75 1a block(x) 00 00 # '᩵'
block(strIndicator) 3d d8 40 de block(x) 00 00 # '🙀'
block(endstrs) # end of strs block

View file

@ -4,7 +4,7 @@
import * as path from 'path';
import * as fs from 'fs';
import { SectionCompiler } from '../../src/keyman/compiler/section-compiler';
import { Section } from '../../src/keyman/kmx/kmx-plus';
import { Elem, GlobalSections, Section, Strs } from '../../src/keyman/kmx/kmx-plus';
import LDMLKeyboardXMLSourceFileReader from '../../src/keyman/ldml-keyboard/ldml-keyboard-xml-reader';
import { CompilerEvent } from '../keyman/compiler/callbacks';
@ -41,5 +41,12 @@ export function loadSectionFixture(compilerClass: typeof SectionCompiler, filena
if(!compiler.validate()) {
return null;
}
return compiler.compile();
let globalSections: GlobalSections = {
strs: new Strs(),
elem: null
};
globalSections.elem = new Elem(globalSections.strs);
return compiler.compile(globalSections);
}

View file

@ -15,11 +15,11 @@ describe('bksp', function () {
assert.lengthOf(bksp.items, 1);
assert.lengthOf(bksp.items[0].from, 2);
assert.strictEqual(bksp.items[0].from[0].value, "្");
assert.strictEqual(bksp.items[0].from[1].value, "ម");
assert.strictEqual(bksp.items[0].from[0].value.value, "្");
assert.strictEqual(bksp.items[0].from[1].value.value, "ម");
assert.strictEqual(bksp.items[0].flags, BkspItemFlags.none);
assert.isEmpty(bksp.items[0].before);
assert.strictEqual(bksp.items[0].to, "");
assert.strictEqual(bksp.items[0].to.value, "");
});
});

View file

@ -15,11 +15,11 @@ describe('finl', function () {
assert.lengthOf(finl.items, 1);
assert.lengthOf(finl.items[0].from, 2);
assert.strictEqual(finl.items[0].from[0].value, "x");
assert.strictEqual(finl.items[0].from[1].value, "x");
assert.strictEqual(finl.items[0].from[0].value.value, "x");
assert.strictEqual(finl.items[0].from[1].value.value, "x");
assert.strictEqual(finl.items[0].flags, FinlItemFlags.error);
assert.isEmpty(finl.items[0].before);
assert.strictEqual(finl.items[0].to, "x");
assert.strictEqual(finl.items[0].to.value, "x");
});
});

View file

@ -14,7 +14,7 @@ describe('loca', function () {
assert.equal(callbacks.messages.length, 0);
assert.equal(loca.locales.length, 1);
assert.equal(loca.locales[0].toLowerCase(), 'mt');
assert.equal(loca.locales[0].value.toLowerCase(), 'mt');
});
it('should compile multiple locales', function() {
@ -30,11 +30,11 @@ describe('loca', function () {
// Original is 6 locales, now five minimized in the results
assert.equal(loca.locales.length, 5);
assert.equal(loca.locales[0], 'mt');
assert.equal(loca.locales[1], 'fr'); // Original fr-FR
assert.equal(loca.locales[2], 'km'); // Original km-Khmr-kh
assert.equal(loca.locales[3], 'qq-Abcd-ZZ-x-foobar');
assert.equal(loca.locales[4], 'en-fonipa');
assert.equal(loca.locales[0].value, 'mt');
assert.equal(loca.locales[1].value, 'fr'); // Original fr-FR
assert.equal(loca.locales[2].value, 'km'); // Original km-Khmr-kh
assert.equal(loca.locales[3].value, 'qq-Abcd-ZZ-x-foobar');
assert.equal(loca.locales[4].value, 'en-fonipa');
});
it('should reject structurally invalid invalid locales', function() {

View file

@ -13,11 +13,11 @@ describe('meta', function () {
let meta = loadSectionFixture(MetaCompiler, 'sections/meta/minimal.xml', callbacks) as Meta;
assert.equal(callbacks.messages.length, 0);
assert.isUndefined(meta.author); // TODO-LDML: default author string "unknown"?
assert.equal(meta.conform, 'techpreview');
assert.isUndefined(meta.layout); // TODO-LDML: assumed layout?
assert.isUndefined(meta.normalization); // TODO-LDML: assumed normalization?
assert.isUndefined(meta.indicator); // TODO-LDML: synthesize an indicator?
assert.isEmpty(meta.author.value); // TODO-LDML: default author string "unknown"?
assert.equal(meta.conform.value, 'techpreview');
assert.isEmpty(meta.layout.value); // TODO-LDML: assumed layout?
assert.isEmpty(meta.normalization.value); // TODO-LDML: assumed normalization?
assert.isEmpty(meta.indicator.value); // TODO-LDML: synthesize an indicator?
assert.equal(meta.settings, KeyboardSettings.none);
});
@ -26,11 +26,11 @@ describe('meta', function () {
let meta = loadSectionFixture(MetaCompiler, 'sections/meta/maximal.xml', callbacks) as Meta;
assert.equal(callbacks.messages.length, 0);
assert.equal(meta.author, 'The Keyman Team');
assert.equal(meta.conform, 'techpreview');
assert.equal(meta.layout, 'QWIRKY');
assert.equal(meta.normalization, 'NFC');
assert.equal(meta.indicator, 'QW');
assert.equal(meta.author.value, 'The Keyman Team');
assert.equal(meta.conform.value, 'techpreview');
assert.equal(meta.layout.value, 'QWIRKY');
assert.equal(meta.normalization.value, 'NFC');
assert.equal(meta.indicator.value, 'QW');
assert.equal(meta.settings, KeyboardSettings.fallback | KeyboardSettings.transformFailure | KeyboardSettings.transformPartial);
});

View file

@ -14,7 +14,7 @@ describe('name', function () {
assert.equal(callbacks.messages.length, 0);
assert.equal(name.names.length, 1);
assert.equal(name.names[0], 'My First Keyboard');
assert.equal(name.names[0].value, 'My First Keyboard');
});
it('should compile multiple names', function() {
@ -23,11 +23,11 @@ describe('name', function () {
assert.equal(callbacks.messages.length, 0);
assert.equal(name.names.length, 5);
assert.equal(name.names[0], 'My Second Keyboard');
assert.equal(name.names[1], 'My 2nd Keyboard');
assert.equal(name.names[2], 'win:kbd2');
assert.equal(name.names[3], 'mac:keybd_2');
assert.equal(name.names[4], 'web:keyboard-2');
assert.equal(name.names[0].value, 'My Second Keyboard');
assert.equal(name.names[1].value, 'My 2nd Keyboard');
assert.equal(name.names[2].value, 'win:kbd2');
assert.equal(name.names[3].value, 'mac:keybd_2');
assert.equal(name.names[4].value, 'web:keyboard-2');
});
//TODO-LDML: should we be linting on repeated names?

View file

@ -15,10 +15,10 @@ describe('ordr', function () {
assert.lengthOf(ordr.items, 1);
assert.lengthOf(ordr.items[0].elements, 4);
assert.strictEqual(ordr.items[0].elements[0].value, "ខ");
assert.strictEqual(ordr.items[0].elements[1].value, "ែ");
assert.strictEqual(ordr.items[0].elements[2].value, "្");
assert.strictEqual(ordr.items[0].elements[3].value, "ម");
assert.strictEqual(ordr.items[0].elements[0].value.value, "ខ");
assert.strictEqual(ordr.items[0].elements[1].value.value, "ែ");
assert.strictEqual(ordr.items[0].elements[2].value.value, "្");
assert.strictEqual(ordr.items[0].elements[3].value.value, "ម");
assert.strictEqual(ordr.items[0].elements[0].order, 1);
assert.strictEqual(ordr.items[0].elements[1].order, 3);
assert.strictEqual(ordr.items[0].elements[2].order, 4);

View file

@ -15,11 +15,11 @@ describe('tran', function () {
assert.lengthOf(tran.items, 1);
assert.lengthOf(tran.items[0].from, 2);
assert.strictEqual(tran.items[0].from[0].value, "x");
assert.strictEqual(tran.items[0].from[1].value, "x");
assert.strictEqual(tran.items[0].from[0].value.value, "x");
assert.strictEqual(tran.items[0].from[1].value.value, "x");
assert.strictEqual(tran.items[0].flags, TranItemFlags.error);
assert.isEmpty(tran.items[0].before);
assert.strictEqual(tran.items[0].to, "x");
assert.strictEqual(tran.items[0].to.value, "x");
});
});