changeset 38:5354cbda7188

Fix HTML Layout. Support multiple formats at once. More handling. This commit is a bad mix of multiple changes. It addresses: - HTML Width is now relative and should fix some pdf creation problems. - Format is now taken from the extension of the file names provided. - Multiple file names are accepted at once. - Parser now handles missing values in Multiple choice answers - Parser now handles unfilled multiple choice values
author Andre Heinecke <andre.heinecke@intevation.de>
date Fri, 15 Apr 2016 15:19:04 +0200
parents b574990e90a5
children f6c53e896008
files src/cconvert_options.h src/constants.h src/converter.cpp src/converter.h src/l10n/main_de_DE.ts src/main.cpp
diffstat 6 files changed, 159 insertions(+), 127 deletions(-) [+]
line wrap: on
line diff
--- a/src/cconvert_options.h	Fri Apr 15 15:15:44 2016 +0200
+++ b/src/cconvert_options.h	Fri Apr 15 15:19:04 2016 +0200
@@ -17,13 +17,10 @@
 {
     QList<QCommandLineOption> options;
 
-    options << QCommandLineOption(QStringList() << QStringLiteral("format")
-                                  << QStringLiteral("f"),
-                                  QObject::tr("Output format (default xlsx)."),
-                                  QStringLiteral("xlsx pdf html"))
-            << QCommandLineOption(QStringList() << QStringLiteral("output")
+    options << QCommandLineOption(QStringList() << QStringLiteral("output")
                                   << QStringLiteral("o"),
-                                  QObject::tr("write output to file (default stdout)"),
+                                  QObject::tr("write output to file (default stdout)") + "\n" +
+                                  QObject::tr("The file type is determined by the extension\nEither: .pdf .html or .xlsx (default)"),
                                   QObject::tr("file"))
             << QCommandLineOption(QStringList() << QStringLiteral("title")
                                   << QStringLiteral("t"),
--- a/src/constants.h	Fri Apr 15 15:15:44 2016 +0200
+++ b/src/constants.h	Fri Apr 15 15:19:04 2016 +0200
@@ -63,7 +63,11 @@
 
 /**
  * @brief The pattern used to match a multiple choice answer. */
-#define CHOICE_PATTERN "\"(.*)\",(\\d+),(\\d+\\.\\d+)"
+#define CHOICE_PATTERN "\"(.*)\",(?<num>\\d+)?,(?<percent>\\d+\\.?\\d+)?"
+
+/**
+ * @brief The pattern used to match an unfilled choice answer. */
+#define CHOICE_UNFILLED_PATTERN "\"([^\"]*)\","
 
 /**
  * @brief The pattern used to match a free text answer. */
@@ -79,8 +83,12 @@
 
 #define DEFAULT_TITLE QStringLiteral("Clicker-Fragen und Antworten")
 
-#define HTML_WIDTH 960
-
 #define BAR_COLOR "#ff9933"
 
+#define IMAGE_WIDTH 200
+
+#define HTML_COL1_PERCENT 35
+#define HTML_COL2_PERCENT 30
+#define HTML_COL3_PERCENT 35
+
 #endif // CONSTANTS_H
--- a/src/converter.cpp	Fri Apr 15 15:15:44 2016 +0200
+++ b/src/converter.cpp	Fri Apr 15 15:19:04 2016 +0200
@@ -22,12 +22,11 @@
 
 QTXLSX_USE_NAMESPACE
 
-Converter::Converter(const QString &input, const QString &output,
-                     ConvertFormat fmt, const QString &title):
+Converter::Converter(const QString &input, const QStringList &outputs,
+                     const QString &title):
     QThread(Q_NULLPTR),
     mInput(input),
-    mOutput(output),
-    mFmt(fmt),
+    mOutputs(outputs),
     mTitle(title)
 {
     mTitleFmt.setFontUnderline(Format::FontUnderlineSingle);
@@ -95,49 +94,49 @@
     }
     QTextStream instream(&infile);
 
-    QFile outfile;
-    if (mOutput.isEmpty()) {
-        if (!outfile.open(stdout, QIODevice::WriteOnly)) {
+    QList<QFile*> outfiles;
+
+    if (mOutputs.isEmpty()) {
+        QFile *outfile = new QFile();
+        if (!outfile->open(stdout, QIODevice::WriteOnly)) {
             mErrors << tr("Failed to open standard output and no output file provided.");
             return;
         }
-    } else {
-        outfile.setFileName(mOutput);
-        if (!outfile.open(QIODevice::WriteOnly)) {
-            mErrors << tr("Failed to open %1 for writing.").arg(mOutput);
+        outfiles << outfile;
+    }
+    foreach (const QString &fileName, mOutputs) {
+        QFile *outfile = new QFile();
+        outfile->setFileName(fileName);
+        if (!outfile->open(QIODevice::WriteOnly)) {
+            mErrors << tr("Failed to open %1 for writing.").arg(fileName);
             return;
         }
+        outfiles << outfile;
     }
-    convertToXSLX(instream, outfile);
+    convertToXSLX(instream, outfiles);
 }
 
-static void makeBar(QTextStream &html, double percent, int width, QTextDocument &doc, bool forPDF)
+static void makeBar(QTextStream &html, double percent, QTextDocument &doc)
 {
-    static int barCnt;
-    if (!forPDF) {
-        html << QStringLiteral("<td style='background:linear-gradient(to right,"
-                               BAR_COLOR ", " BAR_COLOR " %1%, #ffffff %1%)'></td>").arg(percent);
-        return;
-    }
-    QImage image(QSize(width, 25), QImage::Format_RGB32);
+    QImage image(QSize(IMAGE_WIDTH, 25), QImage::Format_RGB32);
     QPainter painter(&image);
     QRect rect = image.rect();
-    qDebug() << "Image of " << width;
-    rect.setRight(rect.right() / (100 / percent));
-    painter.fillRect(rect, QColor(BAR_COLOR));
+    if (percent) {
+        rect.setRight(rect.right() / (100. / percent));
+        painter.fillRect(rect, QColor(BAR_COLOR));
+        rect.setLeft(rect.right());
+    }
     qDebug() << "Filled " << rect << " with color";
-    rect.setLeft(rect.right());
-    rect.setRight(width);
+    rect.setRight(IMAGE_WIDTH);
     painter.fillRect(rect, Qt::white);
     qDebug() << "Filled " << rect << " with white";
-    doc.addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("internal://bar%1.png").arg(barCnt)),
+    doc.addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("internal://bar%1.png").arg((int)percent)),
                     QVariant(image));
-    html << QStringLiteral("<td style='vertical-align: middle'><img src=\"internal://bar%1.png\"/></td>").arg(barCnt++);
-    image.save(QStringLiteral("/tmp/foo.png"));
+    html << QStringLiteral("<td style='vertical-align: middle'><img src=\"internal://bar%1.png\"/></td>").arg((int)percent);
     return;
 }
 
-void Converter::convertToXSLX(QTextStream& instream, QFile &output)
+void Converter::convertToXSLX(QTextStream& instream, QList<QFile *>outputs)
 {
     Document xlsx;
     QTextDocument doc;
@@ -157,21 +156,16 @@
         sum += colWidth[i-1];
     }
 
-    double xlsx2htmlFactor = HTML_WIDTH / sum;
-    int col1Width = colWidth[0] * xlsx2htmlFactor;
-    int col2Width = colWidth[1] * xlsx2htmlFactor;
-    int col3Width = colWidth[2] * xlsx2htmlFactor;
-
     /* For the merged cell wordwrap trick. */
     xlsx.setColumnWidth(26, sum + 1);
     xlsx.setColumnHidden(26, true);
 
     int row = 1;
     html << "<html><meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">"
-            "<body><table border=\"0\" style='width:\"" << HTML_WIDTH << "px\";border-collapse:collapse'>";
-    html << "<tr><th width=\"" << col1Width << "px\"</th>";
-    html << "<th width=\"" << col2Width << "px\"</th>";
-    html << "<th width=\"" << col3Width << "px\"</th>";
+            "<body><table border=\"0\" width:\"100%\">";
+    html << QStringLiteral("<tr><th width=\"%1%\"></th>").arg(HTML_COL1_PERCENT);
+    html << QStringLiteral("<th width=\"%1%\"></th>").arg(HTML_COL2_PERCENT);
+    html << QStringLiteral("<th width=\"%1%\"></th>").arg(HTML_COL3_PERCENT);
 
     const QString title = mTitle.isEmpty() ? DEFAULT_TITLE : mTitle;
     // Set the title of the Questionaire
@@ -184,7 +178,8 @@
 
     QRegularExpression questionEx(QUESTION_PATTERN);
     QRegularExpression choiceEx(CHOICE_PATTERN);
-    QRegularExpression freetxtEx (FREETXT_PATTERN);
+    QRegularExpression choiceAltEx(CHOICE_UNFILLED_PATTERN);
+    QRegularExpression freetxtEx(FREETXT_PATTERN);
 
     QRegularExpressionMatch match = questionEx.match(input);
     bool foundSomething = false;
@@ -206,12 +201,13 @@
         html << mQuestionStyle.arg(question.toHtmlEscaped());
 
         if (answerLine == QStringLiteral(CHOICE_IDENTIFIER)) {
-            QRegularExpressionMatch choiceMatch = choiceEx.match(input, cursor);
             xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT);
             xlsx.write(row++, 1, "Answer", mAnswerChoiceFmt);
             html << mAnswerChoiceStyle;
             int firstChoiceRow = row;
             int lastChoiceRow = row;
+repeat:
+            QRegularExpressionMatch choiceMatch = choiceEx.match(input, cursor);
             while (choiceMatch.hasMatch() && choiceMatch.capturedStart() == cursor + 1) {
                 /* We use the cursor here to keep track of the state. Only if an answer
                    follows immediately behind the last answer we treat it as valid as
@@ -225,15 +221,25 @@
                 }
                 xlsx.write(row, 1, choiceName, mChoiceTextFmt);
                 html << mChoiceTextStyle.arg(choiceName.toHtmlEscaped());
+                qDebug() << "Captured for choice: " << choiceMatch.captured(0);
                 bool ok;
-                double percent = choiceMatch.captured(3).toDouble(&ok);
+                const QString percentStr = choiceMatch.captured("percent");
+                double percent;
+                if (percentStr.isNull()) {
+                    percent = 0;
+                    ok = true;
+                } else {
+                    percent = percentStr.toDouble(&ok);
+                }
                 if (!ok) {
                     mErrors << "Unparsable number in string: " + choiceMatch.captured();
                 }
-                makeBar(html, percent, col2Width, doc, mFmt == Format_PDF);
+                makeBar(html, percent, doc);
                 xlsx.write(row, 2, percent);
+                const QString numStr = choiceMatch.captured("num");
                 const QString numVotesString = QString("%1% | %2 Number of votes").
-                           arg(choiceMatch.captured(3)).arg(choiceMatch.captured(2));
+                           arg(percentStr.isNull() ? QStringLiteral("0") : percentStr).
+                           arg(numStr.isNull() ? QStringLiteral("0") : numStr);
                 html << mChoiceVotesStyle.arg(numVotesString.toHtmlEscaped());
                 xlsx.write(row, 3, numVotesString, mChoiceVotesFmt);
                 xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT);
@@ -243,6 +249,36 @@
                 row++;
                 lastChoiceRow++;
             }
+            choiceMatch = choiceAltEx.match(input, cursor);
+            bool additionalFound = false;
+            while (choiceMatch.hasMatch() && choiceMatch.capturedStart() <= cursor + 1) {
+                additionalFound = true;
+                const QString choice = choiceMatch.captured(1);
+                cursor = choiceMatch.capturedEnd();
+                /* Alternative answer that is just a list of strings */
+                qDebug() << choiceAltEx.captureCount();
+                qDebug() << choiceMatch.captured(2);
+                qDebug() << choiceMatch.capturedTexts();
+                qDebug() << "Caputured unfilled choice: " << choice;
+                html << mChoiceTextStyle.arg(choice.toHtmlEscaped());
+                makeBar(html, 0, doc);
+                xlsx.write(row, 2, 0);
+                const QString numVotesString = QStringLiteral("Keine eingegangenen Antworten");
+                html << mChoiceVotesStyle.arg(numVotesString.toHtmlEscaped());
+                xlsx.write(row, 3, numVotesString, mChoiceVotesFmt);
+                xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT);
+                row++;
+                lastChoiceRow++;
+                choiceMatch = choiceAltEx.match(input, cursor);
+                QRegularExpressionMatch realMatch = choiceEx.match(input, cursor);
+                if (choiceMatch.hasMatch() && choiceMatch.capturedStart() == realMatch.capturedStart()) {
+                    /* We have a real match so back to the other pattern. */
+                    break;
+                }
+            }
+            if (additionalFound) {
+                goto repeat;
+            }
             bars.addRange(QString("B%1:B%2").arg(firstChoiceRow).arg(lastChoiceRow));
 //            xlsx.groupRows(firstChoiceRow - 2, lastChoiceRow - 1, false);
         } else if (answerLine == QStringLiteral(TEXT_IDENTIFIER)) {
@@ -325,33 +361,39 @@
         mErrors << tr("Failed to parse input document.");
     }
 
