// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

#include <ConfigManager.h>
#include <DataNode.h>
#if defined(_WIN32)
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#endif
#include <clocale>
#include <iomanip>
#include <visit-config.h>
#include <DebugStream.h>

// ****************************************************************************
// Method: ConfigManager::ConfigManager
//
// Purpose:
//   Constructor for the ConfigManager class.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:17:00 PST 2000
//
// Modifications:
//
// ****************************************************************************

ConfigManager::ConfigManager()
{
    putback = false;
    putbackChar = 0;
}

// ****************************************************************************
// Method: ConfigManager::~ConfigManager
//
// Purpose:
//   Destructor for the ConfigManager class.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:17:23 PST 2000
//
// Modifications:
//
// ****************************************************************************

ConfigManager::~ConfigManager()
{
}

// ****************************************************************************
// Method: ConfigManager::WriteBack
//
// Purpose:
//   This is a debugging method writes the DataNode tree specified by
//   root to a file called WRITEBACK. It is used to test the validity
//   of a tree that has been read from a file.
//
// Arguments:
//   root : The DataNode tree to write to the file.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:17:58 PST 2000
//
// Modifications:
//   Brad Whitlock, Mon Feb 23 16:29:41 PST 2004
//   Write text file.
//
// ****************************************************************************

void
ConfigManager::WriteBack(DataNode *root)
{
    std::ofstream outf;
    outf.open("WRITEBACK", ios::out | ios::trunc); //"wt"
    // Write the output file to stdout for now.
    outf << "<?xml version=\"1.0\"?>\n";
    WriteObject(outf, root);

    outf.close();
}

// ****************************************************************************
// Method: ConfigManager::WriteIndent
//
// Purpose:
//   This method writes indentation spaces to the open file.
//
// Arguments:
//   indentLevel : The number of levels to indent.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:19:28 PST 2000
//
// Modifications:
//
// ****************************************************************************

void
ConfigManager::WriteIndent(std::ostream& out, int indentLevel)
{
    for(int i = 0; i < indentLevel; ++i)
        out << "    ";
}

// ****************************************************************************
// Method: ConfigManager::WriteQuotedStringData
//
// Purpose:
//   Writes the string to the file surrounded by quotes and and quotes that
//   the string had in it are escaped.
//
// Arguments:
//   str : The string to write to the file.
//
// Programmer: Brad Whitlock
// Creation:   Fri Oct 3 16:18:10 PST 2003
//
// Modifications:
//
//    Allen Sanderson, Thu Sep  1 15:25:29 PDT 2016
//    Replaced duplicate loop of string output with call to WriteEscapedString
// ****************************************************************************

void
ConfigManager::WriteQuotedStringData(std::ostream& out, const std::string &str)
{
    out.put('"');

    WriteEscapedString(out, str);

    out.put('"');
    out.put(' ');
}

// ****************************************************************************
// Method: ConfigManager::WriteEscapedStringData
//
// Purpose:
//   Writes the string to the file escaping various characters as needed.
//
// Arguments:
//   str : The string to write to the file.
//
// Programmer: Jeremy Meredith
// Creation:   August  2, 2005
//
// Modifications:
//
//    Mark C. Miller, Thu Sep  1 14:48:24 PDT 2016
//    Change to proper XML-style escaping of certain characters
// ****************************************************************************

void
ConfigManager::WriteEscapedString(std::ostream &out, const std::string &str)
{
    if(str.size() > 0)
    {
        const char *cptr = str.c_str();
        for (size_t i = 0; i < str.size(); ++i)
        {
            switch (cptr[i])
            {
                case '<':  out.write("&lt;",4);   break;
                case '>':  out.write("&gt;",4);   break;
                case '&':  out.write("&amp;",5);  break;
                case '\'': out.write("&apos;",6); break;
                case '"':  out.write("&quot;",6); break;
                default:   out.put(cptr[i]);
            }
        }
    }
}

// ****************************************************************************
// Method: ConfigManager::WriteData
//
// Purpose:
//   This method writes out the data contained in the specified
//   DataNode to the open file as ASCII.
//
// Arguments:
//   node : The node whose data will be written.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:20:21 PST 2000
//
// Modifications:
//   Brad Whitlock, Fri Mar 30 14:37:21 PST 2001
//   Added unsigned char cases.
//
//   Brad Whitlock, Fri Oct 3 16:00:21 PST 2003
//   I made string vectors and arrays quote their strings so they read in
//   correctly if the strings contain spaces.
//
//   Jeremy Meredith, Tue Aug  2 16:11:36 PDT 2005
//   I made single strings escape special characters.
//
//   Dave Pugmire, Wed Aug 18 09:54:43 EDT 2010
//   Support variable names with leading and trailing spaces in sessionfiles.
//
//   Brad Whitlock, Thu Oct 28 12:16:40 PDT 2010
//   Only quote single strings on write if they contain spaces.
//
//   Kathleen Biagas, Thu Apr 9 07:19:54 MST 2015
//   Set precision for floats and doubles (to highest value that doesn't
//   convert '0.009' to '0.00899999999'.
// 
//   Justin Privitera, Thu Jun 16 18:01:49 PDT 2022
//   Added logic for bool vectors.
//
// ****************************************************************************

