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 }