Реклама

Открываем fb2-файлы средствами Qt

fb2, html, xml, QXmlStreamReader

В настоящее время fb2 является популярным форматом для хранения книг. Файл fb2 – частный случай xml. Основным элементом его структуры, как и для html, является тэг (управляющие слова). В этой статье я расскажу, как создать простейший просмотрщик fb2-файлов. Проект с исходным текстом можно скачать по ссылке .

Общие сведения

Теги делятся блочные и строчные. Блочные теги группируются в пары из открывающего тега, закрывающего тега между которыми располагается содержимое. Например, абзац текста записывается как

<p>Абзац текст</p>

Внутрь такой блочной пары можно поместить другие тэги. Строчные тэги используются для объектов, в которые вложить ничего нельзя. Например, указатель на рисунок

<image l:href = “#_0.jpg”/>

содержит информацию: 1) о том, что в данную точку документа нужно вставить рисунок, 2) ссылку на этот рисунок. Ниже разобран алгоритм вставки рисунка в текст. Различить 3 типа тэгов просто с помощью слеша. У строчного тэга слеш перед закрывающейся скобкой, у закрывающего блочного после открывающейся, у открывающего блочного он отсутствует.

Если хотите полностью разобраться, изучайте html. Между html и fb2 есть некоторая разница, хотя во многом они идентичны. На такие элементы я буду указывать по ходу повествования. Также отмечу, что xml в отличии от html не использует язык CSS, в нашем случае это значит то, что в fb2 файле нет указаний на то, как отформатирован текст (размер и цвет шрифта, расположение абзацев и т.п.). Всё это мы должны (при желании) реализовать самостоятельно.

Структура fb2-файла

В первом тэге <?xml> файла содержится техническая информация о формате, его версии и используемой кодировки. Второй тэг <FictionBook> охватывает саму книгу целиком. Как правило, в любой книге есть 2 части: описание <description> и основная часть <body> (как в html). Описание содержит имя автора, название книги, аннотацию, и т.д. Основная часть содержит заголовки <title> (всей книги или отдельных глав), главы/части/разделы <section> и текст <p> (как в html).

<?xml …>
  <FictionBook …>
    <description>
    …
    </description>
    <body>
    …
    </body>
  …
  </FictionBook>

Кроме того, можно встретить тэги эпиграф <epigraph>, ссылка <a> (как в html), рисунок <image/> и пустая строка <empty-line/> (в html <br/>). Ссылки могут быть внешние и внутренние. Внешние ссылки в качестве параметра содержат URL источника, внутренние – содержат ссылки на элементы в тексте файла (см. выше тэг image). Рисунки содержат аналогичные внутренние ссылки.

После раздела <body> могут располагаться дополнительные элементы. Так в отдельных тэгах<binary> размещаются рисунки, преобразованные к текстовой форме.

Создание программы-читалки

Нашу программу построим следующим образом: будем считывать данные из файла и преобразовывать их в html, затем сформированную строку направим в текстовое поле с помощью функции setHtml(QString). Один маленький лайфхак для тех, кто хочет изучить html: объект класса QTextEdit/QTextBrowser может отобразить форматированный документ в виде исходного текста. Для этого открываем редактор форм, щёлкаем по объекту 2 раза и переключится на закладку «Исходник».

Для обработки fb2-файлов будем использовать класс QXmlStreamReader. Для работы с ним необходимо подключить к проекту модули xml и xmlpatterns. В качестве аргумента ему необходимо предать указатель на объект класса QFile.

    QFile f(name);
    QXmlStreamReader sr(&f);

Открытие самого файла выглядит как цикл с последовательным считыванием строк. Ещё нам понадобятся 3 переменные

    QString book;
    QString imgId;
    QString imgType;

book нужна для хранения сформированного документа, imgId и imgType для вставки рисунков в текст. Класс QXmlStreamReader производит несколько важных действий. Во-первых, определяет и устанавливает нужный декодер. Во-вторых, отделяет тэги от содержимого. В-третьих, выделяет свойства тэгов. Нам остаётся только обрабатывать разделённые данные. Для считывания данных используется функция readNext(). Все фрагменты, прочитанные ей относятся к одному из 5 типов: StartDocument, EndDocument, StartElement, EndElement и Characters. Из них 2 первых для определения начала и конца файла, 2 следующих для считывания тэгов и последний получения заполнителя.

Получив StartDocument нам необходимо добавить заголовочную строку html документа и 2 открывающих тэга

book = "<!DOCTYPE HTML><html><body style=\"font-size:14px\">";

При достижении EndDocument мы закрываем тэги, открытые в начале файла

book.append("</body></html>");

Появление StartElement значит, что прочитан открывающийся или строчный тэг. Соответственно EndElement сигнализирует о считывании закрывающегося тэга. Имя тега определяется вызовом функции sr.name().toString(). Для контроля структуры документа будем хранить список всех открытых тэгов в объекте thisToken класса QStringList. Поэтому в случае StartElement добавляет к thisToken имя текущего тэга и удалять его в случае EndElement. Кроме того, открывающиеся (или строчные) тэги могут содержать атрибуты. Атрибуту будут считаны и сохранены в sr как массив строк. Доступ к ним можно получить с помощью метода sr.attributes(). Нам они понадобятся для добавления рисунков в текст. Так при обнаружении тэга image необходимо добавить в текст метку этого рисунка.

book.append("<p align=\"center\">"+sr.attributes().at(0).value().toString()+"</p>");

Затем, при обнаружении тэга <binary> нам необходимо сохранить его метку и формат.

imgId = sr.attributes().at(0).value().toString();
imgType = sr.attributes().at(1).value().toString();

