Mercurial > clickerconvert
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 } |