Теперь, после лирического отступления, самое интересное: структурирование книги.
Книга может иметь разделение на части, главы, тома и книги, ну мало ли чего придумает автор…
В FB2 структура задается тэгами
разной степени вложенности. Но в любом случае эта структура – дерево. В корне(в первой строчке), я предлагаю писать название книги, а дальше части, главы или что там есть.
Программе для обработки структуры понадобится стек (напомню, стек – это список с правилом «последний пришел – первый вышел» )
Полученный код FB2, как эталоном, я проверяю программой «FictionBook Editor» . Так вот, экзаменатору не нравится такая структура:
// начало примера
H1 | Кальман Миксат. ОСАДА БЕСТЕРЦЕ
S| (История одного чудака)
H2 | ВВЕДЕНИЕ
// конец примера
Т.е. между секциями не должно быть ничего лишнего…
А вот так будет все нормально:
// начало примера
H1 | Кальман Миксат. ОСАДА БЕСТЕРЦЕ
H1 | (История одного чудака)
H2 | ВВЕДЕНИЕ
// конец примера
Итак, когда при обработке списка ListBox1 встречается строка с типом от H1 до H5 вызывается процедура StyleStucture;
// начало кода
procedure StyleStucture;
begin
if CurStyle <> oldStyle then
begin // пока предположим, что предыдущий стиль был не заголовок
if SytleStack.Count = 0 then // если стек пуст
begin // записываем стиль в стек
SytleStack.Add(TObject(CurStyle))
end
else // если в стеке что-то есть
begin // значит надо проверить последний из заголовков
LastStyle:= TmyStyle(SytleStack.Last); // считываем последний стиль
case SubStyle(CurStyle, LastStyle) of // вычисляем разность текущий стиль минус последний
0: OutList.Add('
'); // стили равны, ничего особенного делать не надо
1: SytleStack.Add(TObject(CurStyle)); // новый стиль больше, добавляем его в стек
// предыдущая секция не закончилась, т. к. новая будет в ее входить как матрешка
else // иначе, считаем что разность меньше нуля
begin
OutList.Add('');
while CurStyle <>LastStyle do
begin
SytleStack.Delete(SytleStack.Count-1); // уменьшаем стек
OutList.Add(''); // завершаем секции до тех пор пока
LastStyle:= TmyStyle(SytleStack.Last); // текущий стиль и стиль в стеке не сравняются.
end;
end;
end;// case
end;
OutList.Add('
'); // начинаем новую секцию
OutList.Add('
'); </p>
<p class="paragraph">
end; </p>
<p class="paragraph">
OutList.Add('<p>'+s+'</p>'); // записываем заголовок секции </p>
<p class="paragraph">
end; // StyleStucture; </p>
<p class="paragraph">
// конец кода </p>
<p class="paragraph">
Пожалуй, это самый тяжелый код в данном манускрипте, но он вроде работает, хотя я вижу в нем по крайней мере две неувязки, но что это, не скажу… </p>
<p class="paragraph">
Ну вот с обработкой книги почти закончили, мелкие подробности увидите в исходнике. </p>
<p class="paragraph">
Нажимаем пункт меню File – Save as FB2. </p>
<p class="paragraph">
И – ничего не получается. Запланированная шутка. Вылезла надпись «Заполнить поля» и фокус перенаправлен на начальную закладку. </p>
<p class="paragraph">
Напоминаю FB2 – это не только легкоусвояемый (легкоусваиваемый) текст, но и очень нужный и полезный заголовок книги. </p>
<p class="paragraph">
Давайте посмотрим, все таки, что происходит при выборе пункта Save as FB2 </p>
<p class="paragraph">
// начало кода </p>
<p class="paragraph">
procedure TForm1.SaveasFB21Click(Sender: TObject); </p>
<p class="paragraph">
begin </p>
<p class="paragraph">
if not BookHaveName then // проверяем, все ли в порядке в заголовке </p>
<p class="paragraph">
begin // если нет, то происходит все то что Вы видели </p>
<p class="paragraph">
PageControl1.ActivePageIndex:= 0; </p>
<p class="paragraph">
ShowMessage('Fill the form.'); </p>
<p class="paragraph">
exit; </p>
<p class="paragraph">
end; </p>
<p class="paragraph">
SaveDialog1.FileName:= form1.FB2_file.Text; </p>
<p class="paragraph">
if SaveDialog1.Execute then </p>
<p class="paragraph">
Make_fb2(SaveDialog1.FileName); </p>
<p class="paragraph">
end; </p>
<p class="paragraph">
// конец кода </p>
<p class="paragraph">
Посмотрим на процедуру BookHaveName </p>
<p class="paragraph">
// начало кода </p>
<p class="paragraph">
function BookHaveName: boolean; </p>
<p class="paragraph">
begin </p>
<p class="paragraph">
with Form1 do </p>
<p class="paragraph">
result:= (book_title.Text <> '') and </p>
<p class="paragraph">
(FB2_file.Text <> '') and </p>
<p class="paragraph">
(GenresBox.Count > 0); </p>
<p class="paragraph">
end; </p>
<p class="paragraph">
// конец кода </p>
<p class="paragraph">
Ничего особенного в этой функции нет. Единственно из-за чего я ее вытащил, это сказать, что Вы можете и скорее даже будете вынуждены, как-то изменить ее, чтобы контроль заполнения заголовка книги был более разумным. </p>
<p class="paragraph">
А я пока вернусь к заполнению заголовка. </p>
<p class="paragraph">
В программе Вы видите три закладки Title-info, Document-info и Publish-info. В формате FB2 есть еще кое-что, но я пока это игнорировал. Предоставляю Вам такую возможность. Код Вам в руки… </p>
<p class="paragraph">
Итак Title-info </p>
<p class="paragraph">
Поле Project ― само заполнится при открытии текстового файла. При желании, Вы можете изменить, имя сохраняемого fb2 файла. </p>
<p class="paragraph">
Поле book-title действительно обязательно надо заполнить </p>
<p class="paragraph">
Теперь Genre ― Жанр. </p>
<p class="paragraph">
Ага, тут немного интереснее, есть о чем погуторить. </p>
<p class="paragraph">
Нажимаем кнопку с тремя точками. </p>
<p class="paragraph">
И открывается окошко Жанры. </p>
<p class="paragraph">
Наша цель добавить один или несколько жанров в левый ListBox. </p>
<p class="paragraph">
Выберите подходящий жанр в правом ListBoxсике и нажмите кнопку Add </p>
<p class="paragraph">
В навигации по жанрам поможет верхний ComboBox </p>
<p class="paragraph">
О коде в этом unit мне говорить лень, ничего особенного, рутина. </p>
<p class="paragraph">
Интереснее, вот, что, информация для загрузки в эти Боксики находится в unit dm </p>
<p class="paragraph">
Посмотрите на нее, и поругайте мою лень. Дело в том, что я не уверен, что этот список жанров правилен. Второе, этот список, очевидно, не окончателен. А значит он не должен быть жестко зафиксирован в программе. </p>
<p class="paragraph">
Значит, так. Вам задание ― переписать прогу, чтобы эти списки грузились или из текстового файла или из INI файла. </p>
<p class="paragraph">
Вернемся к заполнению заголовка </p>
<p class="paragraph">
Нам надо ввести данные об авторе / авторах и переводчике / переводчиках </p>
<p class="paragraph">
Так же нажимаем на соответствующую кнопочку с троеточием и работаем в открывшемся окне. </p>
<p class="paragraph">
Вы уже наверно заметили, что мне прискучило очень уж подробно расписывать код. Но в данном unit тоже ничего особенного, единственно, пришлось ввести структуру TPerson, я думаю Вы легко разберетесь зачем она мне нужна. </p>
<p class="paragraph">
Мне интереснее, совершенствование программы. Представьте ситуацию, Вы делаете 10 книг (или 100) одного автора и каждый раз делая новую книгу, заполняете опять и опять данные об этом человеке. Мне было бы лень. Ваши предложения?… </p>
<p class="paragraph">
Ну хорошо мы заполнили и Title-info и Document-info и Publish-info. </p>
<p class="paragraph">
Давайте-ка глянем, что там в коде записи файла FB2. </p>
<p class="paragraph">
// начало кода </p>
<p class="paragraph">
Procedure Make_fb2(S: string); </p>
<p class="paragraph">
begin // </p>
<p class="paragraph">
if Form1.ListBox1.Items.Count = 0 then exit; </p>
<p class="paragraph">
SytleStack.Clear; // подготовка стека стилей </p>
<p class="paragraph">
OutList.Clear; // подготовка выходного списка </p>
<p class="paragraph">
SaveDescription; </p>
<p class="paragraph">
SaveBodyFB2; // это мы уже в общем рассмотрели </p>
<p class="paragraph">
SaveEndnotes; </p>
<p class="paragraph">
OutList.Add('</FictionBook>'); // закрываем книгу </p>
<p class="paragraph">
OutList.SaveToFile(S); // Запись в файл </p>
<p class="paragraph">
showMessage('Done.'); // Сообщаем об удачном завершении </p>
<p class="paragraph">
end; </p>
<p class="paragraph">
// конец кода </p>
<p class="paragraph">
Как видите мы еще не рассмотрели две процедуры. </p>
<p class="paragraph">
// начало кода </p>
<p class="paragraph">
procedure SaveDescription; </p>
<p class="paragraph">
const </p>
<p class="paragraph">
max = 5; // может я захочу изменить число строк в массиве, тогда я изменю только одну цифру </p>
<p class="paragraph">
mas: array[1.. max] of string = </p>
<p class="paragraph">
(// массив для заголовочной части FB2 файла </p>
<p class="paragraph">
'<?xml version=«1.0» encoding=«utf-8» ?>', // как видите я делаю файл в кодировке Win </p>
<p class="paragraph">
// я не вижу смысла в применении юникода, но если речь идет не о русском языке, </p>
<p class="paragraph">
// то сделайте здесь изменение. </p>
<p class="paragraph">
'<FictionBook xmlns=«http://www.gribuser.ru/xml/fictionbook/2.0» ', </p>
<p class="paragraph">
' xmlns: l=«http://www.w3.org/1999/xlink» >', </p>
<p class="paragraph">
' <description>', </p>
<p class="paragraph">
' <title-info>' </p>
<p class="paragraph">
); </p>
<p class="paragraph">
var i: byte; </p>
<p class="paragraph">
begin </p>
<p class="paragraph">
// Выводим в выходной файл начало FB2 файла </p>
<p class="paragraph">
for i:= 1 to max do </p>
<p class="paragraph">
OutList.Add(Mas[i]); </p>
<p class="paragraph">
// конец кода </p>
<p class="paragraph">
Дальше просматриваем списки Жанров, Автором и Переводчиков и выводим оттуда информацию (если она там есть). </p>
<p class="paragraph">
Т.е. проверяем все заполненные поля форм описывающих книгу и выводим информацию в соответствующие секции заголовка книги. </p>
<p class="paragraph">
Будем считать, что с Description – покончили. </p>
<p class="paragraph">
Осталось только </p>
<p class="paragraph">
// начало кода </p>
<p class="paragraph">
procedure SaveEndnotes; </p>
<p class="paragraph">
var </p>
<p class="paragraph">
S: string; </p>
<p class="paragraph">
i: integer; </p>
<p class="paragraph">
begin </p>
<p class="paragraph">
if Form1.EndNotesList.Items.Count = 0 then exit; </p>
<p class="paragraph">
OutList.Add('<body name=«notes» ><title><p>Примечания</p>');
for i:= 0 to Form1.EndNotesList.Items.Count – 1 do
begin
S:= Form1.EndNotesList.Items[i];
OutList.Add('
<p>'+IntToStr(i+1)+'</p>'); </p>
<p class="paragraph">
OutList.Add(''+S+'
');
OutList.Add('
');
end;
OutList.Add('');
end;
// конец кода
Согласитесь, что здесь все просто, просматриваем список сносок и соблюдаем формат FB2. Но остается один маленький вопрос, а что если нам нужно будет вывести многострочную (точнее много абзачную) сноску. Да, возникает вопрос, приходят и варианты решения…
Ладно, кое-что в коде я пропустил. Но основные недостатки программы, я кажется описал. Правда, наверняка есть ляпы, которые я не заметил…
Наконец файл книги в формате FB2 создан.
На этом все? Ну нет, сейчас все авторы заканчивают книги словами «Продолжение следует» . И я замыслил по крайней мере одно продолжение. Мне кажется оно просто необходимо.
Программа должна уметь не только создавать, но и читать файл FB2. Тогда можно растягивать удовольствие изготовления книги на несколько дней, и не потребуется каждый раз заново: читать текстовый файл, расставлять стили, форматировать строки и т. д. и все это с одной и той же книгой ― ужас.
Но, как известно из математики, обратная задача всегда сложнее прямой. И т. к. я считаю, что на сегодня уже достаточно утомил Вас. Давайте рассказ о второй версии программы отложим.
Связаться со мной Вы сможете по адресу w__cat@mail.ru (обратите внимание, 2 подчеркивания, т. к. w_cat@mail.ru оказался уже занят). Предупреждаю сразу, я ленив, почту смотрю не каждый день, да и отвечать всем может и не смогу (я же не знаю сколько найдется желающих мне написать). Второе, эту почту я специально завел для этой программки, и если Вы, друзья мои завалите ее спамом или матом, я просто забуду туда дорогу.
Лицензионные условия таковы, пользуйтесь на здоровье, копируйте, переделывайте, если сможете заработать на этом деньги, буду только рад.
Итак.
Продолжение следует…