void
ConfigManager::WriteData(std::ostream& out, DataNode *node)
{
    switch(node->GetNodeType())
    {
    case CHAR_NODE:
        out << node->AsChar();
        break;
    case UNSIGNED_CHAR_NODE:
        out << (int)node->AsUnsignedChar();
        break;
    case INT_NODE:
        out << node->AsInt();
        break;
    case LONG_NODE:
        out << node->AsLong();
        break;
    case FLOAT_NODE:
        out << std::setprecision(7) <<  node->AsFloat();
        break;
    case DOUBLE_NODE:
        out <<  std::setprecision(15) << node->AsDouble();
        break;
    case STRING_NODE:
        { // new scope
            bool hasSpaces = node->AsString().find(" ") != std::string::npos;
            if(hasSpaces)
                out << "\"";
            WriteEscapedString(out, node->AsString());
            if(hasSpaces)
                out << "\"";
        }
        break;
    case BOOL_NODE:
        if(node->AsBool())
           out << "true";
        else
           out << "false";
        break;
    case CHAR_ARRAY_NODE:
        { // new scope
            const char *cptr = node->AsCharArray();
            for(int i = 0; i < node->GetLength(); ++i)
                out << (char)*cptr++ << " ";
        }
        break;
    case UNSIGNED_CHAR_ARRAY_NODE:
        { // new scope
            const unsigned char *uptr = node->AsUnsignedCharArray();
            for(int i = 0; i < node->GetLength(); ++i)
                out << (int)*uptr++ << " ";
        }
        break;
    case INT_ARRAY_NODE:
        { // new scope
            const int *iptr = node->AsIntArray();
            for(int i = 0; i < node->GetLength(); ++i)
                out << *iptr++ << " ";
        }
        break;
    case LONG_ARRAY_NODE:
        { // new scope
            const long *lptr = node->AsLongArray();
            for(int i = 0; i < node->GetLength(); ++i)
                out << *lptr++ << " ";
        }
        break;
    case FLOAT_ARRAY_NODE:
        { // new scope
            const float *fptr = node->AsFloatArray();
            for(int i = 0; i < node->GetLength(); ++i)
                out << std::setprecision(7) << *fptr++ << " ";
        }
        break;
    case DOUBLE_ARRAY_NODE:
        { // new scope
            const double *dptr = node->AsDoubleArray();
            for(int i = 0; i < node->GetLength(); ++i)
                out << std::setprecision(15) << *dptr++ << " ";
        }
        break;
    case STRING_ARRAY_NODE:
        { // new scope
            const std::string *sptr = node->AsStringArray();
            for(int i = 0; i < node->GetLength(); ++i)
                WriteQuotedStringData(out,*sptr++);
        }
        break;
    case BOOL_ARRAY_NODE:
        { // new scope
            const bool *bptr = node->AsBoolArray();
            for(int i = 0; i < node->GetLength(); ++i)
            {
                if(*bptr++)
                    out << "true ";
                else
                    out << "false ";
            }
        }
        break;
    case CHAR_VECTOR_NODE:
       { // new scope
            const charVector &cvec = node->AsCharVector();
            for(size_t i = 0; i < cvec.size(); ++i)
                out << cvec[i] << " ";
       }
       break;
    case UNSIGNED_CHAR_VECTOR_NODE:
       { // new scope
            const unsignedCharVector &uvec = node->AsUnsignedCharVector();
            for(size_t i = 0; i < uvec.size(); ++i)
                out << (int)uvec[i] << " ";
       }
       break;
    case INT_VECTOR_NODE:
       { // new scope
            const intVector &ivec = node->AsIntVector();
            for(size_t i = 0; i < ivec.size(); ++i)
                out << ivec[i] << " ";
       }
       break;
    case LONG_VECTOR_NODE:
       { // new scope
            const longVector &lvec = node->AsLongVector();
            for(size_t i = 0; i < lvec.size(); ++i)
                out << lvec[i] << " ";
       }
       break;
    case FLOAT_VECTOR_NODE:
       { // new scope
            const floatVector &fvec = node->AsFloatVector();
            for(size_t i = 0; i < fvec.size(); ++i)
                out << std::setprecision(7) << fvec[i] << " ";
       }
       break;
    case DOUBLE_VECTOR_NODE:
       { // new scope
            const doubleVector &dvec = node->AsDoubleVector();
            for(size_t i = 0; i < dvec.size(); ++i)
               out << std::setprecision(15) << dvec[i] << " ";
       }
       break;
    case STRING_VECTOR_NODE:
       { // new scope
            const stringVector &svec = node->AsStringVector();
            for(size_t i = 0; i < svec.size(); ++i)
                WriteQuotedStringData(out, svec[i]);
       }
       break;
    case BOOL_VECTOR_NODE:
       { // new scope
            const boolVector &bvec = node->AsBoolVector();
            for(size_t i = 0; i < bvec.size(); ++i)
            {
                if (bvec[i])
                    out << "\"true\" ";
                else
                    out << "\"false\" ";
            }
       }
       break;
    default:
        fprintf(stderr, "ConfigManager::WriteData: Unsupported type\n");
    }
}