-    if (mFmt == Format_XLSX && !xlsx.saveAs(&output)) {
-        mErrors << tr("Saving the XLSX document failed.");
-        return;
-    }
-
     html << "</table></body></html>";
-
-    if (mFmt == Format_HTML) {
-        QTextStream outstream(&output);
-        outstream << htmlString;
-        return;
-    }
+    doc.setHtml(htmlString);
 
-    if (mFmt == Format_PDF) {
-        output.close();
-        QPrinter printer(QPrinter::PrinterResolution);
-        printer.setOutputFormat(QPrinter::PdfFormat);
-        printer.setPaperSize(QPrinter::A4);
-        printer.setOutputFileName(output.fileName());
-        doc.setHtml(htmlString);
-        /*
-        QPageLayout layout = printer.pageLayout();
-        layout.setUnits(QPageLayout::Millimeter);
-        layout.setMargins(QMarginsF(20, 20, 20, 20));
-        printer.setPageLayout(layout);
-        doc.setPageSize(printer.pageRect().size());
-        */
-        doc.print(&printer);
+    /* Fixup images for html */
+    QRegularExpression htmlRe = QRegularExpression("<td style='vertical-align: middle'><img src=\"internal://bar(\\d+).png\"/></td>");
+    htmlString.replace(htmlRe, QStringLiteral("<td style='background:linear-gradient(to right,"
+                                              BAR_COLOR ", " BAR_COLOR " \\1%, #ffffff \\1%)'></td>"));
+
+    foreach (QFile *output, outputs) {
+        const QString fName = output->fileName().toLower();
+        if (fName.endsWith(".html")) {
+            QTextStream outstream(output);
+            outstream << htmlString;
+            output->close();
+        } else if (fName.endsWith(".pdf")) {
+            output->close();
+            QPrinter printer(QPrinter::PrinterResolution);
+            printer.setOutputFormat(QPrinter::PdfFormat);
+            printer.setPaperSize(QPrinter::A4);
+            printer.setOutputFileName(output->fileName());
+            /*
+            QPageLayout layout = printer.pageLayout();
+            layout.setUnits(QPageLayout::Millimeter);
+            layout.setMargins(QMarginsF(20, 20, 20, 20));
+            printer.setPageLayout(layout);
+            doc.setPageSize(printer.pageRect().size());
+            */
+            doc.print(&printer);
+        } else {
+            if (!xlsx.saveAs(output)) {
+                mErrors << tr("Saving the XLSX document failed.");
+            }
+            output->close();
+        }
     }
 }
