22 #include "htmlexport.h"
23 #include "htmlexportsettings.h"
27 using namespace KCalCore;
30 #include <KCalendarSystem>
34 #include <QtCore/QFile>
35 #include <QtCore/QMap>
36 #include <QtCore/QTextStream>
37 #include <QApplication>
39 using namespace KCalUtils;
41 static QString cleanChars(
const QString &txt );
44 class KCalUtils::HtmlExport::Private
48 : mCalendar( calendar ), mSettings( settings )
52 HTMLExportSettings *mSettings;
53 QMap<QDate,QString> mHolidayMap;
57 HtmlExport::HtmlExport(
MemoryCalendar *calendar, HTMLExportSettings *settings )
58 : d( new Private( calendar, settings ) )
62 HtmlExport::~HtmlExport()
69 QString fn( fileName );
70 if ( fn.isEmpty() && d->mSettings ) {
71 fn = d->mSettings->outputFile();
73 if ( !d->mSettings || fn.isEmpty() ) {
77 if ( !f.open( QIODevice::WriteOnly ) ) {
81 bool success =
save( &ts );
88 if ( !d->mSettings ) {
91 ts->setCodec(
"UTF-8" );
93 *ts <<
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ";
94 *ts <<
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" << endl;
96 *ts <<
"<html><head>" << endl;
97 *ts <<
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=";
98 *ts <<
"UTF-8\" />" << endl;
99 if ( !d->mSettings->pageTitle().isEmpty() ) {
100 *ts <<
" <title>" << d->mSettings->pageTitle() <<
"</title>" << endl;
102 *ts <<
" <style type=\"text/css\">" << endl;
104 *ts <<
" </style>" << endl;
105 *ts <<
"</head><body>" << endl;
110 if ( d->mSettings->eventView() || d->mSettings->monthView() || d->mSettings->weekView() ) {
111 if ( !d->mSettings->eventTitle().isEmpty() ) {
112 *ts <<
"<h1>" << d->mSettings->eventTitle() <<
"</h1>" << endl;
116 if ( d->mSettings->weekView() ) {
117 createWeekView( ts );
120 if ( d->mSettings->monthView() ) {
121 createMonthView( ts );
124 if ( d->mSettings->eventView() ) {
125 createEventList( ts );
130 if ( d->mSettings->todoView() ) {
131 if ( !d->mSettings->todoListTitle().isEmpty() ) {
132 *ts <<
"<h1>" << d->mSettings->todoListTitle() <<
"</h1>" << endl;
134 createTodoList( ts );
138 if ( d->mSettings->journalView() ) {
139 if ( !d->mSettings->journalTitle().isEmpty() ) {
140 *ts <<
"<h1>" << d->mSettings->journalTitle() <<
"</h1>" << endl;
142 createJournalView( ts );
146 if ( d->mSettings->freeBusyView() ) {
147 if ( !d->mSettings->freeBusyTitle().isEmpty() ) {
148 *ts <<
"<h1>" << d->mSettings->freeBusyTitle() <<
"</h1>" << endl;
150 createFreeBusyView( ts );
156 *ts <<
"</body></html>" << endl;
161 void HtmlExport::createMonthView( QTextStream *ts )
163 QDate start = fromDate();
164 start.setYMD( start.year(), start.month(), 1 );
166 QDate end( start.year(), start.month(), start.daysInMonth() );
168 int startmonth = start.month();
169 int startyear = start.year();
171 while ( start < toDate() ) {
173 QDate hDate( start.year(), start.month(), 1 );
174 QString hMon = hDate.toString(
"MMMM" );
175 QString hYear = hDate.toString(
"yyyy" );
177 << i18nc(
"@title month and year",
"%1 %2", hMon, hYear )
179 if ( KGlobal::locale()->weekStartDay() == 1 ) {
180 start = start.addDays( 1 - start.dayOfWeek() );
182 if ( start.dayOfWeek() != 7 ) {
183 start = start.addDays( -start.dayOfWeek() );
186 *ts <<
"<table border=\"1\">" << endl;
190 for (
int i=0; i < 7; ++i ) {
191 *ts <<
"<th>" << KGlobal::locale()->calendar()->weekDayName( start.addDays(i) ) <<
"</th>";
193 *ts <<
"</tr>" << endl;
196 while ( start <= end ) {
197 *ts <<
" <tr>" << endl;
198 for (
int i=0; i < 7; ++i ) {
199 *ts <<
" <td valign=\"top\"><table border=\"0\">";
202 if ( d->mHolidayMap.contains( start ) || start.dayOfWeek() == 7 ) {
203 *ts <<
"class=\"dateholiday\"";
205 *ts <<
"class=\"date\"";
207 *ts <<
">" << QString::number( start.day() );
209 if ( d->mHolidayMap.contains( start ) ) {
210 *ts <<
" <em>" << d->mHolidayMap[start] <<
"</em>";
213 *ts <<
"</td></tr><tr><td valign=\"top\">";
216 if ( start >= fromDate() && start <= toDate() ) {
217 Event::List events = d->mCalendar->events( start, d->mCalendar->timeSpec(),
220 if ( events.count() ) {
222 Event::List::ConstIterator it;
223 for ( it = events.constBegin(); it != events.constEnd(); ++it ) {
224 if ( checkSecrecy( *it ) ) {
225 createEvent( ts, *it, start,
false );
234 *ts <<
"</td></tr></table></td>" << endl;
235 start = start.addDays( 1 );
237 *ts <<
" </tr>" << endl;
239 *ts <<
"</table>" << endl;
241 if ( startmonth > 12 ) {
245 start.setYMD( startyear, startmonth, 1 );
246 end.setYMD( start.year(), start.month(), start.daysInMonth() );
250 void HtmlExport::createEventList( QTextStream *ts )
253 *ts <<
"<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">" << endl;
254 *ts <<
" <tr>" << endl;
255 *ts <<
" <th class=\"sum\">" << i18nc(
"@title:column event start time",
256 "Start Time" ) <<
"</th>" << endl;
257 *ts <<
" <th>" << i18nc(
"@title:column event end time",
258 "End Time" ) <<
"</th>" << endl;
259 *ts <<
" <th>" << i18nc(
"@title:column event description",
260 "Event" ) <<
"</th>" << endl;
261 if ( d->mSettings->eventLocation() ) {
262 *ts <<
" <th>" << i18nc(
"@title:column event locatin",
263 "Location" ) <<
"</th>" << endl;
266 if ( d->mSettings->eventCategories() ) {
267 *ts <<
" <th>" << i18nc(
"@title:column event categories",
268 "Categories" ) <<
"</th>" << endl;
271 if ( d->mSettings->eventAttendees() ) {
272 *ts <<
" <th>" << i18nc(
"@title:column event attendees",
273 "Attendees" ) <<
"</th>" << endl;
277 *ts <<
" </tr>" << endl;
279 for ( QDate dt = fromDate(); dt <= toDate(); dt = dt.addDays(1) ) {
280 kDebug() <<
"Getting events for" << dt.toString();
281 Event::List events = d->mCalendar->events( dt, d->mCalendar->timeSpec(),
284 if ( events.count() ) {
285 *ts <<
" <tr><td colspan=\"" << QString::number( columns )
286 <<
"\" class=\"datehead\"><i>"
287 << KGlobal::locale()->formatDate( dt )
288 <<
"</i></td></tr>" << endl;
290 Event::List::ConstIterator it;
291 for ( it = events.constBegin(); it != events.constEnd(); ++it ) {
292 if ( checkSecrecy( *it ) ) {
293 createEvent( ts, *it, dt );
299 *ts <<
"</table>" << endl;
302 void HtmlExport::createEvent ( QTextStream *ts,
305 bool withDescription )
307 kDebug() <<
event->summary();
308 *ts <<
" <tr>" << endl;
310 if ( !event->allDay() ) {
311 if ( event->isMultiDay( d->mCalendar->timeSpec() ) && ( event->dtStart().date() != date ) ) {
312 *ts <<
" <td> </td>" << endl;
314 *ts <<
" <td valign=\"top\">"
318 if ( event->isMultiDay( d->mCalendar->timeSpec() ) && ( event->dtEnd().date() != date ) ) {
319 *ts <<
" <td> </td>" << endl;
321 *ts <<
" <td valign=\"top\">"
326 *ts <<
" <td> </td><td> </td>" << endl;
329 *ts <<
" <td class=\"sum\">" << endl;
330 *ts <<
" <b>" << cleanChars( event->summary() ) <<
"</b>" << endl;
331 if ( withDescription && !event->description().isEmpty() ) {
332 *ts <<
" <p>" << breakString( cleanChars( event->description() ) ) <<
"</p>" << endl;
334 *ts <<
" </td>" << endl;
336 if ( d->mSettings->eventLocation() ) {
337 *ts <<
" <td>" << endl;
338 formatLocation( ts, event );
339 *ts <<
" </td>" << endl;
342 if ( d->mSettings->eventCategories() ) {
343 *ts <<
" <td>" << endl;
344 formatCategories( ts, event );
345 *ts <<
" </td>" << endl;
348 if ( d->mSettings->eventAttendees() ) {
349 *ts <<
" <td>" << endl;
350 formatAttendees( ts, event );
351 *ts <<
" </td>" << endl;
354 *ts <<
" </tr>" << endl;
357 void HtmlExport::createTodoList ( QTextStream *ts )
359 Todo::List rawTodoList = d->mCalendar->todos();
362 while ( index < rawTodoList.count() ) {
365 const QString uid = ev->relatedTo();
366 if ( !uid.isEmpty() ) {
367 Incidence::Ptr inc = d->mCalendar->incidence( uid );
368 if ( inc && inc->type() == Incidence::TypeTodo ) {
370 if ( !rawTodoList.contains( todo ) ) {
371 rawTodoList.append( todo );
375 index = rawTodoList.indexOf( subev );
382 Todo::List::ConstIterator it;
383 for (
int i = 1; i <= 9; ++i ) {
384 for ( it = rawTodoList.constBegin(); it != rawTodoList.constEnd(); ++it ) {
385 if ( (*it)->priority() == i && checkSecrecy( *it ) ) {
386 todoList.append( *it );
390 for ( it = rawTodoList.constBegin(); it != rawTodoList.constEnd(); ++it ) {
391 if ( (*it)->priority() == 0 && checkSecrecy( *it ) ) {
392 todoList.append( *it );
397 *ts <<
"<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">" << endl;
398 *ts <<
" <tr>" << endl;
399 *ts <<
" <th class=\"sum\">" << i18nc(
"@title:column",
"To-do" ) <<
"</th>" << endl;
400 *ts <<
" <th>" << i18nc(
"@title:column to-do priority",
"Priority" ) <<
"</th>" << endl;
401 *ts <<
" <th>" << i18nc(
"@title:column to-do percent completed",
402 "Completed" ) <<
"</th>" << endl;
403 if ( d->mSettings->taskDueDate() ) {
404 *ts <<
" <th>" << i18nc(
"@title:column to-do due date",
"Due Date" ) <<
"</th>" << endl;
407 if ( d->mSettings->taskLocation() ) {
408 *ts <<
" <th>" << i18nc(
"@title:column to-do location",
"Location" ) <<
"</th>" << endl;
411 if ( d->mSettings->taskCategories() ) {
412 *ts <<
" <th>" << i18nc(
"@title:column to-do categories",
"Categories" ) <<
"</th>" << endl;
415 if ( d->mSettings->taskAttendees() ) {
416 *ts <<
" <th>" << i18nc(
"@title:column to-do attendees",
"Attendees" ) <<
"</th>" << endl;
419 *ts <<
" </tr>" << endl;
422 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
423 if ( (*it)->relatedTo().isEmpty() ) {
424 createTodo( ts, *it );
429 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
430 Incidence::List relations = d->mCalendar->relations( ( *it )->uid() );
432 if ( relations.count() ) {
434 *ts <<
" <tr>" << endl;
435 *ts <<
" <td class=\"subhead\" colspan=";
436 *ts <<
"\"" << QString::number(columns) <<
"\"";
437 *ts <<
"><a name=\"sub" << (*it)->uid() <<
"\"></a>"
438 << i18nc(
"@title:column sub-to-dos of the parent to-do",
439 "Sub-To-dos of: " ) <<
"<a href=\"#"
440 << (*it)->uid() <<
"\"><b>" << cleanChars( (*it)->summary() )
441 <<
"</b></a></td>" << endl;
442 *ts <<
" </tr>" << endl;
447 for (
int i = 1; i <= 9; ++i ) {
448 Incidence::List::ConstIterator it2;
449 for ( it2 = relations.constBegin(); it2 != relations.constEnd(); ++it2 ) {
451 if ( ev3 && ev3->priority() == i ) {
452 sortedList.append( ev3 );
456 Incidence::List::ConstIterator it2;
457 for ( it2 = relations.constBegin(); it2 != relations.constEnd(); ++it2 ) {
459 if ( ev3 && ev3->priority() == 0 ) {
460 sortedList.append( ev3 );
464 Todo::List::ConstIterator it3;
465 for ( it3 = sortedList.constBegin(); it3 != sortedList.constEnd(); ++it3 ) {
466 createTodo( ts, *it3 );
471 *ts <<
"</table>" << endl;
474 void HtmlExport::createTodo( QTextStream *ts,
const Todo::Ptr &todo )
478 const bool completed = todo->isCompleted();
480 Incidence::List relations = d->mCalendar->relations( todo->uid() );
482 *ts <<
"<tr>" << endl;
484 *ts <<
" <td class=\"sum";
485 if (completed) *ts <<
"done";
486 *ts <<
"\">" << endl;
487 *ts <<
" <a name=\"" << todo->uid() <<
"\"></a>" << endl;
488 *ts <<
" <b>" << cleanChars( todo->summary() ) <<
"</b>" << endl;
489 if ( !todo->description().isEmpty() ) {
490 *ts <<
" <p>" << breakString( cleanChars( todo->description() ) ) <<
"</p>" << endl;
492 if ( relations.count() ) {
493 *ts <<
" <div align=\"right\"><a href=\"#sub" << todo->uid()
494 <<
"\">" << i18nc(
"@title:column sub-to-dos of the parent to-do",
495 "Sub-To-dos" ) <<
"</a></div>" << endl;
497 *ts <<
" </td>" << endl;
501 *ts <<
" class=\"done\"";
504 *ts <<
" " << todo->priority() << endl;
505 *ts <<
" </td>" << endl;
509 *ts <<
" class=\"done\"";
512 *ts <<
" " << i18nc(
"@info/plain to-do percent complete",
513 "%1 %", todo->percentComplete() ) << endl;
514 *ts <<
" </td>" << endl;
516 if ( d->mSettings->taskDueDate() ) {
519 *ts <<
" class=\"done\"";
522 if ( todo->hasDueDate() ) {
525 *ts <<
" " << endl;
527 *ts <<
" </td>" << endl;
530 if ( d->mSettings->taskLocation() ) {
533 *ts <<
" class=\"done\"";
536 formatLocation( ts, todo );
537 *ts <<
" </td>" << endl;
540 if ( d->mSettings->taskCategories() ) {
543 *ts <<
" class=\"done\"";
546 formatCategories( ts, todo );
547 *ts <<
" </td>" << endl;
550 if ( d->mSettings->taskAttendees() ) {
553 *ts <<
" class=\"done\"";
556 formatAttendees( ts, todo );
557 *ts <<
" </td>" << endl;
560 *ts <<
"</tr>" << endl;
563 void HtmlExport::createWeekView( QTextStream *ts )
569 void HtmlExport::createJournalView( QTextStream *ts )
576 void HtmlExport::createFreeBusyView( QTextStream *ts )
582 bool HtmlExport::checkSecrecy(
const Incidence::Ptr &incidence )
584 int secrecy = incidence->secrecy();
585 if ( secrecy == Incidence::SecrecyPublic ) {
588 if ( secrecy == Incidence::SecrecyPrivate && !d->mSettings->excludePrivate() ) {
591 if ( secrecy == Incidence::SecrecyConfidential &&
592 !d->mSettings->excludeConfidential() ) {
598 void HtmlExport::formatLocation( QTextStream *ts,
599 const Incidence::Ptr &incidence )
601 if ( !incidence->location().isEmpty() ) {
602 *ts <<
" " << cleanChars( incidence->location() ) << endl;
604 *ts <<
" " << endl;
608 void HtmlExport::formatCategories( QTextStream *ts,
609 const Incidence::Ptr &incidence )
611 if ( !incidence->categoriesStr().isEmpty() ) {
612 *ts <<
" " << cleanChars( incidence->categoriesStr() ) << endl;
614 *ts <<
" " << endl;
618 void HtmlExport::formatAttendees( QTextStream *ts,
619 const Incidence::Ptr &incidence )
622 if ( attendees.count() ) {
624 *ts << incidence->organizer()->fullName();
625 *ts <<
"</em><br />";
626 Attendee::List::ConstIterator it;
627 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
629 if ( !a->email().isEmpty() ) {
630 *ts <<
"<a href=\"mailto:" << a->email();
631 *ts <<
"\">" << cleanChars( a->name() ) <<
"</a>";
633 *ts <<
" " << cleanChars( a->name() );
635 *ts <<
"<br />" << endl;
638 *ts <<
" " << endl;
642 QString HtmlExport::breakString(
const QString &text )
644 int number = text.count(
"\n" );
649 QString tmpText = text;
652 for (
int i = 0; i <= number; ++i ) {
653 pos = tmpText.indexOf(
"\n" );
654 tmp = tmpText.left( pos );
655 tmpText = tmpText.right( tmpText.length() - pos - 1 );
656 out += tmp +
"<br />";
662 void HtmlExport::createFooter( QTextStream *ts )
665 QString trailer = i18nc(
"@info/plain",
"This page was created " );
671 if ( !d->mSettings->eMail().isEmpty() ) {
672 if ( !d->mSettings->name().isEmpty() ) {
673 trailer += i18nc(
"@info/plain page creator email link with name",
674 "by <link url='mailto:%1'>%2</link> ",
675 d->mSettings->eMail(), d->mSettings->name() );
677 trailer += i18nc(
"@info/plain page creator email link",
678 "by <link url='mailto:%1'>%2</link> ",
679 d->mSettings->eMail(), d->mSettings->eMail() );
682 if ( !d->mSettings->name().isEmpty() ) {
683 trailer += i18nc(
"@info/plain page creator name only",
684 "by %1 ", d->mSettings->name() );
687 if ( !d->mSettings->creditName().isEmpty() ) {
688 if ( !d->mSettings->creditURL().isEmpty() ) {
689 trailer += i18nc(
"@info/plain page credit with name and link",
690 "with <link url='%1'>%2</link>",
691 d->mSettings->creditURL(), d->mSettings->creditName() );
693 trailer += i18nc(
"@info/plain page credit name only",
694 "with %1", d->mSettings->creditName() );
697 *ts <<
"<p>" << trailer <<
"</p>" << endl;
700 QString cleanChars(
const QString &text )
703 txt = txt.replace(
'&',
"&" );
704 txt = txt.replace(
'<',
"<" );
705 txt = txt.replace(
'>',
">" );
706 txt = txt.replace(
'\"',
""" );
707 txt = txt.replace( QString::fromUtf8(
"ä" ),
"ä" );
708 txt = txt.replace( QString::fromUtf8(
"Ä" ),
"Ä" );
709 txt = txt.replace( QString::fromUtf8(
"ö" ),
"ö" );
710 txt = txt.replace( QString::fromUtf8(
"Ö" ),
"Ö" );
711 txt = txt.replace( QString::fromUtf8(
"ü" ),
"ü" );
712 txt = txt.replace( QString::fromUtf8(
"Ü" ),
"Ü" );
713 txt = txt.replace( QString::fromUtf8(
"ß" ),
"ß" );
714 txt = txt.replace( QString::fromUtf8(
"€" ),
"€" );
715 txt = txt.replace( QString::fromUtf8(
"é" ),
"é" );
720 QString HtmlExport::styleSheet()
const
722 if ( !d->mSettings->styleSheet().isEmpty() ) {
723 return d->mSettings->styleSheet();
728 if ( QApplication::isRightToLeft() ) {
729 css +=
" body { background-color:white; color:black; direction: rtl }\n";
730 css +=
" td { text-align:center; background-color:#eee }\n";
731 css +=
" th { text-align:center; background-color:#228; color:white }\n";
732 css +=
" td.sumdone { background-color:#ccc }\n";
733 css +=
" td.done { background-color:#ccc }\n";
734 css +=
" td.subhead { text-align:center; background-color:#ccf }\n";
735 css +=
" td.datehead { text-align:center; background-color:#ccf }\n";
736 css +=
" td.space { background-color:white }\n";
737 css +=
" td.dateholiday { color:red }\n";
739 css +=
" body { background-color:white; color:black }\n";
740 css +=
" td { text-align:center; background-color:#eee }\n";
741 css +=
" th { text-align:center; background-color:#228; color:white }\n";
742 css +=
" td.sum { text-align:left }\n";
743 css +=
" td.sumdone { text-align:left; background-color:#ccc }\n";
744 css +=
" td.done { background-color:#ccc }\n";
745 css +=
" td.subhead { text-align:center; background-color:#ccf }\n";
746 css +=
" td.datehead { text-align:center; background-color:#ccf }\n";
747 css +=
" td.space { background-color:white }\n";
748 css +=
" td.date { text-align:left }\n";
749 css +=
" td.dateholiday { text-align:left; color:red }\n";
755 void HtmlExport::addHoliday(
const QDate &date,
const QString &name )
757 if ( d->mHolidayMap[date].isEmpty() ) {
758 d->mHolidayMap[date] = name;
760 d->mHolidayMap[date] = i18nc(
"@info/plain holiday by date and name",
761 "%1, %2", d->mHolidayMap[date], name );
765 QDate HtmlExport::fromDate()
const
767 return d->mSettings->dateStart().date();
770 QDate HtmlExport::toDate()
const
772 return d->mSettings->dateEnd().date();