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