Isis 3 Programmer Reference
PvlKeyword.cpp
1
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
18using namespace std;
19using json = nlohmann::json;
20namespace Isis {
25
26
32 PvlKeyword::PvlKeyword(QString name) {
33 init();
35 }
36
37
46 PvlKeyword::PvlKeyword(QString name, QString value,
47 QString unit) {
48 init();
50 addValue(value, unit);
51 }
52
53
56 init();
57 *this = other;
58 }
59
60
65 if (m_units) {
66 delete m_units;
67 m_units = NULL;
68 }
69
70 if (m_comments) {
71 delete m_comments;
72 m_comments = NULL;
73 }
74
75 if (m_name) {
76 delete [] m_name;
77 m_name = NULL;
78 }
79 }
80
81
84 m_name = NULL;
85 m_units = NULL;
86 m_comments = NULL;
87 m_width = 0;
88 m_indent = 0;
89 m_formatter = NULL;
90
91 clear();
92 }
93
102 bool PvlKeyword::isNull(int index) const {
103 if (size() == 0) return true;
104 if (index < 0 || index >= (int)m_values.size()) {
105 QString msg = Message::ArraySubscriptNotInRange(index);
106 throw IException(IException::Programmer, msg, _FILEINFO_);
107 }
108 if (stringEqual("NULL", m_values[index])) return true;
109 if (stringEqual("", m_values[index])) return true;
110 if (stringEqual("\"\"", m_values[index])) return true;
111 if (stringEqual("''", m_values[index])) return true;
112 return false;
113 }
114
120 void PvlKeyword::setName(QString name) {
121 QString final = name.trimmed();
122 if (final.contains(QRegExp("\\s"))) {
123 QString msg = "[" + name + "] is invalid. Keyword name cannot ";
124 msg += "contain whitespace.";
125 throw IException(IException::User, msg, _FILEINFO_);
126 }
127
128 if (m_name) {
129 delete [] m_name;
130 m_name = NULL;
131 }
132
133 if (final != "") {
134 QByteArray finalAscii = final.toLatin1();
135 m_name = new char[finalAscii.size() + 1];
136 strncpy(m_name, finalAscii.data(), final.size() + 1);
137 }
138 }
139
155 void PvlKeyword::setValue(QString value, QString unit) {
156 clear();
157 addValue(value, unit);
158 }
159
173 void PvlKeyword::setJsonValue(json jsonobj, QString unit)
174 {
175 clear();
176 addJsonValue(jsonobj, unit);
177 }
178
184 void PvlKeyword::setUnits(QString units) {
185 if (!m_units) {
186 m_units = new std::vector<QString>();
187 }
188
189 m_units->clear();
190
191 for (int i = 0; i < m_values.size(); i++) {
192 m_units->push_back(units);
193 }
194 }
195
196
205 void PvlKeyword::setUnits(QString value, QString units) {
206
207 bool found = false;
208 int i = -1;
209 while(!found && ++i < (int) m_values.size()) {
210 if (value == m_values[i]) {
211 found = true;
212 }
213 }
214
215 if (found) {
216 if (!m_units) {
217 m_units = new std::vector<QString>(m_values.size());
218 }
219 else {
220 m_units->resize(m_values.size());
221 }
222
223 (*m_units)[i] = units;
224 }
225 else {
226 IString msg = "PvlKeyword::setUnits called with value [" + value +
227 "] which does not exist in this Keyword";
228 throw IException(IException::Programmer, msg, _FILEINFO_);
229 }
230 }
231
248 clear();
249 addValue(value);
250 return *this;
251 }
252
268 void PvlKeyword::addValue(QString value, QString unit) {
269 m_values.append(value);
270
271 if (unit != "") {
272 if (!m_units) {
273 m_units = new std::vector<QString>(m_values.size());
274 }
275 else {
276 m_units->resize(m_values.size());
277 }
278
279 (*m_units)[m_units->size() - 1] = unit;
280 }
281 else if (m_units) {
282 m_units->push_back("");
283 }
284 }
285
302 void PvlKeyword::addJsonValue(json jsonobj, QString unit) {
303 QString value;
304 if (jsonobj.is_array()) {
305 QString msg = "Unable to convert " + name() + " with nested json array value into PvlKeyword";
306 throw IException(IException::Unknown, msg, _FILEINFO_);
307 }
308 else if (jsonobj.is_number())
309 {
310 value = QString::number(jsonobj.get<double>(), 'g', 16);
311 }
312 else if (jsonobj.is_boolean())
313 {
314 value = QString(jsonobj.get<bool>() ? "true" : "false");
315 }
316 else if (jsonobj.is_null())
317 {
318 value = QString("Null");
319 }
320 else
321 {
322 value = QString::fromStdString(jsonobj);
323 }
324 addValue(value, unit);
325 }
326
343 addValue(value);
344 return *this;
345 }
346
349 m_values.clear();
350
351 if (m_units) {
352 delete m_units;
353 m_units = NULL;
354 }
355 }
356
357
358 PvlKeyword::operator QString() const {
359 return operator[](0);
360 }
361
362
375 QString &PvlKeyword::operator[](int index) {
376 if (index < 0 || index >= (int)m_values.size()) {
377 QString msg = (Message::ArraySubscriptNotInRange(index)) +
378 "for Keyword [" + QString(m_name) + "]";
379 throw IException(IException::Programmer, msg, _FILEINFO_);
380 }
381
382 return m_values[index];
383 }
384
397 const QString &PvlKeyword::operator[](int index) const {
398 if (index < 0 || index >= (int)m_values.size()) {
399 QString msg = Message::ArraySubscriptNotInRange(index);
400 throw IException(IException::Programmer, msg, _FILEINFO_);
401 }
402 return m_values[index];
403 }
404
414 QString PvlKeyword::unit(int index) const {
415 if (!m_units) return "";
416
417 if (index < 0 || index >= (int)m_units->size()) {
418 QString msg = Message::ArraySubscriptNotInRange(index);
419 throw IException(IException::Programmer, msg, _FILEINFO_);
420 }
421 return (*m_units)[index];
422 }
423
433 void PvlKeyword::addComment(QString comment) {
434 if (!m_comments) {
435 m_comments = new std::vector<QString>();
436 }
437
438 if (comment.size() == 0) {
439 m_comments->push_back("#");
440 }
441 if (comment[0] == '#') {
442 m_comments->push_back(comment);
443 }
444 else if (comment.size() == 1) {
445 m_comments->push_back("# " + comment);
446 }
447 else if ((comment[0] == '/') && (comment[1] == '*')) {
448 m_comments->push_back(comment);
449 }
450 else if ((comment[0] == '/') && (comment[1] == '/')) {
451 m_comments->push_back(comment);
452 }
453 else {
454 m_comments->push_back("# " + comment);
455 }
456 }
457
467 void PvlKeyword::addCommentWrapped(QString comment) {
468 IString cmt = comment;
469 IString token = cmt.Token(" ");
470 while(cmt != "") {
471 IString temp = token;
472 token = cmt.Token(" ");
473 int length = temp.size() + token.size() + 1;
474 while((length < 72) && (token.size() > 0)) {
475 temp += " " + token;
476 token = cmt.Token(" ");
477 length = temp.size() + token.size() + 1;
478 }
479 addComment(temp.c_str());
480 }
481 if (token.size() != 0) addComment(token.c_str());
482 }
483
486 if (m_comments) {
487 delete m_comments;
488 m_comments = NULL;
489 }
490 }
491
498 QString PvlKeyword::comment(int index) const {
499 if (!m_comments) return "";
500
501 if (index < 0 || index >= (int)m_comments->size()) {
502 QString msg = Message::ArraySubscriptNotInRange(index);
503 throw IException(IException::Programmer, msg, _FILEINFO_);
504 }
505 return (*m_comments)[index];
506 };
507
515 QString PvlKeyword::reform(const QString &value) const {
516#if 0
517 static bool firstTime = true;
518 static bool iPVL = true;
519 if (firstTime) {
520 firstTime = false;
521 Isis::PvlGroup &g = Isis::Preference::Preferences().findGroup(
522 "UserInterface", Isis::Pvl::Traverse);
523
524 Isis::IString s = (QString) g["PvlFormat"];
525 s.UpCase();
526 if (s == "PVL") iPVL = false;
527 }
528
529 if (iPVL) return toIPvl(value);
530#endif
531 return toPvl(value);
532 }
533
539 QString PvlKeyword::toIPvl(const QString &value) const {
540 QString out;
541 bool upcase = true;
542 bool lastlower = true;
543 for (int i = 0; i < value.size(); i++) {
544 if ((lastlower) && (value[i].isUpper())) upcase = true;
545 if (value[i] == '_') {
546 upcase = true;
547 }
548 else if (upcase) {
549 out += value[i].toUpper();
550 lastlower = false;
551 upcase = false;
552 }
553 else {
554 out += value[i].toLower();
555 if (value[i].isLower()) lastlower = true;
556 upcase = false;
557 }
558 }
559 return out;
560 }
561
567 QString PvlKeyword::toPvl(const QString &value) const {
568 QString out;
569 bool lastlower = false;
570 for (int i = 0; i < value.size(); i++) {
571 if ((lastlower) && (value[i].isUpper())) out += "_";
572 if (value[i] == '_') {
573 out += "_";
574 lastlower = false;
575 }
576 else {
577 out += value[i].toUpper();
578 if (value[i].isLower()) lastlower = true;
579 }
580 }
581 return out;
582 }
583
592 bool PvlKeyword::stringEqual(const QString &QString1,
593 const QString &QString2) {
594 Isis::IString s1(QString1);
595 Isis::IString s2(QString2);
596
597 s1.ConvertWhiteSpace();
598 s2.ConvertWhiteSpace();
599
600 s1.Remove(" _");
601 s2.Remove(" _");
602
603 s1.UpCase();
604 s2.UpCase();
605
606 if (s1 == s2) return true;
607 return false;
608 }
609
619 bool PvlKeyword::isEquivalent(QString QString1, int index) const {
620 if (index < 0 || index >= (int)m_values.size()) {
621 QString msg = Message::ArraySubscriptNotInRange(index);
622 throw IException(IException::Programmer, msg, _FILEINFO_);
623 }
624
625 return stringEqual(m_values[index], QString1);
626 }
627
634 clear();
635 for (int i = 0; i < seq.Size(); i++) {
636 QString temp = "(";
637 for (int j = 0; j < (int)seq[i].size(); j++) {
638 QString val = seq[i][j];
639 if (val.contains(" ")) {
640 temp += "\"" + val + "\"";
641 }
642 else {
643 temp += val;
644 }
645 if (j < (int) seq[i].size() - 1) temp += ", ";
646 }
647 temp += ")";
648 this->operator+=(temp);
649 }
650
651 return *this;
652 }
653
668 ostream &PvlKeyword::writeWithWrap(std::ostream &os,
669 const QString &textToWrite,
670 int startColumn,
671 PvlFormat &format) const {
672
673 /*
674 http://pds.jpl.nasa.gov/tools/standards-reference.shtml
675
676 pds.jpl.nasa.gov/documents/sr/Chapter12.pdf
677
678 Object Description Language Specification and Usage
679 The following provides a complete specification for Object Description Language
680 (ODL), the language used to encode data labels for the Planetary Data System
681 (PDS) and other NASA data systems. This standard contains a formal definition of
682 the grammar semantics of the language. PDS specific implementation notes and
683 standards are referenced in separate sections.
684
685 12.5.3.1 Implementation of String Values
686 A text QString read in from a label is reassembled into a QString of characters.
687 The way in which the QString is broken into lines in a label does not affect the
688 format of the QString after it has been reassembled. The following rules are used
689 when reading text QStrings: If a format effector or a sequence of
690 format effectors is encountered within a text QString,
691 the effector (or sequence of effectors) is replaced by a single space
692 character, unless the last character is a hyphen (dash) character. Any
693 spacing characters at the end of the line are removed and any spacing
694 characters at the beginning of the following line are removed. This
695 allows a text QString in a label to appear with the left and right
696 margins set at arbitrary points without changing the QString value. For
697 example, the following two QStrings are the same: "To be or
698 not to be" and
699 "To be or
700 not to be"
701 If the last character on a line prior to a format effector is a hyphen
702 (dash) character, the hyphen is removed with any spacing characters at
703 the beginning of the following line. This follows the standard
704 convention in English of using a hyphen to break a word across lines.
705 For example, the following two QStrings are the same:
706 "The planet Jupiter is very big" and
707 "The planet Jupi-
708 ter is very big"
709 Control codes, other than the horizontal tabulation character and
710 format effectors, appearing within a text QString are removed.
711 */
712
713 /*
714 We will be adding a condition for human-readable purposes:
715 If a quoted QString of text does not fit on the current line,
716 but will fit on the next line, use the next line.
717 */
718
719 // Position set
720 QString remainingText = textToWrite;
721 int spaceForText = format.charLimit() - 1 - format.formatEOL().length() - startColumn;
722
723 // indexOf quote positions to better determine which line to put the
724 // QString on. Data structure: vector< startPos, endPos > where
725 // remainingText[startPos] and remainingText[endPos] must both be quotes.
726 vector< pair<int, int> > quotedAreas;
727 int quoteStart = -1;
728
729 // if its an array, indent subsequent lines 1 more
730 if (textToWrite.count() > 0 && (textToWrite[0] == '(' || textToWrite[0] == '"')) {
731 startColumn ++;
732 }
733
734 /* Standard 12.3.3.1 ->
735 A quoted text QString may not contain the quotation mark, which is reserved
736 to be the text QString delimiter.
737
738 So we don't have to worry about escaped quotes.
739 */
740
741 vector< pair<char, char> > quoteStartEnds;
742 quoteStartEnds.push_back(pair<char, char>('"', '"'));
743 quoteStartEnds.push_back(pair<char, char>('\'', '\''));
744 quoteStartEnds.push_back(pair<char, char>('<', '>'));
745
746 // clean up any EOL characters, they mustn't interfere, remove sections of
747 // multiple spaces (make them into one), and indexOf quoted areas
748 for (int pos = 0; pos < remainingText.size(); pos++) {
749 // remove \r and \n from QString
750 if (remainingText[pos] == '\n' || remainingText[pos] == '\r') {
751 if (pos != remainingText.size() - 1) {
752 remainingText = remainingText.mid(0, pos) +
753 remainingText.mid(pos + 1);
754 }
755 else {
756 remainingText = remainingText.mid(0, pos);
757 }
758 }
759
760 // convert " " to " " if not quoted
761 if (quoteStart == -1) {
762 while(pos > 0 &&
763 remainingText[pos-1] == ' ' &&
764 remainingText[pos] == ' ') {
765 remainingText = remainingText.mid(0, pos) +
766 remainingText.mid(pos + 1);
767 }
768 }
769
770 // Find quotes
771 for (unsigned int i = 0;
772 (quoteStart < 0) && i < quoteStartEnds.size();
773 i++) {
774 if (quoteStartEnds[i].first == remainingText[pos]) {
775 quoteStart = pos;
776 }
777 }
778
779
780 //bool mismatchQuote = false;
781
782 // Check to see if we're ending a quote if we didn't just
783 // start the quote and we are inside a quote
784 if (quoteStart != (int)pos && quoteStart != -1) {
785 for (unsigned int i = 0; i < quoteStartEnds.size(); i++) {
786 if (quoteStartEnds[i].second == remainingText[pos]) {
787 if (quoteStartEnds[i].first != remainingText[quoteStart]) {
788 continue;
789 // mismatchQuote = true;
790 }
791
792 quotedAreas.push_back(pair<int, int>(quoteStart, pos));
793
794 quoteStart = -1;
795 }
796 }
797 }
798
799 //if (mismatchQuote) {
800 // IString msg = "Pvl keyword values [" + textToWrite +
801 // "] can not have embedded quotes";
802 // throw iException::Message(iException::Programmer, msg, _FILEINFO_);
803 //}
804 }
805
806 int charsLeft = spaceForText;
807 int printedSoFar = 0;
808
809 // while we have something to write, keep going
810 while(!remainingText.isEmpty()) {
811 // search backwards for the last space or comma *in the limit* (80 chars)
812 int lastSpacePosition = charsLeft;
813
814 // if everything fits into our remaining space, consider the last
815 // spot in the QString to be printed still the split position.
816 if (lastSpacePosition >= (int)remainingText.length()) {
817 lastSpacePosition = remainingText.length();
818 }
819 else {
820 // Everything does not fit; use good space for mediocre splits (inside
821 // quoted QStrings), and excellent space for good splits (between array
822 // values for example)
823 int goodSpace = -1;
824 int excellentSpace = -1;
825 int searchPosition = lastSpacePosition;
826 bool doneSearching = false;
827
828 while(!doneSearching) {
829 bool currentPosQuoted = false;
830
831 for (unsigned int i = 0; i < quotedAreas.size(); i++) {
832 if (searchPosition + printedSoFar >= quotedAreas[i].first &&
833 searchPosition + printedSoFar <= quotedAreas[i].second) {
834 currentPosQuoted = true;
835 }
836 }
837
838 if (remainingText[searchPosition] == ' ') {
839 bool validSpace = true;
840
841 // this really isn't a good space if the previous character is a
842 // '-' though - then it would be read wrong when re-imported.
843 if (searchPosition > 0 && remainingText[searchPosition - 1] == '-') {
844 validSpace = false;
845 }
846
847 if (validSpace && goodSpace < 0) {
848 goodSpace = searchPosition;
849 }
850
851 // An excellent space is the prefential break - not quoted and
852 // not units next.
853 // we were already done if we had an excellent space
854 if (validSpace && !currentPosQuoted) {
855 if ((int)searchPosition < (int)(remainingText.size() - 1) &&
856 remainingText[searchPosition+1] != '<') {
857 excellentSpace = searchPosition;
858 }
859 }
860 }
861
862 doneSearching = (excellentSpace >= 0 || searchPosition <= 1);
863 searchPosition --;
864 }
865
866 // Use the best breaking point we have
867 if (excellentSpace > 0) {
868 lastSpacePosition = excellentSpace;
869 }
870 else if (goodSpace > 0) {
871 lastSpacePosition = goodSpace;
872 }
873 else {
874 lastSpacePosition = -1;
875 }
876 }
877
878 // we found a space or comma in our limit, write to that chatacter
879 // and repeat the loop
880 if (lastSpacePosition >= 0) {
881 os << remainingText.mid(0, lastSpacePosition);
882
883 remainingText = remainingText.mid(lastSpacePosition);
884 printedSoFar += lastSpacePosition;
885 }
886 // we failed to indexOf a space or a comma in our limit,
887 // use a hyphen (-)
888 else {
889 // Make sure we don't break on "//" since Isis thinks that is a comment
890 if (remainingText.mid(charsLeft-1, 2) == "//") {
891 os << remainingText.mid(0, charsLeft - 2);
892 os << "-";
893 remainingText = remainingText.mid(charsLeft - 2);
894 printedSoFar += charsLeft - 2;
895 }
896 else {
897 os << remainingText.mid(0, charsLeft - 1);
898 os << "-";
899 remainingText = remainingText.mid(charsLeft - 1);
900 printedSoFar += charsLeft - 1;
901 }
902 }
903
904 // we wrote as much as possible, do a newline and repeat
905 if (!remainingText.isEmpty()) {
906 os << format.formatEOL();
907 writeSpaces(os, startColumn);
908
909 // dont allow spaces to begin the next line inside what we're printing
910 if (remainingText[0] == ' ') {
911 remainingText = remainingText.mid(1);
912 printedSoFar += 1;
913 }
914 }
915
916 charsLeft = spaceForText;
917 }
918
919 return os;
920 }
921
922
929 void PvlKeyword::writeSpaces(std::ostream &os, int numSpaces) const {
930 for (int space = 0; space < numSpaces; space ++) {
931 os << " ";
932 }
933 }
934
935
943 m_formatter = formatter;
944 }
945
946
954 return m_formatter;
955 };
956
966 std::istream &operator>>(std::istream &is, PvlKeyword &result) {
967 result = PvlKeyword();
968 QString line;
969 QString keywordString;
970
971 bool keywordDone = false;
972 bool multiLineComment = false;
973 bool error = !is.good();
974
975 while(!error && !keywordDone) {
976 istream::pos_type beforeLine = is.tellg();
977
978 line = PvlKeyword::readLine(is, multiLineComment);
979
980 // We read an empty line (failed to read next non-empty line)
981 // and didnt complete our keyword, essentially we hit the implicit
982 // keyword named "End"
983 if (line.isEmpty() && !is.good()) {
984 if (keywordString.isEmpty() ||
985 keywordString[keywordString.size()-1] == '\n') {
986 line = "End";
987
988 if (multiLineComment) {
989 error = true;
990 }
991 }
992 else {
993 error = true;
994 }
995 }
996
997 bool comment = false;
998
999 if (!multiLineComment) {
1000 if (line.size() > 0 && line[0] == '#') {
1001 comment = true;
1002 }
1003
1004 if (line.size() > 1 && line[0] == '/' &&
1005 (line[1] == '*' || line[1] == '/')) {
1006 comment = true;
1007
1008 if (line[1] == '*') {
1009 multiLineComment = true;
1010 keywordString += line.mid(0, 2);
1011 line = line.mid(2).trimmed();
1012 }
1013 }
1014 }
1015
1016 if (multiLineComment) {
1017 comment = true;
1018
1019 if (line.contains("/*")) {
1020 IString msg = "Error when reading a pvl: Cannot have ['/*'] inside a "
1021 "multi-line comment";
1022 throw IException(IException::Unknown, msg, _FILEINFO_);
1023 }
1024
1025 if (line.contains("*/")) {
1026 multiLineComment = false;
1027
1028 line = line.mid(0, line.indexOf("*/")).trimmed() + " */";
1029 }
1030 }
1031
1032 if (line.isEmpty()) {
1033 continue;
1034 }
1035 // comment line
1036 else if (comment) {
1037 keywordString += line + '\n';
1038 continue;
1039 }
1040 // first line of keyword data
1041 else if (keywordString.isEmpty()) {
1042 keywordString = line;
1043 }
1044 // concatenation
1045 else if (!comment && keywordString[keywordString.size()-1] == '-') {
1046 keywordString = keywordString.mid(0, keywordString.size() - 1) + line;
1047 }
1048 // Non-commented and non-concatenation -> put in the space
1049 else {
1050 keywordString += " " + line;
1051 }
1052 // if this line concatenates with the next, read the next
1053 if (line[line.size()-1] == '-') {
1054 continue;
1055 }
1056
1057 std::vector<QString> keywordComments;
1058 QString keywordName;
1059 std::vector< std::pair<QString, QString> > keywordValues;
1060
1061 bool attemptedRead = false;
1062
1063 try {
1064 attemptedRead = PvlKeyword::readCleanKeyword(keywordString,
1065 keywordComments,
1066 keywordName,
1067 keywordValues);
1068 }
1069 catch (IException &e) {
1070 if (is.eof() && !is.bad()) {
1071 is.clear();
1072 is.unget();
1073 }
1074
1075 is.seekg(beforeLine, ios::beg);
1076
1077 QString msg = "Unable to read PVL keyword [";
1078 msg += keywordString;
1079 msg += "]";
1080
1081 throw IException(e, IException::Unknown, msg, _FILEINFO_);
1082 }
1083
1084 // Result valid?
1085 if (attemptedRead) {
1086 // if the next line starts with '<' then it should be read too...
1087 // it should be units
1088 // however, you can't have units if there is no value
1089 if (is.good() && is.peek() == '<' && !keywordValues.empty()) {
1090 continue;
1091 }
1092
1093 result.setName(keywordName);
1094 result.addComments(keywordComments);
1095
1096 for (unsigned int value = 0; value < keywordValues.size(); value++) {
1097 result.addValue(keywordValues[value].first,
1098 keywordValues[value].second);
1099 }
1100
1101 keywordDone = true;
1102 }
1103
1104 if (!attemptedRead) {
1105 error = error || !is.good();
1106 }
1107 // else we need to keep reading
1108 }
1109
1110 if (error) {
1111 // skip comments
1112 while(keywordString.contains('\n')) {
1113 keywordString = keywordString.mid(keywordString.indexOf('\n') + 1);
1114 }
1115
1116 QString msg;
1117
1118 if (keywordString.isEmpty() && !multiLineComment) {
1119 msg = "PVL input contains no Pvl Keywords";
1120 }
1121 else if (multiLineComment) {
1122 msg = "PVL input ends while still in a multi-line comment";
1123 }
1124 else {
1125 msg = "The PVL keyword [" + keywordString + "] does not appear to be";
1126 msg += " a valid Pvl Keyword";
1127 }
1128
1129 throw IException(IException::Unknown, msg, _FILEINFO_);
1130 }
1131
1132 if (!keywordDone) {
1133 // skip comments
1134 while(keywordString.contains('\n'))
1135 keywordString = keywordString.mid(keywordString.indexOf('\n') + 1);
1136
1137 QString msg;
1138
1139 if (keywordString.isEmpty()) {
1140 msg = "Error reading PVL keyword";
1141 }
1142 else {
1143 msg = "The PVL keyword [" + keywordString + "] does not appear to be";
1144 msg += " complete";
1145 }
1146
1147 throw IException(IException::Unknown, msg, _FILEINFO_);
1148 }
1149
1150 return is;
1151 }
1152
1159 void PvlKeyword::addComments(const std::vector<QString> &comments) {
1160 for (unsigned int i = 0; i < comments.size(); i++) {
1161 addComment(comments[i]);
1162 }
1163 }
1164
1180 bool PvlKeyword::readCleanKeyword(QString keyword,
1181 std::vector<QString> &keywordComments,
1182 QString &keywordName,
1183 std::vector< std::pair<QString, QString> > &keywordValues) {
1184 // Reset outputs
1185 keywordComments.clear();
1186 keywordName = "";
1187 keywordValues.clear();
1188
1189 // This is in case a close quote doesn't exist
1190 bool explicitIncomplete = false;
1191
1192 // Possible (known) comment starts in pvl
1193 QString comments[] = {
1194 "#",
1195 "//"
1196 };
1197
1198 // Need more data if nothing is here!
1199 if (keyword.isEmpty()) return 0;
1200
1201 /*
1202 Step 1: Read Comments
1203
1204 Theoretically, we should have an input that looks like this:
1205 #Comment
1206 //Comment
1207 / * Comment
1208 Comment * /
1209 Keyword = Data
1210
1211 So we could just grab all of the first lines; however, this method
1212 needs to be as error-proof as possible (it is the basis of reading
1213 all PVLs after all), so verify we have things that look like comments
1214 first, strip them & store them.
1215 */
1216
1217 // While we have newlines, we have comments
1218 while(keyword.contains("\n")) {
1219 // Make sure we strip data every loop of comment types; otherwise we
1220 // have no comment and need to error out.
1221 bool noneStripped = true;
1222
1223 // Check every comment type now and make sure this line (it isn't the last
1224 // line since a newline exists) starts in a comment
1225 QString keywordStart = keyword.mid(0, 2);
1226
1227 // Handle multi-line comments
1228 if (keywordStart == "/*") {
1229 noneStripped = false;
1230 bool inComment = true;
1231
1232 while(inComment && keyword.contains("*/")) {
1233 // Correct the */ to make sure it has a \n after it,
1234 // without this we risk an infinite loop
1235 int closePos = keyword.indexOf("*/\n");
1236
1237 if (closePos == -1) {
1238 closePos = keyword.indexOf("*/") + 2;
1239 keyword = keyword.mid(0, closePos) + "\n" +
1240 keyword.mid(closePos);
1241 }
1242
1243 QString comment = keyword.mid(0, keyword.indexOf("\n")).trimmed();
1244
1245 // Set these to true if too small, false if not (they need if
1246 // cant currently fit).
1247 bool needsStart = (comment.size() < 2);
1248 bool needsStartSpace = comment.size() < 3;
1249
1250 int commentEndPos = comment.size() - 2;
1251 bool needsEnd = (commentEndPos < 0);
1252 bool needsEndSpace = comment.size() < 3;
1253
1254 // Needs are currently set based on QString size, apply real logic
1255 // to test for character sequences (try to convert them from false
1256 // to true).
1257 if (!needsStart) {
1258 needsStart = (comment.mid(0, 2) != "/*");
1259 }
1260
1261 if (!needsEnd) {
1262 needsEnd = (comment.mid(commentEndPos, 2) != "*/");
1263 }
1264
1265 if (!needsStartSpace) {
1266 needsStartSpace = (comment.mid(0, 3) != "/* ");
1267 }
1268
1269 if (!needsEndSpace) {
1270 needsEndSpace = (comment.mid(commentEndPos - 1, 3) != " */");
1271 }
1272
1273 if (needsStart) {
1274 comment = "/* " + comment;
1275 }
1276 else if (needsStartSpace) {
1277 comment = "/* " + comment.mid(2);
1278 }
1279
1280 if (needsEnd) {
1281 comment = comment + " */";
1282 }
1283 else if (needsEndSpace) {
1284 comment = comment.mid(0, comment.size() - 2) + " */";;
1285 }
1286
1287 inComment = needsEnd;
1288
1289 keywordComments.push_back(comment);
1290
1291 if (keyword.contains("\n")) {
1292 keyword = keyword.mid(keyword.indexOf("\n") + 1).trimmed();
1293 }
1294
1295 // Check for another comment start
1296 if (!inComment) {
1297 if (keyword.size() >= 2)
1298 keywordStart = keyword.mid(0, 2);
1299
1300 inComment = (keywordStart == "/*");
1301 }
1302 }
1303
1304 // So we have a bunch of multi-line commands... make them the same size
1305 // Find longest
1306 int longest = 0;
1307 for (unsigned int index = 0; index < keywordComments.size(); index++) {
1308 QString comment = keywordComments[index];
1309
1310 if (comment.size() > longest)
1311 longest = comment.size();
1312 }
1313
1314 // Now make all the sizes match longest
1315 for (unsigned int index = 0; index < keywordComments.size(); index++) {
1316 QString comment = keywordComments[index];
1317
1318 while(comment.size() < longest) {
1319 // This adds a space to the end of the comment
1320 comment = comment.mid(0, comment.size() - 2) + " */";
1321 }
1322
1323 keywordComments[index] = comment;
1324 }
1325 // They should all be the same length now
1326 }
1327
1328 // Search for single line comments
1329 for (unsigned int commentType = 0;
1330 commentType < sizeof(comments) / sizeof(QString);
1331 commentType++) {
1332
1333 if (keywordStart.startsWith(comments[commentType])) {
1334 // Found a comment start; strip this line out and store it as a
1335 // comment!
1336 QString comment = keyword.mid(0, keyword.indexOf("\n"));
1337 keywordComments.push_back(comment.trimmed());
1338
1339 noneStripped = false;
1340
1341 if (keyword.contains("\n")) {
1342 keyword = keyword.mid(keyword.indexOf("\n") + 1).trimmed();
1343 }
1344 }
1345 }
1346
1347 // Does it look like Name=Value/*comm
1348 // mment*/ ?
1349 if (noneStripped && keyword.contains("/*") &&
1350 keyword.contains("*/")) {
1351 QString firstPart = keyword.mid(0, keyword.indexOf("\n"));
1352 QString lastPart = keyword.mid(keyword.indexOf("\n") + 1);
1353
1354 keyword = firstPart.trimmed() + " " + lastPart.trimmed();
1355 noneStripped = false;
1356 }
1357
1358 if (noneStripped) {
1359 QString msg = "Expected a comment in PVL but found [";
1360 msg += keyword;
1361 msg += "]";
1362 throw IException(IException::Unknown, msg, _FILEINFO_);
1363 }
1364 }
1365
1366 // Do we have a keyword at all?
1367 if (keyword.isEmpty()) {
1368 return false; // need more data
1369 }
1370
1371 /*
1372 Step 2: Determine Keyword Format
1373
1374 Make sure we have a keyword after the comments first. We expect
1375 one of three formats:
1376 KEYWORD PROCESSED IN STEP 3.1
1377 KEYWORD = (VALUE,VALUE,...) PROCESSED IN STEP 3.2
1378 KEYWORD = VALUE PROCESSED IN STEP 3.3
1379 */
1380
1381 // Get the keyword name
1382 keywordName = readValue(keyword, explicitIncomplete);
1383
1384 // we have taken the name; if nothing remains then it is value-less
1385 // and we are done.
1386 if (keyword.isEmpty()) {
1387 /*
1388 Step 3.1
1389
1390 Format is determined to be:
1391 KEYWORD
1392
1393 Well, no value/units may exist so we're done processing this keyword.
1394 */
1395 return 1; // Valid & Successful
1396 }
1397
1398 // if we don't have an equal, then we have a problem - an invalid symbol.
1399 // Our possible remaining formats both are KEYWORD = ...
1400 if (keyword[0] != '=') {
1401 QString msg = "Expected an assignment [=] when reading PVL, but found [";
1402 msg += keyword[0];
1403 msg += "]";
1404
1405 throw IException(IException::Unknown, msg, _FILEINFO_);
1406 }
1407
1408 keyword = keyword.mid(1).trimmed();
1409
1410 if (keyword.isEmpty()) {
1411 return false;
1412 }
1413
1414 // now we need to split into two possibilities: array or non-array
1415 if (keyword[0] == '(' || keyword[0] == '{') {
1416 /*
1417 Step 3.2
1418
1419 Our format is confirmed as:
1420 KEYWORD = (...)
1421
1422 We need to read each value/unit in the array.
1423 */
1424
1425 char closingParen = ((keyword[0] == '(') ? ')' : '}');
1426 char wrongClosingParen = ((keyword[0] == '(') ? '}' : ')');
1427 bool closedProperly = false;
1428
1429 vector< pair<char, char> > extraDelims;
1430 extraDelims.push_back(pair<char, char>('(', ')'));
1431 extraDelims.push_back(pair<char, char>('{', '}'));
1432
1433 // strip '(' - onetime, this makes every element in the array the same
1434 // (except the last)
1435 keyword = keyword.mid(1).trimmed();
1436
1437 // handle empty arrays: KEYWORD = ()
1438 if (!keyword.isEmpty() && keyword[0] == closingParen) {
1439 closedProperly = true;
1440 }
1441
1442 // Each iteration of this loop should consume 1 value in the array,
1443 // including the comma, i.e. we should start out with:
1444 // 'VALUE,VALUE,...)' until we hit ')' (our exit condition)
1445 while(!keyword.isEmpty() && keyword[0] != closingParen) {
1446 // foundComma delimits the end of this element in the array (remains
1447 // false for last value which has no comma at the end)
1448 bool foundComma = false;
1449 // keyword should be of the format: VALUE <UNIT>,....)
1450 // Read VALUE from it
1451 QString nextItem = readValue(keyword, explicitIncomplete, extraDelims);
1452
1453 if (!keyword.isEmpty() && keyword[0] == wrongClosingParen) {
1454
1455 QString msg = "Incorrect array close when reading PVL; expected [";
1456 msg += closingParen;
1457 msg += "] but found [";
1458 msg += wrongClosingParen;
1459 msg += "] in keyword named [";
1460 msg += keywordName;
1461 msg += "]";
1462 throw IException(IException::Unknown, msg, _FILEINFO_);
1463 }
1464
1465 // This contains <VALUE, UNIT>
1466 pair<QString, QString> keywordValue;
1467
1468 // Store VALUE
1469 keywordValue.first = nextItem;
1470
1471 // Now there's 2 possibilities: units or no units ->
1472 // if we have units, read them
1473 if (!keyword.isEmpty() && keyword[0] == '<') {
1474 QString unitsValue = readValue(keyword, explicitIncomplete);
1475 keywordValue.second = unitsValue;
1476 }
1477
1478 // Now we should* have a comma, strip it
1479 if (!keyword.isEmpty() && keyword[0] == ',') {
1480 foundComma = true;
1481 keyword = keyword.mid(1).trimmed();
1482 }
1483
1484 // No comma and nothing more in QString - we found
1485 // KEYWORD = (VALUE,VALUE\0
1486 // we need more information to finish this keyword
1487 if (!foundComma && keyword.isEmpty()) {
1488 return false; // could become valid later
1489 }
1490
1491 bool foundCloseParen = (!keyword.isEmpty() && keyword[0] == closingParen);
1492
1493 if (foundCloseParen) {
1494 closedProperly = true;
1495 }
1496
1497 // Check for the condition of:
1498 // keyword = (VALUE,VALUE,)
1499 // which is unrecoverable
1500 if (foundComma && foundCloseParen) {
1501 QString msg = "Unexpected close of keyword-value array when reading "
1502 "PVL";
1503 throw IException(IException::Unknown, msg, _FILEINFO_);
1504 }
1505
1506 // Check for (VALUE VALUE
1507 if (!foundComma && !foundCloseParen) {
1508 // We have ("VALUE VALUE
1509 if (explicitIncomplete) return false;
1510
1511 // We have (VALUE VALUE
1512 QString msg = "Found extra data after [";
1513 msg += nextItem;
1514 msg += "] in array when reading PVL";
1515 throw IException(IException::Unknown, msg, _FILEINFO_);
1516 }
1517
1518 // we're good with this element of the array, remember it
1519 keywordValues.push_back(keywordValue);
1520 }
1521
1522 if (!closedProperly) {
1523 return false;
1524 }
1525
1526 // Trim off the closing paren
1527 if (!keyword.isEmpty()) {
1528 keyword = keyword.mid(1).trimmed();
1529 }
1530
1531 // Read units here if they exist and apply them
1532 // case: (A,B,C) <unit>
1533 if (!keyword.isEmpty() && keyword[0] == '<') {
1534 QString units = readValue(keyword, explicitIncomplete);
1535 for (unsigned int val = 0; val < keywordValues.size(); val++) {
1536 if (keywordValues[val].second.isEmpty()) {
1537 keywordValues[val].second = units;
1538 }
1539 }
1540 }
1541 }
1542 else {
1543 /*
1544 Step 3.3
1545
1546 Our format is confirmed as:
1547 "KEYWORD = VALUE <UNIT>"
1548
1549 We need to read the single value/unit in the keywordValues array.
1550 */
1551 pair<QString, QString> keywordValue;
1552 keywordValue.first = readValue(keyword, explicitIncomplete);
1553
1554 if (!keyword.isEmpty() && keyword[0] == '<') {
1555 keywordValue.second = readValue(keyword, explicitIncomplete);
1556 }
1557
1558 keywordValues.push_back(keywordValue);
1559 }
1560
1561 /*
1562 This is set when a quote is opened somewhere and never closed, it means
1563 we need more information
1564 */
1565 if (explicitIncomplete) {
1566 return false; // unclosed quote at end... need more information
1567 }
1568
1569 /*
1570 See if we have a comment at the end of the keyword...
1571 */
1572 if (!keyword.isEmpty()) {
1573 // if the data left is a comment, we can handle it probably
1574 if (keyword[0] == '#' ||
1575 ((keyword.size() > 1 && keyword[0] == '/') &&
1576 (keyword[1] == '/' || keyword[1] == '*'))) {
1577 keywordComments.push_back(keyword);
1578
1579 if (keyword.size() > 1 && keyword.mid(0, 2) == "/*") {
1580 if (keyword.mid(keyword.size() - 2, 2) != "*/")
1581 return false; // need more comment data
1582 }
1583
1584 keyword = "";
1585 }
1586 }
1587
1588 // check for extraneous data in the keyword ... ,2,3
1589 QRegularExpression regex("^,");
1590 QRegularExpressionMatch match = regex.match(keyword);
1591 if (!keyword.isEmpty() && match.hasMatch()) {
1592 keywordValues.at(0).first += keyword;
1593 keyword = "";
1594 }
1595
1596 /*
1597 If more data remains, it is unrecognized.
1598 */
1599 if (!keyword.isEmpty()) {
1600 QString msg = "Keyword has extraneous data [";
1601 msg += keyword;
1602 msg += "] at the end";
1603 throw IException(IException::Unknown, msg, _FILEINFO_);
1604 }
1605
1606 // We've parsed this keyword! :)
1607 return true;
1608 }
1609
1610
1611 QString PvlKeyword::readValue(QString &keyword, bool &quoteProblem) {
1612 std::vector< std::pair<char, char> > otherDelims;
1613
1614 return readValue(keyword, quoteProblem, otherDelims);
1615 }
1616
1633 QString PvlKeyword::readValue(QString &keyword, bool &quoteProblem,
1634 const std::vector< std::pair<char, char> > &
1635 otherDelimiters) {
1636 QString value = "";
1637
1638 // This method ignores spaces except as delimiters; let's trim the QString
1639 // to start
1640 keyword = keyword.trimmed();
1641
1642 if (keyword.isEmpty()) {
1643 return "";
1644 }
1645
1646 // An implied quote is one that is started without a special character, for
1647 // example HELLO WORLD has HELLO and WORLD separately as implied quotes for
1648 // PVLs. However, "HELLO WORLD" has an explicit (not implied) double quote.
1649 // We do consider <> as quotes.
1650 bool impliedQuote = true;
1651 QChar quoteEnd = ' ';
1652 bool keepQuotes = false;
1653
1654 if (keyword[0] == '\'' || keyword[0] == '"') {
1655 quoteEnd = keyword[0];
1656 impliedQuote = false;
1657 }
1658 else if (keyword[0] == '<') {
1659 quoteEnd = '>';
1660 impliedQuote = false;
1661 }
1662 else {
1663 // we're not quoted, look for alternative delimiters.
1664 char implicitQuotes[] = {
1665 ')',
1666 '}',
1667 ',',
1668 ' ',
1669 '\t',
1670 '<',
1671 '='
1672 };
1673
1674 bool foundImplicitQuote = false;
1675
1676 int currentPos = 0;
1677 while(!foundImplicitQuote && currentPos != keyword.size()) {
1678 for (unsigned int quote = 0;
1679 quote < sizeof(implicitQuotes) / sizeof(char);
1680 quote ++) {
1681 if (keyword[currentPos] == implicitQuotes[quote]) {
1682 quoteEnd = implicitQuotes[quote];
1683 foundImplicitQuote = true;
1684 }
1685 }
1686
1687 if (!foundImplicitQuote) {
1688 currentPos ++;
1689 }
1690 }
1691 }
1692
1693 for (unsigned int delim = 0; delim < otherDelimiters.size(); delim ++) {
1694 if (keyword[0] == otherDelimiters[delim].first) {
1695 quoteEnd = otherDelimiters[delim].second;
1696 keepQuotes = true;
1697 impliedQuote = false;
1698 }
1699 }
1700
1701 QString startQuote;
1702 // non-implied delimeters need the opening delimiter ignored. Remember
1703 // startQuote in case of error later on we can reconstruct the original
1704 // QString.
1705 if (!impliedQuote) {
1706 startQuote += keyword[0];
1707 keyword = keyword.mid(1);
1708 }
1709
1710 // Do we have a known quote end?
1711 int quoteEndPos = keyword.indexOf(quoteEnd);
1712
1713 if (quoteEndPos != -1) {
1714 value = keyword.mid(0, quoteEndPos);
1715
1716 // Trim keyword 1 past end delimiter (if end delimiter is last, this
1717 // results in empty QString). If the close delimiter is ')' or ',' then
1718 // leave it be however, since we need to preserve that to denote this was
1719 // an end of a valuein array keyword.
1720 if (!impliedQuote) {
1721 keyword = keyword.mid(quoteEndPos + 1);
1722 }
1723 else {
1724 keyword = keyword.mid(quoteEndPos);
1725 }
1726
1727 // Make sure we dont have padding
1728 keyword = keyword.trimmed();
1729
1730 if (keepQuotes) {
1731 value = startQuote + value + quoteEnd;
1732 }
1733
1734 return value;
1735 }
1736 // implied quotes terminate at end of keyword; otherwise we have a problem
1737 // (which is this condition)
1738 else if (!impliedQuote) {
1739 // restore the original QString
1740 keyword = startQuote + keyword;
1741 quoteProblem = true;
1742
1743 return "";
1744 }
1745
1746 // we have an implied quote but no quote character, the rest must be the
1747 // value.
1748 value = keyword;
1749 keyword = "";
1750
1751 return value;
1752 }
1753
1754
1768 QString PvlKeyword::readLine(std::istream &is, bool insideComment) {
1769 QString lineOfData;
1770
1771 while(is.good() && lineOfData.isEmpty()) {
1772
1773 // read until \n (works for both \r\n and \n) or */
1774 while(is.good() &&
1775 (!lineOfData.size() || lineOfData[lineOfData.size() - 1] != '\n')) {
1776 char next = is.get();
1777
1778 // if non-ascii found then we're done... immediately
1779 if (next <= 0) {
1780 is.seekg(0, ios::end);
1781 is.get();
1782 return lineOfData;
1783 }
1784
1785 // if any errors (i.e. eof) happen in the get operation then don't
1786 // store this data
1787 if (is.good()) {
1788 lineOfData += next;
1789 }
1790
1791 if (insideComment &&
1792 lineOfData.size() >= 2 && lineOfData[lineOfData.size() - 2] == '*' &&
1793 lineOfData[lineOfData.size() - 1] == '/') {
1794 // End of multi-line comment = end of line!
1795 break;
1796 }
1797 else if (lineOfData.size() >= 2 &&
1798 lineOfData[lineOfData.size() - 2] == '/' &&
1799 lineOfData[lineOfData.size() - 1] == '*') {
1800 insideComment = true;
1801 }
1802 }
1803
1804 // Trim off non-visible characters from this line of data
1805 lineOfData = lineOfData.trimmed();
1806
1807 // read up to next non-whitespace in input stream
1808 while(is.good() &&
1809 (is.peek() == ' ' ||
1810 is.peek() == '\r' ||
1811 is.peek() == '\n')) {
1812 is.get();
1813 }
1814
1815 // if lineOfData is empty (line was empty), we repeat
1816 }
1817
1818 return lineOfData;
1819 }
1820
1821
1830 ostream &operator<<(std::ostream &os, const Isis::PvlKeyword &keyword) {
1831 // Set up a Formatter
1832 PvlFormat *tempFormat = keyword.m_formatter;
1833 bool removeFormatter = false;
1834 if (tempFormat == NULL) {
1835 tempFormat = new PvlFormat();
1836 removeFormatter = true;
1837 }
1838
1839 // Write out the comments
1840 for (int i = 0; i < keyword.comments(); i++) {
1841 for (int j = 0; j < keyword.indent(); j++) os << " ";
1842 os << keyword.comment(i) << tempFormat->formatEOL();
1843 }
1844
1845 // Write the keyword name & add length to startColumn.
1846 int startColumn = 0;
1847 for (int i = 0; i < keyword.indent(); i++) {
1848 os << " ";
1849 ++startColumn;
1850 }
1851 QString keyname = tempFormat->formatName(keyword);
1852 os << keyname;
1853 startColumn += keyname.length();
1854
1855 // Add padding and then write equal sign.
1856 for (int i = 0; i < keyword.width() - (int)keyname.size(); ++i) {
1857 os << " ";
1858 ++startColumn;
1859 }
1860 os << " = ";
1861 startColumn += 3;
1862
1863 // If it has no value then write a NULL
1864 if (keyword.size() == 0) {
1865 os << tempFormat->formatValue(keyword);
1866 }
1867
1868 // Loop and write each array value
1869 QString stringToWrite;
1870 for (int i = 0; i < keyword.size(); i ++) {
1871 stringToWrite += tempFormat->formatValue(keyword, i);
1872 }
1873
1874 keyword.writeWithWrap(os,
1875 stringToWrite,
1876 startColumn,
1877 *tempFormat);
1878
1879 if (removeFormatter) delete tempFormat;
1880
1881 return os;
1882 }
1883
1884
1887 if (this != &other) {
1888 m_formatter = other.m_formatter;
1889
1890 if (m_name) {
1891 delete [] m_name;
1892 m_name = NULL;
1893 }
1894
1895 if (other.m_name) {
1896 setName(other.m_name);
1897 }
1898
1899 m_values = other.m_values;
1900
1901 if (m_units) {
1902 delete m_units;
1903 m_units = NULL;
1904 }
1905
1906 if (other.m_units) {
1907 m_units = new std::vector<QString>(*other.m_units);
1908 }
1909
1910 if (m_comments) {
1911 delete m_comments;
1912 m_comments = NULL;
1913 }
1914
1915 if (other.m_comments) {
1916 m_comments = new std::vector<QString>(*other.m_comments);
1917 }
1918
1919 m_width = other.m_width;
1920 m_indent = other.m_indent;
1921 }
1922
1923 return *this;
1924 }
1925
1938 void PvlKeyword::validateKeyword(PvlKeyword & pvlKwrd, QString psValueType, PvlKeyword* pvlKwrdValue)
1939 {
1940 int iSize = pvlKwrd.size();
1941
1942 QString sType = m_values[0].toLower();
1943
1944 // Value type
1945 if (psValueType.length()) {
1946 psValueType = psValueType.toLower();
1947 }
1948
1949 double dRangeMin=0, dRangeMax=0;
1950 bool bRange = false;
1951 bool bValue = false;
1952 if (pvlKwrdValue != NULL) {
1953 QString sValueName = pvlKwrdValue->name();
1954
1955 // Check for Range
1956 if (sValueName.contains("__Range")) {
1957 dRangeMin = toDouble((*pvlKwrdValue)[0]);
1958 dRangeMax = toDouble((*pvlKwrdValue)[1]);
1959 bRange = true;
1960 }
1961 else if (sValueName.contains("__Value")) {
1962 bValue = true;
1963 }
1964 }
1965
1966 // Type integer
1967 if (sType == "integer") {
1968 for (int i=0; i<iSize; i++) {
1969 QString sValue = pvlKwrd[i].toLower();
1970 if (sValue != "null"){
1971 int iValue=0;
1972 try {
1973 iValue = toInt(sValue);
1974 } catch (IException & e) {
1975 QString sErrMsg = "\"" +pvlKwrd.name() +"\" expects an Integer value";
1976 throw IException(e, IException::User, sErrMsg, _FILEINFO_);
1977 }
1978 if (bRange && (iValue < dRangeMin || iValue > dRangeMax)) {
1979 QString sErrMsg = "\"" +pvlKwrd.name() +"\" is not in the specified Range";
1980 throw IException(IException::User, sErrMsg, _FILEINFO_);
1981 }
1982 if (bValue) {
1983 bool bFound = false;
1984 for (int j=0; j<pvlKwrdValue->size(); j++) {
1985 if (iValue == toInt((*pvlKwrdValue)[j])) {
1986 bFound = true;
1987 break;
1988 }
1989 }
1990 if (!bFound) {
1991 QString sErrMsg = "\"" +pvlKwrd.name() +"\" has value not in the accepted list";
1992 throw IException(IException::User, sErrMsg, _FILEINFO_);
1993 }
1994 }
1995 // Type is specified (positive / negative)
1996 if (psValueType.length()) {
1997 if ((psValueType == "positive" && iValue < 0) || (psValueType == "negative" && iValue >= 0) ) {
1998 QString sErrMsg = "\"" +pvlKwrd.name() +"\" has invalid value";
1999 throw IException(IException::User, sErrMsg, _FILEINFO_);
2000 }
2001 }
2002 }
2003 }
2004 return;
2005 }
2006
2007 // Type double
2008 if (sType == "double") {
2009 for (int i=0; i<iSize; i++) {
2010 QString sValue = pvlKwrd[i].toLower();
2011 if (sValue != "null"){
2012 double dValue = toDouble(sValue);
2013 if (bRange && (dValue < dRangeMin || dValue > dRangeMax)) {
2014 QString sErrMsg = "\"" +pvlKwrd.name() +"\" is not in the specified Range";
2015 throw IException(IException::User, sErrMsg, _FILEINFO_);
2016 }
2017 if (bValue) {
2018 bool bFound = false;
2019 for (int j=0; j<pvlKwrdValue->size(); j++) {
2020 if (dValue == toDouble((*pvlKwrdValue)[j])) {
2021 bFound = true;
2022 break;
2023 }
2024 }
2025 if (!bFound) {
2026 QString sErrMsg = "\"" +pvlKwrd.name() +"\" has value not in the accepted list";
2027 throw IException(IException::User, sErrMsg, _FILEINFO_);
2028 }
2029 }
2030 // Type is specified (positive / negative)
2031 if (psValueType.length()) {
2032 if ((psValueType == "positive" && dValue < 0) || (psValueType == "negative" && dValue >= 0) ) {
2033 QString sErrMsg = "\"" +pvlKwrd.name() +"\" has invalid value";
2034 throw IException(IException::User, sErrMsg, _FILEINFO_);
2035 }
2036 }
2037 }
2038 }
2039 return;
2040 }
2041
2042 // Type boolean
2043 if (sType == "boolean") {
2044 for (int i=0; i<iSize; i++) {
2045 QString sValue = pvlKwrd[i].toLower();
2046 if (sValue != "null" && sValue != "true" && sValue != "false"){
2047 QString sErrMsg = "Wrong Type of value in the Keyword \"" + name() + "\" \n";
2048 throw IException(IException::User, sErrMsg, _FILEINFO_);
2049 }
2050 }
2051 return;
2052 }
2053
2054 // Type String
2055 if (sType == "string") {
2056 for (int i=0; i<iSize; i++) {
2057 QString sValue = pvlKwrd[i].toLower();
2058 if (bValue) {
2059 bool bValFound = false;
2060 for (int i=0; i<pvlKwrdValue->size(); i++) {
2061 if (sValue == (*pvlKwrdValue)[i].toLower()) {
2062 bValFound = true;
2063 break;
2064 }
2065 }
2066 if (!bValFound) {
2067 QString sErrMsg = "Wrong Type of value in the Keyword \"" + name() + "\" \n";
2068 throw IException(IException::User, sErrMsg, _FILEINFO_);
2069 }
2070 }
2071 }
2072 }
2073 }
2074}
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 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
Formats a Pvl name value pair to Isis standards.
Definition PvlFormat.h:108
unsigned int charLimit() const
Retrieves the maximum number of characters in a keyword value that can be printed to a line before it...
Definition PvlFormat.h:138
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:282
QVarLengthArray< QString, 1 > m_values
The values in the keyword.
Definition PvlKeyword.h:276
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:216
QString name() const
Returns the keyword name.
Definition PvlKeyword.h:103
int size() const
Returns the number of values stored in this keyword.
Definition PvlKeyword.h:133
PvlKeyword & operator+=(QString value)
Adds a value.
std::vector< QString > * m_units
The units for the values.
Definition PvlKeyword.h:279
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:266
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:168
QString comment(const int index) const
Return a comment at the specified index.
PvlFormat * m_formatter
Formatter object.
Definition PvlKeyword.h:262
int indent() const
Returns the current indent level.
Definition PvlKeyword.h:221
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:292
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:297
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
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.