KCalUtils

htmlexport.cpp
1 /*
2  This file is part of the kcalutils library.
3 
4  SPDX-FileCopyrightText: 2000, 2001 Cornelius Schumacher <[email protected]>
5  SPDX-FileCopyrightText: 2004 Reinhold Kainhofer <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 #include "htmlexport.h"
10 #include "htmlexportsettings.h"
11 #include "incidenceformatter.h"
12 
13 #include <KCalendarCore/Calendar>
14 using namespace KCalendarCore;
15 
16 #include "kcalutils_debug.h"
17 
18 #include <KLocalizedString>
19 
20 #include <QApplication>
21 #include <QFile>
22 #include <QLocale>
23 #include <QMap>
24 #include <QTextStream>
25 
26 using namespace KCalUtils;
27 
28 static QString cleanChars(const QString &txt);
29 namespace
30 {
31 auto returnEndLine()
32 {
33  return Qt::endl;
34 }
35 }
36 //@cond PRIVATE
37 class KCalUtils::HtmlExportPrivate
38 {
39 public:
40  HtmlExportPrivate(Calendar *calendar, HTMLExportSettings *settings)
41  : mCalendar(calendar)
42  , mSettings(settings)
43  {
44  }
45 
46  Calendar *const mCalendar;
47  HTMLExportSettings *const mSettings;
48  QMap<QDate, QString> mHolidayMap;
49 };
50 //@endcond
51 
52 HtmlExport::HtmlExport(Calendar *calendar, HTMLExportSettings *settings)
53  : d(new HtmlExportPrivate(calendar, settings))
54 {
55 }
56 
57 HtmlExport::~HtmlExport() = default;
58 
59 bool HtmlExport::save(const QString &fileName)
60 {
61  QString fn(fileName);
62  if (fn.isEmpty() && d->mSettings) {
63  fn = d->mSettings->outputFile();
64  }
65  if (!d->mSettings || fn.isEmpty()) {
66  return false;
67  }
68  QFile f(fileName);
69  if (!f.open(QIODevice::WriteOnly)) {
70  return false;
71  }
72  QTextStream ts(&f);
73  bool success = save(&ts);
74  f.close();
75  return success;
76 }
77 
79 {
80  if (!d->mSettings) {
81  return false;
82  }
83 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
84  ts->setCodec("UTF-8");
85 #endif
86  // Write HTML header
87  *ts << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ";
88  *ts << "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" << returnEndLine();
89 
90  *ts << "<html><head>" << returnEndLine();
91  *ts << R"( <meta http-equiv="Content-Type" content="text/html; charset=)";
92  *ts << "UTF-8\" />" << returnEndLine();
93  if (!d->mSettings->pageTitle().isEmpty()) {
94  *ts << " <title>" << d->mSettings->pageTitle() << "</title>" << returnEndLine();
95  }
96  *ts << " <style type=\"text/css\">" << returnEndLine();
97  *ts << styleSheet();
98  *ts << " </style>" << returnEndLine();
99  *ts << "</head><body>" << returnEndLine();
100 
101  // FIXME: Write header
102  // (Heading, Calendar-Owner, Calendar-Date, ...)
103 
104  if (d->mSettings->eventView() || d->mSettings->monthView() || d->mSettings->weekView()) {
105  if (!d->mSettings->eventTitle().isEmpty()) {
106  *ts << "<h1>" << d->mSettings->eventTitle() << "</h1>" << returnEndLine();
107  }
108 
109  // Write Week View
110  if (d->mSettings->weekView()) {
111  createWeekView(ts);
112  }
113  // Write Month View
114  if (d->mSettings->monthView()) {
115  createMonthView(ts);
116  }
117  // Write Event List
118  if (d->mSettings->eventView()) {
119  createEventList(ts);
120  }
121  }
122 
123  // Write Todo List
124  if (d->mSettings->todoView()) {
125  if (!d->mSettings->todoListTitle().isEmpty()) {
126  *ts << "<h1>" << d->mSettings->todoListTitle() << "</h1>" << returnEndLine();
127  }
128  createTodoList(ts);
129  }
130 
131  // Write Journals
132  if (d->mSettings->journalView()) {
133  if (!d->mSettings->journalTitle().isEmpty()) {
134  *ts << "<h1>" << d->mSettings->journalTitle() << "</h1>" << returnEndLine();
135  }
136  createJournalView(ts);
137  }
138 
139  // Write Free/Busy
140  if (d->mSettings->freeBusyView()) {
141  if (!d->mSettings->freeBusyTitle().isEmpty()) {
142  *ts << "<h1>" << d->mSettings->freeBusyTitle() << "</h1>" << returnEndLine();
143  }
144  createFreeBusyView(ts);
145  }
146 
147  createFooter(ts);
148 
149  // Write HTML trailer
150  *ts << "</body></html>" << returnEndLine();
151 
152  return true;
153 }
154 
155 void HtmlExport::createMonthView(QTextStream *ts)
156 {
157  QDate start = fromDate();
158  start.setDate(start.year(), start.month(), 1); // go back to first day in month
159 
160  QDate end(start.year(), start.month(), start.daysInMonth());
161 
162  int startmonth = start.month();
163  int startyear = start.year();
164 
165  while (start < toDate()) {
166  // Write header
167  QDate hDate(start.year(), start.month(), 1);
168  QString hMon = hDate.toString(QStringLiteral("MMMM"));
169  QString hYear = hDate.toString(QStringLiteral("yyyy"));
170  *ts << "<h2>" << i18nc("@title month and year", "%1 %2", hMon, hYear) << "</h2>" << returnEndLine();
171  if (QLocale().firstDayOfWeek() == 1) {
172  start = start.addDays(1 - start.dayOfWeek());
173  } else {
174  if (start.dayOfWeek() != 7) {
175  start = start.addDays(-start.dayOfWeek());
176  }
177  }
178  *ts << "<table border=\"1\">" << returnEndLine();
179 
180  // Write table header
181  *ts << " <tr>";
182  for (int i = 0; i < 7; ++i) {
183  *ts << "<th>" << QLocale().dayName(start.addDays(i).dayOfWeek()) << "</th>";
184  }
185  *ts << "</tr>" << returnEndLine();
186 
187  // Write days
188  while (start <= end) {
189  *ts << " <tr>" << returnEndLine();
190  for (int i = 0; i < 7; ++i) {
191  *ts << R"( <td valign="top"><table border="0">)";
192 
193  *ts << "<tr><td ";
194  if (d->mHolidayMap.contains(start) || start.dayOfWeek() == 7) {
195  *ts << "class=\"dateholiday\"";
196  } else {
197  *ts << "class=\"date\"";
198  }
199  *ts << ">" << QString::number(start.day());
200 
201  if (d->mHolidayMap.contains(start)) {
202  *ts << " <em>" << d->mHolidayMap[start] << "</em>";
203  }
204 
205  *ts << "</td></tr><tr><td valign=\"top\">";
206 
207  // Only print events within the from-to range
208  if (start >= fromDate() && start <= toDate()) {
209  Event::List events = d->mCalendar->events(start, d->mCalendar->timeZone(), EventSortStartDate, SortDirectionAscending);
210  if (!events.isEmpty()) {
211  *ts << "<table>";
212  Event::List::ConstIterator it;
213  Event::List::ConstIterator endEvents(events.constEnd());
214  for (it = events.constBegin(); it != endEvents; ++it) {
215  if (checkSecrecy(*it)) {
216  createEvent(ts, *it, start, false);
217  }
218  }
219  *ts << "</table>";
220  } else {
221  *ts << "&nbsp;";
222  }
223  }
224 
225  *ts << "</td></tr></table></td>" << returnEndLine();
226  start = start.addDays(1);
227  }
228  *ts << " </tr>" << returnEndLine();
229  }
230  *ts << "</table>" << returnEndLine();
231  startmonth += 1;
232  if (startmonth > 12) {
233  startyear += 1;
234  startmonth = 1;
235  }
236  start.setDate(startyear, startmonth, 1);
237  end.setDate(start.year(), start.month(), start.daysInMonth());
238  }
239 }
240 
241 void HtmlExport::createEventList(QTextStream *ts)
242 {
243  int columns = 3;
244  *ts << R"(<table border="0" cellpadding="3" cellspacing="3">)" << returnEndLine();
245  *ts << " <tr>" << returnEndLine();
246  *ts << " <th class=\"sum\">" << i18nc("@title:column event start time", "Start Time") << "</th>" << returnEndLine();
247  *ts << " <th>" << i18nc("@title:column event end time", "End Time") << "</th>" << returnEndLine();
248  *ts << " <th>" << i18nc("@title:column event description", "Event") << "</th>" << returnEndLine();
249  if (d->mSettings->eventLocation()) {
250  *ts << " <th>" << i18nc("@title:column event location", "Location") << "</th>" << returnEndLine();
251  ++columns;
252  }
253  if (d->mSettings->eventCategories()) {
254  *ts << " <th>" << i18nc("@title:column event categories", "Categories") << "</th>" << returnEndLine();
255  ++columns;
256  }
257  if (d->mSettings->eventAttendees()) {
258  *ts << " <th>" << i18nc("@title:column event attendees", "Attendees") << "</th>" << returnEndLine();
259  ++columns;
260  }
261 
262  *ts << " </tr>" << returnEndLine();
263 
264  for (QDate dt = fromDate(); dt <= toDate(); dt = dt.addDays(1)) {
265  qCDebug(KCALUTILS_LOG) << "Getting events for" << dt.toString();
266  Event::List events = d->mCalendar->events(dt, d->mCalendar->timeZone(), EventSortStartDate, SortDirectionAscending);
267  if (!events.isEmpty()) {
268  *ts << " <tr><td colspan=\"" << QString::number(columns) << R"(" class="datehead"><i>)" << QLocale().toString(dt) << "</i></td></tr>"
269  << returnEndLine();
270 
271  Event::List::ConstIterator it;
272  const Event::List::ConstIterator end(events.constEnd());
273  for (it = events.constBegin(); it != end; ++it) {
274  if (checkSecrecy(*it)) {
275  createEvent(ts, *it, dt);
276  }
277  }
278  }
279  }
280 
281  *ts << "</table>" << returnEndLine();
282 }
283 
284 void HtmlExport::createEvent(QTextStream *ts, const Event::Ptr &event, QDate date, bool withDescription)
285 {
286  qCDebug(KCALUTILS_LOG) << event->summary();
287  *ts << " <tr>" << returnEndLine();
288 
289  if (!event->allDay()) {
290  if (event->isMultiDay(d->mCalendar->timeZone()) && (event->dtStart().date() != date)) {
291  *ts << " <td>&nbsp;</td>" << returnEndLine();
292  } else {
293  *ts << " <td valign=\"top\">" << IncidenceFormatter::timeToString(event->dtStart().toLocalTime().time(), true) << "</td>" << returnEndLine();
294  }
295  if (event->isMultiDay(d->mCalendar->timeZone()) && (event->dtEnd().date() != date)) {
296  *ts << " <td>&nbsp;</td>" << returnEndLine();
297  } else {
298  *ts << " <td valign=\"top\">" << IncidenceFormatter::timeToString(event->dtEnd().toLocalTime().time(), true) << "</td>" << returnEndLine();
299  }
300  } else {
301  *ts << " <td>&nbsp;</td><td>&nbsp;</td>" << returnEndLine();
302  }
303 
304  *ts << " <td class=\"sum\">" << returnEndLine();
305  *ts << " <b>" << cleanChars(event->summary()) << "</b>" << returnEndLine();
306  if (withDescription && !event->description().isEmpty()) {
307  *ts << " <p>" << breakString(cleanChars(event->description())) << "</p>" << returnEndLine();
308  }
309  *ts << " </td>" << returnEndLine();
310 
311  if (d->mSettings->eventLocation()) {
312  *ts << " <td>" << returnEndLine();
313  formatLocation(ts, event);
314  *ts << " </td>" << returnEndLine();
315  }
316 
317  if (d->mSettings->eventCategories()) {
318  *ts << " <td>" << returnEndLine();
319  formatCategories(ts, event);
320  *ts << " </td>" << returnEndLine();
321  }
322 
323  if (d->mSettings->eventAttendees()) {
324  *ts << " <td>" << returnEndLine();
325  formatAttendees(ts, event);
326  *ts << " </td>" << returnEndLine();
327  }
328 
329  *ts << " </tr>" << returnEndLine();
330 }
331 
332 void HtmlExport::createTodoList(QTextStream *ts)
333 {
334  Todo::List rawTodoList = d->mCalendar->todos();
335 
336  int index = 0;
337  while (index < rawTodoList.count()) {
338  Todo::Ptr ev = rawTodoList[index];
339  Todo::Ptr subev = ev;
340  const QString uid = ev->relatedTo();
341  if (!uid.isEmpty()) {
342  Incidence::Ptr inc = d->mCalendar->incidence(uid);
343  if (inc && inc->type() == Incidence::TypeTodo) {
344  Todo::Ptr todo = inc.staticCast<Todo>();
345  if (!rawTodoList.contains(todo)) {
346  rawTodoList.append(todo);
347  }
348  }
349  }
350  index = rawTodoList.indexOf(subev);
351  ++index;
352  }
353 
354  // FIXME: Sort list by priorities. This is brute force and should be
355  // replaced by a real sorting algorithm.
356  Todo::List todoList;
357  Todo::List::ConstIterator it;
358  const Todo::List::ConstIterator end(rawTodoList.constEnd());
359  for (int i = 1; i <= 9; ++i) {
360  for (it = rawTodoList.constBegin(); it != end; ++it) {
361  if ((*it)->priority() == i && checkSecrecy(*it)) {
362  todoList.append(*it);
363  }
364  }
365  }
366  for (it = rawTodoList.constBegin(); it != end; ++it) {
367  if ((*it)->priority() == 0 && checkSecrecy(*it)) {
368  todoList.append(*it);
369  }
370  }
371 
372  int columns = 3;
373  *ts << R"(<table border="0" cellpadding="3" cellspacing="3">)" << returnEndLine();
374  *ts << " <tr>" << returnEndLine();
375  *ts << " <th class=\"sum\">" << i18nc("@title:column", "To-do") << "</th>" << returnEndLine();
376  *ts << " <th>" << i18nc("@title:column to-do priority", "Priority") << "</th>" << returnEndLine();
377  *ts << " <th>" << i18nc("@title:column to-do percent completed", "Completed") << "</th>" << returnEndLine();
378  if (d->mSettings->taskDueDate()) {
379  *ts << " <th>" << i18nc("@title:column to-do due date", "Due Date") << "</th>" << returnEndLine();
380  ++columns;
381  }
382  if (d->mSettings->taskLocation()) {
383  *ts << " <th>" << i18nc("@title:column to-do location", "Location") << "</th>" << returnEndLine();
384  ++columns;
385  }
386  if (d->mSettings->taskCategories()) {
387  *ts << " <th>" << i18nc("@title:column to-do categories", "Categories") << "</th>" << returnEndLine();
388  ++columns;
389  }
390  if (d->mSettings->taskAttendees()) {
391  *ts << " <th>" << i18nc("@title:column to-do attendees", "Attendees") << "</th>" << returnEndLine();
392  ++columns;
393  }
394  *ts << " </tr>" << returnEndLine();
395 
396  // Create top-level list.
397  for (it = todoList.constBegin(); it != todoList.constEnd(); ++it) {
398  if ((*it)->relatedTo().isEmpty()) {
399  createTodo(ts, *it);
400  }
401  }
402 
403  // Create sub-level lists
404  for (it = todoList.constBegin(); it != todoList.constEnd(); ++it) {
405  Incidence::List relations = d->mCalendar->relations((*it)->uid());
406 
407  if (!relations.isEmpty()) {
408  // Generate sub-to-do list
409  *ts << " <tr>" << returnEndLine();
410  *ts << " <td class=\"subhead\" colspan=";
411  *ts << "\"" << QString::number(columns) << "\"";
412  *ts << "><a name=\"sub" << (*it)->uid() << "\"></a>" << i18nc("@title:column sub-to-dos of the parent to-do", "Sub-To-dos of: ") << "<a href=\"#"
413  << (*it)->uid() << "\"><b>" << cleanChars((*it)->summary()) << "</b></a></td>" << returnEndLine();
414  *ts << " </tr>" << returnEndLine();
415 
416  Todo::List sortedList;
417  // FIXME: Sort list by priorities. This is brute force and should be
418  // replaced by a real sorting algorithm.
419  for (int i = 1; i <= 9; ++i) {
420  Incidence::List::ConstIterator it2;
421  for (it2 = relations.constBegin(); it2 != relations.constEnd(); ++it2) {
422  Todo::Ptr ev3 = (*it2).staticCast<Todo>();
423  if (ev3 && ev3->priority() == i) {
424  sortedList.append(ev3);
425  }
426  }
427  }
428  Incidence::List::ConstIterator it2;
429  for (it2 = relations.constBegin(); it2 != relations.constEnd(); ++it2) {
430  Todo::Ptr ev3 = (*it2).staticCast<Todo>();
431  if (ev3 && ev3->priority() == 0) {
432  sortedList.append(ev3);
433  }
434  }
435 
436  Todo::List::ConstIterator it3;
437  for (it3 = sortedList.constBegin(); it3 != sortedList.constEnd(); ++it3) {
438  createTodo(ts, *it3);
439  }
440  }
441  }
442 
443  *ts << "</table>" << returnEndLine();
444 }
445 
446 void HtmlExport::createTodo(QTextStream *ts, const Todo::Ptr &todo)
447 {
448  qCDebug(KCALUTILS_LOG);
449 
450  const bool completed = todo->isCompleted();
451 
452  Incidence::List relations = d->mCalendar->relations(todo->uid());
453 
454  *ts << "<tr>" << returnEndLine();
455 
456  *ts << " <td class=\"sum";
457  if (completed) {
458  *ts << "done";
459  }
460  *ts << "\">" << returnEndLine();
461  *ts << " <a name=\"" << todo->uid() << "\"></a>" << returnEndLine();
462  *ts << " <b>" << cleanChars(todo->summary()) << "</b>" << returnEndLine();
463  if (!todo->description().isEmpty()) {
464  *ts << " <p>" << breakString(cleanChars(todo->description())) << "</p>" << returnEndLine();
465  }
466  if (!relations.isEmpty()) {
467  *ts << R"( <div align="right"><a href="#sub)" << todo->uid() << "\">" << i18nc("@title:column sub-to-dos of the parent to-do", "Sub-To-dos")
468  << "</a></div>" << returnEndLine();
469  }
470  *ts << " </td>" << returnEndLine();
471 
472  *ts << " <td";
473  if (completed) {
474  *ts << " class=\"done\"";
475  }
476  *ts << ">" << returnEndLine();
477  *ts << " " << todo->priority() << returnEndLine();
478  *ts << " </td>" << returnEndLine();
479 
480  *ts << " <td";
481  if (completed) {
482  *ts << " class=\"done\"";
483  }
484  *ts << ">" << returnEndLine();
485  *ts << " " << i18nc("@info to-do percent complete", "%1 %", todo->percentComplete()) << returnEndLine();
486  *ts << " </td>" << returnEndLine();
487 
488  if (d->mSettings->taskDueDate()) {
489  *ts << " <td";
490  if (completed) {
491  *ts << " class=\"done\"";
492  }
493  *ts << ">" << returnEndLine();
494  if (todo->hasDueDate()) {
495  *ts << " " << IncidenceFormatter::dateToString(todo->dtDue(true).toLocalTime().date()) << returnEndLine();
496  } else {
497  *ts << " &nbsp;" << returnEndLine();
498  }
499  *ts << " </td>" << returnEndLine();
500  }
501 
502  if (d->mSettings->taskLocation()) {
503  *ts << " <td";
504  if (completed) {
505  *ts << " class=\"done\"";
506  }
507  *ts << ">" << returnEndLine();
508  formatLocation(ts, todo);
509  *ts << " </td>" << returnEndLine();
510  }
511 
512  if (d->mSettings->taskCategories()) {
513  *ts << " <td";
514  if (completed) {
515  *ts << " class=\"done\"";
516  }
517  *ts << ">" << returnEndLine();
518  formatCategories(ts, todo);
519  *ts << " </td>" << returnEndLine();
520  }
521 
522  if (d->mSettings->taskAttendees()) {
523  *ts << " <td";
524  if (completed) {
525  *ts << " class=\"done\"";
526  }
527  *ts << ">" << returnEndLine();
528  formatAttendees(ts, todo);
529  *ts << " </td>" << returnEndLine();
530  }
531 
532  *ts << "</tr>" << returnEndLine();
533 }
534 
535 void HtmlExport::createWeekView(QTextStream *ts)
536 {
537  Q_UNUSED(ts)
538  // FIXME: Implement this!
539 }
540 
541 void HtmlExport::createJournalView(QTextStream *ts)
542 {
543  Q_UNUSED(ts)
544  // Journal::List rawJournalList = d->mCalendar->journals();
545  // FIXME: Implement this!
546 }
547 
548 void HtmlExport::createFreeBusyView(QTextStream *ts)
549 {
550  Q_UNUSED(ts)
551  // FIXME: Implement this!
552 }
553 
554 bool HtmlExport::checkSecrecy(const Incidence::Ptr &incidence)
555 {
556  int secrecy = incidence->secrecy();
557  if (secrecy == Incidence::SecrecyPublic) {
558  return true;
559  }
560  if (secrecy == Incidence::SecrecyPrivate && !d->mSettings->excludePrivate()) {
561  return true;
562  }
563  if (secrecy == Incidence::SecrecyConfidential && !d->mSettings->excludeConfidential()) {
564  return true;
565  }
566  return false;
567 }
568 
569 void HtmlExport::formatLocation(QTextStream *ts, const Incidence::Ptr &incidence)
570 {
571  if (!incidence->location().isEmpty()) {
572  *ts << " " << cleanChars(incidence->location()) << returnEndLine();
573  } else {
574  *ts << " &nbsp;" << returnEndLine();
575  }
576 }
577 
578 void HtmlExport::formatCategories(QTextStream *ts, const Incidence::Ptr &incidence)
579 {
580  if (!incidence->categoriesStr().isEmpty()) {
581  *ts << " " << cleanChars(incidence->categoriesStr()) << returnEndLine();
582  } else {
583  *ts << " &nbsp;" << returnEndLine();
584  }
585 }
586 
587 void HtmlExport::formatAttendees(QTextStream *ts, const Incidence::Ptr &incidence)
588 {
589  const Attendee::List attendees = incidence->attendees();
590  if (!attendees.isEmpty()) {
591  *ts << "<em>";
592  *ts << incidence->organizer().fullName();
593  *ts << "</em><br />";
594  for (const auto &a : attendees) {
595  if (!a.email().isEmpty()) {
596  *ts << "<a href=\"mailto:" << a.email();
597  *ts << "\">" << cleanChars(a.name()) << "</a>";
598  } else {
599  *ts << " " << cleanChars(a.name());
600  }
601  *ts << "<br />" << returnEndLine();
602  }
603  } else {
604  *ts << " &nbsp;" << returnEndLine();
605  }
606 }
607 
608 QString HtmlExport::breakString(const QString &text)
609 {
610  int number = text.count(QLatin1Char('\n'));
611  if (number <= 0) {
612  return text;
613  } else {
614  QString out;
615  QString tmpText = text;
616  QString tmp;
617  for (int i = 0; i <= number; ++i) {
618  int pos = tmpText.indexOf(QLatin1Char('\n'));
619  tmp = tmpText.left(pos);
620  tmpText = tmpText.right(tmpText.length() - pos - 1);
621  out += tmp + QLatin1String("<br />");
622  }
623  return out;
624  }
625 }
626 
627 void HtmlExport::createFooter(QTextStream *ts)
628 {
629  // FIXME: Implement this in a translatable way!
630  QString trailer = i18nc("@info", "This page was created ");
631 
632  /* bool hasPerson = false;
633  bool hasCredit = false;
634  bool hasCreditURL = false;
635  QString mail, name, credit, creditURL;*/
636  if (!d->mSettings->eMail().isEmpty()) {
637  if (!d->mSettings->name().isEmpty()) {
638  trailer +=
639  xi18nc("@info/plain page creator email link with name", "by <link url='mailto:%1'>%2</link> ", d->mSettings->eMail(), d->mSettings->name());
640  } else {
641  trailer += xi18nc("@info/plain page creator email link", "by <link url='mailto:%1'>%2</link> ", d->mSettings->eMail(), d->mSettings->eMail());
642  }
643  } else {
644  if (!d->mSettings->name().isEmpty()) {
645  trailer += i18nc("@info page creator name only", "by %1 ", d->mSettings->name());
646  }
647  }
648  if (!d->mSettings->creditName().isEmpty()) {
649  if (!d->mSettings->creditURL().isEmpty()) {
650  trailer +=
651  xi18nc("@info/plain page credit with name and link", "with <link url='%1'>%2</link>", d->mSettings->creditURL(), d->mSettings->creditName());
652  } else {
653  trailer += i18nc("@info page credit name only", "with %1", d->mSettings->creditName());
654  }
655  }
656  *ts << "<p>" << trailer << "</p>" << returnEndLine();
657 }
658 
659 QString cleanChars(const QString &text)
660 {
661  QString txt = text;
662  txt.replace(QLatin1Char('&'), QLatin1String("&amp;"));
663  txt.replace(QLatin1Char('<'), QLatin1String("&lt;"));
664  txt.replace(QLatin1Char('>'), QLatin1String("&gt;"));
665  txt.replace(QLatin1Char('\"'), QLatin1String("&quot;"));
666  txt.replace(QStringLiteral("ä"), QLatin1String("&auml;"));
667  txt.replace(QStringLiteral("Ä"), QLatin1String("&Auml;"));
668  txt.replace(QStringLiteral("ö"), QLatin1String("&ouml;"));
669  txt.replace(QStringLiteral("Ö"), QLatin1String("&Ouml;"));
670  txt.replace(QStringLiteral("ü"), QLatin1String("&uuml;"));
671  txt.replace(QStringLiteral("Ü"), QLatin1String("&Uuml;"));
672  txt.replace(QStringLiteral("ß"), QLatin1String("&szlig;"));
673  txt.replace(QStringLiteral("€"), QLatin1String("&euro;"));
674  txt.replace(QStringLiteral("é"), QLatin1String("&eacute;"));
675 
676  return txt;
677 }
678 
679 QString HtmlExport::styleSheet() const
680 {
681  if (!d->mSettings->styleSheet().isEmpty()) {
682  return d->mSettings->styleSheet();
683  }
684 
685  QString css;
686 
688  css += QLatin1String(" body { background-color:white; color:black; direction: rtl }\n");
689  css += QLatin1String(" td { text-align:center; background-color:#eee }\n");
690  css += QLatin1String(" th { text-align:center; background-color:#228; color:white }\n");
691  css += QLatin1String(" td.sumdone { background-color:#ccc }\n");
692  css += QLatin1String(" td.done { background-color:#ccc }\n");
693  css += QLatin1String(" td.subhead { text-align:center; background-color:#ccf }\n");
694  css += QLatin1String(" td.datehead { text-align:center; background-color:#ccf }\n");
695  css += QLatin1String(" td.space { background-color:white }\n");
696  css += QLatin1String(" td.dateholiday { color:red }\n");
697  } else {
698  css += QLatin1String(" body { background-color:white; color:black }\n");
699  css += QLatin1String(" td { text-align:center; background-color:#eee }\n");
700  css += QLatin1String(" th { text-align:center; background-color:#228; color:white }\n");
701  css += QLatin1String(" td.sum { text-align:left }\n");
702  css += QLatin1String(" td.sumdone { text-align:left; background-color:#ccc }\n");
703  css += QLatin1String(" td.done { background-color:#ccc }\n");
704  css += QLatin1String(" td.subhead { text-align:center; background-color:#ccf }\n");
705  css += QLatin1String(" td.datehead { text-align:center; background-color:#ccf }\n");
706  css += QLatin1String(" td.space { background-color:white }\n");
707  css += QLatin1String(" td.date { text-align:left }\n");
708  css += QLatin1String(" td.dateholiday { text-align:left; color:red }\n");
709  }
710 
711  return css;
712 }
713 
714 void HtmlExport::addHoliday(QDate date, const QString &name)
715 {
716  if (d->mHolidayMap[date].isEmpty()) {
717  d->mHolidayMap[date] = name;
718  } else {
719  d->mHolidayMap[date] = i18nc("@info holiday by date and name", "%1, %2", d->mHolidayMap[date], name);
720  }
721 }
722 
723 QDate HtmlExport::fromDate() const
724 {
725  return d->mSettings->dateStart().date();
726 }
727 
728 QDate HtmlExport::toDate() const
729 {
730  return d->mSettings->dateEnd().date();
731 }
bool isEmpty() const const
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QTextStream & endl(QTextStream &stream)
QString number(int n, int base)
virtual bool open(QIODevice::OpenMode mode) override
void append(const T &value)
Q_SCRIPTABLE Q_NOREPLY void start()
int indexOf(const T &value, int from) const const
QVector::const_iterator constEnd() const const
KCALUTILS_EXPORT QString timeToString(QTime time, bool shortfmt=true)
Build a QString time representation of a QTime object.
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
QString dayName(int day, QLocale::FormatType type) const const
KCALUTILS_EXPORT QString dateToString(QDate date, bool shortfmt=true)
Build a QString date representation of a QDate object.
bool isEmpty() const const
int length() const const
QString toString(qlonglong i) const const
bool contains(const T &value) const const
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
virtual void close() override
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isRightToLeft()
QString & replace(int position, int n, QChar after)
int count() const const
QString left(int n) const const
QString right(int n) const const
QString name(StandardShortcut id)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
int count(const T &value) const const
KIOCORE_EXPORT QString number(KIO::filesize_t size)
QSharedPointer< X > staticCast() const const
QVector::const_iterator constBegin() const const
void setCodec(QTextCodec *codec)
AKONADI_CALENDAR_EXPORT KCalendarCore::Todo::Ptr todo(const Akonadi::Item &item)
const QList< QKeySequence > & end()
bool save(const QString &fileName=QString())
Writes out the calendar in HTML format.
Definition: htmlexport.cpp:59
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Mar 26 2023 04:09:43 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.