1/*
2 This file is part of the kcal library.
3
4 Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22
23#include "htmlexport.h"
24#include "htmlexportsettings.h"
25#include "incidenceformatter.h"
26#include "calendar.h"
27#include "event.h"
28#include "todo.h"
29#ifndef KORG_NOKABC
30 #include "kabc/stdaddressbook.h"
31#endif
32
33#include <kglobal.h>
34#include <klocalizedstring.h>
35#include <kdebug.h>
36#include <kcalendarsystem.h>
37
38#include <QtCore/QFile>
39#include <QtCore/QTextStream>
40#include <QtCore/QTextCodec>
41#include <QtCore/QRegExp>
42#include <QtCore/QMap>
43#include <QApplication>
44
45using namespace KCal;
46
47static QString cleanChars( const QString &txt );
48
49//@cond PRIVATE
50class KCal::HtmlExport::Private
51{
52 public:
53 Private( Calendar *calendar, HTMLExportSettings *settings )
54 : mCalendar( calendar ),
55 mSettings( settings )
56 {}
57
58 Calendar *mCalendar;
59 HTMLExportSettings *mSettings;
60 QMap<QDate,QString> mHolidayMap;
61};
62//@endcond
63
64HtmlExport::HtmlExport( Calendar *calendar, HTMLExportSettings *settings )
65 : d( new Private( calendar, settings ) )
66{
67}
68
69HtmlExport::~HtmlExport()
70{
71 delete d;
72}
73
74bool HtmlExport::save( const QString &fileName )
75{
76 QString fn( fileName );
77 if ( fn.isEmpty() && d->mSettings ) {
78 fn = d->mSettings->outputFile();
79 }
80 if ( !d->mSettings || fn.isEmpty() ) {
81 return false;
82 }
83 QFile f( fileName );
84 if ( !f.open( QIODevice::WriteOnly ) ) {
85 return false;
86 }
87 QTextStream ts( &f );
88 bool success = save( &ts );
89 f.close();
90 return success;
91}
92
93bool HtmlExport::save( QTextStream *ts )
94{
95 if ( !d->mSettings ) {
96 return false;
97 }
98 ts->setCodec( "UTF-8" );
99 // Write HTML header
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;
102
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;
108 }
109 *ts << " <style type=\"text/css\">" << endl;
110 *ts << styleSheet();
111 *ts << " </style>" << endl;
112 *ts << "</head><body>" << endl;
113
114 // FIXME: Write header
115 // (Heading, Calendar-Owner, Calendar-Date, ...)
116
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;
120 }
121
122 // Write Week View
123 if ( d->mSettings->weekView() ) {
124 createWeekView( ts );
125 }
126 // Write Month View
127 if ( d->mSettings->monthView() ) {
128 createMonthView( ts );
129 }
130 // Write Event List
131 if ( d->mSettings->eventView() ) {
132 createEventList( ts );
133 }
134 }
135
136 // Write Todo List
137 if ( d->mSettings->todoView() ) {
138 if ( !d->mSettings->todoListTitle().isEmpty() ) {
139 *ts << "<h1>" << d->mSettings->todoListTitle() << "</h1>" << endl;
140 }
141 createTodoList( ts );
142 }
143
144 // Write Journals
145 if ( d->mSettings->journalView() ) {
146 if ( !d->mSettings->journalTitle().isEmpty() ) {
147 *ts << "<h1>" << d->mSettings->journalTitle() << "</h1>" << endl;
148 }
149 createJournalView( ts );
150 }
151
152 // Write Free/Busy
153 if ( d->mSettings->freeBusyView() ) {
154 if ( !d->mSettings->freeBusyTitle().isEmpty() ) {
155 *ts << "<h1>" << d->mSettings->freeBusyTitle() << "</h1>" << endl;
156 }
157 createFreeBusyView( ts );
158 }
159
160 createFooter( ts );
161
162 // Write HTML trailer
163 *ts << "</body></html>" << endl;
164
165 return true;
166}
167
168void HtmlExport::createMonthView( QTextStream *ts )
169{
170 QDate start = fromDate();
171 start.setYMD( start.year(), start.month(), 1 ); // go back to first day in month
172
173 QDate end( start.year(), start.month(), start.daysInMonth() );
174
175 int startmonth = start.month();
176 int startyear = start.year();
177
178 while ( start < toDate() ) {
179 // Write header
180 QDate hDate( start.year(), start.month(), 1 );
181 QString hMon = hDate.toString( "MMMM" );
182 QString hYear = hDate.toString( "yyyy" );
183 *ts << "<h2>"
184 << i18nc( "@title month and year", "%1 %2", hMon, hYear )
185 << "</h2>" << endl;
186 if ( KGlobal::locale()->weekStartDay() == 1 ) {
187 start = start.addDays( 1 - start.dayOfWeek() );
188 } else {
189 if ( start.dayOfWeek() != 7 ) {
190 start = start.addDays( -start.dayOfWeek() );
191 }
192 }
193 *ts << "<table border=\"1\">" << endl;
194
195 // Write table header
196 *ts << " <tr>";
197 for ( int i=0; i < 7; ++i ) {
198 *ts << "<th>" << KGlobal::locale()->calendar()->weekDayName( start.addDays(i) ) << "</th>";
199 }
200 *ts << "</tr>" << endl;
201
202 // Write days
203 while ( start <= end ) {
204 *ts << " <tr>" << endl;
205 for ( int i=0; i < 7; ++i ) {
206 *ts << " <td valign=\"top\"><table border=\"0\">";
207
208 *ts << "<tr><td ";
209 if ( d->mHolidayMap.contains( start ) || start.dayOfWeek() == 7 ) {
210 *ts << "class=\"dateholiday\"";
211 } else {
212 *ts << "class=\"date\"";
213 }
214 *ts << ">" << QString::number( start.day() );
215
216 if ( d->mHolidayMap.contains( start ) ) {
217 *ts << " <em>" << d->mHolidayMap[start] << "</em>";
218 }
219
220 *ts << "</td></tr><tr><td valign=\"top\">";
221
222 // Only print events within the from-to range
223 if ( start >= fromDate() && start <= toDate() ) {
224 Event::List events = d->mCalendar->events( start, d->mCalendar->timeSpec(),
225 EventSortStartDate,
226 SortDirectionAscending );
227 if ( events.count() ) {
228 *ts << "<table>";
229 Event::List::ConstIterator it;
230 for ( it = events.constBegin(); it != events.constEnd(); ++it ) {
231 if ( checkSecrecy( *it ) ) {
232 createEvent( ts, *it, start, false );
233 }
234 }
235 *ts << "</table>";
236 } else {
237 *ts << "&nbsp;";
238 }
239 }
240
241 *ts << "</td></tr></table></td>" << endl;
242 start = start.addDays( 1 );
243 }
244 *ts << " </tr>" << endl;
245 }
246 *ts << "</table>" << endl;
247 startmonth += 1;
248 if ( startmonth > 12 ) {
249 startyear += 1;
250 startmonth = 1;
251 }
252 start.setYMD( startyear, startmonth, 1 );
253 end.setYMD( start.year(), start.month(), start.daysInMonth() );
254 }
255}
256
257void HtmlExport::createEventList( QTextStream *ts )
258{
259 int columns = 3;
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;
271 ++columns;
272 }
273 if ( d->mSettings->eventCategories() ) {
274 *ts << " <th>" << i18nc( "@title:column event categories",
275 "Categories" ) << "</th>" << endl;
276 ++columns;
277 }
278 if ( d->mSettings->eventAttendees() ) {
279 *ts << " <th>" << i18nc( "@title:column event attendees",
280 "Attendees" ) << "</th>" << endl;
281 ++columns;
282 }
283
284 *ts << " </tr>" << endl;
285
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(),
289 EventSortStartDate,
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;
296
297 Event::List::ConstIterator it;
298 for ( it = events.constBegin(); it != events.constEnd(); ++it ) {
299 if ( checkSecrecy( *it ) ) {
300 createEvent( ts, *it, dt );
301 }
302 }
303 }
304 }
305
306 *ts << "</table>" << endl;
307}
308
309void HtmlExport::createEvent ( QTextStream *ts, Event *event,
310 QDate date, bool withDescription )
311{
312 kDebug() << event->summary();
313 *ts << " <tr>" << endl;
314
315 if ( !event->allDay() ) {
316 if ( event->isMultiDay( d->mCalendar->timeSpec() ) && ( event->dtStart().date() != date ) ) {
317 *ts << " <td>&nbsp;</td>" << endl;
318 } else {
319 *ts << " <td valign=\"top\">"
320 << IncidenceFormatter::timeToString( event->dtStart(), true, d->mCalendar->timeSpec() )
321 << "</td>" << endl;
322 }
323 if ( event->isMultiDay( d->mCalendar->timeSpec() ) && ( event->dtEnd().date() != date ) ) {
324 *ts << " <td>&nbsp;</td>" << endl;
325 } else {
326 *ts << " <td valign=\"top\">"
327 << IncidenceFormatter::timeToString( event->dtEnd(), true, d->mCalendar->timeSpec() )
328 << "</td>" << endl;
329 }
330 } else {
331 *ts << " <td>&nbsp;</td><td>&nbsp;</td>" << endl;
332 }
333
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;
338 }
339 *ts << " </td>" << endl;
340
341 if ( d->mSettings->eventLocation() ) {
342 *ts << " <td>" << endl;
343 formatLocation( ts, event );
344 *ts << " </td>" << endl;
345 }
346
347 if ( d->mSettings->eventCategories() ) {
348 *ts << " <td>" << endl;
349 formatCategories( ts, event );
350 *ts << " </td>" << endl;
351 }
352
353 if ( d->mSettings->eventAttendees() ) {
354 *ts << " <td>" << endl;
355 formatAttendees( ts, event );
356 *ts << " </td>" << endl;
357 }
358
359 *ts << " </tr>" << endl;
360}
361
362void HtmlExport::createTodoList ( QTextStream *ts )
363{
364 Todo::List rawTodoList = d->mCalendar->todos();
365
366 int index = 0;
367 while ( index < rawTodoList.count() ) {
368 Todo *ev = rawTodoList[ index ];
369 Todo *subev = ev;
370 if ( ev->relatedTo() ) {
371 if ( ev->relatedTo()->type() == "Todo" ) {
372 if ( !rawTodoList.contains( static_cast<Todo *>( ev->relatedTo() ) ) ) {
373 rawTodoList.append( static_cast<Todo *>( ev->relatedTo() ) );
374 }
375 }
376 }
377 index = rawTodoList.indexOf( subev );
378 ++index;
379 }
380
381 // FIXME: Sort list by priorities. This is brute force and should be
382 // replaced by a real sorting algorithm.
383 Todo::List todoList;
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 );
389 }
390 }
391 }
392 for ( it = rawTodoList.constBegin(); it != rawTodoList.constEnd(); ++it ) {
393 if ( (*it)->priority() == 0 && checkSecrecy( *it ) ) {
394 todoList.append( *it );
395 }
396 }
397
398 int columns = 3;
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;
407 ++columns;
408 }
409 if ( d->mSettings->taskLocation() ) {
410 *ts << " <th>" << i18nc( "@title:column to-do location", "Location" ) << "</th>" << endl;
411 ++columns;
412 }
413 if ( d->mSettings->taskCategories() ) {
414 *ts << " <th>" << i18nc( "@title:column to-do categories", "Categories" ) << "</th>" << endl;
415 ++columns;
416 }
417 if ( d->mSettings->taskAttendees() ) {
418 *ts << " <th>" << i18nc( "@title:column to-do attendees", "Attendees" ) << "</th>" << endl;
419 ++columns;
420 }
421 *ts << " </tr>" << endl;
422
423 // Create top-level list.
424 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
425 if ( !(*it)->relatedTo() ) {
426 createTodo( ts, *it );
427 }
428 }
429
430 // Create sub-level lists
431 for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
432 Incidence::List relations = (*it)->relations();
433 if ( relations.count() ) {
434 // Generate sub-to-do list
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;
444
445 Todo::List sortedList;
446 // FIXME: Sort list by priorities. This is brute force and should be
447 // replaced by a real sorting algorithm.
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 );
454 }
455 }
456 }
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 );
462 }
463 }
464
465 Todo::List::ConstIterator it3;
466 for ( it3 = sortedList.constBegin(); it3 != sortedList.constEnd(); ++it3 ) {
467 createTodo( ts, *it3 );
468 }
469 }
470 }
471
472 *ts << "</table>" << endl;
473}
474
475void HtmlExport::createTodo( QTextStream *ts, Todo *todo )
476{
477 kDebug();
478
479 bool completed = todo->isCompleted();
480 Incidence::List relations = todo->relations();
481
482 *ts << "<tr>" << endl;
483
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;
491 }
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;
496 }
497 *ts << " </td>" << endl;
498
499 *ts << " <td";
500 if ( completed ) {
501 *ts << " class=\"done\"";
502 }
503 *ts << ">" << endl;
504 *ts << " " << todo->priority() << endl;
505 *ts << " </td>" << endl;
506
507 *ts << " <td";
508 if ( completed ) {
509 *ts << " class=\"done\"";
510 }
511 *ts << ">" << endl;
512 *ts << " " << i18nc( "@info/plain to-do percent complete",
513 "%1 %", todo->percentComplete() ) << endl;
514 *ts << " </td>" << endl;
515
516 if ( d->mSettings->taskDueDate() ) {
517 *ts << " <td";
518 if ( completed ) {
519 *ts << " class=\"done\"";
520 }
521 *ts << ">" << endl;
522 if ( todo->hasDueDate() ) {
523 *ts << " " << IncidenceFormatter::dateToString( todo->dtDue( true ) ) << endl;
524 } else {
525 *ts << " &nbsp;" << endl;
526 }
527 *ts << " </td>" << endl;
528 }
529
530 if ( d->mSettings->taskLocation() ) {
531 *ts << " <td";
532 if ( completed ) {
533 *ts << " class=\"done\"";
534 }
535 *ts << ">" << endl;
536 formatLocation( ts, todo );
537 *ts << " </td>" << endl;
538 }
539
540 if ( d->mSettings->taskCategories() ) {
541 *ts << " <td";
542 if ( completed ) {
543 *ts << " class=\"done\"";
544 }
545 *ts << ">" << endl;
546 formatCategories( ts, todo );
547 *ts << " </td>" << endl;
548 }
549
550 if ( d->mSettings->taskAttendees() ) {
551 *ts << " <td";
552 if ( completed ) {
553 *ts << " class=\"done\"";
554 }
555 *ts << ">" << endl;
556 formatAttendees( ts, todo );
557 *ts << " </td>" << endl;
558 }
559
560 *ts << "</tr>" << endl;
561}
562
563void HtmlExport::createWeekView( QTextStream *ts )
564{
565 Q_UNUSED( ts );
566 // FIXME: Implement this!
567}
568
569void HtmlExport::createJournalView( QTextStream *ts )
570{
571 Q_UNUSED( ts );
572// Journal::List rawJournalList = d->mCalendar->journals();
573 // FIXME: Implement this!
574}
575
576void HtmlExport::createFreeBusyView( QTextStream *ts )
577{
578 Q_UNUSED( ts );
579 // FIXME: Implement this!
580}
581
582bool HtmlExport::checkSecrecy( Incidence *incidence )
583{
584 int secrecy = incidence->secrecy();
585 if ( secrecy == Incidence::SecrecyPublic ) {
586 return true;
587 }
588 if ( secrecy == Incidence::SecrecyPrivate && !d->mSettings->excludePrivate() ) {
589 return true;
590 }
591 if ( secrecy == Incidence::SecrecyConfidential &&
592 !d->mSettings->excludeConfidential() ) {
593 return true;
594 }
595 return false;
596}
597
598void HtmlExport::formatLocation( QTextStream *ts, Incidence *incidence )
599{
600 if ( !incidence->location().isEmpty() ) {
601 *ts << " " << cleanChars( incidence->location() ) << endl;
602 } else {
603 *ts << " &nbsp;" << endl;
604 }
605}
606
607void HtmlExport::formatCategories( QTextStream *ts, Incidence *incidence )
608{
609 if ( !incidence->categoriesStr().isEmpty() ) {
610 *ts << " " << cleanChars( incidence->categoriesStr() ) << endl;
611 } else {
612 *ts << " &nbsp;" << endl;
613 }
614}
615
616void HtmlExport::formatAttendees( QTextStream *ts, Incidence *incidence )
617{
618 Attendee::List attendees = incidence->attendees();
619 if ( attendees.count() ) {
620 *ts << "<em>";
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;
630 } else {
631 *ts << incidence->organizer().fullName();
632 }
633 }
634#else
635 *ts << incidence->organizer().fullName();
636#endif
637 *ts << "</em><br />";
638 Attendee::List::ConstIterator it;
639 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
640 Attendee *a = *it;
641 if ( !a->email().isEmpty() ) {
642 *ts << "<a href=\"mailto:" << a->email();
643 *ts << "\">" << cleanChars( a->name() ) << "</a>";
644 } else {
645 *ts << " " << cleanChars( a->name() );
646 }
647 *ts << "<br />" << endl;
648 }
649 } else {
650 *ts << " &nbsp;" << endl;
651 }
652}
653
654QString HtmlExport::breakString( const QString &text )
655{
656 int number = text.count( "\n" );
657 if ( number <= 0 ) {
658 return text;
659 } else {
660 QString out;
661 QString tmpText = text;
662 int pos = 0;
663 QString tmp;
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 />";
669 }
670 return out;
671 }
672}
673
674void HtmlExport::createFooter( QTextStream *ts )
675{
676 // FIXME: Implement this in a translatable way!
677 QString trailer = i18nc( "@info/plain", "This page was created " );
678
679/* bool hasPerson = false;
680 bool hasCredit = false;
681 bool hasCreditURL = false;
682 QString mail, name, credit, creditURL;*/
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() );
688 } else {
689 trailer += i18nc( "@info/plain page creator email link",
690 "by <link url='mailto:%1'>%2</link> ",
691 d->mSettings->eMail(), d->mSettings->eMail() );
692 }
693 } else {
694 if ( !d->mSettings->name().isEmpty() ) {
695 trailer += i18nc( "@info/plain page creator name only",
696 "by %1 ", d->mSettings->name() );
697 }
698 }
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() );
704 } else {
705 trailer += i18nc( "@info/plain page credit name only",
706 "with %1", d->mSettings->creditName() );
707 }
708 }
709 *ts << "<p>" << trailer << "</p>" << endl;
710}
711
712QString cleanChars( const QString &text )
713{
714 QString txt = text;
715 txt = txt.replace( '&', "&amp;" );
716 txt = txt.replace( '<', "&lt;" );
717 txt = txt.replace( '>', "&gt;" );
718 txt = txt.replace( '\"', "&quot;" );
719 txt = txt.replace( QString::fromUtf8( "ä" ), "&auml;" );
720 txt = txt.replace( QString::fromUtf8( "Ä" ), "&Auml;" );
721 txt = txt.replace( QString::fromUtf8( "ö" ), "&ouml;" );
722 txt = txt.replace( QString::fromUtf8( "Ö" ), "&Ouml;" );
723 txt = txt.replace( QString::fromUtf8( "ü" ), "&uuml;" );
724 txt = txt.replace( QString::fromUtf8( "Ü" ), "&Uuml;" );
725 txt = txt.replace( QString::fromUtf8( "ß" ), "&szlig;" );
726 txt = txt.replace( QString::fromUtf8( "€" ), "&euro;" );
727 txt = txt.replace( QString::fromUtf8( "é" ), "&eacute;" );
728
729 return txt;
730}
731
732QString HtmlExport::styleSheet() const
733{
734 if ( !d->mSettings->styleSheet().isEmpty() ) {
735 return d->mSettings->styleSheet();
736 }
737
738 QString css;
739
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";
750 } else {
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";
762 }
763
764 return css;
765}
766
767void HtmlExport::addHoliday( const QDate &date, const QString &name )
768{
769 if ( d->mHolidayMap[date].isEmpty() ) {
770 d->mHolidayMap[date] = name;
771 } else {
772 d->mHolidayMap[date] = i18nc( "@info/plain holiday by date and name",
773 "%1, %2", d->mHolidayMap[date], name );
774 }
775}
776
777QDate HtmlExport::fromDate() const
778{
779 return d->mSettings->dateStart().date();
780}
781
782QDate HtmlExport::toDate() const
783{
784 return d->mSettings->dateEnd().date();
785}
786