"Создание электронных книг в формате FictionBook 2.1: практическое руководство (beta 4)" - читать интересную книгу автора (Кондратович Михаил Иосифович)

§ 3.7 Написание собственного конвертора

Все перечисленные выше способы хороши лишь для так называемых среднестатических входных файлов. Когда попадается нечто нестандартное, остается только, сцепив зубы, долго и нудно склеивать файлы, вылавливать куски текста, редактировать погрешности, и т.д. и т.п.

Решить эти проблемы может собственноручное написание конвертора. Помимо возможности тонкой настройки софта под конкретную задачу, этот метод имеет еще одну положительную сторону: претензии по поводу кривизны решений предъявлять просто некому. Кроме как самому себе…

Для написания конвертора наиболее подходящи скриптовые языки программирования, заточенные для работы с текстом и поддерживающие регулярные выражения. TCL, Java, Perl, Python… Лично я остановил свой выбор на самом молодом и многобещающем из них. Имеется в виду язык программирования Ruby.

Ruby был создан в середине 90-х японским программистом Юкихиро Мацумото. Это мощный, многогранный и в то же время очень простой для изучения язык. Он вобрал все лучшее, что было у его предшественников: мощь регулярных выражений Perl, объектно-ориентированное программирование Java и Python, помноженные на феноменальные изящество и функциональность.

Установка Ruby

Скачать транслятор Ruby можно с официального сайта проекта www.ruby-lang.org.

Вариант 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 до сих пор не выходило.

Пример первый.Конвертирование форматированного dos-текста

Данный скрипт размещается исключительно как классический пример. Так сказать для затравки.

Скрипт выполняет простейшее преобразование текста, вмешательство в структуру будущей fb2-книги сведено практически к нулю. Но его ничего не стоит доработать. Например, чтобы он выделял главы, подзаголовки, при этом структурируя будущий файл. Вообще, средствами Ruby можно организовать такую интеллектуальную обработку текста, которая оставит Any2FB2 далеко позади.

# Скрипт для конвертации форматированного текста DOS-866 в FB2

# (C) Юзич, апрель 2008 г.

#

# запускать из директории, где находится входной файл

# запуск: ruby dtxt_fb2.rb lt;input.txtgt;

#

# массив для перекодировки текста. Псевдографика заменяется на код 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 = "lt;empty-line/gt;" # пустая строка?

  else

    line="lt;pgt;"+line+"lt;/pgt;" # нет, не пустая

  end

end

# добавляем заголовок

