Line data Source code
1 : #include "convertertohtml.hpp"
2 :
3 : #include <QFile>
4 : #include <QTextStream>
5 : #include <cmath>
6 :
7 : #include "utils/replacer.hpp"
8 :
9 : namespace qtreports {
10 : namespace detail {
11 :
12 10 : ConverterToHTML::ConverterToHTML(const ReportPtr & report) :
13 10 : m_report(report) {}
14 :
15 10 : ConverterToHTML::~ConverterToHTML() {}
16 :
17 8 : bool ConverterToHTML::convert() {
18 8 : return createHTML();
19 : }
20 :
21 6 : bool ConverterToHTML::convert(const QString & path) {
22 6 : if(!convert()) {
23 3 : return false;
24 : }
25 :
26 6 : QFile file(path);
27 3 : file.open(
28 6 : QIODevice::OpenModeFlag::WriteOnly |
29 : QIODevice::OpenModeFlag::Text |
30 : QIODevice::OpenModeFlag::Truncate
31 3 : );
32 :
33 3 : if(!file.isOpen()) {
34 1 : m_lastError = "The file can not be opened";
35 1 : return false;
36 : }
37 :
38 4 : QTextStream stream(&file);
39 2 : stream.setCodec("UTF-8");
40 2 : stream << m_html;
41 :
42 2 : return true;
43 : }
44 :
45 8 : bool ConverterToHTML::createHTML() {
46 8 : if(m_report.isNull())
47 : {
48 1 : m_lastError = "Report is empty";
49 1 : return false;
50 : }
51 :
52 7 : int pageHeight = m_report->getHeight();
53 7 : int pageWidth = m_report->getWidth();
54 :
55 7 : auto& styles = m_report->getStyles();
56 :
57 7 : m_html += QString("<!DOCTYPE html>\n"
58 : "<html>\n"
59 : " <head>\n"
60 : " <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>\n"
61 : " <title>Report</title>\n"
62 : " <style>\n"
63 :
64 : " div .detail, .title, .band {\n"
65 : " position: relative;\n"
66 : " width: 100%;\n"
67 : " overflow: hidden;\n"
68 : " }\n"
69 :
70 : " body {\n"
71 : " overflow: scroll;\n"
72 : " }\n"
73 :
74 : " @media print {\n"
75 : " .page {page-break-after: always;}\n"
76 : " }\n"
77 :
78 : " .page {\n"
79 : " width: %1px;\n"
80 : " height: %2px;\n"
81 : " }\n"
82 :
83 : " .statictext, .textfield, .shape{\n"
84 : " position: absolute;\n"
85 : " background-color: transparent;\n"
86 : " }\n")
87 14 : .arg(pageWidth).arg(pageHeight);
88 :
89 7 : Style *mainStyle = nullptr;
90 13 : for (auto i = styles.begin(); i != styles.end(); ++i) {
91 6 : auto* style = (*i).data();
92 6 : mainStyle = style;
93 6 : m_html += QString(
94 : " .%1 {\n"
95 : " font-family: %2 !important;\n"
96 : " font-size: %3pt !important;\n"
97 : " color: %4 !important;\n"
98 : " font-style: %5 !important;\n"
99 : " font-weight: %6 !important;\n"
100 : " text-decoration: %7 %8 !important;\n"
101 : " }\n"
102 : )
103 12 : .arg("style-" + style->getName()) // 1 name
104 24 : .arg(style->getFontName()) // 2 font-family
105 24 : .arg(style->getFontSize()) // 3 font-size
106 24 : .arg(style->getFontColor().name()) // 4 color
107 24 : .arg(style->isItalic() ? "italic" : "") // 5 font-style
108 24 : .arg(style->isBold() ? "800" : "300") // 6 font-weight
109 24 : .arg(style->isUnderline() ? "underline" : "") // 7 text-docration
110 24 : .arg(style->isStrikeThrough() ? "line-through" : ""); // 8 text-decoration
111 : }
112 7 : m_defaultStyleName = m_report->getDefaultStyle().isNull() ? "" : "style-" + m_report->getDefaultStyle()->getName();
113 :
114 7 : if(mainStyle == nullptr) {
115 1 : m_html += QString(" .statictext {\n"
116 : " font-family: %3;\n"
117 : " font-size: %4pt;\n"
118 : " color: %5;\n"
119 : " font-style: %6;\n"
120 : " font-weight: %7;\n"
121 : " }\n"
122 :
123 : " .textfield {\n"
124 : " font-family: %8;\n"
125 : " font-size: %9pt;\n"
126 : " color: %10;\n"
127 : " font-style: %11;\n"
128 : " font-weight: %12;\n"
129 : " }\n"
130 :
131 : " </style>\n"
132 : " </head>\n"
133 : " <body>\n")
134 2 : .arg("Verdana").arg("12").arg("black").arg("normal").arg("normal")
135 4 : .arg("Verdana").arg("12").arg("black").arg("normal").arg("normal");
136 : }
137 : else {
138 6 : m_html += QString(" .statictext {\n"
139 : " font-family: %3;\n"
140 : " font-size: %4pt;\n"
141 : " color: %5;\n"
142 : " font-style: %6;\n"
143 : " font-weight: %7;\n"
144 : " }\n"
145 :
146 : " .textfield {\n"
147 : " font-family: %8;\n"
148 : " font-size: %9pt;\n"
149 : " color: %10;\n"
150 : " font-style: %11;\n"
151 : " font-weight: %12;\n"
152 : " }\n"
153 :
154 : " </style>\n"
155 : " </head>\n"
156 : " <body>\n")
157 12 : .arg("'" + mainStyle->getFontName() + "'").arg(mainStyle->getFontSize()).arg(mainStyle->getFontColor().name())
158 24 : .arg(mainStyle->isItalic() ? "italic" : "normal").arg(mainStyle->isBold() ? "800" : "300")
159 24 : .arg(mainStyle->getFontName()).arg(mainStyle->getFontSize()).arg(mainStyle->getFontColor().name())
160 24 : .arg(mainStyle->isItalic() ? "italic" : "normal").arg(mainStyle->isBold() ? "800" : "300");
161 : }
162 :
163 7 : m_html += " <div class='page'>\n"; // Start of <div class='page'>
164 :
165 14 : auto title = m_report->getTitle();
166 7 : if(!title.isNull())
167 : {
168 4 : m_html += " <div class='title'>\n"; // Start of <div class='title'>
169 4 : addBand(title, 0);
170 4 : m_html += " </div>\n"; // End of <div class = 'title'>
171 : }
172 :
173 14 : auto detail = m_report->getDetail();
174 7 : if(detail.isNull())
175 : {
176 2 : m_lastError = "Report->Detail is empty";
177 2 : return false;
178 : }
179 :
180 5 : m_html += QString(" <div class='detail'>\n"); // Start of <div class='detail'>
181 5 : if(!addGroupsIntoReport(detail))
182 : {
183 0 : return false;
184 : }
185 5 : m_html += QString(" </div>\n"); // End of <div class='detail'>
186 :
187 10 : auto summary = m_report->getSummary();
188 5 : if(!summary.isNull())
189 : {
190 4 : m_html += " <div class='summary'>\n"; // Start of <div class='summary'>
191 4 : int lastField = m_report->getFieldsDataCount() - 1;
192 4 : addBand(summary, lastField);
193 4 : m_html += " </div>\n"; // End of <div class = 'summary'>
194 : }
195 :
196 : //Колонтитул
197 5 : m_html += " </div>\n </body>\n</html>\n"; // End of <div class='page'>, <body>, <html>
198 5 : return true;
199 : }
200 :
201 5 : bool ConverterToHTML::addGroupsIntoReport(DetailPtr detail)
202 : {
203 10 : detail::Replacer replacer;
204 :
205 10 : QList<GroupPtr> groups = m_report->getGroups().values();
206 10 : QList<QString> groupNames;
207 : //Сюда помещаем конкретные имена по которым группируем
208 10 : QList<QString> particularNames;
209 8 : for (int i = 0; i<groups.length(); i++)
210 : {
211 6 : auto groupExpression = groups[i]->getExpression();
212 3 : if(groupExpression == "")
213 : {
214 0 : return false;
215 : }
216 3 : groupNames.append(groupExpression);
217 3 : particularNames.append(replacer.replaceField(groupNames[i], m_report, 0));
218 : }
219 : //Открываем хедеры групп
220 8 : for (int i = 0; i<groups.length(); i++)
221 : {
222 6 : auto header = groups[i]->getHeader();
223 3 : if (!header.isNull())
224 : {
225 3 : if(!addBand(header, 0))
226 : {
227 0 : return false;
228 : }
229 : }
230 : }
231 :
232 5 : int rowCount = m_report->getRowCount();
233 60 : for(int i = 0; i < rowCount; i++)
234 : {
235 : //QWidget * sectionWidget = isLayout() ? sectionWidget = addSectionLayout(layout, report->getMargins(), detail->getHeight()) : nullptr;
236 :
237 : //Закрываем футеры, если группа закончилась
238 88 : for(int j = groups.length() - 1; j >= 0; j--)
239 : {
240 : //сверяем поле в заголовке и текущее поле
241 33 : if(particularNames[j] != replacer.replaceField(groupNames[j], m_report, i))
242 : {
243 42 : auto footer = m_report->getGroupByIndex(j)->getFooter();
244 21 : if (!footer.isNull())
245 : {
246 21 : if(!addBand(footer, i - 1))
247 : {
248 0 : return false;
249 : }
250 : }
251 : }
252 : }
253 : //Аналогично открываем хедеры
254 88 : for(int j = 0; j < groupNames.length(); j++)
255 : {
256 : //сверяем поле в заголовке и текущее поле
257 33 : if(particularNames[j] != replacer.replaceField(groupNames[j], m_report, i))
258 : {
259 42 : auto header = m_report->getGroupByIndex(j)->getHeader();
260 21 : if (!header.isNull())
261 : {
262 21 : if(!addBand(header, i))
263 : {
264 0 : return false;
265 : }
266 : }
267 : }
268 : }
269 : //Переписываем имена для сравнения
270 88 : for(int j = 0; j < groupNames.length(); j++)
271 : {
272 33 : particularNames[j] = replacer.replaceField(groupNames[j], m_report, i);
273 : }
274 : //Выводим поле
275 55 : if(!addBand(detail, i))
276 : {
277 0 : return false;
278 : }
279 : }
280 : //Закрываем хедеры групп
281 8 : for(int i = groups.length() - 1; i >= 0; i--)
282 : {
283 6 : auto footer = groups[i]->getFooter();
284 3 : if (!footer.isNull())
285 : {
286 3 : if(!addBand(footer, (rowCount - 1)))
287 : {
288 0 : return false;
289 : }
290 : }
291 : }
292 5 : return true;
293 : }
294 :
295 111 : bool ConverterToHTML::addBand(SectionPtr section, int sectionIndex)
296 : {
297 222 : detail::Replacer replacer;
298 111 : if(!replacer.replace(section, m_report, sectionIndex))
299 : {
300 0 : m_lastError = "Error in replacing process: " + replacer.getLastError();
301 0 : return false;
302 : }
303 :
304 222 : QString bandStr = "";
305 :
306 286 : for(auto && band : section->getBands())
307 : {
308 350 : QString elementStr = "";
309 :
310 348 : for(auto && textField : band->getTextFields())
311 : {
312 173 : if (textField->getText() != "")
313 : {
314 346 : QString textAlignment = "left";
315 346 : QString verticalAlignment = "middle";
316 :
317 173 : if ((textField->getAlignment() & Qt::AlignLeft) == Qt::AlignLeft)
318 134 : textAlignment = "left";
319 173 : if ((textField->getAlignment() & Qt::AlignRight) == Qt::AlignRight)
320 33 : textAlignment = "right";
321 173 : if ((textField->getAlignment() & Qt::AlignHCenter) == Qt::AlignHCenter)
322 6 : textAlignment = "center";
323 173 : if ((textField->getAlignment() & Qt::AlignJustify) == Qt::AlignJustify)
324 0 : textAlignment = "justify";
325 :
326 173 : if ((textField->getAlignment() & Qt::AlignTop) == Qt::AlignTop)
327 33 : verticalAlignment = "top";
328 173 : if ((textField->getAlignment() & Qt::AlignBottom) == Qt::AlignBottom)
329 39 : verticalAlignment = "bottom";
330 173 : if ((textField->getAlignment() & Qt::AlignVCenter) == Qt::AlignVCenter)
331 107 : verticalAlignment = "middle";
332 : //if ((textField->getAlignment() & Qt::AlignBaseline) == Qt::AlignBaseline)
333 : // verticalAlignment = "baseline";
334 :
335 :
336 346 : elementStr += QString(" <div class='textfield " + m_defaultStyleName + "' "
337 : "style='left: %1px; top: %2px; "
338 : "width: %3px; height: %4px; "
339 : "text-align: %5; vertical-align: %6'>%7</div>\n")
340 692 : .arg(textField->getX())
341 692 : .arg(textField->getY())
342 692 : .arg(textField->getWidth())
343 692 : .arg(textField->getHeight())
344 692 : .arg(textAlignment)
345 692 : .arg(verticalAlignment)
346 519 : .arg(textField->getText());
347 : }
348 : }
349 :
350 221 : for(auto && staticText : band->getStaticTexts())
351 : {
352 46 : if (staticText->getText() != "")
353 : {
354 92 : QString textAlignment = "left";
355 92 : QString verticalAlignment = "middle";
356 :
357 46 : if ((staticText->getAlignment() & Qt::AlignLeft) == Qt::AlignLeft)
358 7 : textAlignment = "left";
359 46 : if ((staticText->getAlignment() & Qt::AlignRight) == Qt::AlignRight)
360 36 : textAlignment = "right";
361 46 : if ((staticText->getAlignment() & Qt::AlignHCenter) == Qt::AlignHCenter)
362 3 : textAlignment = "center";
363 46 : if ((staticText->getAlignment() & Qt::AlignJustify) == Qt::AlignJustify)
364 0 : textAlignment = "justify";
365 :
366 46 : if ((staticText->getAlignment() & Qt::AlignTop) == Qt::AlignTop)
367 36 : verticalAlignment = "top";
368 46 : if ((staticText->getAlignment() & Qt::AlignBottom) == Qt::AlignBottom)
369 3 : verticalAlignment = "bottom";
370 46 : if ((staticText->getAlignment() & Qt::AlignVCenter) == Qt::AlignVCenter)
371 7 : verticalAlignment = "middle";
372 : //if ((staticText->getAlignment() & Qt::AlignBaseline) == Qt::AlignBaseline)
373 : // verticalAlignment = "baseline";
374 :
375 92 : elementStr += QString(" <div class='statictext " + m_defaultStyleName + "' "
376 : "style='left: %1px; top: %2px; "
377 : "width: %3px; height: %4px; "
378 : "text-align: %5; vertical-align: %6'>%7</div>\n")
379 184 : .arg(staticText->getX())
380 184 : .arg(staticText->getY())
381 184 : .arg(staticText->getWidth())
382 184 : .arg(staticText->getHeight())
383 184 : .arg(textAlignment)
384 184 : .arg(verticalAlignment)
385 138 : .arg(staticText->getText());
386 : }
387 : }
388 :
389 175 : addShapes(band, elementStr);
390 175 : addCrosstabsIntoReport(band, elementStr);
391 :
392 : bandStr += QString(" <div class='band' "
393 : "style='height: %1px'>\n%2 </div>\n")
394 350 : .arg(band->getSize().height())
395 525 : .arg(elementStr);
396 :
397 : }
398 :
399 111 : m_html += QString("%1").arg(bandStr);
400 111 : return true;
401 : }
402 :
403 175 : void ConverterToHTML::addShapes(BandPtr band, QString &elementStr)
404 : {
405 211 : for (auto && image : band->getImages())
406 : {
407 72 : QImage img = image->getImage();
408 72 : QBuffer imageBuffer; //Создаём буффер для перевода картинки в QByteArray
409 36 : img.save(&imageBuffer, "png");
410 72 : QString base64Img = "data:image/png;base64," + imageBuffer.data().toBase64(); //Преобразовываем картинку в текст
411 :
412 : elementStr += QString(" <div class='shape' "
413 : "style='left: %1px; top: %2px; "
414 : "width: %3px; height: %4px'>\n"
415 72 : " <img src='" + base64Img + "' alt='image_not_found'>\n </div>\n")
416 144 : .arg(image->getX())
417 144 : .arg(image->getY())
418 144 : .arg(image->getWidth())
419 108 : .arg(image->getHeight());
420 : }
421 :
422 : // Получаем количество линий
423 175 : int amountLines = band->getLines().size();
424 175 : int counterLines = 0;
425 : // Получаем количество не граничных линий
426 175 : int amountNotBorderLines = amountLines - 3;
427 :
428 232 : for(auto && line : band->getLines())
429 : {
430 57 : bool isBorderLeft = false;
431 57 : bool isLineRotate = false;
432 57 : float angleRad = atan2(line->getHeight(), line->getWidth());
433 57 : if(line->getWidth() != 1 && line->getHeight() != 1)
434 12 : isLineRotate = true;
435 57 : if(angleRad >= 1.52 && angleRad <= 1.6)
436 39 : isBorderLeft = true;
437 57 : float lineHeight = sqrt(pow(line->getWidth(), 2) + pow(line->getHeight(), 2));
438 :
439 57 : if(isLineRotate) { // Если линия повернута
440 : elementStr += QString(" <div class='shape' "
441 : "style='left: %1px; top: %2px; "
442 : "width: %3px; height: %4px; "
443 : "overflow: visible'>\n"
444 : " <div style='border-top: 1px solid black; height: 1px; width: %5px; "
445 : "transform: rotate(%6rad); transform-origin: 0\% 0\%;"
446 : "'></div>\n </div>\n")
447 24 : .arg(line->getX())
448 48 : .arg(line->getY())
449 48 : .arg((uint)line->getWidth())
450 48 : .arg((uint)line->getHeight())
451 48 : .arg(lineHeight)
452 36 : .arg(angleRad);
453 12 : continue;
454 : }
455 :
456 45 : if(isBorderLeft) { // Если линия вертикальная
457 39 : if(counterLines != amountLines - 2) { // Если линия не последняя
458 : elementStr += QString(" <div class='shape' "
459 : "style='left: %1px; top: %2px; "
460 : "width: %3px; height: %4px; "
461 : "overflow: visible'>\n"
462 : " <div style='border-left: 1px solid black; height: %5px; width: 1px; "
463 : "'></div>\n </div>\n")
464 78 : .arg(line->getX()) // устанавливаем координату X без изменений
465 156 : .arg(line->getY())
466 156 : .arg(line->getWidth())
467 156 : .arg(line->getHeight())
468 117 : .arg(lineHeight);
469 39 : counterLines++;
470 : }
471 : else { // Если линия последняя
472 : elementStr += QString(" <div class='shape' "
473 : "style='left: %1px; top: %2px; "
474 : "width: %3px; height: %4px; "
475 : "overflow: visible'>\n"
476 : " <div style='border-left: 1px solid black; height: %5px; width: 1px; "
477 : "'></div>\n </div>\n")
478 : // Смещаем координату X вправо на положение, равное amountNotBorderLines,
479 : // умноженное на ширину линии. (width: 1px)
480 0 : .arg(line->getX() + amountNotBorderLines - 1) // с учетом ширины последней вертикальной линии (1px)
481 0 : .arg(line->getY())
482 0 : .arg(line->getWidth())
483 0 : .arg(line->getHeight())
484 0 : .arg(lineHeight)
485 0 : ;
486 0 : counterLines = 0;
487 : }
488 : } else { // Если линия не вертикальная
489 : elementStr += QString(" <div class='shape' "
490 : "style='left: %1px; top: %2px; "
491 : "width: %3px; height: %4px; "
492 : "overflow: visible'>\n"
493 : " <div style='border-top: 1px solid black; height: 1px; width: %5px; "
494 : "'></div>\n </div>\n")
495 12 : .arg(line->getX() + 1) // с учетом ширины первой вертикальной линии (1px)
496 24 : .arg(line->getY())
497 24 : .arg(line->getWidth())
498 24 : .arg(line->getHeight())
499 18 : .arg(lineHeight);
500 : }
501 : }
502 :
503 215 : for(auto && rect : band->getRects())
504 : {
505 : elementStr += QString(" <div class='shape' "
506 : "style='border: 1px solid black; left: %1px; top: %2px; "
507 : "width: %3px; height: %4px;'></div>\n")
508 80 : .arg(rect->getX())
509 160 : .arg(rect->getY())
510 160 : .arg(rect->getWidth())
511 120 : .arg(rect->getHeight());
512 : }
513 :
514 211 : for(auto && ellipse : band->getEllipses())
515 : {
516 : elementStr += QString(" <div class='shape' "
517 : "style='border: 1px solid black; "
518 : "left: %1px; top: %2px; "
519 : "width: %3px; height: %4px; "
520 : "-moz-border-radius: %5px / %6px; "
521 : "-webkit-border-radius: %5px / %6px; "
522 : "border-radius: %5px / %6px;'></div>\n")
523 72 : .arg(ellipse->getX())
524 144 : .arg(ellipse->getY())
525 144 : .arg(ellipse->getWidth())
526 144 : .arg(ellipse->getHeight())
527 144 : .arg(ellipse->getWidth() / 2)
528 108 : .arg(ellipse->getHeight() / 2);
529 : }
530 175 : }
531 :
532 175 : bool ConverterToHTML::addCrosstabsIntoReport(BandPtr band, QString &elementStr)
533 : {
534 175 : if(m_report->getFields().size() < 3)
535 : {
536 0 : return false;
537 : }
538 :
539 176 : for(auto && crosstab : band->getCrosstabs())
540 : {
541 2 : detail::Replacer replacer;
542 :
543 2 : QList<QString> colGroups;
544 1 : replacer.replaceColumnGroupsInCrosstab(crosstab, m_report, colGroups);
545 :
546 2 : QList<QString> rowGroups;
547 1 : replacer.replaceRowGroupsInCrosstab(crosstab, m_report, rowGroups);
548 :
549 2 : QList<QString> cells;
550 1 : replacer.replaceCellsInCrosstab(crosstab, m_report, cells);
551 :
552 2 : auto rowGroup = crosstab->getRowGroup();
553 2 : auto colGroup = crosstab->getColumnGroup();
554 2 : auto cell = crosstab->getCrosstabCell();
555 :
556 2 : QString tableBody = "";
557 : QString tableRow = QString(" <th style='border: 0.5px solid black; width: %1px; height: %2px;'></th>\n")
558 2 : .arg(rowGroup->getWidth())
559 4 : .arg(colGroup->getHeight());
560 5 : for (int i = 0; i < colGroups.size(); i++)
561 : {
562 : tableRow += QString(" <th style='border: 0.5px solid black;"
563 8 : " width: %1px'>" + colGroups[i] + "</th>\n")
564 12 : .arg(cell->getWidth());
565 : }
566 1 : tableBody += QString(" <tr>\n%1 </tr>\n").arg(tableRow);
567 :
568 1 : tableRow = "";
569 1 : int cellCount = 0;
570 5 : for (int i = 0; i < rowGroups.size(); i++)
571 : {
572 : tableRow += QString(" <th style='border: 0.5px solid black;"
573 8 : " height: %1px'>" + rowGroups[i] + "</th>\n")
574 12 : .arg(cell->getHeight());
575 20 : for(int i = 0; i < colGroups.size(); i++)
576 : {
577 32 : auto text = cells[cellCount];
578 : tableRow += " <td style='border: 0.5px solid black;'>"
579 16 : + text + "</th>\n";
580 16 : cellCount++;
581 : }
582 4 : tableBody += QString(" <tr>\n%1 </tr>\n").arg(tableRow);
583 4 : tableRow = "";
584 : }
585 :
586 :
587 2 : elementStr += QString(" <table class ='crosstab " + m_defaultStyleName + "'"
588 : " cellspacing=0 "
589 : "style='left: %1px; top: %2px; "
590 : "width: %3px; height: %4px; "
591 : "border: 0.5px solid black;'>"
592 : "\n%5 </table>\n")
593 4 : .arg(crosstab->getRect().x()) // x
594 4 : .arg(crosstab->getRect().y()) // y
595 4 : .arg(crosstab->getRect().width()) // width
596 4 : .arg(crosstab->getRect().height()) // height
597 3 : .arg(tableBody); // table body
598 :
599 : }
600 175 : return true;
601 : }
602 :
603 8 : const QString ConverterToHTML::getLastError() const {
604 8 : return m_lastError;
605 : }
606 :
607 3 : const QString ConverterToHTML::getHTML() const {
608 3 : return m_html;
609 : }
610 :
611 : }
612 : }
|