Пишем программу (в Delphi) для создания книг FB2
В начале было слово, и слово было 2 байта…
Автор мне неизвестен.
Все началось с покупки электронной книжки 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 можно получить соответствующее оформление выделенного текста
(т. е. если текст не выделен ничего не произойдет).
Тут два замечания: отмена такого форматирования возможна только вручную удалением соответствующих тегов, второе, не допустимо форматирование такого вида:
<strong> <emphasis> какой либо текст </strong></emphasis>. Можно конечно отслеживать такую ошибку и программным путем, но небольшое облегчение жизни пользователя, резко усложняет жизнь программиста.
Концевые сноски.
Книга может содержать концевые сноски. Я поленился и сделал пока так: необходимые сноски записываются в файл EndNotes.txt и этот файл должен находится в папке программы.
Внимание! Каждая сноска - одна строка в файле.
В тексте книги в местах сносок надо расставить значки тильды - ~
Ударения.
В первой же книге, которую я делал, в одном слове мне потребовалось сделать ударение и поэтому пришлось ввести значок «крышки» ^
Наконец добрались.
Казалось бы, что проще, бери строку за строкой и вперед…
// начало кода
with Form1.ListBox1 do
for i:= 0 to Count - 1 do // просматриваем текст абзац за абзацем