File indexing completed on 2024-05-12 05:17:27

0001 /*
0002     SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "mainwindow.h"
0008 #include "ui_mainwindow.h"
0009 #include "attributemodel.h"
0010 #include "documentmodel.h"
0011 #include "dommodel.h"
0012 #include "settingsdialog.h"
0013 #include "standarditemmodelhelper.h"
0014 
0015 #include <KItinerary/BarcodeDecoder>
0016 #include <KItinerary/BERElement>
0017 #include <KItinerary/CalendarHandler>
0018 #include <KItinerary/ExtractorPostprocessor>
0019 #include <KItinerary/ExtractorRepository>
0020 #include <KItinerary/ExtractorResult>
0021 #include <KItinerary/ExtractorValidator>
0022 #include <KItinerary/HtmlDocument>
0023 #include <KItinerary/IataBcbp>
0024 #include <KItinerary/JsonLdDocument>
0025 #include <KItinerary/MergeUtil>
0026 #include <KItinerary/PdfDocument>
0027 #include <KItinerary/Reservation>
0028 #include <KItinerary/ELBTicket>
0029 #include <KItinerary/SSBv1Ticket>
0030 #include <KItinerary/SSBv2Ticket>
0031 #include <KItinerary/SSBv3Ticket>
0032 #include <KItinerary/Uic9183Parser>
0033 #include <KItinerary/VdvTicket>
0034 #include <KItinerary/VdvTicketContent>
0035 
0036 #include <KPkPass/Pass>
0037 
0038 #include <KCalendarCore/Event>
0039 #include <KCalendarCore/ICalFormat>
0040 #include <KCalendarCore/MemoryCalendar>
0041 
0042 #include <KMime/Message>
0043 
0044 #include <KTextEditor/Document>
0045 #include <KTextEditor/View>
0046 #include <KTextEditor/Editor>
0047 
0048 #include <KIO/StoredTransferJob>
0049 
0050 #include <KActionCollection>
0051 #include <KLocalizedString>
0052 #include <KStandardAction>
0053 
0054 #include <QBuffer>
0055 #include <QClipboard>
0056 #include <QDebug>
0057 #include <QFontMetrics>
0058 #include <QHBoxLayout>
0059 #include <QImage>
0060 #include <QJsonArray>
0061 #include <QJsonDocument>
0062 #include <QJsonObject>
0063 #include <QMenu>
0064 #include <QMetaEnum>
0065 #include <QMetaObject>
0066 #include <QMimeData>
0067 #include <QSettings>
0068 #include <QStandardItemModel>
0069 #include <QStringEncoder>
0070 #include <QToolBar>
0071 
0072 #include <cctype>
0073 #include <cstring>
0074 
0075 Q_DECLARE_METATYPE(KItinerary::Internal::OwnedPtr<KItinerary::HtmlDocument>)
0076 Q_DECLARE_METATYPE(KItinerary::Internal::OwnedPtr<KItinerary::PdfDocument>)
0077 
0078 static QVector<QVector<QVariant>> batchReservations(const QVector<QVariant> &reservations)
0079 {
0080     using namespace KItinerary;
0081 
0082     QVector<QVector<QVariant>> batches;
0083     QVector<QVariant> batch;
0084 
0085     for (const auto &res : reservations) {
0086         if (batch.isEmpty()) {
0087             batch.push_back(res);
0088             continue;
0089         }
0090 
0091         if (MergeUtil::isSameIncidence(res, batch.at(0))) {
0092             batch.push_back(res);
0093             continue;
0094         }
0095 
0096         batches.push_back(batch);
0097         batch.clear();
0098         batch.push_back(res);
0099     }
0100 
0101     if (!batch.isEmpty()) {
0102         batches.push_back(batch);
0103     }
0104     return batches;
0105 }
0106 
0107 MainWindow::MainWindow(QWidget* parent)
0108     : KXmlGuiWindow(parent)
0109     , ui(new Ui::MainWindow)
0110     , m_extractorDocModel(new DocumentModel(this))
0111     , m_imageModel(new QStandardItemModel(this))
0112     , m_domModel(new DOMModel(this))
0113     , m_attrModel(new AttributeModel(this))
0114     , m_iataBcbpModel(new QStandardItemModel(this))
0115     , m_eraSsbModel(new QStandardItemModel(this))
0116     , m_vdvModel(new QStandardItemModel(this))
0117 {
0118     ui->setupUi(this);
0119     ui->contextDate->setDateTime(QDateTime(QDate::currentDate(), QTime()));
0120     setCentralWidget(ui->mainSplitter);
0121 
0122     m_engine.setHints(KItinerary::ExtractorEngine::ExtractGenericIcalEvents | KItinerary::ExtractorEngine::ExtractFullPageRasterImages);
0123 
0124     connect(ui->senderBox, &QComboBox::currentTextChanged, this, &MainWindow::sourceChanged);
0125     connect(ui->contextDate, &QDateTimeEdit::dateTimeChanged, this, &MainWindow::sourceChanged);
0126     connect(ui->fileRequester, &KUrlRequester::textChanged, this, &MainWindow::urlChanged);
0127     connect(ui->extractorWidget, &ExtractorEditorWidget::extractorChanged, this, &MainWindow::sourceChanged);
0128 
0129     auto editor = KTextEditor::Editor::instance();
0130 
0131     m_sourceDoc = editor->createDocument(nullptr);
0132     connect(m_sourceDoc, &KTextEditor::Document::textChanged, this, &MainWindow::sourceChanged);
0133     m_sourceView = m_sourceDoc->createView(nullptr);
0134     ui->sourceTab->layout()->addWidget(m_sourceView);
0135 
0136     m_preprocDoc = editor->createDocument(nullptr);
0137     auto view = m_preprocDoc->createView(nullptr);
0138     auto layout = new QHBoxLayout(ui->preprocTab);
0139     layout->addWidget(view);
0140 
0141     ui->documentTreeView->setModel(m_extractorDocModel);
0142     ui->documentTreeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0143     connect(ui->documentTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selection) {
0144         if (selection.empty()) {
0145             return;
0146         }
0147         auto idx = selection.at(0).topLeft();
0148         idx = idx.sibling(idx.row(), 0);
0149         setCurrentDocumentNode(idx.data(Qt::UserRole).value<KItinerary::ExtractorDocumentNode>());
0150     });
0151 
0152     m_imageModel->setHorizontalHeaderLabels({i18n("Image")});
0153     ui->imageView->setModel(m_imageModel);
0154     connect(ui->imageView, &QWidget::customContextMenuRequested, this, &MainWindow::imageContextMenu);
0155 
0156     auto domFilterModel = new DOMFilterModel(this);
0157     domFilterModel->setRecursiveFilteringEnabled(true);
0158     domFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
0159     domFilterModel->setSourceModel(m_domModel);
0160     connect(domFilterModel, &QSortFilterProxyModel::layoutChanged, ui->domView, &QTreeView::expandAll);
0161     connect(domFilterModel, &QSortFilterProxyModel::rowsRemoved, ui->domView, &QTreeView::expandAll);
0162     connect(domFilterModel, &QSortFilterProxyModel::rowsInserted, ui->domView, &QTreeView::expandAll);
0163     ui->domView->setModel(domFilterModel);
0164     ui->domView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0165     connect(ui->domSearchLine, &QLineEdit::textChanged, domFilterModel, &QSortFilterProxyModel::setFilterFixedString);
0166     ui->attributeView->setModel(m_attrModel);
0167     ui->attributeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0168     connect(ui->domView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selection) {
0169         auto idx = selection.value(0).topLeft();
0170         m_attrModel->setElement(idx.data(Qt::UserRole).value<KItinerary::HtmlElement>());
0171 
0172         QString path;
0173         idx = idx.sibling(idx.row(), 0);
0174         while (idx.isValid()) {
0175             path.prepend(QLatin1Char('/') + idx.data(Qt::DisplayRole).toString());
0176             idx = idx.parent();
0177         }
0178         ui->domPath->setText(path);
0179     });
0180     ui->domSplitter->setStretchFactor(0, 5);
0181     ui->domSplitter->setStretchFactor(1, 1);
0182     connect(ui->xpathEdit, &QLineEdit::editingFinished, this, [this]() {
0183         if (!m_domModel->document()) {
0184             return;
0185         }
0186         const auto res = m_domModel->document()->eval(ui->xpathEdit->text());
0187         if (!res.canConvert<QVariantList>()) { // TODO show this properly in the UI somehow
0188             qDebug() << "XPath result:" << res;
0189         }
0190         m_domModel->setHighlightNodeSet(res.value<QVariantList>());
0191         ui->domView->viewport()->update(); // dirty, but easier than triggering a proper full model update
0192     });
0193 
0194     m_iataBcbpModel->setHorizontalHeaderLabels({i18n("Field"), i18n("Value")});
0195     ui->iataBcbpView->setModel(m_iataBcbpModel);
0196     ui->iataBcbpView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0197 
0198     m_eraSsbModel->setHorizontalHeaderLabels({i18n("Field"), i18n("Value")});
0199     ui->eraSsbView->setModel(m_eraSsbModel);
0200     ui->eraSsbView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0201 
0202     m_vdvModel->setHorizontalHeaderLabels({i18n("Field"), i18n("Value")});
0203     ui->vdvView->setModel(m_vdvModel);
0204     ui->vdvView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0205 
0206     m_nodeResultDoc = editor->createDocument(nullptr);
0207     m_nodeResultDoc->setMode(QStringLiteral("JSON"));
0208     view = m_nodeResultDoc->createView(nullptr);
0209     layout = new QHBoxLayout(ui->nodeResultTab);
0210     layout->addWidget(view);
0211 
0212     m_outputDoc = editor->createDocument(nullptr);
0213     m_outputDoc->setMode(QStringLiteral("JSON"));
0214     view = m_outputDoc->createView(nullptr);
0215     layout = new QHBoxLayout(ui->outputTab);
0216     layout->addWidget(view);
0217 
0218     m_postprocDoc = editor->createDocument(nullptr);
0219     m_postprocDoc->setMode(QStringLiteral("JSON"));
0220     view = m_postprocDoc->createView(nullptr);
0221     layout = new QHBoxLayout(ui->postprocTab);
0222     layout->addWidget(view);
0223 
0224     m_validatedDoc = editor->createDocument(nullptr);
0225     m_validatedDoc->setMode(QStringLiteral("JSON"));
0226     view = m_validatedDoc->createView(nullptr);
0227     ui->validatedTab->layout()->addWidget(view);
0228     connect(ui->acceptCompleteOnly, &QCheckBox::toggled, this, &MainWindow::sourceChanged);
0229 
0230     m_icalDoc = editor->createDocument(nullptr);
0231     m_icalDoc->setMode(QStringLiteral("vCard, vCalendar, iCalendar"));
0232     view = m_icalDoc->createView(nullptr);
0233     layout = new QHBoxLayout(ui->icalTab);
0234     layout->addWidget(view);
0235 
0236     connect(ui->consoleWidget, &ConsoleOutputWidget::navigateToSource, ui->extractorWidget, &ExtractorEditorWidget::navigateToSource);
0237     connect(ui->consoleWidget, &ConsoleOutputWidget::navigateToSource, this, [this]() {
0238         ui->inputTabWidget->setCurrentIndex(ExtractorEditorTab);
0239     });
0240 
0241     setCurrentDocumentNode({});
0242 
0243     QSettings settings;
0244     settings.beginGroup(QLatin1String("SenderHistory"));
0245     ui->senderBox->addItems(settings.value(QLatin1String("History")).toStringList());
0246     ui->senderBox->setCurrentText(QString());
0247 
0248     connect(ui->actionExtractorRun, &QAction::triggered, this, &MainWindow::sourceChanged);
0249     connect(ui->actionExtractorReloadRepository, &QAction::triggered, this, [this]() {
0250         KItinerary::ExtractorRepository repo;
0251         repo.reload();
0252         ui->extractorWidget->reloadExtractors();
0253     });
0254     connect(ui->actionInputFromClipboard, &QAction::triggered, this, &MainWindow::loadFromClipboard);
0255     connect(ui->actionInputClear, &QAction::triggered, this, [this]() {
0256         ui->fileRequester->clear();
0257         m_sourceDoc->clear();
0258         m_sourceView->show();
0259         sourceChanged();
0260     });
0261     connect(ui->actionSeparateProcess, &QAction::toggled, this, [this](bool checked) {
0262         clearEngine();
0263         m_engine.setUseSeparateProcess(checked);
0264         sourceChanged();
0265     });
0266     connect(ui->actionFullPageRasterImages, &QAction::toggled, this, [this](bool checked) {
0267         clearEngine();
0268         if (checked) {
0269             m_engine.setHints(m_engine.hints() | KItinerary::ExtractorEngine::ExtractFullPageRasterImages);
0270         } else {
0271             m_engine.setHints(m_engine.hints() & ~KItinerary::ExtractorEngine::ExtractFullPageRasterImages);
0272         }
0273         sourceChanged();
0274     });
0275     connect(ui->actionSettingsConfigure, &QAction::triggered, this, [this]() {
0276         SettingsDialog dlg(this);
0277         if (dlg.exec() == QDialog::Accepted) {
0278             ui->actionExtractorReloadRepository->trigger();
0279         }
0280     });
0281     actionCollection()->addAction(QStringLiteral("extractor_run"), ui->actionExtractorRun);
0282     actionCollection()->addAction(QStringLiteral("extractor_reload_repository"), ui->actionExtractorReloadRepository);
0283     actionCollection()->addAction(QStringLiteral("input_from_clipboard"), ui->actionInputFromClipboard);
0284     actionCollection()->addAction(QStringLiteral("input_clear"), ui->actionInputClear);
0285     actionCollection()->addAction(QStringLiteral("file_quit"), KStandardAction::quit(QApplication::instance(), &QApplication::closeAllWindows, this));
0286     actionCollection()->addAction(QStringLiteral("options_configure"), ui->actionSettingsConfigure);
0287     actionCollection()->addAction(QStringLiteral("settings_separate_process"), ui->actionSeparateProcess);
0288     actionCollection()->addAction(QStringLiteral("settings_full_page_raster_images"), ui->actionFullPageRasterImages);
0289     ui->extractorWidget->registerActions(actionCollection());
0290 
0291     setupGUI(Default, QStringLiteral("ui.rc"));
0292 }
0293 
0294 MainWindow::~MainWindow()
0295 {
0296     QSettings settings;
0297 
0298     settings.beginGroup(QLatin1String("SenderHistory"));
0299     QStringList history;
0300     history.reserve(ui->senderBox->count());
0301     for (int i = 0; i < ui->senderBox->count(); ++i)
0302         history.push_back(ui->senderBox->itemText(i));
0303     settings.setValue(QLatin1String("History"), history);
0304     settings.endGroup();
0305 
0306     clearEngine();
0307 }
0308 
0309 void MainWindow::openFile(const QString &file)
0310 {
0311     ui->fileRequester->setText(file);
0312 }
0313 
0314 void MainWindow::clearEngine()
0315 {
0316     // ensure we hold no references to document nodes anymore
0317     ui->documentTreeView->clearSelection();
0318     m_currentNode = {};
0319     StandardItemModelHelper::clearContent(m_extractorDocModel);
0320     m_engine.clear();
0321 }
0322 
0323 void MainWindow::sourceChanged()
0324 {
0325     clearEngine();
0326 
0327     StandardItemModelHelper::clearContent(m_imageModel);
0328     ui->uic9183Widget->clear();
0329     ui->consoleWidget->clear();
0330     using namespace KItinerary;
0331 
0332     if (m_sourceView->isVisible()) {
0333         QStringEncoder codec(m_sourceDoc->encoding().toUtf8().constData());
0334         if (!codec.isValid()) {
0335             codec = QStringEncoder(QStringEncoder::System);
0336         }
0337         m_data = codec.encode(m_sourceDoc->text());
0338     }
0339 
0340     m_contextMsg = std::make_unique<KMime::Message>();
0341     m_contextMsg->from()->fromUnicodeString(ui->senderBox->currentText(), "utf-8");
0342     m_contextMsg->date()->setDateTime(ui->contextDate->dateTime());
0343     m_engine.setContext(QVariant::fromValue<KMime::Content*>(m_contextMsg.get()), u"message/rfc822");
0344 
0345     m_engine.setData(m_data, ui->fileRequester->url().path());
0346     const auto data = m_engine.extract();
0347     ui->extractorWidget->showExtractor(m_engine.usedCustomExtractor());
0348 
0349     m_extractorDocModel->setRootNode(m_engine.rootDocumentNode());
0350     ui->documentTreeView->expandAll();
0351     setCurrentDocumentNode(m_engine.rootDocumentNode());
0352 
0353     m_outputDoc->setReadWrite(true);
0354     m_outputDoc->setText(QString::fromUtf8(QJsonDocument(data).toJson()));
0355     m_outputDoc->setReadWrite(false);
0356 
0357     ExtractorPostprocessor postproc;
0358     postproc.setContextDate(ui->contextDate->dateTime());
0359     postproc.process(JsonLdDocument::fromJson(data));
0360     auto result = postproc.result();
0361 
0362     m_postprocDoc->setReadWrite(true);
0363     m_postprocDoc->setText(QString::fromUtf8(QJsonDocument(JsonLdDocument::toJson(result)).toJson()));
0364     m_postprocDoc->setReadWrite(false);
0365 
0366     ExtractorValidator validator;
0367     validator.setAcceptOnlyCompleteElements(ui->acceptCompleteOnly->isChecked());
0368     result.erase(std::remove_if(result.begin(), result.end(), [&validator](const auto &elem) {
0369         return !validator.isValidElement(elem);
0370     }), result.end());
0371     m_validatedDoc->setReadWrite(true);
0372     m_validatedDoc->setText(QString::fromUtf8(QJsonDocument(JsonLdDocument::toJson(result)).toJson()));
0373     m_validatedDoc->setReadWrite(false);
0374 
0375     const auto batches = batchReservations(result);
0376     KCalendarCore::Calendar::Ptr cal(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
0377     for (const auto &batch : batches) {
0378         KCalendarCore::Event::Ptr event(new KCalendarCore::Event);
0379         CalendarHandler::fillEvent(batch, event);
0380         cal->addEvent(event);
0381     }
0382     KCalendarCore::ICalFormat format;
0383     m_icalDoc->setText(format.toString(cal));
0384 }
0385 
0386 void MainWindow::urlChanged()
0387 {
0388     const auto url = ui->fileRequester->url();
0389     if (!url.isValid()) {
0390         return;
0391     }
0392 
0393     auto job = KIO::storedGet(url);
0394     connect(job, &KJob::finished, this, [this, job, url]() {
0395         if (job->error() != KJob::NoError) {
0396             qWarning() << job->errorString();
0397             return;
0398         }
0399         m_data = job->data();
0400         const auto isText = std::none_of(m_data.begin(), m_data.end(), [](unsigned char c) { return std::iscntrl(c) && !std::isspace(c); });
0401         const auto textExt =
0402             url.fileName().endsWith(QLatin1String(".eml")) ||
0403             url.fileName().endsWith(QLatin1String(".html")) ||
0404             url.fileName().endsWith(QLatin1String(".mbox")) ||
0405             url.fileName().endsWith(QLatin1String(".txt"));
0406         if (isText || textExt) {
0407             if (url.scheme() == QLatin1String("https") || url.scheme() == QLatin1String("http")) {
0408                 m_sourceDoc->setText(QString::fromUtf8(m_data));
0409             } else {
0410                 m_sourceDoc->openUrl(url);
0411             }
0412             m_sourceView->show();
0413         } else {
0414             m_sourceView->hide();
0415             sourceChanged();
0416         }
0417     });
0418 }
0419 
0420 void MainWindow::loadFromClipboard()
0421 {
0422     ui->fileRequester->clear();
0423 
0424     const auto md = QGuiApplication::clipboard()->mimeData();
0425     if (md->hasText()) {
0426         m_sourceDoc->setText(md->text());
0427         m_sourceView->show();
0428     } else if (md->hasFormat(QLatin1String("application/octet-stream"))) {
0429         m_data = md->data(QLatin1String("application/octet-stream"));
0430         m_sourceView->hide();
0431     }
0432     sourceChanged();
0433 }
0434 
0435 void MainWindow::imageContextMenu(QPoint pos)
0436 {
0437     using namespace KItinerary;
0438 
0439     const auto idx = ui->imageView->currentIndex();
0440     if (!idx.isValid())
0441         return;
0442 
0443     QMenu menu;
0444     const auto barcode = menu.addAction(i18n("Decode && Copy Barcode"));
0445     const auto barcodeBinary = menu.addAction(i18n("Decode && Copy Barcode (Binary)"));
0446     menu.addSeparator();
0447     const auto save = menu.addAction(i18n("Save Image..."));
0448     const auto bcSave = menu.addAction(i18n("Save Barcode Content..."));
0449     if (auto action = menu.exec(ui->imageView->viewport()->mapToGlobal(pos))) {
0450         if (action == barcode) {
0451             BarcodeDecoder decoder;
0452             const auto code = decoder.decode(idx.data(Qt::DecorationRole).value<QImage>(), BarcodeDecoder::Any | BarcodeDecoder::IgnoreAspectRatio).toString();
0453             QGuiApplication::clipboard()->setText(code);
0454         } else if (action == barcodeBinary) {
0455             BarcodeDecoder decoder;
0456             const auto b = decoder.decode(idx.data(Qt::DecorationRole).value<QImage>(), BarcodeDecoder::Any | BarcodeDecoder::IgnoreAspectRatio).toByteArray();
0457             auto md = new QMimeData;
0458             md->setData(QStringLiteral("application/octet-stream"), b);
0459             QGuiApplication::clipboard()->setMimeData(md);
0460         } else if (action == save) {
0461             const auto fileName = QFileDialog::getSaveFileName(this, i18n("Save Image"));
0462             idx.data(Qt::DecorationRole).value<QImage>().save(fileName);
0463         } else if (action == bcSave) {
0464             const auto fileName = QFileDialog::getSaveFileName(this, i18n("Save Barcode Content"));
0465             if (!fileName.isEmpty()) {
0466                 BarcodeDecoder decoder;
0467                 const auto b = decoder.decode(idx.data(Qt::DecorationRole).value<QImage>(), BarcodeDecoder::Any | BarcodeDecoder::IgnoreAspectRatio).toByteArray();
0468                 QFile f(fileName);
0469                 if (!f.open(QFile::WriteOnly)) {
0470                     qWarning() << "Failed to open file:" << f.errorString() << fileName;
0471                 } else {
0472                     f.write(b);
0473                 }
0474             }
0475         }
0476     }
0477 }
0478 
0479 void MainWindow::setCurrentDocumentNode(const KItinerary::ExtractorDocumentNode &node)
0480 {
0481     for (auto i : { TextTab, ImageTab, DomTab, Uic9183Tab, IataBcbpTab, EraSsbTab, VdvTab }) {
0482         ui->inputTabWidget->setTabEnabled(i, false);
0483     }
0484 
0485     StandardItemModelHelper::clearContent(m_imageModel);
0486     m_domModel->setDocument(nullptr);
0487 
0488     using namespace KItinerary;
0489     m_currentNode = node;
0490     m_nodeResultDoc->setText(QString::fromUtf8(QJsonDocument(node.result().jsonLdResult()).toJson()));
0491 
0492     if (node.mimeType() == QLatin1String("application/pdf")) {
0493         const auto pdf = node.content<PdfDocument*>();
0494         if (!pdf) {
0495             return;
0496         }
0497         m_preprocDoc->setText(pdf->text());
0498 
0499         for (int i = 0; i < pdf->pageCount(); ++i) {
0500             auto pageItem = new QStandardItem;
0501             pageItem->setText(i18n("Page %1", i + 1));
0502             const auto page = pdf->page(i);
0503             for (int j = 0; j < page.imageCount(); ++j) {
0504                 auto imgItem = new QStandardItem;
0505                 auto pdfImg = page.image(j);
0506                 auto imgData = pdfImg.image();
0507                 bool skippedByExtractor = false;
0508                 if (imgData.isNull()) {
0509                     skippedByExtractor = true;
0510                     pdfImg.setLoadingHints(PdfImage::NoHint);
0511                     imgData = pdfImg.image();
0512                 }
0513                 imgItem->setData(imgData, Qt::DecorationRole);
0514                 imgItem->setToolTip(i18n("Size: %1 x %2\nSource: %3 x %4\nSkipped: %5", pdfImg.width(), pdfImg.height(), pdfImg.sourceWidth(), pdfImg.sourceHeight(), skippedByExtractor));
0515                 pageItem->appendRow(imgItem);
0516             }
0517             m_imageModel->appendRow(pageItem);
0518         }
0519         ui->imageView->expandAll();
0520 
0521         ui->inputTabWidget->setTabEnabled(TextTab, true);
0522         ui->inputTabWidget->setTabEnabled(ImageTab, true);
0523     }
0524     else if (node.mimeType() == QLatin1String("internal/qimage")) {
0525         auto item = new QStandardItem;
0526         item->setData(node.content<QImage>(), Qt::DecorationRole);
0527         m_imageModel->appendRow(item);
0528         ui->inputTabWidget->setTabEnabled(ImageTab, true);
0529     }
0530     else if (node.mimeType() == QLatin1String("internal/uic9183")) {
0531         const auto uic9183 = node.content<Uic9183Parser>();
0532         ui->uic9183Widget->setContent(uic9183);
0533         ui->inputTabWidget->setTabEnabled(Uic9183Tab, true);
0534     }
0535     else if (node.mimeType() == QLatin1String("text/html")) {
0536         const auto html = node.content<HtmlDocument*>();
0537         m_domModel->setDocument(html);
0538         m_preprocDoc->setText(html->root().recursiveContent());
0539         ui->domView->expandAll();
0540         ui->inputTabWidget->setTabEnabled(TextTab, true);
0541         ui->inputTabWidget->setTabEnabled(DomTab, true);
0542     }
0543     else if (node.mimeType() == QLatin1String("text/plain")) {
0544         m_preprocDoc->setText(node.content().value<QString>());
0545         ui->inputTabWidget->setTabEnabled(TextTab, true);
0546     }
0547     else if (node.mimeType() == QLatin1String("application/ld+json")) {
0548         m_preprocDoc->setText(QString::fromUtf8(QJsonDocument(node.content().value<QJsonArray>()).toJson()));
0549         ui->inputTabWidget->setTabEnabled(TextTab, true);
0550     }
0551     else if (node.mimeType() == QLatin1String("internal/iata-bcbp")) {
0552         const auto bcbp = node.content<IataBcbp>();
0553         m_preprocDoc->setText(bcbp.rawData());
0554         ui->inputTabWidget->setTabEnabled(TextTab, true);
0555 
0556         StandardItemModelHelper::clearContent(m_iataBcbpModel);
0557         const auto ums = bcbp.uniqueMandatorySection();
0558         StandardItemModelHelper::fillFromGadget(ums, m_iataBcbpModel->invisibleRootItem());
0559         const auto ucs = bcbp.uniqueConditionalSection();
0560         StandardItemModelHelper::fillFromGadget(ucs, m_iataBcbpModel->invisibleRootItem());
0561         const auto issueDate = ucs.dateOfIssue(node.contextDateTime());
0562         StandardItemModelHelper::addEntry(i18n("Date of issue"), issueDate.toString(Qt::ISODate), m_iataBcbpModel->invisibleRootItem());
0563         for (auto i = 0; i < ums.numberOfLegs(); ++i) {
0564             auto legItem = StandardItemModelHelper::addEntry(i18n("Leg %1", i + 1), {}, m_iataBcbpModel->invisibleRootItem());
0565             const auto rms = bcbp.repeatedMandatorySection(i);
0566             StandardItemModelHelper::fillFromGadget(rms, legItem);
0567             const auto rcs = bcbp.repeatedConditionalSection(i);
0568             StandardItemModelHelper::fillFromGadget(rcs, legItem);
0569             StandardItemModelHelper::addEntry(i18n("Airline use section"), bcbp.airlineUseSection(i), legItem);
0570             StandardItemModelHelper::addEntry(i18n("Date of flight"), rms.dateOfFlight(issueDate.isValid() ? QDateTime(issueDate, {}) : node.contextDateTime()).toString(Qt::ISODate), legItem);
0571         }
0572 
0573         if (bcbp.hasSecuritySection()) {
0574             auto secItem = StandardItemModelHelper::addEntry(i18n("Security"), {}, m_iataBcbpModel->invisibleRootItem());
0575             const auto sec = bcbp.securitySection();
0576             StandardItemModelHelper::fillFromGadget(sec, secItem);
0577         }
0578 
0579         ui->iataBcbpView->expandAll();
0580         ui->inputTabWidget->setTabEnabled(IataBcbpTab, true);
0581     }
0582     else if (node.mimeType() == QLatin1String("internal/era-ssb")) {
0583         StandardItemModelHelper::clearContent(m_eraSsbModel);
0584         if (node.isA<SSBv1Ticket>()) {
0585             const auto ssb = node.content<SSBv1Ticket>();
0586             StandardItemModelHelper::fillFromGadget(ssb, m_eraSsbModel->invisibleRootItem());
0587             StandardItemModelHelper::addEntry(i18n("First day of validity"), ssb.firstDayOfValidity(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0588             StandardItemModelHelper::addEntry(i18n("Departure time"), ssb.departureTime(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0589         } else if (node.isA<SSBv2Ticket>()) {
0590             const auto ssb = node.content<SSBv2Ticket>();
0591             StandardItemModelHelper::fillFromGadget(ssb, m_eraSsbModel->invisibleRootItem());
0592             StandardItemModelHelper::addEntry(i18n("First day of validity"), ssb.firstDayOfValidity(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0593             StandardItemModelHelper::addEntry(i18n("Last day of validity"), ssb.lastDayOfValidity(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0594         } else if (node.isA<SSBv3Ticket>()) {
0595             const auto ssb = node.content<SSBv3Ticket>();
0596             const auto typePrefix = QByteArray("type" + QByteArray::number(ssb.ticketTypeCode()));
0597             for (auto i = 0; i < SSBv3Ticket::staticMetaObject.propertyCount(); ++i) {
0598                 const auto prop = SSBv3Ticket::staticMetaObject.property(i);
0599                 if (!prop.isStored() || (std::strncmp(prop.name(), "type", 4) == 0 && std::strncmp(prop.name(), typePrefix.constData(), 5) != 0)) {
0600                     continue;
0601                 }
0602                 const auto value = prop.readOnGadget(&ssb);
0603                 StandardItemModelHelper::addEntry(QString::fromUtf8(prop.name()), value.toString(), m_eraSsbModel->invisibleRootItem());
0604             }
0605             StandardItemModelHelper::addEntry(i18n("Issuing day"), ssb.issueDate(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0606             switch (ssb.ticketTypeCode()) {
0607                 case SSBv3Ticket::IRT_RES_BOA:
0608                     StandardItemModelHelper::addEntry(i18n("Departure day"), ssb.type1DepartureDay(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0609                     break;
0610                 case SSBv3Ticket::NRT:
0611                     StandardItemModelHelper::addEntry(i18n("Valid from"), ssb.type2ValidFrom(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0612                     StandardItemModelHelper::addEntry(i18n("Valid until"), ssb.type2ValidUntil(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0613                     break;
0614                 case SSBv3Ticket::GRT:
0615                 case SSBv3Ticket::RPT:
0616                     break;
0617             }
0618         } else {
0619             StandardItemModelHelper::fillFromGadget(node.content(), m_eraSsbModel->invisibleRootItem());
0620         }
0621 
0622         ui->eraSsbView->expandAll();
0623         ui->inputTabWidget->setTabEnabled(EraSsbTab, true);
0624     }
0625     else if (node.mimeType() == QLatin1String("internal/era-elb")) {
0626         StandardItemModelHelper::clearContent(m_eraSsbModel);
0627         const auto elb = node.content<ELBTicket>();
0628         for (auto i = 0; i < ELBTicket::staticMetaObject.propertyCount(); ++i) {
0629             const auto prop = ELBTicket::staticMetaObject.property(i);
0630             if (!prop.isStored() || QMetaType(prop.userType()).metaObject()) {
0631                 continue;
0632             }
0633             const auto value = prop.readOnGadget(&elb);
0634             StandardItemModelHelper::addEntry(QString::fromUtf8(prop.name()), value.toString(), m_eraSsbModel->invisibleRootItem());
0635         }
0636         StandardItemModelHelper::addEntry(i18n("Emission date"), elb.emissionDate(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0637         StandardItemModelHelper::addEntry(i18n("Valid from"), elb.validFromDate(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0638         StandardItemModelHelper::addEntry(i18n("Valid until"), elb.validUntilDate(node.contextDateTime()).toString(Qt::ISODate), m_eraSsbModel->invisibleRootItem());
0639 
0640         auto parent = StandardItemModelHelper::addEntry(i18n("Segment 1"), {}, m_eraSsbModel->invisibleRootItem());
0641         StandardItemModelHelper::fillFromGadget(elb.segment1(), parent);
0642         StandardItemModelHelper::addEntry(i18n("Departure date"), elb.segment1().departureDate(node.contextDateTime()).toString(Qt::ISODate), parent);
0643 
0644         if (elb.segment2().isValid()) {
0645             auto parent = StandardItemModelHelper::addEntry(i18n("Segment 2"), {}, m_eraSsbModel->invisibleRootItem());
0646             StandardItemModelHelper::fillFromGadget(elb.segment2(), parent);
0647             StandardItemModelHelper::addEntry(i18n("Departure date"), elb.segment2().departureDate(node.contextDateTime()).toString(Qt::ISODate), parent);
0648         }
0649 
0650         ui->eraSsbView->expandAll();
0651         ui->inputTabWidget->setTabEnabled(EraSsbTab, true);
0652     }
0653     else if (node.mimeType() == QLatin1String("internal/vdv")) {
0654         StandardItemModelHelper::clearContent(m_vdvModel);
0655         const auto vdv = node.content<VdvTicket>();
0656         auto item = StandardItemModelHelper::addEntry(i18n("Header"), {}, m_vdvModel->invisibleRootItem());
0657         StandardItemModelHelper::fillFromGadget(vdv.header(), item);
0658 
0659         item = StandardItemModelHelper::addEntry(i18n("Product data"), {}, m_vdvModel->invisibleRootItem());
0660         for (auto block = vdv.productData().first(); block.isValid(); block = block.next()) {
0661             auto blockItem = StandardItemModelHelper::addEntry(i18n("Block 0x%1 (%2 bytes)", QString::number(block.type(), 16), block.size()), {}, item);
0662             switch (block.type()) {
0663                 case VdvTicketBasicData::Tag:
0664                     StandardItemModelHelper::fillFromGadget(block.contentAt<VdvTicketBasicData>(), blockItem);
0665                     break;
0666             case VdvTicketTravelerData::Tag:
0667             {
0668                 const auto traveler = block.contentAt<VdvTicketTravelerData>();
0669                 StandardItemModelHelper::fillFromGadget(traveler, blockItem);
0670                 StandardItemModelHelper::addEntry(i18n("Name"), QString::fromUtf8(traveler->name(), traveler->nameSize(block.contentSize())), blockItem);
0671                 break;
0672             }
0673             case VdvTicketValidityAreaData::Tag:
0674             {
0675                 const auto area = block.contentAt<VdvTicketValidityAreaData>();
0676 
0677                 switch (area->type) {
0678                     case VdvTicketValidityAreaDataType31::Type:
0679                     {
0680                         const auto area31 = static_cast<const VdvTicketValidityAreaDataType31*>(area);
0681                         StandardItemModelHelper::fillFromGadget(area31, blockItem);
0682                         StandardItemModelHelper::addEntry(i18n("Payload"), StandardItemModelHelper::dataToHex(block.contentData(), block.contentSize(), sizeof(VdvTicketValidityAreaDataType31)), blockItem);
0683                         break;
0684                     }
0685                     default:
0686                         StandardItemModelHelper::fillFromGadget(area, blockItem);
0687                         StandardItemModelHelper::addEntry(i18n("Payload"), StandardItemModelHelper::dataToHex(block.contentData(), block.contentSize(), sizeof(VdvTicketValidityAreaData)), blockItem);
0688                         break;
0689                 }
0690                 break;
0691             }
0692             default:
0693                 StandardItemModelHelper::addEntry(i18n("Data"), StandardItemModelHelper::dataToHex(block.contentData(), block.contentSize()), blockItem);
0694             }
0695         }
0696 
0697         item = StandardItemModelHelper::addEntry(i18n("Transaction data"), {}, m_vdvModel->invisibleRootItem());
0698         StandardItemModelHelper::fillFromGadget(vdv.commonTransactionData(), item);
0699         item = StandardItemModelHelper::addEntry(i18n("Product-specific transaction data (%1 bytes)", vdv.productSpecificTransactionData().contentSize()), {}, m_vdvModel->invisibleRootItem());
0700         for (auto block = vdv.productSpecificTransactionData().first(); block.isValid(); block = block.next()) {
0701             auto blockItem = StandardItemModelHelper::addEntry(i18n("Tag 0x%1 (%2 bytes)", QString::number(block.type(), 16), block.size()), {}, item);
0702             switch (block.type()) {
0703                 default:
0704                     StandardItemModelHelper::addEntry(i18n("Data"), StandardItemModelHelper::dataToHex(block.contentData(), block.contentSize()), blockItem);
0705             }
0706         }
0707 
0708         item = StandardItemModelHelper::addEntry(i18n("Issue data"), {}, m_vdvModel->invisibleRootItem());
0709         StandardItemModelHelper::fillFromGadget(vdv.issueData(), item);
0710         item = StandardItemModelHelper::addEntry(i18n("Trailer"), {}, m_vdvModel->invisibleRootItem());
0711         StandardItemModelHelper::addEntry(i18n("identifier"), QString::fromUtf8(vdv.trailer()->identifier, 3), item);
0712         StandardItemModelHelper::fillFromGadget(vdv.trailer(), item);
0713 
0714         ui->vdvView->expandAll();
0715         ui->inputTabWidget->setTabEnabled(VdvTab, true);
0716     }
0717 }