// ****************************************************************************
// Method: ConfigManager::WriteObject
//
// Purpose:
//   This method writes out the DataNode tree to the open file. This
//   is the code that actually writes a config file.
//
// Arguments:
//   DataNode    : The root of the tree that is to be written.
//   indentLevel : The level of indentation to use for this part of
//                 the tree.
//
// Note:       This function is recursive!
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:21:48 PST 2000
//
// Modifications:
//   Brad Whitlock, Fri Mar 30 14:41:13 PST 2001
//   Added a case for arrays of unsigned characters.
//
//   Brad Whitlock, Thu Jul 3 14:47:24 PST 2003
//   I removed the childObjects kludge.
//
// ****************************************************************************

void
ConfigManager::WriteObject(std::ostream& out, DataNode *node, int indentLevel)
{
    // Write the beginning tag.
    WriteIndent(out, indentLevel);
    if(node->GetNodeType() == INTERNAL_NODE)
        out << "<Object name=\"" << node->GetKey().c_str() << "\">\n";
    else if (node->GetNodeType() == MAP_NODE_NODE)
    {
        out << "<Object name=\""<< node->GetKey().c_str()
            << "\" type=\"MapNode\">\n";
    }
    else
    {
        // Write out the field tag based on the node type.
        switch(node->GetNodeType())
        {
        case CHAR_ARRAY_NODE:
        case UNSIGNED_CHAR_ARRAY_NODE:
        case INT_ARRAY_NODE:
        case LONG_ARRAY_NODE:
        case FLOAT_ARRAY_NODE:
        case DOUBLE_ARRAY_NODE:
        case STRING_ARRAY_NODE:
        case BOOL_ARRAY_NODE:
            out << "<Field name=\"" << node->GetKey().c_str()
                                   << "\" type=\"" << NodeTypeName(node->GetNodeType())
                                   << "\" length=\"" << node->GetLength() << "\">";
            break;
        default:
            out << "<Field name=\""<< node->GetKey().c_str()
                << "\" type=\"" << NodeTypeName(node->GetNodeType()) << "\">";
        }
    }

    // Write the children of the node if there are any.
    if(node->GetNodeType() == INTERNAL_NODE)
    {
        if(node->GetNumChildren() > 0)
        {
            DataNode **children = node->GetChildren();
            for(int i = 0; i < node->GetNumChildren(); ++i)
                WriteObject(out, children[i], indentLevel + 1);
        }
    }
    else if(node->GetNodeType() == MAP_NODE_NODE)
    {
        WriteMapNode(out, node->AsMapNode(), indentLevel+1);
    }
    else
    {
        // We have to write actual values.
        WriteData(out, node);
    }

    // Write the ending tag.
    if(node->GetNodeType() == INTERNAL_NODE ||
       node->GetNodeType() == MAP_NODE_NODE)
    {
        WriteIndent(out, indentLevel);
        out << "</Object>\n";
    }
    else
    {
        out << "</Field>\n";
    }
}

// ****************************************************************************
// Method: ConfigManager::ReadChar
//
// Purpose:
//   This method reads a character from the open file. It also can
//   read one character that has been "put back" into the file.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:23:34 PST 2000
//
// Modifications:
//
// ****************************************************************************

char
ConfigManager::ReadChar(std::istream& in)
{
    if(putback)
    {
        putback = false;
        return putbackChar;
    }
    else
    {
        char c = 0;

        // Read a character until it is greater than ' ' or we
        // encounter eof.
        do
        {
           in.get(c);
        } while(c < ' ' && !in.eof());

        if(in.eof())
            return 0;
        else
            return c;
    }
}

// ****************************************************************************
// Method: ConfigManager::PutBackChar
//
// Purpose:
//   Logically puts a character back into the open file.
//
// Arguments:
//   c : The character to put back.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:24:23 PST 2000
//
// Modifications:
//
// ****************************************************************************

void
ConfigManager::PutBackChar(char c)
{
    putback = true;
    putbackChar = c;
}

// ****************************************************************************
// Method: ConfigManager::FinishTag
//
// Purpose:
//   Read until a '>' character or eof is encountered.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:29:57 PST 2000
//
// Modifications:
//
//   Hank Childs, Thu Jun  8 16:14:54 PDT 2006
//   Initialize 'c' to remove compiler warning.
//
// ****************************************************************************

void
ConfigManager::FinishTag(std::istream& in)
{
    char c = '\0';
    for(int i = 0; !in.eof() && ((c = ReadChar(in)) != '>'); ++i);

    if(c != '>')
        PutBackChar(c);
}

// ****************************************************************************
// Method: ConfigManager::ReadStringVector
//
// Purpose:
//   Reads a vector of strings from the open file until a termination character
//   is encountered.
//
// Arguments:
//   termChar : That character at which to stop reading.
//
// Returns:    A string vector.
//
// Programmer: Brad Whitlock
// Creation:   Thu Jul 3 09:39:11 PDT 2003
//
// Modifications:
//   Brad Whitlock, Fri Oct 3 17:12:07 PST 2003
//   Added crude character escaping.
//
//   Jeremy Meredith, Tue Aug  2 16:13:04 PDT 2005
//   I made escaping work even for < and > characters.
//
//    Allen Sanderson, Thu Sep  1 15:25:29 PDT 2016
//    Add logic to undue XML-style escaping
//
//   Kathleen Biagas, Thu May 17, 2018
//   UNC style paths on Windows start with '\\', account for that and put them
//   back into the string as passed.
//
// ****************************************************************************

