Создание электронных книг в формате FictionBook 2.1: практическое руководство (pre-release)
Шрифт:
Peзюме. Хорошая вещь. 100% must have для тех, у кого на машине установлен OO Writer.
§ 3.7 Написание собственного конвертора
Все перечисленные выше способы хороши лишь для так называемых среднестатических входных файлов. Когда попадается нечто нестандартное, остается только, сцепив зубы, долго и нудно склеивать файлы, вылавливать куски текста, редактировать погрешности, и т.д. и т.п.
Решить эти проблемы может собственноручное написание конвертора. Помимо возможности тонкой настройки софта под конкретную задачу, этот метод имеет еще одну положительную сторону: претензии по поводу кривизны решений предъявлять просто некому. Кроме как самому себе…
Для написания конвертора наиболее подходящи скриптовые языки программирования, заточенные для работы с текстом и поддерживающие регулярные выражения. TCL, Java, Perl, Python… Лично я остановил свой выбор на самом молодом и многобещающем из них. Имеется в виду язык программирования Ruby.
Ruby был создан в середине 90-х японским программистом Юкихиро Мацумото. Это многогранный, элегантный и в то же время очень простой для изучения язык. Он вобрал все лучшее, что было у его предшественников: мощь регулярных выражений Perl, объектно-ориентированное программирование Java и Python, помноженные на феноменальные изящество и функциональность.
Скачать транслятор Ruby
Вариант Ruby для Win32 представлен в двух вариантах. Обычный инсталлятор (rubyxxx-xx.exe)и zip-архив — ruby-xxx-pxxx-i386-mswin32.zip, где xxx — номера версий и обновления. На момент написания этого текста была доступна стабильная версия 1.8.6.
Для наших целей вполне подойдет второй вариант — zip-архив.
Только нужно будет папку, где находится запускной файл интерпретатора добавить в список папок, доступных по умолчанию. Для этого щелкаем правой кнопкой мыши по иконке «My computer», на панели «Advanced» жмем кнопку «Environment Variables». В списке «System variables» находим переменную Path и щелкаем по кнопке «Edit». В открывшемся окошке в поле «Variable Value» через «;» без пробела добавляем путь к нужной папке. Например C:\TOOLS\RUBY\bin.
В принципе, для работы скрипта жизненно необходимы лишь два файла: exe-шник транслятора (ruby.exe) и служебная dll-ка (msvcrt-ruby18.dll). Поэтому, если не требуется использование библиотек, то можно просто забросить эти два файла в папку Windows, или любую другую, доступную по переменной Path. В крайнем случае, можно просто поместить их в ту же папку, где находятся скрипты.
Разумеется, описывать программирование на языке Ruby прямо здесь нет ни необходимости, ни возможности. Это тема для отдельной книги, которуя, с Божьей помощью, я, возможно, напишу. А здесь ограничимся комментариями в самих скриптах.
Если же вы не знаете, где взять документацию по Ruby, то вам прямая дорога на сайт www.flazx.com, где, среди множества другой ценной литературы, можно найти и исчерпывающую подборку книг по Ruby. Правда, англоязычных, но зато много и бесплатно. Из русскоязычных изданий могу посоветовать книгу М. Фицджеральда «Изучаем Ruby» (на момент написания книги была доступна только на бумаге), а также труд П. Фултона «Программирование на языке Ruby» (доступен в Сети).
Данный скрипт размещается исключительно как классический пример. Так сказать для затравки.
Скрипт выполняет простейшее преобразование текста, вмешательство в структуру будущей fb2-книги сведено практически к нулю. Но его ничего не стоит доработать. Например, чтобы он выделял главы, подзаголовки, при этом структурируя будущий файл. Вообще, средствами Ruby можно организовать такую интеллектуальную обработку текста, которая оставит Any2FB2 далеко позади.
# Скрипт для конвертации форматированного текста DOS-866 в FB2
# (C) Юзич, апрель 2008 г.
#
# запускать из директории, где находится входной файл
# запуск: ruby dtxt_fb2.rb <input.txt>
#
# массив для перекодировки текста. Псевдографика заменяется на код 135,
# символы, отсутствующие в кодировке win-1251 - на код 138
doswin = [192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135,
135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135,
135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, 135,
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
168, 184, 170, 186, 175, 191, 161, 162, 176, 149, 183, 138, 185, 164, 138, 138]
# считываем входной файл в одну строку
wtext=ARGF.readlines.to_s
ARGF.close
wtext.gsub!(/\n(\S)/,' \1') # склеиваем строки абзацев
wtext.squeeze!(" ") # убираем лишние пробелы
wtext.gsub!(/^ /,"") # убираем пробелы в начале строки
# перекодировка текста
for i in 0..(wtext.length-1)
case wtext[i]
when 20: wtext[i] = 182 # обработать символ "Пи"
when 21: wtext[i] = 167 # обработать символ "параграф"
when 128..255: wtext[i] = doswin[wtext[i]-128] # обработать все остальное
end
end
wtext.gsub!("...", (133).chr) # многоточие - в символ "многоточие"
wtext.gsub!(/(\s)-\s/,'\1'+(151).chr+" ") # компьютерное тире - в типографское
# порубим строку в массив
wtext = wtext.split(/\n/)
wtext.collect! do |line|
if line =="" then
line = "<empty-line/>" # пустая строка?
else
line=""+line+"" # нет, не пустая
end
end
# добавляем заголовок
wtext.insert(0,"<?xml version=\"1.0\" encoding=\"windows-1251\"?>
<FictionBook xmlns=\"http://www.gribuser.ru/xml/fictionbook/2.0\" xmlns:l=\"http://www.w3.org/1999/xlink\">
<description>
<title-info>
<genre>nonfiction</genre>
<lang>ru</lang>
</title-info>
<document-info>
<program-used>Yuzich Ruby script (dostxt -> FB2)</program-used>
</document-info>
</description>
<body>
<section>")
# добавляем хвост
wtext << "</section>\n</body>\n</FictionBook>\n"
# сохраняем выходной файл
wfile = File.new(ARGF.filename.dup.gsub!(/\.txt/i,".fb2"),"w")
wfile.puts wtext
wfile.close
После того, как скрипт отработает, в директории появится файл <имя_исходного_файла>.fb2. Небольшая правка в FB Editor и он будет полностью готов к распространению.
Эта задачка гораздо интереснее первой. Как раз тот случай, когда написание специализированного конвертора полностью оправдано.
На ресурсе http://www.wisesoft.ru находится множество различных журналов. Форматы в котором они представлены, различаются, Но один из них особо привлекателен для преобразования в FB2. Я говорю о журналах которые представлены в виде пачки html-файлов («Хакер», «Хакер-спец», «Мобильные компьютеры», «Хулиган» и др.). При всей простоте, конвертированию стандартными средствами этот способ хранения данных поддается с трудом.
Более близкое знакомство выявило следующее. Имеется файл index.htm с оглавлением. Статьи представляют собой кучки html-файлов, разбитые по папкам. Текст представляет собой обычные абзацы, обрамленные тэгами «». Оформление html-ок везде стандартное.
К сожалению, такая
Наша задача заключается в том, чтобы вырезать куски текста из html-файлов, склеить их в один файл.
Скрипт, представленный ниже, не только блестяще справляется с этим, но и выдает на выходе вполне приличный FB2, с заполненным заголовком, готовой аннотацией и разбитый на секции.
# Скрипт для конвертации журналов с WiseSoft.ru в FB2
# (C) Юзич, апрель-май 2008 г.
#
# поддерживаемый формат файлов: пачка html, журналы сделанные c ноября 2003 г. по ноябрь 2006 г. включительно.
# примечание: изменения в декабре 2006 и марте 2007 - некритичные
# теоретически должен обрабатывать журналы сделанные по июль 2007 включительно
# но уже с мая 2007 в тексте могут попадаться дополнительные тэги, мешающие правильной конвертации
# хотя все это касается, в основном, журнала "Хакер" ("Хакер-спец").
# "Мобильные компьютеры", к примеру, по-прежнему, как шли, так и идут в старом формате
# да и чтобы обрабатывать журналы, сделанные до ноября 2003, достаточно подправить ключевые фразы
#
# запускать из директории, где находится файл-оглавление журнала (index.htm)
# запуск: ruby ws_j_cnv.rb
#
# считываем файл-оглавление в строку
wfile=File.open("index.htm")
ltext=wfile.readlines.to_s
wfile.close
# выгрызаем заголовок
fbtitle=/<SPAN CLASS="titleSet">(.+)<font color="#FF0000">(#\d{1,3})<\/font>(.+)<\/SPAN>/.match(ltext).captures
# выгрызаем ссылки на статьи и разделители
filtr=/<SPAN CLASS="minSet">(.+)<\/SPAN>|<a href="(\d{3}\/\d.htm)">(.+)<\/a>/
lmas = ltext.grep(filtr)
# начинаем формировать выходной текст
outtext="<title>"+fbtitle[0]+fbtitle[1]+fbtitle[2]+"</title>\n"
# начинаем формировать аннотацию
annotation="Содержание номера:\n"
# флажок открытой секции раздела
flagSect=false
# прокручиваем список ссылок
lmas.each do |line|
if line.include? "SPAN" then # ССЫЛКА ИЛИ РАЗДЕЛИТЕЛЬ?
if flagSect then outtext=outtext+"</section>\n" end # РАЗДЕЛИТЕЛЬ. Секцию закрывать надо?
/<SPAN CLASS="minSet">(.+)<\/SPAN>/.match(line) # выгрызаем разделитель...
outtext=outtext+"<section>\n<title>"+$1+"</title>\n" # открываем секцию раздела
annotation=annotation+""+$1+":\n" # и дополняем аннотацию
flagSect=true # секция осталась открыта...
else # НЕ-ЕТ, ВСЕ-ТАКИ ССЫЛКА...
filtr.match(line)
pathf = $2 # берем путь к первому файлу статьи...
annotation=annotation+""+(149).chr+" "+$3+"\n" # дополняем аннотацию...
puts pathf[0..2] # это чтобы не скучно было ждать...
outtext=outtext+"<section>\n" # открыли секцию...
while File::exists?(pathf) do # обрабатываем статью
# считали файлик в текстовую строку...
wfile=File.open(pathf)
wtext=wfile.readlines.to_s
wfile.close
wtext[/<html>.+?<\/p>/m]="" # чик! головка...
wtext[/<center>.+<\/html>/m]="" # чик! хвостик...
# заголовок статьи оставлять?
wtext = pathf[4,2].to_i == 1 ? wtext.sub(/.*\n(.*\n.*\n).*\n.*\n/,' \1') : wtext.sub(/.*\n.*\n.*\n.*\n.*\n/,' \1')
outtext=outtext+wtext # оставшийся текст - к основному массиву
# модифицируем имя файла
if pathf[4,2].to_i >= 10
pathf[4,2] = (pathf[4,2].to_i + 1).to_s
else
pathf[4] = (pathf[4,2].to_i + 1).to_s
end
end # конец цикла
outtext=outtext+"</section>\n" # закрываем секцию
end # усе. статью оформили...
end # все статьи собрали в одну строку...
# модифицируем выходной текст под FB2
outtext.gsub!("<big><big><big><strong>","<title>")
outtext.gsub!("</strong></big></big></big>","</title>")
outtext.gsub!("<big><strong>","<epigraph>")
outtext.gsub!("</strong></big>","</epigraph>")
outtext.gsub!(" ",(160).chr)
outtext.gsub!(/&(?!lt;|gt;)/,"&")
annotation.gsub!(/&(?!lt;|gt;)/,"&")
outtext.gsub!("<br>","")
outtext.gsub!("<br>","")
# чистим мусор
outtext.gsub!(/\x01|\x12|\x18|\x1E/, "?") # удаляем непечатные символы
# корректируем неправильное использование "<" и ">"
# заодно прибиваем ненужные тэги
outtext.gsub!(/(.*)(<\/p>)/) do |line|
subl1,subl2,subl3 = $1,$2,$3
subl2.gsub!("<","\x8b")
subl2.gsub!(">","\x9b")
line=subl1+subl2+subl3
end
# а линки выделим жирным
outtext.gsub!(/\x8Ba href.+?\x9B(.*?)\x8B\/a\x9B/) {|line| line="<strong>"+$1+"<\/strong>"}
# компьютерное тире - в типографское
outtext.gsub!(/\s-\s/," \x97 ")
outtext.gsub!("-", "\x97")
annotation.gsub!(/\s-\s/," \x97 ")
# добавляем заголовок
outtext="<?xml version=\"1.0\" encoding=\"windows-1251\"?>
<FictionBook xmlns=\"http://www.gribuser.ru/xml/fictionbook/2.0\" xmlns:l=\"http://www.w3.org/1999/xlink\">
<description>
<title-info>
<genre>nonfiction</genre>
<author>
<nickname>Редакция журнала</nickname>
</author>
<book-title>"+fbtitle[0]+fbtitle[1]+fbtitle[2]+"</book-title>
<annotation>\n"+annotation+"</annotation>
<date>"+fbtitle[2][-7,4]+"</date>
<lang>ru</lang>
<sequence name=\""+fbtitle[0]+"\" number=\""+fbtitle[1][1,2]+"\"/>
</title-info>
<document-info>
<program-used>Yuzich Ruby script (WiseSoft -> FB2)</program-used>
<src-url>http://www.wisesoft.ru</src-url>
</document-info>
</description>
<body>\n"+outtext
if flagSect then outtext=outtext+"</section>" end # если надо, закроем последнюю секцию раздела
outtext=outtext+"</body>\n</FictionBook>\n" # добавляем хвост
# в имени выходного файла не должно быть двоеточия
fbtitle[0].gsub!(":","-")
# и типографские кавычки - это не есть хорошо
fbtitle[0].gsub!((171).chr,"`")
fbtitle[0].gsub!((187).chr,"`")
# сохраняем выходной файл
wfile = File.new(fbtitle[0]+fbtitle[1]+".fb2","w")
wfile.puts outtext
wfile.close