1 /*
2 # PostgreSQL Database Modeler (pgModeler)
3 #
4 # Copyright 2006-2020 - Raphael Araújo e Silva <raphael@pgmodeler.io>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation version 3.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # The complete text of GPLv3 is at LICENSE file on source code root directory.
16 # Also, you can get the complete GNU General Public License at <http://www.gnu.org/licenses/>
17 */
18
19 #include "schemaparser.h"
20 #include "attributes.h"
21
22 const char SchemaParser::CharComment='#';
23 const char SchemaParser::CharLineEnd='\n';
24 const char SchemaParser::CharTabulation='\t';
25 const char SchemaParser::CharSpace=' ';
26 const char SchemaParser::CharIniAttribute='{';
27 const char SchemaParser::CharEndAttribute='}';
28 const char SchemaParser::CharIniConditional='%';
29 const char SchemaParser::CharIniMetachar='$';
30 const char SchemaParser::CharIniPlainText='[';
31 const char SchemaParser::CharEndPlainText=']';
32 const char SchemaParser::CharIniCompExpr='(';
33 const char SchemaParser::CharEndCompExpr=')';
34 const char SchemaParser::CharValueDelim='"';
35 const char SchemaParser::CharValueOf='@';
36
37 const QString SchemaParser::TokenIf=QString("if");
38 const QString SchemaParser::TokenThen=QString("then");
39 const QString SchemaParser::TokenElse=QString("else");
40 const QString SchemaParser::TokenEnd=QString("end");
41 const QString SchemaParser::TokenOr=QString("or");
42 const QString SchemaParser::TokenAnd=QString("and");
43 const QString SchemaParser::TokenNot=QString("not");
44 const QString SchemaParser::TokenSet=QString("set");
45 const QString SchemaParser::TokenUnset=QString("unset");
46
47 const QString SchemaParser::TokenMetaSp=QString("sp");
48 const QString SchemaParser::TokenMetaBr=QString("br");
49 const QString SchemaParser::TokenMetaTb=QString("tb");
50 const QString SchemaParser::TokenMetaOb=QString("ob");
51 const QString SchemaParser::TokenMetaCb=QString("cb");
52 const QString SchemaParser::TokenMetaOc=QString("oc");
53 const QString SchemaParser::TokenMetaCc=QString("cc");
54
55 const QString SchemaParser::TokenEqOper=QString("==");
56 const QString SchemaParser::TokenNeOper=QString("!=");
57 const QString SchemaParser::TokenGtOper=QString(">");
58 const QString SchemaParser::TokenLtOper=QString("<");
59 const QString SchemaParser::TokenGtEqOper=QString(">=");
60 const QString SchemaParser::TokenLtEqOper=QString("<=");
61
62 const QRegExp SchemaParser::AttribRegExp=QRegExp("^([a-z])([a-z]*|(\\d)*|(\\-)*|(_)*)+", Qt::CaseInsensitive);
63
SchemaParser()64 SchemaParser::SchemaParser()
65 {
66 line=column=comment_count=0;
67 ignore_unk_atribs=ignore_empty_atribs=false;
68 pgsql_version=PgSqlVersions::DefaulVersion;
69 }
70
setPgSQLVersion(const QString & pgsql_ver)71 void SchemaParser::setPgSQLVersion(const QString &pgsql_ver)
72 {
73 unsigned curr_ver = QString(pgsql_ver).remove('.').toUInt(),
74 version90 = QString(PgSqlVersions::PgSqlVersion90).remove('.').toUInt(),
75 default_ver = QString(PgSqlVersions::DefaulVersion).remove('.').toUInt();
76
77 if(curr_ver != 0 && (curr_ver < version90))
78 throw Exception(Exception::getErrorMessage(ErrorCode::InvPostgreSQLVersion)
79 .arg(pgsql_ver)
80 .arg(PgSqlVersions::PgSqlVersion90)
81 .arg(PgSqlVersions::DefaulVersion),
82 ErrorCode::InvPostgreSQLVersion,__PRETTY_FUNCTION__,__FILE__,__LINE__);
83
84 if(curr_ver > 0 && curr_ver <= default_ver)
85 pgsql_version=pgsql_ver;
86 else
87 pgsql_version=PgSqlVersions::DefaulVersion;
88 }
89
getPgSQLVersion()90 QString SchemaParser::getPgSQLVersion()
91 {
92 return pgsql_version;
93 }
94
extractAttributes()95 QStringList SchemaParser::extractAttributes()
96 {
97 QStringList attribs;
98 int start=0, end=0;
99
100 for(QString line : buffer)
101 {
102 //Find the first occurrence of '{' in the line
103 start=line.indexOf(CharIniAttribute, start);
104
105 while(start >= 0 && start < line.size())
106 {
107 end=line.indexOf(CharEndAttribute, start);
108 if(end >= 0)
109 {
110 //Extract the name between {} and push it into the list
111 attribs.push_back(line.mid(start + 1, end - start -1));
112 //Start searching new attribute start now from the last position
113 start=line.indexOf(CharIniAttribute, end);
114 }
115 else
116 break;
117 }
118
119 start=end=0;
120 }
121
122 attribs.removeDuplicates();
123 return attribs;
124 }
125
restartParser()126 void SchemaParser::restartParser()
127 {
128 /* Clears the buffer and resets the counters for line,
129 column and amount of comments */
130 buffer.clear();
131 attributes.clear();
132 line=column=comment_count=0;
133 }
134
loadBuffer(const QString & buf)135 void SchemaParser::loadBuffer(const QString &buf)
136 {
137 QString buf_aux=buf, lin,
138 escaped_comm_chr=QString("\\%1").arg(CharComment),
139 placeholder = QString(QChar::ReplacementCharacter);
140 QTextStream ts(&buf_aux);
141 int pos=0;
142 bool comm_holder_used = false;
143
144 //Prepares the parser to do new reading
145 restartParser();
146
147 filename="[memory buffer]";
148
149 //While the input file doesn't reach the end
150 while(!ts.atEnd())
151 {
152 //Get one line from stream (until the last char before \n)
153 lin = ts.readLine();
154
155 /* Special treatment for escaped comment characters (e.g.: \#):
156 * In order to avoid removing wrongly the # from the the line where it appear in the form \#
157 * we need to replace it temporarily by a placeholder <?> and remove other portions of the line
158 * the is considered a real comment and then replace back that placeholder by the comment char again.
159 * This is useful if the user intend to represent the hash (#) char in the schema code and not use it as comment. */
160 if(lin.indexOf(escaped_comm_chr) >= 0)
161 {
162 lin.replace(escaped_comm_chr, placeholder);
163 comm_holder_used = true;
164 }
165
166 /* Since the method getline discards the \n when the line was just a line break
167 its needed to treat it in order to not lost it */
168 if(lin.isEmpty()) lin+=CharLineEnd;
169
170 //If the entire line is commented out increases the comment lines counter
171 if(lin[0]==CharComment) comment_count++;
172
173 //Looking for the position of other comment characters for deletion
174 pos=lin.indexOf(CharComment);
175
176 //Removes the characters from the found position
177 if(pos >= 0)
178 lin.remove(pos, lin.size());
179
180 //Replacing the comment placeholder by the comment char causing that character to be printed to the code
181 if(comm_holder_used)
182 {
183 lin.replace(placeholder, QString(CharComment));
184 comm_holder_used = false;
185 }
186
187 if(!lin.isEmpty())
188 {
189 //Add a line break in case the last character is not
190 if(lin[lin.size()-1]!=CharLineEnd)
191 lin+=CharLineEnd;
192
193 //Add the treated line in the buffer
194 buffer.push_back(lin);
195 }
196 }
197 }
198
loadFile(const QString & filename)199 void SchemaParser::loadFile(const QString &filename)
200 {
201 if(!filename.isEmpty())
202 {
203 QFile input;
204 QString buf;
205
206 //Open the file for reading
207 input.setFileName(filename);
208 input.open(QFile::ReadOnly);
209
210 if(!input.isOpen())
211 throw Exception(Exception::getErrorMessage(ErrorCode::FileDirectoryNotAccessed).arg(filename),
212 ErrorCode::FileDirectoryNotAccessed,__PRETTY_FUNCTION__,__FILE__,__LINE__);
213
214 buf=input.readAll();
215 input.close();
216
217 //Loads the parser buffer
218 loadBuffer(buf);
219 SchemaParser::filename=filename;
220 }
221 }
222
getAttribute()223 QString SchemaParser::getAttribute()
224 {
225 QString atrib, current_line;
226 bool start_attrib, end_attrib, error=false;
227
228 //Get the current line from the buffer
229 current_line=buffer[line];
230
231 /* Only start extracting an attribute if it starts with a {
232 even if the current character is an attribute delimiter */
233 if(current_line[column]!=CharIniAttribute)
234 error=true;
235 else
236 {
237 //Step to the next column in the line
238 column++;
239
240 //Marks the flag indicating start of attribute
241 start_attrib=true;
242 //Unmarks the flag indicating end of attribute
243 end_attrib=false;
244
245 /* Attempt to extract an attribute until a space, end of line
246 or attribute is encountered */
247 while(current_line[column]!=CharLineEnd &&
248 current_line[column]!=CharSpace &&
249 current_line[column]!=CharTabulation &&
250 !end_attrib && !error)
251 {
252 if(current_line[column]!=CharEndAttribute)
253 atrib+=current_line[column];
254 else if(current_line[column]==CharEndAttribute && !atrib.isEmpty())
255 end_attrib=true;
256 else
257 error=true;
258 column++;
259 }
260
261 /* If the attribute has been started but not finished
262 ie absence of the } in its statement (ie. {attr),
263 generates an error. */
264 if(start_attrib && !end_attrib) error=true;
265 }
266
267 if(error)
268 {
269 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
270 .arg(filename).arg((line + comment_count + 1)).arg((column+1)),
271 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
272 }
273 else if(!AttribRegExp.exactMatch(atrib))
274 {
275 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidAttribute)
276 .arg(atrib).arg(filename).arg((line + comment_count + 1)).arg((column+1)),
277 ErrorCode::InvalidAttribute,__PRETTY_FUNCTION__,__FILE__,__LINE__);
278 }
279
280 return atrib;
281 }
282
getWord()283 QString SchemaParser::getWord()
284 {
285 QString word, current_line;
286
287 //Gets the current line buffer
288 current_line=buffer[line];
289
290 /* Attempt to extract a word if the first character is not
291 a special character. */
292 if(!isSpecialCharacter(current_line[column].toLatin1()))
293 {
294 /* Extract the word while it is not end of line, space or
295 special character */
296 while(current_line[column]!=CharLineEnd &&
297 !isSpecialCharacter(current_line[column].toLatin1()) &&
298 current_line[column]!=CharSpace &&
299 current_line[column]!=CharTabulation)
300 {
301 word+=current_line[column];
302 column++;
303 }
304 }
305
306 return word;
307 }
308
getPureText()309 QString SchemaParser::getPureText()
310 {
311 QString text, current_line;
312 bool error=false;
313
314 current_line=buffer[line];
315
316 //Attempt to extract a pure text if the first character is a [
317 if(current_line[column]==CharIniPlainText)
318 {
319 //Moves to the next character that contains the beginning of the text
320 column++;
321
322 /* Extracts the text while the end of pure text (]), end of buffer or
323 beginning of other pure text ([) is reached */
324 while(current_line[column]!=CharEndPlainText &&
325 line < buffer.size() &&
326 current_line[column]!=CharIniPlainText)
327 {
328 text+=current_line[column];
329
330 /* Special case to end of line. Unlike other elements of
331 language, a pure text can be extracted until the end of the buffer,
332 thus, this method also controls the lines transitions */
333 if(current_line[column]==CharLineEnd)
334 {
335 //Step to the next line
336 line++;
337 column=0;
338
339 if(line < buffer.size())
340 current_line=buffer[line];
341 }
342 else column++;
343 }
344
345 if(current_line[column]==CharEndPlainText)
346 column++;
347 else
348 error=true;
349 }
350 else error=true;
351
352 if(error)
353 {
354 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
355 .arg(filename).arg((line + comment_count + 1)).arg((column+1)),
356 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
357 }
358
359 return text;
360 }
361
getConditional()362 QString SchemaParser::getConditional()
363 {
364 QString conditional, current_line;
365 bool error=false;
366
367 current_line=buffer[line];
368
369 //Will initiate extraction if a % is found
370 if(current_line[column]==CharIniConditional)
371 {
372 /* Passa para o próximo caractere que é o início do
373 do nome da palavra condicional */
374 column++;
375
376 /* Moves to the next character that is the beginning of
377 the name of the conditional word */
378 while(current_line[column]!=CharLineEnd &&
379 current_line[column]!=CharSpace &&
380 current_line[column]!=CharTabulation)
381 {
382 conditional+=current_line[column];
383 column++;
384 }
385
386 //If no word was extracted an error is raised
387 if(conditional.isEmpty()) error=true;
388 }
389 else error=true;
390
391 if(error)
392 {
393 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
394 .arg(filename).arg(line + comment_count + 1).arg(column+1),
395 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
396 }
397
398 return conditional;
399 }
400
getMetaCharacter()401 QString SchemaParser::getMetaCharacter()
402 {
403 QString meta, current_line;
404 bool error=false;
405
406 current_line=buffer[line];
407
408 //Begins the extraction in case of a $ is found
409 if(current_line[column]==CharIniMetachar)
410 {
411 //Moves to the next character that is the beginning of the metacharacter
412 column++;
413
414 //Extracts the metacharacter until doesn't finds a space or end of line
415 while(current_line[column]!=CharLineEnd &&
416 current_line[column]!=CharSpace &&
417 current_line[column]!=CharTabulation)
418 {
419 meta+=current_line[column];
420 column++;
421 }
422
423 //If no metacharacter was extracted an error is raised
424 if(meta.isEmpty()) error=true;
425 }
426 else error=true;
427
428 if(error)
429 {
430 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
431 .arg(filename).arg(line + comment_count + 1).arg(column+1),
432 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
433 }
434
435 return meta;
436 }
437
isSpecialCharacter(char chr)438 bool SchemaParser::isSpecialCharacter(char chr)
439 {
440 return chr==CharIniAttribute || chr==CharEndAttribute ||
441 chr==CharIniConditional || chr==CharIniMetachar ||
442 chr==CharIniPlainText || chr==CharEndPlainText;
443 }
444
evaluateComparisonExpr()445 bool SchemaParser::evaluateComparisonExpr()
446 {
447 QString curr_line, attrib, value, oper, valid_op_chrs="=!<>fi";
448 bool error=false, end_eval=false, expr_is_true=true;
449 static QStringList opers = { TokenEqOper, TokenNeOper, TokenGtOper,
450 TokenLtOper, TokenGtEqOper, TokenLtEqOper };
451
452 try
453 {
454 curr_line=buffer[line];
455 column++;
456
457 while(!end_eval && !error)
458 {
459 ignoreBlankChars(curr_line);
460
461 /* If the scan reached the end of the line and the expression was not closed raises an syntax error
462 Comparison expr must start and end in the same line */
463 if(curr_line[column]==CharLineEnd && !end_eval)
464 error=true;
465
466 switch(curr_line[column].toLatin1())
467 {
468 case CharIniAttribute:
469 /* Extract the attribute (the first element in the expression) only
470 if the comparison operator and values aren't extracted */
471 if(attrib.isEmpty() && oper.isEmpty() && value.isEmpty())
472 attrib=getAttribute();
473 else
474 error=true;
475 break;
476
477 case CharValueDelim:
478 /* Extract the value (the last element in the expression) only
479 if the attribute and operator were extracted */
480 if(value.isEmpty() && !attrib.isEmpty() && !oper.isEmpty())
481 {
482 value+=curr_line[column++];
483
484 while(column < curr_line.size())
485 {
486 value+=curr_line[column++];
487
488 if(curr_line[column]==CharValueDelim)
489 {
490 value+=CharValueDelim;
491 column++;
492 break;
493 }
494 }
495 }
496 else
497 error=true;
498
499 break;
500
501 case CharEndCompExpr:
502 column++;
503
504 //If one of the elements are missing, raise an syntax error
505 if(attrib.isEmpty() || oper.isEmpty() || value.isEmpty())
506 error=true;
507 else if(!opers.contains(QString(oper).remove('f').remove('i')))
508 {
509 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidOperatorInExpression)
510 .arg(oper).arg(filename).arg((line + comment_count + 1)).arg((column+1)),
511 ErrorCode::InvalidOperatorInExpression,__PRETTY_FUNCTION__,__FILE__,__LINE__);
512 }
513 else if(attributes.count(attrib)==0 && !ignore_unk_atribs)
514 {
515 throw Exception(Exception::getErrorMessage(ErrorCode::UnkownAttribute)
516 .arg(attrib).arg(filename).arg((line + comment_count +1)).arg((column+1)),
517 ErrorCode::UnkownAttribute,__PRETTY_FUNCTION__,__FILE__,__LINE__);
518 }
519 else
520 {
521 QVariant left_val, right_val;
522 value.remove(CharValueDelim);
523
524 //Evaluating the attribute value against the one captured on the expression without casting
525 if(oper.endsWith('f'))
526 {
527 left_val = QVariant(attributes[attrib].toFloat());
528 right_val = QVariant(value.toFloat());
529 oper.remove('f');
530 expr_is_true = getExpressionResult<float>(oper, left_val, right_val);
531 }
532 else if(oper.endsWith('i'))
533 {
534 left_val = QVariant(attributes[attrib].toInt());
535 right_val = QVariant(value.toInt());
536 oper.remove('i');
537 expr_is_true = getExpressionResult<int>(oper, left_val, right_val);
538 }
539 else
540 {
541 left_val = QVariant(attributes[attrib]);
542 right_val = QVariant(value);
543 expr_is_true = getExpressionResult<QString>(oper, left_val, right_val);
544 }
545
546 end_eval=true;
547 }
548 break;
549
550 default:
551 /* Extract the operator (the second element in the expression) only
552 if the attribute was extracted and the value not */
553 if(oper.size() <= 3 && !attrib.isEmpty() && value.isEmpty())
554 {
555 //If the current char is a valid operator capture it otherwise raise an error
556 if(valid_op_chrs.indexOf(curr_line[column]) >= 0)
557 oper+=curr_line[column++];
558 else
559 error=true;
560 }
561 else
562 error=true;
563
564 break;
565 }
566 }
567 }
568 catch(Exception &e)
569 {
570 throw Exception(e.getErrorMessage(),e.getErrorCode(),__PRETTY_FUNCTION__,__FILE__,__LINE__,&e);
571 }
572
573 if(error)
574 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
575 .arg(filename).arg((line + comment_count + 1)).arg((column+1)),
576 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
577
578 return expr_is_true;
579 }
580
defineAttribute()581 void SchemaParser::defineAttribute()
582 {
583 QString curr_line, attrib, value, new_attrib;
584 bool error=false, end_def=false, use_val_as_name=false;
585
586 try
587 {
588 curr_line=buffer[line];
589
590 while(!end_def && !error)
591 {
592 ignoreBlankChars(curr_line);
593
594 switch(curr_line[column].toLatin1())
595 {
596 case CharLineEnd:
597 end_def=true;
598 break;
599
600 case CharValueOf:
601 if(!use_val_as_name)
602 {
603 use_val_as_name=true;
604 column++;
605 new_attrib=getAttribute();
606 }
607 else
608 error=true;
609 break;
610
611 case CharIniConditional:
612 error=true;
613 break;
614
615 case CharIniAttribute:
616 if(new_attrib.isEmpty())
617 new_attrib=getAttribute();
618 else
619 {
620 //Get the attribute in the middle of the value
621 attrib=getAttribute();
622
623 if(attributes.count(attrib)==0 && !ignore_unk_atribs)
624 {
625 throw Exception(Exception::getErrorMessage(ErrorCode::UnkownAttribute)
626 .arg(attrib).arg(filename).arg((line + comment_count +1)).arg((column+1)),
627 ErrorCode::UnkownAttribute,__PRETTY_FUNCTION__,__FILE__,__LINE__);
628 }
629
630 value+=attributes[attrib];
631 }
632 break;
633
634 case CharIniPlainText:
635 value+=getPureText();
636 break;
637
638 case CharIniMetachar:
639 value+=translateMetaCharacter(getMetaCharacter());
640 break;
641
642 default:
643 value+=getWord();
644 break;
645 }
646
647 //If the attribute name was not extracted yet returns a error
648 if(new_attrib.isEmpty())
649 error=true;
650 }
651 }
652 catch(Exception &e)
653 {
654 throw Exception(e.getErrorMessage(),e.getErrorCode(),__PRETTY_FUNCTION__,__FILE__,__LINE__,&e);
655 }
656
657 if(!error)
658 {
659 attrib=(use_val_as_name ? attributes[new_attrib] : new_attrib);
660
661 //Checking if the attribute has a valid name
662 if(!AttribRegExp.exactMatch(attrib))
663 {
664 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidAttribute)
665 .arg(attrib).arg(filename).arg((line + comment_count + 1)).arg((column+1)),
666 ErrorCode::InvalidAttribute,__PRETTY_FUNCTION__,__FILE__,__LINE__);
667 }
668
669 /* Creates the attribute in the attribute map of the schema, making the attribute
670 available on the rest of the script being parsed */
671
672 attributes[attrib]=value;
673 }
674 else
675 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
676 .arg(filename).arg((line + comment_count + 1)).arg((column+1)),
677 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
678 }
679
unsetAttribute()680 void SchemaParser::unsetAttribute()
681 {
682 QString curr_line, attrib;
683 bool end_def=false;
684
685 try
686 {
687 curr_line=buffer[line];
688
689 while(!end_def)
690 {
691 ignoreBlankChars(curr_line);
692
693 switch(curr_line[column].toLatin1())
694 {
695 case CharLineEnd:
696 end_def=true;
697 break;
698
699 case CharIniAttribute:
700 attrib=getAttribute();
701
702 if(attributes.count(attrib)==0 && !ignore_unk_atribs)
703 {
704 throw Exception(Exception::getErrorMessage(ErrorCode::UnkownAttribute)
705 .arg(attrib).arg(filename).arg((line + comment_count +1)).arg((column+1)),
706 ErrorCode::UnkownAttribute,__PRETTY_FUNCTION__,__FILE__,__LINE__);
707 }
708 else if(!AttribRegExp.exactMatch(attrib))
709 {
710 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidAttribute)
711 .arg(attrib).arg(filename).arg((line + comment_count + 1)).arg((column+1)),
712 ErrorCode::InvalidAttribute,__PRETTY_FUNCTION__,__FILE__,__LINE__);
713 }
714
715 attributes[attrib]="";
716 break;
717
718 default:
719 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
720 .arg(filename).arg((line + comment_count + 1)).arg((column+1)),
721 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
722 }
723 }
724 }
725 catch(Exception &e)
726 {
727 throw Exception(e.getErrorMessage(),e.getErrorCode(),__PRETTY_FUNCTION__,__FILE__,__LINE__,&e);
728 }
729 }
evaluateExpression()730 bool SchemaParser::evaluateExpression()
731 {
732 QString current_line, cond, attrib, prev_cond;
733 bool error=false, end_eval=false, expr_is_true=true, attrib_true=true, comp_true=true;
734 unsigned attrib_count=0, and_or_count=0;
735
736 try
737 {
738 current_line=buffer[line];
739
740 while(!end_eval && !error)
741 {
742 ignoreBlankChars(current_line);
743
744 if(current_line[column]==CharLineEnd)
745 {
746 line++;
747 if(line < buffer.size())
748 {
749 current_line=buffer[line];
750 column=0;
751 ignoreBlankChars(current_line);
752 }
753 else if(!end_eval)
754 error=true;
755 }
756
757 switch(current_line[column].toLatin1())
758 {
759 //Extract the next conditional token
760 case CharIniConditional:
761 prev_cond=cond;
762 cond=getConditional();
763
764 //Error 1: %if {a} %or %or %then
765 error=(cond==prev_cond ||
766 //Error 2: %if {a} %and %or %then
767 (cond==TokenAnd && prev_cond==TokenOr) ||
768 //Error 3: %if {a} %or %and %then
769 (cond==TokenOr && prev_cond==TokenAnd) ||
770 //Error 4: %if %and {a} %then
771 (attrib_count==0 && (cond==TokenAnd || cond==TokenOr)));
772
773 if(cond==TokenThen)
774 {
775 /* Returns the parser to the token %then because additional
776 operations is done whe this token is found */
777 column-=cond.length()+1;
778 end_eval=true;
779
780 //Error 1: %if {a} %not %then
781 error=(prev_cond==TokenNot ||
782 //Error 2: %if %then
783 attrib_count==0 ||
784 //Error 3: %if {a} %and %then
785 (and_or_count!=attrib_count-1));
786 }
787 else if(cond==TokenOr || cond==TokenAnd)
788 and_or_count++;
789 break;
790
791 case CharIniAttribute:
792 attrib=getAttribute();
793
794 //Raises an error if the attribute does is unknown
795 if(attributes.count(attrib)==0 && !ignore_unk_atribs)
796 {
797 throw Exception(Exception::getErrorMessage(ErrorCode::UnkownAttribute)
798 .arg(attrib).arg(filename).arg((line + comment_count +1)).arg((column+1)),
799 ErrorCode::UnkownAttribute,__PRETTY_FUNCTION__,__FILE__,__LINE__);
800 }
801
802 //Error 1: A conditional token other than %or %not %and if found on conditional expression
803 error=(!cond.isEmpty() && cond!=TokenOr && cond!=TokenAnd && cond!=TokenNot) ||
804 //Error 2: A %not token if found after an attribute: %if {a} %not %then
805 (attrib_count > 0 && cond==TokenNot && prev_cond.isEmpty()) ||
806 //Error 3: Two attributes not separated by any conditional token: %if {a} {b} %then
807 (attrib_count > 0 && cond.isEmpty());
808
809 //Increments the extracted attribute counter
810 attrib_count++;
811
812 if(!error)
813 {
814 //Appliyng the NOT operator if found
815 attrib_true=(cond==TokenNot ? attributes[attrib].isEmpty() : !attributes[attrib].isEmpty());
816
817 //Executing the AND operation if the token is found
818 if(cond==TokenAnd || prev_cond==TokenAnd)
819 expr_is_true=(expr_is_true && attrib_true);
820 else if(cond==TokenOr || prev_cond==TokenOr)
821 expr_is_true=(expr_is_true || attrib_true);
822 else
823 expr_is_true=attrib_true;
824
825 cond.clear();
826 prev_cond.clear();
827 }
828 break;
829
830 case CharIniCompExpr:
831 comp_true=evaluateComparisonExpr();
832
833 //Appliyng the NOT operator if found
834 if(cond==TokenNot) comp_true=!comp_true;
835
836 //Executing the AND operation if the token is found
837 if(cond==TokenAnd || prev_cond==TokenAnd)
838 expr_is_true=(expr_is_true && comp_true);
839 else if(cond==TokenOr || prev_cond==TokenOr)
840 expr_is_true=(expr_is_true || comp_true);
841 else
842 expr_is_true=comp_true;
843
844 //Consider the comparison expression as an attribute evaluation
845 attrib_count++;
846 cond.clear();
847 prev_cond.clear();
848 break;
849
850 default:
851 error=true;
852 break;
853 }
854 }
855 }
856 catch(Exception &e)
857 {
858 throw Exception(e.getErrorMessage(),e.getErrorCode(), __PRETTY_FUNCTION__,__FILE__,__LINE__, &e);
859 }
860
861 if(error)
862 {
863 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
864 .arg(filename).arg((line + comment_count + 1)).arg((column+1)),
865 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
866 }
867
868 return expr_is_true;
869 }
870
ignoreBlankChars(const QString & line)871 void SchemaParser::ignoreBlankChars(const QString &line)
872 {
873 while(column < line.size() &&
874 (line[column]==CharSpace ||
875 line[column]==CharTabulation)) column++;
876 }
877
translateMetaCharacter(const QString & meta)878 char SchemaParser::translateMetaCharacter(const QString &meta)
879 {
880 static map<QString, char> metas={{ TokenMetaSp, CharSpace },
881 { TokenMetaTb, CharTabulation },
882 { TokenMetaBr, CharLineEnd },
883 { TokenMetaOb, CharIniPlainText },
884 { TokenMetaCb, CharEndPlainText },
885 { TokenMetaOc, CharIniAttribute },
886 { TokenMetaCc, CharEndAttribute }};
887
888 if(metas.count(meta)==0)
889 {
890 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidMetacharacter)
891 .arg(meta).arg(filename).arg(line + comment_count +1).arg(column+1),
892 ErrorCode::InvalidMetacharacter,__PRETTY_FUNCTION__,__FILE__,__LINE__);
893 }
894
895 return metas.at(meta);
896 }
897
getCodeDefinition(const QString & obj_name,attribs_map & attribs,unsigned def_type)898 QString SchemaParser::getCodeDefinition(const QString & obj_name, attribs_map &attribs, unsigned def_type)
899 {
900 try
901 {
902 QString filename;
903
904 if(def_type==SqlDefinition)
905 {
906 //Formats the filename
907 filename = GlobalAttributes::getSchemaFilePath(GlobalAttributes::SQLSchemaDir, obj_name);
908 attribs[Attributes::PgSqlVersion]=pgsql_version;
909
910 //Try to get the object definitin from the specified path
911 return getCodeDefinition(filename, attribs);
912 }
913 else
914 {
915 filename = GlobalAttributes::getSchemaFilePath(GlobalAttributes::XMLSchemaDir, obj_name);
916 return XmlParser::convertCharsToXMLEntities(getCodeDefinition(filename, attribs));
917 }
918 }
919 catch(Exception &e)
920 {
921 throw Exception(e.getErrorMessage(),e.getErrorCode(), __PRETTY_FUNCTION__,__FILE__,__LINE__,&e);
922 }
923 }
924
ignoreUnkownAttributes(bool ignore)925 void SchemaParser::ignoreUnkownAttributes(bool ignore)
926 {
927 ignore_unk_atribs=ignore;
928 }
929
ignoreEmptyAttributes(bool ignore)930 void SchemaParser::ignoreEmptyAttributes(bool ignore)
931 {
932 ignore_empty_atribs=ignore;
933 }
934
getCodeDefinition(attribs_map & attribs)935 QString SchemaParser::getCodeDefinition(attribs_map &attribs)
936 {
937 QString object_def;
938 unsigned end_cnt, if_cnt;
939 int if_level, prev_if_level;
940 QString atrib, cond, prev_cond, word, meta;
941 bool error, if_expr;
942 char chr;
943 vector<bool> vet_expif, vet_tk_if, vet_tk_then, vet_tk_else;
944 map<int, vector<QString> > if_map, else_map;
945 vector<QString>::iterator itr, itr_end;
946 vector<int> vet_prev_level;
947 vector<QString> *vet_aux;
948
949 //In case the file was successfuly loaded
950 if(buffer.size() > 0)
951 {
952 //Init the control variables
953 attributes=attribs;
954 error=if_expr=false;
955 if_level=-1;
956 end_cnt=if_cnt=0;
957
958 while(line < buffer.size())
959 {
960 chr=buffer[line][column].toLatin1();
961 switch(chr)
962 {
963 /* Increments the number of rows causing the parser
964 to get the next line buffer for analysis */
965 case CharLineEnd:
966 line++;
967 column=0;
968 break;
969
970 case CharTabulation:
971 case CharSpace:
972 //The parser will ignore the spaces that are not within pure texts
973 while(buffer[line][column]==CharSpace ||
974 buffer[line][column]==CharTabulation) column++;
975 break;
976
977 //Metacharacter extraction
978 case CharIniMetachar:
979 meta=getMetaCharacter();
980
981 //Checks whether the metacharacter is part of the 'if' expression (this is an error)
982 if(if_level>=0 && vet_tk_if[if_level] && !vet_tk_then[if_level])
983 {
984 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
985 .arg(filename).arg(line + comment_count +1).arg(column+1),
986 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
987 }
988 else
989 {
990 //Converting the metacharacter drawn to the character that represents this
991 chr=translateMetaCharacter(meta);
992 meta="";
993 meta+=chr;
994
995 //If the parser is inside an 'if / else' extracting tokens
996 if(if_level>=0)
997 {
998 /* If the parser is in 'if' section,
999 places the metacharacter on the word map of the current 'if' */
1000 if(vet_tk_if[if_level] &&
1001 vet_tk_then[if_level] &&
1002 !vet_tk_else[if_level])
1003 if_map[if_level].push_back(meta);
1004
1005 /* If the parser is in 'else' section,
1006 places the metacharacter on the word map of the current 'else'*/
1007 else if(vet_tk_else[if_level])
1008 else_map[if_level].push_back(meta);
1009 }
1010 else
1011 /* If the parsers is not in a 'if / else', puts the metacharacter
1012 in the definition sql */
1013 object_def+=meta;
1014 }
1015
1016 break;
1017
1018 //Attribute extraction
1019 case CharIniAttribute:
1020 case CharEndAttribute:
1021 atrib=getAttribute();
1022
1023 //Checks if the attribute extracted belongs to the passed list of attributes
1024 if(attributes.count(atrib)==0)
1025 {
1026 if(!ignore_unk_atribs)
1027 {
1028 throw Exception(Exception::getErrorMessage(ErrorCode::UnkownAttribute)
1029 .arg(atrib).arg(filename).arg((line + comment_count +1)).arg((column+1)),
1030 ErrorCode::UnkownAttribute,__PRETTY_FUNCTION__,__FILE__,__LINE__);
1031 }
1032 else
1033 attributes[atrib]="";
1034 }
1035
1036 //If the parser is inside an 'if / else' extracting tokens
1037 if(if_level>=0)
1038 {
1039 //If the parser evaluated the 'if' conditional and is inside the current if block
1040 if(!(!if_expr && vet_tk_if[if_level] && !vet_tk_then[if_level]))
1041 {
1042 word=atrib;
1043 atrib="";
1044 atrib+=CharIniAttribute;
1045 atrib+=word;
1046 atrib+=CharEndAttribute;
1047
1048 //If the parser is in the 'if' section
1049 if(vet_tk_if[if_level] &&
1050 vet_tk_then[if_level] &&
1051 !vet_tk_else[if_level])
1052 //Inserts the attribute value in the map of the words of current the 'if' section
1053 if_map[if_level].push_back(atrib);
1054 else if(vet_tk_else[if_level])
1055 //Inserts the attribute value in the map of the words of current the 'else' section
1056 else_map[if_level].push_back(atrib);
1057 }
1058 }
1059 else
1060 {
1061 /* If the attribute has no value set and parser must not ignore empty values
1062 raises an exception */
1063 if(attributes[atrib].isEmpty() && !ignore_empty_atribs)
1064 {
1065 throw Exception(Exception::getErrorMessage(ErrorCode::UndefinedAttributeValue)
1066 .arg(atrib).arg(filename).arg(line + comment_count +1).arg(column+1),
1067 ErrorCode::UndefinedAttributeValue,__PRETTY_FUNCTION__,__FILE__,__LINE__);
1068 }
1069
1070 /* If the parser is not in an if / else, concatenates the value of the attribute
1071 directly in definition in sql */
1072 object_def+=attributes[atrib];
1073 }
1074 break;
1075
1076 //Conditional instruction extraction
1077 case CharIniConditional:
1078 prev_cond=cond;
1079 cond=getConditional();
1080
1081 //Checks whether the extracted token is a valid conditional
1082 if(cond!=TokenIf && cond!=TokenElse &&
1083 cond!=TokenThen && cond!=TokenEnd &&
1084 cond!=TokenOr && cond!=TokenNot &&
1085 cond!=TokenAnd && cond!=TokenSet &&
1086 cond!=TokenUnset)
1087 {
1088 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidInstruction)
1089 .arg(cond).arg(filename).arg(line + comment_count +1).arg(column+1),
1090 ErrorCode::InvalidInstruction,__PRETTY_FUNCTION__,__FILE__,__LINE__);
1091 }
1092 else if(cond==TokenSet || cond==TokenUnset)
1093 {
1094 bool extract=false;
1095
1096 /* Extracts or unset the attribute only if the process is not in the middle of a 'if-then-else' or
1097 if the parser is inside the 'if' part and the expression is evaluated as true, or in the 'else' part
1098 and the related 'if' is false. Otherwise the line where %set is located will be completely ignored */
1099 extract=(if_level < 0 || vet_expif.empty());
1100
1101 if(!extract && if_level >= 0)
1102 {
1103 //If in 'else' the related 'if' is false, extracts the attribute
1104 if(prev_cond == TokenElse && !vet_expif[if_level])
1105 extract=true;
1106 else if(prev_cond != TokenElse)
1107 {
1108 //If in the 'if' part all the previous ifs until the current must be true
1109 extract=true;
1110 for(int i=0; i <= if_level && extract; i++)
1111 {
1112 extract=(vet_expif[i] && !vet_tk_else[i]) ||
1113 (!vet_expif[i] && vet_tk_else[i]);
1114 }
1115 }
1116 }
1117
1118 if(extract)
1119 {
1120 if(cond==TokenSet)
1121 defineAttribute();
1122 else
1123 unsetAttribute();
1124 }
1125 else
1126 {
1127 column=0;
1128 line++;
1129 }
1130 }
1131 else
1132 {
1133 //If the toke is an 'if'
1134 if(cond==TokenIf)
1135 {
1136 //Evaluates the if expression storing the result on the vector
1137 if_expr=true;
1138 vet_expif.push_back(evaluateExpression());
1139
1140 /* Inserts the value of the current 'if' level in the previous 'if' levels vector.
1141 This vector is used to know which 'if' the parser was in before entering current if */
1142 vet_prev_level.push_back(if_level);
1143
1144 /* Mark the flags indicating that an 'if' was found and a
1145 'then' and 'else' have not been found yet */
1146 vet_tk_if.push_back(true);
1147 vet_tk_then.push_back(false);
1148 vet_tk_else.push_back(false);
1149
1150 //Defines the current if level as the size of list 'if' tokens found -1
1151 if_level=(vet_tk_if.size()-1);
1152
1153 //Increases the number of found 'if's
1154 if_cnt++;
1155 }
1156 //If the parser is in 'if / else' and one 'then' token is found
1157 else if(cond==TokenThen && if_level>=0)
1158 {
1159 //Marks the then token flag of the current 'if'
1160 vet_tk_then[if_level]=true;
1161
1162 /* Clears the expression extracted flag from the 'if - then',
1163 so that the parser does not generate an error when it encounters another
1164 'if - then' with an expression still unextracted */
1165 if_expr=false;
1166 }
1167 //If the parser is in 'if / else' and a 'else' token is found
1168 else if(cond==TokenElse && if_level>=0)
1169 //Mark the o flag do token else do if atual
1170 vet_tk_else[if_level]=true;
1171 //Case the parser is in 'if/else' and a 'end' token was found
1172 else if(cond==TokenEnd && if_level>=0)
1173 {
1174 //Increments the number of 'end' tokes found
1175 end_cnt++;
1176
1177 //Get the level of the previous 'if' where the parser was
1178 prev_if_level=vet_prev_level[if_level];
1179
1180 //In case the current 'if' be internal (nested) (if_level > 0)
1181 if(if_level > 0)
1182 {
1183 //In case the current 'if' doesn't in the 'else' section of the above 'if' (previous if level)
1184 if(!vet_tk_else[prev_if_level])
1185 //Get the extracted word vector on the above 'if'
1186 vet_aux=&if_map[prev_if_level];
1187 else
1188 //Get the extracted word vector on the above 'else'
1189 vet_aux=&else_map[prev_if_level];
1190 }
1191 else
1192 vet_aux=nullptr;
1193
1194 /* Initializes the iterators to scan
1195 the auxiliary vector if necessary */
1196 itr=itr_end=if_map[0].end();
1197
1198 /* In case the expression of the current 'if' has the value true
1199 then the parser will scan the list of words on the 'if' part of the
1200 current 'if' */
1201 if(vet_expif[if_level])
1202 {
1203 itr=if_map[if_level].begin();
1204 itr_end=if_map[if_level].end();
1205 }
1206
1207 /* If there is a 'else' part on the current 'if'
1208 then the parser will scan the list of words on the 'else' part */
1209 else if(else_map.count(if_level)>0)
1210 {
1211 itr=else_map[if_level].begin();
1212 itr_end=else_map[if_level].end();
1213 }
1214
1215 /* This iteration scans the list of words selected above
1216 inserting them in 'if' part of the 'if' or 'else' superior to current.
1217 This is done so that only the words extracted based on
1218 ifs expressions of the buffer are embedded in defining sql */
1219 while(itr!=itr_end)
1220 {
1221 //If the auxiliary vector is allocated, inserts the word on above 'if / else'
1222 if(vet_aux)
1223 vet_aux->push_back((*itr));
1224 else
1225 {
1226 word=(*itr);
1227
1228 //Check if the word is not an attribute
1229 if(!word.isEmpty() && word.startsWith(CharIniAttribute) && word.endsWith(CharEndAttribute))
1230 {
1231 //If its an attribute, extracts the name between { } and checks if the same has empty value
1232 atrib=word.mid(1, word.size()-2);
1233 word=attributes[atrib];
1234
1235 /* If the attribute has no value set and parser must not ignore empty values
1236 raises an exception */
1237 if(word.isEmpty() && !ignore_empty_atribs)
1238 {
1239 throw Exception(Exception::getErrorMessage(ErrorCode::UndefinedAttributeValue)
1240 .arg(atrib).arg(filename).arg(line + comment_count +1).arg(column+1),
1241 ErrorCode::UndefinedAttributeValue,__PRETTY_FUNCTION__,__FILE__,__LINE__);
1242 }
1243 }
1244
1245 //Else, insert the work directly on the object definition
1246 object_def+=word;
1247 }
1248 itr++;
1249 }
1250
1251 //Case the current if is nested (internal)
1252 if(if_level > 0)
1253 //Causes the parser to return to the earlier 'if'
1254 if_level=prev_if_level;
1255
1256 /* In case the 'if' be the uppermost (level 0) indicates that all
1257 the if's has already been checked, so the parser will clear the
1258 used auxiliary structures*/
1259 else
1260 {
1261 if_map.clear();
1262 else_map.clear();
1263 vet_tk_if.clear();
1264 vet_tk_then.clear();
1265 vet_tk_else.clear();
1266 vet_expif.clear();
1267 vet_prev_level.clear();
1268
1269 //Resets the ifs levels
1270 if_level=prev_if_level=-1;
1271 }
1272 }
1273 else
1274 error=true;
1275
1276 if(!error)
1277 {
1278 /* Verifying that the conditional words appear in a valid order if not
1279 the parser generates an error. Correct order means IF before THEN,
1280 ELSE after IF and before END */
1281 if((prev_cond==TokenIf && cond!=TokenThen) ||
1282 (prev_cond==TokenElse && cond!=TokenIf && cond!=TokenEnd) ||
1283 (prev_cond==TokenThen && cond==TokenThen))
1284 error=true;
1285 }
1286
1287 if(error)
1288 {
1289 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
1290 .arg(filename).arg(line + comment_count +1).arg(column+1),
1291 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
1292 }
1293 }
1294 break;
1295
1296 //Extraction of pure text or simple words
1297 default:
1298 if(chr==CharIniPlainText ||
1299 chr==CharEndPlainText)
1300 word=getPureText();
1301 else
1302 word=getWord();
1303
1304 //Case the parser is in 'if/else'
1305 if(if_level>=0)
1306 {
1307 /* In case the word/text be inside 'if' expression, the parser returns an error
1308 because only an attribute must be on the 'if' expression */
1309 if(vet_tk_if[if_level] && !vet_tk_then[if_level])
1310 {
1311 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
1312 .arg(filename).arg(line + comment_count +1).arg(column+1),
1313 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
1314 }
1315 //Case the parser is in 'if' section
1316 else if(vet_tk_if[if_level] &&
1317 vet_tk_then[if_level] &&
1318 !vet_tk_else[if_level])
1319 //Inserts the word on the words map extracted on 'if' section
1320 if_map[if_level].push_back(word);
1321 else if(vet_tk_else[if_level])
1322 //Inserts the word on the words map extracted on 'else' section
1323 else_map[if_level].push_back(word);
1324 }
1325 else
1326 //Case the parser is not in 'if/else' concatenates the word/text directly on the object definition
1327 object_def+=word;
1328 break;
1329 }
1330 }
1331
1332 /* If has more 'if' toknes than 'end' tokens, this indicates that some 'if' in code
1333 was not closed thus the parser returns an error */
1334 if(if_cnt!=end_cnt)
1335 {
1336 throw Exception(Exception::getErrorMessage(ErrorCode::InvalidSyntax)
1337 .arg(filename).arg(line + comment_count +1).arg(column+1),
1338 ErrorCode::InvalidSyntax,__PRETTY_FUNCTION__,__FILE__,__LINE__);
1339 }
1340 }
1341
1342 restartParser();
1343 ignore_unk_atribs=false;
1344 ignore_empty_atribs=false;
1345 return object_def;
1346 }
1347
getCodeDefinition(const QString & filename,attribs_map & attribs)1348 QString SchemaParser::getCodeDefinition(const QString &filename, attribs_map &attribs)
1349 {
1350 try
1351 {
1352 loadFile(filename);
1353 attribs[Attributes::PgSqlVersion]=pgsql_version;
1354 return getCodeDefinition(attribs);
1355 }
1356 catch(Exception &e)
1357 {
1358 throw Exception(e.getErrorMessage(),e.getErrorCode(),__PRETTY_FUNCTION__,__FILE__,__LINE__, &e);
1359 }
1360 }
1361
1362 template<typename Type>
getExpressionResult(const QString & oper,const QVariant & left_val,const QVariant & right_val)1363 bool SchemaParser::getExpressionResult(const QString &oper, const QVariant &left_val, const QVariant &right_val){
1364 return ((oper==TokenEqOper && (left_val.value<Type>() == right_val.value<Type>())) ||
1365 (oper==TokenNeOper && (left_val.value<Type>() != right_val.value<Type>())) ||
1366 (oper==TokenGtOper && (left_val.value<Type>() > right_val.value<Type>())) ||
1367 (oper==TokenLtOper && (left_val.value<Type>() < right_val.value<Type>())) ||
1368 (oper==TokenGtEqOper && (left_val.value<Type>() >= right_val.value<Type>())) ||
1369 (oper==TokenLtEqOper && (left_val.value<Type>() <= right_val.value<Type>())));
1370 }
1371