Mercurial > clickerconvert
comparison src/converter.cpp @ 76:1e6e7699f0b8
Add replacements.ini to configure text replacements
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Wed, 05 Oct 2016 14:23:23 +0200 |
parents | 92139cc60121 |
children | f230ed9022e0 |
comparison
equal
deleted
inserted
replaced
75:92139cc60121 | 76:1e6e7699f0b8 |
---|---|
12 #include <QRegularExpressionMatch> | 12 #include <QRegularExpressionMatch> |
13 #include <QTextDocument> | 13 #include <QTextDocument> |
14 #include <QPrinter> | 14 #include <QPrinter> |
15 #include <QImage> | 15 #include <QImage> |
16 #include <QPainter> | 16 #include <QPainter> |
17 #include <QMessageBox> | |
18 #include <QDir> | |
19 #include <QCoreApplication> | |
20 #include <QSettings> | |
17 | 21 |
18 #include "xlsxdocument.h" | 22 #include "xlsxdocument.h" |
19 #include "xlsxconditionalformatting.h" | 23 #include "xlsxconditionalformatting.h" |
20 | 24 |
21 #include "constants.h" | 25 #include "constants.h" |
133 QVariant(image)); | 137 QVariant(image)); |
134 html << QStringLiteral("<td style='vertical-align: middle'><img src=\"internal://bar%1.png\"/></td>").arg((int)percent); | 138 html << QStringLiteral("<td style='vertical-align: middle'><img src=\"internal://bar%1.png\"/></td>").arg((int)percent); |
135 return; | 139 return; |
136 } | 140 } |
137 | 141 |
138 static void unescapeRegex(QString &str, const QRegularExpression &exp) | 142 static const QMap<QRegularExpression*, QString> loadExpressionsFromFile(const QString &path, QStringList &errors) |
139 { | 143 { |
140 QRegularExpressionMatch match = exp.match(str); | 144 QFile file(path); |
141 while (match.hasMatch()) { | 145 QMap<QRegularExpression*, QString> ret; |
142 str.replace(match.capturedStart(), match.capturedLength(), match.captured(1)); | 146 |
143 match = exp.match(str); | 147 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { |
144 } | 148 errors << QObject::tr("Failed to open replacement file:") + "\n\"" + path + "\""; |
145 } | 149 return ret; |
146 | 150 } |
147 static void unescapeString(QString &str) | 151 |
148 { | 152 while (!file.atEnd()) { |
149 static const QRegularExpression imgEx(IMAGE_PATTERN); | 153 const auto line = file.readLine(); |
150 static const QRegularExpression texEx(LATEX_PATTERN); | 154 auto strline = QString::fromUtf8(line).trimmed(); |
151 | 155 if (strline.startsWith(QStringLiteral(";")) || strline.isEmpty()) { |
152 unescapeRegex(str, imgEx); | 156 continue; |
153 unescapeRegex(str, texEx); | 157 } |
158 | |
159 auto split = strline.split("="); | |
160 if (split.count() != 2) { | |
161 errors << QObject::tr("Invalid replacement line:") + "\n\"" + strline + "\""; | |
162 continue; | |
163 } | |
164 | |
165 auto exp = new QRegularExpression (split[0], QRegularExpression::MultilineOption); | |
166 if (!exp->isValid()) { | |
167 errors << QObject::tr("Invalid regular expression:") + "\n\"" + split[0] + "\"" + | |
168 "\n" + QObject::tr("Error: ") + exp->errorString(); | |
169 continue; | |
170 } | |
171 const auto rep = split[1].replace("\"", ""); | |
172 ret.insert(exp, rep); | |
173 qDebug() << "Loaded replacement: " << *exp << " replacement:" << rep << ""; | |
174 } | |
175 /* Special one that does not fit into the ini format well. */ | |
176 ret.insert(new QRegularExpression("^="), " ="); | |
177 return ret; | |
178 } | |
179 | |
180 static const QMap<QRegularExpression*, QString> loadExpressions(QStringList &errors) | |
181 { | |
182 QMap<QRegularExpression*, QString> regexs; | |
183 /* Look for file next to our place */ | |
184 auto ourDir = QDir(QCoreApplication::applicationDirPath()); | |
185 const auto filename = QStringLiteral(CONFIG_FILE_NAME); | |
186 if (ourDir.exists(filename)) { | |
187 regexs = loadExpressionsFromFile(ourDir.filePath(filename), errors); | |
188 return regexs; | |
189 } | |
190 | |
191 /* Look in ../share/apps/PROJECT_NAME */ | |
192 ourDir.cd(QStringLiteral("../share/apps/" APPNAME).toLower()); | |
193 if (ourDir.exists(filename)) { | |
194 regexs = loadExpressionsFromFile(ourDir.filePath(filename), errors); | |
195 } else { | |
196 qDebug() << "Failed to find regular expressions."; | |
197 } | |
198 return regexs; | |
199 } | |
200 | |
201 static const QStringList sanitizeInput(QString &str) | |
202 { | |
203 QStringList errors; | |
204 str.replace("\r\n", "\n"); | |
205 str.replace("\n\r", "\n"); | |
206 const auto expressions = loadExpressions(errors); | |
207 for (const auto regex: expressions.keys()) { | |
208 str.replace(*regex, expressions.value(regex)); | |
209 delete regex; | |
210 } | |
211 return errors; | |
212 } | |
213 | |
214 static void xlsEscape(QString &str) | |
215 { | |
154 if (str.startsWith("=")) { | 216 if (str.startsWith("=")) { |
155 str = " " + str; | 217 str = " " + str; |
156 } | 218 } |
157 } | 219 } |
158 | 220 |
201 QRegularExpression choiceEx(CHOICE_PATTERN); | 263 QRegularExpression choiceEx(CHOICE_PATTERN); |
202 QRegularExpression choiceAltEx(CHOICE_UNFILLED_PATTERN); | 264 QRegularExpression choiceAltEx(CHOICE_UNFILLED_PATTERN); |
203 QRegularExpression freetxtEx(FREETXT_PATTERN); | 265 QRegularExpression freetxtEx(FREETXT_PATTERN); |
204 QRegularExpression firstQuestionEx(FIRST_QUESTION_PATTERN); | 266 QRegularExpression firstQuestionEx(FIRST_QUESTION_PATTERN); |
205 | 267 |
206 input.replace("\r\n", "\n"); | 268 mErrors += sanitizeInput(input); |
207 input.replace("\n\r", "\n"); | |
208 input.replace("#NAME?\n", ""); | |
209 | 269 |
210 QRegularExpressionMatch match = firstQuestionEx.match(input); | 270 QRegularExpressionMatch match = firstQuestionEx.match(input); |
211 bool foundSomething = false; | 271 bool foundSomething = false; |
212 int cursor = match.capturedEnd(); | 272 int cursor = match.capturedEnd(); |
213 while (match.hasMatch() && cursor != -1) { | 273 while (match.hasMatch() && cursor != -1) { |
227 unescapeString(question); | 287 unescapeString(question); |
228 qDebug() << "First question: " << question; | 288 qDebug() << "First question: " << question; |
229 const QString answerLine = match.captured(2).trimmed(); | 289 const QString answerLine = match.captured(2).trimmed(); |
230 xlsx.write(row, 2, QString(" "), mQuestionFmt); | 290 xlsx.write(row, 2, QString(" "), mQuestionFmt); |
231 xlsx.write(row, 3, QString(" "), mQuestionFmt); | 291 xlsx.write(row, 3, QString(" "), mQuestionFmt); |
292 xlsEscape(question); | |
232 xlsx.write(row++, 1, question, mQuestionFmt); | 293 xlsx.write(row++, 1, question, mQuestionFmt); |
233 html << mQuestionStyle.arg(question.toHtmlEscaped()); | 294 html << mQuestionStyle.arg(question.toHtmlEscaped()); |
234 | 295 |
235 if (answerLine == QStringLiteral(CHOICE_IDENTIFIER)) { | 296 if (answerLine == QStringLiteral(CHOICE_IDENTIFIER)) { |
236 xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT); | 297 xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT); |
246 otherwise we can't figure out when the next question begins. */ | 307 otherwise we can't figure out when the next question begins. */ |
247 cursor = choiceMatch.capturedEnd(); | 308 cursor = choiceMatch.capturedEnd(); |
248 | 309 |
249 /* Write the values */ | 310 /* Write the values */ |
250 QString choiceName = choiceMatch.captured(1).trimmed(); | 311 QString choiceName = choiceMatch.captured(1).trimmed(); |
251 if (choiceName.startsWith("=")) { | 312 xlsEscape(choiceName); |
252 choiceName = " " + choiceName; | |
253 } | |
254 unescapeString(choiceName); | |
255 xlsx.write(row, 1, choiceName, mChoiceTextFmt); | 313 xlsx.write(row, 1, choiceName, mChoiceTextFmt); |
256 html << mChoiceTextStyle.arg(choiceName.toHtmlEscaped()); | 314 html << mChoiceTextStyle.arg(choiceName.toHtmlEscaped()); |
257 qDebug() << "Captured for choice: " << choiceMatch.captured(0); | 315 qDebug() << "Captured for choice: " << choiceMatch.captured(0); |
258 bool ok; | 316 bool ok; |
259 QString percentStr = choiceMatch.captured("percent"); | 317 QString percentStr = choiceMatch.captured("percent"); |
290 while (choiceMatch.hasMatch() && choiceMatch.capturedStart() <= cursor + 1) { | 348 while (choiceMatch.hasMatch() && choiceMatch.capturedStart() <= cursor + 1) { |
291 additionalFound = true; | 349 additionalFound = true; |
292 QString choice = choiceMatch.captured(1); | 350 QString choice = choiceMatch.captured(1); |
293 cursor = choiceMatch.capturedEnd(); | 351 cursor = choiceMatch.capturedEnd(); |
294 /* Alternative answer that is just a list of strings */ | 352 /* Alternative answer that is just a list of strings */ |
295 unescapeString(choice); | |
296 qDebug() << "Captured unfilled choice: " << choice; | 353 qDebug() << "Captured unfilled choice: " << choice; |
297 html << mChoiceTextStyle.arg(choice.toHtmlEscaped()); | 354 html << mChoiceTextStyle.arg(choice.toHtmlEscaped()); |
298 makeBar(html, 0, doc); | 355 makeBar(html, 0, doc); |
356 xlsEscape(choice); | |
299 xlsx.write(row, 1, choice); | 357 xlsx.write(row, 1, choice); |
300 xlsx.write(row, 2, QVariant()); | 358 xlsx.write(row, 2, QVariant()); |
301 const QString numVotesString = QStringLiteral("Keine eingegangenen Antworten"); | 359 const QString numVotesString = QStringLiteral("Keine eingegangenen Antworten"); |
302 html << mChoiceVotesStyle.arg(numVotesString.toHtmlEscaped()); | 360 html << mChoiceVotesStyle.arg(numVotesString.toHtmlEscaped()); |
303 xlsx.write(row, 3, numVotesString, mChoiceVotesFmt); | 361 xlsx.write(row, 3, numVotesString, mChoiceVotesFmt); |
343 const QString lastRow = xlsx.read(row - 1, 26).toString(); | 401 const QString lastRow = xlsx.read(row - 1, 26).toString(); |
344 int unquotedLen = textMatch.capturedStart() - cursor; | 402 int unquotedLen = textMatch.capturedStart() - cursor; |
345 const QString unquoted = input.mid(cursor, unquotedLen); | 403 const QString unquoted = input.mid(cursor, unquotedLen); |
346 qDebug() << "Found inner quoted string: " << unquoted; | 404 qDebug() << "Found inner quoted string: " << unquoted; |
347 /* Now combine */ | 405 /* Now combine */ |
348 const QString combined = QString("%1\"%2\"%3").arg(lastRow). | 406 QString combined = QString("%1\"%2\"%3").arg(lastRow). |
349 arg(unquoted). | 407 arg(unquoted). |
350 arg(textMatch.captured(1).trimmed()); | 408 arg(textMatch.captured(1).trimmed()); |
351 qDebug() << "Last row: " << lastRow; | 409 qDebug() << "Last row: " << lastRow; |
352 qDebug() << "Next Question is at: " << nextQuestion.capturedStart(); | 410 qDebug() << "Next Question is at: " << nextQuestion.capturedStart(); |
353 qDebug() << "Text match is: " << textMatch.captured(1).trimmed(); | 411 qDebug() << "Text match is: " << textMatch.captured(1).trimmed(); |
354 qDebug() << "cursor is at: " << cursor; | 412 qDebug() << "cursor is at: " << cursor; |
355 qDebug() << "text match starts at: " << textMatch.capturedStart(); | 413 qDebug() << "text match starts at: " << textMatch.capturedStart(); |
414 xlsEscape(combined); | |
356 xlsx.write(row - 1, 26, combined, mFreeTextFmt); | 415 xlsx.write(row - 1, 26, combined, mFreeTextFmt); |
357 xlsx.write(row - 1, 1, combined, mFreeTextFmt); | 416 xlsx.write(row - 1, 1, combined, mFreeTextFmt); |
358 cursor = textMatch.capturedEnd(); | 417 cursor = textMatch.capturedEnd(); |
359 textMatch = freetxtEx.match(input, cursor); | 418 textMatch = freetxtEx.match(input, cursor); |
360 continue; | 419 continue; |
372 | 431 |
373 /* Merged cells ignore wordwrap the following trick is based on: | 432 /* Merged cells ignore wordwrap the following trick is based on: |
374 http://excel.tips.net/T003207_Automatic_Row_Height_For_Merged_Cells_with_Text_Wrap.html | 433 http://excel.tips.net/T003207_Automatic_Row_Height_For_Merged_Cells_with_Text_Wrap.html |
375 */ | 434 */ |
376 /* Write the values */ | 435 /* Write the values */ |
436 xlsEscape(text); | |
377 xlsx.write(QString("Z%1").arg(row), text, mFreeTextFmt); | 437 xlsx.write(QString("Z%1").arg(row), text, mFreeTextFmt); |
378 xlsx.write(row, 1, text, mFreeTextFmt); | 438 xlsx.write(row, 1, text, mFreeTextFmt); |
379 row++; | 439 row++; |
380 | 440 |
381 textMatch = freetxtEx.match(input, cursor); | 441 textMatch = freetxtEx.match(input, cursor); |