stringVector
ConfigManager::ReadStringVector(std::istream& in, char termChar)
{
    stringVector retval;

    std::string tempString;
    char c;
    bool reading = false, keepgoing = true;
    bool quoted = false;
    bool escaped = false;

    while(keepgoing)
    {
        c = ReadChar(in);
        keepgoing = (!in.eof() && (escaped || c != termChar));

        if(c == ' ')
        {
            if(quoted)
            {
                // Add to the current string.
                tempString += c;
                reading = true;
            }
            else if(reading)
            {
                retval.push_back(tempString);
                tempString = "";
                reading = false;
            }
        }
        else if(escaped && (c == '<' || c == '>'))
        {
            tempString += c;
            reading = true;
            escaped = false;
        }
        else if(c == '\\')
        {
            if(escaped)
            {
                tempString += "\\\\";
                escaped = false;
            }
            else
                escaped = true;
        }
        else if(c == '"')
        {
            if(escaped)
            {
                escaped = false;
                tempString += c;
                reading = true;
            }
            else
            {
                quoted = !quoted;
                // Add to the current string.
                tempString += c;
                reading = true;
            }
        }
        else if(c != '\t' && c != '<' && c != '>')
        {
            if(escaped)
            {
                tempString += '\\';
                escaped = false;
            }

            // Add to the current string.
            tempString += c;
            reading = true;
        }
        else if(reading)
        {
            if(escaped)
            {
                tempString += '\\';
                escaped = false;
            }

            retval.push_back(tempString);
            tempString = "";
            reading = false;
        }
    }

    //  Change the XML-style escaping of certain characters back to ascii
    //  characters.
    for( unsigned int i=0; i<retval.size(); ++i )
    {
      std::size_t pos;

      do {
        if( (pos = retval[i].find("&lt;")) != std::string::npos)
          retval[i].replace(pos,4,"<");
      }
      while(pos != std::string::npos);

      do {
        if( (pos = retval[i].find("&gt;")) != std::string::npos)
          retval[i].replace(pos,4,">");
      }
      while(pos != std::string::npos);

      do {
        if( (pos = retval[i].find("&amp;")) != std::string::npos)
          retval[i].replace(pos,5,"&");
      }
      while(pos != std::string::npos);

      do {
        if( (pos = retval[i].find("&apos;")) != std::string::npos)
          retval[i].replace(pos,6,"'");
      }
      while(pos != std::string::npos);

      do {
        if( (pos = retval[i].find("&quot;")) != std::string::npos)
          retval[i].replace(pos,6,"\"");
      }
      while(pos != std::string::npos);
    }

    return retval;
}

// ****************************************************************************
// Method: ConfigManager::RemoveLeadAndTailQuotes
//
// Purpose:
//   Removes leading and trailing quotes in all of the strings in the vector.
//
// Arguments:
//   sv : The string vector to change.
//
// Programmer: Brad Whitlock
// Creation:   Mon Oct 6 08:55:23 PDT 2003
//
// Modifications:
//
// ****************************************************************************

void
ConfigManager::RemoveLeadAndTailQuotes(stringVector &sv)
{
    for(size_t i = 0; i < sv.size(); ++i)
    {
        std::string &s = sv[i];
        if(s.size() > 0)
        {
            int head = (s[0] == '"') ? 1 : 0;
            int tail = (s[s.size()-1] == '"') ? 1 : 0;
            sv[i] = s.substr(head, s.size() - head - tail);
        }
    }
}

// ****************************************************************************
// Method: ConfigManager::ReadFieldData
//
// Purpose:
//   Reads the data for the specified node type, creates a DataNode to
//   store it, and returns the created DataNode.
//
// Arguments:
//   fieldName : The name of the DataNode.
//   type      : The DataNode's desired type.
//
// Returns:    A pointer to a new DataNode.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:32:56 PST 2000
//
// Modifications:
//   Brad Whitlock, Fri Mar 30 14:41:53 PST 2001
//   Added cases to read unsigned chars.
//
//   Brad Whitlock, Thu Jul 3 16:16:26 PST 2003
//   Made it use stringVector class.
//
//   Brad Whitlock, Tue Jul 22 10:09:22 PDT 2003
//   I fixed a bug in reading in CHAR_NODE data.
//
//   Brad Whitlock, Fri Oct 3 17:19:42 PST 2003
//   Added code to strip leading and tailing quotes from string vectors.
//
//   Dave Pugmire, Wed Aug 18 09:54:43 EDT 2010
//   Support variable names with leading and trailing spaces in sessionfiles.
// 
//   Justin Privitera, Thu Jun 16 18:01:49 PDT 2022
//   Added logic for bool vectors.
//
// ****************************************************************************

