@@ -26,7 +23,21 @@
Creating your first lexical model
- Click the New Model button to create a model for any device. This will open the Model Editor.
+ Click the New Model button to create a lexical model for predictive text. Lexical model source files are TypeScript files.
+ In version 12, the model format supported is "trie-1.0". The model source file will include references to wordlist source
+ files of .tsv format, which can be edited in a compatible spreadsheet program or generated from a number of other tools;
+ check the online documentation for more details.
+
+
+
+ Lexical model filenames should have the format author.bcp47.uniq.model.ts. A compiled lexical
+ model file is a JavaScript file.
+
+
+
+ A corresponding package file will be needed (with extension .model.kps) in order to deploy the model to mobile
+ devices. This can be created in the Packages tab. Note that a package file can contain either model file(s) or
+ keyboard file(s), not both.
diff --git a/windows/src/developer/TIKE/xml/project/project.css b/windows/src/developer/TIKE/xml/project/project.css
index 1c0ef5614a..bbfa70bea0 100644
--- a/windows/src/developer/TIKE/xml/project/project.css
+++ b/windows/src/developer/TIKE/xml/project/project.css
@@ -64,7 +64,17 @@ body.tab-3 .page#page-3,
body.tab-4 .page#page-4 {
display: block;
}
-
+
+html.type-lexical-model .tabb#tabb1 {
+ /* Hide keyboards tab for lexical model projects */
+ display: none;
+}
+
+html.type-keyboard .tabb#tabb2 {
+ /* Hide lexical-models tab for keyboard projects */
+ display: none;
+}
+
.help-container {
display: none;
diff --git a/windows/src/developer/TIKE/xml/project/project.js b/windows/src/developer/TIKE/xml/project/project.js
index 9846b3011c..52bc584f24 100644
--- a/windows/src/developer/TIKE/xml/project/project.js
+++ b/windows/src/developer/TIKE/xml/project/project.js
@@ -106,15 +106,26 @@ function showhideupper()
}
savestate();
}
+function isTabVisible(n) {
+ return document.getElementById('tabb'+n).offsetParent != null;
+}
document.onkeydown = function()
{
switch(event.keyCode)
{
case 33: //pgup
- if(tabselected > 0) selecttabb(tabselected-1);
+ if(tabselected > 0) {
+ do {
+ selecttabb(tabselected-1);
+ } while(!isTabVisible(tabselected) && tabselected > 0);
+ }
break;
case 34: //pgdn
- if(tabselected < 3) selecttabb(tabselected+1);
+ if(tabselected < 4) {
+ do {
+ selecttabb(tabselected+1);
+ } while(!isTabVisible(tabselected) && tabselected < 4)
+ }
break;
default:
event.returnValue = true;
diff --git a/windows/src/developer/TIKE/xml/project/project.xsl b/windows/src/developer/TIKE/xml/project/project.xsl
index a59d938a33..4c2452a6c3 100644
--- a/windows/src/developer/TIKE/xml/project/project.xsl
+++ b/windows/src/developer/TIKE/xml/project/project.xsl
@@ -11,6 +11,15 @@
+
+
+
+
+
+ type-lexical-model
+ type-keyboard
+
+
diff --git a/windows/src/developer/kmcomp/kccompileproject.pas b/windows/src/developer/kmcomp/kccompileproject.pas
index 8ceaceab10..10c7852414 100644
--- a/windows/src/developer/kmcomp/kccompileproject.pas
+++ b/windows/src/developer/kmcomp/kccompileproject.pas
@@ -66,7 +66,7 @@ var
begin
Result := False;
Found := False;
- with TProjectConsole.Create(AProjectFilename, False) do
+ with TProjectConsole.Create(ptUnknown, AProjectFilename, False) do
try
Options.CheckFilenameConventions := Options.CheckFilenameConventions or ACheckFilenameConventions; // never downgrade this option
FullySilent := AFullySilent;
diff --git a/windows/src/developer/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas b/windows/src/developer/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas
index ffddb40d6e..5e20e35e1f 100644
--- a/windows/src/developer/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas
+++ b/windows/src/developer/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas
@@ -6,31 +6,24 @@ uses
System.SysUtils,
kpsfile,
- UKeymanTargets;
+ Keyman.Developer.System.ProjectTemplate,
+ UKeymanTargets,
+ utilfiletypes;
type
- EKeyboardProjectTemplate = class(Exception);
+ EKeyboardProjectTemplate = class(EProjectTemplate);
- TKeyboardProjectTemplate = class
+ TKeyboardProjectTemplate = class(TProjectTemplate)
private
- FName: string;
- FBasePath: string;
- FKeyboardID: string;
FTargets: TKeymanTargets;
FIconFilename: string;
FOSKFilename: string;
FTouchLayoutFilename: string;
- FAuthor: string;
- FCopyright: string;
- FVersion: string;
- FBCP47Tags: string;
FIncludeIcon: Boolean;
- function GetFilename(Base: string): string;
+
function GetIconFilename: string;
function GetKeyboardFilename: string;
function GetOSKFilename: string;
- function GetPackageFilename: string;
- function GetProjectFilename: string;
function GetTouchLayoutFilename: string;
function HasIcon: Boolean;
@@ -40,29 +33,23 @@ type
procedure WriteKMN;
procedure WriteKPS;
+ procedure WriteKPJ;
procedure WriteKVKS;
procedure WriteTouchLayout;
- procedure WriteKPJ;
- procedure WriteRepositoryMetadata;
- procedure WriteDocumentation;
procedure WriteKeyboardInfo;
- procedure Transform(const SourceFile: string; DestFile: string = '');
- function DataPath: string;
- procedure SetKeyboardLanguages(kps: TkpsFile);
+ protected
+ const
+ SFileTemplate_KeyboardInfo = '%s.keyboard_info'; // in root
+ SDataPath_BasicKeyboard = 'basic-keyboard\';
+
+ function DataPath: string; override;
public
constructor Create(const BasePath, KeyboardID: string; Targets: TKeymanTargets);
- destructor Destroy; override;
- procedure Generate;
- property Name: string read FName write FName;
- property Copyright: string read FCopyright write FCopyright;
- property Author: string read FAuthor write FAuthor;
- property Version: string read FVersion write FVersion;
- property BCP47Tags: string read FBCP47Tags write FBCP47Tags;
+ procedure Generate; override;
+
property IncludeIcon: Boolean read FIncludeIcon write FIncludeIcon;
property KeyboardFilename: string read GetKeyboardFilename;
- property ProjectFilename: string read GetProjectFilename;
- property PackageFilename: string read GetPackageFilename;
property OSKFilename: string read GetOSKFilename;
property IconFilename: string read GetIconFilename;
property TouchLayoutFilename: string read GetTouchLayoutFilename;
@@ -86,43 +73,22 @@ uses
PackageInfo,
RedistFiles,
TouchLayout,
- utilfiletypes,
utilstr,
VisualKeyboard;
-const
- SFolder_Source = 'source';
- SFolder_Build = 'build';
-
- SFile_ReadmeMD = 'README.md'; // in root
- SFile_HistoryMD = 'HISTORY.md'; // in root
- SFile_LicenseMD = 'LICENSE.md'; // in root
- SFile_WelcomeHTM = 'welcome.htm'; // in source/
- SFile_ReadmeHTM = 'readme.htm'; // in source/
-
- SFileTemplate_KeyboardInfo = '%s.keyboard_info'; // in root
-
{ TKeyboardProjectTemplate }
constructor TKeyboardProjectTemplate.Create(const BasePath, KeyboardID: string; Targets: TKeymanTargets);
begin
- inherited Create;
- FBasePath := IncludeTrailingPathDelimiter(ExpandFileName(BasePath));
- FKeyboardID := LowerCase(KeyboardID);
+ inherited Create(BasePath, KeyboardID);
FTargets := Targets;
- FVersion := '1.0';
-end;
-
-destructor TKeyboardProjectTemplate.Destroy;
-begin
- inherited Destroy;
end;
procedure TKeyboardProjectTemplate.Generate;
begin
- if not ForceDirectories(FBasePath + FKeyboardID + '\' + SFolder_Source) then
- raise EKeyboardProjectTemplate.Create('Could not create destination path '+FBasePath+FKeyboardID);
+ if not ForceDirectories(BasePath + ID + '\' + SFolder_Source) then
+ raise EKeyboardProjectTemplate.Create('Could not create destination path '+BasePath+ID);
WriteDocumentation;
@@ -140,16 +106,6 @@ begin
WriteKeyboardInfo;
end;
-function TKeyboardProjectTemplate.GetFilename(Base: string): string;
-begin
- if Base = '' then
- Result := ''
- else if ExtractFileExt(Base) = Base then
- Result := FBasePath + FKeyboardID + '\' + SFolder_Source + '\' + FKeyboardID + Base
- else
- Result := FBasePath + FKeyboardID + '\' + SFolder_Source + '\' + Base;
-end;
-
function TKeyboardProjectTemplate.GetIconFilename: string;
begin
if FIncludeIcon
@@ -167,17 +123,6 @@ begin
Result := GetFilename(FOSKFilename);
end;
-function TKeyboardProjectTemplate.GetPackageFilename: string;
-begin
- Result := GetFilename(Ext_PackageSource);
-end;
-
-function TKeyboardProjectTemplate.GetProjectFilename: string;
-begin
- // Project file goes in parent folder
- Result := FBasePath + FKeyboardID + '\' + FKeyboardID + Ext_ProjectSource;
-end;
-
function TKeyboardProjectTemplate.GetTouchLayoutFilename: string;
begin
Result := GetFilename(FTouchLayoutFilename);
@@ -203,19 +148,12 @@ begin
Result := (TouchKeymanTargets+[ktAny]) * FTargets <> [];
end;
-procedure TKeyboardProjectTemplate.WriteDocumentation;
-begin
- // Write readme.htm and welcome.htm
- Transform(SFolder_Source + '\' + SFile_WelcomeHTM);
- Transform(SFolder_Source + '\' + SFile_ReadmeHTM);
-end;
-
procedure TKeyboardProjectTemplate.WriteKeyboardInfo;
begin
// Write keyboardid.keyboard_info
Transform(
Format(SFileTemplate_KeyboardInfo, ['keyboard']),
- Format(SFileTemplate_KeyboardInfo, [FKeyboardID])
+ Format(SFileTemplate_KeyboardInfo, [ID])
);
end;
@@ -230,13 +168,13 @@ begin
kp.FileName := KeyboardFilename;
kp.InitialComment := #13#10+
- FKeyboardID+' generated from template at '+FormatDateTime('yyyy-mm-dd hh:nn:ss', Now)+#13#10+
- 'with name "'+FName+'"'#13#10;
- kp.SetSystemStoreValue(ssName, FName);
- if FCopyright <> '' then
- kp.SetSystemStoreValue(ssCopyright, FCopyright);
+ ID+' generated from template at '+FormatDateTime('yyyy-mm-dd hh:nn:ss', Now)+#13#10+
+ 'with name "'+Name+'"'#13#10;
+ kp.SetSystemStoreValue(ssName, Name);
+ if Copyright <> '' then
+ kp.SetSystemStoreValue(ssCopyright, Copyright);
kp.SetSystemStoreValue(ssVersion, SKeymanKeyboardVersion);
- kp.SetSystemStoreValue(ssKeyboardVersion, FVersion);
+ kp.SetSystemStoreValue(ssKeyboardVersion, Version);
kp.SetSystemStoreValue(ssTargets, KeymanTargetsToString(FTargets));
if HasIcon then
@@ -272,7 +210,7 @@ procedure TKeyboardProjectTemplate.WriteKPJ;
var
kpj: TProject;
begin
- kpj := TProject.Create(GetProjectFilename);
+ kpj := TProject.Create(ptKeyboard, GetProjectFilename);
try
kpj.Options.BuildPath := '$PROJECTPATH\' + SFolder_Build;
kpj.Options.WarnDeprecatedCode := True;
@@ -284,10 +222,10 @@ begin
kpj.Files.Add(TkpsProjectFile.Create(kpj, GetPackageFilename, nil));
// Add metadata files to project
- kpj.Files.Add(TOpenableProjectFile.Create(kpj, FBasePath + FKeyboardID + '\' + SFile_HistoryMD, nil));
- kpj.Files.Add(TOpenableProjectFile.Create(kpj, FBasePath + FKeyboardID + '\' + SFile_LicenseMD, nil));
- kpj.Files.Add(TOpenableProjectFile.Create(kpj, FBasePath + FKeyboardID + '\' + SFile_ReadmeMD, nil));
- kpj.Files.Add(TOpenableProjectFile.Create(kpj, FBasePath + FKeyboardID + '\' + Format(SFileTemplate_KeyboardInfo, [FKeyboardID]), nil));
+ kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_HistoryMD, nil));
+ kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_LicenseMD, nil));
+ kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_ReadmeMD, nil));
+ kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + Format(SFileTemplate_KeyboardInfo, [ID]), nil));
kpj.Save;
finally
@@ -304,9 +242,9 @@ begin
kps := TKPSFile.Create;
try
// Set kps metadata
- kps.Info.Desc[PackageInfo_Name] := FName;
- kps.Info.Desc[PackageInfo_Copyright] := FCopyright;
- kps.Info.Desc[PackageInfo_Author] := FAuthor;
+ kps.Info.Desc[PackageInfo_Name] := Name;
+ kps.Info.Desc[PackageInfo_Copyright] := Copyright;
+ kps.Info.Desc[PackageInfo_Author] := Author;
kps.KPSOptions.FollowKeyboardVersion := True;
kps.FileName := GetPackageFilename;
@@ -314,7 +252,7 @@ begin
if HasKMX then
begin
f := TPackageContentFile.Create(kps);
- f.FileName := FBasePath + FKeyboardID + '\' + SFolder_Build + '\' + FKeyboardID + Ext_KeymanFile;
+ f.FileName := BasePath + ID + '\' + SFolder_Build + '\' + ID + Ext_KeymanFile;
kps.Files.Add(f);
end;
@@ -322,7 +260,7 @@ begin
if HasTouchLayout then
begin
f := TPackageContentFile.Create(kps);
- f.FileName := FBasePath + FKeyboardID + '\' + SFolder_Build + '\' + FKeyboardID + Ext_Javascript;
+ f.FileName := BasePath + ID + '\' + SFolder_Build + '\' + ID + Ext_Javascript;
kps.Files.Add(f);
end;
@@ -330,29 +268,29 @@ begin
if HasKVKS then
begin
f := TPackageContentFile.Create(kps);
- f.FileName := FBasePath + FKeyboardID + '\' + SFolder_Build + '\' + FKeyboardID + Ext_VisualKeyboard;
+ f.FileName := BasePath + ID + '\' + SFolder_Build + '\' + ID + Ext_VisualKeyboard;
kps.Files.Add(f);
end;
// Add welcome
f := TPackageContentFile.Create(kps);
- f.FileName := FBasePath + FKeyboardID + '\' + SFolder_Source + '\' + SFile_WelcomeHTM;
+ f.FileName := BasePath + ID + '\' + SFolder_Source + '\' + SFile_WelcomeHTM;
kps.Files.Add(f);
// Add readme
f := TPackageContentFile.Create(kps);
- f.FileName := FBasePath + FKeyboardID + '\' + SFolder_Source + '\' + SFile_ReadmeHTM;
+ f.FileName := BasePath + ID + '\' + SFolder_Source + '\' + SFile_ReadmeHTM;
kps.Files.Add(f);
kps.Options.ReadmeFile := f;
// Add metadata about the keyboard
pk := TPackageKeyboard.Create(kps);
- pk.Name := FName;
- pk.ID := FKeyboardID;
- pk.Version := FVersion;
+ pk.Name := Name;
+ pk.ID := ID;
+ pk.Version := Version;
kps.Keyboards.Add(pk);
- SetKeyboardLanguages(kps);
+ SetPackageLanguageMetadata(kps, pk.Languages);
kps.SaveXML;
finally
@@ -360,43 +298,6 @@ begin
end;
end;
-procedure TKeyboardProjectTemplate.SetKeyboardLanguages(kps: TkpsFile);
-var
- tag, tags, s: string;
- bcp47tag: TBCP47Tag;
- pkl: TPackageKeyboardLanguage;
-begin
- tags := Trim(FBCP47Tags);
- tag := StrToken(tags, ' ');
- while tag <> '' do
- begin
- pkl := TPackageKeyboardLanguage.Create(kps);
-
- bcp47tag := TBCP47Tag.Create(tag);
- try
- bcp47tag.Canonicalize;
- pkl.ID := bcp47tag.Tag;
-
- if not TLanguageCodeUtils.BCP47Languages.TryGetValue(bcp47tag.Language, pkl.Name) then
- begin
- pkl.Name := bcp47tag.Language;
- end;
-
- if bcp47tag.Script <> '' then
- begin
- if TLanguageCodeUtils.BCP47Languages.TryGetValue(bcp47tag.Script, s) then
- pkl.Name := pkl.Name + ' ('+s+')';
- end;
- finally
- bcp47tag.Free;
- end;
-
- kps.Keyboards[0].Languages.Add(pkl);
-
- tag := StrToken(tags, ' ');
- end;
-end;
-
procedure TKeyboardProjectTemplate.WriteKVKS;
var
vk: TVisualKeyboard;
@@ -404,20 +305,13 @@ begin
Assert(HasKVKS);
vk := TVisualKeyboard.Create;
try
- vk.Header.AssociatedKeyboard := FKeyboardID;
+ vk.Header.AssociatedKeyboard := ID;
vk.SaveToFile(OSKFilename, kvksfXML);
finally
vk.Free;
end;
end;
-procedure TKeyboardProjectTemplate.WriteRepositoryMetadata;
-begin
- Transform(SFile_HistoryMD);
- Transform(SFile_LicenseMD);
- Transform(SFile_ReadmeMD);
-end;
-
procedure TKeyboardProjectTemplate.WriteTouchLayout;
var
tl: TTouchLayout;
@@ -457,80 +351,7 @@ end;
function TKeyboardProjectTemplate.DataPath: string;
begin
- Result := GetRedistProjectTemplatePath;
-end;
-
-procedure TKeyboardProjectTemplate.Transform(const SourceFile: string; DestFile: string = '');
-var
- s: string;
- ss: TStringStream;
-
- function GetLanguageTagListForKeyboardInfo: string;
- var
- tags, tag: string;
- bcp47tag: TBCP47Tag;
- begin
- // We won't worry about line breaks...
- tags := Trim(FBCP47Tags);
- tag := StrToken(tags, ' ');
- Result := '';
- while tag <> '' do
- begin
- if Result <> '' then
- Result := Result + ', ';
-
- bcp47tag := TBCP47Tag.Create(tag);
- try
- bcp47tag.Canonicalize;
- Result := Result + '"' + bcp47tag.Tag + '"';
- finally
- bcp47tag.Free;
- end;
- tag := StrToken(tags, ' ');
- end;
- end;
-
- function GetPlatformsDotListForReadme: string;
- var
- kt: TKeymanTarget;
- begin
- Result := '';
- for kt := Low(TKeymanTarget) to High(TKeymanTarget) do
- begin
- if kt = ktAny then
- Continue;
- Result := Result + ' * ' + SKeymanTargetNames[kt] + #13#10;
- end;
- end;
-
-begin
- if DestFile = '' then
- DestFile := SourceFile;
-
- ss := TStringStream.Create('', TEncoding.UTF8);
- try
- ss.LoadFromFile(DataPath + SourceFile);
- s := ss.DataString;
- finally
- ss.Free;
- end;
-
- s := ReplaceStr(s, '$NAME', FName);
- s := ReplaceStr(s, '$VERSION', FVersion);
- s := ReplaceStr(s, '$COPYRIGHT', FCopyright);
- s := ReplaceStr(s, '$AUTHOR', FAuthor);
- s := ReplaceStr(s, '$DATE', FormatDateTime('yyyy-mm-dd', Now));
- if Pos('$LANGUAGES_KEYBOARD_INFO', s) > 0 then
- s := ReplaceStr(s, '$LANGUAGES_KEYBOARD_INFO', GetLanguageTagListForKeyboardInfo);
- if Pos('$PLATFORMS_DOTLIST_README', s) > 0 then
- s := ReplaceStr(s, '$PLATFORMS_DOTLIST_README', GetPlatformsDotListForReadme);
-
- ss := TStringStream.Create(s, TEncoding.UTF8);
- try
- ss.SaveToFile(FBasePath + FKeyboardID + '\' + DestFile);
- finally
- ss.Free;
- end;
+ Result := inherited DataPath + SDataPath_BasicKeyboard;
end;
end.
diff --git a/windows/src/developer/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas b/windows/src/developer/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas
new file mode 100644
index 0000000000..834081d316
--- /dev/null
+++ b/windows/src/developer/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas
@@ -0,0 +1,203 @@
+unit Keyman.Developer.System.ModelProjectTemplate;
+
+interface
+
+uses
+ System.SysUtils,
+
+ Keyman.Developer.System.ProjectTemplate,
+ kpsfile,
+ UKeymanTargets,
+ utilfiletypes;
+
+type
+ EModelProjectTemplate = class(Exception);
+
+ TModelProjectTemplate = class(TProjectTemplate)
+ private
+ procedure WriteModel;
+ procedure WriteWordlist;
+ procedure WriteKPS;
+ procedure WriteKPJ;
+ procedure WriteModelInfo;
+ function GetModelFilename: string;
+ function GetWordlistFilename: string;
+
+ protected
+ const
+ SFileTemplate_ModelInfo = '%s.model_info'; // in root
+ SFileTemplate_Package = '%s.model.kps';
+ SFilename_Model = 'model.ts'; // Only for source
+ SFilename_Wordlist = 'wordlist.tsv';
+ SFileTemplate_Model = '%s.model.ts';
+ SFileTemplate_CompiledModel = '%s.model.js';
+ SDataPath_WordlistLexicalModel = 'wordlist-lexical-model\';
+
+ function DataPath: string; override;
+ function GetPackageFilename: string; override;
+
+ public
+ constructor Create(const BasePath, ModelID: string);
+ procedure Generate; override;
+
+ property ModelFilename: string read GetModelFilename;
+ property WordlistFilename: string read GetWordlistFilename;
+ end;
+
+implementation
+
+uses
+ System.Classes,
+ System.StrUtils,
+
+ BCP47Tag,
+ Keyman.System.LanguageCodeUtils,
+ KeymanVersion,
+ Keyman.Developer.System.Project.modelTsProjectFile,
+ Keyman.Developer.System.Project.kpsProjectFile,
+ Keyman.Developer.System.Project.ProjectFile,
+ Keyman.Developer.System.Project.ProjectFiles,
+ PackageInfo,
+ RedistFiles,
+ TouchLayout,
+ utilstr,
+ VisualKeyboard;
+
+{ TModelProjectTemplate }
+
+constructor TModelProjectTemplate.Create(const BasePath, ModelID: string);
+begin
+ inherited Create(BasePath, ModelID);
+end;
+
+procedure TModelProjectTemplate.Generate;
+begin
+ if not ForceDirectories(BasePath + ID + '\' + SFolder_Source) then
+ raise EModelProjectTemplate.Create('Could not create destination path '+BasePath+ID);
+
+ WriteDocumentation;
+
+ WriteModel;
+ WriteWordlist;
+ WriteKPS;
+ WriteKPJ;
+
+ WriteRepositoryMetadata;
+
+ WriteModelInfo;
+end;
+
+function TModelProjectTemplate.GetModelFilename: string;
+begin
+ Result := GetFilename(Format(SFileTemplate_Model, [ID]));
+end;
+
+function TModelProjectTemplate.GetWordlistFilename: string;
+begin
+ Result := GetFilename(SFilename_Wordlist);
+end;
+
+function TModelProjectTemplate.GetPackageFilename: string;
+begin
+ Result := GetFilename(Format(SFileTemplate_Package, [ID]));
+end;
+
+procedure TModelProjectTemplate.WriteModel;
+begin
+ Transform(SFolder_Source + '\' + SFilename_Model,
+ Format(SFolder_Source + '\' + SFileTemplate_Model, [ID]));
+end;
+
+procedure TModelProjectTemplate.WriteModelInfo;
+begin
+ // Write modelid.model_info
+ Transform(
+ Format(SFileTemplate_ModelInfo, ['model']),
+ Format(SFileTemplate_ModelInfo, [ID])
+ );
+end;
+
+procedure TModelProjectTemplate.WriteWordlist;
+begin
+ Transform(SFolder_Source + '\' + SFilename_Wordlist);
+end;
+
+procedure TModelProjectTemplate.WriteKPJ;
+var
+ kpj: TProject;
+begin
+ kpj := TProject.Create(ptLexicalModel, GetProjectFilename);
+ try
+ kpj.Options.BuildPath := '$PROJECTPATH\' + SFolder_Build;
+ kpj.Options.WarnDeprecatedCode := True;
+ kpj.Options.CompilerWarningsAsErrors := True;
+ kpj.Options.CheckFilenameConventions := True;
+
+ // Add keyboard and package to project
+ kpj.Files.Add(TmodelTsProjectFile.Create(kpj, GetModelFilename, nil));
+ kpj.Files.Add(TkpsProjectFile.Create(kpj, GetPackageFilename, nil));
+
+ // Add metadata files to project
+ kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_HistoryMD, nil));
+ kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_LicenseMD, nil));
+ kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_ReadmeMD, nil));
+ kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + Format(SFileTemplate_ModelInfo, [ID]), nil));
+
+ kpj.Save;
+ finally
+ kpj.Free;
+ end;
+end;
+
+procedure TModelProjectTemplate.WriteKPS;
+var
+ kps: TKPSFile;
+ f: TPackageContentFile;
+ plm: TPackageLexicalModel;
+begin
+ kps := TKPSFile.Create;
+ try
+ // Set kps metadata
+ kps.Info.Desc[PackageInfo_Name] := Name;
+ kps.Info.Desc[PackageInfo_Copyright] := Copyright;
+ kps.Info.Desc[PackageInfo_Author] := Author;
+ kps.KPSOptions.FollowKeyboardVersion := True;
+ kps.FileName := GetPackageFilename;
+
+ // Add model.js
+ f := TPackageContentFile.Create(kps);
+ f.FileName := BasePath + ID + '\' + SFolder_Build + '\' + Format(SFileTemplate_CompiledModel, [ID]);
+ kps.Files.Add(f);
+
+ // Add welcome
+ f := TPackageContentFile.Create(kps);
+ f.FileName := BasePath + ID + '\' + SFolder_Source + '\' + SFile_WelcomeHTM;
+ kps.Files.Add(f);
+
+ // Add readme
+ f := TPackageContentFile.Create(kps);
+ f.FileName := BasePath + ID + '\' + SFolder_Source + '\' + SFile_ReadmeHTM;
+ kps.Files.Add(f);
+ kps.Options.ReadmeFile := f;
+
+ // Add metadata about the keyboard
+ plm := TPackageLexicalModel.Create(kps);
+ plm.Name := Name;
+ plm.ID := ID;
+ plm.Version := Version;
+ kps.LexicalModels.Add(plm);
+
+ SetPackageLanguageMetadata(kps, plm.Languages);
+
+ kps.SaveXML;
+ finally
+ kps.Free;
+ end;
+end;
+
+function TModelProjectTemplate.DataPath: string;
+begin
+ Result := inherited DataPath + SDataPath_WordlistLexicalModel;
+end;
+
+end.
diff --git a/windows/src/developer/kmconvert/Keyman.Developer.System.ProjectTemplate.pas b/windows/src/developer/kmconvert/Keyman.Developer.System.ProjectTemplate.pas
new file mode 100644
index 0000000000..dfddf52630
--- /dev/null
+++ b/windows/src/developer/kmconvert/Keyman.Developer.System.ProjectTemplate.pas
@@ -0,0 +1,247 @@
+unit Keyman.Developer.System.ProjectTemplate;
+
+interface
+
+uses
+ System.SysUtils,
+
+ kpsfile,
+ PackageInfo,
+ UKeymanTargets,
+ utilfiletypes;
+
+type
+ EProjectTemplate = class(Exception);
+
+ TProjectTemplate = class
+ private
+ FID: string;
+ FName: string;
+ FBasePath: string;
+ FAuthor: string;
+ FCopyright: string;
+ FVersion: string;
+ FBCP47Tags: string;
+ FProjectType: TKeymanProjectType;
+
+ protected
+ const
+ SFolder_Source = 'source';
+ SFolder_Build = 'build';
+
+ SFile_ReadmeMD = 'README.md'; // in root
+ SFile_HistoryMD = 'HISTORY.md'; // in root
+ SFile_LicenseMD = 'LICENSE.md'; // in root
+ SFile_WelcomeHTM = 'welcome.htm'; // in source/
+ SFile_ReadmeHTM = 'readme.htm'; // in source/
+
+ function GetFilename(Base: string): string;
+
+ function DataPath: string; virtual;
+
+ function GetPackageFilename: string; virtual;
+ function GetProjectFilename: string; virtual;
+
+ procedure WriteRepositoryMetadata;
+ procedure WriteDocumentation;
+ procedure Transform(const SourceFile: string; DestFile: string = '');
+ procedure SetPackageLanguageMetadata(kps: TKpsFile; languages: TPackageKeyboardLanguageList);
+
+ property BasePath: string read FBasePath;
+ property ID: string read FID;
+ public
+ constructor Create(const BasePath, ID: string);
+
+ procedure Generate; virtual; abstract;
+
+ property ProjectType: TKeymanProjectType read FProjectType;
+
+ property Name: string read FName write FName;
+ property Copyright: string read FCopyright write FCopyright;
+ property Author: string read FAuthor write FAuthor;
+ property Version: string read FVersion write FVersion;
+ property BCP47Tags: string read FBCP47Tags write FBCP47Tags;
+
+ property ProjectFilename: string read GetProjectFilename;
+ property PackageFilename: string read GetPackageFilename;
+ end;
+
+implementation
+
+uses
+ System.Classes,
+ System.StrUtils,
+
+ BCP47Tag,
+ Keyman.System.LanguageCodeUtils,
+ KeyboardParser,
+ KeymanVersion,
+ Keyman.Developer.System.Project.kpsProjectFile,
+ Keyman.Developer.System.Project.ProjectFile,
+ Keyman.Developer.System.Project.ProjectFiles,
+ RedistFiles,
+ TouchLayout,
+ utilstr,
+ VisualKeyboard;
+
+{ TProjectTemplate }
+
+constructor TProjectTemplate.Create(const BasePath, ID: string);
+begin
+ inherited Create;
+ FBasePath := IncludeTrailingPathDelimiter(ExpandFileName(BasePath));
+ FID := ID;
+ FVersion := '1.0';
+end;
+
+function TProjectTemplate.GetFilename(Base: string): string;
+begin
+ if Base = '' then
+ Result := ''
+ else if ExtractFileExt(Base) = Base then
+ Result := FBasePath + FID + '\' + SFolder_Source + '\' + FID + Base
+ else
+ Result := FBasePath + FID + '\' + SFolder_Source + '\' + Base;
+end;
+
+function TProjectTemplate.GetPackageFilename: string;
+begin
+ Result := GetFilename(Ext_PackageSource);
+end;
+
+function TProjectTemplate.GetProjectFilename: string;
+begin
+ // Project file goes in parent folder
+ Result := FBasePath + FID + '\' + FID + Ext_ProjectSource;
+end;
+
+procedure TProjectTemplate.WriteDocumentation;
+begin
+ // Write readme.htm and welcome.htm
+ Transform(SFolder_Source + '\' + SFile_WelcomeHTM);
+ Transform(SFolder_Source + '\' + SFile_ReadmeHTM);
+end;
+
+procedure TProjectTemplate.SetPackageLanguageMetadata(kps: TKpsFile; languages: TPackageKeyboardLanguageList);
+var
+ tag, tags, s: string;
+ bcp47tag: TBCP47Tag;
+ pkl: TPackageKeyboardLanguage;
+begin
+ tags := Trim(FBCP47Tags);
+ tag := StrToken(tags, ' ');
+ while tag <> '' do
+ begin
+ pkl := TPackageKeyboardLanguage.Create(kps);
+
+ bcp47tag := TBCP47Tag.Create(tag);
+ try
+ bcp47tag.Canonicalize;
+ pkl.ID := bcp47tag.Tag;
+
+ if not TLanguageCodeUtils.BCP47Languages.TryGetValue(bcp47tag.Language, pkl.Name) then
+ begin
+ pkl.Name := bcp47tag.Language;
+ end;
+
+ if bcp47tag.Script <> '' then
+ begin
+ if TLanguageCodeUtils.BCP47Languages.TryGetValue(bcp47tag.Script, s) then
+ pkl.Name := pkl.Name + ' ('+s+')';
+ end;
+ finally
+ bcp47tag.Free;
+ end;
+
+ languages.Add(pkl);
+
+ tag := StrToken(tags, ' ');
+ end;
+end;
+
+procedure TProjectTemplate.WriteRepositoryMetadata;
+begin
+ Transform(SFile_HistoryMD);
+ Transform(SFile_LicenseMD);
+ Transform(SFile_ReadmeMD);
+end;
+
+function TProjectTemplate.DataPath: string;
+begin
+ Result := GetRedistProjectTemplatePath;
+end;
+
+procedure TProjectTemplate.Transform(const SourceFile: string; DestFile: string = '');
+var
+ s: string;
+ ss: TStringStream;
+
+ function GetLanguageTagListForKeyboardInfo: string;
+ var
+ tags, tag: string;
+ bcp47tag: TBCP47Tag;
+ begin
+ // We won't worry about line breaks...
+ tags := Trim(FBCP47Tags);
+ tag := StrToken(tags, ' ');
+ Result := '';
+ while tag <> '' do
+ begin
+ if Result <> '' then
+ Result := Result + ', ';
+
+ bcp47tag := TBCP47Tag.Create(tag);
+ try
+ bcp47tag.Canonicalize;
+ Result := Result + '"' + bcp47tag.Tag + '"';
+ finally
+ bcp47tag.Free;
+ end;
+ tag := StrToken(tags, ' ');
+ end;
+ end;
+
+ function GetPlatformsDotListForReadme: string;
+ var
+ kt: TKeymanTarget;
+ begin
+ Result := '';
+ for kt := Low(TKeymanTarget) to High(TKeymanTarget) do
+ begin
+ if kt = ktAny then
+ Continue;
+ Result := Result + ' * ' + SKeymanTargetNames[kt] + #13#10;
+ end;
+ end;
+
+begin
+ if DestFile = '' then
+ DestFile := SourceFile;
+
+ ss := TStringStream.Create('', TEncoding.UTF8);
+ try
+ ss.LoadFromFile(DataPath + SourceFile);
+ s := ss.DataString;
+ finally
+ ss.Free;
+ end;
+
+ s := ReplaceStr(s, '$NAME', FName);
+ s := ReplaceStr(s, '$VERSION', FVersion);
+ s := ReplaceStr(s, '$COPYRIGHT', FCopyright);
+ s := ReplaceStr(s, '$AUTHOR', FAuthor);
+ s := ReplaceStr(s, '$DATE', FormatDateTime('yyyy-mm-dd', Now));
+ if Pos('$LANGUAGES_KEYBOARD_INFO', s) > 0 then
+ s := ReplaceStr(s, '$LANGUAGES_KEYBOARD_INFO', GetLanguageTagListForKeyboardInfo);
+ if Pos('$PLATFORMS_DOTLIST_README', s) > 0 then
+ s := ReplaceStr(s, '$PLATFORMS_DOTLIST_README', GetPlatformsDotListForReadme);
+
+ ss := TStringStream.Create(s, TEncoding.UTF8);
+ try
+ ss.SaveToFile(FBasePath + FID + '\' + DestFile);
+ finally
+ ss.Free;
+ end;
+end;
+
+end.
diff --git a/windows/src/developer/kmconvert/data/HISTORY.md b/windows/src/developer/kmconvert/data/basic-keyboard/HISTORY.md
similarity index 100%
rename from windows/src/developer/kmconvert/data/HISTORY.md
rename to windows/src/developer/kmconvert/data/basic-keyboard/HISTORY.md
diff --git a/windows/src/developer/kmconvert/data/LICENSE.md b/windows/src/developer/kmconvert/data/basic-keyboard/LICENSE.md
similarity index 100%
rename from windows/src/developer/kmconvert/data/LICENSE.md
rename to windows/src/developer/kmconvert/data/basic-keyboard/LICENSE.md
diff --git a/windows/src/developer/kmconvert/data/README.md b/windows/src/developer/kmconvert/data/basic-keyboard/README.md
similarity index 100%
rename from windows/src/developer/kmconvert/data/README.md
rename to windows/src/developer/kmconvert/data/basic-keyboard/README.md
diff --git a/windows/src/developer/kmconvert/data/keyboard.keyboard_info b/windows/src/developer/kmconvert/data/basic-keyboard/keyboard.keyboard_info
similarity index 100%
rename from windows/src/developer/kmconvert/data/keyboard.keyboard_info
rename to windows/src/developer/kmconvert/data/basic-keyboard/keyboard.keyboard_info
diff --git a/windows/src/developer/kmconvert/data/source/readme.htm b/windows/src/developer/kmconvert/data/basic-keyboard/source/readme.htm
similarity index 100%
rename from windows/src/developer/kmconvert/data/source/readme.htm
rename to windows/src/developer/kmconvert/data/basic-keyboard/source/readme.htm
diff --git a/windows/src/developer/kmconvert/data/source/welcome.htm b/windows/src/developer/kmconvert/data/basic-keyboard/source/welcome.htm
similarity index 100%
rename from windows/src/developer/kmconvert/data/source/welcome.htm
rename to windows/src/developer/kmconvert/data/basic-keyboard/source/welcome.htm
diff --git a/windows/src/developer/kmconvert/data/wordlist-lexical-model/HISTORY.md b/windows/src/developer/kmconvert/data/wordlist-lexical-model/HISTORY.md
new file mode 100644
index 0000000000..9a605b69a3
--- /dev/null
+++ b/windows/src/developer/kmconvert/data/wordlist-lexical-model/HISTORY.md
@@ -0,0 +1,6 @@
+$NAME Change History
+====================
+
+$VERSION ($DATE)
+----------------
+* Created by $AUTHOR
diff --git a/windows/src/developer/kmconvert/data/wordlist-lexical-model/LICENSE.md b/windows/src/developer/kmconvert/data/wordlist-lexical-model/LICENSE.md
new file mode 100644
index 0000000000..f36b87c8df
--- /dev/null
+++ b/windows/src/developer/kmconvert/data/wordlist-lexical-model/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+$COPYRIGHT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/windows/src/developer/kmconvert/data/wordlist-lexical-model/README.md b/windows/src/developer/kmconvert/data/wordlist-lexical-model/README.md
new file mode 100644
index 0000000000..9458e3e54a
--- /dev/null
+++ b/windows/src/developer/kmconvert/data/wordlist-lexical-model/README.md
@@ -0,0 +1,18 @@
+$NAME lexical model
+===================
+
+$COPYRIGHT
+
+Version $VERSION
+
+Description
+-----------
+
+$NAME generated from template
+
+Links
+-----
+
+Supported Platforms
+-------------------
+$PLATFORMS_DOTLIST_README
diff --git a/windows/src/developer/kmconvert/data/wordlist-lexical-model/model.model_info b/windows/src/developer/kmconvert/data/wordlist-lexical-model/model.model_info
new file mode 100644
index 0000000000..62777e5e12
--- /dev/null
+++ b/windows/src/developer/kmconvert/data/wordlist-lexical-model/model.model_info
@@ -0,0 +1,7 @@
+{
+ "license": "mit",
+ "languages": [
+ $LANGUAGES_KEYBOARD_INFO
+ ],
+ "description": "$NAME generated from template"
+}
diff --git a/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/model.ts b/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/model.ts
new file mode 100644
index 0000000000..b333964042
--- /dev/null
+++ b/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/model.ts
@@ -0,0 +1,13 @@
+/*
+ $NAME $VERSION generated from template.
+
+ This is a minimal lexical model source that uses a tab delimited wordlist.
+ See documentation online at https://help.keyman.com/developer/ for
+ additional parameters.
+*/
+
+const source: LexicalModelSource = {
+ format: 'trie-1.0',
+ sources: ['wordlist.tsv'],
+};
+export default source;
\ No newline at end of file
diff --git a/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/readme.htm b/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/readme.htm
new file mode 100644
index 0000000000..53b02990da
--- /dev/null
+++ b/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/readme.htm
@@ -0,0 +1,23 @@
+
+
+
+
+ $NAME
+
+
+
+
+$NAME
+
+
+ $NAME $VERSION generated from template.
+
+
+$COPYRIGHT
+
+
+
diff --git a/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/welcome.htm b/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/welcome.htm
new file mode 100644
index 0000000000..1626bd4ca6
--- /dev/null
+++ b/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/welcome.htm
@@ -0,0 +1,27 @@
+
+
+
+
+ Start Using $NAME
+
+
+
+
+Start Using $NAME
+
+
+ $NAME $VERSION generated from template.
+
+
+Keyboard Layout
+
+
+
+$COPYRIGHT
+
+
+
\ No newline at end of file
diff --git a/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/wordlist.tsv b/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/wordlist.tsv
new file mode 100644
index 0000000000..567a98abc7
--- /dev/null
+++ b/windows/src/developer/kmconvert/data/wordlist-lexical-model/source/wordlist.tsv
@@ -0,0 +1,16 @@
+#
+# $NAME $VERSION generated from template.
+#
+# This is an example tab-separated wordlist file that can be edited in a spreadsheet
+# program or regenerated from a wordlist tool. See lexical model documentation at
+# https://help.keyman.com/developer/ for tools.
+#
+# Columns
+# =======
+# WORD FREQUENCY (Optional)NOTES
+#
+the 100
+example 5
+hello 10
+world 8
+wordlist 3
\ No newline at end of file
diff --git a/windows/src/global/delphi/general/utilfiletypes.pas b/windows/src/global/delphi/general/utilfiletypes.pas
index a98fc500cb..4660642e80 100644
--- a/windows/src/global/delphi/general/utilfiletypes.pas
+++ b/windows/src/global/delphi/general/utilfiletypes.pas
@@ -54,6 +54,10 @@ const
Ext_ProjectSourceUser = '.kpj.user';
Ext_Javascript = '.js';
Ext_LexicalModelSource = '.ts'; // can't use .model.ts unless we change all extension comparisons to support multiple dots...
+ Ext_LexicalModelSource_FULL = '.model.ts'; // TODO: Refactor these two constants
+ Ext_LexicalModelWordlist = '.tsv';
+ Ext_LexicalModelPackageSource = '.model.kps';
+ Ext_LexicalModelProject = '.model.kpj';
const ExtFileTypes: array[0..13] of TKMFileTypeInfo = (
(Ext: Ext_KeymanSource; FileType: ftKeymanSource),
@@ -87,7 +91,7 @@ type
end;
type
- TKeymanProjectType = (kptUnknown, kptBasic, kptBlank, kptImportWindowsKeyboard);
+ TKeymanProjectType = (kptUnknown, kptBasic, kptBlank, kptImportWindowsKeyboard, kptBlankLexicalModel, kptWordlistLexicalModel);
implementation
diff --git a/windows/src/global/delphi/lexicalmodels/Keyman.System.LexicalModelUtils.pas b/windows/src/global/delphi/lexicalmodels/Keyman.System.LexicalModelUtils.pas
index 9d8878dd5c..f164faf0da 100644
--- a/windows/src/global/delphi/lexicalmodels/Keyman.System.LexicalModelUtils.pas
+++ b/windows/src/global/delphi/lexicalmodels/Keyman.System.LexicalModelUtils.pas
@@ -14,6 +14,10 @@ type
'The model source file %0:s does not follow the recommended model filename conventions. The name should be all lower case, '+
'include only alphanumeric characters and underscore (_), and should have the structure ...model.ts';
+ const SProjectFileNameDoesNotFollowLexicalModelConventions_Message: string =
+ 'The model project file %0:s does not follow the recommended model filename conventions. The name should be all lower case, '+
+ 'include only alphanumeric characters and underscore (_), and should have the structure ...model.kpj';
+
class function LexicalModelFileNameToID(filename: string): string;
class function LexicalModelIDToFileName(id: string): string;
class function DoesPackageFilenameFollowLexicalModelConventions(
@@ -22,6 +26,8 @@ type
const Name: string): Boolean; static;
class function DoesTSFilenameFollowLexicalModelConventions(
const Name: string): Boolean; static;
+ class function DoesProjectFilenameFollowLexicalModelConventions(
+ const Name: string): Boolean; static;
end;
implementation
@@ -37,9 +43,10 @@ const
// The model ID SHOULD adhere to this pattern (see also developer/js/index.ts):
// author .bcp47 .uniq
- MODEL_ID_PATTERN_JS = '^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.model\.(js)$';
- MODEL_ID_PATTERN_TS = '^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.model\.(ts)$';
+ MODEL_ID_PATTERN_JS = '^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.model\.js$';
+ MODEL_ID_PATTERN_TS = '^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.model\.ts$';
MODEL_ID_PATTERN_PACKAGE = '^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.model\.(kps|kmp)$';
+ MODEL_ID_PATTERN_PROJECT = '^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.model\.kpj$';
class function TLexicalModelUtils.DoesJSFilenameFollowLexicalModelConventions(
const Name: string): Boolean;
@@ -61,6 +68,12 @@ begin
Result := TRegEx.IsMatch(ExtractFileName(Name), MODEL_ID_PATTERN_PACKAGE);
end;
+class function TLexicalModelUtils.DoesProjectFilenameFollowLexicalModelConventions(
+ const Name: string): Boolean;
+begin
+ Result := TRegEx.IsMatch(ExtractFileName(Name), MODEL_ID_PATTERN_PROJECT);
+end;
+
class function TLexicalModelUtils.LexicalModelFileNameToID(filename: string): string;
begin
Result := ExtractFileName(filename);