23 #include "htmlexport.h"
24 #include "htmlexportsettings.h"
30 #include "kabc/stdaddressbook.h"
36 #include <kcalendarsystem.h>
38 #include <QtCore/QFile>
39 #include <QtCore/QTextStream>
40 #include <QtCore/QTextCodec>
41 #include <QtCore/QRegExp>
42 #include <QtCore/QMap>
43 #include <QApplication>
47 static QString cleanChars(
const QString &txt );
50 class KCal::HtmlExport::Private
53 Private(
Calendar *calendar, HTMLExportSettings *settings )
54 : mCalendar( calendar ),
59 HTMLExportSettings *mSettings;
60 QMap<QDate,QString> mHolidayMap;
65 : d( new Private( calendar, settings ) )
69 HtmlExport::~HtmlExport()
76 QString fn( fileName );
77 if ( fn.isEmpty() && d->mSettings ) {
78 fn = d->mSettings->outputFile();
80 if ( !d->mSettings || fn.isEmpty() ) {
84 if ( !f.open( QIODevice::WriteOnly ) ) {
88 bool success =
save( &ts );
95 if ( !d->mSettings ) {
98 ts->setCodec(
"UTF-8" );
100 *ts <<
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ";
101 *ts <<
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" << endl;
103 *ts <<
"<html><head>" << endl;
104 *ts <<
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=";
105 *ts <<
"UTF-8\" />" << endl;
106 if ( !d->mSettings->pageTitle().isEmpty() ) {
107 *ts <<
" <title>" << d->mSettings->pageTitle() <<
"</title>" << endl;
109 *ts <<
" <style type=\"text/css\">" << endl;
111 *ts <<
" </style>" << endl;
112 *ts <<
"</head><body>" << endl;
117 if ( d->mSettings->eventView() || d->mSettings->monthView() || d->mSettings->weekView() ) {
118 if ( !d->mSettings->eventTitle().isEmpty() ) {
119 *ts <<
"<h1>" << d->mSettings->eventTitle() <<
"</h1>" << endl;
123 if ( d->mSettings->weekView() ) {
124 createWeekView( ts );
127 if ( d->mSettings->monthView() ) {
128 createMonthView( ts );
131 if ( d->mSettings->eventView() ) {
132 createEventList( ts );
137 if ( d->mSettings->todoView() ) {
138 if ( !d->mSettings->todoListTitle().isEmpty() ) {
139 *ts <<
"<h1>" << d->mSettings->todoListTitle() <<
"</h1>" << endl;
141 createTodoList( ts );
145 if ( d->mSettings->journalView() ) {
146 if ( !d->mSettings->journalTitle().isEmpty() ) {
147 *ts <<
"<h1>" << d->mSettings->journalTitle() <<
"</h1>" << endl;
149 createJournalView( ts );
153 if ( d->mSettings->freeBusyView() ) {
154 if ( !d->mSettings->freeBusyTitle().isEmpty() ) {
155 *ts <<
"<h1>" << d->mSettings->freeBusyTitle() <<
"</h1>" << endl;
157 createFreeBusyView( ts );
163 *ts <<
"</body></html>" << endl;
168 void HtmlExport::createMonthView( QTextStream *ts )
170 QDate start = fromDate();
171 start.setYMD( start.year(), start.month(), 1 );
173 QDate end( start.year(), start.month(), start.daysInMonth() );
175 int startmonth = start.month();
176 int startyear = start.year();
178 while ( start < toDate() ) {
180 QDate hDate( start.year(), start.month(), 1 );
181 QString hMon = hDate.toString(
"MMMM" );
182 QString hYear = hDate.toString(
"yyyy" );
184 << i18nc(
"@title month and year",
"%1 %2", hMon, hYear )
186 if ( KGlobal::locale()->weekStartDay() == 1 ) {
187 start = start.addDays( 1 - start.dayOfWeek() );
189 if ( start.dayOfWeek() != 7 ) {
190 start = start.addDays( -start.dayOfWeek() );
193 *ts <<
"<table border=\"1\">" << endl;
197 for (
int i=0; i < 7; ++i ) {
198 *ts <<
"<th>" << KGlobal::locale()->calendar()->weekDayName( start.addDays(i) ) <<
"</th>";
200 *ts <<
"</tr>" << endl;
203 while ( start <= end ) {
204 *ts <<
" <tr>" << endl;
205 for (
int i=0; i < 7; ++i ) {
206 *ts <<
" <td valign=\"top\"><table border=\"0\">";
209 if ( d->mHolidayMap.contains( start ) || start.dayOfWeek() == 7 ) {
210 *ts <<
"class=\"dateholiday\"";
212 *ts <<
"class=\"date\"";
214 *ts <<
">" << QString::number( start.day() );
216 if ( d->mHolidayMap.contains( start ) ) {
217 *ts <<
" <em>" << d->mHolidayMap[start] <<
"</em>";
220 *ts <<
"</td></tr><tr><td valign=\"top\">";
223 if ( start >= fromDate() && start <= toDate() ) {
224 Event::List events = d->mCalendar->events( start, d->mCalendar->timeSpec(),
227 if ( events.count() ) {
229 Event::List::ConstIterator it;
230 for ( it = events.constBegin(); it != events.constEnd(); ++it ) {
231 if ( checkSecrecy( *it ) ) {
232 createEvent( ts, *it, start,
false );
241 *ts <<
"</td></tr></table></td>" << endl;
242 start = start.addDays( 1 );
244 *ts <<
" </tr>" << endl;
246 *ts <<
"</table>" << endl;
248 if ( startmonth > 12 ) {
252 start.setYMD( startyear, startmonth, 1 );
253 end.setYMD( start.year(), start.month(), start.daysInMonth() );
257 void HtmlExport::createEventList( QTextStream *ts )
260 *ts <<
"<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">" << endl;
261 *ts <<
" <tr>" << endl;
262 *ts <<
" <th class=\"sum\">" << i18nc(
"@title:column event start time",
263 "Start Time" ) <<
"</th>" << endl;
264 *ts <<
" <th>" << i18nc(
"@title:column event end time",
265 "End Time" ) <<
"</th>" << endl;
266 *ts <<
" <th>" << i18nc(
"@title:column event description",
267 "Event" ) <<
"</th>" << endl;
268 if ( d->mSettings->eventLocation() ) {
269 *ts <<
" <th>" << i18nc(
"@title:column event location",
270 "Location" ) <<
"</th>" << endl;
273 if ( d->mSettings->eventCategories() ) {
274 *ts <<
" <th>" << i18nc(
"@title:column event categories",
275 "Categories" ) <<
"</th>" << endl;
278 if ( d->mSettings->eventAttendees() ) {
279 *ts <<
" <th>" << i18nc(
"@title:column event attendees",
280 "Attendees" ) <<
"</th>" << endl;
284 *ts <<
" </tr>" << endl;
286 for ( QDate dt = fromDate(); dt <= toDate(); dt = dt.addDays(1) ) {
287 kDebug() <<
"Getting events for" << dt.toString();
288 Event::List events = d->mCalendar->events( dt, d->mCalendar->timeSpec(),
291 if ( events.count() ) {
292 *ts <<
" <tr><td colspan=\"" << QString::number( columns )
293 <<
"\" class=\"datehead\"><i>"
294 << KGlobal::locale()->formatDate( dt )
295 <<
"</i></td></tr>" << endl;
297 Event::List::ConstIterator it;
298 for ( it = events.constBegin(); it != events.constEnd(); ++it ) {
299 if ( checkSecrecy( *it ) ) {
300 createEvent( ts, *it, dt );
306 *ts <<
"</table>" << endl;
309 void HtmlExport::createEvent ( QTextStream *ts,
Event *event,
310 QDate date,
bool withDescription )
312 kDebug() <<
event->summary();
313 *ts <<
" <tr>" << endl;
316 if ( event->
isMultiDay( d->mCalendar->timeSpec() ) && ( event->
dtStart().date() != date ) ) {
317 *ts <<
" <td> </td>" << endl;
319 *ts <<
" <td valign=\"top\">"
323 if ( event->
isMultiDay( d->mCalendar->timeSpec() ) && ( event->
dtEnd().date() != date ) ) {
324 *ts <<
" <td> </td>" << endl;
326 *ts <<
" <td valign=\"top\">"
331 *ts <<
" <td> </td><td> </td>" << endl;
334 *ts <<
" <td class=\"sum\">" << endl;
335 *ts <<
" <b>" << cleanChars( event->
summary() ) <<
"</b>" << endl;
336 if ( withDescription && !event->
description().isEmpty() ) {
337 *ts <<
" <p>" << breakString( cleanChars( event->
description() ) ) <<
"</p>" << endl;
339 *ts <<
" </td>" << endl;
341 if ( d->mSettings->eventLocation() ) {
342 *ts <<
" <td>" << endl;
343 formatLocation( ts, event );
344 *ts <<
" </td>" << endl;
347 if ( d->mSettings->eventCategories() ) {
348 *ts <<
" <td>" << endl;
349 formatCategories( ts, event );
350 *ts <<
" </td>" << endl;
353 if ( d->mSettings->eventAttendees() ) {
354 *ts <<
" <td>" << endl;
355 formatAttendees( ts, event );
356 *ts <<
" </td>" << endl;
359 *ts <<
" </tr>" << endl;
362 void HtmlExport::createTodoList ( QTextStream *ts )
364 Todo::List rawTodoList = d->mCalendar->todos();
367 while ( index < rawTodoList.count() ) {
368 Todo *ev = rawTodoList[ index ];
372 if ( !rawTodoList.contains( static_cast<Todo *>( ev->
relatedTo() ) ) ) {
373 rawTodoList.append( static_cast<Todo *>( ev->
relatedTo() ) );
377 index = rawTodoList.indexOf( subev );
384 Todo::List::ConstIterator it;
385 for (
int i = 1; i <= 9; ++i ) {
386 for ( it = rawTodoList.constBegin(); it != rawTodoList.constEnd(); ++it ) {
387 if ( (*it)->priority() == i && checkSecrecy( *it ) ) {
388 todoList.append( *it );
392 for ( it = rawTodoList.constBegin(); it != rawTodoList.constEnd(); ++it ) {
393 if ( (*it)->priority() == 0 && checkSecrecy( *it ) ) {
394 todoList.append( *it );
399 *ts <<
"<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">" << endl;
400 *ts <<
" <tr>" << endl;
401 *ts <<
" <th class=\"sum\">" << i18nc(
"@title:column",
"To-do" ) <<
"</th>" << endl;
402 *ts <<
" <th>" << i18nc(
"@title:column to-do priority",
"Priority" ) <<
"</th>" << endl;
403 *ts <<
" <th>" << i18nc(
"@title:column to-do percent completed",
404 "Completed" ) <<
"</th>" << endl;
405 if ( d->mSettings->taskDueDate() ) {
406 *ts <<
" <th>" << i18nc(
"@title:column to-do due date",
"Due Date" ) <<
"</th>" << endl;
409 if ( d->mSettings->taskLocation() ) {
410 *ts <<
" <th>" << i18nc(
"@title:column to-do location",
"Location" ) <<
"</th>" << endl;
413 if ( d->mSettings->taskCategories() ) {
414 *ts <<
" <th>" << i18nc(
"@title:column to-do categories",
"Categories" ) <<
"</th>" << endl;
417 if ( d->mSettings->taskAttendees() ) {
418 *ts <<
" <th>" << i18nc(
"@title:column to-do attendees",
"Attendees" ) <<
"</th>" << endl;
421 *ts <<
" </tr>" << endl;
424 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
425 if ( !(*it)->relatedTo() ) {
426 createTodo( ts, *it );
431 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
433 if ( relations.count() ) {
435 *ts <<
" <tr>" << endl;
436 *ts <<
" <td class=\"subhead\" colspan=";
437 *ts <<
"\"" << QString::number(columns) <<
"\"";
438 *ts <<
"><a name=\"sub" << (*it)->uid() <<
"\"></a>"
439 << i18nc(
"@title:column sub-to-dos of the parent to-do",
440 "Sub-To-dos of: " ) <<
"<a href=\"#"
441 << (*it)->uid() <<
"\"><b>" << cleanChars( (*it)->summary() )
442 <<
"</b></a></td>" << endl;
443 *ts <<
" </tr>" << endl;
448 for (
int i = 1; i <= 9; ++i ) {
449 Incidence::List::ConstIterator it2;
450 for ( it2 = relations.constBegin(); it2 != relations.constEnd(); ++it2 ) {
451 Todo *ev3 =
dynamic_cast<Todo *
>( *it2 );
452 if ( ev3 && ev3->
priority() == i ) {
453 sortedList.append( ev3 );
457 Incidence::List::ConstIterator it2;
458 for ( it2 = relations.constBegin(); it2 != relations.constEnd(); ++it2 ) {
459 Todo *ev3 =
dynamic_cast<Todo *
>( *it2 );
460 if ( ev3 && ev3->
priority() == 0 ) {
461 sortedList.append( ev3 );
465 Todo::List::ConstIterator it3;
466 for ( it3 = sortedList.constBegin(); it3 != sortedList.constEnd(); ++it3 ) {
467 createTodo( ts, *it3 );
472 *ts <<
"</table>" << endl;
475 void HtmlExport::createTodo( QTextStream *ts,
Todo *todo )
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;
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",
514 *ts <<
" </td>" << endl;
516 if ( d->mSettings->taskDueDate() ) {
519 *ts <<
" class=\"done\"";
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(
Incidence *incidence )
584 int secrecy = incidence->
secrecy();
592 !d->mSettings->excludeConfidential() ) {
598 void HtmlExport::formatLocation( QTextStream *ts,
Incidence *incidence )
600 if ( !incidence->
location().isEmpty() ) {
601 *ts <<
" " << cleanChars( incidence->
location() ) << endl;
603 *ts <<
" " << endl;
607 void HtmlExport::formatCategories( QTextStream *ts,
Incidence *incidence )
610 *ts <<
" " << cleanChars( incidence->
categoriesStr() ) << endl;
612 *ts <<
" " << endl;
616 void HtmlExport::formatAttendees( QTextStream *ts,
Incidence *incidence )
619 if ( attendees.count() ) {
621 #if !defined(KORG_NOKABC) && !defined(KDEPIM_NO_KRESOURCES)
622 KABC::AddressBook *add_book = KABC::StdAddressBook::self(
true );
623 KABC::Addressee::List addressList;
624 addressList = add_book->findByEmail( incidence->
organizer().
email() );
625 if ( !addressList.isEmpty() ) {
626 KABC::Addressee o = addressList.first();
627 if ( !o.isEmpty() && addressList.size() < 2 ) {
628 *ts <<
"<a href=\"mailto:" << incidence->
organizer().
email() <<
"\">";
629 *ts << cleanChars( o.formattedName() ) <<
"</a>" << endl;
637 *ts <<
"</em><br />";
638 Attendee::List::ConstIterator it;
639 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
641 if ( !a->
email().isEmpty() ) {
642 *ts <<
"<a href=\"mailto:" << a->
email();
643 *ts <<
"\">" << cleanChars( a->
name() ) <<
"</a>";
645 *ts <<
" " << cleanChars( a->
name() );
647 *ts <<
"<br />" << endl;
650 *ts <<
" " << endl;
654 QString HtmlExport::breakString(
const QString &text )
656 int number = text.count(
"\n" );
661 QString tmpText = text;
664 for (
int i = 0; i <= number; ++i ) {
665 pos = tmpText.indexOf(
"\n" );
666 tmp = tmpText.left( pos );
667 tmpText = tmpText.right( tmpText.length() - pos - 1 );
668 out += tmp +
"<br />";
674 void HtmlExport::createFooter( QTextStream *ts )
677 QString trailer = i18nc(
"@info/plain",
"This page was created " );
683 if ( !d->mSettings->eMail().isEmpty() ) {
684 if ( !d->mSettings->name().isEmpty() ) {
685 trailer += i18nc(
"@info/plain page creator email link with name",
686 "by <link url='mailto:%1'>%2</link> ",
687 d->mSettings->eMail(), d->mSettings->name() );
689 trailer += i18nc(
"@info/plain page creator email link",
690 "by <link url='mailto:%1'>%2</link> ",
691 d->mSettings->eMail(), d->mSettings->eMail() );
694 if ( !d->mSettings->name().isEmpty() ) {
695 trailer += i18nc(
"@info/plain page creator name only",
696 "by %1 ", d->mSettings->name() );
699 if ( !d->mSettings->creditName().isEmpty() ) {
700 if ( !d->mSettings->creditURL().isEmpty() ) {
701 trailer += i18nc(
"@info/plain page credit with name and link",
702 "with <link url='%1'>%2</link>",
703 d->mSettings->creditURL(), d->mSettings->creditName() );
705 trailer += i18nc(
"@info/plain page credit name only",
706 "with %1", d->mSettings->creditName() );
709 *ts <<
"<p>" << trailer <<
"</p>" << endl;
712 QString cleanChars(
const QString &text )
715 txt = txt.replace(
'&',
"&" );
716 txt = txt.replace(
'<',
"<" );
717 txt = txt.replace(
'>',
">" );
718 txt = txt.replace(
'\"',
""" );
719 txt = txt.replace( QString::fromUtf8(
"ä" ),
"ä" );
720 txt = txt.replace( QString::fromUtf8(
"Ä" ),
"Ä" );
721 txt = txt.replace( QString::fromUtf8(
"ö" ),
"ö" );
722 txt = txt.replace( QString::fromUtf8(
"Ö" ),
"Ö" );
723 txt = txt.replace( QString::fromUtf8(
"ü" ),
"ü" );
724 txt = txt.replace( QString::fromUtf8(
"Ü" ),
"Ü" );
725 txt = txt.replace( QString::fromUtf8(
"ß" ),
"ß" );
726 txt = txt.replace( QString::fromUtf8(
"€" ),
"€" );
727 txt = txt.replace( QString::fromUtf8(
"é" ),
"é" );
732 QString HtmlExport::styleSheet()
const
734 if ( !d->mSettings->styleSheet().isEmpty() ) {
735 return d->mSettings->styleSheet();
740 if ( QApplication::isRightToLeft() ) {
741 css +=
" body { background-color:white; color:black; direction: rtl }\n";
742 css +=
" td { text-align:center; background-color:#eee }\n";
743 css +=
" th { text-align:center; background-color:#228; color:white }\n";
744 css +=
" td.sumdone { background-color:#ccc }\n";
745 css +=
" td.done { background-color:#ccc }\n";
746 css +=
" td.subhead { text-align:center; background-color:#ccf }\n";
747 css +=
" td.datehead { text-align:center; background-color:#ccf }\n";
748 css +=
" td.space { background-color:white }\n";
749 css +=
" td.dateholiday { color:red }\n";
751 css +=
" body { background-color:white; color:black }\n";
752 css +=
" td { text-align:center; background-color:#eee }\n";
753 css +=
" th { text-align:center; background-color:#228; color:white }\n";
754 css +=
" td.sum { text-align:left }\n";
755 css +=
" td.sumdone { text-align:left; background-color:#ccc }\n";
756 css +=
" td.done { background-color:#ccc }\n";
757 css +=
" td.subhead { text-align:center; background-color:#ccf }\n";
758 css +=
" td.datehead { text-align:center; background-color:#ccf }\n";
759 css +=
" td.space { background-color:white }\n";
760 css +=
" td.date { text-align:left }\n";
761 css +=
" td.dateholiday { text-align:left; color:red }\n";
767 void HtmlExport::addHoliday(
const QDate &date,
const QString &name )
769 if ( d->mHolidayMap[date].isEmpty() ) {
770 d->mHolidayMap[date] = name;
772 d->mHolidayMap[date] = i18nc(
"@info/plain holiday by date and name",
773 "%1, %2", d->mHolidayMap[date], name );
777 QDate HtmlExport::fromDate()
const
779 return d->mSettings->dateStart().date();
782 QDate HtmlExport::toDate()
const
784 return d->mSettings->dateEnd().date();