DataNode *
ConfigManager::ReadFieldData(std::istream& in,
                             const std::string &tagName, NodeTypeEnum type,
                             int tagLength, bool noEndTag)
{
    DataNode *retval = 0;

    int           i;
    char          cval;
    unsigned char uval;
    int           ival;
    long          lval;
    float         fval;
    double        dval;
    bool          bval;

    stringVector sv;

    // If there is no ending tag then a short cut and there is no data.
    if( !noEndTag )
    {
      // Read strings until we get a '<' character.
      sv = ReadStringVector(in, '<');
    }

    int minSize = (tagLength == 0) ? (int)sv.size() :
                  ((tagLength < (int)sv.size()) ? tagLength : (int)sv.size());

    // All 20, or whatever, cases.
    switch(type)
    {
    case CHAR_NODE:
        // Read a character.
        if(minSize > 0)
        {
            retval = new DataNode(tagName, sv[0][0]);
        }
        break;
    case UNSIGNED_CHAR_NODE:
        // Read an int and turn it into an unsigned character.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%d", &ival);
            uval = (unsigned char)ival;
            retval = new DataNode(tagName, uval);
        }
        break;
    case INT_NODE:
        // Read an int.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%d", &ival);
            retval = new DataNode(tagName, ival);
        }
        break;
    case LONG_NODE:
        // Read a long.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%ld", &lval);
            retval = new DataNode(tagName, lval);
        }
        break;
    case FLOAT_NODE:
        // Read a float.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%g", &fval);
            retval = new DataNode(tagName, fval);
        }
        break;
    case DOUBLE_NODE:
        // Read a double.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%lg", &dval);
            retval = new DataNode(tagName, dval);
        }
        break;
    case STRING_NODE:
        { // new scope
            RemoveLeadAndTailQuotes(sv);
            std::string temp;
            for(i = 0; i < minSize; ++i)
            {
                temp += sv[i];
                if(i < (minSize - 1))
                    temp += " ";
            }
            retval = new DataNode(tagName, temp);
        }
        break;
    case BOOL_NODE:
        { // new scope
            bval = false;
            if(minSize > 0)
            {
                bval = (sv[0] == "true");
            }
            retval = new DataNode(tagName, bval);
        }
        break;
    case CHAR_ARRAY_NODE:
        { // new scope
            char *cvalArray = 0;
            if(minSize > 0)
            {
                cvalArray = new char[minSize];
                for(i = 0; i < minSize; ++i)
                    sscanf(sv[i].c_str(), "%c", &cvalArray[i]);

                retval = new DataNode(tagName, cvalArray, minSize);
            }

            if(cvalArray != 0)
                delete [] cvalArray;
        }
        break;
    case UNSIGNED_CHAR_ARRAY_NODE:
        { // new scope
            unsigned char *uvalArray = 0;
            if(minSize > 0)
            {
                uvalArray = new unsigned char[minSize];
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%d", &ival);
                    uvalArray[i] = (unsigned char)ival;
                }

                retval = new DataNode(tagName, uvalArray, minSize);
            }

            if(uvalArray != 0)
                delete [] uvalArray;
        }
        break;
    case INT_ARRAY_NODE:
        { // new scope
            int *ivalArray = 0;
            if(minSize > 0)
            {
                ivalArray = new int[minSize];
                for(i = 0; i < minSize; ++i)
                    sscanf(sv[i].c_str(), "%d", &ivalArray[i]);

                retval = new DataNode(tagName, ivalArray, minSize);
            }

            if(ivalArray != 0)
                delete [] ivalArray;
        }
        break;
    case LONG_ARRAY_NODE:
        { // new scope
            long *lvalArray = 0;
            if(minSize > 0)
            {
                lvalArray = new long[minSize];
                for(i = 0; i < minSize; ++i)
                    sscanf(sv[i].c_str(), "%ld", &lvalArray[i]);

                retval = new DataNode(tagName, lvalArray, minSize);
            }

            if(lvalArray != 0)
                delete [] lvalArray;
        }
        break;
    case FLOAT_ARRAY_NODE:
        { // new scope
            float *fvalArray = 0;
            if(minSize > 0)
            {
                fvalArray = new float[minSize];
                for(i = 0; i < minSize; ++i)
                    sscanf(sv[i].c_str(), "%g", &fvalArray[i]);

                retval = new DataNode(tagName, fvalArray, minSize);
            }

            if(fvalArray != 0)
                delete [] fvalArray;
        }
        break;
    case DOUBLE_ARRAY_NODE:
        { // new scope
            double *dvalArray = 0;
            if(minSize > 0)
            {
                dvalArray = new double[minSize];
                for(i = 0; i < minSize; ++i)
                    sscanf(sv[i].c_str(), "%lg", &dvalArray[i]);

                retval = new DataNode(tagName, dvalArray, minSize);
            }

            if(dvalArray != 0)
                delete [] dvalArray;
        }
        break;
    case STRING_ARRAY_NODE:
        { // new scope
            std::string *svalArray = 0;
            if(minSize > 0)
            {
                RemoveLeadAndTailQuotes(sv);
                svalArray = new std::string[minSize];
                for(i = 0; i < minSize; ++i)
                    svalArray[i] = sv[i];

                retval = new DataNode(tagName, svalArray, minSize);
            }

            if(svalArray != 0)
                delete [] svalArray;
        }
        break;
    case BOOL_ARRAY_NODE:
        { // new scope
            bool *bvalArray = 0;
            if(minSize > 0)
            {
                bvalArray = new bool[minSize];
                for(i = 0; i < minSize; ++i)
                    bvalArray[i] = (sv[i] == "true");

                retval = new DataNode(tagName, bvalArray, minSize);
            }

            if(bvalArray != 0)
                delete [] bvalArray;
        }
        break;
    case CHAR_VECTOR_NODE:
        { // new scope
            charVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%c", &cval);
                    temp.push_back(cval);
                }

                retval = new DataNode(tagName, temp);
            }
        }
        break;
    case UNSIGNED_CHAR_VECTOR_NODE:
        { // new scope
            unsignedCharVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%d", &ival);
                    uval = (unsigned char)ival;
                    temp.push_back(uval);
                }

                retval = new DataNode(tagName, temp);
            }
        }
        break;
    case INT_VECTOR_NODE:
        { // new scope
            intVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%d", &ival);
                    temp.push_back(ival);
                }

                retval = new DataNode(tagName, temp);
            }
        }
        break;
    case LONG_VECTOR_NODE:
        { // new scope
            longVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%ld", &lval);
                    temp.push_back(lval);
                }

                retval = new DataNode(tagName, temp);
            }
        }
        break;
    case FLOAT_VECTOR_NODE:
        { // new scope
            floatVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%g", &fval);
                    temp.push_back(fval);
                }

                retval = new DataNode(tagName, temp);
            }
        }
        break;
    case DOUBLE_VECTOR_NODE:
        { // new scope
            doubleVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%lg", &dval);
                    temp.push_back(dval);
                }

                retval = new DataNode(tagName, temp);
            }
        }
        break;
    case STRING_VECTOR_NODE:
        if(minSize > 0)
        {
            RemoveLeadAndTailQuotes(sv);
            retval = new DataNode(tagName, sv);
        }
        break;
    case BOOL_VECTOR_NODE:
        { // new scope
            boolVector temp;
            if(minSize > 0)
            {
                for (i = 0; i < minSize; ++i)
                    temp.push_back(sv[i] == "\"true\"");
                retval = new DataNode(tagName, temp);
            }
        }
        break;
    default:
        break;
    }

    return retval;
}