wtext.insert(0,"lt;?xml version=\"1.0\" encoding=\"windows-1251\"?gt;

lt;FictionBook xmlns=\"http://www.gribuser.ru/xml/fictionbook/2.0\" xmlns:l=\"http://www.w3.org/1999/xlink\"gt;

lt;descriptiongt;

lt;title-infogt;

lt;genregt;nonfictionlt;/genregt;

lt;langgt;rult;/langgt;

lt;/title-infogt;

lt;document-infogt;

lt;program-usedgt;Yuzich Ruby script (dostxt -gt; FB2)lt;/program-usedgt;

lt;/document-infogt;

lt;/descriptiongt;

lt;bodygt;

lt;sectiongt;")

# добавляем хвост

wtext lt;lt; "lt;/sectiongt;\nlt;/bodygt;\nlt;/FictionBookgt;\n"

# сохраняем выходной файл

wfile = File.new(ARGF.filename.dup.gsub!(/\.txt/i,".fb2"),"w")

wfile.puts wtext

wfile.close

После того, как скрипт отработает, в директории появится файл lt;имя_исходного_файлаgt;.fb2. Небольшая правка в FB Editor и он полностью готов.

Пример второй.Конвертирование журналов c WiseSoft.ru

Эта задачка гораздо интереснее первой. Как раз тот случай, когда написание специализированного конвертора полностью оправдано.

На ресурсе http://www.wisesoft.ru находится множество различных журналов. Форматы в котором они представлены, различаются, Но один из них особо привлекателен для преобразования в FB2. Я говорю о журналах которые представлены в виде пачки html-файлов («Хакер», «Хакер-спец», «Мобильные компьютеры», «Хулиган» и др.).  При всей простоте, конвертированию стандартными средствами этот способ хранения данных поддается с трудом.

Более близкое знакомство выявило следующее. Имеется  файл index.htm с оглавлением. Статьи представляют собой кучки html-файлов, разбитые по папкам. Текст представляет собой обычные абзацы, обрамленные тэгами «lt;pgt;». Оформление html-ок везде стандартное.

К сожалению, такая лафа наблюдается не везде. С декабря 2006 года формат журналов начал эволюционировать. Статьи стали вмещаться в одну 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=/lt;SPAN CLASS="titleSet"gt;(.+)lt;font color="#FF0000"gt;(#\d{1,3})lt;\/fontgt;(.+)lt;\/SPANgt;/.match(ltext).captures

# выгрызаем ссылки на статьи и разделители

filtr=/lt;SPAN CLASS="minSet"gt;(.+)lt;\/SPANgt;|lt;a href="(\d{3}\/\d.htm)"gt;(.+)lt;\/agt;/

lmas = ltext.grep(filtr)

# начинаем формировать выходной текст

outtext="lt;titlegt;lt;pgt;"+fbtitle[0]+fbtitle[1]+fbtitle[2]+"lt;/pgt;lt;/titlegt;\n"

# начинаем формировать аннотацию

annotation="lt;pgt;Содержание номера:lt;/pgt;\n"

# флажок открытой секции раздела

flagSect=false

# прокручиваем список ссылок

lmas.each do |line|

  if line.include? "SPAN" then     # ССЫЛКА ИЛИ РАЗДЕЛИТЕЛЬ?

    if flagSect then outtext=outtext+"lt;/sectiongt;\n" end # РАЗДЕЛИТЕЛЬ. Секцию закрывать надо?

    /lt;SPAN CLASS="minSet"gt;(.+)lt;\/SPANgt;/.match(line) # выгрызаем разделитель...

    outtext=outtext+"lt;sectiongt;\nlt;titlegt;lt;pgt;"+$1+"lt;/pgt;lt;/titlegt;\n" # открываем секцию раздела

    annotation=annotation+"lt;pgt;"+$1+":lt;/pgt;\n" # и дополняем аннотацию

    flagSect=true # секция осталась открыта...

  else # НЕ-ЕТ, ВСЕ-ТАКИ ССЫЛКА...

    filtr.match(line)

    pathf = $2 # берем путь к первому файлу статьи...

    annotation=annotation+"lt;pgt;"+(149).chr+" "+$3+"lt;/pgt;\n" # дополняем аннотацию...

    puts pathf[0..2] # это чтобы не скучно было ждать...

    outtext=outtext+"lt;sectiongt;\n" # открыли секцию...

    while File::exists?(pathf) do # обрабатываем статью

      # считали файлик в текстовую строку...

      wfile=File.open(pathf)

      wtext=wfile.readlines.to_s

      wfile.close

      wtext[/lt;htmlgt;.+?lt;\/pgt;/m]="" # чик! головка...

      wtext[/lt;centergt;.+lt;\/htmlgt;/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 gt;= 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+"lt;/sectiongt;\n" # закрываем секцию

  end # усе. статью оформили...

end # все статьи собрали в одну строку...

# модифицируем выходной текст под FB2

outtext.gsub!("lt;pgt;lt;biggt;lt;biggt;lt;biggt;lt;stronggt;","lt;titlegt;lt;pgt;")

outtext.gsub!("lt;/stronggt;lt;/biggt;lt;/biggt;lt;/biggt;lt;/pgt;","lt;/pgt;lt;/titlegt;")

outtext.gsub!("lt;pgt;lt;biggt;lt;stronggt;","lt;epigraphgt;lt;pgt;")

outtext.gsub!("lt;/stronggt;lt;/biggt;lt;/pgt;","lt;/pgt;lt;/epigraphgt;")

outtext.gsub!("amp;nbsp;",(160).chr)

outtext.gsub!(/amp;(?!lt;|gt;)/,"amp;amp;")

annotation.gsub!(/amp;(?!lt;|gt;)/,"amp;amp;")

outtext.gsub!("lt;brgt;","lt;/pgt;lt;pgt;")

outtext.gsub!("lt;brgt;","lt;/pgt;lt;pgt;")

# чистим мусор

outtext.gsub!(/\x01|\x12|\x18|\x1E/, "?")  # удаляем непечатные символы

# корректируем неправильное использование "lt;" и "gt;"

# заодно прибиваем ненужные тэги

outtext.gsub!(/(lt;pgt;)(.*)(lt;\/pgt;)/) do |line|

  subl1,subl2,subl3 = $1,$2,$3

  subl2.gsub!("lt;","\x8b")

  subl2.gsub!("gt;","\x9b")

  line=subl1+subl2+subl3

end

# а линки выделим жирным

outtext.gsub!(/\x8Ba href.+?\x9B(.*?)\x8B\/a\x9B/) {|line| line="lt;stronggt;"+$1+"lt;\/stronggt;"}

# компьютерное тире - в типографское

outtext.gsub!(/\s-\s/," \x97 ")

outtext.gsub!("lt;pgt;-", "lt;pgt;\x97")

annotation.gsub!(/\s-\s/," \x97 ")

# добавляем заголовок

outtext="lt;?xml version=\"1.0\" encoding=\"windows-1251\"?gt;

lt;FictionBook xmlns=\"http://www.gribuser.ru/xml/fictionbook/2.0\" xmlns:l=\"http://www.w3.org/1999/xlink\"gt;

lt;descriptiongt;

lt;title-infogt;

lt;genregt;nonfictionlt;/genregt;

lt;authorgt;

lt;nicknamegt;Редакция журналаlt;/nicknamegt;

lt;/authorgt;

lt;book-titlegt;"+fbtitle[0]+fbtitle[1]+fbtitle[2]+"lt;/book-titlegt;

lt;annotationgt;\n"+annotation+"lt;/annotationgt;

lt;dategt;"+fbtitle[2][-7,4]+"lt;/dategt;

lt;langgt;rult;/langgt;

lt;sequence name=\""+fbtitle[0]+"\" number=\""+fbtitle[1][1,2]+"\"/gt;

lt;/title-infogt;

lt;document-infogt;

lt;program-usedgt;Yuzich Ruby script (WiseSoft -gt; FB2)lt;/program-usedgt;

lt;src-urlgt;http://www.wisesoft.rult;/src-urlgt;

lt;/document-infogt;

lt;/descriptiongt;

lt;bodygt;\n"+outtext

if flagSect then outtext=outtext+"lt;/sectiongt;" end # если надо, закроем последнюю секцию раздела

outtext=outtext+"lt;/bodygt;\nlt;/FictionBookgt;\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

Вам останется только загрузить готовый FB2-файл в FB Editor и доделать, то, с чем не справился скрипт: вставить обложку, разметить подзаголовки и цитаты,  подправить эпиграфы. Но это не должно отнять много времени. Основную часть работы проделал скрипт.


Рабочие версии этих скриптов вы можете взять с моего сайта http://yuzzich.narod.ru.