Обратите внимание, что imgId идентичен атрибуту тэга <image> за исключением отсутствия знака диез (#).

Теперь нам остаётся только поместить содержимое в строку book. Здесь можно использовать различный набор правил. Например, игнорировать описание книги

if(thisToken.contains("description"))
{
    break; // не выводим
}

или выделять заголовки цветом, размером и типом шрифта. Остановимся только на рисунках. Для их вставки надо сформировать строку типа

QString image = "<img src=\"data:"
        + imgType +";base64,"
        + sr.text().toString()
        + "\"/>";

где sr.text().toString() содержимое тэга <binary>. Затем следует провести замену в нашей строке-документе метки, соответствующей данному рисунку на данную строку

book.replace("#"+imgId, image);

Алгоритма чтения fb2-файла

    while( !sr.atEnd() )
    {
        switch( sr.readNext() )
        {
        case QXmlStreamReader::NoToken:
            qDebug() << "QXmlStreamReader::NoToken";
            break;
        case QXmlStreamReader::StartDocument:
            book = "<!DOCTYPE HTML><html><body style=\"font-size:14px\">";
            break;
        case QXmlStreamReader::EndDocument:
            book.append("</body></html>");
            break;
        case QXmlStreamReader::StartElement:
            thisToken.append( sr.name().toString() );
            if( sr.name().toString() == "image" ) // расположение рисунков
            {
                if(sr.attributes().count() > 0)
                    book.append("<p align=\"center\">"+sr.attributes().at(0).value().toString()+"</p>");
            }
            if(sr.name() == "binary") // хранилище рисунков
            {
                imgId = sr.attributes().at(0).value().toString();
                imgType = sr.attributes().at(1).value().toString();
            }
            break;
        case QXmlStreamReader::EndElement:
            if( thisToken.last() == sr.name().toString() )
                thisToken.removeLast();
            else
                qDebug() << "error token";
            break;
        case QXmlStreamReader::Characters:
            if( sr.text().toString().contains( QRegExp("[A-Z]|[a-z]|[А-Я]|[а-я]") )) // если есть текст в блоке
            {
                if(thisToken.contains("description")) // ОПИСАНИЕ КНИГИ
                {
                    break; // не выводим
                }
                if(thisToken.contains("div"))
                    break;
                if(!thisToken.contains( "binary" ))
                    book.append("<p>" + sr.text().toString() + "</p>");
            }
            if(thisToken.contains( "binary" ) )//для рисунков
            {
                QString image = "<img src=\"data:"
                        + imgType +";base64,"
                        + sr.text().toString()
                        + "\"/>";
                book.replace("#"+imgId, image);
            }
            break;
        }
    }

Наш документ готов. Осталось только установить сформированную строку в текстовое поле

ui->textBrowser->setHtml(book);
Для полноценной работы fb2-читалки необходимо добавить обработку ссылок, таблиц и некоторых других объектов. Но приведённого материала достаточно извлечения основного содержимого книги.
Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
  • BlinCT
  • 22 октября 2017 г. 12:46

C++ - Тест 003. Условия и циклы

  • Результат 64 баллов
  • Очки рейтинга -1
  • Kiops
  • 22 октября 2017 г. 3:56

C++ - Тест 001. Первая программа и типы данных

  • Результат 86 баллов
  • Очки рейтинга 6
  • Kiops
  • 22 октября 2017 г. 2:41

Qt - Тест 001. Сигналы и слоты

  • Результат 100 баллов
  • Очки рейтинга 10
Последние комментарии
  • EVILEG
  • 21 октября 2017 г. 3:06

Qt/C++ - Урок 031. QCustomPlot - строим график по времени

Добавил архив с проектом

  • EVILEG
  • 20 октября 2017 г. 20:06

Qt/C++ - Урок 031. QCustomPlot - строим график по времени

После работы поищу, должен где-то быть на винте.

  • Миша
  • 20 октября 2017 г. 20:04

Qt/C++ - Урок 031. QCustomPlot - строим график по времени

не могли бы вы выложить архив с рабочей версией скрипта?

  • EVILEG
  • 20 октября 2017 г. 20:03

Qt/C++ - Урок 030. QCustomPlot - быстрый старт в работе с графиками

Использование дизайнера в Qt Creator и использование ui файлов является распространённой практикой в Qt фреймворке. Написать отдельную статью про то, что это такое? - может быть. Опи...

  • Миша
  • 20 октября 2017 г. 19:43

Qt/C++ - Урок 030. QCustomPlot - быстрый старт в работе с графиками

Но почему вы это не описали? Не могли бы вы описать.

Сейчас обсуждают на форуме
  • EVILEG
  • 22 октября 2017 г. 12:05

Закрепление якорей в момент создания объекта через JS

Добрый день! Якоря - это не те свойства, которые можно устанавливать сразу по инициализации, лучше их править после создания объекта, поскольку при одновременной установке они могут в...

  • EVILEG
  • 21 октября 2017 г. 23:33

Создание истории редактирования постов на сайте

Ясно. Тогда я лучше не буду тратить время на его проверку. Тем более, что я использую гугловский prettyprint для подсветки кода. Спасибо за информацию.

QFile::copy() возвращает false

Получилось! Спасибо огромное! path1 = "C:/Users/555/Pictures/00GAF13AP001-002.jpg"true

  • cordsac
  • 19 октября 2017 г. 15:49

How can I select the QGraphicView Item and change the properties

Ok I'll check it sir,If you can please do article(tutorial) about this,Its really useful.Thank you if you can give me some sample code when you free.thanks again

  • cordsac
  • 17 октября 2017 г. 19:28

How can I open SVG file through QT

Okay,Thank you sir :)