// ****************************************************************************
// Method: ConfigManager::ReadObject
//
// Purpose:
//   This method reads the top level and all child DataNodes from the
//   config file and makes them children of parentNode.
//
// Arguments:
//   parentNode : This is the parent of top level node that will be
//                read from the file.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 29 17:36:21 PST 2000
//
// Modifications:
//
//    Mark C. Miller, Tue Mar 31 18:54:53 PDT 2015
//    Incorporate changes as per Brad's guidance to force locale to en_US
//    when reading.
//
//    Alister Maguire, Wed Jun 24 15:37:46 PDT 2020
//    Changed setlocale from en_US to en_US.UTF-8 for Ubuntu compatibility,
//    and added a check to see if it succeeded or not.
//
// ****************************************************************************

bool
ConfigManager::ReadObject(std::istream& in, DataNode *parentNode)
{
    // Query current locale setting
    std::string current_locale = setlocale(LC_ALL, 0);

    // Force US locale.
    const char * result = setlocale(LC_ALL, "en_US.UTF-8");

    // Did we succeed?
    if (result == NULL)
    {
        debug1 << "setlocale failed! This may result in erroneous reads..."
            << endl;
    }

    // Read the settings.
    bool te = false;
    bool retval = ReadObjectHelper(in, parentNode, te);

    // Restore previous locale.
    setlocale(LC_ALL, current_locale.c_str());

    return retval;
}

// ****************************************************************************
// Method: ConfigManager::ReadObjectHelper
//
// Purpose:
//   This method recursively reads all objects and fields from the file.
//
// Arguments:
//   parentNode : The node to which new objects and fields are added.
//   te         : Whether we encountered an ending tag.
//
// Returns:    True if we should keep reading, false otherwise.
//
// Note:
//
// Programmer: Brad Whitlock
// Creation:   Thu Jul 3 16:09:28 PST 2003
//
// Modifications:
//
// ****************************************************************************

bool
ConfigManager::ReadObjectHelper(std::istream &in, DataNode *parentNode, bool &te)
{
    bool keepReading = true;
    bool tagIsEndTag = false;
    bool noEndTag = false;
    std::string  tagName;
    NodeTypeEnum tagType = INTERNAL_NODE;
    int          tagLength = 0;

    // Read the opening tag.
    keepReading = ReadTag(in, tagName, tagType, tagLength,
                          tagIsEndTag, noEndTag);

    if(tagIsEndTag && keepReading)
    {
        te = true;
        return keepReading;
    }

    if(tagType == INTERNAL_NODE)
    {
        DataNode *node = new DataNode(tagName);
        parentNode->AddNode(node);

        while(keepReading && !tagIsEndTag && !noEndTag)
        {
            keepReading = ReadObjectHelper(in, node, tagIsEndTag);
        }

        if(tagIsEndTag || noEndTag)
            return keepReading;
    }
    else if(tagType == MAP_NODE_NODE)
    {
        MapNode mn;
        // Read all the fields into the MapNode
        ReadMapNodeFields(in, mn, noEndTag);
        if (mn.GetNumEntries() > 0)
        {
            DataNode *node = new DataNode(tagName, mn);
            parentNode->AddNode(node);
        }
    }
    else
      keepReading =
        ReadField(in, parentNode, tagName, tagType, tagLength, noEndTag);

    // No ending tag so keep reading.
    if( noEndTag )
    {
      keepReading = true;
    }
    // Read the ending tag.
    else
    {
      stringVector sv = ReadStringVector(in,'>');
      // If ending tag then must have been an error.
      keepReading = sv.size() > 0;
    }

    te = false;

    return keepReading;
}

