Проект “Data Keeper” предлагает универсальное решение пользовательской классификации объектов, он имеет как свои плюсы, так и минусы. Но сама идея прекрасна и востребована: иметь возможность пользовательской настройки системы классификации. Поэтому проект “Logos” модно назвать двоюродным братом “Data Keeper“. Он также имеет возможность настройки классификации свойств хранимых объектов, но вместо универсального решения с сомнительной производительностью, предлагает специальные алгоритмы и структуру данных, которые позволят проиллюстрировать принципы пользовательской классификации на примере словаря.
Слова, в зависимости от того, какой частью речи они являются, могут иметь различный набор признаков. Хотя в примере используется русский язык, в большинстве других языков действуют те же принципы. Поэтому для начала рассмотрим структуру хранения данных.
Схема данных
Каждое слово имеет одно постоянное значение свойства wpropA и набор значений свойств word_prop, состав которых зависит от основного свойства wpropA. Например, существительные имеют род, число и склонение, глаголы – спряжение и число, а причастие – залог, время и вид.
Word
Словарные формы
- word – написание формы слова
- description – значение слова
- transcription – транскрипция (звучание слова)
- sillable – правила переноса
- addDate – дата добавления в словарь
- id_wpropA – часть речи
- verifed – вычисляемый признак заполненности значимых полей
- propList – вычисляемый список свойств
- codeList – вычисляемый список свойств в виде сокращений
Для формирования списков значений свойств используется функция GROUP_CONCAT:
(( SELECT wpropA.name FROM wpropA WHERE wpropA.id = word.id_wpropA )||'; '||
COALESCE((
SELECT LOWER( GROUP_CONCAT(data.Name,", ") )
FROM
( SELECT wpropB.Name
FROM word_prop
LEFT JOIN wpropB ON wpropB.id = word_prop.id_wpropb
LEFT JOIN propExt ON propExt.id = wpropb.id_propExt
WHERE id_word = word.id
ORDER BY propExt.orderNum ) as data
),
''))
Code language: SQL (Structured Query Language) (sql)
WPropA
Части речи
- name – название
- code – сокращенное название
- description – описание
PropExt
Дополнительные свойства (признаки), характерные только для данной части речи.
- id_wpropA – часть речи
- name – название признака
- description – описание признака
- orderNum – порядковый номер
WPropB
Справочные значения дополнительных свойств.
- id_propExt – дополнительное свойство
- name – название
- code – сокращение
- description – описание
- orderNum – порядковый номер
Word_Prop
Признаки слов
- id_word
- id_wpropB
Формы
Используя технологию Dynamic Table Form (DTF), создание форм сводится к описанию их свойств в файле dforms.ini:
[Word]
caption=Словарь
table=word
fields=word.verifed,word.word,word.transcription,word.codeList
captions=*,Слово,Транскрипция,Признаки
sort=word.word
[WPropA]
caption=Часть речи
table=wpropA
fields=wpropA.code,wpropA.name
captions=Сокр.,Название
sort=wpropA.code
detailForms=dtfPropExt,dtfWPropB
parentControl=frmPOS.panWPropA
[PropExt]
caption=Параметры
table=propExt
fields=propExt.orderNum,propExt.name
captions=#,Название
sort=propExt.orderNum
masterField=id_wpropA
isDetail=True
masterForm=dtfWPropA
parentControl=frmPOS.panPropExt
detailForms=dtfWPropB
[WPropB]
caption=Значения
table=wpropB
fields=wpropB.orderNum,wpropB.name
captions=#,Название
sort=wpropB.orderNum
masterField=id_propExt
isDetail=True
masterForm=dtfPropExt
parentControl=frmPOS.panWPropB
Code language: PHP (php)
Для справочника частей речи необходимо создать статическую форму-контейнер frmPos, на которой автоматически будут созданы необходимые табличные элементы.
Также необходимо создать формы редактирования для каждого табличного представления, не забывая о соглашении по наименованию форм, согласно которому форма редактирования должна начинаться с префикса efm и иметь то же название, что и форма табличного представления.
efmWord
Самой сложной является форма редактирования efmWord, так как кроме обычных полей для свойств таблицы word, на ней расположены таблицы для редактирования списка word_prop.
Для выбора нужного значения морфологического признака достаточно кликнуть по первой колонке таблицы, в результате чего в базу данных заносятся соответствующие записи. Или записи удаляются, если вы сменили значение или вовсе передумали присваивать ему значение.
Также на форме находятся пара кнопок для открытия популярных интернет-ресурсов для уточнения значения слова или его произношения.
Скрипты
Значения морфологических признаков формируются через SQL-запрос, который создается при клике на невидимую кнопку btnWPropBUpd. Для визуализации чекера используется юникод-символ с кодом 10003.
procedure efmWord_btnWPropBUpd_OnClick (Sender: TObject; var Cancel: boolean);
// выборка данных для списка с чекерами
var
tmpSQL:string;
tmpIDWord: string;
tmpIDPropExt: string;
begin
tmpIDWord := IntToStr(efmWord.btnSave.dbGeneralTableId); // слово
tmpIDPropExt := IntToStr( efmWord.tgrPropExt.dbItemID ); // свойство части речи
tmpSQL := //
' SELECT ' + //
' ( case when word_prop.id is null then "" else char(10003) end ),'+ // отобразить чекер, если есть такое значение в базе
' wpropB.name, '+ // название
' wpropB.id '+ // ID, загружается в последнюю колонку, невидимую
' FROM wpropB '+ // выборка из списка всех свойств части речи
' LEFT JOIN word_prop ON word_prop.id_wpropB = wpropB.id and word_prop.id_word = '+tmpIDWord+ // добавляем значения свойств с учетом текущего слова
' WHERE wpropB.id_PropExt = '+tmpIDPropExt; // с фильтрацией по части речи
efmWord.btnWPropBUpd.dbSQL := tmpSQL;
end;
Code language: Delphi (delphi)
При клике по первой колонке вызывается обработчик, в котором производятся необходимые манипуляции с данными в базе: удаляется предыдущее значение и записывается новое.
procedure efmWord_tgrWPropB_OnCellClick (Sender: TObject; ACol, ARow: Integer);
// редактирование таблицы значений морфологических свойств слова.
var
tmpSQL: string;
tmpIDWord: string;
tmpIDPropExt: string;
tmpIDWPropB: string;
begin
if ACol = 0 then // срабатывает только при клике по первой колонке
begin
tmpIDWord := IntToStr(efmWord.btnSave.dbGeneralTableId); // слово
tmpIDPropExt := IntToStr( efmWord.tgrPropExt.dbItemID ); // часть речи
tmpIDWPropB := IntToStr( efmWord.tgrWPropB.dbIndexToID(ARow) ); // значение свойства
// удаляем предыдущие значений
tmpSQL :=
'DELETE FROM word_prop '+
'WHERE id_word = '+tmpIDWord+
' AND id_wpropb in ( SELECT id FROM wpropB WHERE id_propExt ='+tmpIDPropExt+' )';
SQLExecute(tmpSQL);
// если чекера не было, то добавляем
if efmWord.tgrWPropB.Cells[ACol,ARow] = '' then
begin
if tmpIDWord = '-1' then // если запись новая, то сохранить данные
begin // чтобы получить ID слова
SaveWithoutClose(efmWord.btnSave);
tmpIDWord := IntToStr(efmWord.btnSave.dbGeneralTableId);
end; // если сохранение удалось, то добавляем запись
if tmpIDWord <> '-1' then
begin
// добавляем новое значение
tmpSQL :='INSERT INTO word_prop (id_word,id_wpropb) VALUES ('+tmpIDWord+','+tmpIDWPropB+')';
SQLExecute(tmpSQL);
end;
end;
// BClick - это безопасный с точки зрения внутренних механизмов стека вызов обработчика нажатия кнопки
BClick( efmWord.btnWPropBUpd ); // обновить отображение таблицы
BClick( efmWord.btnUpdMemPropList ); // обновить отображение списка свойств слова
end;
end;
Code language: Delphi (delphi)
Обновление списка свойств на форме редактирования позволяет сразу видеть результат:
procedure efmWord_btnUpdMemPropList_OnClick (Sender: TObject; var Cancel: boolean);
// обновить отображение текста
var
tmpSQL: string;
tmpIDWord: string;
begin
tmpIDWord := IntToStr(efmWord.btnSave.dbGeneralTableId);
tmpSQL := //
' SELECT LOWER( GROUP_CONCAT(data.Name,", ") ) FROM '+
' ( SELECT wpropB.Name '+
' FROM word_prop ' +
' LEFT JOIN wpropB ON wpropB.id = word_prop.id_wpropb '+
' LEFT JOIN propExt ON propExt.id = wpropb.id_propExt '+
' WHERE id_word = '+tmpIDWord+
' ORDER BY propExt.orderNum ) as data ';
efmWord.memPropList.Text := LowerCase(efmWord.cmbWPropA.Text) + '; '+ SQLExecute(tmpSQL);
end;
Code language: Delphi (delphi)
Ниже приводятся остальные скрипты, которые необходимы для работы: инициализация таблицы морфологических свойств части речи, а также открытие страниц с сайтами, где можно узнать правильность написания слова или его звучание.
procedure efmWord_OnShow (Sender: TObject; Action: string);
// отображение формы редактирования
begin
efmWord.cmbWPropA.DoOnChange; // вызвать обработчик смены части речи
efmWord.btnWPropBUpd.Click; // обновить (сбросить) содержимое списка свойств
end;
procedure efmWord_cmbWPropA_OnChange (Sender: TObject);
// выбор части речи
begin
// отфильтровать свойства по части речи
efmWord.tgrPropExt.dbFilter := 'id_WPropA = '+IntToStr( efmWord.cmbWPropA.dbItemID );
efmWord.tgrPropExt.dbUpdate;
end;
procedure efmWord_btnOnLine_2_OnClick (Sender: TObject; var Cancel: boolean);
begin
OpenURL('https://frazbor.ru/'+efmWord.edtName.Text);
end;
procedure efmWord_btnOnLine_OnClick (Sender: TObject; var Cancel: boolean);
begin
OpenURL('https://gramota.ru/poisk?query='+efmWord.edtName.Text+'&mode=all');
end;
procedure BClick(AButton: TdbButton);
// клик без рекурсии в скриптах
// нельзя создавать рекурсию для методов-обработчиков событий, поэтому клик заменяем на PostMessage
begin
PostMessage(AButton.Handle, wm_LButtonDown, 0, 0);
PostMessage(AButton.Handle, wm_LButtonUp, 0, 0);
end;
Code language: Delphi (delphi)
Ссылки
- Logos 1.0 – исходный код проекта
P.S.
При отладке данного проекта у меня возникла ошибка уровня среды исполнения: MVDB вылетал без подсветки кода и указания на строку скрипта, косвенно указывая на проблему использования библиотеки ntdll.dll при обращении к БД.
По совету коллег на форуме я решил обновить sqlite3.dll, скачав с официального сайта самую последнюю версию. После этого данные ошибки исчезли. Но стоит учитывать, что теперь в проекте “Logos” находится 64-битная версия этой библиотеки:
Если вам интересен данный проект, вы хотите его использовать или ожидаете его следующую более функциональную версию, вы можете оставлять ваши отзывы и предложения в комментариях к данной статье.