Unverified Commit 854a7961 authored by acpaquette's avatar acpaquette Committed by GitHub
Browse files

PvlKeyword from JSON (#5030)

* Json reading fixes

* Addressed PR feedback

* Converted PvlKeyword unittest to gtest

* Added new tests for addJsonValue and setJsonValue
parent c970b543
Loading
Loading
Loading
Loading
+60 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ find files of those names at the top level of this repository. **/
#include "PvlSequence.h"

using namespace std;
using json = nlohmann::json;
namespace Isis {
  //! Constructs a blank PvlKeyword object.
  PvlKeyword::PvlKeyword() {
@@ -157,6 +158,24 @@ namespace Isis {
    addValue(value, unit);
  }

  /**
   * Sets new value from Json.
   *
   * If no current value exists, this method sets the given json value
   * to the PvlKeyword.  Otherwise, it clears any existing values
   * and resets to the value given using addJsonValue(). Defaults to
   * unit = "" (empty QString).
   *
   * @param jsonobj New jsobobj to be parsed and assigned.
   * @param unit Units of measurement corresponding to the value.
   *
   * @see addJsonValue()
   */
  void PvlKeyword::setJsonValue(json jsonobj, QString unit)
  {
    clear();
    addJsonValue(jsonobj, unit);
  }

  /**
   * Sets the unit of measure for all current values if any exist
@@ -267,6 +286,47 @@ namespace Isis {
    }
  }

  /**
   * Adds a value with units.
   *
   * If no current value exists, this method sets the given json value.
   * Otherwise, it retains any current values and adds the json value
   * given to the array of values for this PvlKeyword object using addValue.
   * Defaults to unit = "" (empty QString).
   *
   * @param jsonobj New jsonobj to be parsed and assigned.
   * @param unit Units of measurement corresponding to the value.
   *
   * @see setJsonValue()
   * @see addValue()
   *
   * @throws Isis::iException::Unknown - jsonobj cannot be an array of values
   */
  void PvlKeyword::addJsonValue(json jsonobj, QString unit) {
    QString value;
    if (jsonobj.is_array()) {
      QString msg = "Unable to convert " + name() + " with nested json array value into PvlKeyword";
      throw IException(IException::Unknown, msg, _FILEINFO_);
    }
    else if (jsonobj.is_number())
    {
      value = QString::number(jsonobj.get<double>(), 'g', 16);
    }
    else if (jsonobj.is_boolean())
    {
      value = QString(jsonobj.get<bool>() ? "true" : "false");
    }
    else if (jsonobj.is_null())
    {
      value = QString("Null");
    }
    else
    {
      value = QString::fromStdString(jsonobj);
    }
    addValue(value, unit);
  }

  /**
   * Adds a value.
   *
+8 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@ find files of those names at the top level of this repository. **/
#include "Constants.h"
#include "IString.h"

#include <nlohmann/json.hpp>

namespace Isis {
  class PvlSequence;
  class PvlFormat;
@@ -78,6 +80,9 @@ namespace Isis {
   *                          the QString "Yes" instead of the keyword name. Added padding on
   *                          control statements to bring the code closer to ISIS Coding Standards.
   *                          References #1659.
   * 
   *  @history 2022-08-15 Adam Paquette - Added the ability to add and set PvlKeyword values from JSON
   *                          keyword value pairs.
   */
  class PvlKeyword {
    public:
@@ -112,6 +117,7 @@ namespace Isis {
      };

      void setValue(QString value, QString unit = "");
      void setJsonValue(nlohmann::json jsonobj, QString unit = "");

      void setUnits(QString units);
      void setUnits(QString value, QString units);
@@ -119,6 +125,8 @@ namespace Isis {
      PvlKeyword &operator=(QString value);

      void addValue(QString value, QString unit = "");
      void addJsonValue(nlohmann::json jsonobj, QString unit = "");

      PvlKeyword &operator+=(QString value);

      //! Returns the number of values stored in this keyword
+0 −632
Original line number Diff line number Diff line


----- Testing Basic Read/Write -----
'KEYWORD' -----------------------> VALID
    NAME: KEYWORD
'KEYWORD X' ---------------------> INVALID
    **ERROR** Expected an assignment [=] when reading PVL, but found [X].
'KEYWORD =' ---------------------> INCOMPLETE
'KEYWORD = SOME_VAL' ------------> VALID
    NAME: KEYWORD
    VALUE: SOME_VAL
'KEYWORD = "  val  "' -----------> VALID
    NAME: KEYWORD
    VALUE:   val  
'KEYWORD = " 'val' "' -----------> VALID
    NAME: KEYWORD
    VALUE:  'val' 
'KEYWORD = (VAL' ----------------> INCOMPLETE
'KEYWORD = (VAL1,VAL2' ----------> INCOMPLETE
'KEYWORD = (A B,C,D)' -----------> INVALID
    **ERROR** Found extra data after [A] in array when reading PVL.
'KEYWORD = ((A B),(C),(D' -------> INCOMPLETE
'KEYWORD = (SOME_VAL)' ----------> VALID
    NAME: KEYWORD
    VALUE: SOME_VAL
'KEYWORD = (SOME_VAL) <a>' ------> VALID
    NAME: KEYWORD
    VALUE: SOME_VAL <a>
'KEYWORD=(SOME_VAL)<a>' ---------> VALID
    NAME: KEYWORD
    VALUE: SOME_VAL <a>
'KEYWORD = (A, )' ---------------> INVALID
    **ERROR** Unexpected close of keyword-value array when reading PVL.
'KEYWORD = ()' ------------------> VALID
    NAME: KEYWORD
'KEYWORD = (A,B)' ---------------> VALID
    NAME: KEYWORD
    VALUE: A
    VALUE: B
'KEYWORD = {A, B}' --------------> VALID
    NAME: KEYWORD
    VALUE: A
    VALUE: B
'KEYWORD = (A,B) #comment this' -> VALID
    COMMENT: #comment this
    NAME: KEYWORD
    VALUE: A
    VALUE: B
'KEYWORD = ( A , B )' -----------> VALID
    NAME: KEYWORD
    VALUE: A
    VALUE: B
'KEYWORD	=	( A	,	B )' -----------> VALID
    NAME: KEYWORD
    VALUE: A
    VALUE: B
'KEYWORD = (A, B,C,D,E))' -------> INVALID
    **ERROR** Keyword has extraneous data [)] at the end.
'KEYWORD = ((1, 2), {3,  4}, (5), 6)' > VALID
    NAME: KEYWORD
    VALUE: (1, 2)
    VALUE: {3,  4}
    VALUE: (5)
    VALUE: 6
'KEYWORD = { "VAL1" ,   "VAL2", "VAL3"}' > VALID
    NAME: KEYWORD
    VALUE: VAL1
    VALUE: VAL2
    VALUE: VAL3
'KEYWORD = { "VAL1" , "VAL2", "VAL3")' > INVALID
    **ERROR** Incorrect array close when reading PVL; expected [}] but found [)] in keyword named [KEYWORD].
'KEYWORD = { "VAL1" ,' ----------> INCOMPLETE
'KEYWORD = "(A,B,"' -------------> VALID
    NAME: KEYWORD
    VALUE: (A,B,
'KEYWORD = ',E)'' ---------------> VALID
    NAME: KEYWORD
    VALUE: ,E)
'KEYWORD = ((1,2))' -------------> VALID
    NAME: KEYWORD
    VALUE: (1,2)
'KEYWORD = ("(f1+f2)","/(f1-f2)")' > VALID
    NAME: KEYWORD
    VALUE: (f1+f2)
    VALUE: /(f1-f2)
'KEYWORD = "(F1+F2)/(F1-F2)"' ---> VALID
    NAME: KEYWORD
    VALUE: (F1+F2)/(F1-F2)
'KEYWORD = ( (1,2)  , (A,B) )' --> VALID
    NAME: KEYWORD
    VALUE: (1,2)
    VALUE: (A,B)
'KEYWORD = "(f1 + min(f2,f3))"' -> VALID
    NAME: KEYWORD
    VALUE: (f1 + min(f2,f3))
'KEYWORD = "(min(f2,f3) + f1)"' -> VALID
    NAME: KEYWORD
    VALUE: (min(f2,f3) + f1)
'KEYWORD = "min(f2,f3) + f1"' ---> VALID
    NAME: KEYWORD
    VALUE: min(f2,f3) + f1
'KEYWORD = "f1 + min(f2,f3)"' ---> VALID
    NAME: KEYWORD
    VALUE: f1 + min(f2,f3)
'KEYWORD = (A <a>, B <b>, C, D <d>)' > VALID
    NAME: KEYWORD
    VALUE: A <a>
    VALUE: B <b>
    VALUE: C
    VALUE: D <d>
'KEYWORD = (A <a>, B <b>, C, D <d>) <e>' > VALID
    NAME: KEYWORD
    VALUE: A <a>
    VALUE: B <b>
    VALUE: C <e>
    VALUE: D <d>
'KEYWORD = ',E) <unit>' ---------> INCOMPLETE
'KEYWORD = ,E) <unit>' ----------> INVALID
    **ERROR** Keyword has extraneous data [,E) <unit>] at the end.
#SOMECOMMENT
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: #SOMECOMMENT
    NAME: KEYWORD
    VALUE: SOME_VAL
#SOMECOMMENT1
#SOMECOMMENT2
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: #SOMECOMMENT1
    COMMENT: #SOMECOMMENT2
    NAME: KEYWORD
    VALUE: SOME_VAL
//SOMECOMMENT1
#SOMECOMMENT2
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: //SOMECOMMENT1
    COMMENT: #SOMECOMMENT2
    NAME: KEYWORD
    VALUE: SOME_VAL
/*SOMECOMMENT1*/
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: /* SOMECOMMENT1 */
    NAME: KEYWORD
    VALUE: SOME_VAL
KEYWORD = '/*
'*/'' ---------------------------> VALID
    NAME: KEYWORD
    VALUE: /* */
/* SOMECOMMENT1
  SOMECOMMENT2
SOMECOMMENT3 */
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: /* SOMECOMMENT1 */
    COMMENT: /* SOMECOMMENT2 */
    COMMENT: /* SOMECOMMENT3 */
    NAME: KEYWORD
    VALUE: SOME_VAL
/*C1

A
/*
C3*/
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: /* C1 */
    COMMENT: /* A  */
    COMMENT: /*    */
    COMMENT: /* C3 */
    NAME: KEYWORD
    VALUE: SOME_VAL
/*C1
/**/
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: /* C1 */
    COMMENT: /*    */
    NAME: KEYWORD
    VALUE: SOME_VAL
/*C1
A/**/
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: /* C1  */
    COMMENT: /* A/* */
    NAME: KEYWORD
    VALUE: SOME_VAL
/*           A            */
/* B *//*C*/
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: /*           A            */
    COMMENT: /* B *//*C                */
    NAME: KEYWORD
    VALUE: SOME_VAL
/*C1/**/
'KEYWORD = SOME_VAL' ------------> VALID
    COMMENT: /* C1/* */
    NAME: KEYWORD
    VALUE: SOME_VAL
/*C1   

A

C3*//*Neato*//*Man*/KEYWORD = (A,B,C) /*Right?
'Yes!*/' ------------------------> VALID
    COMMENT: /* C1    */
    COMMENT: /* A     */
    COMMENT: /* C3    */
    COMMENT: /* Neato */
    COMMENT: /* Man   */
    COMMENT: /*Right? Yes!*/
    NAME: KEYWORD
    VALUE: A
    VALUE: B
    VALUE: C


----- Testing Stream Read/Write -----
Input:
KEYWORD

Output: 
KEYWORD = Null

Input:
KEYWORD X

Output: 
**ERROR** Unable to read PVL keyword [KEYWORD X].
**ERROR** Expected an assignment [=] when reading PVL, but found [X].

Input:
KEYWORD =

Output: 
**ERROR** The PVL keyword [KEYWORD =] does not appear to be a valid Pvl Keyword.

Input:
KEYWORD = SOME_VAL

Output: 
KEYWORD = SOME_VAL

Input:
KEYWORD = "  val  "

Output: 
KEYWORD = "  val  "

Input:
KEYWORD = " 'val' "

Output: 
KEYWORD = " 'val' "

Input:
KEYWORD = (VAL

Output: 
**ERROR** The PVL keyword [KEYWORD = (VAL] does not appear to be a valid Pvl Keyword.

Input:
KEYWORD = (VAL1,VAL2

Output: 
**ERROR** The PVL keyword [KEYWORD = (VAL1,VAL2] does not appear to be a valid Pvl Keyword.

Input:
KEYWORD = (A B,C,D)

Output: 
**ERROR** Unable to read PVL keyword [KEYWORD = (A B,C,D)].
**ERROR** Found extra data after [A] in array when reading PVL.

Input:
KEYWORD = ((A B),(C),(D

Output: 
**ERROR** The PVL keyword [KEYWORD = ((A B),(C),(D] does not appear to be a valid Pvl Keyword.

Input:
KEYWORD = (SOME_VAL)

Output: 
KEYWORD = SOME_VAL

Input:
KEYWORD = (SOME_VAL) <a>

Output: 
KEYWORD = SOME_VAL <a>

Input:
KEYWORD=(SOME_VAL)<a>

Output: 
KEYWORD = SOME_VAL <a>

Input:
KEYWORD = (A, )

Output: 
**ERROR** Unable to read PVL keyword [KEYWORD = (A, )].
**ERROR** Unexpected close of keyword-value array when reading PVL.

Input:
KEYWORD = ()

Output: 
KEYWORD = Null

Input:
KEYWORD = (A,B)

Output: 
KEYWORD = (A, B)

Input:
KEYWORD = {A, B}

Output: 
KEYWORD = (A, B)

Input:
KEYWORD = (A,B) #comment this

Output: 
#comment this
KEYWORD = (A, B)

Input:
KEYWORD = ( A , B )

Output: 
KEYWORD = (A, B)

Input:
KEYWORD	=	( A	,	B )

Output: 
KEYWORD = (A, B)

Input:
KEYWORD = (A, B,C,D,E))

Output: 
**ERROR** Unable to read PVL keyword [KEYWORD = (A, B,C,D,E))].
**ERROR** Keyword has extraneous data [)] at the end.

Input:
KEYWORD = ((1, 2), {3,  4}, (5), 6)

Output: 
KEYWORD = ((1, 2), {3, 4}, (5), 6)

Input:
KEYWORD = { "VAL1" ,   "VAL2", "VAL3"}

Output: 
KEYWORD = (VAL1, VAL2, VAL3)

Input:
KEYWORD = { "VAL1" , "VAL2", "VAL3")

Output: 
**ERROR** Unable to read PVL keyword [KEYWORD = { "VAL1" , "VAL2", "VAL3")].
**ERROR** Incorrect array close when reading PVL; expected [}] but found [)] in keyword named [KEYWORD].

Input:
KEYWORD = { "VAL1" ,

Output: 
**ERROR** The PVL keyword [KEYWORD = { "VAL1" ,] does not appear to be a valid Pvl Keyword.

Input:
KEYWORD = "(A,B,"

Output: 
KEYWORD = "(A,B,"

Input:
KEYWORD = ',E)'

Output: 
KEYWORD = ",E)"

Input:
KEYWORD = ((1,2))

Output: 
KEYWORD = (1,2)

Input:
KEYWORD = ("(f1+f2)","/(f1-f2)")

Output: 
KEYWORD = ((f1+f2), "/(f1-f2)")

Input:
KEYWORD = "(F1+F2)/(F1-F2)"

Output: 
KEYWORD = "(F1+F2)/(F1-F2)"

Input:
KEYWORD = ( (1,2)  , (A,B) )

Output: 
KEYWORD = ((1,2), (A,B))

Input:
KEYWORD = "(f1 + min(f2,f3))"

Output: 
KEYWORD = "(f1 + min(f2,f3))"

Input:
KEYWORD = "(min(f2,f3) + f1)"

Output: 
KEYWORD = "(min(f2,f3) + f1)"

Input:
KEYWORD = "min(f2,f3) + f1"

Output: 
KEYWORD = "min(f2,f3) + f1"

Input:
KEYWORD = "f1 + min(f2,f3)"

Output: 
KEYWORD = "f1 + min(f2,f3)"

Input:
KEYWORD = (A <a>, B <b>, C, D <d>)

Output: 
KEYWORD = (A <a>, B <b>, C, D <d>)

Input:
KEYWORD = (A <a>, B <b>, C, D <d>) <e>

Output: 
KEYWORD = (A <a>, B <b>, C <e>, D <d>)

Input:
KEYWORD = ',E) <unit>

Output: 
**ERROR** The PVL keyword [KEYWORD = ',E) <unit>] does not appear to be a valid Pvl Keyword.

Input:
KEYWORD = ,E) <unit>

Output: 
**ERROR** Unable to read PVL keyword [KEYWORD = ,E) <unit>].
**ERROR** Keyword has extraneous data [,E) <unit>] at the end.

Input:
#SOMECOMMENT
KEYWORD = SOME_VAL

Output: 
#SOMECOMMENT
KEYWORD = SOME_VAL

Input:
#SOMECOMMENT1
#SOMECOMMENT2
KEYWORD = SOME_VAL

Output: 
#SOMECOMMENT1
#SOMECOMMENT2
KEYWORD = SOME_VAL

Input:
//SOMECOMMENT1
#SOMECOMMENT2
KEYWORD = SOME_VAL

Output: 
//SOMECOMMENT1
#SOMECOMMENT2
KEYWORD = SOME_VAL

Input:
/*SOMECOMMENT1*/
KEYWORD = SOME_VAL

Output: 
/* SOMECOMMENT1 */
KEYWORD = SOME_VAL

Input:
KEYWORD = '/*
*/'

Output: 
KEYWORD = "/* */"

Input:
/* SOMECOMMENT1
  SOMECOMMENT2
SOMECOMMENT3 */
KEYWORD = SOME_VAL

Output: 
/* SOMECOMMENT1 */
/* SOMECOMMENT2 */
/* SOMECOMMENT3 */
KEYWORD = SOME_VAL

Input:
/*C1

A
/*
C3*/
KEYWORD = SOME_VAL

Output: 
**ERROR** Error when reading a pvl: Cannot have ['/*'] inside a multi-line comment.

Input:
/*C1
/**/
KEYWORD = SOME_VAL

Output: 
**ERROR** Error when reading a pvl: Cannot have ['/*'] inside a multi-line comment.

Input:
/*C1
A/**/
KEYWORD = SOME_VAL

Output: 
**ERROR** Error when reading a pvl: Cannot have ['/*'] inside a multi-line comment.

Input:
/*           A            */
/* B *//*C*/
KEYWORD = SOME_VAL

Output: 
/* A */
/* B */
/* C */
KEYWORD = SOME_VAL

Input:
/*C1/**/
KEYWORD = SOME_VAL

Output: 
**ERROR** Error when reading a pvl: Cannot have ['/*'] inside a multi-line comment.

Input:
/*C1   

A

C3*//*Neato*//*Man*/KEYWORD = (A,B,C) /*Right?
Yes!*/

Output: 
/* C1    */
/* A     */
/* C3    */
/* Neato */
/* Man   */
/*Right? Yes!*/
KEYWORD = (A, B, C)

----- Testing Difficult Cases Read/Write -----
FROM = /archive/projects/cassini/VIMS/UnivAZraw/tour/S60/cubes/GLO000OBMAP00-
       2//V1654449360_4.QUB
THE_INTERNET = "Seven thousand eight hundred forty three million seventy four
                nine seventy six forty two eighty nine sixty seven thirty five
                million jillion bajillion google six nine four one two three
                four five six seven eight nine ten eleven twelve thirteen
                fourteen" <terrabytes>
BIG_HUGE_LONG_NAME_THAT_SHOULD_TEST_OUT_PARSING = "Seven thousand eight
                                                   hundred forty three million
                                                   seventy four" <bubble baths>
ARRAY_TEST = (5.87, 5465.6, 574.6) <lightyears>
FIRST_100_DIGITS_OF_PI = 3.14159265358979323846264338327950288419716939937510-
                         58209749445923078164062862089986280348253421170679
Raw Data -->
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679

A = XXXXXXXXXXxxxxxxxxxxXXXXXXXXXXxxxxxxxxxxXXXXXXXXXXxxxxxxxxxxXXXXXXXXXXxxxx
TREE = (MAPLE, ELM, PINE)
UGHHHHHHHHHHHH = (59999.0, 59999.0, 59999.0, 59999.0, 59999.0, 59999.0,
                  59999.0, 59999.0, 59999.0, 59999.0, 59999.0, 59999.0)
NAME = 5.2 <meters>
KEY = Null
# Hello World! This is a really really long comment that needs to be
# wrapped onto several different lines to make the PVL file look really
# pretty!
KEY = (5, Null, 3.3 <feet>, "Hello World!")

# Hello World! This is a really really long comment that needs to be
# wrapped onto several different lines to make the PVL file look really
# pretty!
KEY = (5, 88, 3.3 <feet>, "Hello World!")
key = ((a, b, c), ("Hubba Hubba", Bubba))


Test SetUnits methods:

  original condition of Keyword k :
    k = (radius, circumference) <meters>

  after k.SetUnits("circumference", "Fathoms") :
    k = (radius <meters>, circumference <Fathoms>)

  after k.SetUnits("TeraFathoms") :
    k = (radius, circumference) <TeraFathoms>


----------------------------------------
Testing cast operators
string     = I'm being casted
int     = 465721
BigInt     = 465721
double     = 131.244
Test_key_2 = "Might work"
**USER ERROR** [Bob is a name] is invalid. Keyword name cannot contain whitespace.
Test_key_3 = "Might'not work"
Invalid Keyword Type: Integer Expected
Positive number Expected
Integer not in the Range. Expected (0-10)
Invalid Keyword Value: Expected values "value1", "value2", "value3"
Invalid Keyword Type: Expected  Boolean values "true", "false", "null"
+0 −400

File deleted.

Preview size limit exceeded, changes collapsed.

+10 −17
Original line number Diff line number Diff line
@@ -63,27 +63,20 @@ namespace Isis {

    for(auto it = jsonobj.begin(); it != jsonobj.end(); it++) {
        PvlKeyword keyword;
        if (it.value().is_array()) {
        keyword.setName(QString::fromStdString(it.key()));
        if (it.value().is_array()) {
          for(auto ar = it.value().begin(); ar!=it.value().end(); ar++) {
            keyword += QString::number(ar->get<double>(), 'g', 16);
            try {
              keyword.addJsonValue(*ar);
            }
            catch (IException &e) {
              QString msg = "While attempting to parse " + name + " the following occured";
              throw IException(e, IException::Unknown, msg, _FILEINFO_);
            }
        else if(it.value().is_number()) {
          keyword.setName(QString::fromStdString(it.key()));
          keyword.setValue(QString::number(it->get<double>()));
          }
        else if(it.value().is_boolean()) {
          keyword.setName(QString::fromStdString(it.key()));
          keyword.setValue(QString(it->get<bool>() ? "true" : "false"));
        }
        else if(it.value().is_null()) {
          keyword.setName(QString::fromStdString(it.key()));
          keyword.setValue(QString("Null"));
        }
        else {
          keyword.setName(QString::fromStdString(it.key()));
          keyword.setValue(QString::fromStdString(it.value()));
          keyword.setJsonValue(*it);
        }
        addKeyword(keyword);
    }
Loading