9 #include "htmlexport.h"
10 #include "htmlexportsettings.h"
13 #include <KCalendarCore/Calendar>
14 using namespace KCalendarCore;
16 #include "kcalutils_debug.h"
18 #include <KLocalizedString>
20 #include <QApplication>
24 #include <QTextStream>
26 using namespace KCalUtils;
37 class KCalUtils::HtmlExportPrivate
40 HtmlExportPrivate(
Calendar *calendar, HTMLExportSettings *settings)
47 HTMLExportSettings *
const mSettings;
52 HtmlExport::HtmlExport(
Calendar *calendar, HTMLExportSettings *settings)
53 : d(new HtmlExportPrivate(calendar, settings))
57 HtmlExport::~HtmlExport() =
default;
62 if (fn.
isEmpty() && d->mSettings) {
63 fn = d->mSettings->outputFile();
65 if (!d->mSettings || fn.
isEmpty()) {
73 bool success =
save(&ts);
83 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
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();
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();
96 *ts <<
" <style type=\"text/css\">" << returnEndLine();
98 *ts <<
" </style>" << returnEndLine();
99 *ts <<
"</head><body>" << returnEndLine();
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();
110 if (d->mSettings->weekView()) {
114 if (d->mSettings->monthView()) {
118 if (d->mSettings->eventView()) {
124 if (d->mSettings->todoView()) {
125 if (!d->mSettings->todoListTitle().isEmpty()) {
126 *ts <<
"<h1>" << d->mSettings->todoListTitle() <<
"</h1>" << returnEndLine();
132 if (d->mSettings->journalView()) {
133 if (!d->mSettings->journalTitle().isEmpty()) {
134 *ts <<
"<h1>" << d->mSettings->journalTitle() <<
"</h1>" << returnEndLine();
136 createJournalView(ts);
140 if (d->mSettings->freeBusyView()) {
141 if (!d->mSettings->freeBusyTitle().isEmpty()) {
142 *ts <<
"<h1>" << d->mSettings->freeBusyTitle() <<
"</h1>" << returnEndLine();
144 createFreeBusyView(ts);
150 *ts <<
"</body></html>" << returnEndLine();
162 int startmonth =
start.month();
163 int startyear =
start.year();
165 while (
start < toDate()) {
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) {
174 if (
start.dayOfWeek() != 7) {
178 *ts <<
"<table border=\"1\">" << returnEndLine();
182 for (
int i = 0; i < 7; ++i) {
185 *ts <<
"</tr>" << returnEndLine();
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">)";
194 if (d->mHolidayMap.contains(
start) ||
start.dayOfWeek() == 7) {
195 *ts <<
"class=\"dateholiday\"";
197 *ts <<
"class=\"date\"";
201 if (d->mHolidayMap.contains(
start)) {
202 *ts <<
" <em>" << d->mHolidayMap[
start] <<
"</em>";
205 *ts <<
"</td></tr><tr><td valign=\"top\">";
208 if (
start >= fromDate() &&
start <= toDate()) {
209 Event::List events = d->mCalendar->events(
start, d->mCalendar->timeZone(), EventSortStartDate, SortDirectionAscending);
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);
225 *ts <<
"</td></tr></table></td>" << returnEndLine();
228 *ts <<
" </tr>" << returnEndLine();
230 *ts <<
"</table>" << returnEndLine();
232 if (startmonth > 12) {
236 start.setDate(startyear, startmonth, 1);
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();
253 if (d->mSettings->eventCategories()) {
254 *ts <<
" <th>" <<
i18nc(
"@title:column event categories",
"Categories") <<
"</th>" << returnEndLine();
257 if (d->mSettings->eventAttendees()) {
258 *ts <<
" <th>" <<
i18nc(
"@title:column event attendees",
"Attendees") <<
"</th>" << returnEndLine();
262 *ts <<
" </tr>" << returnEndLine();
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);
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);
281 *ts <<
"</table>" << returnEndLine();
286 qCDebug(KCALUTILS_LOG) <<
event->summary();
287 *ts <<
" <tr>" << returnEndLine();
289 if (!
event->allDay()) {
290 if (
event->isMultiDay(d->mCalendar->timeZone()) && (
event->dtStart().date() != date)) {
291 *ts <<
" <td> </td>" << returnEndLine();
295 if (
event->isMultiDay(d->mCalendar->timeZone()) && (
event->dtEnd().date() != date)) {
296 *ts <<
" <td> </td>" << returnEndLine();
301 *ts <<
" <td> </td><td> </td>" << returnEndLine();
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();
309 *ts <<
" </td>" << returnEndLine();
311 if (d->mSettings->eventLocation()) {
312 *ts <<
" <td>" << returnEndLine();
313 formatLocation(ts, event);
314 *ts <<
" </td>" << returnEndLine();
317 if (d->mSettings->eventCategories()) {
318 *ts <<
" <td>" << returnEndLine();
319 formatCategories(ts, event);
320 *ts <<
" </td>" << returnEndLine();
323 if (d->mSettings->eventAttendees()) {
324 *ts <<
" <td>" << returnEndLine();
325 formatAttendees(ts, event);
326 *ts <<
" </td>" << returnEndLine();
329 *ts <<
" </tr>" << returnEndLine();
334 Todo::List rawTodoList = d->mCalendar->todos();
337 while (index < rawTodoList.
count()) {
340 const QString uid = ev->relatedTo();
343 if (inc && inc->type() == Incidence::TypeTodo) {
350 index = rawTodoList.
indexOf(subev);
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)) {
366 for (it = rawTodoList.
constBegin(); it != end; ++it) {
367 if ((*it)->priority() == 0 && checkSecrecy(*it)) {
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();
382 if (d->mSettings->taskLocation()) {
383 *ts <<
" <th>" <<
i18nc(
"@title:column to-do location",
"Location") <<
"</th>" << returnEndLine();
386 if (d->mSettings->taskCategories()) {
387 *ts <<
" <th>" <<
i18nc(
"@title:column to-do categories",
"Categories") <<
"</th>" << returnEndLine();
390 if (d->mSettings->taskAttendees()) {
391 *ts <<
" <th>" <<
i18nc(
"@title:column to-do attendees",
"Attendees") <<
"</th>" << returnEndLine();
394 *ts <<
" </tr>" << returnEndLine();
398 if ((*it)->relatedTo().isEmpty()) {
409 *ts <<
" <tr>" << returnEndLine();
410 *ts <<
" <td class=\"subhead\" colspan=";
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();
419 for (
int i = 1; i <= 9; ++i) {
420 Incidence::List::ConstIterator it2;
423 if (ev3 && ev3->priority() == i) {
428 Incidence::List::ConstIterator it2;
431 if (ev3 && ev3->priority() == 0) {
436 Todo::List::ConstIterator it3;
438 createTodo(ts, *it3);
443 *ts <<
"</table>" << returnEndLine();
448 qCDebug(KCALUTILS_LOG);
450 const bool completed =
todo->isCompleted();
454 *ts <<
"<tr>" << returnEndLine();
456 *ts <<
" <td class=\"sum";
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();
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();
470 *ts <<
" </td>" << returnEndLine();
474 *ts <<
" class=\"done\"";
476 *ts <<
">" << returnEndLine();
477 *ts <<
" " <<
todo->priority() << returnEndLine();
478 *ts <<
" </td>" << returnEndLine();
482 *ts <<
" class=\"done\"";
484 *ts <<
">" << returnEndLine();
485 *ts <<
" " <<
i18nc(
"@info to-do percent complete",
"%1 %",
todo->percentComplete()) << returnEndLine();
486 *ts <<
" </td>" << returnEndLine();
488 if (d->mSettings->taskDueDate()) {
491 *ts <<
" class=\"done\"";
493 *ts <<
">" << returnEndLine();
494 if (
todo->hasDueDate()) {
497 *ts <<
" " << returnEndLine();
499 *ts <<
" </td>" << returnEndLine();
502 if (d->mSettings->taskLocation()) {
505 *ts <<
" class=\"done\"";
507 *ts <<
">" << returnEndLine();
508 formatLocation(ts, todo);
509 *ts <<
" </td>" << returnEndLine();
512 if (d->mSettings->taskCategories()) {
515 *ts <<
" class=\"done\"";
517 *ts <<
">" << returnEndLine();
518 formatCategories(ts, todo);
519 *ts <<
" </td>" << returnEndLine();
522 if (d->mSettings->taskAttendees()) {
525 *ts <<
" class=\"done\"";
527 *ts <<
">" << returnEndLine();
528 formatAttendees(ts, todo);
529 *ts <<
" </td>" << returnEndLine();
532 *ts <<
"</tr>" << returnEndLine();
541 void HtmlExport::createJournalView(
QTextStream *ts)
548 void HtmlExport::createFreeBusyView(
QTextStream *ts)
557 if (secrecy == Incidence::SecrecyPublic) {
560 if (secrecy == Incidence::SecrecyPrivate && !d->mSettings->excludePrivate()) {
563 if (secrecy == Incidence::SecrecyConfidential && !d->mSettings->excludeConfidential()) {
572 *ts <<
" " << cleanChars(
incidence->location()) << returnEndLine();
574 *ts <<
" " << returnEndLine();
580 if (!
incidence->categoriesStr().isEmpty()) {
581 *ts <<
" " << cleanChars(
incidence->categoriesStr()) << returnEndLine();
583 *ts <<
" " << returnEndLine();
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>";
599 *ts <<
" " << cleanChars(a.name());
601 *ts <<
"<br />" << returnEndLine();
604 *ts <<
" " << returnEndLine();
617 for (
int i = 0; i <=
number; ++i) {
619 tmp = tmpText.
left(pos);
620 tmpText = tmpText.
right(tmpText.
length() - pos - 1);
630 QString trailer =
i18nc(
"@info",
"This page was created ");
636 if (!d->mSettings->eMail().isEmpty()) {
637 if (!d->mSettings->name().isEmpty()) {
639 xi18nc(
"@info/plain page creator email link with name",
"by <link url='mailto:%1'>%2</link> ", d->mSettings->eMail(), d->mSettings->name());
641 trailer +=
xi18nc(
"@info/plain page creator email link",
"by <link url='mailto:%1'>%2</link> ", d->mSettings->eMail(), d->mSettings->eMail());
644 if (!d->mSettings->name().isEmpty()) {
645 trailer +=
i18nc(
"@info page creator name only",
"by %1 ", d->mSettings->name());
648 if (!d->mSettings->creditName().isEmpty()) {
649 if (!d->mSettings->creditURL().isEmpty()) {
651 xi18nc(
"@info/plain page credit with name and link",
"with <link url='%1'>%2</link>", d->mSettings->creditURL(), d->mSettings->creditName());
653 trailer +=
i18nc(
"@info page credit name only",
"with %1", d->mSettings->creditName());
656 *ts <<
"<p>" << trailer <<
"</p>" << returnEndLine();
679 QString HtmlExport::styleSheet()
const
681 if (!d->mSettings->styleSheet().isEmpty()) {
682 return d->mSettings->styleSheet();
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");
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");
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");
708 css +=
QLatin1String(
" td.dateholiday { text-align:left; color:red }\n");
714 void HtmlExport::addHoliday(
QDate date,
const QString &name)
716 if (d->mHolidayMap[date].isEmpty()) {
717 d->mHolidayMap[date] =
name;
719 d->mHolidayMap[date] =
i18nc(
"@info holiday by date and name",
"%1, %2", d->mHolidayMap[date], name);
723 QDate HtmlExport::fromDate()
const
725 return d->mSettings->dateStart().date();
728 QDate HtmlExport::toDate()
const
730 return d->mSettings->dateEnd().date();