--- a/src/converter.h	Fri Apr 15 15:15:44 2016 +0200
+++ b/src/converter.h	Fri Apr 15 15:19:04 2016 +0200
@@ -13,25 +13,13 @@
 #include <QStringList>
 #include <QTextStream>
 #include <QFile>
+#include <QList>
 
 #include "xlsxformat.h"
 
 /** @file Declaration of the Converter class.
  */
 
-/**
- * @enum ConvertFormat
- * @brief Possible output format values.
- */
-enum ConvertFormat {
-    /*! XLSX (default). */
-    Format_XLSX,
-    /*! PDF */
-    Format_PDF,
-    /*! HTML */
-    Format_HTML,
-};
-
 /** @brief Base class of Convert operations.
  *
  * Set up an instance of this using the ctor and according setters and
@@ -48,11 +36,9 @@
      * is empty stdout is used.
      *
      * @param input input filename.
-     * @param output output filename.
-     * @param format the format of this.
+     * @param outputs the files to create.
      */
-    Converter(const QString &input, const QString &output,
-              ConvertFormat fmt = Format_XLSX,
+    Converter(const QString &input, const QStringList &outputs,
               const QString &title = QString());
 
     /** Check for errors
@@ -61,11 +47,11 @@
     const QStringList & errors() {return mErrors;}
 
 protected:
-    void convertToXSLX(QTextStream &instream, QFile &output);
+    void convertToXSLX(QTextStream &instream, QList<QFile*> outputs);
     void run();
 
-    QString mInput, mOutput;
-    ConvertFormat mFmt;
+    QString mInput;
+    QStringList mOutputs;
     QStringList mErrors;
     QString mTitle;
 
--- a/src/l10n/main_de_DE.ts	Fri Apr 15 15:15:44 2016 +0200
+++ b/src/l10n/main_de_DE.ts	Fri Apr 15 15:19:04 2016 +0200
@@ -2,34 +2,41 @@
 <!DOCTYPE TS>
 <TS version="2.1" language="de_DE">
 <context>
+    <name></name>
+    <message>
+        <source>file</source>
+        <translation type="obsolete">Datei</translation>
+    </message>
+</context>
+<context>
     <name>Converter</name>
     <message>
-        <location filename="../converter.cpp" line="86"/>
+        <location filename="../converter.cpp" line="85"/>
         <source>Failed to open standard input and no input file provided.</source>
         <translation>Öffnen des Eingabekanals Fehlgeschlagen und keine Eingabedatei übergeben.</translation>
     </message>
     <message>
-        <location filename="../converter.cpp" line="92"/>
+        <location filename="../converter.cpp" line="91"/>
         <source>Failed to open %1 for reading.</source>
         <translation>Die Datei &quot;%1&quot; konnte nicht gelesen werden.</translation>
     </message>
     <message>
-        <location filename="../converter.cpp" line="101"/>
+        <location filename="../converter.cpp" line="102"/>
         <source>Failed to open standard output and no output file provided.</source>
         <translation>Öffnen des Ausgabekanals Fehlgeschlagen und keine Ausgabedatei übergeben.</translation>
     </message>
     <message>
-        <location filename="../converter.cpp" line="107"/>
+        <location filename="../converter.cpp" line="111"/>
         <source>Failed to open %1 for writing.</source>
         <translation>Die Datei &quot;%1&quot; konnte nicht geöffnet werden.</translation>
     </message>
     <message>
-        <location filename="../converter.cpp" line="325"/>
+        <location filename="../converter.cpp" line="361"/>
         <source>Failed to parse input document.</source>
         <translation>Fehler bei der verarbeitung des Eingabedokuments.</translation>
     </message>
     <message>
-        <location filename="../converter.cpp" line="329"/>
+        <location filename="../converter.cpp" line="394"/>
         <source>Saving the XLSX document failed.</source>
         <translation>Das erstellen des XLSX Dokuments ist fehlgeschlagen.</translation>
     </message>
@@ -37,42 +44,47 @@
 <context>
     <name>QObject</name>
     <message>
-        <location filename="../cconvert_options.h" line="26"/>
+        <location filename="../cconvert_options.h" line="22"/>
         <source>write output to file (default stdout)</source>
         <translation>Ausgabe in Datei schreiben (standard stdout)</translation>
     </message>
     <message>
-        <location filename="../cconvert_options.h" line="42"/>
+        <location filename="../cconvert_options.h" line="39"/>
         <source>[file]</source>
         <translation>[Datei]</translation>
     </message>
     <message>
-        <location filename="../cconvert_options.h" line="30"/>
+        <location filename="../cconvert_options.h" line="27"/>
         <source>Set the title of the document.</source>
         <translation>Titel des Dokuments.</translation>
     </message>
     <message>
-        <location filename="../cconvert_options.h" line="22"/>
         <source>Output format (default xlsx).</source>
-        <translation>Ausgabeformat (standard xlsx)</translation>
+        <translation type="vanished">Ausgabeformat (standard xlsx)</translation>
     </message>
     <message>
-        <location filename="../cconvert_options.h" line="27"/>
+        <location filename="../cconvert_options.h" line="23"/>
+        <source>The file type is determined by the extension
+Either: .pdf .html or .xlsx (default)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../cconvert_options.h" line="24"/>
         <source>file</source>
         <translation>Datei</translation>
     </message>
     <message>
-        <location filename="../cconvert_options.h" line="31"/>
+        <location filename="../cconvert_options.h" line="28"/>
         <source>&quot;The Title&quot;</source>
         <translation>&quot;Der Titel&quot;</translation>
     </message>
     <message>
-        <location filename="../cconvert_options.h" line="33"/>
+        <location filename="../cconvert_options.h" line="30"/>
         <source>Print debug output.</source>
         <translation>Debug ausgaben aktivieren</translation>
     </message>
     <message>
-        <location filename="../cconvert_options.h" line="41"/>
+        <location filename="../cconvert_options.h" line="38"/>
         <source>File to process (default stdin)</source>
         <translation>Eingabedatei (standard stdin)</translation>
     </message>
--- a/src/main.cpp	Fri Apr 15 15:15:44 2016 +0200
+++ b/src/main.cpp	Fri Apr 15 15:19:04 2016 +0200
@@ -129,24 +129,11 @@
         parser.showHelp(1);
     }
 
-    ConvertFormat fmt = Format_XLSX;
-    /* Initialize the converter. */
-    const QString format = parser.value("format").toLower();
-    if (format == "xlsx" || format.isEmpty()) {
-        fmt = Format_XLSX;
-    } else if (format == "html") {
-        fmt = Format_HTML;
-    } else if (format == "pdf") {
-        fmt = Format_PDF;
-    } else {
-        qCritical() << "Format: " << parser.value("format") << "not supported";
-        exit(1);
-    }
     QString infile;
     if (args.size()) {
         infile = args.first();
     }
-    Converter conv(infile, parser.value("output"), fmt,
+    Converter conv(infile, parser.values("output"),
                    parser.value("title"));
 
     conv.start();
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)