23#include "htmlexport.h"
24#include "htmlexportsettings.h"
30 #include "kabc/stdaddressbook.h"
34#include <klocalizedstring.h>
36#include <kcalendarsystem.h>
38#include <QtCore/QFile>
39#include <QtCore/QTextStream>
40#include <QtCore/QTextCodec>
41#include <QtCore/QRegExp>
43#include <QApplication>
47static QString cleanChars(
const QString &txt );
50class 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 ) )
69HtmlExport::~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;
168void 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(),
226 SortDirectionAscending );
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() );
257void 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(),
290 SortDirectionAscending );
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;
309void 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;
362void 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;
475void 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;
563void HtmlExport::createWeekView( QTextStream *ts )
569void HtmlExport::createJournalView( QTextStream *ts )
576void HtmlExport::createFreeBusyView( QTextStream *ts )
582bool HtmlExport::checkSecrecy(
Incidence *incidence )
584 int secrecy = incidence->
secrecy();
592 !d->mSettings->excludeConfidential() ) {
598void HtmlExport::formatLocation( QTextStream *ts,
Incidence *incidence )
600 if ( !incidence->
location().isEmpty() ) {
601 *ts <<
" " << cleanChars( incidence->
location() ) << endl;
603 *ts <<
" " << endl;
607void HtmlExport::formatCategories( QTextStream *ts,
Incidence *incidence )
610 *ts <<
" " << cleanChars( incidence->
categoriesStr() ) << endl;
612 *ts <<
" " << endl;
616void 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;
654QString 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 />";
674void 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;
712QString 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(
"é" ),
"é" );
732QString 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";
767void 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 );
777QDate HtmlExport::fromDate()
const
779 return d->mSettings->dateStart().date();
782QDate HtmlExport::toDate()
const
784 return d->mSettings->dateEnd().date();
This file is part of the API for handling calendar data and defines the Calendar class.
Represents information related to an attendee of an Calendar Incidence, typically a meeting or task (...
QString email() const
Returns the email address for this person.
QString name() const
Returns the person name string.
Represents the main calendar class.
This class provides an Event in the sense of RFC2445.
bool isMultiDay(const KDateTime::Spec &spec=KDateTime::Spec()) const
Returns true if the event spans multiple days, otherwise return false.
virtual KDateTime dtEnd() const
Returns the event end date and time.
bool save(const QString &fileName=QString())
Writes out the calendar in HTML format.
HtmlExport(Calendar *calendar, HTMLExportSettings *settings)
Create new HTML exporter for calendar.
bool allDay() const
Returns true or false depending on whether the incidence is all-day.
virtual QByteArray type() const =0
Prints the type of Incidence as a string.
virtual KDateTime dtStart() const
Returns an incidence's starting date/time as a KDateTime.
const Attendee::List & attendees() const
Returns a list of incidence attendees.
Person organizer() const
Returns the Person associated with this incidence.
QString uid() const
Returns the unique id (uid) for the incidence.
Provides the abstract base class common to non-FreeBusy (Events, To-dos, Journals) calendar component...
QString categoriesStr() const
Returns the incidence categories as a comma separated string.
Incidence * relatedTo() const
Returns a pointer for the incidence that is related to this one.
QString description() const
Returns the incidence description.
int priority() const
Returns the incidence priority.
@ SecrecyPrivate
Secret to the owner.
@ SecrecyConfidential
Secret to the owner and some others.
@ SecrecyPublic
Not secret (default)
Incidence::List relations() const
Returns a list of all incidences related to this one.
QString summary() const
Returns the incidence summary.
Secrecy secrecy() const
Returns the incidence Secrecy.
QString location() const
Returns the incidence location.
This class provides a template for lists of pointers.
QString email() const
Returns the email address for this person.
QString fullName() const
Returns the full name of this person.
Provides a To-do in the sense of RFC2445.
KDateTime dtDue(bool first=false) const
Returns due date and time.
bool hasDueDate() const
Returns true if the todo has a due date, otherwise return false.
bool isCompleted() const
Returns true if the todo is 100% completed, otherwise return false.
int percentComplete() const
Returns what percentage of the to-do is completed.
This file is part of the API for handling calendar data and defines the Event class.
This file is part of the API for handling calendar data and defines the Todo class.