OSDN Git Service

- Partial fix for timezone handling.
[tjqt4port/tj2qt4.git] / taskjuggler / ProjectFile.cpp
1 /*
2  * ProjectFile.cpp - TaskJuggler
3  *
4  * Copyright (c) 2001, 2002 by Chris Schlaeger <cs@suse.de>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of version 2 of the GNU General Public License as
8  * published by the Free Software Foundation.
9  *
10  * $Id$
11  */
12
13 #include <ctype.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <stream.h>
17 #include <unistd.h>
18
19 #include <qregexp.h>
20
21 #include "ProjectFile.h"
22 #include "Project.h"
23 #include "Token.h"
24 #include "ExpressionTree.h"
25 #include "kotrus.h"
26
27 // Dummy marco to mark all keywords of taskjuggler syntax
28 #define KW(a) a
29
30 #define READ_DATE(a, b) \
31 (token == a) \
32 { \
33         if ((tt = nextToken(token)) == DATE) \
34                 task->b(date2time(token)); \
35         else \
36         { \
37                 fatalError("Date expected"); \
38                 return FALSE; \
39         } \
40 }
41
42 FileInfo::FileInfo(ProjectFile* p, const QString& file_)
43         : pf(p)
44 {
45         tokenTypeBuf = INVALID;
46         file = file_;
47 }
48
49 bool
50 FileInfo::open()
51 {
52         if ((f = fopen(file, "r")) == 0)
53                 return FALSE;
54
55         lineBuf = "";
56         currLine = 1;
57         return TRUE;
58 }
59
60 bool
61 FileInfo::close()
62 {
63         if (fclose(f) == EOF)
64                 return FALSE;
65
66         return TRUE;
67 }
68
69 int
70 FileInfo::getC(bool expandMacros)
71 {
72  BEGIN:
73         int c;
74         if (ungetBuf.isEmpty())
75         {
76                 c = getc(f);
77         }
78         else
79         {
80                 c = ungetBuf.last();
81                 ungetBuf.remove(ungetBuf.fromLast());
82                 if (c == EOM)
83                 {
84                         macroStack.removeLast();
85                         pf->getMacros().popArguments();
86                         goto BEGIN;
87                 }
88         }
89         lineBuf += c;
90
91         if (expandMacros)
92         {
93                 if (c == '$')
94                 {
95                         int d;
96                         if ((d = getC()) == '{')
97                         {
98                                 // remove $ from lineBuf;
99                                 lineBuf = lineBuf.left(lineBuf.length() - 1);
100                                 readMacroCall();
101                                 goto BEGIN;
102                         }
103                         else
104                                 ungetC(d);
105                 }
106         }
107
108         return c;
109 }
110
111 void
112 FileInfo::ungetC(int c)
113 {
114         lineBuf = lineBuf.left(lineBuf.length() - 1);
115         ungetBuf.append(c);
116 }
117
118 bool
119 FileInfo::getDateFragment(QString& token, int& c)
120 {
121         token += c;
122         c = getC();
123         // c must be a digit
124         if (!isdigit(c))
125         {
126                 fatalError("Corrupted date");
127                 return FALSE;
128         }
129         token += c;
130         // read other digits
131         while ((c = getC()) != EOF && isdigit(c))
132                 token += c;
133
134         return TRUE;
135 }
136
137 QString
138 FileInfo::getPath() const
139 {
140         if (file.find('/') >= 0)
141                 return file.left(file.findRev('/') + 1);
142         else
143                 return "";
144 }
145
146 TokenType
147 FileInfo::nextToken(QString& token)
148 {
149         if (tokenTypeBuf != INVALID)
150         {
151                 token = tokenBuf;
152                 TokenType tt = tokenTypeBuf;
153                 tokenTypeBuf = INVALID;
154                 return tt;
155         }
156
157         token = "";
158
159         // skip blanks and comments
160         for ( ; ; )
161         {
162                 int c = getC();
163                 switch (c)
164                 {
165                 case EOF:
166                         return EndOfFile;
167                 case ' ':
168                 case '\t':
169                         break;
170                 case '/':
171                         /* This code skips c-style comments like the one you are just
172                          * reading. */
173                         if ((c = getC(FALSE)) == '*')
174                         {
175                                 do
176                                 {
177                                         while ((c = getC(FALSE)) != '*')
178                                         {
179                                                 if (c == '\n')
180                                                         currLine++;
181                                                 else if (c == EOF)
182                                                 {
183                                                         fatalError("Unterminated comment");
184                                                         return EndOfFile;
185                                                 }
186                                         }
187                                 } while ((c = getC(FALSE)) != '/');
188                         }
189                         else
190                         {
191                                 ungetC(c);
192                                 ungetC('/');
193                                 goto BLANKS_DONE;
194                         }
195                         break;
196                 case '#':       // Comments start with '#' and reach towards end of line
197                         while ((c = getC(FALSE)) != '\n' && c != EOF)
198                                 ;
199                         if (c == EOF)
200                                 return EndOfFile;
201                         // break missing on purpose
202                 case '\n':
203                         // Increase line counter only when not replaying a macro.
204                         if (macroStack.isEmpty())
205                                 currLine++;
206                         lineBuf = "";
207                         break;
208                 default:
209                         ungetC(c);
210                         goto BLANKS_DONE;
211                 }
212         }
213  BLANKS_DONE:
214
215         // analyse non blank characters
216         for ( ; ; )
217         {
218                 int c = getC();
219                 if (c == EOF)
220                 {
221                         fatalError("Unexpected end of file");
222                         return EndOfFile;
223                 }
224                 else if (isalpha(c) || (c == '_') || (c == '!'))
225                 {
226                         token += c;
227                         while ((c = getC()) != EOF &&
228                                    (isalnum(c) || (c == '_') || (c == '.') || (c == '!')))
229                                 token += c;
230                         ungetC(c);
231                         if (token.contains('!') || token.contains('.'))
232                                 return RELATIVE_ID;
233                         else
234                                 return ID;
235                 }
236                 else if (isdigit(c))
237                 {
238                         // read first number (maybe a year)
239                         token += c;
240                         while ((c = getC()) != EOF && isdigit(c))
241                                 token += c;
242                         if (c == '-')
243                         {
244                                 // this must be a ISO date yyyy-mm-dd[[-hh:mm:[ss]]-TZ]
245                                 getDateFragment(token, c);
246                                 if (c != '-')
247                                 {
248                                         fatalError("Corrupted date");
249                                         return EndOfFile;
250                                 }
251                                 getDateFragment(token, c);
252                                 if (c == '-')
253                                 {
254                                         getDateFragment(token, c);
255                                         if (c != ':')
256                                         {
257                                                 fatalError("Corrupted date");
258                                                 return EndOfFile;
259                                         }
260                                         getDateFragment(token, c);
261                                         if (c == ':')
262                                                 getDateFragment(token, c);
263                                 }
264                                 int i = 0;
265                                 if (c == '-')
266                                 {
267                                         token += c;
268                                         while ((c = getC()) != EOF && isalnum(c) && i++ < 12)
269                                                 token += c;
270                                 }
271                                 ungetC(c);
272                                 return DATE;
273                         }
274                         else if (c == '.')
275                         {
276                                 // must be a real number
277                                 token += c;
278                                 while ((c = getC()) != EOF && isdigit(c))
279                                         token += c;
280                                 ungetC(c);
281                                 return REAL;
282                         }
283                         else if (c == ':')
284                         {
285                                 // must be a time (HH:MM)
286                                 token += c;
287                                 for (int i = 0; i < 2; i++)
288                                 {
289                                         if ((c = getC()) != EOF && isdigit(c))
290                                                 token += c;
291                                         else
292                                         {
293                                                 fatalError("2 digits minutes expected");
294                                                 return EndOfFile;
295                                         }
296                                 }
297                                 return HOUR;
298                         }
299                         else
300                         {
301                                 ungetC(c);
302                                 return INTEGER;
303                         }
304                 }
305                 else if (c == '"')
306                 {
307                         // quoted string
308                         while ((c = getC()) != EOF && c != '"')
309                         {
310                                 if (c == '\n')
311                                         currLine++;
312                                 token += c;
313                         }
314                         if (c == EOF)
315                         {
316                                 fatalError("Non terminated string");
317                                 return EndOfFile;
318                         }
319                         return STRING;
320                 }
321                 else if (c == '[')
322                 {
323                         int nesting = 0;
324                         while ((c = getC(FALSE)) != EOF && (c != ']' || nesting > 0))
325                         {
326                                 if (c == '[')
327                                         nesting++;
328                                 else if (c == ']')
329                                         nesting--;
330                                 if (c == '\n')
331                                         currLine++;
332                                 token += c;
333                         }
334                         if (c == EOF)
335                         {
336                                 fatalError("Non terminated macro definition");
337                                 return EndOfFile;
338                         }
339                         return MacroBody;
340                 }
341                 else
342                 {
343                         token += c;
344                         switch (c)
345                         {
346                         case '{':
347                                 return LCBRACE;
348                         case '}':
349                                 return RCBRACE;
350                         case '(':
351                                 return LBRACE;
352                         case ')':
353                                 return RBRACE;
354                         case ',':
355                                 return COMMA;
356                         case '~':
357                                 return TILDE;
358                         case '-':
359                                 return MINUS;
360                         case '&':
361                                 return AND;
362                         case '|':
363                                 return OR;
364                         default:
365                                 fatalError(QString("Illegal character '") + c + "'");
366                                 return EndOfFile;
367                         }
368                 }
369         }
370 }
371
372 bool
373 FileInfo::readMacroCall()
374 {
375         QString id;
376         TokenType tt;
377         if ((tt = nextToken(id)) != ID && tt != INTEGER)
378         {
379                 fatalError("Macro ID expected");
380                 return FALSE;
381         }
382         QString token;
383         // Store all arguments in a newly created string list.
384         QStringList* sl = new QStringList;
385         while ((tt = nextToken(token)) == STRING)
386                 sl->append(token);
387         if (tt != RCBRACE)
388         {
389                 fatalError("'}' expected");
390                 return FALSE;
391         }
392
393         // push string list to global argument stack
394         pf->getMacros().pushArguments(sl);
395
396         // expand the macro
397         QString macro = pf->getMacros().expand(id);
398         if (macro.isNull())
399         {
400                 fatalError(QString("Unknown macro ") + id);
401                 return FALSE;
402         }
403
404         // Push pointer to macro on stack. Needed for error handling.
405         macroStack.append(pf->getMacros().getMacro(id));
406
407         // mark end of macro
408         ungetC(EOM);
409         // push expanded macro reverse into ungetC buffer.
410         for (int i = macro.length() - 1; i >= 0; --i)
411                 ungetC(macro[i].latin1());
412         return TRUE;
413 }
414
415 void
416 FileInfo::returnToken(TokenType tt, const QString& buf)
417 {
418         if (tokenTypeBuf != INVALID)
419         {
420                 qFatal("Internal Error: Token buffer overflow!");
421                 return;
422         }
423         tokenTypeBuf = tt;
424         tokenBuf = buf;
425 }
426
427 void
428 FileInfo::fatalError(const QString& msg)
429 {
430         if (macroStack.isEmpty())
431         {
432                 qWarning("%s:%d:%s", file.latin1(), currLine, msg.latin1());
433                 qWarning("%s", lineBuf.latin1());
434         }
435         else
436         {
437                 qWarning("Error in expanded macro");
438                 qWarning("%s:%d: %s",
439                                  macroStack.last()->getFile().latin1(),
440                                  macroStack.last()->getLine(), msg.latin1());
441                 qWarning("%s", lineBuf.latin1());
442         }
443 }
444
445 ProjectFile::ProjectFile(Project* p)
446 {
447         proj = p;
448
449         openFiles.setAutoDelete(TRUE);
450 }
451
452 bool
453 ProjectFile::open(const QString& file)
454 {
455         QString absFileName = file;
456         if (absFileName[0] != '/')
457         {
458                 if (openFiles.isEmpty())
459                 {
460                         char buf[1024];
461                         if (getcwd(buf, 1023) != 0)
462                                 absFileName = QString(buf) + "/" + absFileName;
463                         else
464                                 qFatal("ProjectFile::open(): getcwd failed");
465                 }
466                 else
467                         absFileName = openFiles.last()->getPath() + absFileName;
468                 if (debugLevel > 2)
469                         qWarning("Expanded filename to %s", absFileName.latin1());
470         }
471         int end = 0;
472         while (absFileName.find("/../", end) >= 0)
473         {
474                 end = absFileName.find("/../");
475                 int start = absFileName.findRev('/', end - 1);
476                 if (start < 0)
477                         start = 0;
478                 else
479                         start++;        // move after '/'
480                 if (start < end && absFileName.mid(start, end - start) != "..")
481                         absFileName.remove(start, end + strlen("/../") - start);
482                 end += strlen("/..");
483         }
484
485         // Make sure that we include each file only once.
486         if (includedFiles.findIndex(absFileName) != -1)
487         {
488                 if (debugLevel > 2)
489                         qWarning("Ignoring already read file %s",
490                                          absFileName.latin1());
491                 return TRUE;
492         }
493                 
494         FileInfo* fi = new FileInfo(this, absFileName);
495
496         if (!fi->open())
497         {
498                 qFatal("Cannot open '%s'", absFileName.latin1());
499                 return FALSE;
500         }
501
502         if (debugLevel > 2)
503                 qWarning("Reading %s", absFileName.latin1());
504
505         openFiles.append(fi);
506         includedFiles.append(absFileName);
507         return TRUE;
508 }
509
510 bool
511 ProjectFile::close()
512 {
513         bool error = FALSE;
514
515         FileInfo* fi = openFiles.getLast();
516
517         if (!fi->close())
518                 error = TRUE;
519         openFiles.removeLast();
520
521         return error;
522 }
523
524 bool
525 ProjectFile::parse()
526 {
527         TokenType tt;
528         QString token;
529
530         for ( ; ; )
531         {
532                 switch (tt = nextToken(token))
533                 {
534                 case EndOfFile:
535                         return TRUE;
536                 case ID:
537                         if (token == KW("task"))
538                         {
539                                 if (!readTask(0))
540                                         return FALSE;
541                                 break;
542                         }
543                         if (token == KW("account"))
544                         {
545                                 if (!readAccount(0))
546                                         return FALSE;
547                                 break;
548                         }
549                         else if (token == KW("resource"))
550                         {
551                                 if (!readResource(0))
552                                         return FALSE;
553                                 break;
554                         }
555                         else if (token == KW("shift"))
556                         {
557                                 if (!readShift(0))
558                                         return FALSE;
559                                 break;  
560                         }
561                         else if (token == KW("vacation"))
562                         {
563                                 time_t from, to;
564                                 QString name;
565                                 if (!readVacation(from, to, TRUE, &name))
566                                         return FALSE;
567                                 proj->addVacation(name, from, to);
568                                 break;
569                         }
570                         else if (token == KW("priority"))
571                         {
572                                 int priority;
573                                 if (!readPriority(priority))
574                                         return FALSE;
575                                 proj->setPriority(priority);
576                                 break;
577                         }
578                         else if (token == KW("now"))
579                         {
580                                 if (nextToken(token) != DATE)
581                                 {
582                                         fatalError("Date expected");
583                                         return FALSE;
584                                 }
585                                 proj->setNow(date2time(token));
586                                 break;
587                         }
588                         else if (token == KW("mineffort"))
589                         {
590                                 if (nextToken(token) != REAL)
591                                 {
592                                         fatalError("Real value exptected");
593                                         return FALSE;
594                                 }
595                                 proj->setMinEffort(token.toDouble());
596                                 break;
597                         }
598                         else if (token == KW("maxeffort"))
599                         {
600                                 if (nextToken(token) != REAL)
601                                 {
602                                         fatalError("Real value exptected");
603                                         return FALSE;
604                                 }
605                                 proj->setMaxEffort(token.toDouble());
606                                 break;
607                         }
608                         else if (token == KW("rate"))
609                         {
610                                 if (nextToken(token) != REAL)
611                                 {
612                                         fatalError("Real value exptected");
613                                         return FALSE;
614                                 }
615                                 proj->setRate(token.toDouble());
616                                 break;
617                         }
618                         else if (token == KW("currency"))
619                         {
620                                 if (nextToken(token) != STRING)
621                                 {
622                                         fatalError("String expected");
623                                         return FALSE;
624                                 }
625                                 proj->setCurrency(token);
626                                 break;
627                         }
628                         else if (token == KW("currencydigits"))
629                         {
630                                 if (nextToken(token) != INTEGER)
631                                 {
632                                         fatalError("Integer value expected");
633                                         return FALSE;
634                                 }
635                                 proj->setCurrencyDigits(token.toInt());
636                                 break;
637                         }
638                         else if (token == KW("timingresolution"))
639                         {
640                                 ulong resolution;
641                                 if (!readTimeValue(resolution))
642                                         return FALSE;
643                                 if (proj->resourceCount() > 0)
644                                 {
645                                         fatalError("The timing resolution cannot be changed after "
646                                                            "resources have been declared.");
647                                         return FALSE;
648                                 }
649                                 if (resolution < 60 * 5)
650                                 {
651                                         fatalError("timing resolution must be at least 5 min");
652                                         return FALSE;
653                                 }
654                                 proj->setScheduleGranularity(resolution);
655                                 break;
656                         }
657                         else if (token == KW("copyright"))
658                         {
659                                 if (nextToken(token) != STRING)
660                                 {
661                                         fatalError("String expected");
662                                         return FALSE;
663                                 }
664                                 proj->setCopyright(token);
665                                 break;
666                         }
667                         else if (token == KW("include"))
668                         {
669                                 if (!readInclude())
670                                         return FALSE;
671                                 break;
672                         }
673                         else if (token == KW("macro"))
674                         {
675                                 QString id;
676                                 if (nextToken(id) != ID)
677                                 {
678                                         fatalError("Macro ID expected");
679                                         return FALSE;
680                                 }
681                                 QString file = openFiles.last()->getFile();
682                                 uint line = openFiles.last()->getLine();
683                                 if (nextToken(token) != MacroBody)
684                                 {
685                                         fatalError("Macro body expected");
686                                         return FALSE;
687                                 }
688                                 Macro* macro = new Macro(id, token, file, line);
689                                 if (!macros.addMacro(macro))
690                                 {
691                                         fatalError("Macro has been defined already");
692                                         delete macro;
693                                         return FALSE;
694                                 }
695                                 break;
696                         }
697                         else if (token == KW("flags"))
698                         {
699                                 for ( ; ; )
700                                 {
701                                         QString flag;
702                                         if (nextToken(flag) != ID)
703                                         {
704                                                 fatalError("flag ID expected");
705                                                 return FALSE;
706                                         }
707
708                                         /* Flags can be declared multiple times, but we
709                                          * register a flag only once. */
710                                         if (!proj->isAllowedFlag(flag))
711                                                 proj->addAllowedFlag(flag);
712
713                                         if ((tt = nextToken(token)) != COMMA)
714                                         {
715                                                 returnToken(tt, token);
716                                                 break;
717                                         }
718                                 }
719                                 break;
720                         }
721                         else if (token == KW("project"))
722                         {
723                                 if (nextToken(token) != ID)
724                                 {
725                                         fatalError("Project ID expected");
726                                         return FALSE;
727                                 }
728                                 if (!proj->addId(token))
729                                 {
730                                         fatalError(QString().sprintf(
731                                                 "Project ID %s has already been registered",
732                                                 token.latin1()));
733                                         return FALSE;
734                                 }
735                                 if (nextToken(token) != STRING)
736                                 {
737                                         fatalError("Project name expected");
738                                         return FALSE;
739                                 }
740                                 proj->setName(token);
741                                 if (nextToken(token) != STRING)
742                                 {
743                                         fatalError("Version string expected");
744                                         return FALSE;
745                                 }
746                                 proj->setVersion(token);
747                                 time_t start, end;
748                                 if (nextToken(token) != DATE)
749                                 {
750                                         fatalError("Start date expected");
751                                         return FALSE;
752                                 }
753                                 start = date2time(token);
754                                 if (nextToken(token) != DATE)
755                                 {
756                                         fatalError("End date expected");
757                                         return FALSE;
758                                 }
759                                 end = date2time(token);
760                                 if (end <= start)
761                                 {
762                                         fatalError("End date must be larger then start date");
763                                         return FALSE;
764                                 }
765                                 proj->setStart(start);
766                                 proj->setEnd(end);
767                                 break;
768                         }
769                         else if (token == KW("projectid"))
770                         {
771                                 for ( ; ; )
772                                 {
773                                         QString id;
774                                         if (nextToken(id) != ID)
775                                         {
776                                                 fatalError("Project ID expected");
777                                                 return FALSE;
778                                         }
779
780                                         if (!proj->addId(id))
781                                         {
782                                                 fatalError(QString().sprintf(
783                                                         "Project ID %s has already been registered",
784                                                         id.latin1()));
785                                                 return FALSE;
786                                         }
787
788                                         if ((tt = nextToken(token)) != COMMA)
789                                         {
790                                                 returnToken(tt, token);
791                                                 break;
792                                         }
793                                 }
794                                 break;
795                         }
796                         else if (token == KW("xmltaskreport"))
797                         {
798                            if( !readXMLTaskReport())
799                               return FALSE;
800                            break;
801                         }
802 #ifdef HAVE_KDE
803                         else if (token == "icalreport" )
804                         {
805                            if( !readICalTaskReport())
806                               return FALSE;
807                            break;
808                         }
809 #endif
810                         else if (token == KW("htmltaskreport") ||
811                                          token == KW("htmlresourcereport"))
812                         {
813                                 if (!readHTMLReport(token))
814                                         return FALSE;
815                                 break;
816                         }
817                         else if (token == KW("htmlaccountreport"))
818                         {
819                                 if (!readHTMLAccountReport())
820                                         return FALSE;
821                                 break;
822                         }
823                         else if (token == KW("export"))
824                         {
825                                 if (!readExportReport())
826                                         return FALSE;
827                                 break;
828                         }
829                         else if (token == KW("kotrusmode"))
830                         {
831                                 if (nextToken(token) != STRING ||
832                                         (token != KW("db") && token != KW("xml") &&
833                                          token != KW("nokotrus")))
834                                 {
835                                         fatalError("Unknown kotrus mode");
836                                         return FALSE;
837                                 }
838                                 if (token != KW("nokotrus"))
839                                 {
840                                         Kotrus* kotrus = new Kotrus();
841                                         kotrus->setKotrusMode(token);
842                                         proj->setKotrus(kotrus);
843                                 }
844                                 break;
845                         }
846                         else if (token == KW("supplement"))
847                         {
848                                 if (nextToken(token) != ID ||
849                                         (token != KW("task") && (token != KW("resource"))))
850                                 {
851                                         fatalError("'task' or 'resource' expected");
852                                         return FALSE;
853                                 }
854                                 if ((token == "task" && !readTaskSupplement()) ||
855                                         (token == "resource" && !readResourceSupplement()))
856                                         return FALSE;
857                                 break;
858                         }       
859                         // break missing on purpose!
860                 default:
861                         fatalError(QString("Syntax Error at '") + token + "'!");
862                         return FALSE;
863                 }
864         }
865
866         return TRUE;
867 }
868
869 TokenType
870 ProjectFile::nextToken(QString& buf)
871 {
872         if (openFiles.isEmpty())
873                 return EndOfFile;
874
875         TokenType tt;
876         while ((tt = openFiles.last()->nextToken(buf)) == EndOfFile)
877         {
878                 close();
879                 if (openFiles.isEmpty())
880                         return EndOfFile;
881         }
882
883         return tt;
884 }
885
886 void
887 ProjectFile::fatalError(const QString& msg)
888 {
889         if (openFiles.isEmpty())
890                 qWarning("Unexpected end of file found. Probably a missing '}'.");
891         else
892                 openFiles.last()->fatalError(msg);
893 }
894
895 bool
896 ProjectFile::readInclude()
897 {
898         QString token;
899
900         if (nextToken(token) != STRING)
901         {
902                 fatalError("File name expected");
903                 return FALSE;
904         }
905         if (!open(token))
906                 return FALSE;
907
908         return TRUE;
909 }
910
911 bool
912 ProjectFile::readTask(Task* parent)
913 {
914         TokenType tt;
915         QString token;
916
917         QString id;
918         if ((tt = nextToken(id)) != ID &&
919                 (tt != RELATIVE_ID))
920         {
921                 fatalError("ID expected");
922                 return FALSE;
923         }
924
925         if (tt == RELATIVE_ID)
926         {
927                 /* If a relative ID has been specified the task is declared out of
928                  * it's actual scope. So we have to set 'parent' to point to the
929                  * correct parent task. */
930                 do
931                 {
932                         if (id[0] == '!')
933                         {
934                                 if (parent != 0)
935                                         parent = parent->getParent();
936                                 else
937                                 {
938                                         fatalError("Invalid relative task ID");
939                                         return FALSE;
940                                 }
941                                 id = id.right(id.length() - 1);
942                         }
943                         else if (id.find('.') >= 0)
944                         {
945                                 QString tn = (parent ? parent->getId() + "." : QString())
946                                         + id.left(id.find('.'));
947                                 TaskList tl;
948                                 if (parent)
949                                         parent->getSubTaskList(tl);
950                                 else
951                                         tl = proj->getTaskList();
952                                 bool found = FALSE;
953                                 for (Task* t = tl.first(); t != 0; t = tl.next())
954                                         if (t->getId() == tn)
955                                         {
956                                                 parent = t;
957                                                 id = id.right(id.length() - id.find('.') - 1);
958                                                 found = TRUE;
959                                                 break;
960                                         }
961                                 if (!found)
962                                 {
963                                         fatalError(QString("Task ") + tn + " unknown");
964                                         return FALSE;
965                                 }
966                         }
967                 } while (id[0] == '!' || id.find('.') >= 0);
968         }
969
970         QString name;
971         if ((tt = nextToken(name)) != STRING)
972         {
973                 fatalError("String expected");
974                 return FALSE;
975         }
976
977         if ((tt = nextToken(token)) != LCBRACE)
978         {
979                 fatalError("{ expected");
980                 return FALSE;
981         }
982
983         if (parent)
984                 id = parent->getId() + "." + id;
985         
986         // We need to check that the task id has not been declared before.
987         TaskList tl = proj->getTaskList();
988         for (Task* t = tl.first(); t != 0; t = tl.next())
989                 if (t->getId() == id)
990                 {
991                         fatalError(QString().sprintf
992                                            ("Task %s has already been declared", id.latin1()));
993                         return FALSE;
994                 }
995
996         Task* task = new Task(proj, id, name, parent, getFile(), getLine());
997
998         proj->addTask(task);
999         if (parent)
1000                 parent->addSub(task);
1001
1002
1003         if (!readTaskBody(task))
1004                 return FALSE;
1005         
1006         if (task->getName().isEmpty())
1007         {
1008                 fatalError(QString("No name specified for task ") + id + "!");
1009                 return FALSE;
1010         }
1011
1012         return TRUE;
1013 }
1014
1015 bool
1016 ProjectFile::readTaskSupplement()
1017 {
1018         QString token;
1019         TokenType tt;
1020         Task* task;
1021
1022         if (((tt = nextToken(token)) != ID && tt != RELATIVE_ID) ||
1023                 ((task = proj->getTask(token)) == 0))
1024         {
1025                 fatalError("Already defined task ID expected");
1026                 return FALSE;
1027         }
1028         if (nextToken(token) != LCBRACE)
1029         {
1030                 fatalError("'}' expected");
1031                 return FALSE;
1032         }
1033         return readTaskBody(task);
1034 }
1035
1036 bool
1037 ProjectFile::readTaskBody(Task* task)
1038 {
1039         QString token;
1040         TokenType tt;
1041         
1042         for (bool done = false ; !done; )
1043         {
1044                 switch (tt = nextToken(token))
1045                 {
1046                 case ID:
1047                         if (token == KW("task"))
1048                         {
1049                                 if (!readTask(task))
1050                                         return FALSE;
1051                         }
1052                         else if (token == KW("note"))
1053                         {
1054                                 if ((tt = nextToken(token)) == STRING)
1055                                         task->setNote(token);
1056                                 else
1057                                 {
1058                                         fatalError("String expected");
1059                                         return FALSE;
1060                                 }
1061                         }
1062                         else if (token == KW("milestone"))
1063                         {
1064                                 task->setMilestone();
1065                         }
1066                         else if READ_DATE(KW("start"), setPlanStart)
1067                         else if READ_DATE(KW("end"), setPlanEnd)
1068                         else if READ_DATE(KW("minstart"), setMinStart)
1069                         else if READ_DATE(KW("maxstart"), setMaxStart)
1070                         else if READ_DATE(KW("minend"), setMinEnd)
1071                         else if READ_DATE(KW("maxend"), setMaxEnd)
1072                         else if READ_DATE(KW("actualstart"), setActualStart)
1073                         else if READ_DATE(KW("actualend"), setActualEnd)
1074                         else if (token == KW("length"))
1075                         {
1076                                 double d;
1077                                 if (!readPlanTimeFrame(task, d))
1078                                         return FALSE;
1079                                 task->setPlanLength(d);
1080                         }
1081                         else if (token == KW("effort"))
1082                         {
1083                                 double d;
1084                                 if (!readPlanTimeFrame(task, d))
1085                                         return FALSE;
1086                                 task->setPlanEffort(d);
1087                         }
1088                         else if (token == KW("duration"))
1089                         {
1090                                 double d;
1091                                 if (!readPlanTimeFrame(task, d))
1092                                         return FALSE;
1093                                 task->setPlanDuration(d);
1094                         }
1095                         else if (token == KW("actuallength"))
1096                         {
1097                                 double d;
1098                                 if (!readPlanTimeFrame(task, d))
1099                                         return FALSE;
1100                                 task->setActualLength(d);
1101                         }
1102                         else if (token == KW("actualeffort"))
1103                         {
1104                                 double d;
1105                                 if (!readPlanTimeFrame(task, d))
1106                                         return FALSE;
1107                                 task->setActualEffort(d);
1108                         }
1109                         else if (token == KW("actualduration"))
1110                         {
1111                                 double d;
1112                                 if (!readPlanTimeFrame(task, d))
1113                                         return FALSE;
1114                                 task->setActualDuration(d);
1115                         }
1116                         else if (token == KW("complete"))
1117                         {
1118                                 if (nextToken(token) != INTEGER)
1119                                 {
1120                                         fatalError("Integer value expected");
1121                                         return FALSE;
1122                                 }
1123                                 int complete = token.toInt();
1124                                 if (complete < 0 || complete > 100)
1125                                 {
1126                                         fatalError("Value of complete must be between 0 and 100");
1127                                         return FALSE;
1128                                 }
1129                                 task->setComplete(complete);
1130                         }
1131                         else if (token == KW("responsible"))
1132                         {
1133                                 Resource* r;
1134                                 if (nextToken(token) != ID ||
1135                                         (r = proj->getResource(token)) == 0)
1136                                 {
1137                                         fatalError("Resource ID expected");
1138                                         return FALSE;
1139                                 }
1140                                 task->setResponsible(r);
1141                         }
1142                         else if (token == KW("allocate"))
1143                         {
1144                                 if (!readAllocate(task))
1145                                         return FALSE;
1146                         }
1147                         else if (token == KW("depends"))
1148                         {
1149                                 for ( ; ; )
1150                                 {
1151                                         QString id;
1152                                         if ((tt = nextToken(id)) != ID &&
1153                                                 tt != RELATIVE_ID)
1154                                         {
1155                                                 fatalError("Task ID expected");
1156                                                 return FALSE;
1157                                         }
1158                                         task->addDependency(id);
1159                                         task->setScheduling(Task::ASAP);
1160                                         if ((tt = nextToken(token)) != COMMA)
1161                                         {
1162                                                 returnToken(tt, token);
1163                                                 break;
1164                                         }
1165                                 }
1166                         }
1167                         else if (token == KW("preceeds"))
1168                         {
1169                                 for ( ; ; )
1170                                 {
1171                                         QString id;
1172                                         if ((tt = nextToken(id)) != ID &&
1173                                                 tt != RELATIVE_ID)
1174                                         {
1175                                                 fatalError("Task ID expected");
1176                                                 return FALSE;
1177                                         }
1178                                         task->addPreceeds(id);
1179                                         task->setScheduling(Task::ALAP);
1180                                         if ((tt = nextToken(token)) != COMMA)
1181                                         {
1182                                                 returnToken(tt, token);
1183                                                 break;
1184                                         }
1185                                 }
1186                         }
1187                         else if (token == KW("scheduling"))
1188                         {
1189                                 nextToken(token);
1190                                 if (token == KW("asap"))
1191                                         task->setScheduling(Task::ASAP);
1192                                 else if (token == KW("alap"))
1193                                         task->setScheduling(Task::ALAP);
1194                                 else
1195                                 {
1196                                         fatalError("Unknown scheduling policy");
1197                                         return FALSE;
1198                                 }
1199                         }
1200                         else if (token == KW("flags"))
1201                         {
1202                                 for ( ; ; )
1203                                 {
1204                                         QString flag;
1205                                         if (nextToken(flag) != ID || !proj->isAllowedFlag(flag))
1206                                         {
1207                                                 fatalError("Flag unknown");
1208                                                 return FALSE;
1209                                         }
1210                                         task->addFlag(flag);
1211                                         if ((tt = nextToken(token)) != COMMA)
1212                                         {
1213                                                 returnToken(tt, token);
1214                                                 break;
1215                                         }
1216                                 }
1217                         }
1218                         else if (token == KW("priority"))
1219                         {
1220                                 int priority;
1221                                 if (!readPriority(priority))
1222                                         return FALSE;
1223                                 task->setPriority(priority);
1224                                 break;
1225                         }
1226                         else if (token == KW("account"))
1227                         {
1228                                 QString account;
1229                                 if (nextToken(account) != ID ||
1230                                         proj->getAccount(account) == 0)
1231                                 {
1232                                         fatalError("Account ID expected");
1233                                         return FALSE;
1234                                 }
1235                                 task->setAccount(proj->getAccount(account));
1236                                 break;
1237                         }
1238                         else if (token == KW("startcredit"))
1239                         {
1240                                 if (nextToken(token) != REAL)
1241                                 {
1242                                         fatalError("Real value expected");
1243                                         return FALSE;
1244                                 }
1245                                 task->setStartCredit(token.toDouble());
1246                                 break;
1247                         }
1248                         else if (token == KW("endcredit"))
1249                         {
1250                                 if (nextToken(token) != REAL)
1251                                 {
1252                                         fatalError("Real value expected");
1253                                         return FALSE;
1254                                 }
1255                                 task->setEndCredit(token.toDouble());
1256                                 break;
1257                         }
1258                         else if (token == KW("projectid"))
1259                         {
1260                                 if (nextToken(token) != ID ||
1261                                         !proj->isValidId(token))
1262                                 {
1263                                         fatalError("Project ID expected");
1264                                         return FALSE;
1265                                 }
1266                                 task->setProjectId(token);
1267                                 break;
1268                         }
1269                         else if (token == KW("include"))
1270                         {
1271                                 if (!readInclude())
1272                                         return FALSE;
1273                                 break;
1274                         }
1275                         else
1276                         {
1277                                 fatalError(QString("Illegal task attribute '")
1278                                                    + token + "'");
1279                                 return FALSE;
1280                         }
1281                         break;
1282                 case RCBRACE:
1283                         done = true;
1284                         break;
1285                 default:
1286                         fatalError(QString("Syntax Error at '") + token + "'");
1287                         return FALSE;
1288                 }
1289         }
1290
1291         return TRUE;
1292 }
1293
1294 bool
1295 ProjectFile::readVacation(time_t& from, time_t& to, bool readName,
1296                                                   QString* n)
1297 {
1298         TokenType tt;
1299         if (readName)
1300         {
1301                 if ((tt = nextToken(*n)) != STRING)
1302                 {
1303                         fatalError("String expected");
1304                         return FALSE;
1305                 }
1306         }
1307         QString start;
1308         if ((tt = nextToken(start)) != DATE)
1309         {
1310                 fatalError("Date expected");
1311                 return FALSE;
1312         }
1313         QString token;
1314         if ((tt = nextToken(token)) != MINUS)
1315         {
1316                 // vacation e. g. 2001-11-28
1317                 returnToken(tt, token);
1318                 from = date2time(start);
1319                 to = sameTimeNextDay(date2time(start)) - 1;
1320         }
1321         else
1322         {
1323                 // vacation e. g. 2001-11-28 - 2001-11-30
1324                 QString end;
1325                 if ((tt = nextToken(end)) != DATE)
1326                 {
1327                         fatalError("Date expected");
1328                         return FALSE;
1329                 }
1330                 from = date2time(start);
1331                 if (date2time(start) > date2time(end))
1332                 {
1333                         fatalError("Vacation must start before end");
1334                         return FALSE;
1335                 }
1336                 to = date2time(end) - 1;
1337         }
1338         return TRUE;
1339 }
1340
1341 bool
1342 ProjectFile::readResource(Resource* parent)
1343 {
1344         // Syntax: 'resource id "name" { ... }
1345         QString id;
1346         if (nextToken(id) != ID)
1347         {
1348                 fatalError("ID expected");
1349                 return FALSE;
1350         }
1351         QString name;
1352         if (nextToken(name) != STRING)
1353         {
1354                 fatalError("String expected");
1355                 return FALSE;
1356         }
1357
1358         if (proj->getResource(id))
1359         {
1360                 fatalError(QString().sprintf(
1361                         "Resource %s has already been defined", id.latin1()));
1362                 return FALSE;
1363         }
1364
1365         Resource* r = new Resource(proj, id, name, parent);
1366
1367         TokenType tt;
1368         QString token;
1369         if ((tt = nextToken(token)) == LCBRACE)
1370         {
1371                 // read optional attributes
1372                 if (!readResourceBody(r))
1373                         return FALSE;
1374         }
1375         else
1376                 returnToken(tt, token);
1377
1378         proj->addResource(r);
1379
1380         return TRUE;
1381 }
1382
1383 bool
1384 ProjectFile::readResourceSupplement()
1385 {
1386         QString token;
1387         Resource* r;
1388         if (nextToken(token) != ID || (r = proj->getResource(token)) == 0)
1389         {
1390                 fatalError("Already defined resource ID expected");
1391                 return FALSE;
1392         }
1393         if (nextToken(token) != LCBRACE)
1394         {
1395                 fatalError("'{' expected");
1396                 return FALSE;
1397         }
1398         return readResourceBody(r);
1399 }
1400
1401 bool
1402 ProjectFile::readResourceBody(Resource* r)
1403 {
1404         QString token;
1405         TokenType tt;
1406
1407         while ((tt = nextToken(token)) != RCBRACE)
1408         {
1409                 if (tt != ID)
1410                 {
1411                         fatalError(QString("Unknown attribute '") + token + "'");
1412                         return FALSE;
1413                 }
1414                 if (token == KW("resource"))
1415                 {
1416                         if (!readResource(r))
1417                                 return FALSE;
1418                 }
1419                 else if (token == KW("mineffort"))
1420                 {
1421                         if (nextToken(token) != REAL)
1422                         {
1423                                 fatalError("Real value exptected");
1424                                 return FALSE;
1425                         }
1426                         r->setMinEffort(token.toDouble());
1427                 }
1428                 else if (token == KW("maxeffort"))
1429                 {
1430                         if (nextToken(token) != REAL)
1431                         {
1432                                 fatalError("Real value exptected");
1433                                 return FALSE;
1434                         }
1435                         r->setMaxEffort(token.toDouble());
1436                 }
1437                 else if (token == KW("efficiency"))
1438                 {
1439                         if (nextToken(token) != REAL)
1440                         {
1441                                 fatalError("Read value expected");
1442                                 return FALSE;
1443                         }
1444                         r->setEfficiency(token.toDouble());
1445                 }
1446                 else if (token == KW("rate"))
1447                 {
1448                         if (nextToken(token) != REAL)
1449                         {
1450                                 fatalError("Real value exptected");
1451                                 return FALSE;
1452                         }
1453                         r->setRate(token.toDouble());
1454                 }
1455                 else if (token == KW("kotrusid"))
1456                 {
1457                         if (nextToken(token) != STRING)
1458                         {
1459                                 fatalError("String expected");
1460                                 return FALSE;
1461                         }
1462                         r->setKotrusId(token);
1463                 }
1464                 else if (token == KW("vacation"))
1465                 {
1466                         time_t from, to;
1467                         if (!readVacation(from, to))
1468                                 return FALSE;
1469                         r->addVacation(new Interval(from, to));
1470                 }
1471                 else if (token == KW("workinghours"))
1472                 {
1473                         int dow;
1474                         QPtrList<Interval>* l = new QPtrList<Interval>();
1475                         if (!readWorkingHours(dow, l))
1476                                 return FALSE;
1477
1478                         r->setWorkingHours(dow, l);
1479                 }
1480                 else if (token == KW("shift"))
1481                 {
1482                         QString id;
1483                         if (nextToken(id) != ID)
1484                         {
1485                                 fatalError("Shift ID expected");
1486                                 return FALSE;
1487                         }
1488                         Shift* s;
1489                         if ((s = proj->getShift(id)) == 0)
1490                         {
1491                                 fatalError("Unknown shift");
1492                                 return FALSE;
1493                         }
1494                         time_t from, to;
1495                         if (!readVacation(from, to))
1496                                 return FALSE;
1497                         if (!r->addShift(Interval(from, to), s))
1498                         {
1499                                 fatalError("Shift interval overlaps with other");
1500                                 return FALSE;
1501                         }
1502                 }
1503                 else if (token == KW("planbooking"))
1504                 {
1505                         Booking* b;
1506                         if ((b = readBooking()) == 0)
1507                                 return FALSE;
1508                         if (!r->addPlanBooking(b))
1509                         {
1510                                 fatalError("Resource is already booked during this period");
1511                                 return FALSE;
1512                         }
1513                 }
1514                 else if (token == KW("actualbooking"))
1515                 {
1516                         Booking* b;
1517                         if ((b = readBooking()) == 0)
1518                                 return FALSE;
1519                         if (!r->addActualBooking(b))
1520                         {
1521                                 fatalError("Resource is already booked during this period");
1522                                 return FALSE;
1523                         }
1524                 }
1525                 else if (token == KW("flags"))
1526                 {
1527                         for ( ; ; )
1528                         {
1529                                 QString flag;
1530                                 if (nextToken(flag) != ID || !proj->isAllowedFlag(flag))
1531                                 {
1532                                         fatalError("flag unknown");
1533                                         return FALSE;
1534                                 }
1535                                 r->addFlag(flag);
1536                                 if ((tt = nextToken(token)) != COMMA)
1537                                 {
1538                                         returnToken(tt, token);
1539                                         break;
1540                                 }
1541                         }
1542                 }
1543                 else if (token == KW("include"))
1544                 {
1545                         if (!readInclude())
1546                                 return FALSE;
1547                         break;
1548                 }
1549                 else
1550                 {
1551                         fatalError(QString("Unknown attribute '") + token + "'");
1552                         return FALSE;
1553                 }
1554         }
1555
1556         return TRUE;
1557 }
1558
1559 bool
1560 ProjectFile::readShift(Shift* parent)
1561 {
1562         // Syntax: 'shift id "name" { ... }
1563         QString id;
1564         if (nextToken(id) != ID)
1565         {
1566                 fatalError("ID expected");
1567                 return FALSE;
1568         }
1569         QString name;
1570         if (nextToken(name) != STRING)
1571         {
1572                 fatalError("String expected");
1573                 return FALSE;
1574         }
1575
1576         if (proj->getShift(id))
1577         {
1578                 fatalError(QString().sprintf(
1579                         "Shift %s has already been defined", id.latin1()));
1580                 return FALSE;
1581         }
1582
1583         Shift* s = new Shift(proj, id, name, parent);
1584
1585         TokenType tt;
1586         QString token;
1587         if ((tt = nextToken(token)) == LCBRACE)
1588         {
1589                 // read optional attributes
1590                 while ((tt = nextToken(token)) != RCBRACE)
1591                 {
1592                         if (tt != ID)
1593                         {
1594                                 fatalError(QString("Unknown attribute '") + token + "'");
1595                                 return FALSE;
1596                         }
1597                         if (token == KW("shift"))
1598                         {
1599                                 if (!readShift(s))
1600                                         return FALSE;
1601                         }
1602                         else if (token == KW("workinghours"))
1603                         {
1604                                 int dow;
1605                                 QPtrList<Interval>* l = new QPtrList<Interval>();
1606                                 if (!readWorkingHours(dow, l))
1607                                         return FALSE;
1608                                 
1609                                 s->setWorkingHours(dow, l);
1610                         }
1611                         else if (token == KW("include"))
1612                         {
1613                                 if (!readInclude())
1614                                         return FALSE;
1615                                 break;
1616                         }
1617                         else
1618                         {
1619                                 fatalError(QString("Unknown attribute '") + token + "'");
1620                                 return FALSE;
1621                         }
1622                 }
1623         }
1624         else
1625                 returnToken(tt, token);
1626
1627         proj->addShift(s);
1628
1629         return TRUE;
1630 }
1631
1632 Booking*
1633 ProjectFile::readBooking()
1634 {
1635         QString token;
1636
1637         if (nextToken(token) != DATE)
1638         {
1639                 fatalError("Start date expected");
1640                 return 0;
1641         }
1642         time_t start = date2time(token);
1643         if (start < proj->getStart() || start >= proj->getEnd())
1644         {
1645                 fatalError("Start date must be within the project timeframe");
1646                 return 0;
1647         }
1648         
1649         if (nextToken(token) != DATE)
1650         {
1651                 fatalError("End date expected");
1652                 return 0;
1653         }
1654         time_t end = date2time(token);
1655         if (end <= proj->getStart() || end > proj->getEnd())
1656         {
1657                 fatalError("End date must be within the project timeframe");
1658                 return 0;
1659         }
1660         if (start >= end)
1661         {
1662                 fatalError("End date must be after start date");
1663                 return 0;
1664         }
1665
1666         QString pid;
1667         if (nextToken(pid) != ID || !proj->isValidId(pid))
1668         {
1669                 fatalError("Known project ID expected");
1670                 return 0;
1671         }
1672         Task* task;
1673         TokenType tt;
1674         if (((tt = nextToken(token)) != ID && tt != RELATIVE_ID) ||
1675                 (task = proj->getTask(token)) == 0)
1676         {
1677                 fatalError("Task ID expected");
1678                 return 0;
1679         }
1680         return new Booking(Interval(start, end), task, "", pid);
1681 }
1682
1683 bool
1684 ProjectFile::readAccount(Account* parent)
1685 {
1686         // Syntax: 'account id "name" { ... }
1687         QString id;
1688         if (nextToken(id) != ID)
1689         {
1690                 fatalError("ID expected");
1691                 return FALSE;
1692         }
1693
1694         if (proj->getAccount(id))
1695         {
1696                 fatalError(QString().sprintf(
1697                         "Account %s has already been defined", id.latin1()));
1698                 return FALSE;
1699         }
1700
1701         QString name;
1702         if (nextToken(name) != STRING)
1703         {
1704                 fatalError("String expected");
1705                 return FALSE;
1706         }
1707         Account::AccountType acctType;
1708         if (parent == 0)
1709         {
1710                 /* Only accounts with no parent can have a type specifier. All
1711                  * sub accounts inherit the type of the parent. */
1712                 QString at;
1713                 if (nextToken(at) != ID && (at != KW("cost") ||
1714                                                                         at != KW("revenue")))
1715                 {
1716                         fatalError("Account type 'cost' or 'revenue' expected");
1717                         return FALSE;
1718                 }
1719                 acctType = at == KW("cost") ? Account::Cost : Account::Revenue;
1720         }
1721         else
1722                 acctType = parent->getAcctType();
1723
1724         Account* a = new Account(proj, id, name, parent, acctType);
1725         if (parent)
1726                 parent->addSub(a);
1727
1728         TokenType tt;
1729         QString token;
1730         if ((tt = nextToken(token)) == LCBRACE)
1731         {
1732                 bool hasSubAccounts = FALSE;
1733                 bool cantBeParent = FALSE;
1734                 // read optional attributes
1735                 while ((tt = nextToken(token)) != RCBRACE)
1736                 {
1737                         if (tt != ID)
1738                         {
1739                                 fatalError(QString("Unknown attribute '") + token + "'");
1740                                 return FALSE;
1741                         }
1742                         if (token == KW("account") && !cantBeParent)
1743                         {
1744                                 if (!readAccount(a))
1745                                         return FALSE;
1746                                 hasSubAccounts = TRUE;
1747                         }
1748                         else if (token == KW("credit"))
1749                         {
1750                                 if (!readCredit(a))
1751                                         return FALSE;
1752                         }
1753                         else if (token == KW("kotrusid") && !hasSubAccounts)
1754                         {
1755                                 if (nextToken(token) != STRING)
1756                                 {
1757                                         fatalError("String expected");
1758                                         return FALSE;
1759                                 }
1760                                 a->setKotrusId(token);
1761                                 cantBeParent = TRUE;
1762                         }
1763                         else if (token == KW("include"))
1764                         {
1765                                 if (!readInclude())
1766                                         return FALSE;
1767                         }
1768                         else
1769                         {
1770                                 fatalError("Illegal attribute");
1771                                 return FALSE;
1772                         }
1773                 }
1774         }
1775         else
1776                 returnToken(tt, token);
1777
1778         proj->addAccount(a);
1779
1780         return TRUE;
1781 }
1782
1783 bool
1784 ProjectFile::readCredit(Account* a)
1785 {
1786         QString token;
1787
1788         if (nextToken(token) != DATE)
1789         {
1790                 fatalError("Date expected");
1791                 return FALSE;
1792         }
1793         time_t date = date2time(token);
1794
1795         QString description;
1796         if (nextToken(description) != STRING)
1797         {
1798                 fatalError("String expected");
1799                 return FALSE;
1800         }
1801
1802         if (nextToken(token) != REAL)
1803         {
1804                 fatalError("Real value expected");
1805                 return FALSE;
1806         }
1807         Transaction* t = new Transaction(date, token.toDouble(), description);
1808         a->credit(t);
1809
1810         return TRUE;
1811 }
1812
1813 bool
1814 ProjectFile::readAllocate(Task* t)
1815 {
1816         QString id;
1817         Resource* r;
1818         if (nextToken(id) != ID || (r = proj->getResource(id)) == 0)
1819         {
1820                 fatalError("Resource ID expected");
1821                 return FALSE;
1822         }
1823         Allocation* a = new Allocation(r);
1824         QString token;
1825         TokenType tt;
1826         if ((tt = nextToken(token)) == LCBRACE)
1827         {
1828                 while ((tt = nextToken(token)) != RCBRACE)
1829                 {
1830                         if (tt != ID)
1831                         {
1832                                 fatalError(QString("Unknown attribute '") + token + "'");
1833                                 return FALSE;
1834                         }
1835                         if (token == KW("load"))
1836                         {
1837                                 if (nextToken(token) != REAL)
1838                                 {
1839                                         fatalError("Real value expected");
1840                                         return FALSE;
1841                                 }
1842                                 double load = token.toDouble();
1843                                 if (load < 0.01 || load > 1.0)
1844                                 {
1845                                         fatalError("Value must be in the range 0.01 - 1.0");
1846                                         return FALSE;
1847                                 }
1848                                 a->setLoad((int) (100 * load));
1849                         }
1850                         else if (token == KW("persistent"))
1851                         {
1852                                 a->setPersistent(TRUE);
1853                         }
1854                         else if (token == KW("alternative"))
1855                         {
1856                                 do
1857                                 {
1858                                         Resource* r;
1859                                         if ((tt = nextToken(token)) != ID ||
1860                                                 (r = proj->getResource(token)) == 0)
1861                                         {
1862                                                 fatalError("Resource ID expected");
1863                                                 return FALSE;
1864                                         }
1865                                         a->addAlternative(r);
1866                                 } while ((tt = nextToken(token)) == COMMA);
1867                                 returnToken(tt, token);
1868                         }
1869                 }
1870         }
1871         else
1872                 returnToken(tt, token);
1873         t->addAllocation(a);
1874
1875         return TRUE;
1876 }
1877
1878 bool
1879 ProjectFile::readTimeValue(ulong& value)
1880 {
1881         QString val;
1882         if (nextToken(val) != INTEGER)
1883         {
1884                 fatalError("Integer value expected");
1885                 return FALSE;
1886         }
1887         QString unit;
1888         if (nextToken(unit) != ID)
1889         {
1890                 fatalError("Unit expected");
1891                 return FALSE;
1892         }
1893         if (unit == KW("min"))
1894                 value = val.toULong() * 60;
1895         else if (unit == KW("h"))
1896                 value = val.toULong() * (60 * 60);
1897         else if (unit == KW("d"))
1898                 value = val.toULong() * (60 * 60 * 24);
1899         else if (unit == KW("w"))
1900                 value = val.toULong() * (60 * 60 * 24 * 7);
1901         else if (unit == KW("m"))
1902                 value = val.toULong() * (60 * 60 * 24 * 30);
1903         else if (unit == KW("y"))
1904                 value = val.toULong() * (60 * 60 * 24 * 356);
1905         else
1906         {
1907                 fatalError("Unit expected");
1908                 return FALSE;
1909         }
1910         return TRUE;
1911 }
1912
1913 bool
1914 ProjectFile::readPlanTimeFrame(Task* task, double& value)
1915 {
1916         QString val;
1917         TokenType tt;
1918         if ((tt = nextToken(val)) != REAL && tt != INTEGER)
1919         {
1920                 fatalError("Real value expected");
1921                 return FALSE;
1922         }
1923         QString unit;
1924         if (nextToken(unit) != ID)
1925         {
1926                 fatalError("Unit expected");
1927                 return FALSE;
1928         }
1929         if (unit == KW("min"))
1930                 value = val.toDouble() / (8 * 60);
1931         else if (unit == KW("h"))
1932                 value = val.toDouble() / 8;
1933         else if (unit == KW("d"))
1934                 value = val.toDouble();
1935         else if (unit == KW("w"))
1936                 value = val.toDouble() * 5;
1937         else if (unit == KW("m"))
1938                 value = val.toDouble() * 20;
1939         else if (unit == KW("y"))
1940                 value = val.toDouble() * 240;
1941         else
1942         {
1943                 fatalError("Unit expected");
1944                 return FALSE;
1945         }
1946
1947         return TRUE;
1948 }
1949
1950 bool
1951 ProjectFile::readWorkingHours(int& dayOfWeek, QPtrList<Interval>* l)
1952 {
1953         l->setAutoDelete(TRUE);
1954         QString day;
1955         if (nextToken(day) != ID)
1956         {
1957                 fatalError("Weekday expected");
1958                 return FALSE;
1959         }
1960         const char* days[] = { KW("sun"), KW("mon"), KW("tue"), KW("wed"),
1961                 KW("thu"), KW("fri"), KW("sat") };
1962         for (dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++)
1963                 if (days[dayOfWeek] == day)
1964                         break;
1965         if (dayOfWeek == 7)
1966         {
1967                 fatalError("Weekday expected");
1968                 return FALSE;
1969         }
1970
1971         QString token;
1972         TokenType tt;
1973         if ((tt = nextToken(token)) == ID && token == KW("off"))
1974                 return TRUE;
1975         else
1976                 returnToken(tt, token);
1977
1978         for ( ; ; )
1979         {
1980                 QString start;
1981                 if (nextToken(start) != HOUR)
1982                 {
1983                         fatalError("Start time as HH:MM expected");
1984                         return FALSE;
1985                 }
1986                 QString token;
1987                 if (nextToken(token) != MINUS)
1988                 {
1989                         fatalError("'-' expected");
1990                         return FALSE;
1991                 }
1992                 QString end;
1993                 if (nextToken(end) != HOUR)
1994                 {
1995                         fatalError("End time as HH:MM expected");
1996                         return FALSE;
1997                 }
1998                 l->append(new Interval(hhmm2time(start), hhmm2time(end)));
1999                 TokenType tt;
2000                 if ((tt = nextToken(token)) != COMMA)
2001                 {
2002                         returnToken(tt, token);
2003                         break;
2004                 }
2005         }
2006         return TRUE;
2007 }
2008
2009 bool
2010 ProjectFile::readPriority(int& priority)
2011 {
2012         QString token;
2013
2014         if (nextToken(token) != INTEGER)
2015         {
2016                 fatalError("Integer value expected");
2017                 return FALSE;
2018         }
2019         priority = token.toInt();
2020         if (priority < 1 || priority > 1000)
2021         {
2022                 fatalError("Priority value must be between 1 and 1000");
2023                 return FALSE;
2024         }
2025         return TRUE;
2026 }
2027
2028 #ifdef HAVE_KDE
2029 bool
2030 ProjectFile::readICalTaskReport()
2031 {
2032    QString token;
2033    if (nextToken(token) != STRING)
2034    {
2035       fatalError("File name expected");
2036       return FALSE;
2037    }
2038    ReportICal *rep = new ReportICal( proj, token, proj->getStart(), proj->getEnd());
2039    proj->addICalReport( rep );
2040
2041    return( true );
2042 }
2043 #endif
2044
2045 bool
2046 ProjectFile::readXMLTaskReport()
2047 {
2048    QString token;
2049    if (nextToken(token) != STRING)
2050    {
2051       fatalError("File name expected");
2052       return FALSE;
2053    }
2054    ReportXML *rep = new ReportXML(proj, token, proj->getStart(),
2055                                                                   proj->getEnd());
2056    proj->addXMLReport( rep );
2057
2058    return( true );
2059 }
2060
2061
2062 bool
2063 ProjectFile::readHTMLReport(const QString& reportType)
2064 {
2065         QString token;
2066         if (nextToken(token) != STRING)
2067         {
2068                 fatalError("File name expected");
2069                 return FALSE;
2070         }
2071         
2072         ReportHtml* report;
2073         if (reportType == KW("htmltaskreport"))
2074                 report = new HTMLTaskReport(proj, token, proj->getStart(),
2075                                                                         proj->getEnd());
2076         else if (reportType == KW("htmlresourcereport"))
2077                 report = new HTMLResourceReport(proj, token, proj->getStart(),
2078                                                                                 proj->getEnd());
2079         else
2080         {
2081                 qFatal("readHTMLReport: bad report type");
2082                 return FALSE;   // Just to please the compiler.
2083         }
2084                 
2085         TokenType tt;
2086         if ((tt = nextToken(token)) != LCBRACE)
2087         {
2088                 returnToken(tt, token);
2089                 return TRUE;
2090         }
2091
2092         for ( ; ; )
2093         {
2094                 if ((tt = nextToken(token)) == RCBRACE)
2095                         break;
2096                 else if (tt != ID)
2097                 {
2098                         fatalError("Attribute ID or '}' expected");
2099                         return FALSE;
2100                 }
2101                 if (token == KW("columns"))
2102                 {
2103                         report->clearColumns();
2104                         for ( ; ; )
2105                         {
2106                                 QString col;
2107                                 if ((tt = nextToken(col)) != ID)
2108                                 {
2109                                         fatalError("Column ID expected");
2110                                         return FALSE;
2111                                 }
2112                                 report->addColumn(col);
2113                                 if ((tt = nextToken(token)) != COMMA)
2114                                 {
2115                                         returnToken(tt, token);
2116                                         break;
2117                                 }
2118                         }
2119                 }
2120                 else if (token == KW("start"))
2121                 {
2122                         if (nextToken(token) != DATE)
2123                         {
2124                                 fatalError("Date expected");
2125                                 return FALSE;
2126                         }
2127                         report->setStart(date2time(token));
2128                 }
2129                 else if (token == KW("end"))
2130                 {
2131                         if (nextToken(token) != DATE)
2132                         {
2133                                 fatalError("Date expected");
2134                                 return FALSE;
2135                         }
2136                         report->setEnd(date2time(token));
2137                 }
2138                 else if (token == KW("headline"))
2139                 {
2140                         if (nextToken(token) != STRING)
2141                         {
2142                                 fatalError("String exptected");
2143                                 return FALSE;
2144                         }
2145                         report->setHeadline(token);
2146                 }
2147                 else if (token == KW("caption"))
2148                 {
2149                         if (nextToken(token) != STRING)
2150                         {
2151                                 fatalError("String exptected");
2152                                 return FALSE;
2153                         }
2154                         report->setCaption(token);
2155                 }
2156                 else if (token == KW("showactual"))
2157                 {
2158                         report->setShowActual(TRUE);
2159                 }
2160                 else if (token == KW("showprojectids"))
2161                 {
2162                         report->setShowPIDs(TRUE);
2163                 }
2164                 else if (token == KW("hidetask"))
2165                 {
2166                         Operation* op;
2167                         if ((op = readLogicalExpression()) == 0)
2168                                 return FALSE;
2169                         ExpressionTree* et = new ExpressionTree(op);
2170                         report->setHideTask(et);
2171                 }
2172                 else if (token == KW("rolluptask"))
2173                 {
2174                         Operation* op;
2175                         if ((op = readLogicalExpression()) == 0)
2176                                 return FALSE;
2177                         ExpressionTree* et = new ExpressionTree(op);
2178                         report->setRollUpTask(et);
2179                 }
2180                 else if (token == KW("sorttasks"))
2181                 {
2182                         if (!readSorting(report, 0))
2183                                 return FALSE;
2184                 }
2185                 else if (token == KW("hideresource"))
2186                 {
2187                         Operation* op;
2188                         if ((op = readLogicalExpression()) == 0)
2189                                 return FALSE;
2190                         ExpressionTree* et = new ExpressionTree(op);
2191                         report->setHideResource(et);
2192                 }
2193                 else if (token == KW("rollupresource"))
2194                 {
2195                         Operation* op;
2196                         if ((op = readLogicalExpression()) == 0)
2197                                 return FALSE;
2198                         ExpressionTree* et = new ExpressionTree(op);
2199                         report->setRollUpResource(et);
2200                 }
2201                 else if (token == KW("sortresources"))
2202                 {
2203                         if (!readSorting(report, 1))
2204                                 return FALSE;
2205                 }
2206                 else
2207                 {
2208                         fatalError("Illegal attribute");
2209                         return FALSE;
2210                 }
2211         }
2212
2213         if (reportType == KW("htmltaskreport"))
2214                 proj->addHTMLTaskReport((HTMLTaskReport*) report);
2215         else
2216                 proj->addHTMLResourceReport((HTMLResourceReport*) report);
2217
2218         return TRUE;
2219 }
2220
2221 bool
2222 ProjectFile::readHTMLAccountReport()
2223 {
2224         QString token;
2225         if (nextToken(token) != STRING)
2226         {
2227                 fatalError("File name expected");
2228                 return FALSE;
2229         }
2230         
2231         HTMLAccountReport* report;
2232         report = new HTMLAccountReport(proj, token, proj->getStart(),
2233                                                                    proj->getEnd());
2234                 
2235         TokenType tt;
2236         if ((tt = nextToken(token)) != LCBRACE)
2237         {
2238                 returnToken(tt, token);
2239                 return TRUE;
2240         }
2241
2242         for ( ; ; )
2243         {
2244                 if ((tt = nextToken(token)) == RCBRACE)
2245                         break;
2246                 else if (tt != ID)
2247                 {
2248                         fatalError("Attribute ID or '}' expected");
2249                         return FALSE;
2250                 }
2251                 if (token == KW("columns"))
2252                 {
2253                         report->clearColumns();
2254                         for ( ; ; )
2255                         {
2256                                 QString col;
2257                                 if ((tt = nextToken(col)) != ID)
2258                                 {
2259                                         fatalError("Column ID expected");
2260                                         return FALSE;
2261                                 }
2262                                 report->addColumn(col);
2263                                 if ((tt = nextToken(token)) != COMMA)
2264                                 {
2265                                         returnToken(tt, token);
2266                                         break;
2267                                 }
2268                         }
2269                 }
2270                 else if (token == KW("start"))
2271                 {
2272                         if (nextToken(token) != DATE)
2273                         {
2274                                 fatalError("Date expected");
2275                                 return FALSE;
2276                         }
2277                         report->setStart(date2time(token));
2278                 }
2279                 else if (token == KW("end"))
2280                 {
2281                         if (nextToken(token) != DATE)
2282                         {
2283                                 fatalError("Date expected");
2284                                 return FALSE;
2285                         }
2286                         report->setEnd(date2time(token));
2287                 }
2288                 else if (token == KW("headline"))
2289                 {
2290                         if (nextToken(token) != STRING)
2291                         {
2292                                 fatalError("String exptected");
2293                                 return FALSE;
2294                         }
2295                         report->setHeadline(token);
2296                 }
2297                 else if (token == KW("caption"))
2298                 {
2299                         if (nextToken(token) != STRING)
2300                         {
2301                                 fatalError("String exptected");
2302                                 return FALSE;
2303                         }
2304                         report->setCaption(token);
2305                 }
2306                 else if (token == KW("hideplan"))
2307                 {
2308                         report->setHidePlan(TRUE);
2309                 }
2310                 else if (token == KW("showactual"))
2311                 {
2312                         report->setShowActual(TRUE);
2313                 }
2314                 else if (token == KW("accumulate"))
2315                 {
2316                         report->setAccumulate(TRUE);
2317                 }
2318                 else if (token == KW("hideaccount"))
2319                 {
2320                         Operation* op;
2321                         if ((op = readLogicalExpression()) == 0)
2322                                 return FALSE;
2323                         ExpressionTree* et = new ExpressionTree(op);
2324                         report->setHideAccount(et);
2325                 }
2326                 else if (token == KW("rollupaccount"))
2327                 {
2328                         Operation* op;
2329                         if ((op = readLogicalExpression()) == 0)
2330                                 return FALSE;
2331                         ExpressionTree* et = new ExpressionTree(op);
2332                         report->setRollUpAccount(et);
2333                 }
2334                 else if (token == KW("sortaccounts"))
2335                 {
2336                         if (!readSorting(report, 2))
2337                                 return FALSE;
2338                 }
2339                 else
2340                 {
2341                         fatalError("Illegal attribute");
2342                         return FALSE;
2343                 }
2344         }
2345
2346         proj->addHTMLAccountReport(report);
2347
2348         return TRUE;
2349 }
2350
2351 bool
2352 ProjectFile::readExportReport()
2353 {
2354         QString token;
2355         if (nextToken(token) != STRING)
2356         {
2357                 fatalError("File name expected");
2358                 return FALSE;
2359         }
2360         
2361         ExportReport* report;
2362         report = new ExportReport(proj, token);
2363                 
2364         TokenType tt;
2365         if ((tt = nextToken(token)) != LCBRACE)
2366         {
2367                 returnToken(tt, token);
2368                 return TRUE;
2369         }
2370
2371         for ( ; ; )
2372         {
2373                 if ((tt = nextToken(token)) == RCBRACE)
2374                         break;
2375                 else if (tt != ID)
2376                 {
2377                         fatalError("Attribute ID or '}' expected");
2378                         return FALSE;
2379                 }
2380                 
2381                 if (token == KW("hidetask"))
2382                 {
2383                         Operation* op;
2384                         if ((op = readLogicalExpression()) == 0)
2385                                 return FALSE;
2386                         ExpressionTree* et = new ExpressionTree(op);
2387                         report->setHideTask(et);
2388                 }
2389                 else if (token == KW("rolluptask"))
2390                 {
2391                         Operation* op;
2392                         if ((op = readLogicalExpression()) == 0)
2393                                 return FALSE;
2394                         ExpressionTree* et = new ExpressionTree(op);
2395                         report->setRollUpTask(et);
2396                 }
2397                 else if (token == KW("hideresource"))
2398                 {
2399                         Operation* op;
2400                         if ((op = readLogicalExpression()) == 0)
2401                                 return FALSE;
2402                         ExpressionTree* et = new ExpressionTree(op);
2403                         report->setHideResource(et);
2404                 }
2405                 else if (token == KW("rollupresource"))
2406                 {
2407                         Operation* op;
2408                         if ((op = readLogicalExpression()) == 0)
2409                                 return FALSE;
2410                         ExpressionTree* et = new ExpressionTree(op);
2411                         report->setRollUpResource(et);
2412                 }
2413                 else
2414                 {
2415                         fatalError("Illegal attribute");
2416                         return FALSE;
2417                 }
2418         }
2419
2420         proj->addExportReport(report);
2421
2422         return TRUE;
2423 }
2424
2425 Operation*
2426 ProjectFile::readLogicalExpression(int precedence)
2427 {
2428         Operation* op;
2429         QString token;
2430         TokenType tt;
2431
2432         if ((tt = nextToken(token)) == ID || tt == RELATIVE_ID)
2433         {
2434                 if (proj->isAllowedFlag(token))
2435                         op = new Operation(token);
2436                 else if (proj->getTask(token))
2437                         op = new Operation(Operation::TaskId, token);
2438                 else if (proj->getResource(token))
2439                         op = new Operation(Operation::ResourceId, token);
2440                 else if (proj->getAccount(token))
2441                         op = new Operation(Operation::AccountId, token);
2442                 else if (ExpressionTree::isFunction(token))
2443                 {
2444                         if ((op = readFunctionCall(token)) == 0)
2445                                 return 0;
2446                 }
2447                 else
2448                 {
2449                         fatalError(QString("Flag or function '") + token + "' is unknown.");
2450                         return 0;
2451                 }
2452         }
2453         else if (tt == INTEGER)
2454         {
2455                 op = new Operation(token.toLong());
2456         }
2457         else if (tt == TILDE)
2458         {
2459                 if ((op = readLogicalExpression(1)) == 0)
2460                 {
2461                         return 0;
2462                 }
2463                 op = new Operation(op, Operation::Not);
2464         }
2465         else if (tt == LBRACE)
2466         {
2467                 if ((op = readLogicalExpression()) == 0)
2468                 {
2469                         return 0;
2470                 }
2471                 if ((tt = nextToken(token)) != RBRACE)
2472                 {
2473                         fatalError("')' expected");
2474                         return 0;
2475                 }
2476         }
2477         else
2478         {
2479                 fatalError("Logical expression expected");
2480                 return 0;
2481         }
2482         
2483         if (precedence < 1)
2484         {
2485                 if ((tt = nextToken(token)) != AND && tt != OR)
2486                 {
2487                         returnToken(tt, token);
2488                 }
2489                 else if (tt == AND)
2490                 {
2491                         Operation* op2 = readLogicalExpression();
2492                         op = new Operation(op, Operation::And, op2);
2493                 }
2494                 else if (tt == OR)
2495                 {
2496                         Operation* op2 = readLogicalExpression();
2497                         op = new Operation(op, Operation::Or, op2);
2498                 }
2499         }
2500
2501         return op;
2502 }
2503
2504 Operation*
2505 ProjectFile::readFunctionCall(const QString& name)
2506 {
2507         QString token;
2508         TokenType tt;
2509         
2510         if ((tt = nextToken(token)) != LBRACE)
2511         {
2512                 fatalError("'(' expected");
2513                 return 0;
2514         }
2515         QPtrList<Operation> args;
2516         for (int i = 0; i < ExpressionTree::arguments(name); i++)
2517         {
2518                 Operation* op;
2519                 if ((op = readLogicalExpression()) == 0)
2520                         return 0;
2521                 args.append(op);
2522                 if ((i < ExpressionTree::arguments(name) - 1) &&
2523                         nextToken(token) != COMMA)
2524                 {
2525                         fatalError("Comma expected");
2526                         return 0;
2527                 }
2528         }
2529         if ((tt = nextToken(token)) != RBRACE)
2530         {
2531                 fatalError("')' expected");
2532                 return 0;
2533         }
2534         return new Operation(name, args);
2535 }
2536
2537 bool
2538 ProjectFile::readSorting(Report* report, int which)
2539 {
2540         QString token;
2541
2542         nextToken(token);
2543         CoreAttributesList::SortCriteria sorting;
2544         if (token == KW("tree"))
2545                 sorting = CoreAttributesList::TreeMode;
2546         else if (token == KW("indexup"))
2547                 sorting = CoreAttributesList::IndexUp;
2548         else if (token == KW("indexdown"))
2549                 sorting = CoreAttributesList::IndexDown;
2550         else if (token == KW("idup"))
2551                 sorting = CoreAttributesList::IdUp;
2552         else if (token == KW("iddown"))
2553                 sorting = CoreAttributesList::IdDown;
2554         else if (token == KW("fullnameup"))
2555                 sorting = CoreAttributesList::FullNameUp;
2556         else if (token == KW("fullnamedown"))
2557                 sorting = CoreAttributesList::FullNameDown;
2558         else if (token == KW("nameup"))
2559                 sorting = CoreAttributesList::NameUp;
2560         else if (token == KW("namedown"))
2561                 sorting = CoreAttributesList::NameDown;
2562         else if (token == KW("startup"))
2563                 sorting = CoreAttributesList::StartUp;
2564         else if (token == KW("startdown"))
2565                 sorting = CoreAttributesList::StartDown;
2566         else if (token == KW("endup"))
2567                 sorting = CoreAttributesList::EndUp;
2568         else if (token == KW("enddown"))
2569                 sorting = CoreAttributesList::EndDown;
2570         else if (token == KW("priorityup"))
2571                 sorting = CoreAttributesList::PrioUp;
2572         else if (token == KW("prioritydown"))
2573                 sorting = CoreAttributesList::PrioDown;
2574         else if (token == KW("responsibleup"))
2575                 sorting = CoreAttributesList::ResponsibleUp;
2576         else if (token == KW("responsibledown"))
2577                 sorting = CoreAttributesList::ResponsibleDown;
2578         else if (token == KW("mineffortup"))
2579                 sorting = CoreAttributesList::MinEffortUp;
2580         else if (token == KW("mineffortdown"))
2581                 sorting = CoreAttributesList::MinEffortDown;
2582         else if (token == KW("maxeffortup"))
2583                 sorting = CoreAttributesList::MaxEffortUp;
2584         else if (token == KW("maxeffortdown"))
2585                 sorting = CoreAttributesList::MaxEffortDown;
2586         else if (token == KW("rateup"))
2587                 sorting = CoreAttributesList::RateUp;
2588         else if (token == KW("ratedown"))
2589                 sorting = CoreAttributesList::RateDown;
2590         else if (token == KW("kotrusidup"))
2591                 sorting = CoreAttributesList::KotrusIdUp;
2592         else if (token == KW("kotrusiddown"))
2593                 sorting = CoreAttributesList::KotrusIdDown;
2594         else
2595         {
2596                 fatalError("Sorting criteria expected");
2597                 return FALSE;
2598         }
2599
2600         switch (which)
2601         {
2602         case 0:
2603                 report->setTaskSorting(sorting);
2604                 break;
2605         case 1:
2606                 report->setResourceSorting(sorting);
2607                 break;
2608         case 2:
2609                 report->setAccountSorting(sorting);
2610                 break;
2611         default:
2612                 qFatal("readSorting: Unknown sorting attribute");
2613                 return FALSE;
2614         }
2615
2616         return TRUE;
2617 }
2618
2619 time_t
2620 ProjectFile::date2time(const QString& date)
2621 {
2622         int y, m, d, hour, min, sec;
2623         char tZone[16] = "";
2624         char savedTZ[16] = "";
2625         if (sscanf(date, "%d-%d-%d-%d:%d:%d-%s",
2626                            &y, &m, &d, &hour, &min, &sec, tZone) == 7 ||
2627                 (sec = 0) ||    // set sec to 0
2628                 sscanf(date, "%d-%d-%d-%d:%d-%s",
2629                            &y, &m, &d, &hour, &min, tZone) == 6)
2630         {
2631                 if (getenv("TZ"))
2632                         strcpy(getenv("TZ"), savedTZ);
2633                 const char* tz;
2634                 if ((tz = timezone2tz(tZone)) == 0)
2635                         fatalError(QString("Illegal timezone %s").arg(tZone));
2636                 else
2637                         setenv("TZ", tz, 1);
2638         }
2639         else if (sscanf(date, "%d-%d-%d-%d:%d:%d",
2640                                         &y, &m, &d, &hour, &min, &sec) == 6)
2641                 tZone[0] = '\0';
2642         else if (sscanf(date, "%d-%d-%d-%d:%d", &y, &m, &d, &hour, &min) == 5)
2643         {
2644                 sec = 0;
2645                 tZone[0] = '\0';
2646         }
2647         else if (sscanf(date, "%d-%d-%d", &y, &m, &d) == 3)
2648         {
2649                 tZone[0] = '\0';
2650                 hour = min = sec = 0;
2651         }
2652         else
2653         {
2654                 qFatal("Illegal date: %s", date.latin1());
2655                 return 0;
2656         }
2657
2658         if (y < 1970)
2659         {
2660                 fatalError("Year must be larger than 1969");
2661                 y = 1970;
2662         }
2663         if (m < 1 || m > 12)
2664         {
2665                 fatalError("Month must be between 1 and 12");
2666                 m = 1;
2667         }
2668         if (d < 1 || d > 31)
2669         {
2670                 fatalError("Day must be between 1 and 31");
2671                 d = 1;
2672         }
2673
2674         struct tm t = { sec, min, hour, d, m - 1, y - 1900, 0, 0, -1, 0, 0 };
2675         time_t localTime = mktime(&t);
2676
2677         if (strcmp(savedTZ, "") != 0) 
2678                 setenv("TZ", savedTZ, 1);
2679         else
2680                 unsetenv("TZ");
2681
2682         return localTime;
2683 }
2684
2685 time_t
2686 ProjectFile::hhmm2time(const QString& hhmm)
2687 {
2688         int hour, min;
2689         sscanf(hhmm, "%d:%d", &hour, &min);
2690         return hour * 60 * 60 + min * 60;
2691 }