// ****************************************************************************
// Method: ConfigManager::ReadTag
//
// Purpose:
//   Reads a tag header and determines the type of object being read.
//
// Arguments:
//   tagName        : The return name for the tag.
//   tagType        : The return type for the tag.
//   tagLength      : The return length for the tag.
//   tafIsEndTag    : Whether or not the tag is an ending tag.
//
// Returns:    True if a tag was read.
//
// Programmer: Brad Whitlock
// Creation:   Thu Jul 3 16:11:08 PST 2003
//
// Modifications:
//
// ****************************************************************************

bool
ConfigManager::ReadTag(std::istream& in, std::string &tagName,
                       NodeTypeEnum &tagType,
                       int &tagLength,
                       bool &tagIsEndTag,
                       bool &noEndTag)
{
    // Read strings.
    stringVector sv = ReadStringVector(in, '>');

    std::string tagTypeStr("");
    tagName = "";
    tagType = INTERNAL_NODE;
    tagLength = 0;
    tagIsEndTag = false;
    noEndTag = false;

    // For the last string check to see if there is a forward slash
    // "/" in front of the terminal character ">" (i.e. "/>"). If so
    // there will not be a ending tag as there is no data.
    if( !sv.empty() )
    {
      std::string &str = sv.back();

      // Check for the short cut ending tag.
      if( str.find_last_of("/") == str.length()-1)
      {
        noEndTag = true;

        // If the forward slash was by itself remove the string.
        if( str.size() == 1 )
          sv.pop_back();
        // Else remove the forward slash so the string can be processed.
        else
          str.erase(str.length()-1, 1);
      }
    }

    for(size_t i = 0; i < sv.size(); ++i)
    {
        std::string::size_type pos = sv[i].find('=') + 1;
        std::string token(sv[i].substr(0, pos));
        std::string tokenValue;

        if(pos != std::string::npos && token.size() > 0)
            tokenValue = sv[i].substr(pos + 1, sv[i].size() - pos - 2);

        if(sv[i][0] == '/')
        {
            tagIsEndTag = true;
            return true;
        }
        else if(token == "type=")
        {
            tagTypeStr = tokenValue;
        }
        else if(token == "name=")
        {
            tagName = tokenValue;
        }
        else if(token == "length=")
        {
            int len;
            if(sscanf(tokenValue.c_str(), "%d", &len) == 1)
            {
                tagLength = (len > 0) ? len : tagLength;
            }
        }
        else if(token == "childObjects=")
        {
            // Skip this tag, it is obsolete.
        }
        else
            tagTypeStr = sv[i];
    }

    // Get the NodeTypeEnum from the tag's type name.
    tagType = GetNodeType(tagTypeStr.c_str());

    return sv.size() > 0;
}

// ****************************************************************************
// Method: ConfigManager::ReadField
//
// Purpose:
//   Reads a field and adds it to the parentNode.
//
// Arguments:
//   parentNode : The node to which the field is added.
//   tagName    : The name of the field.
//   tagType    : The type of the field.
//   tagLength  : The length of the field.
//
// Returns:    True if a field was added to the parentNode.
//
// Programmer: Brad Whitlock
// Creation:   Thu Jul 3 16:15:00 PST 2003
//
// Modifications:
//
// ****************************************************************************

bool
ConfigManager::ReadField(std::istream& in,
                         DataNode *parentNode, const std::string &tagName,
                         NodeTypeEnum tagType, int tagLength, bool noEndTag)
{
    DataNode *retval =
      ReadFieldData(in, tagName, tagType, tagLength, noEndTag);

    if(retval != 0)
    {
        parentNode->AddNode(retval);
    }

    return retval != 0;
}


// ****************************************************************************
// Method: ConfigManager::WriteMapNode
//
// Purpose:
//   This method writes out the data contained in the specified
//   MapeNode to the passed output stream.
//
// Arguments:
//   out:            The output stream used for writing.
//   mn:             The MapNode node whose data will be written.
//   indentLevel:    The indent level to use.
//
// Programmer: Kathleen Biagas
// Creation:   November 28, 2018
//
// Modifications:
//
// ****************************************************************************

void
ConfigManager::WriteMapNode(std::ostream &out, const MapNode &mn, int indentLevel)
{
    stringVector names;
    mn.GetEntryNames(names);
    for (size_t i = 0; i < names.size(); ++i)
    {
        const MapNode *entry = mn.GetEntry(names[i]);
        WriteIndent(out, indentLevel);
        out << "<Field name=\"" << names[i] << "\" type=\""
            << entry->TypeName() << "\">"
            << const_cast<MapNode *>(entry)->ConvertToString()
            << "</Field>\n";
    }
}


// ****************************************************************************
// Method: ConfigManager::ReadMapNodeFieldData
//
// Purpose:
//   Reads data from the input stream and stores the field in the MapNode.
//
// Arguments:
//   in:        The input stream.
//   mn:        The MapNode used to store the field.
//   tagName:   The name of the field, used as MapNode key.
//   type:      The type of the field.
//   tagLength: The length of the field.
//   noEndTag:  Flag
//
// Programmer: Kathleen Biagas
// Creation:   November 28, 2018
//
// Modifications:
//   Justin Privitera, Thu Jun 16 18:01:49 PDT 2022
//   Added logic for bool vectors.
//
// ****************************************************************************

