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]-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);
266 while ((c = getC()) != EOF && isalnum(c) && i++ < 12)
274 // must be a real number
276 while ((c = getC()) != EOF && isdigit(c))
283 // must be a time (HH:MM)
285 for (int i = 0; i < 2; i++)
287 if ((c = getC()) != EOF && isdigit(c))
291 fatalError("2 digits minutes expected");
306 while ((c = getC()) != EOF && c != '"')
314 fatalError("Non terminated string");
322 while ((c = getC(FALSE)) != EOF && (c != ']' || nesting > 0))
334 fatalError("Non terminated macro definition");
363 fatalError(QString("Illegal character '") + c + "'");
371 FileInfo::readMacroCall()
375 if ((tt = nextToken(id)) != ID && tt != INTEGER)
377 fatalError("Macro ID expected");
381 // Store all arguments in a newly created string list.
382 QStringList* sl = new QStringList;
383 while ((tt = nextToken(token)) == STRING)
387 fatalError("'}' expected");
391 // push string list to global argument stack
392 pf->getMacros().pushArguments(sl);
395 QString macro = pf->getMacros().expand(id);
398 fatalError(QString("Unknown macro ") + id);
402 // Push pointer to macro on stack. Needed for error handling.
403 macroStack.append(pf->getMacros().getMacro(id));
407 // push expanded macro reverse into ungetC buffer.
408 for (int i = macro.length() - 1; i >= 0; --i)
409 ungetC(macro[i].latin1());
414 FileInfo::returnToken(TokenType tt, const QString& buf)
416 if (tokenTypeBuf != INVALID)
418 qFatal("Internal Error: Token buffer overflow!");
426 FileInfo::fatalError(const QString& msg)
428 if (macroStack.isEmpty())
430 qWarning("%s:%d:%s", file.latin1(), currLine, msg.latin1());
431 qWarning("%s", lineBuf.latin1());
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());
443 ProjectFile::ProjectFile(Project* p)
447 openFiles.setAutoDelete(TRUE);
451 ProjectFile::open(const QString& file)
453 QString absFileName = file;
454 if (absFileName[0] != '/')
456 if (openFiles.isEmpty())
459 if (getcwd(buf, 1023) != 0)
460 absFileName = QString(buf) + "/" + absFileName;
462 qFatal("ProjectFile::open(): getcwd failed");
465 absFileName = openFiles.last()->getPath() + absFileName;
467 qWarning("Expanded filename to %s", absFileName.latin1());
470 while (absFileName.find("/../", end) >= 0)
472 end = absFileName.find("/../");
473 int start = absFileName.findRev('/', end - 1);
477 start++; // move after '/'
478 if (start < end && absFileName.mid(start, end - start) != "..")
479 absFileName.remove(start, end + strlen("/../") - start);
480 end += strlen("/..");
483 // Make sure that we include each file only once.
484 if (includedFiles.findIndex(absFileName) != -1)
487 qWarning("Ignoring already read file %s",
488 absFileName.latin1());
492 FileInfo* fi = new FileInfo(this, absFileName);
496 qFatal("Cannot open '%s'", absFileName.latin1());
501 qWarning("Reading %s", absFileName.latin1());
503 openFiles.append(fi);
504 includedFiles.append(absFileName);
513 FileInfo* fi = openFiles.getLast();
517 openFiles.removeLast();
530 switch (tt = nextToken(token))
535 if (token == KW("task"))
541 if (token == KW("account"))
547 else if (token == KW("resource"))
549 if (!readResource(0))
553 else if (token == KW("shift"))
559 else if (token == KW("vacation"))
562 bool isResourceVacation;
564 if (!readVacation(from, to, TRUE, &name,
565 &isResourceVacation))
567 if (isResourceVacation)
568 proj->getResource(name)->addVacation(
569 new Interval(from, to));
570 proj->addVacation(name, from, to);
573 else if (token == KW("priority"))
576 if (!readPriority(priority))
578 proj->setPriority(priority);
581 else if (token == KW("now"))
583 if (nextToken(token) != DATE)
585 fatalError("Date expected");
588 proj->setNow(date2time(token));
591 else if (token == KW("mineffort"))
593 if (nextToken(token) != REAL)
595 fatalError("Real value exptected");
598 proj->setMinEffort(token.toDouble());
601 else if (token == KW("maxeffort"))
603 if (nextToken(token) != REAL)
605 fatalError("Real value exptected");
608 proj->setMaxEffort(token.toDouble());
611 else if (token == KW("rate"))
613 if (nextToken(token) != REAL)
615 fatalError("Real value exptected");
618 proj->setRate(token.toDouble());
621 else if (token == KW("currency"))
623 if (nextToken(token) != STRING)
625 fatalError("String expected");
628 proj->setCurrency(token);
631 else if (token == KW("currencydigits"))
633 if (nextToken(token) != INTEGER)
635 fatalError("Integer value expected");
638 proj->setCurrencyDigits(token.toInt());
641 else if (token == KW("timingresolution"))
644 if (!readTimeValue(resolution))
646 if (proj->resourceCount() > 0)
648 fatalError("The timing resolution cannot be changed after "
649 "resources have been declared.");
652 if (resolution < 60 * 5)
654 fatalError("timing resolution must be at least 5 min");
657 proj->setScheduleGranularity(resolution);
660 else if (token == KW("copyright"))
662 if (nextToken(token) != STRING)
664 fatalError("String expected");
667 proj->setCopyright(token);
670 else if (token == KW("include"))
676 else if (token == KW("macro"))
679 if (nextToken(id) != ID)
681 fatalError("Macro ID expected");
684 QString file = openFiles.last()->getFile();
685 uint line = openFiles.last()->getLine();
686 if (nextToken(token) != MacroBody)
688 fatalError("Macro body expected");
691 Macro* macro = new Macro(id, token, file, line);
692 if (!macros.addMacro(macro))
694 fatalError("Macro has been defined already");
700 else if (token == KW("flags"))
705 if (nextToken(flag) != ID)
707 fatalError("flag ID expected");
711 /* Flags can be declared multiple times, but we
712 * register a flag only once. */
713 if (!proj->isAllowedFlag(flag))
714 proj->addAllowedFlag(flag);
716 if ((tt = nextToken(token)) != COMMA)
718 openFiles.last()->returnToken(tt, token);
724 else if (token == KW("project"))
726 if (nextToken(token) != ID)
728 fatalError("Project ID expected");
731 if (!proj->addId(token))
733 fatalError(QString().sprintf(
734 "Project ID %s has already been registered",
738 if (nextToken(token) != STRING)
740 fatalError("Project name expected");
743 proj->setName(token);
744 if (nextToken(token) != STRING)
746 fatalError("Version string expected");
749 proj->setVersion(token);
751 if (nextToken(token) != DATE)
753 fatalError("Start date expected");
756 start = date2time(token);
757 if (nextToken(token) != DATE)
759 fatalError("End date expected");
762 end = date2time(token);
765 fatalError("End date must be larger then start date");
768 proj->setStart(start);
772 else if (token == KW("projectid"))
777 if (nextToken(id) != ID)
779 fatalError("Project ID expected");
783 if (!proj->addId(id))
785 fatalError(QString().sprintf(
786 "Project ID %s has already been registered",
791 if ((tt = nextToken(token)) != COMMA)
793 openFiles.last()->returnToken(tt, token);
799 else if (token == KW("xmltaskreport"))
801 if( !readXMLTaskReport())
806 else if (token == "icalreport" )
808 if( !readICalTaskReport())
813 else if (token == KW("htmltaskreport") ||
814 token == KW("htmlresourcereport"))
816 if (!readHTMLReport(token))
820 else if (token == KW("htmlaccountreport"))
822 if (!readHTMLAccountReport())
826 else if (token == KW("export"))
828 if (!readExportReport())
832 else if (token == KW("kotrusmode"))
834 if (nextToken(token) != STRING ||
835 (token != KW("db") && token != KW("xml") &&
836 token != KW("nokotrus")))
838 fatalError("Unknown kotrus mode");
841 if (token != KW("nokotrus"))
843 Kotrus* kotrus = new Kotrus();
844 kotrus->setKotrusMode(token);
845 proj->setKotrus(kotrus);
849 else if (token == KW("supplement"))
851 if (nextToken(token) != ID ||
852 (token != KW("task") && (token != KW("resource"))))
854 fatalError("'task' or 'resource' expected");
857 if ((token == "task" && !readTaskSupplement()) ||
858 (token == "resource" && !readResourceSupplement()))
862 // break missing on purpose!
864 fatalError(QString("Syntax Error at '") + token + "'!");
873 ProjectFile::nextToken(QString& buf)
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 openFiles.last()->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 openFiles.last()->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 openFiles.last()->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,
1296 QString* n, bool* isResourceVacation)
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
1305 *isResourceVacation = FALSE;
1306 if ((tt = nextToken(*n)) == STRING)
1307 ; // We don't have to do anything
1311 if (!proj->getResource(*n))
1313 fatalError(QString().sprintf(
1314 "Resource %s is undefined", n->latin1()));
1317 *isResourceVacation = TRUE;
1322 fatalError("String expected");
1327 if ((tt = nextToken(start)) != DATE)
1329 fatalError("Date expected");
1333 if ((tt = nextToken(token)) != MINUS)
1335 // vacation e. g. 2001-11-28
1336 openFiles.last()->returnToken(tt, token);
1337 from = date2time(start);
1338 to = sameTimeNextDay(date2time(start)) - 1;
1342 // vacation e. g. 2001-11-28 - 2001-11-30
1344 if ((tt = nextToken(end)) != DATE)
1346 fatalError("Date expected");
1349 from = date2time(start);
1350 if (date2time(start) > date2time(end))
1352 fatalError("Vacation must start before end");
1355 to = date2time(end) - 1;
1361 ProjectFile::readResource(Resource* parent)
1363 // Syntax: 'resource id "name" { ... }
1365 if (nextToken(id) != ID)
1367 fatalError("ID expected");
1371 if (nextToken(name) != STRING)
1373 fatalError("String expected");
1377 if (proj->getResource(id))
1379 fatalError(QString().sprintf(
1380 "Resource %s has already been defined", id.latin1()));
1384 Resource* r = new Resource(proj, id, name, parent);
1388 if ((tt = nextToken(token)) == LCBRACE)
1390 // read optional attributes
1391 if (!readResourceBody(r))
1395 openFiles.last()->returnToken(tt, token);
1397 proj->addResource(r);
1403 ProjectFile::readResourceSupplement()
1407 if (nextToken(token) != ID || (r = proj->getResource(token)) == 0)
1409 fatalError("Already defined resource ID expected");
1412 if (nextToken(token) != LCBRACE)
1414 fatalError("'{' expected");
1417 return readResourceBody(r);
1421 ProjectFile::readResourceBody(Resource* r)
1426 while ((tt = nextToken(token)) != RCBRACE)
1430 fatalError(QString("Unknown attribute '") + token + "'");
1433 if (token == KW("resource"))
1435 if (!readResource(r))
1438 else if (token == KW("mineffort"))
1440 if (nextToken(token) != REAL)
1442 fatalError("Real value exptected");
1445 r->setMinEffort(token.toDouble());
1447 else if (token == KW("maxeffort"))
1449 if (nextToken(token) != REAL)
1451 fatalError("Real value exptected");
1454 r->setMaxEffort(token.toDouble());
1456 else if (token == KW("efficiency"))
1458 if (nextToken(token) != REAL)
1460 fatalError("Read value expected");
1463 r->setEfficiency(token.toDouble());
1465 else if (token == KW("rate"))
1467 if (nextToken(token) != REAL)
1469 fatalError("Real value exptected");
1472 r->setRate(token.toDouble());
1474 else if (token == KW("kotrusid"))
1476 if (nextToken(token) != STRING)
1478 fatalError("String expected");
1481 r->setKotrusId(token);
1483 else if (token == KW("vacation"))
1486 if (!readVacation(from, to))
1488 r->addVacation(new Interval(from, to));
1490 else if (token == KW("workinghours"))
1493 QPtrList<Interval>* l = new QPtrList<Interval>();
1494 if (!readWorkingHours(dow, l))
1497 r->setWorkingHours(dow, l);
1499 else if (token == KW("shift"))
1502 if (nextToken(id) != ID)
1504 fatalError("Shift ID expected");
1508 if ((s = proj->getShift(id)) == 0)
1510 fatalError("Unknown shift");
1514 if (!readVacation(from, to))
1516 if (!r->addShift(Interval(from, to), s))
1518 fatalError("Shift interval overlaps with other");
1522 else if (token == KW("flags"))
1527 if (nextToken(flag) != ID || !proj->isAllowedFlag(flag))
1529 fatalError("flag unknown");
1533 if ((tt = nextToken(token)) != COMMA)
1535 openFiles.last()->returnToken(tt, token);
1540 else if (token == KW("include"))
1548 fatalError(QString("Unknown attribute '") + token + "'");
1557 ProjectFile::readShift(Shift* parent)
1559 // Syntax: 'shift id "name" { ... }
1561 if (nextToken(id) != ID)
1563 fatalError("ID expected");
1567 if (nextToken(name) != STRING)
1569 fatalError("String expected");
1573 if (proj->getShift(id))
1575 fatalError(QString().sprintf(
1576 "Shift %s has already been defined", id.latin1()));
1580 Shift* s = new Shift(proj, id, name, parent);
1584 if ((tt = nextToken(token)) == LCBRACE)
1586 // read optional attributes
1587 while ((tt = nextToken(token)) != RCBRACE)
1591 fatalError(QString("Unknown attribute '") + token + "'");
1594 if (token == KW("shift"))
1599 else if (token == KW("workinghours"))
1602 QPtrList<Interval>* l = new QPtrList<Interval>();
1603 if (!readWorkingHours(dow, l))
1606 s->setWorkingHours(dow, l);
1608 else if (token == KW("include"))
1616 fatalError(QString("Unknown attribute '") + token + "'");
1622 openFiles.last()->returnToken(tt, token);
1630 ProjectFile::readAccount(Account* parent)
1632 // Syntax: 'account id "name" { ... }
1634 if (nextToken(id) != ID)
1636 fatalError("ID expected");
1640 if (proj->getAccount(id))
1642 fatalError(QString().sprintf(
1643 "Account %s has already been defined", id.latin1()));
1648 if (nextToken(name) != STRING)
1650 fatalError("String expected");
1653 Account::AccountType acctType;
1656 /* Only accounts with no parent can have a type specifier. All
1657 * sub accounts inherit the type of the parent. */
1659 if (nextToken(at) != ID && (at != KW("cost") ||
1660 at != KW("revenue")))
1662 fatalError("Account type 'cost' or 'revenue' expected");
1665 acctType = at == KW("cost") ? Account::Cost : Account::Revenue;
1668 acctType = parent->getAcctType();
1670 Account* a = new Account(proj, id, name, parent, acctType);
1676 if ((tt = nextToken(token)) == LCBRACE)
1678 bool hasSubAccounts = FALSE;
1679 bool cantBeParent = FALSE;
1680 // read optional attributes
1681 while ((tt = nextToken(token)) != RCBRACE)
1685 fatalError(QString("Unknown attribute '") + token + "'");
1688 if (token == KW("account") && !cantBeParent)
1690 if (!readAccount(a))
1692 hasSubAccounts = TRUE;
1694 else if (token == KW("credit"))
1699 else if (token == KW("kotrusid") && !hasSubAccounts)
1701 if (nextToken(token) != STRING)
1703 fatalError("String expected");
1706 a->setKotrusId(token);
1707 cantBeParent = TRUE;
1709 else if (token == KW("include"))
1716 fatalError("Illegal attribute");
1722 openFiles.last()->returnToken(tt, token);
1724 proj->addAccount(a);
1730 ProjectFile::readCredit(Account* a)
1734 if (nextToken(token) != DATE)
1736 fatalError("Date expected");
1739 time_t date = date2time(token);
1741 QString description;
1742 if (nextToken(description) != STRING)
1744 fatalError("String expected");
1748 if (nextToken(token) != REAL)
1750 fatalError("Real value expected");
1753 Transaction* t = new Transaction(date, token.toDouble(), description);
1760 ProjectFile::readAllocate(Task* t)
1764 if (nextToken(id) != ID || (r = proj->getResource(id)) == 0)
1766 fatalError("Resource ID expected");
1769 Allocation* a = new Allocation(r);
1772 if ((tt = nextToken(token)) == LCBRACE)
1774 while ((tt = nextToken(token)) != RCBRACE)
1778 fatalError(QString("Unknown attribute '") + token + "'");
1781 if (token == KW("load"))
1783 if (nextToken(token) != REAL)
1785 fatalError("Real value expected");
1788 double load = token.toDouble();
1789 if (load < 0.01 || load > 1.0)
1791 fatalError("Value must be in the range 0.01 - 1.0");
1794 a->setLoad((int) (100 * load));
1796 else if (token == KW("persistent"))
1798 a->setPersistent(TRUE);
1800 else if (token == KW("alternative"))
1805 if ((tt = nextToken(token)) != ID ||
1806 (r = proj->getResource(token)) == 0)
1808 fatalError("Resource ID expected");
1811 a->addAlternative(r);
1812 } while ((tt = nextToken(token)) == COMMA);
1813 openFiles.last()->returnToken(tt, token);
1818 openFiles.last()->returnToken(tt, token);
1819 t->addAllocation(a);
1825 ProjectFile::readTimeValue(ulong& value)
1828 if (nextToken(val) != INTEGER)
1830 fatalError("Integer value expected");
1834 if (nextToken(unit) != ID)
1836 fatalError("Unit expected");
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);
1853 fatalError("Unit expected");
1860 ProjectFile::readPlanTimeFrame(Task* task, double& value)
1864 if ((tt = nextToken(val)) != REAL && tt != INTEGER)
1866 fatalError("Real value expected");
1870 if (nextToken(unit) != ID)
1872 fatalError("Unit expected");
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;
1889 fatalError("Unit expected");
1897 ProjectFile::readWorkingHours(int& dayOfWeek, QPtrList<Interval>* l)
1899 l->setAutoDelete(TRUE);
1901 if (nextToken(day) != ID)
1903 fatalError("Weekday expected");
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)
1913 fatalError("Weekday expected");
1919 if ((tt = nextToken(token)) == ID && token == KW("off"))
1922 returnToken(tt, token);
1927 if (nextToken(start) != HOUR)
1929 fatalError("Start time as HH:MM expected");
1933 if (nextToken(token) != MINUS)
1935 fatalError("'-' expected");
1939 if (nextToken(end) != HOUR)
1941 fatalError("End time as HH:MM expected");
1944 l->append(new Interval(hhmm2time(start), hhmm2time(end)));
1946 if ((tt = nextToken(token)) != COMMA)
1948 returnToken(tt, token);
1956 ProjectFile::readPriority(int& priority)
1960 if (nextToken(token) != INTEGER)
1962 fatalError("Integer value expected");
1965 priority = token.toInt();
1966 if (priority < 1 || priority > 1000)
1968 fatalError("Priority value must be between 1 and 1000");
1976 ProjectFile::readICalTaskReport()
1979 if (nextToken(token) != STRING)
1981 fatalError("File name expected");
1984 ReportICal *rep = new ReportICal( proj, token, proj->getStart(), proj->getEnd());
1985 proj->addICalReport( rep );
1992 ProjectFile::readXMLTaskReport()
1995 if (nextToken(token) != STRING)
1997 fatalError("File name expected");
2000 ReportXML *rep = new ReportXML(proj, token, proj->getStart(),
2002 proj->addXMLReport( rep );
2009 ProjectFile::readHTMLReport(const QString& reportType)
2012 if (nextToken(token) != STRING)
2014 fatalError("File name expected");
2019 if (reportType == KW("htmltaskreport"))
2020 report = new HTMLTaskReport(proj, token, proj->getStart(),
2022 else if (reportType == KW("htmlresourcereport"))
2023 report = new HTMLResourceReport(proj, token, proj->getStart(),
2027 qFatal("readHTMLReport: bad report type");
2028 return FALSE; // Just to please the compiler.
2032 if ((tt = nextToken(token)) != LCBRACE)
2034 openFiles.last()->returnToken(tt, token);
2040 if ((tt = nextToken(token)) == RCBRACE)
2044 fatalError("Attribute ID or '}' expected");
2047 if (token == KW("columns"))
2049 report->clearColumns();
2053 if ((tt = nextToken(col)) != ID)
2055 fatalError("Column ID expected");
2058 report->addColumn(col);
2059 if ((tt = nextToken(token)) != COMMA)
2061 openFiles.last()->returnToken(tt, token);
2066 else if (token == KW("start"))
2068 if (nextToken(token) != DATE)
2070 fatalError("Date expected");
2073 report->setStart(date2time(token));
2075 else if (token == KW("end"))
2077 if (nextToken(token) != DATE)
2079 fatalError("Date expected");
2082 report->setEnd(date2time(token));
2084 else if (token == KW("headline"))
2086 if (nextToken(token) != STRING)
2088 fatalError("String exptected");
2091 report->setHeadline(token);
2093 else if (token == KW("caption"))
2095 if (nextToken(token) != STRING)
2097 fatalError("String exptected");
2100 report->setCaption(token);
2102 else if (token == KW("showactual"))
2104 report->setShowActual(TRUE);
2106 else if (token == KW("showprojectids"))
2108 report->setShowPIDs(TRUE);
2110 else if (token == KW("hidetask"))
2113 if ((op = readLogicalExpression()) == 0)
2115 ExpressionTree* et = new ExpressionTree(op);
2116 report->setHideTask(et);
2118 else if (token == KW("rolluptask"))
2121 if ((op = readLogicalExpression()) == 0)
2123 ExpressionTree* et = new ExpressionTree(op);
2124 report->setRollUpTask(et);
2126 else if (token == KW("sorttasks"))
2128 if (!readSorting(report, 0))
2131 else if (token == KW("hideresource"))
2134 if ((op = readLogicalExpression()) == 0)
2136 ExpressionTree* et = new ExpressionTree(op);
2137 report->setHideResource(et);
2139 else if (token == KW("rollupresource"))
2142 if ((op = readLogicalExpression()) == 0)
2144 ExpressionTree* et = new ExpressionTree(op);
2145 report->setRollUpResource(et);
2147 else if (token == KW("sortresources"))
2149 if (!readSorting(report, 1))
2154 fatalError("Illegal attribute");
2159 if (reportType == KW("htmltaskreport"))
2160 proj->addHTMLTaskReport((HTMLTaskReport*) report);
2162 proj->addHTMLResourceReport((HTMLResourceReport*) report);
2168 ProjectFile::readHTMLAccountReport()
2171 if (nextToken(token) != STRING)
2173 fatalError("File name expected");
2177 HTMLAccountReport* report;
2178 report = new HTMLAccountReport(proj, token, proj->getStart(),
2182 if ((tt = nextToken(token)) != LCBRACE)
2184 openFiles.last()->returnToken(tt, token);
2190 if ((tt = nextToken(token)) == RCBRACE)
2194 fatalError("Attribute ID or '}' expected");
2197 if (token == KW("columns"))
2199 report->clearColumns();
2203 if ((tt = nextToken(col)) != ID)
2205 fatalError("Column ID expected");
2208 report->addColumn(col);
2209 if ((tt = nextToken(token)) != COMMA)
2211 openFiles.last()->returnToken(tt, token);
2216 else if (token == KW("start"))
2218 if (nextToken(token) != DATE)
2220 fatalError("Date expected");
2223 report->setStart(date2time(token));
2225 else if (token == KW("end"))
2227 if (nextToken(token) != DATE)
2229 fatalError("Date expected");
2232 report->setEnd(date2time(token));
2234 else if (token == KW("headline"))
2236 if (nextToken(token) != STRING)
2238 fatalError("String exptected");
2241 report->setHeadline(token);
2243 else if (token == KW("caption"))
2245 if (nextToken(token) != STRING)
2247 fatalError("String exptected");
2250 report->setCaption(token);
2252 else if (token == KW("hideplan"))
2254 report->setHidePlan(TRUE);
2256 else if (token == KW("showactual"))
2258 report->setShowActual(TRUE);
2260 else if (token == KW("accumulate"))
2262 report->setAccumulate(TRUE);
2264 else if (token == KW("hideaccount"))
2267 if ((op = readLogicalExpression()) == 0)
2269 ExpressionTree* et = new ExpressionTree(op);
2270 report->setHideAccount(et);
2272 else if (token == KW("rollupaccount"))
2275 if ((op = readLogicalExpression()) == 0)
2277 ExpressionTree* et = new ExpressionTree(op);
2278 report->setRollUpAccount(et);
2280 else if (token == KW("sortaccounts"))
2282 if (!readSorting(report, 2))
2287 fatalError("Illegal attribute");
2292 proj->addHTMLAccountReport(report);
2298 ProjectFile::readExportReport()
2301 if (nextToken(token) != STRING)
2303 fatalError("File name expected");
2307 ExportReport* report;
2308 report = new ExportReport(proj, token);
2311 if ((tt = nextToken(token)) != LCBRACE)
2313 openFiles.last()->returnToken(tt, token);
2319 if ((tt = nextToken(token)) == RCBRACE)
2323 fatalError("Attribute ID or '}' expected");
2327 if (token == KW("hidetask"))
2330 if ((op = readLogicalExpression()) == 0)
2332 ExpressionTree* et = new ExpressionTree(op);
2333 report->setHideTask(et);
2335 else if (token == KW("rolluptask"))
2338 if ((op = readLogicalExpression()) == 0)
2340 ExpressionTree* et = new ExpressionTree(op);
2341 report->setRollUpTask(et);
2345 fatalError("Illegal attribute");
2350 proj->addExportReport(report);
2356 ProjectFile::readLogicalExpression(int precedence)
2362 if ((tt = nextToken(token)) == ID || tt == RELATIVE_ID)
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))
2374 if ((op = readFunctionCall(token)) == 0)
2379 fatalError(QString("Flag or function '") + token + "' is unknown.");
2383 else if (tt == INTEGER)
2385 op = new Operation(token.toLong());
2387 else if (tt == TILDE)
2389 if ((op = readLogicalExpression(1)) == 0)
2393 op = new Operation(op, Operation::Not);
2395 else if (tt == LBRACE)
2397 if ((op = readLogicalExpression()) == 0)
2401 if ((tt = nextToken(token)) != RBRACE)
2403 fatalError("')' expected");
2409 fatalError("Logical expression expected");
2415 if ((tt = nextToken(token)) != AND && tt != OR)
2417 returnToken(tt, token);
2421 Operation* op2 = readLogicalExpression();
2422 op = new Operation(op, Operation::And, op2);
2426 Operation* op2 = readLogicalExpression();
2427 op = new Operation(op, Operation::Or, op2);
2435 ProjectFile::readFunctionCall(const QString& name)
2440 if ((tt = nextToken(token)) != LBRACE)
2442 fatalError("'(' expected");
2445 QPtrList<Operation> args;
2446 for (int i = 0; i < ExpressionTree::arguments(name); i++)
2449 if ((op = readLogicalExpression()) == 0)
2452 if ((i < ExpressionTree::arguments(name) - 1) &&
2453 nextToken(token) != COMMA)
2455 fatalError("Comma expected");
2459 if ((tt = nextToken(token)) != RBRACE)
2461 fatalError("')' expected");
2464 return new Operation(name, args);
2468 ProjectFile::readSorting(Report* report, int which)
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;
2526 fatalError("Sorting criteria expected");
2533 report->setTaskSorting(sorting);
2536 report->setResourceSorting(sorting);
2539 report->setAccountSorting(sorting);
2542 qFatal("readSorting: Unknown sorting attribute");
2550 ProjectFile::date2time(const QString& date)
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)
2556 else if (sscanf(date, "%d-%d-%d-%d:%d", &y, &m, &d, &hour, &min) == 5)
2558 else if (sscanf(date, "%d-%d-%d", &y, &m, &d) == 3)
2564 char savedTZ[16] = "";
2565 if (strcmp(tZone, "") != 0)
2568 strcpy(getenv("TZ"), savedTZ);
2569 setenv("TZ", tZone, 1);
2574 fatalError("Year must be larger than 1969");
2577 if (m < 1 || m > 12)
2579 fatalError("Month must be between 1 and 12");
2582 if (d < 1 || d > 31)
2584 fatalError("Day must be between 1 and 31");
2588 struct tm t = { 0, min, hour, d, m - 1, y - 1900, 0, 0, -1, 0, 0 };
2589 time_t localTime = mktime(&t);
2591 if (strcmp(savedTZ, "") != 0)
2592 setenv("TZ", savedTZ, 1);
2600 ProjectFile::hhmm2time(const QString& hhmm)
2603 sscanf(hhmm, "%d:%d", &hour, &min);
2604 return hour * 60 * 60 + min * 60;