Official websites use .gov
A .gov website belongs to an official government organization in the United States.

Secure .gov websites use HTTPS
A lock ( ) or https:// means you’ve safely connected to the .gov website. Share sensitive information only on official, secure websites.

Isis 3 Programmer Reference
PvlKeyword.cpp
1
5
6/* SPDX-License-Identifier: CC0-1.0 */
7
8#include <QDebug>
9#include <QString>
10#include <QRegularExpression>
11#include "PvlKeyword.h"
12#include "IException.h"
13#include "Message.h"
14#include "IString.h"
15#include "PvlFormat.h"
16#include "PvlSequence.h"
17
18#include <sstream>
19
20using namespace std;
21using json = nlohmann::json;
22namespace Isis {
27
28
35 init();
37 }
38
39
48 PvlKeyword::PvlKeyword(QString name, QString value,
49 QString unit) {
50 init();
52 addValue(value, unit);
53 }
54
64 PvlKeyword::PvlKeyword(QString name, vector<string> vecValue,
65 QString unit) {
66 init();
68 for (int i = 0; i < vecValue.size(); i++) {
69 addValue(QString::fromStdString(vecValue[i]), unit);
70 }
71 }
72
73
76 init();
77 *this = other;
78 }
79
80
85 if (m_units) {
86 delete m_units;
87 m_units = NULL;
88 }
89
90 if (m_comments) {
91 delete m_comments;
92 m_comments = NULL;
93 }
94
95 if (m_name) {
96 delete [] m_name;
97 m_name = NULL;
98 }
99 }
100
101
104 m_name = NULL;
105 m_units = NULL;
106 m_comments = NULL;
107 m_width = 0;
108 m_indent = 0;
109 m_formatter = NULL;
110
111 clear();
112 }
113
122 bool PvlKeyword::isNull(int index) const {
123 if (size() == 0) return true;
124 if (index < 0 || index >= (int)m_values.size()) {
125 QString msg = Message::ArraySubscriptNotInRange(index);
126 throw IException(IException::Programmer, msg, _FILEINFO_);
127 }
128 if (stringEqual("NULL", m_values[index])) return true;
129 if (stringEqual("", m_values[index])) return true;
130 if (stringEqual("\"\"", m_values[index])) return true;
131 if (stringEqual("''", m_values[index])) return true;
132 return false;
133 }
134
140 void PvlKeyword::setName(QString name) {
141 QString final = name.trimmed();
142 if (final.contains(QRegExp("\\s"))) {
143 QString msg = "[" + name + "] is invalid. Keyword name cannot ";
144 msg += "contain whitespace.";
145 throw IException(IException::User, msg, _FILEINFO_);
146 }
147
148 if (m_name) {
149 delete [] m_name;
150 m_name = NULL;
151 }
152
153 if (final != "") {
154 QByteArray finalAscii = final.toLatin1();
155 m_name = new char[finalAscii.size() + 1];
156 strncpy(m_name, finalAscii.data(), final.size() + 1);
157 }
158 }
159
175 void PvlKeyword::setValue(QString value, QString unit) {
176 clear();
177 addValue(value, unit);
178 }
179
193 void PvlKeyword::setJsonValue(json jsonobj, QString unit)
194 {
195 clear();
196 addJsonValue(jsonobj, unit);
197 }
198
204 void PvlKeyword::setUnits(QString units) {
205 if (!m_units) {
206 m_units = new std::vector<QString>();
207 }
208
209 m_units->clear();
210
211 for (int i = 0; i < m_values.size(); i++) {
212 m_units->push_back(units);
213 }
214 }
215
216
225 void PvlKeyword::setUnits(QString value, QString units) {
226
227 bool found = false;
228 int i = -1;
229 while(!found && ++i < (int) m_values.size()) {
230 if (value == m_values[i]) {
231 found = true;
232 }
233 }
234
235 if (found) {
236 if (!m_units) {
237 m_units = new std::vector<QString>(m_values.size());
238 }
239 else {
240 m_units->resize(m_values.size());
241 }
242
243 (*m_units)[i] = units;
244 }
245 else {
246 IString msg = "PvlKeyword::setUnits called with value [" + value +
247 "] which does not exist in this Keyword";
248 throw IException(IException::Programmer, msg, _FILEINFO_);
249 }
250 }
251
268 clear();
269 addValue(value);
270 return *this;
271 }
272
288 void PvlKeyword::addValue(QString value, QString unit) {
289 m_values.append(value);
290
291 if (unit != "") {
292 if (!m_units) {
293 m_units = new std::vector<QString>(m_values.size());
294 }
295 else {
296 m_units->resize(m_values.size());
297 }
298
299 (*m_units)[m_units->size() - 1] = unit;
300 }
301 else if (m_units) {
302 m_units->push_back("");
303 }
304 }
305
322 void PvlKeyword::addJsonValue(json jsonobj, QString unit) {
323 QString value;
324 if (jsonobj.is_array()) {
325 QString msg = "Unable to convert " + name() + " with nested json array value into PvlKeyword";
326 throw IException(IException::Unknown, msg, _FILEINFO_);
327 }
328 else if (jsonobj.is_number())
329 {
330 value = QString::number(jsonobj.get<double>(), 'g', 16);
331 }
332 else if (jsonobj.is_boolean())
333 {
334 value = QString(jsonobj.get<bool>() ? "true" : "false");
335 }
336 else if (jsonobj.is_null())
337 {
338 value = QString("Null");
339 }
340 else
341 {
342 value = QString::fromStdString(jsonobj);
343 }
344 addValue(value, unit);
345 }
346
363 addValue(value);
364 return *this;
365 }
366
369 m_values.clear();
370
371 if (m_units) {
372 delete m_units;
373 m_units = NULL;
374 }
375 }
376
377
378 PvlKeyword::operator QString() const {
379 return operator[](0);
380 }
381
382
395 QString &PvlKeyword::operator[](int index) {
396 if (index < 0 || index >= (int)m_values.size()) {
397 QString msg = (Message::ArraySubscriptNotInRange(index)) +
398 "for Keyword [" + QString(m_name) + "]";
399 throw IException(IException::Programmer, msg, _FILEINFO_);
400 }
401
402 return m_values[index];
403 }
404
417 const QString &PvlKeyword::operator[](int index) const {
418 if (index < 0 || index >= (int)m_values.size()) {
419 QString msg = Message::ArraySubscriptNotInRange(index);
420 throw IException(IException::Programmer, msg, _FILEINFO_);
421 }
422 return m_values[index];
423 }
424
434 QString PvlKeyword::unit(int index) const {
435 if (!m_units) return "";
436
437 if (index < 0 || index >= (int)m_units->size()) {
438 QString msg = Message::ArraySubscriptNotInRange(index);
439 throw IException(IException::Programmer, msg, _FILEINFO_);
440 }
441 return (*m_units)[index];
442 }
443
454 if (!m_comments) {
455 m_comments = new std::vector<QString>();
456 }
457
458 if (comment.size() == 0) {
459 m_comments->push_back("#");
460 }
461 if (comment[0] == '#') {
462 m_comments->push_back(comment);
463 }
464 else if (comment.size() == 1) {
465 m_comments->push_back("# " + comment);
466 }
467 else if ((comment[0] == '/') && (comment[1] == '*')) {
468 m_comments->push_back(comment);
469 }
470 else if ((comment[0] == '/') && (comment[1] == '/')) {
471 m_comments->push_back(comment);
472 }
473 else {
474 m_comments->push_back("# " + comment);
475 }
476 }
477
488 IString cmt = comment;
489 IString token = cmt.Token(" ");
490 while(cmt != "") {
491 IString temp = token;
492 token = cmt.Token(" ");
493 int length = temp.size() + token.size() + 1;
494 while((length < 72) && (token.size() > 0)) {
495 temp += " " + token;
496 token = cmt.Token(" ");
497 length = temp.size() + token.size() + 1;
498 }
499 addComment(temp.c_str());
500 }
501 if (token.size() != 0) addComment(token.c_str());
502 }
503
506 if (m_comments) {
507 delete m_comments;
508 m_comments = NULL;
509 }
510 }
511
518 QString PvlKeyword::comment(int index) const {
519 if (!m_comments) return "";
520
521 if (index < 0 || index >= (int)m_comments->size()) {
522 QString msg = Message::ArraySubscriptNotInRange(index);
523 throw IException(IException::Programmer, msg, _FILEINFO_);
524 }
525 return (*m_comments)[index];
526 };
527
535 QString PvlKeyword::reform(const QString &value) const {
536#if 0
537 static bool firstTime = true;
538 static bool iPVL = true;
539 if (firstTime) {
540 firstTime = false;
541 Isis::PvlGroup &g = Isis::Preference::Preferences().findGroup(
542 "UserInterface", Isis::Pvl::Traverse);
543
544 Isis::IString s = (QString) g["PvlFormat"];
545 s.UpCase();
546 if (s == "PVL") iPVL = false;
547 }
548
549 if (iPVL) return toIPvl(value);
550#endif
551 return toPvl(value);
552 }
553
559 QString PvlKeyword::toIPvl(const QString &value) const {
560 QString out;
561 bool upcase = true;
562 bool lastlower = true;
563 for (int i = 0; i < value.size(); i++) {
564 if ((lastlower) && (value[i].isUpper())) upcase = true;
565 if (value[i] == '_') {
566 upcase = true;
567 }
568 else if (upcase) {
569 out += value[i].toUpper();
570 lastlower = false;
571 upcase = false;
572 }
573 else {
574 out += value[i].toLower();
575 if (value[i].isLower()) lastlower = true;
576 upcase = false;
577 }
578 }
579 return out;
580 }
581
587 QString PvlKeyword::toPvl(const QString &value) const {
588 QString out;
589 bool lastlower = false;
590 for (int i = 0; i < value.size(); i++) {
591 if ((lastlower) && (value[i].isUpper())) out += "_";
592 if (value[i] == '_') {
593 out += "_";
594 lastlower = false;
595 }
596 else {
597 out += value[i].toUpper();
598 if (value[i].isLower()) lastlower = true;
599 }
600 }
601 return out;
602 }
603
612 bool PvlKeyword::stringEqual(const QString &QString1,
613 const QString &QString2) {
614 Isis::IString s1(QString1);
615 Isis::IString s2(QString2);
616
619
620 s1.Remove(" _");
621 s2.Remove(" _");
622
623 s1.UpCase();
624 s2.UpCase();
625
626 if (s1 == s2) return true;
627 return false;
628 }
629
639 bool PvlKeyword::isEquivalent(QString QString1, int index) const {
640 if (index < 0 || index >= (int)m_values.size()) {
641 QString msg = Message::ArraySubscriptNotInRange(index);
642 throw IException(IException::Programmer, msg, _FILEINFO_);
643 }
644
645 return stringEqual(m_values[index], QString1);
646 }
647
654 clear();
655 for (int i = 0; i < seq.Size(); i++) {
656 QString temp = "(";
657 for (int j = 0; j < (int)seq[i].size(); j++) {
658 QString val = seq[i][j];
659 if (val.contains(" ")) {
660 temp += "\"" + val + "\"";
661 }
662 else {
663 temp += val;
664 }
665 if (j < (int) seq[i].size() - 1) temp += ", ";
666 }
667 temp += ")";
668 this->operator+=(temp);
669 }
670
671 return *this;
672 }
673
688 ostream &PvlKeyword::writeWithWrap(std::ostream &os,
689 const QString &textToWrite,
690 int startColumn,
691 PvlFormat &format) const {
692
693 /*
694 http://pds.jpl.nasa.gov/tools/standards-reference.shtml
695
696 pds.jpl.nasa.gov/documents/sr/Chapter12.pdf
697
698 Object Description Language Specification and Usage
699 The following provides a complete specification for Object Description Language
700 (ODL), the language used to encode data labels for the Planetary Data System
701 (PDS) and other NASA data systems. This standard contains a formal definition of
702 the grammar semantics of the language. PDS specific implementation notes and
703 standards are referenced in separate sections.
704
705 12.5.3.1 Implementation of String Values
706 A text QString read in from a label is reassembled into a QString of characters.
707 The way in which the QString is broken into lines in a label does not affect the
708 format of the QString after it has been reassembled. The following rules are used
709 when reading text QStrings: If a format effector or a sequence of
710 format effectors is encountered within a text QString,
711 the effector (or sequence of effectors) is replaced by a single space
712 character, unless the last character is a hyphen (dash) character. Any
713 spacing characters at the end of the line are removed and any spacing
714 characters at the beginning of the following line are removed. This
715 allows a text QString in a label to appear with the left and right
716 margins set at arbitrary points without changing the QString value. For
717 example, the following two QStrings are the same: "To be or
718 not to be" and
719 "To be or
720 not to be"
721 If the last character on a line prior to a format effector is a hyphen
722 (dash) character, the hyphen is removed with any spacing characters at
723 the beginning of the following line. This follows the standard
724 convention in English of using a hyphen to break a word across lines.
725 For example, the following two QStrings are the same:
726 "The planet Jupiter is very big" and
727 "The planet Jupi-
728 ter is very big"
729 Control codes, other than the horizontal tabulation character and
730 format effectors, appearing within a text QString are removed.
731 */
732
733 /*
734 We will be adding a condition for human-readable purposes:
735 If a quoted QString of text does not fit on the current line,
736 but will fit on the next line, use the next line.
737 */
738
739 // Position set
740 QString remainingText = textToWrite;
741 int spaceForText = format.charLimit() - 1 - format.formatEOL().length() - startColumn;
742
743 // indexOf quote positions to better determine which line to put the
744 // QString on. Data structure: vector< startPos, endPos > where
745 // remainingText[startPos] and remainingText[endPos] must both be quotes.
746 vector< pair<int, int> > quotedAreas;
747 int quoteStart = -1;
748
749 // if its an array, indent subsequent lines 1 more
750 if (textToWrite.count() > 0 && (textToWrite[0] == '(' || textToWrite[0] == '"')) {
751 startColumn ++;
752 }
753
754 /* Standard 12.3.3.1 ->
755 A quoted text QString may not contain the quotation mark, which is reserved
756 to be the text QString delimiter.
757
758 So we don't have to worry about escaped quotes.
759 */
760
761 vector< pair<char, char> > quoteStartEnds;
762 quoteStartEnds.push_back(pair<char, char>('"', '"'));
763 quoteStartEnds.push_back(pair<char, char>('\'', '\''));
764 quoteStartEnds.push_back(pair<char, char>('<', '>'));
765
766 // clean up any EOL characters, they mustn't interfere, remove sections of
767 // multiple spaces (make them into one), and indexOf quoted areas
768 for (int pos = 0; pos < remainingText.size(); pos++) {
769 // remove \r and \n from QString
770 if (remainingText[pos] == '\n' || remainingText[pos] == '\r') {
771 if (pos != remainingText.size() - 1) {
772 remainingText = remainingText.mid(0, pos) +
773 remainingText.mid(pos + 1);
774 }
775 else {
776 remainingText = remainingText.mid(0, pos);
777 }
778 }
779
780 // convert " " to " " if not quoted
781 if (quoteStart == -1) {
782 while(pos > 0 &&
783 remainingText[pos-1] == ' ' &&
784 remainingText[pos] == ' ') {
785 remainingText = remainingText.mid(0, pos) +
786 remainingText.mid(pos + 1);
787 }
788 }
789
790 // Find quotes
791 for (unsigned int i = 0;
792 (quoteStart < 0) && i < quoteStartEnds.size();
793 i++) {
794 if (quoteStartEnds[i].first == remainingText[pos]) {
795 quoteStart = pos;
796 }
797 }
798
799
800 //bool mismatchQuote = false;
801
802 // Check to see if we're ending a quote if we didn't just
803 // start the quote and we are inside a quote
804 if (quoteStart != (int)pos && quoteStart != -1) {
805 for (unsigned int i = 0; i < quoteStartEnds.size(); i++) {
806 if (quoteStartEnds[i].second == remainingText[pos]) {
807 if (quoteStartEnds[i].first != remainingText[quoteStart]) {
808 continue;
809 // mismatchQuote = true;
810 }
811
812 quotedAreas.push_back(pair<int, int>(quoteStart, pos));
813
814 quoteStart = -1;
815 }
816 }
817 }
818
819 //if (mismatchQuote) {
820 // IString msg = "Pvl keyword values [" + textToWrite +
821 // "] can not have embedded quotes";
822 // throw iException::Message(iException::Programmer, msg, _FILEINFO_);
823 //}
824 }
825
826 int charsLeft = spaceForText;
827 int printedSoFar = 0;
828
829 // while we have something to write, keep going
830 while(!remainingText.isEmpty()) {
831 // search backwards for the last space or comma *in the limit* (80 chars)
832 int lastSpacePosition = charsLeft;
833
834 // if everything fits into our remaining space, consider the last
835 // spot in the QString to be printed still the split position.
836 if (lastSpacePosition >= (int)remainingText.length()) {
837 lastSpacePosition = remainingText.length();
838 }
839 else {
840 // Everything does not fit; use good space for mediocre splits (inside
841 // quoted QStrings), and excellent space for good splits (between array
842 // values for example)
843 int goodSpace = -1;
844 int excellentSpace = -1;
845 int searchPosition = lastSpacePosition;
846 bool doneSearching = false;
847
848 while(!doneSearching) {
849 bool currentPosQuoted = false;
850
851 for (unsigned int i = 0; i < quotedAreas.size(); i++) {
852 if (searchPosition + printedSoFar >= quotedAreas[i].first &&
853 searchPosition + printedSoFar <= quotedAreas[i].second) {
854 currentPosQuoted = true;
855 }
856 }
857
858 if (remainingText[searchPosition] == ' ') {
859 bool validSpace = true;
860
861 // this really isn't a good space if the previous character is a
862 // '-' though - then it would be read wrong when re-imported.
863 if (searchPosition > 0 && remainingText[searchPosition - 1] == '-') {
864 validSpace = false;
865 }
866
867 if (validSpace && goodSpace < 0) {
868 goodSpace = searchPosition;
869 }
870
871 // An excellent space is the prefential break - not quoted and
872 // not units next.
873 // we were already done if we had an excellent space
874 if (validSpace && !currentPosQuoted) {
875 if ((int)searchPosition < (int)(remainingText.size() - 1) &&
876 remainingText[searchPosition+1] != '<') {
877 excellentSpace = searchPosition;
878 }
879 }
880 }
881
882 doneSearching = (excellentSpace >= 0 || searchPosition <= 1);
883 searchPosition --;
884 }
885
886 // Use the best breaking point we have
887 if (excellentSpace > 0) {
888 lastSpacePosition = excellentSpace;
889 }
890 else if (goodSpace > 0) {
891 lastSpacePosition = goodSpace;
892 }
893 else {
894 lastSpacePosition = -1;
895 }
896 }
897
898 // we found a space or comma in our limit, write to that chatacter
899 // and repeat the loop
900 if (lastSpacePosition >= 0) {
901 os << remainingText.mid(0, lastSpacePosition);
902
903 remainingText = remainingText.mid(lastSpacePosition);
904 printedSoFar += lastSpacePosition;
905 }
906 // we failed to indexOf a space or a comma in our limit,
907 // use a hyphen (-)
908 else {
909 // Make sure we don't break on "//" since Isis thinks that is a comment
910 if (remainingText.mid(charsLeft-1, 2) == "//") {
911 os << remainingText.mid(0, charsLeft - 2);
912 os << "-";
913 remainingText = remainingText.mid(charsLeft - 2);
914 printedSoFar += charsLeft - 2;
915 }
916 else {
917 os << remainingText.mid(0, charsLeft - 1);
918 os << "-";
919 remainingText = remainingText.mid(charsLeft - 1);
920 printedSoFar += charsLeft - 1;
921 }
922 }
923
924 // we wrote as much as possible, do a newline and repeat
925 if (!remainingText.isEmpty()) {
926 os << format.formatEOL();
927 writeSpaces(os, startColumn);
928
929 // dont allow spaces to begin the next line inside what we're printing
930 if (remainingText[0] == ' ') {
931 remainingText = remainingText.mid(1);
932 printedSoFar += 1;
933 }
934 }
935
936 charsLeft = spaceForText;
937 }
938
939 return os;
940 }
941
942
949 void PvlKeyword::writeSpaces(std::ostream &os, int numSpaces) const {
950 for (int space = 0; space < numSpaces; space ++) {
951 os << " ";
952 }
953 }
954
955
963 m_formatter = formatter;
964 }
965
966
974 return m_formatter;
975 };
976
986 std::istream &operator>>(std::istream &is, PvlKeyword &result) {
987 result = PvlKeyword();
988 QString line;
989 QString keywordString;
990
991 bool keywordDone = false;
992 bool multiLineComment = false;
993 bool error = !is.good();
994
995 while(!error && !keywordDone) {
996 istream::pos_type beforeLine = is.tellg();
997
998 line = PvlKeyword::readLine(is, multiLineComment);
999
1000 // We read an empty line (failed to read next non-empty line)
1001 // and didnt complete our keyword, essentially we hit the implicit
1002 // keyword named "End"
1003 if (line.isEmpty() && !is.good()) {
1004 if (keywordString.isEmpty() ||
1005 keywordString[keywordString.size()-1] == '\n') {
1006 line = "End";
1007
1008 if (multiLineComment) {
1009 error = true;
1010 }
1011 }
1012 else {
1013 error = true;
1014 }
1015 }
1016
1017 bool comment = false;
1018
1019 if (!multiLineComment) {
1020 if (line.size() > 0 && line[0] == '#') {
1021 comment = true;
1022 }
1023
1024 if (line.size() > 1 && line[0] == '/' &&
1025 (line[1] == '*' || line[1] == '/')) {
1026 comment = true;
1027
1028 if (line[1] == '*') {
1029 multiLineComment = true;
1030 keywordString += line.mid(0, 2);
1031 line = line.mid(2).trimmed();
1032 }
1033 }
1034 }
1035
1036 if (multiLineComment) {
1037 comment = true;
1038
1039 if (line.contains("/*")) {
1040 IString msg = "Error when reading a pvl: Cannot have ['/*'] inside a "
1041 "multi-line comment";
1042 throw IException(IException::Unknown, msg, _FILEINFO_);
1043 }
1044
1045 if (line.contains("*/")) {
1046 multiLineComment = false;
1047
1048 line = line.mid(0, line.indexOf("*/")).trimmed() + " */";
1049 }
1050 }
1051
1052 if (line.isEmpty()) {
1053 continue;
1054 }
1055 // comment line
1056 else if (comment) {
1057 keywordString += line + '\n';
1058 continue;
1059 }
1060 // first line of keyword data
1061 else if (keywordString.isEmpty()) {
1062 keywordString = line;
1063 }
1064 // concatenation
1065 else if (!comment && keywordString[keywordString.size()-1] == '-') {
1066 keywordString = keywordString.mid(0, keywordString.size() - 1) + line;
1067 }
1068 // Non-commented and non-concatenation -> put in the space
1069 else {
1070 keywordString += " " + line;
1071 }
1072 // if this line concatenates with the next, read the next
1073 if (line[line.size()-1] == '-') {
1074 continue;
1075 }
1076
1077 std::vector<QString> keywordComments;
1078 QString keywordName;
1079 std::vector< std::pair<QString, QString> > keywordValues;
1080
1081 bool attemptedRead = false;
1082
1083 try {
1084 attemptedRead = PvlKeyword::readCleanKeyword(keywordString,
1085 keywordComments,
1086 keywordName,
1087 keywordValues);
1088 }
1089 catch (IException &e) {
1090 if (is.eof() && !is.bad()) {
1091 is.clear();
1092 is.unget();
1093 }
1094
1095 is.seekg(beforeLine, ios::beg);
1096
1097 QString msg = "Unable to read PVL keyword [";
1098 msg += keywordString;
1099 msg += "]";
1100
1101 throw IException(e, IException::Unknown, msg, _FILEINFO_);
1102 }
1103
1104 // Result valid?
1105 if (attemptedRead) {
1106 // if the next line starts with '<' then it should be read too...
1107 // it should be units
1108 // however, you can't have units if there is no value
1109 if (is.good() && is.peek() == '<' && !keywordValues.empty()) {
1110 continue;
1111 }
1112
1113 result.setName(keywordName);
1114 result.addComments(keywordComments);
1115
1116 for (unsigned int value = 0; value < keywordValues.size(); value++) {
1117 result.addValue(keywordValues[value].first,
1118 keywordValues[value].second);
1119 }
1120
1121 keywordDone = true;
1122 }
1123
1124 if (!attemptedRead) {
1125 error = error || !is.good();
1126 }
1127 // else we need to keep reading
1128 }
1129
1130 if (error) {
1131 // skip comments
1132 while(keywordString.contains('\n')) {
1133 keywordString = keywordString.mid(keywordString.indexOf('\n') + 1);
1134 }
1135
1136 QString msg;
1137
1138 if (keywordString.isEmpty() && !multiLineComment) {
1139 msg = "PVL input contains no Pvl Keywords";
1140 }
1141 else if (multiLineComment) {
1142 msg = "PVL input ends while still in a multi-line comment";
1143 }
1144 else {
1145 msg = "The PVL keyword [" + keywordString + "] does not appear to be";
1146 msg += " a valid Pvl Keyword";
1147 }
1148
1149 throw IException(IException::Unknown, msg, _FILEINFO_);
1150 }
1151
1152 if (!keywordDone) {
1153 // skip comments
1154 while(keywordString.contains('\n'))
1155 keywordString = keywordString.mid(keywordString.indexOf('\n') + 1);
1156
1157 QString msg;
1158
1159 if (keywordString.isEmpty()) {
1160 msg = "Error reading PVL keyword";
1161 }
1162 else {
1163 msg = "The PVL keyword [" + keywordString + "] does not appear to be";
1164 msg += " complete";
1165 }
1166
1167 throw IException(IException::Unknown, msg, _FILEINFO_);
1168 }
1169
1170 return is;
1171 }
1172
1179 void PvlKeyword::addComments(const std::vector<QString> &comments) {
1180 for (unsigned int i = 0; i < comments.size(); i++) {
1181 addComment(comments[i]);
1182 }
1183 }
1184
1200 bool PvlKeyword::readCleanKeyword(QString keyword,
1201 std::vector<QString> &keywordComments,
1202 QString &keywordName,
1203 std::vector< std::pair<QString, QString> > &keywordValues) {
1204 // Reset outputs
1205 keywordComments.clear();
1206 keywordName = "";
1207 keywordValues.clear();
1208
1209 // This is in case a close quote doesn't exist
1210 bool explicitIncomplete = false;
1211
1212 // Possible (known) comment starts in pvl
1213 QString comments[] = {
1214 "#",
1215 "//"
1216 };
1217
1218 // Need more data if nothing is here!
1219 if (keyword.isEmpty()) return 0;
1220
1221 /*
1222 Step 1: Read Comments
1223
1224 Theoretically, we should have an input that looks like this:
1225 #Comment
1226 //Comment
1227 / * Comment
1228 Comment * /
1229 Keyword = Data
1230
1231 So we could just grab all of the first lines; however, this method
1232 needs to be as error-proof as possible (it is the basis of reading
1233 all PVLs after all), so verify we have things that look like comments
1234 first, strip them & store them.
1235 */
1236
1237 // While we have newlines, we have comments
1238 while(keyword.contains("\n")) {
1239 // Make sure we strip data every loop of comment types; otherwise we
1240 // have no comment and need to error out.
1241 bool noneStripped = true;
1242
1243 // Check every comment type now and make sure this line (it isn't the last
1244 // line since a newline exists) starts in a comment
1245 QString keywordStart = keyword.mid(0, 2);
1246
1247 // Handle multi-line comments
1248 if (keywordStart == "/*") {
1249 noneStripped = false;
1250 bool inComment = true;
1251
1252 while(inComment && keyword.contains("*/")) {
1253 // Correct the */ to make sure it has a \n after it,
1254 // without this we risk an infinite loop
1255 int closePos = keyword.indexOf("*/\n");
1256
1257 if (closePos == -1) {
1258 closePos = keyword.indexOf("*/") + 2;
1259 keyword = keyword.mid(0, closePos) + "\n" +
1260 keyword.mid(closePos);
1261 }
1262
1263 QString comment = keyword.mid(0, keyword.indexOf("\n")).trimmed();
1264
1265 // Set these to true if too small, false if not (they need if
1266 // cant currently fit).
1267 bool needsStart = (comment.size() < 2);
1268 bool needsStartSpace = comment.size() < 3;
1269
1270 int commentEndPos = comment.size() - 2;
1271 bool needsEnd = (commentEndPos < 0);
1272 bool needsEndSpace = comment.size() < 3;
1273
1274 // Needs are currently set based on QString size, apply real logic
1275 // to test for character sequences (try to convert them from false
1276 // to true).
1277 if (!needsStart) {
1278 needsStart = (comment.mid(0, 2) != "/*");
1279 }
1280
1281 if (!needsEnd) {
1282 needsEnd = (comment.mid(commentEndPos, 2) != "*/");
1283 }
1284
1285 if (!needsStartSpace) {
1286 needsStartSpace = (comment.mid(0, 3) != "/* ");
1287 }
1288
1289 if (!needsEndSpace) {
1290 needsEndSpace = (comment.mid(commentEndPos - 1, 3) != " */");
1291 }
1292
1293 if (needsStart) {
1294 comment = "/* " + comment;
1295 }
1296 else if (needsStartSpace) {
1297 comment = "/* " + comment.mid(2);
1298 }
1299
1300 if (needsEnd) {
1301 comment = comment + " */";
1302 }
1303 else if (needsEndSpace) {
1304 comment = comment.mid(0, comment.size() - 2) + " */";;
1305 }
1306
1307 inComment = needsEnd;
1308
1309 keywordComments.push_back(comment);
1310
1311 if (keyword.contains("\n")) {
1312 keyword = keyword.mid(keyword.indexOf("\n") + 1).trimmed();
1313 }
1314
1315 // Check for another comment start
1316 if (!inComment) {
1317 if (keyword.size() >= 2)
1318 keywordStart = keyword.mid(0, 2);
1319
1320 inComment = (keywordStart == "/*");
1321 }
1322 }
1323
1324 // So we have a bunch of multi-line commands... make them the same size
1325 // Find longest
1326 int longest = 0;
1327 for (unsigned int index = 0; index < keywordComments.size(); index++) {
1328 QString comment = keywordComments[index];
1329
1330 if (comment.size() > longest)
1331 longest = comment.size();
1332 }
1333
1334 // Now make all the sizes match longest
1335 for (unsigned int index = 0; index < keywordComments.size(); index++) {
1336 QString comment = keywordComments[index];
1337
1338 while(comment.size() < longest) {
1339 // This adds a space to the end of the comment
1340 comment = comment.mid(0, comment.size() - 2) + " */";
1341 }
1342
1343 keywordComments[index] = comment;
1344 }
1345 // They should all be the same length now
1346 }
1347
1348 // Search for single line comments
1349 for (unsigned int commentType = 0;
1350 commentType < sizeof(comments) / sizeof(QString);
1351 commentType++) {
1352
1353 if (keywordStart.startsWith(comments[commentType])) {
1354 // Found a comment start; strip this line out and store it as a
1355 // comment!
1356 QString comment = keyword.mid(0, keyword.indexOf("\n"));
1357 keywordComments.push_back(comment.trimmed());
1358
1359 noneStripped = false;
1360
1361 if (keyword.contains("\n")) {
1362 keyword = keyword.mid(keyword.indexOf("\n") + 1).trimmed();
1363 }
1364 }
1365 }
1366
1367 // Does it look like Name=Value/*comm
1368 // mment*/ ?
1369 if (noneStripped && keyword.contains("/*") &&
1370 keyword.contains("*/")) {
1371 QString firstPart = keyword.mid(0, keyword.indexOf("\n"));
1372 QString lastPart = keyword.mid(keyword.indexOf("\n") + 1);
1373
1374 keyword = firstPart.trimmed() + " " + lastPart.trimmed();
1375 noneStripped = false;
1376 }
1377
1378 if (noneStripped) {
1379 QString msg = "Expected a comment in PVL but found [";
1380 msg += keyword;
1381 msg += "]";
1382 throw IException(IException::Unknown, msg, _FILEINFO_);
1383 }
1384 }
1385
1386 // Do we have a keyword at all?
1387 if (keyword.isEmpty()) {
1388 return false; // need more data
1389 }
1390
1391 /*
1392 Step 2: Determine Keyword Format
1393
1394 Make sure we have a keyword after the comments first. We expect
1395 one of three formats:
1396 KEYWORD PROCESSED IN STEP 3.1
1397 KEYWORD = (VALUE,VALUE,...) PROCESSED IN STEP 3.2
1398 KEYWORD = VALUE PROCESSED IN STEP 3.3
1399 */
1400
1401 // Get the keyword name
1402 keywordName = readValue(keyword, explicitIncomplete);
1403
1404 // we have taken the name; if nothing remains then it is value-less
1405 // and we are done.
1406 if (keyword.isEmpty()) {
1407 /*
1408 Step 3.1
1409
1410 Format is determined to be:
1411 KEYWORD
1412
1413 Well, no value/units may exist so we're done processing this keyword.
1414 Now check if it's a reserved keyword that is valueless
1415 */
1416 bool allowedValueless = false;
1417 static const std::vector<QString> reservedKeywordNames = {"OBJECT",
1418 "BEGIN_OBJECT",
1419 "END_OBJECT",
1420 "ENDOBJECT",
1421 "GROUP",
1422 "END_GROUP",
1423 "ENDGROUP",
1424 "END"};
1425 const QString keywordNameUpper = keywordName.toUpper();
1426 for (const auto &reservedKeywordName : reservedKeywordNames) {
1427 if (keywordNameUpper == reservedKeywordName) {
1428 allowedValueless = true;
1429 break;
1430 }
1431 }
1432 return allowedValueless;
1433 }
1434
1435 // if we don't have an equal, then we have a problem - an invalid symbol.
1436 // Our possible remaining formats both are KEYWORD = ...
1437 if (keyword[0] != '=') {
1438 QString msg = "Expected an assignment [=] when reading PVL, but found [";
1439 msg += keyword[0];
1440 msg += "]";
1441
1442 throw IException(IException::Unknown, msg, _FILEINFO_);
1443 }
1444
1445 keyword = keyword.mid(1).trimmed();
1446
1447 if (keyword.isEmpty()) {
1448 return false;
1449 }
1450
1451 // now we need to split into two possibilities: array or non-array
1452 if (keyword[0] == '(' || keyword[0] == '{') {
1453 /*
1454 Step 3.2
1455
1456 Our format is confirmed as:
1457 KEYWORD = (...)
1458
1459 We need to read each value/unit in the array.
1460 */
1461
1462 char closingParen = ((keyword[0] == '(') ? ')' : '}');
1463 char wrongClosingParen = ((keyword[0] == '(') ? '}' : ')');
1464 bool closedProperly = false;
1465
1466 vector< pair<char, char> > extraDelims;
1467 extraDelims.push_back(pair<char, char>('(', ')'));
1468 extraDelims.push_back(pair<char, char>('{', '}'));
1469
1470 // strip '(' - onetime, this makes every element in the array the same
1471 // (except the last)
1472 keyword = keyword.mid(1).trimmed();
1473
1474 // handle empty arrays: KEYWORD = ()
1475 if (!keyword.isEmpty() && keyword[0] == closingParen) {
1476 closedProperly = true;
1477 }
1478
1479 // Each iteration of this loop should consume 1 value in the array,
1480 // including the comma, i.e. we should start out with:
1481 // 'VALUE,VALUE,...)' until we hit ')' (our exit condition)
1482 while(!keyword.isEmpty() && keyword[0] != closingParen) {
1483 // foundComma delimits the end of this element in the array (remains
1484 // false for last value which has no comma at the end)
1485 bool foundComma = false;
1486 // keyword should be of the format: VALUE <UNIT>,....)
1487 // Read VALUE from it
1488 QString nextItem = readValue(keyword, explicitIncomplete, extraDelims);
1489
1490 if (!keyword.isEmpty() && keyword[0] == wrongClosingParen) {
1491
1492 QString msg = "Incorrect array close when reading PVL; expected [";
1493 msg += closingParen;
1494 msg += "] but found [";
1495 msg += wrongClosingParen;
1496 msg += "] in keyword named [";
1497 msg += keywordName;
1498 msg += "]";
1499 throw IException(IException::Unknown, msg, _FILEINFO_);
1500 }
1501
1502 // This contains <VALUE, UNIT>
1503 pair<QString, QString> keywordValue;
1504
1505 // Store VALUE
1506 keywordValue.first = nextItem;
1507
1508 // Now there's 2 possibilities: units or no units ->
1509 // if we have units, read them
1510 if (!keyword.isEmpty() && keyword[0] == '<') {
1511 QString unitsValue = readValue(keyword, explicitIncomplete);
1512 keywordValue.second = unitsValue;
1513 }
1514
1515 // Now we should* have a comma, strip it
1516 if (!keyword.isEmpty() && keyword[0] == ',') {
1517 foundComma = true;
1518 keyword = keyword.mid(1).trimmed();
1519 }
1520
1521 // No comma and nothing more in QString - we found
1522 // KEYWORD = (VALUE,VALUE\0
1523 // we need more information to finish this keyword
1524 if (!foundComma && keyword.isEmpty()) {
1525 return false; // could become valid later
1526 }
1527
1528 bool foundCloseParen = (!keyword.isEmpty() && keyword[0] == closingParen);
1529
1530 if (foundCloseParen) {
1531 closedProperly = true;
1532 }
1533
1534 // Check for the condition of:
1535 // keyword = (VALUE,VALUE,)
1536 // which is unrecoverable
1537 if (foundComma && foundCloseParen) {
1538 QString msg = "Unexpected close of keyword-value array when reading "
1539 "PVL";
1540 throw IException(IException::Unknown, msg, _FILEINFO_);
1541 }
1542
1543 // Check for (VALUE VALUE
1544 if (!foundComma && !foundCloseParen) {
1545 // We have ("VALUE VALUE
1546 if (explicitIncomplete) return false;
1547
1548 // We have (VALUE VALUE
1549 QString msg = "Found extra data after [";
1550 msg += nextItem;
1551 msg += "] in array when reading PVL";
1552 throw IException(IException::Unknown, msg, _FILEINFO_);
1553 }
1554
1555 // we're good with this element of the array, remember it
1556 keywordValues.push_back(keywordValue);
1557 }
1558
1559 if (!closedProperly) {
1560 return false;
1561 }
1562
1563 // Trim off the closing paren
1564 if (!keyword.isEmpty()) {
1565 keyword = keyword.mid(1).trimmed();
1566 }
1567
1568 // Read units here if they exist and apply them
1569 // case: (A,B,C) <unit>
1570 if (!keyword.isEmpty() && keyword[0] == '<') {
1571 QString units = readValue(keyword, explicitIncomplete);
1572 for (unsigned int val = 0; val < keywordValues.size(); val++) {
1573 if (keywordValues[val].second.isEmpty()) {
1574 keywordValues[val].second = units;
1575 }
1576 }
1577 }
1578 }
1579 else {
1580 /*
1581 Step 3.3
1582
1583 Our format is confirmed as:
1584 "KEYWORD = VALUE <UNIT>"
1585
1586 We need to read the single value/unit in the keywordValues array.
1587 */
1588 pair<QString, QString> keywordValue;
1589 keywordValue.first = readValue(keyword, explicitIncomplete);
1590
1591 if (!keyword.isEmpty() && keyword[0] == '<') {
1592 keywordValue.second = readValue(keyword, explicitIncomplete);
1593 }
1594
1595 keywordValues.push_back(keywordValue);
1596 }
1597
1598 /*
1599 This is set when a quote is opened somewhere and never closed, it means
1600 we need more information
1601 */
1602 if (explicitIncomplete) {
1603 return false; // unclosed quote at end... need more information
1604 }
1605
1606 /*
1607 See if we have a comment at the end of the keyword...
1608 */
1609 if (!keyword.isEmpty()) {
1610 // if the data left is a comment, we can handle it probably
1611 if (keyword[0] == '#' ||
1612 ((keyword.size() > 1 && keyword[0] == '/') &&
1613 (keyword[1] == '/' || keyword[1] == '*'))) {
1614 keywordComments.push_back(keyword);
1615
1616 if (keyword.size() > 1 && keyword.mid(0, 2) == "/*") {
1617 if (keyword.mid(keyword.size() - 2, 2) != "*/")
1618 return false; // need more comment data
1619 }
1620
1621 keyword = "";
1622 }
1623 }
1624
1625 // check for extraneous data in the keyword ... ,2,3
1626 QRegularExpression regex("^,");
1627 QRegularExpressionMatch match = regex.match(keyword);
1628 if (!keyword.isEmpty() && match.hasMatch()) {
1629 keywordValues.at(0).first += keyword;
1630 keyword = "";
1631 }
1632
1633 /*
1634 If more data remains, it is unrecognized.
1635 */
1636 if (!keyword.isEmpty()) {
1637 QString msg = "Keyword has extraneous data [";
1638 msg += keyword;
1639 msg += "] at the end";
1640 throw IException(IException::Unknown, msg, _FILEINFO_);
1641 }
1642
1643 // We've parsed this keyword! :)
1644 return true;
1645 }
1646
1647
1648 QString PvlKeyword::readValue(QString &keyword, bool &quoteProblem) {
1649 std::vector< std::pair<char, char> > otherDelims;
1650
1651 return readValue(keyword, quoteProblem, otherDelims);
1652 }
1653
1670 QString PvlKeyword::readValue(QString &keyword, bool &quoteProblem,
1671 const std::vector< std::pair<char, char> > &
1672 otherDelimiters) {
1673 QString value = "";
1674
1675 // This method ignores spaces except as delimiters; let's trim the QString
1676 // to start
1677 keyword = keyword.trimmed();
1678
1679 if (keyword.isEmpty()) {
1680 return "";
1681 }
1682
1683 // An implied quote is one that is started without a special character, for
1684 // example HELLO WORLD has HELLO and WORLD separately as implied quotes for
1685 // PVLs. However, "HELLO WORLD" has an explicit (not implied) double quote.
1686 // We do consider <> as quotes.
1687 bool impliedQuote = true;
1688 QChar quoteEnd = ' ';
1689 bool keepQuotes = false;
1690
1691 if (keyword[0] == '\'' || keyword[0] == '"') {
1692 quoteEnd = keyword[0];
1693 impliedQuote = false;
1694 }
1695 else if (keyword[0] == '<') {
1696 quoteEnd = '>';
1697 impliedQuote = false;
1698 }
1699 else {
1700 // we're not quoted, look for alternative delimiters.
1701 char implicitQuotes[] = {
1702 ')',
1703 '}',
1704 ',',
1705 ' ',
1706 '\t',
1707 '<',
1708 '='
1709 };
1710
1711 bool foundImplicitQuote = false;
1712
1713 int currentPos = 0;
1714 while(!foundImplicitQuote && currentPos != keyword.size()) {
1715 for (unsigned int quote = 0;
1716 quote < sizeof(implicitQuotes) / sizeof(char);
1717 quote ++) {
1718 if (keyword[currentPos] == implicitQuotes[quote]) {
1719 quoteEnd = implicitQuotes[quote];
1720 foundImplicitQuote = true;
1721 }
1722 }
1723
1724 if (!foundImplicitQuote) {
1725 currentPos ++;
1726 }
1727 }
1728 }
1729
1730 for (unsigned int delim = 0; delim < otherDelimiters.size(); delim ++) {
1731 if (keyword[0] == otherDelimiters[delim].first) {
1732 quoteEnd = otherDelimiters[delim].second;
1733 keepQuotes = true;
1734 impliedQuote = false;
1735 }
1736 }
1737
1738 QString startQuote;
1739 // non-implied delimeters need the opening delimiter ignored. Remember
1740 // startQuote in case of error later on we can reconstruct the original
1741 // QString.
1742 if (!impliedQuote) {
1743 startQuote += keyword[0];
1744 keyword = keyword.mid(1);
1745 }
1746
1747 // Do we have a known quote end?
1748 int quoteEndPos = keyword.indexOf(quoteEnd);
1749
1750 if (quoteEndPos != -1) {
1751 value = keyword.mid(0, quoteEndPos);
1752
1753 // Trim keyword 1 past end delimiter (if end delimiter is last, this
1754 // results in empty QString). If the close delimiter is ')' or ',' then
1755 // leave it be however, since we need to preserve that to denote this was
1756 // an end of a valuein array keyword.
1757 if (!impliedQuote) {
1758 keyword = keyword.mid(quoteEndPos + 1);
1759 }
1760 else {
1761 keyword = keyword.mid(quoteEndPos);
1762 }
1763
1764 // Make sure we dont have padding
1765 keyword = keyword.trimmed();
1766
1767 if (keepQuotes) {
1768 value = startQuote + value + quoteEnd;
1769 }
1770
1771 return value;
1772 }
1773 // implied quotes terminate at end of keyword; otherwise we have a problem
1774 // (which is this condition)
1775 else if (!impliedQuote) {
1776 // restore the original QString
1777 keyword = startQuote + keyword;
1778 quoteProblem = true;
1779
1780 return "";
1781 }
1782
1783 // we have an implied quote but no quote character, the rest must be the
1784 // value.
1785 value = keyword;
1786 keyword = "";
1787
1788 return value;
1789 }
1790
1791
1805 QString PvlKeyword::readLine(std::istream &is, bool insideComment) {
1806 QString lineOfData;
1807
1808 while(is.good() && lineOfData.isEmpty()) {
1809
1810 // read until \n (works for both \r\n and \n) or */
1811 while(is.good() &&
1812 (!lineOfData.size() || lineOfData[lineOfData.size() - 1] != '\n')) {
1813 char next = is.get();
1814
1815 // if non-ascii found then we're done... immediately
1816 if (next <= 0) {
1817 is.seekg(0, ios::end);
1818 is.get();
1819 return lineOfData;
1820 }
1821
1822 // if any errors (i.e. eof) happen in the get operation then don't
1823 // store this data
1824 if (is.good()) {
1825 lineOfData += next;
1826 }
1827
1828 if (insideComment &&
1829 lineOfData.size() >= 2 && lineOfData[lineOfData.size() - 2] == '*' &&
1830 lineOfData[lineOfData.size() - 1] == '/') {
1831 // End of multi-line comment = end of line!
1832 break;
1833 }
1834 else if (lineOfData.size() >= 2 &&
1835 lineOfData[lineOfData.size() - 2] == '/' &&
1836 lineOfData[lineOfData.size() - 1] == '*') {
1837 insideComment = true;
1838 }
1839 }
1840
1841 // Trim off non-visible characters from this line of data
1842 lineOfData = lineOfData.trimmed();
1843
1844 // read up to next non-whitespace in input stream
1845 while(is.good() &&
1846 (is.peek() == ' ' ||
1847 is.peek() == '\r' ||
1848 is.peek() == '\n')) {
1849 is.get();
1850 }
1851
1852 // if lineOfData is empty (line was empty), we repeat
1853 }
1854
1855 return lineOfData;
1856 }
1857
1858
1867 ostream &operator<<(std::ostream &os, const Isis::PvlKeyword &keyword) {
1868 // Set up a Formatter
1869 PvlFormat *tempFormat = keyword.m_formatter;
1870 bool removeFormatter = false;
1871 if (tempFormat == NULL) {
1872 tempFormat = new PvlFormat();
1873 removeFormatter = true;
1874 }
1875
1876 // Write out the comments
1877 for (int i = 0; i < keyword.comments(); i++) {
1878 for (int j = 0; j < keyword.indent(); j++) os << " ";
1879 os << keyword.comment(i) << tempFormat->formatEOL();
1880 }
1881
1882 // Write the keyword name & add length to startColumn.
1883 int startColumn = 0;
1884 for (int i = 0; i < keyword.indent(); i++) {
1885 os << " ";
1886 ++startColumn;
1887 }
1888 QString keyname = tempFormat->formatName(keyword);
1889 os << keyname;
1890 startColumn += keyname.length();
1891
1892 // Add padding and then write equal sign.
1893 for (int i = 0; i < keyword.width() - (int)keyname.size(); ++i) {
1894 os << " ";
1895 ++startColumn;
1896 }
1897 os << " = ";
1898 startColumn += 3;
1899
1900 // If it has no value then write a NULL
1901 if (keyword.size() == 0) {
1902 os << tempFormat->formatValue(keyword);
1903 }
1904
1905 // Loop and write each array value
1906 QString stringToWrite;
1907 for (int i = 0; i < keyword.size(); i ++) {
1908 stringToWrite += tempFormat->formatValue(keyword, i);
1909 }
1910
1911 keyword.writeWithWrap(os,
1912 stringToWrite,
1913 startColumn,
1914 *tempFormat);
1915
1916 if (removeFormatter) delete tempFormat;
1917
1918 return os;
1919 }
1920
1921
1924 if (this != &other) {
1925 m_formatter = other.m_formatter;
1926
1927 if (m_name) {
1928 delete [] m_name;
1929 m_name = NULL;
1930 }
1931
1932 if (other.m_name) {
1933 setName(other.m_name);
1934 }
1935
1936 m_values = other.m_values;
1937
1938 if (m_units) {
1939 delete m_units;
1940 m_units = NULL;
1941 }
1942
1943 if (other.m_units) {
1944 m_units = new std::vector<QString>(*other.m_units);
1945 }
1946
1947 if (m_comments) {
1948 delete m_comments;
1949 m_comments = NULL;
1950 }
1951
1952 if (other.m_comments) {
1953 m_comments = new std::vector<QString>(*other.m_comments);
1954 }
1955
1956 m_width = other.m_width;
1957 m_indent = other.m_indent;
1958 }
1959
1960 return *this;
1961 }
1962
1975 void PvlKeyword::validateKeyword(PvlKeyword & pvlKwrd, QString psValueType, PvlKeyword* pvlKwrdValue)
1976 {
1977 int iSize = pvlKwrd.size();
1978
1979 QString sType = m_values[0].toLower();
1980
1981 // Value type
1982 if (psValueType.length()) {
1983 psValueType = psValueType.toLower();
1984 }
1985
1986 double dRangeMin=0, dRangeMax=0;
1987 bool bRange = false;
1988 bool bValue = false;
1989 if (pvlKwrdValue != NULL) {
1990 QString sValueName = pvlKwrdValue->name();
1991
1992 // Check for Range
1993 if (sValueName.contains("__Range")) {
1994 dRangeMin = toDouble((*pvlKwrdValue)[0]);
1995 dRangeMax = toDouble((*pvlKwrdValue)[1]);
1996 bRange = true;
1997 }
1998 else if (sValueName.contains("__Value")) {
1999 bValue = true;
2000 }
2001 }
2002
2003 // Type integer
2004 if (sType == "integer") {
2005 for (int i=0; i<iSize; i++) {
2006 QString sValue = pvlKwrd[i].toLower();
2007 if (sValue != "null"){
2008 int iValue=0;
2009 try {
2010 iValue = toInt(sValue);
2011 } catch (IException & e) {
2012 QString sErrMsg = "\"" +pvlKwrd.name() +"\" expects an Integer value";
2013 throw IException(e, IException::User, sErrMsg, _FILEINFO_);
2014 }
2015 if (bRange && (iValue < dRangeMin || iValue > dRangeMax)) {
2016 QString sErrMsg = "\"" +pvlKwrd.name() +"\" is not in the specified Range";
2017 throw IException(IException::User, sErrMsg, _FILEINFO_);
2018 }
2019 if (bValue) {
2020 bool bFound = false;
2021 for (int j=0; j<pvlKwrdValue->size(); j++) {
2022 if (iValue == toInt((*pvlKwrdValue)[j])) {
2023 bFound = true;
2024 break;
2025 }
2026 }
2027 if (!bFound) {
2028 QString sErrMsg = "\"" +pvlKwrd.name() +"\" has value not in the accepted list";
2029 throw IException(IException::User, sErrMsg, _FILEINFO_);
2030 }
2031 }
2032 // Type is specified (positive / negative)
2033 if (psValueType.length()) {
2034 if ((psValueType == "positive" && iValue < 0) || (psValueType == "negative" && iValue >= 0) ) {
2035 QString sErrMsg = "\"" +pvlKwrd.name() +"\" has invalid value";
2036 throw IException(IException::User, sErrMsg, _FILEINFO_);
2037 }
2038 }
2039 }
2040 }
2041 return;
2042 }
2043
2044 // Type double
2045 if (sType == "double") {
2046 for (int i=0; i<iSize; i++) {
2047 QString sValue = pvlKwrd[i].toLower();
2048 if (sValue != "null"){
2049 double dValue = toDouble(sValue);
2050 if (bRange && (dValue < dRangeMin || dValue > dRangeMax)) {
2051 QString sErrMsg = "\"" +pvlKwrd.name() +"\" is not in the specified Range";
2052 throw IException(IException::User, sErrMsg, _FILEINFO_);
2053 }
2054 if (bValue) {
2055 bool bFound = false;
2056 for (int j=0; j<pvlKwrdValue->size(); j++) {
2057 if (dValue == toDouble((*pvlKwrdValue)[j])) {
2058 bFound = true;
2059 break;
2060 }
2061 }
2062 if (!bFound) {
2063 QString sErrMsg = "\"" +pvlKwrd.name() +"\" has value not in the accepted list";
2064 throw IException(IException::User, sErrMsg, _FILEINFO_);
2065 }
2066 }
2067 // Type is specified (positive / negative)
2068 if (psValueType.length()) {
2069 if ((psValueType == "positive" && dValue < 0) || (psValueType == "negative" && dValue >= 0) ) {
2070 QString sErrMsg = "\"" +pvlKwrd.name() +"\" has invalid value";
2071 throw IException(IException::User, sErrMsg, _FILEINFO_);
2072 }
2073 }
2074 }
2075 }
2076 return;
2077 }
2078
2079 // Type boolean
2080 if (sType == "boolean") {
2081 for (int i=0; i<iSize; i++) {
2082 QString sValue = pvlKwrd[i].toLower();
2083 if (sValue != "null" && sValue != "true" && sValue != "false"){
2084 QString sErrMsg = "Wrong Type of value in the Keyword \"" + name() + "\" \n";
2085 throw IException(IException::User, sErrMsg, _FILEINFO_);
2086 }
2087 }
2088 return;
2089 }
2090
2091 // Type String
2092 if (sType == "string") {
2093 for (int i=0; i<iSize; i++) {
2094 QString sValue = pvlKwrd[i].toLower();
2095 if (bValue) {
2096 bool bValFound = false;
2097 for (int i=0; i<pvlKwrdValue->size(); i++) {
2098 if (sValue == (*pvlKwrdValue)[i].toLower()) {
2099 bValFound = true;
2100 break;
2101 }
2102 }
2103 if (!bValFound) {
2104 QString sErrMsg = "Wrong Type of value in the Keyword \"" + name() + "\" \n";
2105 throw IException(IException::User, sErrMsg, _FILEINFO_);
2106 }
2107 }
2108 }
2109 }
2110 }
2111}
Isis exception class.
Definition IException.h:91
@ Unknown
A type of error that cannot be classified as any of the other error types.
Definition IException.h:118
@ User
A type of error that could only have occurred due to a mistake on the user's part (e....
Definition IException.h:126
@ Programmer
This error is for when a programmer made an API call that was illegal.
Definition IException.h:146
Adds specific functionality to C++ strings.
Definition IString.h:165
IString Remove(const std::string &del)
Remove all instances of any character in the string from the IString.
Definition IString.cpp:1266
IString UpCase()
Converst any lower case characters in the object IString with uppercase characters.
Definition IString.cpp:617
IString Token(const IString &separator)
Returns the first token in the IString.
Definition IString.cpp:897
IString ConvertWhiteSpace()
Returns the string with all "new lines", "carriage returns", "tabs", "formfeeds", "vertical tabs" and...
Definition IString.cpp:1238
Formats a Pvl name value pair to Isis standards.
Definition PvlFormat.h:108
Contains multiple PvlContainers.
Definition PvlGroup.h:41
A single keyword-value pair.
Definition PvlKeyword.h:87
PvlKeyword()
Constructs a blank PvlKeyword object.
std::vector< QString > * m_comments
The comments for the keyword.
Definition PvlKeyword.h:284
QVarLengthArray< QString, 1 > m_values
The values in the keyword.
Definition PvlKeyword.h:278
const QString & operator[](int index) const
Gets value for this object at specified index.
void setName(QString name)
Sets the keyword name.
void setJsonValue(nlohmann::json jsonobj, QString unit="")
Sets new value from Json.
QString toIPvl(const QString &value) const
Converts a value to iPVL format.
int width() const
Returns the current set longest keyword name.
Definition PvlKeyword.h:218
QString name() const
Returns the keyword name.
Definition PvlKeyword.h:105
int size() const
Returns the number of values stored in this keyword.
Definition PvlKeyword.h:135
PvlKeyword & operator+=(QString value)
Adds a value.
std::vector< QString > * m_units
The units for the values.
Definition PvlKeyword.h:281
bool isNull(const int index=0) const
Decides whether a value is null or not at a given index.
char * m_name
The keyword's name... This is a c-string for memory efficiency.
Definition PvlKeyword.h:268
static bool readCleanKeyword(QString keyword, std::vector< QString > &keywordComments, QString &keywordName, std::vector< std::pair< QString, QString > > &keywordValues)
This reads a keyword compressed back to 1 line of data (excluding comments, which are included on sep...
QString unit(const int index=0) const
Returns the units of measurement of the element of the array of values for the object at the specifie...
~PvlKeyword()
Destructs a PvlKeyword object.
void setFormat(PvlFormat *formatter)
Set the PvlFormatter used to format the keyword name and value(s)
int comments() const
Returns the number of lines of comments associated with this keyword.
Definition PvlKeyword.h:170
QString comment(const int index) const
Return a comment at the specified index.
PvlFormat * m_formatter
Formatter object.
Definition PvlKeyword.h:264
int indent() const
Returns the current indent level.
Definition PvlKeyword.h:223
QString reform(const QString &value) const
Checks if the value needs to be converted to PVL or iPVL and returns it in the correct format.
void setUnits(QString units)
Sets the unit of measure for all current values if any exist.
static bool stringEqual(const QString &string1, const QString &string2)
Checks to see if two QStrings are equal.
PvlFormat * format()
Get the current PvlFormat or create one.
void addCommentWrapped(QString comment)
Automatically wraps and adds long comments to the PvlKeyword.
QString toPvl(const QString &value) const
Converts a value to PVL format.
void addJsonValue(nlohmann::json jsonobj, QString unit="")
Adds a value with units.
PvlKeyword & operator=(QString value)
Sets new values.
void writeSpaces(std::ostream &, int) const
This writes numSpaces spaces to the ostream.
std::ostream & writeWithWrap(std::ostream &os, const QString &textToWrite, int startColumn, PvlFormat &format) const
Wraps output so that length doesn't exceed the character limit.
void setValue(QString value, QString unit="")
Sets new values.
void addComment(QString comment)
Add a comment to the PvlKeyword.
int m_width
The width of the longest keyword.
Definition PvlKeyword.h:294
bool isEquivalent(QString string1, int index=0) const
Checks to see if a value with a specified index is equivalent to another QString.
void clearComment()
Clears the current comments.
void addComments(const std::vector< QString > &comments)
This method adds multiple comments at once by calling AddComments on each element in the vector.
static QString readLine(std::istream &is, bool insideComment)
This method reads one line of data from the input stream.
int m_indent
The number of indentations to make.
Definition PvlKeyword.h:299
void clear()
Clears all values and units for this PvlKeyword object.
void validateKeyword(PvlKeyword &pvlKwrd, QString psValueType="", PvlKeyword *pvlKwrdRange=NULL)
Validate Keyword for type and required values.
void init()
Clears all PvlKeyword data.
void addValue(QString value, QString unit="")
Adds a value with units.
@ Traverse
Search child objects.
Definition PvlObject.h:158
Parse and return elements of a Pvl sequence.
Definition PvlSequence.h:46
int Size() const
Number of arrays in the sequence.
Definition PvlSequence.h:70
QString ArraySubscriptNotInRange(int index)
This error should be used when an Isis object or application is checking array bounds and the legal r...
This is free and unencumbered software released into the public domain.
Definition Apollo.h:16
int toInt(const QString &string)
Global function to convert from a string to an integer.
Definition IString.cpp:93
std::istream & operator>>(std::istream &is, CSVReader &csv)
Input read operator for input stream sources.
double toDouble(const QString &string)
Global function to convert from a string to a double.
Definition IString.cpp:149
QDebug operator<<(QDebug debug, const Hillshade &hillshade)
Print this class out to a QDebug object.
Namespace for the standard library.