Реклама

Открываем 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-читалки необходимо добавить обработку ссылок, таблиц и некоторых других объектов. Но приведённого материала достаточно извлечения основного содержимого книги.
Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь

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

  • Результат 5 баллов
  • Очки рейтинга -10

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

  • Результат 57 баллов
  • Очки рейтинга -2

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

  • Результат 7 баллов
  • Очки рейтинга -10
Последние комментарии
  • EVILEG
  • 7 декабря 2017 г. 9:47

Django - Урок 011. Добавление комментариев на сайт с Django

Визуальный пример чего? комментариев? При ответе на конкретный комментарий рядом с ником отвечающего будет стрелочка и указание ник другого пользователя. Который будет ссылкой на коммента...

  • Bernar
  • 7 декабря 2017 г. 9:24

Django - Урок 011. Добавление комментариев на сайт с Django

есть визуальный пример ?

  • EVILEG
  • 6 декабря 2017 г. 11:30

Django - Урок 011. Добавление комментариев на сайт с Django

Да, так будет даже лучше, я на сайте уже обновил до такого вида код Вот это уже не нужно if request.method == 'POST': Поскольку Вы и так используете метод post, то есть эта про...

  • Bernar
  • 6 декабря 2017 г. 11:19

Django - Урок 011. Добавление комментариев на сайт с Django

сделал немного по другому class EArticleView(View): template_name = 'knowledge/article.html' comment_form = CommentForm def get(self, request, *args, **kwargs): ...

Сейчас обсуждают на форуме
  • Миша
  • 15 декабря 2017 г. 11:26

Как найти в QVector макс и мин

Спасибо

  • Galant
  • 14 декабря 2017 г. 19:58

LPT

Понял! Спасибо!

  • EVILEG
  • 14 декабря 2017 г. 13:38

QCustomPlot можно ли построить прерывистую линию на одном графике?

Во-первых: В pro файле проект по идее достаточно указать следующий define для включения возможности рендеринга через OpenGL DEFINES += QCUSTOMPLOT_USE_OPENGL И во вторых:...

  • EVILEG
  • 13 декабря 2017 г. 8:05

В многопоточности выполнять действие только в одном из потоков

Статическиe методs QThread::currentThread(); и QThread::currentThreadId() могут возвращать указатель на поток и его handle id соответственно. Можете попробовать через как...

  • EVILEG
  • 13 декабря 2017 г. 7:57

А что по поводу авторизации ?

Наличие токена - это правильный подход. Например, у меня на сайте в каждой форме есть токен, чтобы не было возможности подделки запросов. Что касается SSL, то стоит поискать информацию н...