diff --git a/PdfTool/PdfTool.pro b/PdfTool/PdfTool.pro
index 4ed7ca7..43b3c0a 100644
--- a/PdfTool/PdfTool.pro
+++ b/PdfTool/PdfTool.pro
@@ -42,6 +42,7 @@ LIBS += -lPDFForQtLib
SOURCES += \
main.cpp \
+ pdfoutputformatter.cpp \
pdftoolabstractapplication.cpp
# Default rules for deployment.
@@ -54,4 +55,5 @@ application.path = $$DESTDIR/install
INSTALLS += application
HEADERS += \
+ pdfoutputformatter.h \
pdftoolabstractapplication.h
diff --git a/PdfTool/pdfoutputformatter.cpp b/PdfTool/pdfoutputformatter.cpp
new file mode 100644
index 0000000..2836b46
--- /dev/null
+++ b/PdfTool/pdfoutputformatter.cpp
@@ -0,0 +1,571 @@
+// Copyright (C) 2020 Jakub Melka
+//
+// This file is part of PdfForQt.
+//
+// PdfForQt is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// PdfForQt is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with PDFForQt. If not, see .
+
+#include "pdfoutputformatter.h"
+
+#include
+#include
+#include
+
+#include
+
+namespace pdftool
+{
+
+class PDFOutputFormatterImpl
+{
+public:
+ explicit PDFOutputFormatterImpl() = default;
+ virtual ~PDFOutputFormatterImpl() = default;
+
+ /// Starts a new element in structure tree. Each call of this function must be
+ /// accompanied with matching call to \p endElement function. Element can have
+ /// internal name (in xml file), text description for user (as string), and reference
+ /// number. Also alignment can also be specified. Element type and name must
+ /// always be specified.
+ /// \param type Element type
+ /// \param name Internal element name (for example, xml tag if style is XML)
+ /// \param description Text description for user
+ /// \param alignment Cell alignment in table
+ /// \param reference Reference number
+ virtual void beginElement(PDFOutputFormatter::Element type, QString name, QString description = QString(), Qt::Alignment alignment = Qt::AlignLeft, int reference = 0) = 0;
+
+ /// Ends current element. Must match with a call of \p beginElement
+ virtual void endElement() = 0;
+
+ /// Get result string in unicode.
+ virtual QString getString() const = 0;
+};
+
+class PDFTextOutputFormatterImpl : public PDFOutputFormatterImpl
+{
+public:
+ PDFTextOutputFormatterImpl();
+
+ virtual void beginElement(PDFOutputFormatter::Element type, QString name, QString description, Qt::Alignment alignment, int reference) override;
+ virtual void endElement() override;
+ virtual QString getString() const override;
+
+private:
+ static constexpr const int INDENT_STEP = 2;
+
+ void writeIndent();
+
+ struct TableCell
+ {
+ QString text;
+ Qt::Alignment alignment;
+ };
+
+ const TableCell& getTableCell(size_t row, size_t column) const;
+
+ QString m_string;
+ QTextStream m_streamWriter;
+ int m_depth;
+ int m_indent;
+ std::stack m_elementStack;
+ std::vector> m_table;
+};
+
+class PDFXmlOutputFormatterImpl : public PDFOutputFormatterImpl
+{
+public:
+ PDFXmlOutputFormatterImpl();
+
+ virtual void beginElement(PDFOutputFormatter::Element type, QString name, QString description, Qt::Alignment alignment, int reference) override;
+ virtual void endElement() override;
+ virtual QString getString() const override;
+
+private:
+ QString m_string;
+ QString m_namespace;
+ QString m_prefix;
+ QXmlStreamWriter m_streamWriter;
+ int m_depth;
+};
+
+class PDFHtmlOutputFormatterImpl : public PDFOutputFormatterImpl
+{
+public:
+ PDFHtmlOutputFormatterImpl();
+
+ virtual void beginElement(PDFOutputFormatter::Element type, QString name, QString description, Qt::Alignment alignment, int reference) override;
+ virtual void endElement() override;
+ virtual QString getString() const override;
+
+private:
+ QString m_string;
+ QXmlStreamWriter m_streamWriter;
+ int m_depth;
+ int m_headerDepth;
+ std::stack m_elementStack;
+};
+
+PDFTextOutputFormatterImpl::PDFTextOutputFormatterImpl() :
+ m_string(),
+ m_streamWriter(&m_string, QIODevice::WriteOnly),
+ m_depth(0),
+ m_elementStack()
+{
+
+}
+
+void PDFTextOutputFormatterImpl::beginElement(PDFOutputFormatter::Element type, QString name, QString description, Qt::Alignment alignment, int reference)
+{
+ Q_UNUSED(reference);
+ m_elementStack.push(type);
+
+ switch (type)
+ {
+ case PDFOutputFormatter::Element::Root:
+ {
+ m_streamWriter << description << Qt::endl;
+ m_indent += INDENT_STEP;
+ break;
+ }
+
+ case PDFOutputFormatter::Element::Table:
+ case PDFOutputFormatter::Element::Header:
+ {
+ writeIndent();
+ m_streamWriter << description << Qt::endl;
+ m_indent += INDENT_STEP;
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableRow:
+ case PDFOutputFormatter::Element::TableHeaderRow:
+ {
+ m_table.emplace_back();
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableHeaderColumn:
+ case PDFOutputFormatter::Element::TableColumn:
+ {
+ TableCell cell;
+ cell.text = QString(" %1 ").arg(description);
+ cell.alignment = alignment;
+
+ Q_ASSERT(!m_table.empty());
+ m_table.back().emplace_back(qMove(cell));
+ break;
+ }
+
+ default:
+ {
+ Q_ASSERT(false);
+ break;
+ }
+ }
+
+ // Increment depth by one
+ ++m_depth;
+}
+
+void PDFTextOutputFormatterImpl::endElement()
+{
+ PDFOutputFormatter::Element type = m_elementStack.top();
+ m_elementStack.pop();
+ --m_depth;
+
+ switch (type)
+ {
+ case PDFOutputFormatter::Element::Root:
+ {
+ m_indent -= INDENT_STEP;
+ break;
+ }
+
+ case PDFOutputFormatter::Element::Table:
+ {
+ // Print the table
+ const size_t rows = m_table.size();
+ const size_t columns = (*std::max_element(m_table.cbegin(), m_table.cend(), [](const auto& l, const auto& r) { return l.size() < r.size(); })).size();
+
+ // Detect maximal column size
+ std::vector columnSize(columns, 0);
+ for (size_t row = 0; row < rows; ++row)
+ {
+ for (size_t column = 0; column < columns; ++column)
+ {
+ const TableCell& tableCell = getTableCell(row, column);
+ columnSize[column] = qMax(columnSize[column], tableCell.text.size());
+ }
+ }
+
+ // Print cells
+ m_streamWriter.setPadChar(QChar(QChar::Space));
+ for (size_t row = 0; row < rows; ++row)
+ {
+ for (size_t column = 0; column < columns; ++column)
+ {
+ const TableCell& tableCell = getTableCell(row, column);
+ m_streamWriter.setFieldWidth(columnSize[column]);
+
+ if (tableCell.alignment.testFlag(Qt::AlignLeft))
+ {
+ m_streamWriter.setFieldAlignment(QTextStream::AlignLeft);
+ }
+ else if (tableCell.alignment.testFlag(Qt::AlignCenter))
+ {
+ m_streamWriter.setFieldAlignment(QTextStream::AlignCenter);
+ }
+ else if (tableCell.alignment.testFlag(Qt::AlignRight))
+ {
+ m_streamWriter.setFieldAlignment(QTextStream::AlignRight);
+ }
+
+ m_streamWriter << tableCell.text;
+ }
+
+ m_streamWriter.setFieldWidth(0);
+ m_streamWriter << Qt::endl;
+ }
+
+ m_indent -= INDENT_STEP;
+ m_table.clear();
+ break;
+ }
+
+ case PDFOutputFormatter::Element::Header:
+ {
+ m_indent -= INDENT_STEP;
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableRow:
+ case PDFOutputFormatter::Element::TableHeaderRow:
+ case PDFOutputFormatter::Element::TableHeaderColumn:
+ case PDFOutputFormatter::Element::TableColumn:
+ {
+ break;
+ }
+
+ default:
+ {
+ Q_ASSERT(false);
+ break;
+ }
+ }
+}
+
+QString PDFTextOutputFormatterImpl::getString() const
+{
+ return m_string;
+}
+
+void PDFTextOutputFormatterImpl::writeIndent()
+{
+ QString str(m_indent, QChar(QChar::Space));
+ m_streamWriter << str;
+}
+
+const PDFTextOutputFormatterImpl::TableCell& PDFTextOutputFormatterImpl::getTableCell(size_t row, size_t column) const
+{
+ if (row < m_table.size())
+ {
+ const auto& columns = m_table[row];
+ if (column < columns.size())
+ {
+ return columns[column];
+ }
+ }
+
+ static const TableCell dummy;
+ return dummy;
+}
+
+PDFHtmlOutputFormatterImpl::PDFHtmlOutputFormatterImpl() :
+ m_string(),
+ m_streamWriter(&m_string),
+ m_depth(0),
+ m_headerDepth(1),
+ m_elementStack()
+{
+
+}
+
+void PDFHtmlOutputFormatterImpl::beginElement(PDFOutputFormatter::Element type, QString name, QString description, Qt::Alignment alignment, int reference)
+{
+ Q_UNUSED(reference);
+ m_elementStack.push(type);
+
+ auto writeTableCellAlignment = [this, alignment]()
+ {
+ if (alignment.testFlag(Qt::AlignLeft))
+ {
+ m_streamWriter.writeAttribute("align", "left");
+ }
+ if (alignment.testFlag(Qt::AlignCenter))
+ {
+ m_streamWriter.writeAttribute("align", "center");
+ }
+ if (alignment.testFlag(Qt::AlignRight))
+ {
+ m_streamWriter.writeAttribute("align", "right");
+ }
+ };
+
+ switch (type)
+ {
+ case PDFOutputFormatter::Element::Root:
+ {
+ m_streamWriter.writeStartDocument();
+
+ QString title = QString("%1 - Processed by %2 %3").arg(description, QCoreApplication::applicationName(), QCoreApplication::applicationVersion());
+ m_streamWriter.writeStartElement("html");
+ m_streamWriter.writeStartElement("head");
+ m_streamWriter.writeTextElement("title", title);
+ m_streamWriter.writeEndElement();
+ m_streamWriter.writeStartElement("body");
+ m_streamWriter.writeStartElement("p");
+ m_streamWriter.writeTextElement("h1", title);
+ m_streamWriter.writeEndElement();
+ break;
+ }
+
+ case PDFOutputFormatter::Element::Header:
+ {
+ // Just print single paragraph with header
+ ++m_headerDepth;
+ int headerTagDepth = qBound(1, m_headerDepth, 6);
+ QString headerTag = QString("h%1").arg(headerTagDepth);
+ m_streamWriter.writeStartElement("p");
+ m_streamWriter.writeTextElement(headerTag, description);
+ m_streamWriter.writeEndElement();
+ break;
+ }
+
+ case PDFOutputFormatter::Element::Table:
+ {
+ m_streamWriter.writeStartElement("table");
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableHeaderRow:
+ {
+ m_streamWriter.writeStartElement("tr");
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableHeaderColumn:
+ {
+ m_streamWriter.writeStartElement("th");
+ writeTableCellAlignment();
+ m_streamWriter.writeCharacters(description);
+ m_streamWriter.writeEndElement();
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableRow:
+ {
+ m_streamWriter.writeStartElement("tr");
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableColumn:
+ {
+ m_streamWriter.writeStartElement("td");
+ writeTableCellAlignment();
+ m_streamWriter.writeCharacters(description);
+ m_streamWriter.writeEndElement();
+ break;
+ }
+
+ default:
+ {
+ Q_ASSERT(false);
+ break;
+ }
+ }
+
+ // Increment depth by one
+ ++m_depth;
+}
+
+void PDFHtmlOutputFormatterImpl::endElement()
+{
+ PDFOutputFormatter::Element type = m_elementStack.top();
+ m_elementStack.pop();
+ --m_depth;
+
+ switch (type)
+ {
+ case PDFOutputFormatter::Element::Root:
+ {
+ Q_ASSERT(m_depth == 0);
+ m_streamWriter.writeEndElement();
+ m_streamWriter.writeEndElement();
+ m_streamWriter.writeEndDocument();
+ break;
+ }
+
+ case PDFOutputFormatter::Element::Header:
+ {
+ // Just decrement header depth
+ --m_headerDepth;
+ break;
+ }
+ case PDFOutputFormatter::Element::Table:
+ case PDFOutputFormatter::Element::TableHeaderRow:
+ case PDFOutputFormatter::Element::TableRow:
+ {
+ m_streamWriter.writeEndElement();
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableHeaderColumn:
+ case PDFOutputFormatter::Element::TableColumn:
+ {
+ // Do nothing...
+ break;
+ }
+
+ default:
+ {
+ Q_ASSERT(false);
+ break;
+ }
+ }
+}
+
+QString PDFHtmlOutputFormatterImpl::getString() const
+{
+ QString html = m_string;
+ html.remove(0, html.indexOf("?>") + 2);
+ html.prepend("");
+ return html;
+}
+
+PDFXmlOutputFormatterImpl::PDFXmlOutputFormatterImpl() :
+ m_string(),
+ m_streamWriter(&m_string),
+ m_depth(0)
+{
+ m_streamWriter.setAutoFormatting(true);
+ m_streamWriter.setAutoFormattingIndent(2);
+
+ m_namespace = "https://github.com/JakubMelka/PdfForQt";
+ m_prefix = "pdftool";
+}
+
+void PDFXmlOutputFormatterImpl::beginElement(PDFOutputFormatter::Element type, QString name, QString description, Qt::Alignment alignment, int reference)
+{
+ Q_UNUSED(alignment);
+
+ switch (type)
+ {
+ case PDFOutputFormatter::Element::Root:
+ {
+ m_streamWriter.writeStartDocument();
+
+ QString comment = QString("Processed by %1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion());
+
+ m_streamWriter.writeComment(comment);
+ m_streamWriter.writeNamespace(m_namespace, m_prefix);
+
+ m_streamWriter.writeStartElement(m_namespace, name);
+ break;
+ }
+
+ case PDFOutputFormatter::Element::TableColumn:
+ case PDFOutputFormatter::Element::TableHeaderColumn:
+ {
+ m_streamWriter.writeTextElement(m_namespace, name, description);
+ break;
+ }
+
+ default:
+ {
+ m_streamWriter.writeStartElement(m_namespace, name);
+
+ if (!description.isEmpty())
+ {
+ m_streamWriter.writeAttribute(m_namespace, "description", description);
+ }
+
+ if (reference > 0)
+ {
+ m_streamWriter.writeAttribute(m_namespace, "ref", QString::number(reference));
+ }
+ break;
+ }
+ }
+
+ // Increment depth by one
+ ++m_depth;
+}
+
+void PDFXmlOutputFormatterImpl::endElement()
+{
+ m_streamWriter.writeEndElement();
+
+ // Do we finish the document? If yes, then tell stream writer to end the document
+ if (--m_depth == 0)
+ {
+ m_streamWriter.writeEndDocument();
+ }
+}
+
+QString PDFXmlOutputFormatterImpl::getString() const
+{
+ return m_string;
+}
+
+PDFOutputFormatter::PDFOutputFormatter(Style style) :
+ m_impl(nullptr)
+{
+ switch (style)
+ {
+ case Style::Text:
+ m_impl = new PDFTextOutputFormatterImpl();
+ break;
+
+ case Style::Xml:
+ m_impl = new PDFXmlOutputFormatterImpl();
+ break;
+
+ case Style::Html:
+ m_impl = new PDFHtmlOutputFormatterImpl();
+ break;
+ }
+
+ Q_ASSERT(m_impl);
+}
+
+PDFOutputFormatter::~PDFOutputFormatter()
+{
+ delete m_impl;
+}
+
+void PDFOutputFormatter::beginElement(PDFOutputFormatter::Element type, QString name, QString description, Qt::Alignment alignment, int reference)
+{
+ m_impl->beginElement(type, name, description, alignment, reference);
+}
+
+void PDFOutputFormatter::endElement()
+{
+ m_impl->endElement();
+}
+
+QString PDFOutputFormatter::getString() const
+{
+ return m_impl->getString();
+}
+
+} // pdftool
diff --git a/PdfTool/pdfoutputformatter.h b/PdfTool/pdfoutputformatter.h
new file mode 100644
index 0000000..cae9b15
--- /dev/null
+++ b/PdfTool/pdfoutputformatter.h
@@ -0,0 +1,78 @@
+// Copyright (C) 2020 Jakub Melka
+//
+// This file is part of PdfForQt.
+//
+// PdfForQt is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// PdfForQt is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with PDFForQt. If not, see .
+
+#ifndef PDFOUTPUTFORMATTER_H
+#define PDFOUTPUTFORMATTER_H
+
+#include
+
+namespace pdftool
+{
+class PDFOutputFormatterImpl;
+
+/// Output formatter for text output in various format (text, xml, html...)
+/// to the output console. Text output is in form of a structure tree,
+/// for example, xml format.
+class PDFOutputFormatter
+{
+public:
+ enum class Style
+ {
+ Text,
+ Xml,
+ Html
+ };
+
+ explicit PDFOutputFormatter(Style style);
+ ~PDFOutputFormatter();
+
+ enum class Element
+ {
+ Root, ///< Root element, this must be used only once at start/end of writing
+ Header, ///< Header
+ Table, ///< Table of rows/columns (2D grid)
+ TableHeaderRow, ///< Table header row (consists of columns)
+ TableHeaderColumn, ///< Table header column
+ TableRow, ///< Table row (consists of columns)
+ TableColumn ///< Table column
+ };
+
+ /// Starts a new element in structure tree. Each call of this function must be
+ /// accompanied with matching call to \p endElement function. Element can have
+ /// internal name (in xml file), text description for user (as string), and reference
+ /// number. Also alignment can also be specified. Element type and name must
+ /// always be specified.
+ /// \param type Element type
+ /// \param name Internal element name (for example, xml tag if style is XML)
+ /// \param description Text description for user
+ /// \param alignment Cell alignment in table
+ /// \param reference Reference number
+ void beginElement(Element type, QString name, QString description = QString(), Qt::Alignment alignment = Qt::AlignLeft, int reference = 0);
+
+ /// Ends current element. Must match with a call of \p beginElement
+ void endElement();
+
+ /// Get result string in unicode.
+ QString getString() const;
+
+private:
+ PDFOutputFormatterImpl* m_impl;
+};
+
+} // namespace pdftool
+
+#endif // PDFOUTPUTFORMATTER_H