2 * ProjectFile.cpp - TaskJuggler
4 * Copyright (c) 2001, 2002 by Chris Schlaeger <cs@suse.de>
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.
21 #include "ProjectFile.h"
24 #include "ExpressionTree.h"
27 // Dummy marco to mark all keywords of taskjuggler syntax
30 #define READ_DATE(a, b) \
33 if ((tt = nextToken(token)) == DATE) \
34 task->b(date2time(token)); \
37 fatalError("Date expected"); \
42 FileInfo::FileInfo(ProjectFile* p, const QString& file_)
45 tokenTypeBuf = INVALID;
52 if ((f = fopen(file, "r")) == 0)
70 FileInfo::getC(bool expandMacros)
74 if (ungetBuf.isEmpty())
81 ungetBuf.remove(ungetBuf.fromLast());
84 macroStack.removeLast();
85 pf->getMacros().popArguments();
96 if ((d = getC()) == '{')
98 // remove $ from lineBuf;
99 lineBuf = lineBuf.left(lineBuf.length() - 1);
112 FileInfo::ungetC(int c)
114 lineBuf = lineBuf.left(lineBuf.length() - 1);
119 FileInfo::getDateFragment(QString& token, int& c)
126 fatalError("Corrupted date");
131 while ((c = getC()) != EOF && isdigit(c))
138 FileInfo::getPath() const
140 if (file.find('/') >= 0)
141 return file.left(file.findRev('/') + 1);
147 FileInfo::nextToken(QString& token)
149 if (tokenTypeBuf != INVALID)
152 TokenType tt = tokenTypeBuf;
153 tokenTypeBuf = INVALID;
159 // skip blanks and comments
171 /* This code skips c-style comments like the one you are just
173 if ((c = getC(FALSE)) == '*')
177 while ((c = getC(FALSE)) != '*')
183 fatalError("Unterminated comment");
187 } while ((c = getC(FALSE)) != '/');
196 case '#': // Comments start with '#' and reach towards end of line
197 while ((c = getC(FALSE)) != '\n' && c != EOF)
201 // break missing on purpose
203 // Increase line counter only when not replaying a macro.
204 if (macroStack.isEmpty())
215 // analyse non blank characters
221 fatalError("Unexpected end of file");
224 else if (isalpha(c) || (c == '_') || (c == '!'))
227 while ((c = getC()) != EOF &&
228 (isalnum(c) || (c == '_') || (c == '.') || (c == '!')))
231 if (token.contains('!') || token.contains('.'))
238 // read first number (maybe a year)
240 while ((c = getC()) != EOF && isdigit(c))
244 // this must be a ISO date yyyy-mm-dd[[-hh:mm:[ss]]-TZ]
245 getDateFragment(token, c);
248 fatalError("Corrupted date");
251 getDateFragment(token, c);
254 getDateFragment(token, c);
257 fatalError("Corrupted date");
260 getDateFragment(token, c);
262 getDateFragment(token, c);
268 while ((c = getC()) != EOF && isalnum(c) && i++ < 12)
276 // must be a real number
278 while ((c = getC()) != EOF && isdigit(c))
285 // must be a time (HH:MM)
287 for (int i = 0; i < 2; i++)
289 if ((c = getC()) != EOF && isdigit(c))
293 fatalError("2 digits minutes expected");
308 while ((c = getC()) != EOF && c != '"')
316 fatalError("Non terminated string");
324 while ((c = getC(FALSE)) != EOF && (c != ']' || nesting > 0))
336 fatalError("Non terminated macro definition");
365 fatalError(QString("Illegal character '") + c + "'");
373 FileInfo::readMacroCall()
377 if ((tt = nextToken(id)) != ID && tt != INTEGER)
379 fatalError("Macro ID expected");
383 // Store all arguments in a newly created string list.
384 QStringList* sl = new QStringList;
385 while ((tt = nextToken(token)) == STRING)
389 fatalError("'}' expected");
393 // push string list to global argument stack
394 pf->getMacros().pushArguments(sl);
397 QString macro = pf->getMacros().expand(id);
400 fatalError(QString("Unknown macro ") + id);
404 // Push pointer to macro on stack. Needed for error handling.
405 macroStack.append(pf->getMacros().getMacro(id));
409 // push expanded macro reverse into ungetC buffer.
410 for (int i = macro.length() - 1; i >= 0; --i)
411 ungetC(macro[i].latin1());
416 FileInfo::returnToken(TokenType tt, const QString& buf)
418 if (tokenTypeBuf != INVALID)
420 qFatal("Internal Error: Token buffer overflow!");
428 FileInfo::fatalError(const QString& msg)
430 if (macroStack.isEmpty())
432 qWarning("%s:%d:%s", file.latin1(), currLine, msg.latin1());
433 qWarning("%s", lineBuf.latin1());
437 qWarning("Error in expanded macro");
438 qWarning("%s:%d: %s",
439 macroStack.last()->getFile().latin1(),
440 macroStack.last()->getLine(), msg.latin1());
441 qWarning("%s", lineBuf.latin1());
445 ProjectFile::ProjectFile(Project* p)
449 openFiles.setAutoDelete(TRUE);
453 ProjectFile::open(const QString& file)
455 QString absFileName = file;
456 if (absFileName[0] != '/')
458 if (openFiles.isEmpty())
461 if (getcwd(buf, 1023) != 0)
462 absFileName = QString(buf) + "/" + absFileName;
464 qFatal("ProjectFile::open(): getcwd failed");
467 absFileName = openFiles.last()->getPath() + absFileName;
469 qWarning("Expanded filename to %s", absFileName.latin1());
472 while (absFileName.find("/../", end) >= 0)
474 end = absFileName.find("/../");
475 int start = absFileName.findRev('/', end - 1);
479 start++; // move after '/'
480 if (start < end && absFileName.mid(start, end - start) != "..")
481 absFileName.remove(start, end + strlen("/../") - start);
482 end += strlen("/..");
485 // Make sure that we include each file only once.
486 if (includedFiles.findIndex(absFileName) != -1)
489 qWarning("Ignoring already read file %s",
490 absFileName.latin1());
494 FileInfo* fi = new FileInfo(this, absFileName);
498 qFatal("Cannot open '%s'", absFileName.latin1());
503 qWarning("Reading %s", absFileName.latin1());
505 openFiles.append(fi);
506 includedFiles.append(absFileName);
515 FileInfo* fi = openFiles.getLast();
519 openFiles.removeLast();
532 switch (tt = nextToken(token))
537 if (token == KW("task"))
543 if (token == KW("account"))
549 else if (token == KW("resource"))
551 if (!readResource(0))
555 else if (token == KW("shift"))
561 else if (token == KW("vacation"))
565 if (!readVacation(from, to, TRUE, &name))
567 proj->addVacation(name, from, to);
570 else if (token == KW("priority"))
573 if (!readPriority(priority))
575 proj->setPriority(priority);
578 else if (token == KW("now"))
580 if (nextToken(token) != DATE)
582 fatalError("Date expected");
585 proj->setNow(date2time(token));
588 else if (token == KW("mineffort"))
590 if (nextToken(token) != REAL)
592 fatalError("Real value exptected");
595 proj->setMinEffort(token.toDouble());
598 else if (token == KW("maxeffort"))
600 if (nextToken(token) != REAL)
602 fatalError("Real value exptected");
605 proj->setMaxEffort(token.toDouble());
608 else if (token == KW("rate"))
610 if (nextToken(token) != REAL)
612 fatalError("Real value exptected");
615 proj->setRate(token.toDouble());
618 else if (token == KW("currency"))
620 if (nextToken(token) != STRING)
622 fatalError("String expected");
625 proj->setCurrency(token);
628 else if (token == KW("currencydigits"))
630 if (nextToken(token) != INTEGER)
632 fatalError("Integer value expected");
635 proj->setCurrencyDigits(token.toInt());
638 else if (token == KW("timingresolution"))
641 if (!readTimeValue(resolution))
643 if (proj->resourceCount() > 0)
645 fatalError("The timing resolution cannot be changed after "
646 "resources have been declared.");
649 if (resolution < 60 * 5)
651 fatalError("timing resolution must be at least 5 min");
654 proj->setScheduleGranularity(resolution);
657 else if (token == KW("copyright"))
659 if (nextToken(token) != STRING)
661 fatalError("String expected");
664 proj->setCopyright(token);
667 else if (token == KW("include"))
673 else if (token == KW("macro"))
676 if (nextToken(id) != ID)
678 fatalError("Macro ID expected");
681 QString file = openFiles.last()->getFile();
682 uint line = openFiles.last()->getLine();
683 if (nextToken(token) != MacroBody)
685 fatalError("Macro body expected");
688 Macro* macro = new Macro(id, token, file, line);
689 if (!macros.addMacro(macro))
691 fatalError("Macro has been defined already");
697 else if (token == KW("flags"))
702 if (nextToken(flag) != ID)
704 fatalError("flag ID expected");
708 /* Flags can be declared multiple times, but we
709 * register a flag only once. */
710 if (!proj->isAllowedFlag(flag))
711 proj->addAllowedFlag(flag);
713 if ((tt = nextToken(token)) != COMMA)
715 returnToken(tt, token);
721 else if (token == KW("project"))
723 if (nextToken(token) != ID)
725 fatalError("Project ID expected");
728 if (!proj->addId(token))
730 fatalError(QString().sprintf(
731 "Project ID %s has already been registered",
735 if (nextToken(token) != STRING)
737 fatalError("Project name expected");
740 proj->setName(token);
741 if (nextToken(token) != STRING)
743 fatalError("Version string expected");
746 proj->setVersion(token);
748 if (nextToken(token) != DATE)
750 fatalError("Start date expected");
753 start = date2time(token);
754 if (nextToken(token) != DATE)
756 fatalError("End date expected");
759 end = date2time(token);
762 fatalError("End date must be larger then start date");
765 proj->setStart(start);
769 else if (token == KW("projectid"))
774 if (nextToken(id) != ID)
776 fatalError("Project ID expected");
780 if (!proj->addId(id))
782 fatalError(QString().sprintf(
783 "Project ID %s has already been registered",
788 if ((tt = nextToken(token)) != COMMA)
790 returnToken(tt, token);
796 else if (token == KW("xmltaskreport"))
798 if( !readXMLTaskReport())
803 else if (token == "icalreport" )
805 if( !readICalTaskReport())
810 else if (token == KW("htmltaskreport") ||
811 token == KW("htmlresourcereport"))
813 if (!readHTMLReport(token))
817 else if (token == KW("htmlaccountreport"))
819 if (!readHTMLAccountReport())
823 else if (token == KW("export"))
825 if (!readExportReport())
829 else if (token == KW("kotrusmode"))
831 if (nextToken(token) != STRING ||
832 (token != KW("db") && token != KW("xml") &&
833 token != KW("nokotrus")))
835 fatalError("Unknown kotrus mode");
838 if (token != KW("nokotrus"))
840 Kotrus* kotrus = new Kotrus();
841 kotrus->setKotrusMode(token);
842 proj->setKotrus(kotrus);
846 else if (token == KW("supplement"))
848 if (nextToken(token) != ID ||
849 (token != KW("task") && (token != KW("resource"))))
851 fatalError("'task' or 'resource' expected");
854 if ((token == "task" && !readTaskSupplement()) ||
855 (token == "resource" && !readResourceSupplement()))
859 // break missing on purpose!
861 fatalError(QString("Syntax Error at '") + token + "'!");
870 ProjectFile::nextToken(QString& buf)
872 if (openFiles.isEmpty())
876 while ((tt = openFiles.last()->nextToken(buf)) == EndOfFile)
879 if (openFiles.isEmpty())
887 ProjectFile::fatalError(const QString& msg)
889 if (openFiles.isEmpty())
890 qWarning("Unexpected end of file found. Probably a missing '}'.");
892 openFiles.last()->fatalError(msg);
896 ProjectFile::readInclude()
900 if (nextToken(token) != STRING)
902 fatalError("File name expected");
912 ProjectFile::readTask(Task* parent)
918 if ((tt = nextToken(id)) != ID &&
921 fatalError("ID expected");
925 if (tt == RELATIVE_ID)
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. */
935 parent = parent->getParent();
938 fatalError("Invalid relative task ID");
941 id = id.right(id.length() - 1);
943 else if (id.find('.') >= 0)
945 QString tn = (parent ? parent->getId() + "." : QString())
946 + id.left(id.find('.'));
949 parent->getSubTaskList(tl);
951 tl = proj->getTaskList();
953 for (Task* t = tl.first(); t != 0; t = tl.next())
954 if (t->getId() == tn)
957 id = id.right(id.length() - id.find('.') - 1);
963 fatalError(QString("Task ") + tn + " unknown");
967 } while (id[0] == '!' || id.find('.') >= 0);
971 if ((tt = nextToken(name)) != STRING)
973 fatalError("String expected");
977 if ((tt = nextToken(token)) != LCBRACE)
979 fatalError("{ expected");
984 id = parent->getId() + "." + id;
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)
991 fatalError(QString().sprintf
992 ("Task %s has already been declared", id.latin1()));
996 Task* task = new Task(proj, id, name, parent, getFile(), getLine());
1000 parent->addSub(task);
1003 if (!readTaskBody(task))
1006 if (task->getName().isEmpty())
1008 fatalError(QString("No name specified for task ") + id + "!");
1016 ProjectFile::readTaskSupplement()
1022 if (((tt = nextToken(token)) != ID && tt != RELATIVE_ID) ||
1023 ((task = proj->getTask(token)) == 0))
1025 fatalError("Already defined task ID expected");
1028 if (nextToken(token) != LCBRACE)
1030 fatalError("'}' expected");
1033 return readTaskBody(task);
1037 ProjectFile::readTaskBody(Task* task)
1042 for (bool done = false ; !done; )
1044 switch (tt = nextToken(token))
1047 if (token == KW("task"))
1049 if (!readTask(task))
1052 else if (token == KW("note"))
1054 if ((tt = nextToken(token)) == STRING)
1055 task->setNote(token);
1058 fatalError("String expected");
1062 else if (token == KW("milestone"))
1064 task->setMilestone();
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"))
1077 if (!readPlanTimeFrame(task, d))
1079 task->setPlanLength(d);
1081 else if (token == KW("effort"))
1084 if (!readPlanTimeFrame(task, d))
1086 task->setPlanEffort(d);
1088 else if (token == KW("duration"))
1091 if (!readPlanTimeFrame(task, d))
1093 task->setPlanDuration(d);
1095 else if (token == KW("actuallength"))
1098 if (!readPlanTimeFrame(task, d))
1100 task->setActualLength(d);
1102 else if (token == KW("actualeffort"))
1105 if (!readPlanTimeFrame(task, d))
1107 task->setActualEffort(d);
1109 else if (token == KW("actualduration"))
1112 if (!readPlanTimeFrame(task, d))
1114 task->setActualDuration(d);
1116 else if (token == KW("complete"))
1118 if (nextToken(token) != INTEGER)
1120 fatalError("Integer value expected");
1123 int complete = token.toInt();
1124 if (complete < 0 || complete > 100)
1126 fatalError("Value of complete must be between 0 and 100");
1129 task->setComplete(complete);
1131 else if (token == KW("responsible"))
1134 if (nextToken(token) != ID ||
1135 (r = proj->getResource(token)) == 0)
1137 fatalError("Resource ID expected");
1140 task->setResponsible(r);
1142 else if (token == KW("allocate"))
1144 if (!readAllocate(task))
1147 else if (token == KW("depends"))
1152 if ((tt = nextToken(id)) != ID &&
1155 fatalError("Task ID expected");
1158 task->addDependency(id);
1159 task->setScheduling(Task::ASAP);
1160 if ((tt = nextToken(token)) != COMMA)
1162 returnToken(tt, token);
1167 else if (token == KW("preceeds"))
1172 if ((tt = nextToken(id)) != ID &&
1175 fatalError("Task ID expected");
1178 task->addPreceeds(id);
1179 task->setScheduling(Task::ALAP);
1180 if ((tt = nextToken(token)) != COMMA)
1182 returnToken(tt, token);
1187 else if (token == KW("scheduling"))
1190 if (token == KW("asap"))
1191 task->setScheduling(Task::ASAP);
1192 else if (token == KW("alap"))
1193 task->setScheduling(Task::ALAP);
1196 fatalError("Unknown scheduling policy");
1200 else if (token == KW("flags"))
1205 if (nextToken(flag) != ID || !proj->isAllowedFlag(flag))
1207 fatalError("Flag unknown");
1210 task->addFlag(flag);
1211 if ((tt = nextToken(token)) != COMMA)
1213 returnToken(tt, token);
1218 else if (token == KW("priority"))
1221 if (!readPriority(priority))
1223 task->setPriority(priority);
1226 else if (token == KW("account"))
1229 if (nextToken(account) != ID ||
1230 proj->getAccount(account) == 0)
1232 fatalError("Account ID expected");
1235 task->setAccount(proj->getAccount(account));
1238 else if (token == KW("startcredit"))
1240 if (nextToken(token) != REAL)
1242 fatalError("Real value expected");
1245 task->setStartCredit(token.toDouble());
1248 else if (token == KW("endcredit"))
1250 if (nextToken(token) != REAL)
1252 fatalError("Real value expected");
1255 task->setEndCredit(token.toDouble());
1258 else if (token == KW("projectid"))
1260 if (nextToken(token) != ID ||
1261 !proj->isValidId(token))
1263 fatalError("Project ID expected");
1266 task->setProjectId(token);
1269 else if (token == KW("include"))
1277 fatalError(QString("Illegal task attribute '")
1286 fatalError(QString("Syntax Error at '") + token + "'");
1295 ProjectFile::readVacation(time_t& from, time_t& to, bool readName,
1301 if ((tt = nextToken(*n)) != STRING)
1303 fatalError("String expected");
1308 if ((tt = nextToken(start)) != DATE)
1310 fatalError("Date expected");
1314 if ((tt = nextToken(token)) != MINUS)
1316 // vacation e. g. 2001-11-28
1317 returnToken(tt, token);
1318 from = date2time(start);
1319 to = sameTimeNextDay(date2time(start)) - 1;
1323 // vacation e. g. 2001-11-28 - 2001-11-30
1325 if ((tt = nextToken(end)) != DATE)
1327 fatalError("Date expected");
1330 from = date2time(start);
1331 if (date2time(start) > date2time(end))
1333 fatalError("Vacation must start before end");
1336 to = date2time(end) - 1;
1342 ProjectFile::readResource(Resource* parent)
1344 // Syntax: 'resource id "name" { ... }
1346 if (nextToken(id) != ID)
1348 fatalError("ID expected");
1352 if (nextToken(name) != STRING)
1354 fatalError("String expected");
1358 if (proj->getResource(id))
1360 fatalError(QString().sprintf(
1361 "Resource %s has already been defined", id.latin1()));
1365 Resource* r = new Resource(proj, id, name, parent);
1369 if ((tt = nextToken(token)) == LCBRACE)
1371 // read optional attributes
1372 if (!readResourceBody(r))
1376 returnToken(tt, token);
1378 proj->addResource(r);
1384 ProjectFile::readResourceSupplement()
1388 if (nextToken(token) != ID || (r = proj->getResource(token)) == 0)
1390 fatalError("Already defined resource ID expected");
1393 if (nextToken(token) != LCBRACE)
1395 fatalError("'{' expected");
1398 return readResourceBody(r);
1402 ProjectFile::readResourceBody(Resource* r)
1407 while ((tt = nextToken(token)) != RCBRACE)
1411 fatalError(QString("Unknown attribute '") + token + "'");
1414 if (token == KW("resource"))
1416 if (!readResource(r))
1419 else if (token == KW("mineffort"))
1421 if (nextToken(token) != REAL)
1423 fatalError("Real value exptected");
1426 r->setMinEffort(token.toDouble());
1428 else if (token == KW("maxeffort"))
1430 if (nextToken(token) != REAL)
1432 fatalError("Real value exptected");
1435 r->setMaxEffort(token.toDouble());
1437 else if (token == KW("efficiency"))
1439 if (nextToken(token) != REAL)
1441 fatalError("Read value expected");
1444 r->setEfficiency(token.toDouble());
1446 else if (token == KW("rate"))
1448 if (nextToken(token) != REAL)
1450 fatalError("Real value exptected");
1453 r->setRate(token.toDouble());
1455 else if (token == KW("kotrusid"))
1457 if (nextToken(token) != STRING)
1459 fatalError("String expected");
1462 r->setKotrusId(token);
1464 else if (token == KW("vacation"))
1467 if (!readVacation(from, to))
1469 r->addVacation(new Interval(from, to));
1471 else if (token == KW("workinghours"))
1474 QPtrList<Interval>* l = new QPtrList<Interval>();
1475 if (!readWorkingHours(dow, l))
1478 r->setWorkingHours(dow, l);
1480 else if (token == KW("shift"))
1483 if (nextToken(id) != ID)
1485 fatalError("Shift ID expected");
1489 if ((s = proj->getShift(id)) == 0)
1491 fatalError("Unknown shift");
1495 if (!readVacation(from, to))
1497 if (!r->addShift(Interval(from, to), s))
1499 fatalError("Shift interval overlaps with other");
1503 else if (token == KW("planbooking"))
1506 if ((b = readBooking()) == 0)
1508 if (!r->addPlanBooking(b))
1510 fatalError("Resource is already booked during this period");
1514 else if (token == KW("actualbooking"))
1517 if ((b = readBooking()) == 0)
1519 if (!r->addActualBooking(b))
1521 fatalError("Resource is already booked during this period");
1525 else if (token == KW("flags"))
1530 if (nextToken(flag) != ID || !proj->isAllowedFlag(flag))
1532 fatalError("flag unknown");
1536 if ((tt = nextToken(token)) != COMMA)
1538 returnToken(tt, token);
1543 else if (token == KW("include"))
1551 fatalError(QString("Unknown attribute '") + token + "'");
1560 ProjectFile::readShift(Shift* parent)
1562 // Syntax: 'shift id "name" { ... }
1564 if (nextToken(id) != ID)
1566 fatalError("ID expected");
1570 if (nextToken(name) != STRING)
1572 fatalError("String expected");
1576 if (proj->getShift(id))
1578 fatalError(QString().sprintf(
1579 "Shift %s has already been defined", id.latin1()));
1583 Shift* s = new Shift(proj, id, name, parent);
1587 if ((tt = nextToken(token)) == LCBRACE)
1589 // read optional attributes
1590 while ((tt = nextToken(token)) != RCBRACE)
1594 fatalError(QString("Unknown attribute '") + token + "'");
1597 if (token == KW("shift"))
1602 else if (token == KW("workinghours"))
1605 QPtrList<Interval>* l = new QPtrList<Interval>();
1606 if (!readWorkingHours(dow, l))
1609 s->setWorkingHours(dow, l);
1611 else if (token == KW("include"))
1619 fatalError(QString("Unknown attribute '") + token + "'");
1625 returnToken(tt, token);
1633 ProjectFile::readBooking()
1637 if (nextToken(token) != DATE)
1639 fatalError("Start date expected");
1642 time_t start = date2time(token);
1643 if (start < proj->getStart() || start >= proj->getEnd())
1645 fatalError("Start date must be within the project timeframe");
1649 if (nextToken(token) != DATE)
1651 fatalError("End date expected");
1654 time_t end = date2time(token);
1655 if (end <= proj->getStart() || end > proj->getEnd())
1657 fatalError("End date must be within the project timeframe");
1662 fatalError("End date must be after start date");
1667 if (nextToken(pid) != ID || !proj->isValidId(pid))
1669 fatalError("Known project ID expected");
1674 if (((tt = nextToken(token)) != ID && tt != RELATIVE_ID) ||
1675 (task = proj->getTask(token)) == 0)
1677 fatalError("Task ID expected");
1680 return new Booking(Interval(start, end), task, "", pid);
1684 ProjectFile::readAccount(Account* parent)
1686 // Syntax: 'account id "name" { ... }
1688 if (nextToken(id) != ID)
1690 fatalError("ID expected");
1694 if (proj->getAccount(id))
1696 fatalError(QString().sprintf(
1697 "Account %s has already been defined", id.latin1()));
1702 if (nextToken(name) != STRING)
1704 fatalError("String expected");
1707 Account::AccountType acctType;
1710 /* Only accounts with no parent can have a type specifier. All
1711 * sub accounts inherit the type of the parent. */
1713 if (nextToken(at) != ID && (at != KW("cost") ||
1714 at != KW("revenue")))
1716 fatalError("Account type 'cost' or 'revenue' expected");
1719 acctType = at == KW("cost") ? Account::Cost : Account::Revenue;
1722 acctType = parent->getAcctType();
1724 Account* a = new Account(proj, id, name, parent, acctType);
1730 if ((tt = nextToken(token)) == LCBRACE)
1732 bool hasSubAccounts = FALSE;
1733 bool cantBeParent = FALSE;
1734 // read optional attributes
1735 while ((tt = nextToken(token)) != RCBRACE)
1739 fatalError(QString("Unknown attribute '") + token + "'");
1742 if (token == KW("account") && !cantBeParent)
1744 if (!readAccount(a))
1746 hasSubAccounts = TRUE;
1748 else if (token == KW("credit"))
1753 else if (token == KW("kotrusid") && !hasSubAccounts)
1755 if (nextToken(token) != STRING)
1757 fatalError("String expected");
1760 a->setKotrusId(token);
1761 cantBeParent = TRUE;
1763 else if (token == KW("include"))
1770 fatalError("Illegal attribute");
1776 returnToken(tt, token);
1778 proj->addAccount(a);
1784 ProjectFile::readCredit(Account* a)
1788 if (nextToken(token) != DATE)
1790 fatalError("Date expected");
1793 time_t date = date2time(token);
1795 QString description;
1796 if (nextToken(description) != STRING)
1798 fatalError("String expected");
1802 if (nextToken(token) != REAL)
1804 fatalError("Real value expected");
1807 Transaction* t = new Transaction(date, token.toDouble(), description);
1814 ProjectFile::readAllocate(Task* t)
1818 if (nextToken(id) != ID || (r = proj->getResource(id)) == 0)
1820 fatalError("Resource ID expected");
1823 Allocation* a = new Allocation(r);
1826 if ((tt = nextToken(token)) == LCBRACE)
1828 while ((tt = nextToken(token)) != RCBRACE)
1832 fatalError(QString("Unknown attribute '") + token + "'");
1835 if (token == KW("load"))
1837 if (nextToken(token) != REAL)
1839 fatalError("Real value expected");
1842 double load = token.toDouble();
1843 if (load < 0.01 || load > 1.0)
1845 fatalError("Value must be in the range 0.01 - 1.0");
1848 a->setLoad((int) (100 * load));
1850 else if (token == KW("persistent"))
1852 a->setPersistent(TRUE);
1854 else if (token == KW("alternative"))
1859 if ((tt = nextToken(token)) != ID ||
1860 (r = proj->getResource(token)) == 0)
1862 fatalError("Resource ID expected");
1865 a->addAlternative(r);
1866 } while ((tt = nextToken(token)) == COMMA);
1867 returnToken(tt, token);
1872 returnToken(tt, token);
1873 t->addAllocation(a);
1879 ProjectFile::readTimeValue(ulong& value)
1882 if (nextToken(val) != INTEGER)
1884 fatalError("Integer value expected");
1888 if (nextToken(unit) != ID)
1890 fatalError("Unit expected");
1893 if (unit == KW("min"))
1894 value = val.toULong() * 60;
1895 else if (unit == KW("h"))
1896 value = val.toULong() * (60 * 60);
1897 else if (unit == KW("d"))
1898 value = val.toULong() * (60 * 60 * 24);
1899 else if (unit == KW("w"))
1900 value = val.toULong() * (60 * 60 * 24 * 7);
1901 else if (unit == KW("m"))
1902 value = val.toULong() * (60 * 60 * 24 * 30);
1903 else if (unit == KW("y"))
1904 value = val.toULong() * (60 * 60 * 24 * 356);
1907 fatalError("Unit expected");
1914 ProjectFile::readPlanTimeFrame(Task* task, double& value)
1918 if ((tt = nextToken(val)) != REAL && tt != INTEGER)
1920 fatalError("Real value expected");
1924 if (nextToken(unit) != ID)
1926 fatalError("Unit expected");
1929 if (unit == KW("min"))
1930 value = val.toDouble() / (8 * 60);
1931 else if (unit == KW("h"))
1932 value = val.toDouble() / 8;
1933 else if (unit == KW("d"))
1934 value = val.toDouble();
1935 else if (unit == KW("w"))
1936 value = val.toDouble() * 5;
1937 else if (unit == KW("m"))
1938 value = val.toDouble() * 20;
1939 else if (unit == KW("y"))
1940 value = val.toDouble() * 240;
1943 fatalError("Unit expected");
1951 ProjectFile::readWorkingHours(int& dayOfWeek, QPtrList<Interval>* l)
1953 l->setAutoDelete(TRUE);
1955 if (nextToken(day) != ID)
1957 fatalError("Weekday expected");
1960 const char* days[] = { KW("sun"), KW("mon"), KW("tue"), KW("wed"),
1961 KW("thu"), KW("fri"), KW("sat") };
1962 for (dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++)
1963 if (days[dayOfWeek] == day)
1967 fatalError("Weekday expected");
1973 if ((tt = nextToken(token)) == ID && token == KW("off"))
1976 returnToken(tt, token);
1981 if (nextToken(start) != HOUR)
1983 fatalError("Start time as HH:MM expected");
1987 if (nextToken(token) != MINUS)
1989 fatalError("'-' expected");
1993 if (nextToken(end) != HOUR)
1995 fatalError("End time as HH:MM expected");
1998 l->append(new Interval(hhmm2time(start), hhmm2time(end)));
2000 if ((tt = nextToken(token)) != COMMA)
2002 returnToken(tt, token);
2010 ProjectFile::readPriority(int& priority)
2014 if (nextToken(token) != INTEGER)
2016 fatalError("Integer value expected");
2019 priority = token.toInt();
2020 if (priority < 1 || priority > 1000)
2022 fatalError("Priority value must be between 1 and 1000");
2030 ProjectFile::readICalTaskReport()
2033 if (nextToken(token) != STRING)
2035 fatalError("File name expected");
2038 ReportICal *rep = new ReportICal( proj, token, proj->getStart(), proj->getEnd());
2039 proj->addICalReport( rep );
2046 ProjectFile::readXMLTaskReport()
2049 if (nextToken(token) != STRING)
2051 fatalError("File name expected");
2054 ReportXML *rep = new ReportXML(proj, token, proj->getStart(),
2056 proj->addXMLReport( rep );
2063 ProjectFile::readHTMLReport(const QString& reportType)
2066 if (nextToken(token) != STRING)
2068 fatalError("File name expected");
2073 if (reportType == KW("htmltaskreport"))
2074 report = new HTMLTaskReport(proj, token, proj->getStart(),
2076 else if (reportType == KW("htmlresourcereport"))
2077 report = new HTMLResourceReport(proj, token, proj->getStart(),
2081 qFatal("readHTMLReport: bad report type");
2082 return FALSE; // Just to please the compiler.
2086 if ((tt = nextToken(token)) != LCBRACE)
2088 returnToken(tt, token);
2094 if ((tt = nextToken(token)) == RCBRACE)
2098 fatalError("Attribute ID or '}' expected");
2101 if (token == KW("columns"))
2103 report->clearColumns();
2107 if ((tt = nextToken(col)) != ID)
2109 fatalError("Column ID expected");
2112 report->addColumn(col);
2113 if ((tt = nextToken(token)) != COMMA)
2115 returnToken(tt, token);
2120 else if (token == KW("start"))
2122 if (nextToken(token) != DATE)
2124 fatalError("Date expected");
2127 report->setStart(date2time(token));
2129 else if (token == KW("end"))
2131 if (nextToken(token) != DATE)
2133 fatalError("Date expected");
2136 report->setEnd(date2time(token));
2138 else if (token == KW("headline"))
2140 if (nextToken(token) != STRING)
2142 fatalError("String exptected");
2145 report->setHeadline(token);
2147 else if (token == KW("caption"))
2149 if (nextToken(token) != STRING)
2151 fatalError("String exptected");
2154 report->setCaption(token);
2156 else if (token == KW("showactual"))
2158 report->setShowActual(TRUE);
2160 else if (token == KW("showprojectids"))
2162 report->setShowPIDs(TRUE);
2164 else if (token == KW("hidetask"))
2167 if ((op = readLogicalExpression()) == 0)
2169 ExpressionTree* et = new ExpressionTree(op);
2170 report->setHideTask(et);
2172 else if (token == KW("rolluptask"))
2175 if ((op = readLogicalExpression()) == 0)
2177 ExpressionTree* et = new ExpressionTree(op);
2178 report->setRollUpTask(et);
2180 else if (token == KW("sorttasks"))
2182 if (!readSorting(report, 0))
2185 else if (token == KW("hideresource"))
2188 if ((op = readLogicalExpression()) == 0)
2190 ExpressionTree* et = new ExpressionTree(op);
2191 report->setHideResource(et);
2193 else if (token == KW("rollupresource"))
2196 if ((op = readLogicalExpression()) == 0)
2198 ExpressionTree* et = new ExpressionTree(op);
2199 report->setRollUpResource(et);
2201 else if (token == KW("sortresources"))
2203 if (!readSorting(report, 1))
2208 fatalError("Illegal attribute");
2213 if (reportType == KW("htmltaskreport"))
2214 proj->addHTMLTaskReport((HTMLTaskReport*) report);
2216 proj->addHTMLResourceReport((HTMLResourceReport*) report);
2222 ProjectFile::readHTMLAccountReport()
2225 if (nextToken(token) != STRING)
2227 fatalError("File name expected");
2231 HTMLAccountReport* report;
2232 report = new HTMLAccountReport(proj, token, proj->getStart(),
2236 if ((tt = nextToken(token)) != LCBRACE)
2238 returnToken(tt, token);
2244 if ((tt = nextToken(token)) == RCBRACE)
2248 fatalError("Attribute ID or '}' expected");
2251 if (token == KW("columns"))
2253 report->clearColumns();
2257 if ((tt = nextToken(col)) != ID)
2259 fatalError("Column ID expected");
2262 report->addColumn(col);
2263 if ((tt = nextToken(token)) != COMMA)
2265 returnToken(tt, token);
2270 else if (token == KW("start"))
2272 if (nextToken(token) != DATE)
2274 fatalError("Date expected");
2277 report->setStart(date2time(token));
2279 else if (token == KW("end"))
2281 if (nextToken(token) != DATE)
2283 fatalError("Date expected");
2286 report->setEnd(date2time(token));
2288 else if (token == KW("headline"))
2290 if (nextToken(token) != STRING)
2292 fatalError("String exptected");
2295 report->setHeadline(token);
2297 else if (token == KW("caption"))
2299 if (nextToken(token) != STRING)
2301 fatalError("String exptected");
2304 report->setCaption(token);
2306 else if (token == KW("hideplan"))
2308 report->setHidePlan(TRUE);
2310 else if (token == KW("showactual"))
2312 report->setShowActual(TRUE);
2314 else if (token == KW("accumulate"))
2316 report->setAccumulate(TRUE);
2318 else if (token == KW("hideaccount"))
2321 if ((op = readLogicalExpression()) == 0)
2323 ExpressionTree* et = new ExpressionTree(op);
2324 report->setHideAccount(et);
2326 else if (token == KW("rollupaccount"))
2329 if ((op = readLogicalExpression()) == 0)
2331 ExpressionTree* et = new ExpressionTree(op);
2332 report->setRollUpAccount(et);
2334 else if (token == KW("sortaccounts"))
2336 if (!readSorting(report, 2))
2341 fatalError("Illegal attribute");
2346 proj->addHTMLAccountReport(report);
2352 ProjectFile::readExportReport()
2355 if (nextToken(token) != STRING)
2357 fatalError("File name expected");
2361 ExportReport* report;
2362 report = new ExportReport(proj, token);
2365 if ((tt = nextToken(token)) != LCBRACE)
2367 returnToken(tt, token);
2373 if ((tt = nextToken(token)) == RCBRACE)
2377 fatalError("Attribute ID or '}' expected");
2381 if (token == KW("hidetask"))
2384 if ((op = readLogicalExpression()) == 0)
2386 ExpressionTree* et = new ExpressionTree(op);
2387 report->setHideTask(et);
2389 else if (token == KW("rolluptask"))
2392 if ((op = readLogicalExpression()) == 0)
2394 ExpressionTree* et = new ExpressionTree(op);
2395 report->setRollUpTask(et);
2397 else if (token == KW("hideresource"))
2400 if ((op = readLogicalExpression()) == 0)
2402 ExpressionTree* et = new ExpressionTree(op);
2403 report->setHideResource(et);
2405 else if (token == KW("rollupresource"))
2408 if ((op = readLogicalExpression()) == 0)
2410 ExpressionTree* et = new ExpressionTree(op);
2411 report->setRollUpResource(et);
2415 fatalError("Illegal attribute");
2420 proj->addExportReport(report);
2426 ProjectFile::readLogicalExpression(int precedence)
2432 if ((tt = nextToken(token)) == ID || tt == RELATIVE_ID)
2434 if (proj->isAllowedFlag(token))
2435 op = new Operation(token);
2436 else if (proj->getTask(token))
2437 op = new Operation(Operation::TaskId, token);
2438 else if (proj->getResource(token))
2439 op = new Operation(Operation::ResourceId, token);
2440 else if (proj->getAccount(token))
2441 op = new Operation(Operation::AccountId, token);
2442 else if (ExpressionTree::isFunction(token))
2444 if ((op = readFunctionCall(token)) == 0)
2449 fatalError(QString("Flag or function '") + token + "' is unknown.");
2453 else if (tt == INTEGER)
2455 op = new Operation(token.toLong());
2457 else if (tt == TILDE)
2459 if ((op = readLogicalExpression(1)) == 0)
2463 op = new Operation(op, Operation::Not);
2465 else if (tt == LBRACE)
2467 if ((op = readLogicalExpression()) == 0)
2471 if ((tt = nextToken(token)) != RBRACE)
2473 fatalError("')' expected");
2479 fatalError("Logical expression expected");
2485 if ((tt = nextToken(token)) != AND && tt != OR)
2487 returnToken(tt, token);
2491 Operation* op2 = readLogicalExpression();
2492 op = new Operation(op, Operation::And, op2);
2496 Operation* op2 = readLogicalExpression();
2497 op = new Operation(op, Operation::Or, op2);
2505 ProjectFile::readFunctionCall(const QString& name)
2510 if ((tt = nextToken(token)) != LBRACE)
2512 fatalError("'(' expected");
2515 QPtrList<Operation> args;
2516 for (int i = 0; i < ExpressionTree::arguments(name); i++)
2519 if ((op = readLogicalExpression()) == 0)
2522 if ((i < ExpressionTree::arguments(name) - 1) &&
2523 nextToken(token) != COMMA)
2525 fatalError("Comma expected");
2529 if ((tt = nextToken(token)) != RBRACE)
2531 fatalError("')' expected");
2534 return new Operation(name, args);
2538 ProjectFile::readSorting(Report* report, int which)
2543 CoreAttributesList::SortCriteria sorting;
2544 if (token == KW("tree"))
2545 sorting = CoreAttributesList::TreeMode;
2546 else if (token == KW("indexup"))
2547 sorting = CoreAttributesList::IndexUp;
2548 else if (token == KW("indexdown"))
2549 sorting = CoreAttributesList::IndexDown;
2550 else if (token == KW("idup"))
2551 sorting = CoreAttributesList::IdUp;
2552 else if (token == KW("iddown"))
2553 sorting = CoreAttributesList::IdDown;
2554 else if (token == KW("fullnameup"))
2555 sorting = CoreAttributesList::FullNameUp;
2556 else if (token == KW("fullnamedown"))
2557 sorting = CoreAttributesList::FullNameDown;
2558 else if (token == KW("nameup"))
2559 sorting = CoreAttributesList::NameUp;
2560 else if (token == KW("namedown"))
2561 sorting = CoreAttributesList::NameDown;
2562 else if (token == KW("startup"))
2563 sorting = CoreAttributesList::StartUp;
2564 else if (token == KW("startdown"))
2565 sorting = CoreAttributesList::StartDown;
2566 else if (token == KW("endup"))
2567 sorting = CoreAttributesList::EndUp;
2568 else if (token == KW("enddown"))
2569 sorting = CoreAttributesList::EndDown;
2570 else if (token == KW("priorityup"))
2571 sorting = CoreAttributesList::PrioUp;
2572 else if (token == KW("prioritydown"))
2573 sorting = CoreAttributesList::PrioDown;
2574 else if (token == KW("responsibleup"))
2575 sorting = CoreAttributesList::ResponsibleUp;
2576 else if (token == KW("responsibledown"))
2577 sorting = CoreAttributesList::ResponsibleDown;
2578 else if (token == KW("mineffortup"))
2579 sorting = CoreAttributesList::MinEffortUp;
2580 else if (token == KW("mineffortdown"))
2581 sorting = CoreAttributesList::MinEffortDown;
2582 else if (token == KW("maxeffortup"))
2583 sorting = CoreAttributesList::MaxEffortUp;
2584 else if (token == KW("maxeffortdown"))
2585 sorting = CoreAttributesList::MaxEffortDown;
2586 else if (token == KW("rateup"))
2587 sorting = CoreAttributesList::RateUp;
2588 else if (token == KW("ratedown"))
2589 sorting = CoreAttributesList::RateDown;
2590 else if (token == KW("kotrusidup"))
2591 sorting = CoreAttributesList::KotrusIdUp;
2592 else if (token == KW("kotrusiddown"))
2593 sorting = CoreAttributesList::KotrusIdDown;
2596 fatalError("Sorting criteria expected");
2603 report->setTaskSorting(sorting);
2606 report->setResourceSorting(sorting);
2609 report->setAccountSorting(sorting);
2612 qFatal("readSorting: Unknown sorting attribute");
2620 ProjectFile::date2time(const QString& date)
2622 int y, m, d, hour, min, sec;
2623 char tZone[16] = "";
2624 char savedTZ[16] = "";
2625 if (sscanf(date, "%d-%d-%d-%d:%d:%d-%s",
2626 &y, &m, &d, &hour, &min, &sec, tZone) == 7 ||
2627 (sec = 0) || // set sec to 0
2628 sscanf(date, "%d-%d-%d-%d:%d-%s",
2629 &y, &m, &d, &hour, &min, tZone) == 6)
2632 strcpy(getenv("TZ"), savedTZ);
2634 if ((tz = timezone2tz(tZone)) == 0)
2635 fatalError(QString("Illegal timezone %s").arg(tZone));
2637 setenv("TZ", tz, 1);
2639 else if (sscanf(date, "%d-%d-%d-%d:%d:%d",
2640 &y, &m, &d, &hour, &min, &sec) == 6)
2642 else if (sscanf(date, "%d-%d-%d-%d:%d", &y, &m, &d, &hour, &min) == 5)
2647 else if (sscanf(date, "%d-%d-%d", &y, &m, &d) == 3)
2650 hour = min = sec = 0;
2654 qFatal("Illegal date: %s", date.latin1());
2660 fatalError("Year must be larger than 1969");
2663 if (m < 1 || m > 12)
2665 fatalError("Month must be between 1 and 12");
2668 if (d < 1 || d > 31)
2670 fatalError("Day must be between 1 and 31");
2674 struct tm t = { sec, min, hour, d, m - 1, y - 1900, 0, 0, -1, 0, 0 };
2675 time_t localTime = mktime(&t);
2677 if (strcmp(savedTZ, "") != 0)
2678 setenv("TZ", savedTZ, 1);
2686 ProjectFile::hhmm2time(const QString& hhmm)
2689 sscanf(hhmm, "%d:%d", &hour, &min);
2690 return hour * 60 * 60 + min * 60;