comparison src/converter.cpp @ 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 ca66763b6524
children f6c53e896008
comparison
equal deleted inserted replaced
37:b574990e90a5 38:5354cbda7188
20 20
21 #include "constants.h" 21 #include "constants.h"
22 22
23 QTXLSX_USE_NAMESPACE 23 QTXLSX_USE_NAMESPACE
24 24
25 Converter::Converter(const QString &input, const QString &output, 25 Converter::Converter(const QString &input, const QStringList &outputs,
26 ConvertFormat fmt, const QString &title): 26 const QString &title):
27 QThread(Q_NULLPTR), 27 QThread(Q_NULLPTR),
28 mInput(input), 28 mInput(input),
29 mOutput(output), 29 mOutputs(outputs),
30 mFmt(fmt),
31 mTitle(title) 30 mTitle(title)
32 { 31 {
33 mTitleFmt.setFontUnderline(Format::FontUnderlineSingle); 32 mTitleFmt.setFontUnderline(Format::FontUnderlineSingle);
34 mTitleFmt.setFontSize(18); 33 mTitleFmt.setFontSize(18);
35 mTitleFmt.setFontName("Calibri"); 34 mTitleFmt.setFontName("Calibri");
93 return; 92 return;
94 } 93 }
95 } 94 }
96 QTextStream instream(&infile); 95 QTextStream instream(&infile);
97 96
98 QFile outfile; 97 QList<QFile*> outfiles;
99 if (mOutput.isEmpty()) { 98
100 if (!outfile.open(stdout, QIODevice::WriteOnly)) { 99 if (mOutputs.isEmpty()) {
100 QFile *outfile = new QFile();
101 if (!outfile->open(stdout, QIODevice::WriteOnly)) {
101 mErrors << tr("Failed to open standard output and no output file provided."); 102 mErrors << tr("Failed to open standard output and no output file provided.");
102 return; 103 return;
103 } 104 }
104 } else { 105 outfiles << outfile;
105 outfile.setFileName(mOutput); 106 }
106 if (!outfile.open(QIODevice::WriteOnly)) { 107 foreach (const QString &fileName, mOutputs) {
107 mErrors << tr("Failed to open %1 for writing.").arg(mOutput); 108 QFile *outfile = new QFile();
108 return; 109 outfile->setFileName(fileName);
109 } 110 if (!outfile->open(QIODevice::WriteOnly)) {
110 } 111 mErrors << tr("Failed to open %1 for writing.").arg(fileName);
111 convertToXSLX(instream, outfile); 112 return;
113 }
114 outfiles << outfile;
115 }
116 convertToXSLX(instream, outfiles);
112 } 117 }
113 118
114 static void makeBar(QTextStream &html, double percent, int width, QTextDocument &doc, bool forPDF) 119 static void makeBar(QTextStream &html, double percent, QTextDocument &doc)
115 { 120 {
116 static int barCnt; 121 QImage image(QSize(IMAGE_WIDTH, 25), QImage::Format_RGB32);
117 if (!forPDF) {
118 html << QStringLiteral("<td style='background:linear-gradient(to right,"
119 BAR_COLOR ", " BAR_COLOR " %1%, #ffffff %1%)'></td>").arg(percent);
120 return;
121 }
122 QImage image(QSize(width, 25), QImage::Format_RGB32);
123 QPainter painter(&image); 122 QPainter painter(&image);
124 QRect rect = image.rect(); 123 QRect rect = image.rect();
125 qDebug() << "Image of " << width; 124 if (percent) {
126 rect.setRight(rect.right() / (100 / percent)); 125 rect.setRight(rect.right() / (100. / percent));
127 painter.fillRect(rect, QColor(BAR_COLOR)); 126 painter.fillRect(rect, QColor(BAR_COLOR));
127 rect.setLeft(rect.right());
128 }
128 qDebug() << "Filled " << rect << " with color"; 129 qDebug() << "Filled " << rect << " with color";
129 rect.setLeft(rect.right()); 130 rect.setRight(IMAGE_WIDTH);
130 rect.setRight(width);
131 painter.fillRect(rect, Qt::white); 131 painter.fillRect(rect, Qt::white);
132 qDebug() << "Filled " << rect << " with white"; 132 qDebug() << "Filled " << rect << " with white";
133 doc.addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("internal://bar%1.png").arg(barCnt)), 133 doc.addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("internal://bar%1.png").arg((int)percent)),
134 QVariant(image)); 134 QVariant(image));
135 html << QStringLiteral("<td style='vertical-align: middle'><img src=\"internal://bar%1.png\"/></td>").arg(barCnt++); 135 html << QStringLiteral("<td style='vertical-align: middle'><img src=\"internal://bar%1.png\"/></td>").arg((int)percent);
136 image.save(QStringLiteral("/tmp/foo.png"));
137 return; 136 return;
138 } 137 }
139 138
140 void Converter::convertToXSLX(QTextStream& instream, QFile &output) 139 void Converter::convertToXSLX(QTextStream& instream, QList<QFile *>outputs)
141 { 140 {
142 Document xlsx; 141 Document xlsx;
143 QTextDocument doc; 142 QTextDocument doc;
144 QString htmlString; 143 QString htmlString;
145 QTextStream html (&htmlString); 144 QTextStream html (&htmlString);
155 for (int i = 1; i <= COLUMN_CNT; i++) { 154 for (int i = 1; i <= COLUMN_CNT; i++) {
156 xlsx.setColumnWidth(i, colWidth[i-1]); 155 xlsx.setColumnWidth(i, colWidth[i-1]);
157 sum += colWidth[i-1]; 156 sum += colWidth[i-1];
158 } 157 }
159 158
160 double xlsx2htmlFactor = HTML_WIDTH / sum;
161 int col1Width = colWidth[0] * xlsx2htmlFactor;
162 int col2Width = colWidth[1] * xlsx2htmlFactor;
163 int col3Width = colWidth[2] * xlsx2htmlFactor;
164
165 /* For the merged cell wordwrap trick. */ 159 /* For the merged cell wordwrap trick. */
166 xlsx.setColumnWidth(26, sum + 1); 160 xlsx.setColumnWidth(26, sum + 1);
167 xlsx.setColumnHidden(26, true); 161 xlsx.setColumnHidden(26, true);
168 162
169 int row = 1; 163 int row = 1;
170 html << "<html><meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">" 164 html << "<html><meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">"
171 "<body><table border=\"0\" style='width:\"" << HTML_WIDTH << "px\";border-collapse:collapse'>"; 165 "<body><table border=\"0\" width:\"100%\">";
172 html << "<tr><th width=\"" << col1Width << "px\"</th>"; 166 html << QStringLiteral("<tr><th width=\"%1%\"></th>").arg(HTML_COL1_PERCENT);
173 html << "<th width=\"" << col2Width << "px\"</th>"; 167 html << QStringLiteral("<th width=\"%1%\"></th>").arg(HTML_COL2_PERCENT);
174 html << "<th width=\"" << col3Width << "px\"</th>"; 168 html << QStringLiteral("<th width=\"%1%\"></th>").arg(HTML_COL3_PERCENT);
175 169
176 const QString title = mTitle.isEmpty() ? DEFAULT_TITLE : mTitle; 170 const QString title = mTitle.isEmpty() ? DEFAULT_TITLE : mTitle;
177 // Set the title of the Questionaire 171 // Set the title of the Questionaire
178 xlsx.write(row++, 1, title, mTitleFmt); 172 xlsx.write(row++, 1, title, mTitleFmt);
179 html << mTitleStyle.arg(title.toHtmlEscaped()); 173 html << mTitleStyle.arg(title.toHtmlEscaped());
182 176
183 const QString input = instream.readAll(); 177 const QString input = instream.readAll();
184 178
185 QRegularExpression questionEx(QUESTION_PATTERN); 179 QRegularExpression questionEx(QUESTION_PATTERN);
186 QRegularExpression choiceEx(CHOICE_PATTERN); 180 QRegularExpression choiceEx(CHOICE_PATTERN);
187 QRegularExpression freetxtEx (FREETXT_PATTERN); 181 QRegularExpression choiceAltEx(CHOICE_UNFILLED_PATTERN);
182 QRegularExpression freetxtEx(FREETXT_PATTERN);
188 183
189 QRegularExpressionMatch match = questionEx.match(input); 184 QRegularExpressionMatch match = questionEx.match(input);
190 bool foundSomething = false; 185 bool foundSomething = false;
191 int cursor = match.capturedEnd(); 186 int cursor = match.capturedEnd();
192 while (match.hasMatch() && cursor != -1) { 187 while (match.hasMatch() && cursor != -1) {
204 xlsx.write(row, 3, QString(" "), mQuestionFmt); 199 xlsx.write(row, 3, QString(" "), mQuestionFmt);
205 xlsx.write(row++, 1, question, mQuestionFmt); 200 xlsx.write(row++, 1, question, mQuestionFmt);
206 html << mQuestionStyle.arg(question.toHtmlEscaped()); 201 html << mQuestionStyle.arg(question.toHtmlEscaped());
207 202
208 if (answerLine == QStringLiteral(CHOICE_IDENTIFIER)) { 203 if (answerLine == QStringLiteral(CHOICE_IDENTIFIER)) {
209 QRegularExpressionMatch choiceMatch = choiceEx.match(input, cursor);
210 xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT); 204 xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT);
211 xlsx.write(row++, 1, "Answer", mAnswerChoiceFmt); 205 xlsx.write(row++, 1, "Answer", mAnswerChoiceFmt);
212 html << mAnswerChoiceStyle; 206 html << mAnswerChoiceStyle;
213 int firstChoiceRow = row; 207 int firstChoiceRow = row;
214 int lastChoiceRow = row; 208 int lastChoiceRow = row;
209 repeat:
210 QRegularExpressionMatch choiceMatch = choiceEx.match(input, cursor);
215 while (choiceMatch.hasMatch() && choiceMatch.capturedStart() == cursor + 1) { 211 while (choiceMatch.hasMatch() && choiceMatch.capturedStart() == cursor + 1) {
216 /* We use the cursor here to keep track of the state. Only if an answer 212 /* We use the cursor here to keep track of the state. Only if an answer
217 follows immediately behind the last answer we treat it as valid as 213 follows immediately behind the last answer we treat it as valid as
218 otherwise we can't figure out when the next question begins. */ 214 otherwise we can't figure out when the next question begins. */
219 cursor = choiceMatch.capturedEnd(); 215 cursor = choiceMatch.capturedEnd();
223 if (choiceName.startsWith("=")) { 219 if (choiceName.startsWith("=")) {
224 choiceName = " " + choiceName; 220 choiceName = " " + choiceName;
225 } 221 }
226 xlsx.write(row, 1, choiceName, mChoiceTextFmt); 222 xlsx.write(row, 1, choiceName, mChoiceTextFmt);
227 html << mChoiceTextStyle.arg(choiceName.toHtmlEscaped()); 223 html << mChoiceTextStyle.arg(choiceName.toHtmlEscaped());
224 qDebug() << "Captured for choice: " << choiceMatch.captured(0);
228 bool ok; 225 bool ok;
229 double percent = choiceMatch.captured(3).toDouble(&ok); 226 const QString percentStr = choiceMatch.captured("percent");
227 double percent;
228 if (percentStr.isNull()) {
229 percent = 0;
230 ok = true;
231 } else {
232 percent = percentStr.toDouble(&ok);
233 }
230 if (!ok) { 234 if (!ok) {
231 mErrors << "Unparsable number in string: " + choiceMatch.captured(); 235 mErrors << "Unparsable number in string: " + choiceMatch.captured();
232 } 236 }
233 makeBar(html, percent, col2Width, doc, mFmt == Format_PDF); 237 makeBar(html, percent, doc);
234 xlsx.write(row, 2, percent); 238 xlsx.write(row, 2, percent);
239 const QString numStr = choiceMatch.captured("num");
235 const QString numVotesString = QString("%1% | %2 Number of votes"). 240 const QString numVotesString = QString("%1% | %2 Number of votes").
236 arg(choiceMatch.captured(3)).arg(choiceMatch.captured(2)); 241 arg(percentStr.isNull() ? QStringLiteral("0") : percentStr).
242 arg(numStr.isNull() ? QStringLiteral("0") : numStr);
237 html << mChoiceVotesStyle.arg(numVotesString.toHtmlEscaped()); 243 html << mChoiceVotesStyle.arg(numVotesString.toHtmlEscaped());
238 xlsx.write(row, 3, numVotesString, mChoiceVotesFmt); 244 xlsx.write(row, 3, numVotesString, mChoiceVotesFmt);
239 xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT); 245 xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT);
240 /* As long as we can match a choice which is either before the next question 246 /* As long as we can match a choice which is either before the next question
241 or before the end of the document */ 247 or before the end of the document */
242 choiceMatch = choiceEx.match(input, cursor); 248 choiceMatch = choiceEx.match(input, cursor);
243 row++; 249 row++;
244 lastChoiceRow++; 250 lastChoiceRow++;
251 }
252 choiceMatch = choiceAltEx.match(input, cursor);
253 bool additionalFound = false;
254 while (choiceMatch.hasMatch() && choiceMatch.capturedStart() <= cursor + 1) {
255 additionalFound = true;
256 const QString choice = choiceMatch.captured(1);
257 cursor = choiceMatch.capturedEnd();
258 /* Alternative answer that is just a list of strings */
259 qDebug() << choiceAltEx.captureCount();
260 qDebug() << choiceMatch.captured(2);
261 qDebug() << choiceMatch.capturedTexts();
262 qDebug() << "Caputured unfilled choice: " << choice;
263 html << mChoiceTextStyle.arg(choice.toHtmlEscaped());
264 makeBar(html, 0, doc);
265 xlsx.write(row, 2, 0);
266 const QString numVotesString = QStringLiteral("Keine eingegangenen Antworten");
267 html << mChoiceVotesStyle.arg(numVotesString.toHtmlEscaped());
268 xlsx.write(row, 3, numVotesString, mChoiceVotesFmt);
269 xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT);
270 row++;
271 lastChoiceRow++;
272 choiceMatch = choiceAltEx.match(input, cursor);
273 QRegularExpressionMatch realMatch = choiceEx.match(input, cursor);
274 if (choiceMatch.hasMatch() && choiceMatch.capturedStart() == realMatch.capturedStart()) {
275 /* We have a real match so back to the other pattern. */
276 break;
277 }
278 }
279 if (additionalFound) {
280 goto repeat;
245 } 281 }
246 bars.addRange(QString("B%1:B%2").arg(firstChoiceRow).arg(lastChoiceRow)); 282 bars.addRange(QString("B%1:B%2").arg(firstChoiceRow).arg(lastChoiceRow));
247 // xlsx.groupRows(firstChoiceRow - 2, lastChoiceRow - 1, false); 283 // xlsx.groupRows(firstChoiceRow - 2, lastChoiceRow - 1, false);
248 } else if (answerLine == QStringLiteral(TEXT_IDENTIFIER)) { 284 } else if (answerLine == QStringLiteral(TEXT_IDENTIFIER)) {
249 QRegularExpressionMatch textMatch = freetxtEx.match(input, cursor); 285 QRegularExpressionMatch textMatch = freetxtEx.match(input, cursor);
323 359
324 if (!foundSomething) { 360 if (!foundSomething) {
325 mErrors << tr("Failed to parse input document."); 361 mErrors << tr("Failed to parse input document.");
326 } 362 }
327 363
328 if (mFmt == Format_XLSX && !xlsx.saveAs(&output)) {
329 mErrors << tr("Saving the XLSX document failed.");
330 return;
331 }
332
333 html << "</table></body></html>"; 364 html << "</table></body></html>";
334 365 doc.setHtml(htmlString);
335 if (mFmt == Format_HTML) { 366
336 QTextStream outstream(&output); 367 /* Fixup images for html */
337 outstream << htmlString; 368 QRegularExpression htmlRe = QRegularExpression("<td style='vertical-align: middle'><img src=\"internal://bar(\\d+).png\"/></td>");
338 return; 369 htmlString.replace(htmlRe, QStringLiteral("<td style='background:linear-gradient(to right,"
339 } 370 BAR_COLOR ", " BAR_COLOR " \\1%, #ffffff \\1%)'></td>"));
340 371
341 if (mFmt == Format_PDF) { 372 foreach (QFile *output, outputs) {
342 output.close(); 373 const QString fName = output->fileName().toLower();
343 QPrinter printer(QPrinter::PrinterResolution); 374 if (fName.endsWith(".html")) {
344 printer.setOutputFormat(QPrinter::PdfFormat); 375 QTextStream outstream(output);
345 printer.setPaperSize(QPrinter::A4); 376 outstream << htmlString;
346 printer.setOutputFileName(output.fileName()); 377 output->close();
347 doc.setHtml(htmlString); 378 } else if (fName.endsWith(".pdf")) {
348 /* 379 output->close();
349 QPageLayout layout = printer.pageLayout(); 380 QPrinter printer(QPrinter::PrinterResolution);
350 layout.setUnits(QPageLayout::Millimeter); 381 printer.setOutputFormat(QPrinter::PdfFormat);
351 layout.setMargins(QMarginsF(20, 20, 20, 20)); 382 printer.setPaperSize(QPrinter::A4);
352 printer.setPageLayout(layout); 383 printer.setOutputFileName(output->fileName());
353 doc.setPageSize(printer.pageRect().size()); 384 /*
354 */ 385 QPageLayout layout = printer.pageLayout();
355 doc.print(&printer); 386 layout.setUnits(QPageLayout::Millimeter);
387 layout.setMargins(QMarginsF(20, 20, 20, 20));
388 printer.setPageLayout(layout);
389 doc.setPageSize(printer.pageRect().size());
390 */
391 doc.print(&printer);
392 } else {
393 if (!xlsx.saveAs(output)) {
394 mErrors << tr("Saving the XLSX document failed.");
395 }
396 output->close();
397 }
356 } 398 }
357 } 399 }
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)