Читаем Пишем программу для создания книг FB2 (СИ) онлайн бесплатно
Пишем программу для создания книг FB2 (СИ)
Подробный комментарий к исходному коду программы создающей книжку в формате FB2.
Компьютеры и Интернет / Программное обеспечениеAnnotation
Подробный комментарий к исходному коду программы создающей книжку в формате FB2.
Пишем программу (в Delphi) для создания книг FB2
Введение
Начинаем
Читаем текстовый файл
Устанавливаем стиль заголовка
Редактирование строки
Создание FB2
Структура
Приложение
Unit1.dfm
Код файла Unit1.pas соответствующего Form1
genres.dfm
genres.pas
EditStr.dfm
EditStr.pas
authors.dfm
authors.pas
dm.pas
my_FB2.dpr
UmFB2.pas
notes
1
Пишем программу (в Delphi) для создания книг FB2
Введение
Все началось с покупки электронной книжки LBook eReader V3.
Затем я убедился, что книги, лучше всего читаются в формате FB2.
Потом мне захотелось оцифровать книги моего любимого писателя Кальмана Миксата, и тут я увидел, что все не так просто…
Да я в этой «тусовке» недавно и возможно чёто не понимаю, но все свои проблемы я привык решать программным путем.
Состряпал программку, конечно еще сырую, а потом вспомнил опыт Линуса Торвальдса и подумал:
- А, кину я исходник в рунет, и может добрые люди выкормят, вырастят моего ребенка и выведут в люди.
Вы можете спросить, а чего же ты сам это не сделаешь? Во-первых, меня ждут другие "великие дела", во-вторых, я уверен, что коллективным разумом, можно сделать больше и быстрее…
Писал я в своем любимом Delphi (Delphi 6) - но думаю это не принципиально, перевести можно в любой язык.
Это не учебник Delphi и основы, я рассказывать не собираюсь, но постараюсь расписать как можно подробней.
В программе используются только стандартные компоненты Дельфи.
Начинаем
План работы:
* Берем текстовый файл
* Присваиваем строчкам стили
* Делаем файл FB2.
Общие принципы программы.
Содержание книги будет хранится в ListBox1.
Каждая строчка в ListBox1 будет содержать абзац текста и будет начинаться с идентификатора стиля абзаца, например:
// начало примера.
H1 | Кальман Миксат. ЧЕРНЫЙ ГОРОД
H2 | ЧАСТЬ ПЕРВАЯ
H3 | ГЛАВА ПЕРВАЯ.
S| В которой содержатся сведения и подробности, весьма важные для читателя
N| Пал Гёргей был самым примечательным вице-губернатором Спеша во времена Тёкёли
// конец примера.
Символ | отделяет информацию о стиле от строки текста. Теперь надо объяснить, что означают эти буковки.
С H1 по H5: заголовки разных уровней структуры книги (части, главы, разделы и т. п.), я посчитал, что 5 уровней более чем достаточно, мне пока требовалось только три.
S: Subtitle - подзаголовок.
N: Normal - обычный абзац.
Еще могут использоваться стили:
E: Epigraph - эпиграф
T: Text-author - автор цитаты / эпиграфа
P: Poem - стихи
-: None строка будет игнорироваться при записи FB2 файла.
Если потребуется Вы добавите еще…
Читаем текстовый файл
При чтении текстового файла, к каждой строчке прибавляется начало ' N| ' т. к. форматирование еще не сделано и все строки одинаково обычны.
// начало кода
procedure LoadTXT(FName: string);
var
L: TStringList;
i, j: integer;
s, ss: string;
begin
L:= TStringList.Create; // создаем временный список
L.LoadFromFile(fname); // читаем из файла // можно сделать грамотнее с помощью try
for i:= 0 to L.Count - 1 do// просматриваем текст
begin
s:= ''; ss:= L[i];
for j:= 1 to length(Ss) do
begin // просматриваем строку
case ss[j] of
'<': S:= S + '<'; // знак < вызывает сбой в читалке. т. к. она думает что дальше следует тэг
'>': S:= S + '>'; // заменяем, на всякий случай
'^': S:= S + '^'; // этот символ будет использован в служебных целях
'~': S:= S + '~'; // - // -
'&': S:= S + '&';
else S:= S + ss[j]; // иначе, претензий нет, символ добавляем к строке
end; // case
end; // обработка строки завершена
L[i]:= ' N| ' + S; // в начало каждой строки вводим указатель стиля Normal
end; // обработка текста завершена
Form1.ListBox1.Items.Assign(L); // сбрасываем список в ListBox
L.Free; // удаляем временный список
end;
// конец кода
Если файл считан, теперь мы можем его форматировать.
Просматриваем текст книги, выделяем нужную строку, выбираем необходимый стиль и нажимаем кнопку
[>]
При этом вызывается процедура ChangeStyle(TmyStyle(RG.itemindex));
Как параметр она получает стиль из радио - списка RG.
К сожалению это все делать надо ручками. Конечно, возможна некая автоматизация, но пока идет речь об упрощенной программе…
Процедура считывает выделенную строку из списка ListBox1, удаляет сведения о типе и записывает строку на старое место с новым стилем.
// начало кода
procedure ChangeStyle(LStyle: TmyStyle);
var
n, curIndex: integer;
S: string;
begin
with Form1.ListBox1 do
begin
curIndex:= ItemIndex; // читаем текущий индекс в списке ListBox
if curIndex = -1 then exit; // если ничего не выделено выходим
S:= Items[curIndex]; // считываем текущую строку
n:= pos('|', s); // находим разделитель
/ / хотя это лишнее, n всегда = 4 / когда писал это еще не было ясно, утрясался формат…
// в окончательном варианте n можно удалить
delete(S, 1, n+1); // удаляем информацию о стиле
// Записывается строка с новым стилем. Приводить SetStyle не буду, она очень простенькая
Items[curIndex]:= SetStyle1(LStyle)+ S;
if ItemIndex < Items.Count - 1
then ItemIndex:= ItemIndex+1;
SetFocus; // активным снова становится список с содержимым книги.
end;
end;
// конец кода
(Одно предложение: можно, и не трудно, предоставить пользователю возможность возврата старого стиля)
Теперь о расстановке заголовков
Для этой работы предназначены три кнопки: [+] [H1] [-]. Вообще-то средняя кнопка будем менять свое название, и показывать этим текущий (в данном месте текста) стиль заголовка.
Посмотрим, как это делается:
При любом клике на ListBox вызывается процедура ShowHeadStyle ее параметром является индекс выделенной строки.
// начало кода
procedure ShowHeadStyle(n: integer);
var
LStyle: TmyStyle;
begin
LStyle:= ScanUpStyle(n); // получаем тип заголовка к которому относится эта строка
Form1.Button2.Caption:= SetStyle(LStyle); // меняем название кнопки
Form1.Button2.Tag:= integer(LStyle); // запоминаем этот стиль, чтобы потом меньше возиться.
end;
// конец кода
Теперь посмотрим, как мы получаем информацию о стиле.
Элементарно, Ватсон!
// начало кода
function ScanUpStyle(n: integer):TmyStyle;
var
i: integer;
LStyle: TmyStyle;
begin
with Form1.ListBox1 do
for i:= n downto 0 do
begin // просматриваем список от заданной строки вверх
GetStyle(Items[i], LStyle); // получаем стиль строки
if LStyle in [H1..H5] then
begin // если стиль строки заголовочный
result:= LStyle; // записываем его в результат
exit; // и выходим, нечего больше время терять!
end;
end; // если дошли до начала списка, а заголовков не найдено…
result:= H1; // присваиваем тип заголовка H1
end;
// конец кода
Устанавливаем стиль заголовка
Выбираем строку в тексте
И если указанный на кнопке стиль подходит, нажимаем ее.
При этом вызывается процедура ChangeStyle(TmyStyle(Button2.Tag));
Параметром ее будет ранее сохраненные сведения о текущем стиле заголовка.
Процедура ChangeStyle описана ранее.
Теперь кнопки [+] и [-]
Код процедур аналогичен, разница только в одной строчке
// начало кода
procedure TForm1.Button5Click(Sender: TObject);
var // кнопка плюс
LStyle: TmyStyle;
begin
LStyle:= TmyStyle(Button2.Tag); // получаем текущий стиль
if LStyle < H5 then ChangeStyle(Succ(LStyle)); // если он не слишком велик, прибавляем единицу
// а для кнопки минус, вот эта строчка. Вычитается единичка, если есть откуда вычитать
// if LStyle > H1 then ChangeStyle(Pred(LStyle));
end;
// конец кода
Редактирование строки
Двойной щелчок на строке и открывается окно редактирования
Текст можно исправить или строку разбить на несколько. После нажатия ОК все содержимое записывается в книгу с сохранением старого стиля.
Нажатием кнопок Bold и Italic можно получить соответствующее оформление выделенного текста
(т. е. если текст не выделен ничего не произойдет).
Тут два замечания: отмена такого форматирования возможна только вручную удалением соответствующих тегов, второе, не допустимо форматирование такого вида:
Концевые сноски.
Книга может содержать концевые сноски. Я поленился и сделал пока так: необходимые сноски записываются в файл EndNotes.txt и этот файл должен находится в папке программы.
Внимание! Каждая сноска - одна строка в файле.
В тексте книги в местах сносок надо расставить значки тильды - ~
Ударения.
В первой же книге, которую я делал, в одном слове мне потребовалось сделать ударение и поэтому пришлось ввести значок «крышки» ^
Создание FB2
Наконец добрались.
Казалось бы, что проще, бери строку за строкой и вперед…
// начало кода
with Form1.ListBox1 do
for i:= 0 to Count - 1 do // просматриваем текст абзац за абзацем
begin
S:= Items[i]; // считываем строку
Ss:= GetStyle(S, CurStyle); // получаем чистую строку и стиль
s:= ''; // подготавливаемся к преобразованию строки
if ss <> '' then
for j:= 1 to length(Ss) do
begin // просматриваем строку посимвольно
case ss[j] of
'~': begin // если это концевая сноска
S:= S + ''
+IntToStr(EndNotes_count)+'';
inc(EndNotes_count); // увеличиваем счетчик сносок
end;
'^': S:= S + '́'; // ставим ударение
else S:= S + ss[j]; // иначе записываем символ в итоговую строку
end; // case
end;
…
// тут я пока немножко пропущу
…
// анализ стилей
case CurStyle of // в зависимости от стиля абзаца
Norm,Epig,Citat: OutList.Add('
'+S+'
');H1..H5: StyleStucture; // Heading
Sub: OutList.Add('
// конец кода
Давайте рассмотрим все по порядку:
Начнем со стихов. В стандарте FB2 используется три тега для работы со стихами, я использую только один стиль "P".
Для разделения стихов на строфы я предлагаю использовать пустые строки помеченные стилем "P".
// начало кода
if (CurStyle <> oldStyle) then // если предыдущий стиль отличен от текущего
begin // а нынешний стиль есть в данном списке, то значит надо начинать нужный блок.
case CurStyle of // начало блока
Poem: OutList.Add('
Epig: OutList.Add('
Citat: OutList.Add('');
end; // case начало блока
end;
// конец кода
А для обработки стиля используется следующие строки
// начало кода
case CurStyle of // в зависимости от стиля абзаца
Norm,Epig,Citat: OutList.Add('
'+S+'
');Poem: begin
if S = ''
then OutList.Add('
else OutList.Add('
end;
// конец кода
В случае Нормальное стиля, Эпиграфа и Цитаты, просто добавляются абзацы, а для стихов еще отслеживается пустая строка…
Как видите блоки не завершены. Эту функцию выполняет следующий код.
// начало кода
if (CurStyle <> oldStyle) and (CurStyle <> Auth) then
begin
case oldStyle of // завершение предыдущего блока
Poem: OutList.Add('');
Epig: OutList.Add('');
Citat: OutList.Add('');
end; // case завершение предыдущего блока
end;
// конец кода
Но как Вы увидите в исходнике последний программный кусок находится выше предыдущего (и вообще все немного не так), но в данном тексте, мне пришлось расположить их так для последовательного, логичного объяснения, а в программе: сначала проверяется завершенность предыдущих блоков, затем при необходимости начинается другой, а затем обрабатываем текущий стиль.
В данном сочинении, я часто буду пользоваться таким приемом, отступлением от порядка следования текста в исходнике, что делать, человеческая логика и машинная не совсем совпадают.
Если Вы внимательно следите за процессом, то заметили " and (CurStyle <> Auth) " в предыдущем кусочке о начале блока, я это дело опустил, что бы не затуманивать описание.
Это достаточно забавный код призван выполнить требования формата:
// начало цитаты
Внутри тэгов
// конец цитаты
А теперь как это я сделал.
// начало кода
Auth: begin
OutList.Add('
if oldStyle in [Poem, Epig, Citat]
then CurStyle:= oldStyle;
// т. е. корректно отработается закрытие родительских блоков
end;
// конец кода
Т.к. естественно я сделаю эту брошюрку с помощью своей программки. Опробуем вышеизложенные методы форматирования на следующем оптимистичном стихотворении.
Я с содроганием смотрел
На эту кость иного века…
И нас такой же ждет удел:
Пройдет и время человека…
Умолкнет славы нашей шум;
Умрут о людях и преданья;
Всё, чем могуч и горд наш ум,
В иные не войдет созданья.
Оледенелою звездой
Или потухнувшим волканом
Помчится, как корабль пустой,
Земля небесным океаном.
И, странствуя между миров,
Воссядет дух мимолетящий
На остов наших городов,
Как на гранит
…
Так разум в тайнах бытия
Читает нам… Но сердце бьется,
Надежду робкую тая -
Авось он, гордый, ошибется!
Структура
Теперь, после лирического отступления, самое интересное: структурирование книги.
Книга может иметь разделение на части, главы, тома и книги, ну мало ли чего придумает автор…
В 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('
end;





