OSDN Git Service

* Support for later completion of task and resources added. By
[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]-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                                 }
262                                 int i = 0;
263                                 if (c == '-')
264                                 {
265                                         token += c;
266                                         while ((c = getC()) != EOF && isalnum(c) && i++ < 12)
267                                                 token += c;
268                                 }
269                                 ungetC(c);
270                                 return DATE;
271                         }
272                         else if (c == '.')
273                         {
274                                 // must be a real number
275                                 token += c;
276                                 while ((c = getC()) != EOF && isdigit(c))
277                                         token += c;
278                                 ungetC(c);
279                                 return REAL;
280                         }
281                         else if (c == ':')
282                         {
283                                 // must be a time (HH:MM)
284                                 token += c;
285                                 for (int i = 0; i < 2; i++)
286                                 {
287                                         if ((c = getC()) != EOF && isdigit(c))
288                                                 token += c;
289                                         else
290                                         {
291                                                 fatalError("2 digits minutes expected");
292                                                 return EndOfFile;
293                                         }
294                                 }
295                                 return HOUR;
296                         }
297                         else
298                         {
299                                 ungetC(c);
300                                 return INTEGER;
301                         }
302                 }
303                 else if (c == '"')
304                 {
305                         // quoted string
306                         while ((c = getC()) != EOF && c != '"')
307                         {
308                                 if (c == '\n')
309                                         currLine++;
310                                 token += c;
311                         }
312                         if (c == EOF)
313                         {
314                                 fatalError("Non terminated string");
315                                 return EndOfFile;
316                         }
317                         return STRING;
318                 }
319                 else if (c == '[')
320                 {
321                         int nesting = 0;
322                         while ((c = getC(FALSE)) != EOF && (c != ']' || nesting > 0))
323                         {
324                                 if (c == '[')
325                                         nesting++;
326                                 else if (c == ']')
327                                         nesting--;
328                                 if (c == '\n')
329                                         currLine++;
330                                 token += c;
331                         }
332                         if (c == EOF)
333                         {
334                                 fatalError("Non terminated macro definition");
335                                 return EndOfFile;
336                         }
337                         return MacroBody;
338                 }
339                 else
340                 {
341                         token += c;
342                         switch (c)
343                         {
344                         case '{':
345                                 return LCBRACE;
346                         case '}':
347                                 return RCBRACE;
348                         case '(':
349                                 return LBRACE;
350                         case ')':
351                                 return RBRACE;
352                         case ',':
353                                 return COMMA;
354                         case '~':
355                                 return TILDE;
356                         case '-':
357                                 return MINUS;
358                         case '&':
359                                 return AND;
360                         case '|':
361                                 return OR;
362                         default:
363                                 fatalError(QString("Illegal character '") + c + "'");
364                                 return EndOfFile;
365                         }
366                 }
367         }
368 }
369
370 bool
371 FileInfo::readMacroCall()
372 {
373         QString id;
374         TokenType tt;
375         if ((tt = nextToken(id)) != ID && tt != INTEGER)
376         {
377                 fatalError("Macro ID expected");
378                 return FALSE;
379         }
380         QString token;
381         // Store all arguments in a newly created string list.
382         QStringList* sl = new QStringList;
383         while ((tt = nextToken(token)) == STRING)
384                 sl->append(token);
385         if (tt != RCBRACE)
386         {
387                 fatalError("'}' expected");
388                 return FALSE;
389         }
390
391         // push string list to global argument stack
392         pf->getMacros().pushArguments(sl);
393
394         // expand the macro
395         QString macro = pf->getMacros().expand(id);
396         if (macro.isNull())
397         {
398                 fatalError(QString("Unknown macro ") + id);
399                 return FALSE;
400         }
401
402         // Push pointer to macro on stack. Needed for error handling.
403         macroStack.append(pf->getMacros().getMacro(id));
404
405         // mark end of macro
406         ungetC(EOM);
407         // push expanded macro reverse into ungetC buffer.
408         for (int i = macro.length() - 1; i >= 0; --i)
409                 ungetC(macro[i].latin1());
410         return TRUE;
411 }
412
413 void
414 FileInfo::returnToken(TokenType tt, const QString& buf)
415 {
416         if (tokenTypeBuf != INVALID)
417         {
418                 qFatal("Internal Error: Token buffer overflow!");
419                 return;
420         }
421         tokenTypeBuf = tt;
422         tokenBuf = buf;
423 }
424
425 void
426 FileInfo::fatalError(const QString& msg)
427 {
428         if (macroStack.isEmpty())
429         {
430                 qWarning("%s:%d:%s", file.latin1(), currLine, msg.latin1());
431                 qWarning("%s", lineBuf.latin1());
432         }
433         else
434         {
435                 qWarning("Error in expanded macro");
436                 qWarning("%s:%d: %s",
437                                  macroStack.last()->getFile().latin1(),
438                                  macroStack.last()->getLine(), msg.latin1());
439                 qWarning("%s", lineBuf.latin1());
440         }
441 }
442
443 ProjectFile::ProjectFile(Project* p)
444 {
445         proj = p;
446
447         openFiles.setAutoDelete(TRUE);
448 }
449
450 bool
451 ProjectFile::open(const QString& file)
452 {
453         QString absFileName = file;
454         if (absFileName[0] != '/')
455         {
456                 if (openFiles.isEmpty())
457                 {
458                         char buf[1024];
459                         if (getcwd(buf, 1023) != 0)
460                                 absFileName = QString(buf) + "/" + absFileName;
461                         else
462                                 qFatal("ProjectFile::open(): getcwd failed");
463                 }
464                 else
465                         absFileName = openFiles.last()->getPath() + absFileName;
466                 if (debugLevel > 2)
467                         qWarning("Expanded filename to %s", absFileName.latin1());
468         }
469         int end = 0;
470         while (absFileName.find("/../", end) >= 0)
471         {
472                 end = absFileName.find("/../");
473                 int start = absFileName.findRev('/', end - 1);
474                 if (start < 0)
475                         start = 0;
476                 else
477                         start++;        // move after '/'
478                 if (start < end && absFileName.mid(start, end - start) != "..")
479                         absFileName.remove(start, end + strlen("/../") - start);
480                 end += strlen("/..");
481         }
482
483         // Make sure that we include each file only once.
484         if (includedFiles.findIndex(absFileName) != -1)
485         {
486                 if (debugLevel > 2)
487                         qWarning("Ignoring already read file %s",
488                                          absFileName.latin1());
489                 return TRUE;
490         }
491                 
492         FileInfo* fi = new FileInfo(this, absFileName);
493
494         if (!fi->open())
495         {
496                 qFatal("Cannot open '%s'", absFileName.latin1());
497                 return FALSE;
498         }
499
500         if (debugLevel > 2)
501                 qWarning("Reading %s", absFileName.latin1());
502
503         openFiles.append(fi);
504         includedFiles.append(absFileName);
505         return TRUE;
506 }
507
508 bool
509 ProjectFile::close()
510 {
511         bool error = FALSE;
512
513         FileInfo* fi = openFiles.getLast();
514
515         if (!fi->close())
516                 error = TRUE;
517         openFiles.removeLast();
518
519         return error;
520 }
521
522 bool
523 ProjectFile::parse()
524 {
525         TokenType tt;
526         QString token;
527
528         for ( ; ; )
529         {
530                 switch (tt = nextToken(token))
531                 {
532                 case EndOfFile:
533                         return TRUE;
534                 case ID:
535                         if (token == KW("task"))
536                         {
537                                 if (!readTask(0))
538                                         return FALSE;
539                                 break;
540                         }
541                         if (token == KW("account"))
542                         {
543                                 if (!readAccount(0))
544                                         return FALSE;
545                                 break;
546                         }
547                         else if (token == KW("resource"))
548                         {
549                                 if (!readResource(0))
550                                         return FALSE;
551                                 break;
552                         }
553                         else if (token == KW("shift"))
554                         {
555                                 if (!readShift(0))
556                                         return FALSE;
557                                 break;  
558                         }
559                         else if (token == KW("vacation"))
560                         {
561                                 time_t from, to;
562                                 bool isResourceVacation;
563                                 QString name;
564                                 if (!readVacation(from, to, TRUE, &name,
565                                                                   &isResourceVacation))
566                                         return FALSE;
567                                 if (isResourceVacation)
568                                         proj->getResource(name)->addVacation(
569                                                 new Interval(from, to));
570                                 proj->addVacation(name, from, to);
571                                 break;
572                         }
573                         else if (token == KW("priority"))
574                         {
575                                 int priority;
576                                 if (!readPriority(priority))
577                                         return FALSE;
578                                 proj->setPriority(priority);
579                                 break;
580                         }
581                         else if (token == KW("now"))
582                         {
583                                 if (nextToken(token) != DATE)
584                                 {
585                                         fatalError("Date expected");
586                                         return FALSE;
587                                 }
588                                 proj->setNow(date2time(token));
589                                 break;
590                         }
591                         else if (token == KW("mineffort"))
592                         {
593                                 if (nextToken(token) != REAL)
594                                 {
595                                         fatalError("Real value exptected");
596                                         return FALSE;
597                                 }
598                                 proj->setMinEffort(token.toDouble());
599                                 break;
600                         }
601                         else if (token == KW("maxeffort"))
602                         {
603                                 if (nextToken(token) != REAL)
604                                 {
605                                         fatalError("Real value exptected");
606                                         return FALSE;
607                                 }
608                                 proj->setMaxEffort(token.toDouble());
609                                 break;
610                         }
611                         else if (token == KW("rate"))
612                         {
613                                 if (nextToken(token) != REAL)
614                                 {
615                                         fatalError("Real value exptected");
616                                         return FALSE;
617                                 }
618                                 proj->setRate(token.toDouble());
619                                 break;
620                         }
621                         else if (token == KW("currency"))
622                         {
623                                 if (nextToken(token) != STRING)
624                                 {
625                                         fatalError("String expected");
626                                         return FALSE;
627                                 }
628                                 proj->setCurrency(token);
629                                 break;
630                         }
631                         else if (token == KW("currencydigits"))
632                         {
633                                 if (nextToken(token) != INTEGER)
634                                 {
635                                         fatalError("Integer value expected");
636                                         return FALSE;
637                                 }
638                                 proj->setCurrencyDigits(token.toInt());
639                                 break;
640                         }
641                         else if (token == KW("timingresolution"))
642                         {
643                                 ulong resolution;
644                                 if (!readTimeValue(resolution))
645                                         return FALSE;
646                                 if (proj->resourceCount() > 0)
647                                 {
648                                         fatalError("The timing resolution cannot be changed after "
649                                                            "resources have been declared.");
650                                         return FALSE;
651                                 }
652                                 if (resolution < 60 * 5)
653                                 {
654                                         fatalError("timing resolution must be at least 5 min");
655                                         return FALSE;
656                                 }
657                                 proj->setScheduleGranularity(resolution);
658                                 break;
659                         }
660                         else if (token == KW("copyright"))
661                         {
662                                 if (nextToken(token) != STRING)
663                                 {
664                                         fatalError("String expected");
665                                         return FALSE;
666                                 }
667                                 proj->setCopyright(token);
668                                 break;
669                         }
670                         else if (token == KW("include"))
671                         {
672                                 if (!readInclude())
673                                         return FALSE;
674                                 break;
675                         }
676                         else if (token == KW("macro"))
677                         {
678                                 QString id;
679                                 if (nextToken(id) != ID)
680                                 {
681                                         fatalError("Macro ID expected");
682                                         return FALSE;
683                                 }
684                                 QString file = openFiles.last()->getFile();
685                                 uint line = openFiles.last()->getLine();
686                                 if (nextToken(token) != MacroBody)
687                                 {
688                                         fatalError("Macro body expected");
689                                         return FALSE;
690                                 }
691                                 Macro* macro = new Macro(id, token, file, line);
692                                 if (!macros.addMacro(macro))
693                                 {
694                                         fatalError("Macro has been defined already");
695                                         delete macro;
696                                         return FALSE;
697                                 }
698                                 break;
699                         }
700                         else if (token == KW("flags"))
701                         {
702                                 for ( ; ; )
703                                 {
704                                         QString flag;
705                                         if (nextToken(flag) != ID)
706                                         {
707                                                 fatalError("flag ID expected");
708                                                 return FALSE;
709                                         }
710
711                                         /* Flags can be declared multiple times, but we
712                                          * register a flag only once. */
713                                         if (!proj->isAllowedFlag(flag))
714                                                 proj->addAllowedFlag(flag);
715
716                                         if ((tt = nextToken(token)) != COMMA)
717                                         {
718                                                 openFiles.last()->returnToken(tt, token);
719                                                 break;
720                                         }
721                                 }
722                                 break;
723                         }
724                         else if (token == KW("project"))
725                         {
726                                 if (nextToken(token) != ID)
727                                 {
728                                         fatalError("Project ID expected");
729                                         return FALSE;
730                                 }
731                                 if (!proj->addId(token))
732                                 {
733                                         fatalError(QString().sprintf(
734                                                 "Project ID %s has already been registered",
735                                                 token.latin1()));
736                                         return FALSE;
737                                 }
738                                 if (nextToken(token) != STRING)
739                                 {
740                                         fatalError("Project name expected");
741                                         return FALSE;
742                                 }
743                                 proj->setName(token);
744                                 if (nextToken(token) != STRING)
745                                 {
746                                         fatalError("Version string expected");
747                                         return FALSE;
748                                 }
749                                 proj->setVersion(token);
750                                 time_t start, end;
751                                 if (nextToken(token) != DATE)
752                                 {
753                                         fatalError("Start date expected");
754                                         return FALSE;
755                                 }
756                                 start = date2time(token);
757                                 if (nextToken(token) != DATE)
758                                 {
759                                         fatalError("End date expected");
760                                         return FALSE;
761                                 }
762                                 end = date2time(token);
763                                 if (end <= start)
764                                 {
765                                         fatalError("End date must be larger then start date");
766                                         return FALSE;
767                                 }
768                                 proj->setStart(start);
769                                 proj->setEnd(end);
770                                 break;
771                         }
772                         else if (token == KW("projectid"))
773                         {
774                                 for ( ; ; )
775                                 {
776                                         QString id;
777                                         if (nextToken(id) != ID)
778                                         {
779                                                 fatalError("Project ID expected");
780                                                 return FALSE;
781                                         }
782
783                                         if (!proj->addId(id))
784                                         {
785                                                 fatalError(QString().sprintf(
786                                                         "Project ID %s has already been registered",
787                                                         id.latin1()));
788                                                 return FALSE;
789                                         }
790
791                                         if ((tt = nextToken(token)) != COMMA)
792                                         {
793                                                 openFiles.last()->returnToken(tt, token);
794                                                 break;
795                                         }
796                                 }
797                                 break;
798                         }
799                         else if (token == KW("xmltaskreport"))
800                         {
801                            if( !readXMLTaskReport())
802                               return FALSE;
803                            break;
804                         }
805 #ifdef HAVE_KDE
806                         else if (token == "icalreport" )
807                         {
808                            if( !readICalTaskReport())
809                               return FALSE;
810                            break;
811                         }
812 #endif
813                         else if (token == KW("htmltaskreport") ||
814                                          token == KW("htmlresourcereport"))
815                         {
816                                 if (!readHTMLReport(token))
817                                         return FALSE;
818                                 break;
819                         }
820                         else if (token == KW("htmlaccountreport"))
821                         {
822                                 if (!readHTMLAccountReport())
823                                         return FALSE;
824                                 break;
825                         }
826                         else if (token == KW("export"))
827                         {
828                                 if (!readExportReport())
829                                         return FALSE;
830                                 break;
831                         }
832                         else if (token == KW("kotrusmode"))
833                         {
834                                 if (nextToken(token) != STRING ||
835                                         (token != KW("db") && token != KW("xml") &&
836                                          token != KW("nokotrus")))
837                                 {
838                                         fatalError("Unknown kotrus mode");
839                                         return FALSE;
840                                 }
841                                 if (token != KW("nokotrus"))
842                                 {
843                                         Kotrus* kotrus = new Kotrus();
844                                         kotrus->setKotrusMode(token);
845                                         proj->setKotrus(kotrus);
846                                 }
847                                 break;
848                         }
849                         else if (token == KW("supplement"))
850                         {
851                                 if (nextToken(token) != ID ||
852                                         (token != KW("task") && (token != KW("resource"))))
853                                 {
854                                         fatalError("'task' or 'resource' expected");
855                                         return FALSE;
856                                 }
857                                 if ((token == "task" && !readTaskSupplement()) ||
858                                         (token == "resource" && !readResourceSupplement()))
859                                         return FALSE;
860                                 break;
861                         }       
862                         // break missing on purpose!
863                 default:
864                         fatalError(QString("Syntax Error at '") + token + "'!");
865                         return FALSE;
866                 }
867         }
868
869         return TRUE;
870 }
871
872 TokenType
873 ProjectFile::nextToken(QString& buf)
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                                                 openFiles.last()->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                                                 openFiles.last()->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                                                 openFiles.last()->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, bool* isResourceVacation)
1297 {
1298         TokenType tt;
1299         if (readName)
1300         {
1301                 /* If we find a string then we expect a global vacation
1302                  * definition. If we find an ID then this is an out-of-scope
1303                  * vacation definition for the resource with this particular
1304                  * ID. */
1305                 *isResourceVacation = FALSE;
1306                 if ((tt = nextToken(*n)) == STRING)
1307                         ;       // We don't have to do anything
1308 #if 0
1309                 else if (tt == ID)
1310                 {
1311                         if (!proj->getResource(*n))
1312                         {
1313                                 fatalError(QString().sprintf(
1314                                         "Resource %s is undefined", n->latin1()));
1315                                 return FALSE;
1316                         }
1317                         *isResourceVacation = TRUE;
1318                 }
1319 #endif
1320                 else
1321                 {
1322                         fatalError("String expected");
1323                         return FALSE;
1324                 }
1325         }
1326         QString start;
1327         if ((tt = nextToken(start)) != DATE)
1328         {
1329                 fatalError("Date expected");
1330                 return FALSE;
1331         }
1332         QString token;
1333         if ((tt = nextToken(token)) != MINUS)
1334         {
1335                 // vacation e. g. 2001-11-28
1336                 openFiles.last()->returnToken(tt, token);
1337                 from = date2time(start);
1338                 to = sameTimeNextDay(date2time(start)) - 1;
1339         }
1340         else
1341         {
1342                 // vacation e. g. 2001-11-28 - 2001-11-30
1343                 QString end;
1344                 if ((tt = nextToken(end)) != DATE)
1345                 {
1346                         fatalError("Date expected");
1347                         return FALSE;
1348                 }
1349                 from = date2time(start);
1350                 if (date2time(start) > date2time(end))
1351                 {
1352                         fatalError("Vacation must start before end");
1353                         return FALSE;
1354                 }
1355                 to = date2time(end) - 1;
1356         }
1357         return TRUE;
1358 }
1359
1360 bool
1361 ProjectFile::readResource(Resource* parent)
1362 {
1363         // Syntax: 'resource id "name" { ... }
1364         QString id;
1365         if (nextToken(id) != ID)
1366         {
1367                 fatalError("ID expected");
1368                 return FALSE;
1369         }
1370         QString name;
1371         if (nextToken(name) != STRING)
1372         {
1373                 fatalError("String expected");
1374                 return FALSE;
1375         }
1376
1377         if (proj->getResource(id))
1378         {
1379                 fatalError(QString().sprintf(
1380                         "Resource %s has already been defined", id.latin1()));
1381                 return FALSE;
1382         }
1383
1384         Resource* r = new Resource(proj, id, name, parent);
1385
1386         TokenType tt;
1387         QString token;
1388         if ((tt = nextToken(token)) == LCBRACE)
1389         {
1390                 // read optional attributes
1391                 if (!readResourceBody(r))
1392                         return FALSE;
1393         }
1394         else
1395                 openFiles.last()->returnToken(tt, token);
1396
1397         proj->addResource(r);
1398
1399         return TRUE;
1400 }
1401
1402 bool
1403 ProjectFile::readResourceSupplement()
1404 {
1405         QString token;
1406         Resource* r;
1407         if (nextToken(token) != ID || (r = proj->getResource(token)) == 0)
1408         {
1409                 fatalError("Already defined resource ID expected");
1410                 return FALSE;
1411         }
1412         if (nextToken(token) != LCBRACE)
1413         {
1414                 fatalError("'{' expected");
1415                 return FALSE;
1416         }
1417         return readResourceBody(r);
1418 }
1419
1420 bool
1421 ProjectFile::readResourceBody(Resource* r)
1422 {
1423         QString token;
1424         TokenType tt;
1425
1426         while ((tt = nextToken(token)) != RCBRACE)
1427         {
1428                 if (tt != ID)
1429                 {
1430                         fatalError(QString("Unknown attribute '") + token + "'");
1431                         return FALSE;
1432                 }
1433                 if (token == KW("resource"))
1434                 {
1435                         if (!readResource(r))
1436                                 return FALSE;
1437                 }
1438                 else if (token == KW("mineffort"))
1439                 {
1440                         if (nextToken(token) != REAL)
1441                         {
1442                                 fatalError("Real value exptected");
1443                                 return FALSE;
1444                         }
1445                         r->setMinEffort(token.toDouble());
1446                 }
1447                 else if (token == KW("maxeffort"))
1448                 {
1449                         if (nextToken(token) != REAL)
1450                         {
1451                                 fatalError("Real value exptected");
1452                                 return FALSE;
1453                         }
1454                         r->setMaxEffort(token.toDouble());
1455                 }
1456                 else if (token == KW("efficiency"))
1457                 {
1458                         if (nextToken(token) != REAL)
1459                         {
1460                                 fatalError("Read value expected");
1461                                 return FALSE;
1462                         }
1463                         r->setEfficiency(token.toDouble());
1464                 }
1465                 else if (token == KW("rate"))
1466                 {
1467                         if (nextToken(token) != REAL)
1468                         {
1469                                 fatalError("Real value exptected");
1470                                 return FALSE;
1471                         }
1472                         r->setRate(token.toDouble());
1473                 }
1474                 else if (token == KW("kotrusid"))
1475                 {
1476                         if (nextToken(token) != STRING)
1477                         {
1478                                 fatalError("String expected");
1479                                 return FALSE;
1480                         }
1481                         r->setKotrusId(token);
1482                 }
1483                 else if (token == KW("vacation"))
1484                 {
1485                         time_t from, to;
1486                         if (!readVacation(from, to))
1487                                 return FALSE;
1488                         r->addVacation(new Interval(from, to));
1489                 }
1490                 else if (token == KW("workinghours"))
1491                 {
1492                         int dow;
1493                         QPtrList<Interval>* l = new QPtrList<Interval>();
1494                         if (!readWorkingHours(dow, l))
1495                                 return FALSE;
1496
1497                         r->setWorkingHours(dow, l);
1498                 }
1499                 else if (token == KW("shift"))
1500                 {
1501                         QString id;
1502                         if (nextToken(id) != ID)
1503                         {
1504                                 fatalError("Shift ID expected");
1505                                 return FALSE;
1506                         }
1507                         Shift* s;
1508                         if ((s = proj->getShift(id)) == 0)
1509                         {
1510                                 fatalError("Unknown shift");
1511                                 return FALSE;
1512                         }
1513                         time_t from, to;
1514                         if (!readVacation(from, to))
1515                                 return FALSE;
1516                         if (!r->addShift(Interval(from, to), s))
1517                         {
1518                                 fatalError("Shift interval overlaps with other");
1519                                 return FALSE;
1520                         }
1521                 }
1522                 else if (token == KW("flags"))
1523                 {
1524                         for ( ; ; )
1525                         {
1526                                 QString flag;
1527                                 if (nextToken(flag) != ID || !proj->isAllowedFlag(flag))
1528                                 {
1529                                         fatalError("flag unknown");
1530                                         return FALSE;
1531                                 }
1532                                 r->addFlag(flag);
1533                                 if ((tt = nextToken(token)) != COMMA)
1534                                 {
1535                                         openFiles.last()->returnToken(tt, token);
1536                                         break;
1537                                 }
1538                         }
1539                 }
1540                 else if (token == KW("include"))
1541                 {
1542                         if (!readInclude())
1543                                 return FALSE;
1544                         break;
1545                 }
1546                 else
1547                 {
1548                         fatalError(QString("Unknown attribute '") + token + "'");
1549                         return FALSE;
1550                 }
1551         }
1552
1553         return TRUE;
1554 }
1555
1556 bool
1557 ProjectFile::readShift(Shift* parent)
1558 {
1559         // Syntax: 'shift id "name" { ... }
1560         QString id;
1561         if (nextToken(id) != ID)
1562         {
1563                 fatalError("ID expected");
1564                 return FALSE;
1565         }
1566         QString name;
1567         if (nextToken(name) != STRING)
1568         {
1569                 fatalError("String expected");
1570                 return FALSE;
1571         }
1572
1573         if (proj->getShift(id))
1574         {
1575                 fatalError(QString().sprintf(
1576                         "Shift %s has already been defined", id.latin1()));
1577                 return FALSE;
1578         }
1579
1580         Shift* s = new Shift(proj, id, name, parent);
1581
1582         TokenType tt;
1583         QString token;
1584         if ((tt = nextToken(token)) == LCBRACE)
1585         {
1586                 // read optional attributes
1587                 while ((tt = nextToken(token)) != RCBRACE)
1588                 {
1589                         if (tt != ID)
1590                         {
1591                                 fatalError(QString("Unknown attribute '") + token + "'");
1592                                 return FALSE;
1593                         }
1594                         if (token == KW("shift"))
1595                         {
1596                                 if (!readShift(s))
1597                                         return FALSE;
1598                         }
1599                         else if (token == KW("workinghours"))
1600                         {
1601                                 int dow;
1602                                 QPtrList<Interval>* l = new QPtrList<Interval>();
1603                                 if (!readWorkingHours(dow, l))
1604                                         return FALSE;
1605                                 
1606                                 s->setWorkingHours(dow, l);
1607                         }
1608                         else if (token == KW("include"))
1609                         {
1610                                 if (!readInclude())
1611                                         return FALSE;
1612                                 break;
1613                         }
1614                         else
1615                         {
1616                                 fatalError(QString("Unknown attribute '") + token + "'");
1617                                 return FALSE;
1618                         }
1619                 }
1620         }
1621         else
1622                 openFiles.last()->returnToken(tt, token);
1623
1624         proj->addShift(s);
1625
1626         return TRUE;
1627 }
1628
1629 bool
1630 ProjectFile::readAccount(Account* parent)
1631 {
1632         // Syntax: 'account id "name" { ... }
1633         QString id;
1634         if (nextToken(id) != ID)
1635         {
1636                 fatalError("ID expected");
1637                 return FALSE;
1638         }
1639
1640         if (proj->getAccount(id))
1641         {
1642                 fatalError(QString().sprintf(
1643                         "Account %s has already been defined", id.latin1()));
1644                 return FALSE;
1645         }
1646
1647         QString name;
1648         if (nextToken(name) != STRING)
1649         {
1650                 fatalError("String expected");
1651                 return FALSE;
1652         }
1653         Account::AccountType acctType;
1654         if (parent == 0)
1655         {
1656                 /* Only accounts with no parent can have a type specifier. All
1657                  * sub accounts inherit the type of the parent. */
1658                 QString at;
1659                 if (nextToken(at) != ID && (at != KW("cost") ||
1660                                                                         at != KW("revenue")))
1661                 {
1662                         fatalError("Account type 'cost' or 'revenue' expected");
1663                         return FALSE;
1664                 }
1665                 acctType = at == KW("cost") ? Account::Cost : Account::Revenue;
1666         }
1667         else
1668                 acctType = parent->getAcctType();
1669
1670         Account* a = new Account(proj, id, name, parent, acctType);
1671         if (parent)
1672                 parent->addSub(a);
1673
1674         TokenType tt;
1675         QString token;
1676         if ((tt = nextToken(token)) == LCBRACE)
1677         {
1678                 bool hasSubAccounts = FALSE;
1679                 bool cantBeParent = FALSE;
1680                 // read optional attributes
1681                 while ((tt = nextToken(token)) != RCBRACE)
1682                 {
1683                         if (tt != ID)
1684                         {
1685                                 fatalError(QString("Unknown attribute '") + token + "'");
1686                                 return FALSE;
1687                         }
1688                         if (token == KW("account") && !cantBeParent)
1689                         {
1690                                 if (!readAccount(a))
1691                                         return FALSE;
1692                                 hasSubAccounts = TRUE;
1693                         }
1694                         else if (token == KW("credit"))
1695                         {
1696                                 if (!readCredit(a))
1697                                         return FALSE;
1698                         }
1699                         else if (token == KW("kotrusid") && !hasSubAccounts)
1700                         {
1701                                 if (nextToken(token) != STRING)
1702                                 {
1703                                         fatalError("String expected");
1704                                         return FALSE;
1705                                 }
1706                                 a->setKotrusId(token);
1707                                 cantBeParent = TRUE;
1708                         }
1709                         else if (token == KW("include"))
1710                         {
1711                                 if (!readInclude())
1712                                         return FALSE;
1713                         }
1714                         else
1715                         {
1716                                 fatalError("Illegal attribute");
1717                                 return FALSE;
1718                         }
1719                 }
1720         }
1721         else
1722                 openFiles.last()->returnToken(tt, token);
1723
1724         proj->addAccount(a);
1725
1726         return TRUE;
1727 }
1728
1729 bool
1730 ProjectFile::readCredit(Account* a)
1731 {
1732         QString token;
1733
1734         if (nextToken(token) != DATE)
1735         {
1736                 fatalError("Date expected");
1737                 return FALSE;
1738         }
1739         time_t date = date2time(token);
1740
1741         QString description;
1742         if (nextToken(description) != STRING)
1743         {
1744                 fatalError("String expected");
1745                 return FALSE;
1746         }
1747
1748         if (nextToken(token) != REAL)
1749         {
1750                 fatalError("Real value expected");
1751                 return FALSE;
1752         }
1753         Transaction* t = new Transaction(date, token.toDouble(), description);
1754         a->credit(t);
1755
1756         return TRUE;
1757 }
1758
1759 bool
1760 ProjectFile::readAllocate(Task* t)
1761 {
1762         QString id;
1763         Resource* r;
1764         if (nextToken(id) != ID || (r = proj->getResource(id)) == 0)
1765         {
1766                 fatalError("Resource ID expected");
1767                 return FALSE;
1768         }
1769         Allocation* a = new Allocation(r);
1770         QString token;
1771         TokenType tt;
1772         if ((tt = nextToken(token)) == LCBRACE)
1773         {
1774                 while ((tt = nextToken(token)) != RCBRACE)
1775                 {
1776                         if (tt != ID)
1777                         {
1778                                 fatalError(QString("Unknown attribute '") + token + "'");
1779                                 return FALSE;
1780                         }
1781                         if (token == KW("load"))
1782                         {
1783                                 if (nextToken(token) != REAL)
1784                                 {
1785                                         fatalError("Real value expected");
1786                                         return FALSE;
1787                                 }
1788                                 double load = token.toDouble();
1789                                 if (load < 0.01 || load > 1.0)
1790                                 {
1791                                         fatalError("Value must be in the range 0.01 - 1.0");
1792                                         return FALSE;
1793                                 }
1794                                 a->setLoad((int) (100 * load));
1795                         }
1796                         else if (token == KW("persistent"))
1797                         {
1798                                 a->setPersistent(TRUE);
1799                         }
1800                         else if (token == KW("alternative"))
1801                         {
1802                                 do
1803                                 {
1804                                         Resource* r;
1805                                         if ((tt = nextToken(token)) != ID ||
1806                                                 (r = proj->getResource(token)) == 0)
1807                                         {
1808                                                 fatalError("Resource ID expected");
1809                                                 return FALSE;
1810                                         }
1811                                         a->addAlternative(r);
1812                                 } while ((tt = nextToken(token)) == COMMA);
1813                                 openFiles.last()->returnToken(tt, token);
1814                         }
1815                 }
1816         }
1817         else
1818                 openFiles.last()->returnToken(tt, token);
1819         t->addAllocation(a);
1820
1821         return TRUE;
1822 }
1823
1824 bool
1825 ProjectFile::readTimeValue(ulong& value)
1826 {
1827         QString val;
1828         if (nextToken(val) != INTEGER)
1829         {
1830                 fatalError("Integer value expected");
1831                 return FALSE;
1832         }
1833         QString unit;
1834         if (nextToken(unit) != ID)
1835         {
1836                 fatalError("Unit expected");
1837                 return FALSE;
1838         }
1839         if (unit == KW("min"))
1840                 value = val.toULong() * 60;
1841         else if (unit == KW("h"))
1842                 value = val.toULong() * (60 * 60);
1843         else if (unit == KW("d"))
1844                 value = val.toULong() * (60 * 60 * 24);
1845         else if (unit == KW("w"))
1846                 value = val.toULong() * (60 * 60 * 24 * 7);
1847         else if (unit == KW("m"))
1848                 value = val.toULong() * (60 * 60 * 24 * 30);
1849         else if (unit == KW("y"))
1850                 value = val.toULong() * (60 * 60 * 24 * 356);
1851         else
1852         {
1853                 fatalError("Unit expected");
1854                 return FALSE;
1855         }
1856         return TRUE;
1857 }
1858
1859 bool
1860 ProjectFile::readPlanTimeFrame(Task* task, double& value)
1861 {
1862         QString val;
1863         TokenType tt;
1864         if ((tt = nextToken(val)) != REAL && tt != INTEGER)
1865         {
1866                 fatalError("Real value expected");
1867                 return FALSE;
1868         }
1869         QString unit;
1870         if (nextToken(unit) != ID)
1871         {
1872                 fatalError("Unit expected");
1873                 return FALSE;
1874         }
1875         if (unit == KW("min"))
1876                 value = val.toDouble() / (8 * 60);
1877         else if (unit == KW("h"))
1878                 value = val.toDouble() / 8;
1879         else if (unit == KW("d"))
1880                 value = val.toDouble();
1881         else if (unit == KW("w"))
1882                 value = val.toDouble() * 5;
1883         else if (unit == KW("m"))
1884                 value = val.toDouble() * 20;
1885         else if (unit == KW("y"))
1886                 value = val.toDouble() * 240;
1887         else
1888         {
1889                 fatalError("Unit expected");
1890                 return FALSE;
1891         }
1892
1893         return TRUE;
1894 }
1895
1896 bool
1897 ProjectFile::readWorkingHours(int& dayOfWeek, QPtrList<Interval>* l)
1898 {
1899         l->setAutoDelete(TRUE);
1900         QString day;
1901         if (nextToken(day) != ID)
1902         {
1903                 fatalError("Weekday expected");
1904                 return FALSE;
1905         }
1906         const char* days[] = { KW("sun"), KW("mon"), KW("tue"), KW("wed"),
1907                 KW("thu"), KW("fri"), KW("sat") };
1908         for (dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++)
1909                 if (days[dayOfWeek] == day)
1910                         break;
1911         if (dayOfWeek == 7)
1912         {
1913                 fatalError("Weekday expected");
1914                 return FALSE;
1915         }
1916
1917         QString token;
1918         TokenType tt;
1919         if ((tt = nextToken(token)) == ID && token == KW("off"))
1920                 return TRUE;
1921         else
1922                 returnToken(tt, token);
1923
1924         for ( ; ; )
1925         {
1926                 QString start;
1927                 if (nextToken(start) != HOUR)
1928                 {
1929                         fatalError("Start time as HH:MM expected");
1930                         return FALSE;
1931                 }
1932                 QString token;
1933                 if (nextToken(token) != MINUS)
1934                 {
1935                         fatalError("'-' expected");
1936                         return FALSE;
1937                 }
1938                 QString end;
1939                 if (nextToken(end) != HOUR)
1940                 {
1941                         fatalError("End time as HH:MM expected");
1942                         return FALSE;
1943                 }
1944                 l->append(new Interval(hhmm2time(start), hhmm2time(end)));
1945                 TokenType tt;
1946                 if ((tt = nextToken(token)) != COMMA)
1947                 {
1948                         returnToken(tt, token);
1949                         break;
1950                 }
1951         }
1952         return TRUE;
1953 }
1954
1955 bool
1956 ProjectFile::readPriority(int& priority)
1957 {
1958         QString token;
1959
1960         if (nextToken(token) != INTEGER)
1961         {
1962                 fatalError("Integer value expected");
1963                 return FALSE;
1964         }
1965         priority = token.toInt();
1966         if (priority < 1 || priority > 1000)
1967         {
1968                 fatalError("Priority value must be between 1 and 1000");
1969                 return FALSE;
1970         }
1971         return TRUE;
1972 }
1973
1974 #ifdef HAVE_KDE
1975 bool
1976 ProjectFile::readICalTaskReport()
1977 {
1978    QString token;
1979    if (nextToken(token) != STRING)
1980    {
1981       fatalError("File name expected");
1982       return FALSE;
1983    }
1984    ReportICal *rep = new ReportICal( proj, token, proj->getStart(), proj->getEnd());
1985    proj->addICalReport( rep );
1986
1987    return( true );
1988 }
1989 #endif
1990
1991 bool
1992 ProjectFile::readXMLTaskReport()
1993 {
1994    QString token;
1995    if (nextToken(token) != STRING)
1996    {
1997       fatalError("File name expected");
1998       return FALSE;
1999    }
2000    ReportXML *rep = new ReportXML(proj, token, proj->getStart(),
2001                                                                   proj->getEnd());
2002    proj->addXMLReport( rep );
2003
2004    return( true );
2005 }
2006
2007
2008 bool
2009 ProjectFile::readHTMLReport(const QString& reportType)
2010 {
2011         QString token;
2012         if (nextToken(token) != STRING)
2013         {
2014                 fatalError("File name expected");
2015                 return FALSE;
2016         }
2017         
2018         ReportHtml* report;
2019         if (reportType == KW("htmltaskreport"))
2020                 report = new HTMLTaskReport(proj, token, proj->getStart(),
2021                                                                         proj->getEnd());
2022         else if (reportType == KW("htmlresourcereport"))
2023                 report = new HTMLResourceReport(proj, token, proj->getStart(),
2024                                                                                 proj->getEnd());
2025         else
2026         {
2027                 qFatal("readHTMLReport: bad report type");
2028                 return FALSE;   // Just to please the compiler.
2029         }
2030                 
2031         TokenType tt;
2032         if ((tt = nextToken(token)) != LCBRACE)
2033         {
2034                 openFiles.last()->returnToken(tt, token);
2035                 return TRUE;
2036         }
2037
2038         for ( ; ; )
2039         {
2040                 if ((tt = nextToken(token)) == RCBRACE)
2041                         break;
2042                 else if (tt != ID)
2043                 {
2044                         fatalError("Attribute ID or '}' expected");
2045                         return FALSE;
2046                 }
2047                 if (token == KW("columns"))
2048                 {
2049                         report->clearColumns();
2050                         for ( ; ; )
2051                         {
2052                                 QString col;
2053                                 if ((tt = nextToken(col)) != ID)
2054                                 {
2055                                         fatalError("Column ID expected");
2056                                         return FALSE;
2057                                 }
2058                                 report->addColumn(col);
2059                                 if ((tt = nextToken(token)) != COMMA)
2060                                 {
2061                                         openFiles.last()->returnToken(tt, token);
2062                                         break;
2063                                 }
2064                         }
2065                 }
2066                 else if (token == KW("start"))
2067                 {
2068                         if (nextToken(token) != DATE)
2069                         {
2070                                 fatalError("Date expected");
2071                                 return FALSE;
2072                         }
2073                         report->setStart(date2time(token));
2074                 }
2075                 else if (token == KW("end"))
2076                 {
2077                         if (nextToken(token) != DATE)
2078                         {
2079                                 fatalError("Date expected");
2080                                 return FALSE;
2081                         }
2082                         report->setEnd(date2time(token));
2083                 }
2084                 else if (token == KW("headline"))
2085                 {
2086                         if (nextToken(token) != STRING)
2087                         {
2088                                 fatalError("String exptected");
2089                                 return FALSE;
2090                         }
2091                         report->setHeadline(token);
2092                 }
2093                 else if (token == KW("caption"))
2094                 {
2095                         if (nextToken(token) != STRING)
2096                         {
2097                                 fatalError("String exptected");
2098                                 return FALSE;
2099                         }
2100                         report->setCaption(token);
2101                 }
2102                 else if (token == KW("showactual"))
2103                 {
2104                         report->setShowActual(TRUE);
2105                 }
2106                 else if (token == KW("showprojectids"))
2107                 {
2108                         report->setShowPIDs(TRUE);
2109                 }
2110                 else if (token == KW("hidetask"))
2111                 {
2112                         Operation* op;
2113                         if ((op = readLogicalExpression()) == 0)
2114                                 return FALSE;
2115                         ExpressionTree* et = new ExpressionTree(op);
2116                         report->setHideTask(et);
2117                 }
2118                 else if (token == KW("rolluptask"))
2119                 {
2120                         Operation* op;
2121                         if ((op = readLogicalExpression()) == 0)
2122                                 return FALSE;
2123                         ExpressionTree* et = new ExpressionTree(op);
2124                         report->setRollUpTask(et);
2125                 }
2126                 else if (token == KW("sorttasks"))
2127                 {
2128                         if (!readSorting(report, 0))
2129                                 return FALSE;
2130                 }
2131                 else if (token == KW("hideresource"))
2132                 {
2133                         Operation* op;
2134                         if ((op = readLogicalExpression()) == 0)
2135                                 return FALSE;
2136                         ExpressionTree* et = new ExpressionTree(op);
2137                         report->setHideResource(et);
2138                 }
2139                 else if (token == KW("rollupresource"))
2140                 {
2141                         Operation* op;
2142                         if ((op = readLogicalExpression()) == 0)
2143                                 return FALSE;
2144                         ExpressionTree* et = new ExpressionTree(op);
2145                         report->setRollUpResource(et);
2146                 }
2147                 else if (token == KW("sortresources"))
2148                 {
2149                         if (!readSorting(report, 1))
2150                                 return FALSE;
2151                 }
2152                 else
2153                 {
2154                         fatalError("Illegal attribute");
2155                         return FALSE;
2156                 }
2157         }
2158
2159         if (reportType == KW("htmltaskreport"))
2160                 proj->addHTMLTaskReport((HTMLTaskReport*) report);
2161         else
2162                 proj->addHTMLResourceReport((HTMLResourceReport*) report);
2163
2164         return TRUE;
2165 }
2166
2167 bool
2168 ProjectFile::readHTMLAccountReport()
2169 {
2170         QString token;
2171         if (nextToken(token) != STRING)
2172         {
2173                 fatalError("File name expected");
2174                 return FALSE;
2175         }
2176         
2177         HTMLAccountReport* report;
2178         report = new HTMLAccountReport(proj, token, proj->getStart(),
2179                                                                    proj->getEnd());
2180                 
2181         TokenType tt;
2182         if ((tt = nextToken(token)) != LCBRACE)
2183         {
2184                 openFiles.last()->returnToken(tt, token);
2185                 return TRUE;
2186         }
2187
2188         for ( ; ; )
2189         {
2190                 if ((tt = nextToken(token)) == RCBRACE)
2191                         break;
2192                 else if (tt != ID)
2193                 {
2194                         fatalError("Attribute ID or '}' expected");
2195                         return FALSE;
2196                 }
2197                 if (token == KW("columns"))
2198                 {
2199                         report->clearColumns();
2200                         for ( ; ; )
2201                         {
2202                                 QString col;
2203                                 if ((tt = nextToken(col)) != ID)
2204                                 {
2205                                         fatalError("Column ID expected");
2206                                         return FALSE;
2207                                 }
2208                                 report->addColumn(col);
2209                                 if ((tt = nextToken(token)) != COMMA)
2210                                 {
2211                                         openFiles.last()->returnToken(tt, token);
2212                                         break;
2213                                 }
2214                         }
2215                 }
2216                 else if (token == KW("start"))
2217                 {
2218                         if (nextToken(token) != DATE)
2219                         {
2220                                 fatalError("Date expected");
2221                                 return FALSE;
2222                         }
2223                         report->setStart(date2time(token));
2224                 }
2225                 else if (token == KW("end"))
2226                 {
2227                         if (nextToken(token) != DATE)
2228                         {
2229                                 fatalError("Date expected");
2230                                 return FALSE;
2231                         }
2232                         report->setEnd(date2time(token));
2233                 }
2234                 else if (token == KW("headline"))
2235                 {
2236                         if (nextToken(token) != STRING)
2237                         {
2238                                 fatalError("String exptected");
2239                                 return FALSE;
2240                         }
2241                         report->setHeadline(token);
2242                 }
2243                 else if (token == KW("caption"))
2244                 {
2245                         if (nextToken(token) != STRING)
2246                         {
2247                                 fatalError("String exptected");
2248                                 return FALSE;
2249                         }
2250                         report->setCaption(token);
2251                 }
2252                 else if (token == KW("hideplan"))
2253                 {
2254                         report->setHidePlan(TRUE);
2255                 }
2256                 else if (token == KW("showactual"))
2257                 {
2258                         report->setShowActual(TRUE);
2259                 }
2260                 else if (token == KW("accumulate"))
2261                 {
2262                         report->setAccumulate(TRUE);
2263                 }
2264                 else if (token == KW("hideaccount"))
2265                 {
2266                         Operation* op;
2267                         if ((op = readLogicalExpression()) == 0)
2268                                 return FALSE;
2269                         ExpressionTree* et = new ExpressionTree(op);
2270                         report->setHideAccount(et);
2271                 }
2272                 else if (token == KW("rollupaccount"))
2273                 {
2274                         Operation* op;
2275                         if ((op = readLogicalExpression()) == 0)
2276                                 return FALSE;
2277                         ExpressionTree* et = new ExpressionTree(op);
2278                         report->setRollUpAccount(et);
2279                 }
2280                 else if (token == KW("sortaccounts"))
2281                 {
2282                         if (!readSorting(report, 2))
2283                                 return FALSE;
2284                 }
2285                 else
2286                 {
2287                         fatalError("Illegal attribute");
2288                         return FALSE;
2289                 }
2290         }
2291
2292         proj->addHTMLAccountReport(report);
2293
2294         return TRUE;
2295 }
2296
2297 bool
2298 ProjectFile::readExportReport()
2299 {
2300         QString token;
2301         if (nextToken(token) != STRING)
2302         {
2303                 fatalError("File name expected");
2304                 return FALSE;
2305         }
2306         
2307         ExportReport* report;
2308         report = new ExportReport(proj, token);
2309                 
2310         TokenType tt;
2311         if ((tt = nextToken(token)) != LCBRACE)
2312         {
2313                 openFiles.last()->returnToken(tt, token);
2314                 return TRUE;
2315         }
2316
2317         for ( ; ; )
2318         {
2319                 if ((tt = nextToken(token)) == RCBRACE)
2320                         break;
2321                 else if (tt != ID)
2322                 {
2323                         fatalError("Attribute ID or '}' expected");
2324                         return FALSE;
2325                 }
2326                 
2327                 if (token == KW("hidetask"))
2328                 {
2329                         Operation* op;
2330                         if ((op = readLogicalExpression()) == 0)
2331                                 return FALSE;
2332                         ExpressionTree* et = new ExpressionTree(op);
2333                         report->setHideTask(et);
2334                 }
2335                 else if (token == KW("rolluptask"))
2336                 {
2337                         Operation* op;
2338                         if ((op = readLogicalExpression()) == 0)
2339                                 return FALSE;
2340                         ExpressionTree* et = new ExpressionTree(op);
2341                         report->setRollUpTask(et);
2342                 }
2343                 else
2344                 {
2345                         fatalError("Illegal attribute");
2346                         return FALSE;
2347                 }
2348         }
2349
2350         proj->addExportReport(report);
2351
2352         return TRUE;
2353 }
2354
2355 Operation*
2356 ProjectFile::readLogicalExpression(int precedence)
2357 {
2358         Operation* op;
2359         QString token;
2360         TokenType tt;
2361
2362         if ((tt = nextToken(token)) == ID || tt == RELATIVE_ID)
2363         {
2364                 if (proj->isAllowedFlag(token))
2365                         op = new Operation(token);
2366                 else if (proj->getTask(token))
2367                         op = new Operation(Operation::TaskId, token);
2368                 else if (proj->getResource(token))
2369                         op = new Operation(Operation::ResourceId, token);
2370                 else if (proj->getAccount(token))
2371                         op = new Operation(Operation::AccountId, token);
2372                 else if (ExpressionTree::isFunction(token))
2373                 {
2374                         if ((op = readFunctionCall(token)) == 0)
2375                                 return 0;
2376                 }
2377                 else
2378                 {
2379                         fatalError(QString("Flag or function '") + token + "' is unknown.");
2380                         return 0;
2381                 }
2382         }
2383         else if (tt == INTEGER)
2384         {
2385                 op = new Operation(token.toLong());
2386         }
2387         else if (tt == TILDE)
2388         {
2389                 if ((op = readLogicalExpression(1)) == 0)
2390                 {
2391                         return 0;
2392                 }
2393                 op = new Operation(op, Operation::Not);
2394         }
2395         else if (tt == LBRACE)
2396         {
2397                 if ((op = readLogicalExpression()) == 0)
2398                 {
2399                         return 0;
2400                 }
2401                 if ((tt = nextToken(token)) != RBRACE)
2402                 {
2403                         fatalError("')' expected");
2404                         return 0;
2405                 }
2406         }
2407         else
2408         {
2409                 fatalError("Logical expression expected");
2410                 return 0;
2411         }
2412         
2413         if (precedence < 1)
2414         {
2415                 if ((tt = nextToken(token)) != AND && tt != OR)
2416                 {
2417                         returnToken(tt, token);
2418                 }
2419                 else if (tt == AND)
2420                 {
2421                         Operation* op2 = readLogicalExpression();
2422                         op = new Operation(op, Operation::And, op2);
2423                 }
2424                 else if (tt == OR)
2425                 {
2426                         Operation* op2 = readLogicalExpression();
2427                         op = new Operation(op, Operation::Or, op2);
2428                 }
2429         }
2430
2431         return op;
2432 }
2433
2434 Operation*
2435 ProjectFile::readFunctionCall(const QString& name)
2436 {
2437         QString token;
2438         TokenType tt;
2439         
2440         if ((tt = nextToken(token)) != LBRACE)
2441         {
2442                 fatalError("'(' expected");
2443                 return 0;
2444         }
2445         QPtrList<Operation> args;
2446         for (int i = 0; i < ExpressionTree::arguments(name); i++)
2447         {
2448                 Operation* op;
2449                 if ((op = readLogicalExpression()) == 0)
2450                         return 0;
2451                 args.append(op);
2452                 if ((i < ExpressionTree::arguments(name) - 1) &&
2453                         nextToken(token) != COMMA)
2454                 {
2455                         fatalError("Comma expected");
2456                         return 0;
2457                 }
2458         }
2459         if ((tt = nextToken(token)) != RBRACE)
2460         {
2461                 fatalError("')' expected");
2462                 return 0;
2463         }
2464         return new Operation(name, args);
2465 }
2466
2467 bool
2468 ProjectFile::readSorting(Report* report, int which)
2469 {
2470         QString token;
2471
2472         nextToken(token);
2473         CoreAttributesList::SortCriteria sorting;
2474         if (token == KW("tree"))
2475                 sorting = CoreAttributesList::TreeMode;
2476         else if (token == KW("indexup"))
2477                 sorting = CoreAttributesList::IndexUp;
2478         else if (token == KW("indexdown"))
2479                 sorting = CoreAttributesList::IndexDown;
2480         else if (token == KW("idup"))
2481                 sorting = CoreAttributesList::IdUp;
2482         else if (token == KW("iddown"))
2483                 sorting = CoreAttributesList::IdDown;
2484         else if (token == KW("fullnameup"))
2485                 sorting = CoreAttributesList::FullNameUp;
2486         else if (token == KW("fullnamedown"))
2487                 sorting = CoreAttributesList::FullNameDown;
2488         else if (token == KW("nameup"))
2489                 sorting = CoreAttributesList::NameUp;
2490         else if (token == KW("namedown"))
2491                 sorting = CoreAttributesList::NameDown;
2492         else if (token == KW("startup"))
2493                 sorting = CoreAttributesList::StartUp;
2494         else if (token == KW("startdown"))
2495                 sorting = CoreAttributesList::StartDown;
2496         else if (token == KW("endup"))
2497                 sorting = CoreAttributesList::EndUp;
2498         else if (token == KW("enddown"))
2499                 sorting = CoreAttributesList::EndDown;
2500         else if (token == KW("priorityup"))
2501                 sorting = CoreAttributesList::PrioUp;
2502         else if (token == KW("prioritydown"))
2503                 sorting = CoreAttributesList::PrioDown;
2504         else if (token == KW("responsibleup"))
2505                 sorting = CoreAttributesList::ResponsibleUp;
2506         else if (token == KW("responsibledown"))
2507                 sorting = CoreAttributesList::ResponsibleDown;
2508         else if (token == KW("mineffortup"))
2509                 sorting = CoreAttributesList::MinEffortUp;
2510         else if (token == KW("mineffortdown"))
2511                 sorting = CoreAttributesList::MinEffortDown;
2512         else if (token == KW("maxeffortup"))
2513                 sorting = CoreAttributesList::MaxEffortUp;
2514         else if (token == KW("maxeffortdown"))
2515                 sorting = CoreAttributesList::MaxEffortDown;
2516         else if (token == KW("rateup"))
2517                 sorting = CoreAttributesList::RateUp;
2518         else if (token == KW("ratedown"))
2519                 sorting = CoreAttributesList::RateDown;
2520         else if (token == KW("kotrusidup"))
2521                 sorting = CoreAttributesList::KotrusIdUp;
2522         else if (token == KW("kotrusiddown"))
2523                 sorting = CoreAttributesList::KotrusIdDown;
2524         else
2525         {
2526                 fatalError("Sorting criteria expected");
2527                 return FALSE;
2528         }
2529
2530         switch (which)
2531         {
2532         case 0:
2533                 report->setTaskSorting(sorting);
2534                 break;
2535         case 1:
2536                 report->setResourceSorting(sorting);
2537                 break;
2538         case 2:
2539                 report->setAccountSorting(sorting);
2540                 break;
2541         default:
2542                 qFatal("readSorting: Unknown sorting attribute");
2543                 return FALSE;
2544         }
2545
2546         return TRUE;
2547 }
2548
2549 time_t
2550 ProjectFile::date2time(const QString& date)
2551 {
2552         int y, m, d, hour, min;
2553         char tZone[16] = "";
2554         if (sscanf(date, "%d-%d-%d-%d:%d-%s", &y, &m, &d, &hour, &min, tZone) == 6)
2555                 ;
2556         else if (sscanf(date, "%d-%d-%d-%d:%d", &y, &m, &d, &hour, &min) == 5)
2557                 tZone[0] = '\0';
2558         else if (sscanf(date, "%d-%d-%d", &y, &m, &d) == 3)
2559         {                       
2560                 tZone[0] = '\0';
2561                 hour = min = 0;
2562         }
2563
2564         char savedTZ[16] = "";
2565         if (strcmp(tZone, "") != 0)
2566         {
2567                 if (getenv("TZ"))
2568                         strcpy(getenv("TZ"), savedTZ);
2569                 setenv("TZ", tZone, 1);
2570         }
2571         
2572         if (y < 1970)
2573         {
2574                 fatalError("Year must be larger than 1969");
2575                 y = 1970;
2576         }
2577         if (m < 1 || m > 12)
2578         {
2579                 fatalError("Month must be between 1 and 12");
2580                 m = 1;
2581         }
2582         if (d < 1 || d > 31)
2583         {
2584                 fatalError("Day must be between 1 and 31");
2585                 d = 1;
2586         }
2587
2588         struct tm t = { 0, min, hour, d, m - 1, y - 1900, 0, 0, -1, 0, 0 };
2589         time_t localTime = mktime(&t);
2590
2591         if (strcmp(savedTZ, "") != 0) 
2592                 setenv("TZ", savedTZ, 1);
2593         else
2594                 unsetenv("TZ");
2595
2596         return localTime;
2597 }
2598
2599 time_t
2600 ProjectFile::hhmm2time(const QString& hhmm)
2601 {
2602         int hour, min;
2603         sscanf(hhmm, "%d:%d", &hour, &min);
2604         return hour * 60 * 60 + min * 60;
2605 }