void
ConfigManager::ReadMapNodeFieldData(std::istream& in, MapNode &mn,
                             const std::string &tagName, NodeTypeEnum type,
                             int tagLength, bool noEndTag)
{
    int           i;
    char          cval;
    unsigned char uval;
    int           ival;
    long          lval;
    float         fval;
    double        dval;
    bool          bval;

    stringVector sv;

    // If there is no ending tag then a short cut and there is no data.
    if( !noEndTag )
    {
      // Read strings until we get a '<' character.
      sv = ReadStringVector(in, '<');
    }

    int minSize = (tagLength == 0) ? (int)sv.size() :
                  ((tagLength < (int)sv.size()) ? tagLength : (int)sv.size());

    // All 20, or whatever, cases.
    switch(type)
    {
    case CHAR_NODE:
        // Read a character.
        if(minSize > 0)
        {
            mn[tagName] =  sv[0][0];
        }
        break;
    case UNSIGNED_CHAR_NODE:
        // Read an int and turn it into an unsigned character.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%d", &ival);
            uval = (unsigned char)ival;
            mn[tagName]  = uval;
        }
        break;
    case INT_NODE:
        // Read an int.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%d", &ival);
            mn[tagName]  = ival;
        }
        break;
    case LONG_NODE:
        // Read a long.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%ld", &lval);
            mn[tagName]  = lval;
        }
        break;
    case FLOAT_NODE:
        // Read a float.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%g", &fval);
            mn[tagName]  = fval;
        }
        break;
    case DOUBLE_NODE:
        // Read a double.
        if(minSize > 0)
        {
            sscanf(sv[0].c_str(), "%lg", &dval);
            mn[tagName]  = dval;
        }
        break;
    case STRING_NODE:
        { // new scope
            RemoveLeadAndTailQuotes(sv);
            std::string temp;
            for(i = 0; i < minSize; ++i)
            {
                temp += sv[i];
                if(i < (minSize - 1))
                    temp += " ";
            }
            mn[tagName] = temp;
        }
        break;
    case BOOL_NODE:
        { // new scope
            bval = false;
            if(minSize > 0)
            {
                bval = (sv[0] == "true");
            }
            mn[tagName] = bval;
        }
        break;
    case CHAR_VECTOR_NODE:
        { // new scope
            charVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%c", &cval);
                    temp.push_back(cval);
                }

                mn[tagName] = temp;
            }
        }
        break;
    case UNSIGNED_CHAR_VECTOR_NODE:
        { // new scope
            unsignedCharVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%d", &ival);
                    uval = (unsigned char)ival;
                    temp.push_back(uval);
                }

                mn[tagName] = temp;
            }
        }
        break;
    case INT_VECTOR_NODE:
        { // new scope
            intVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%d", &ival);
                    temp.push_back(ival);
                }

                mn[tagName] = temp;
            }
        }
        break;
    case LONG_VECTOR_NODE:
        { // new scope
            longVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%ld", &lval);
                    temp.push_back(lval);
                }

                mn[tagName] = temp;
            }
        }
        break;
    case FLOAT_VECTOR_NODE:
        { // new scope
            floatVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%g", &fval);
                    temp.push_back(fval);
                }

                mn[tagName] = temp;
            }
        }
        break;
    case DOUBLE_VECTOR_NODE:
        { // new scope
            doubleVector temp;
            if(minSize > 0)
            {
                for(i = 0; i < minSize; ++i)
                {
                    sscanf(sv[i].c_str(), "%lg", &dval);
                    temp.push_back(dval);
                }

                mn[tagName] = temp;
            }
        }
        break;
    case STRING_VECTOR_NODE:
        if(minSize > 0)
        {
            RemoveLeadAndTailQuotes(sv);
            mn[tagName] = sv;
        }
        break;
    case BOOL_VECTOR_NODE:
        { // new scope
            boolVector temp;
            if(minSize > 0)
            {
                for (i = 0; i < minSize; ++i)
                    temp.push_back(sv[i] == "true");
                mn[tagName] = temp;
            }
        }
        break;
    default:
        break;
    }
}


// ****************************************************************************
// Method: ConfigManager::ReadMapNodeFields
//
// Purpose:
//   Reads data from the input stream and stores the fields in the MapNode.
//
// Arguments:
//   in:        The input stream.
//   mn:        The MapNode used to store the field.
//   te:        Indicates tag is and end tag.
//
// Programmer: Kathleen Biagas
// Creation:   November 28, 2018
//
// Modifications:
//
// ****************************************************************************

void
ConfigManager::ReadMapNodeFields(std::istream &in, MapNode &mn, bool &te)
{
    bool tagIsEndTag = false;
    bool noEndTag = false;
    std::string  tagName;
    NodeTypeEnum tagType = INTERNAL_NODE;
    int          tagLength = 0;


    ReadTag(in, tagName, tagType, tagLength,
                          tagIsEndTag, noEndTag);
    if (tagIsEndTag)
    {
        // MapNode is empty
        te = true;
        return;
    }

    while (!tagIsEndTag)
    {
        ReadMapNodeFieldData(in, mn, tagName, tagType, tagLength, noEndTag);
        // Consume the end tag for the Field object
        stringVector sv = ReadStringVector(in,'>');
        ReadTag(in, tagName, tagType, tagLength, tagIsEndTag, noEndTag);
    }

    te = true;
}

