root/src/JsonReader.cpp

////////////////////////////////////////////////////////////////////////
// Copyright (c) Nehmulos 2011-2014
// This file is part of N0 Strain Serialization Library.
//
// N0Strain-Serialization-Library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// N0Strain-Serialization-Library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with N0Strain-Serialization-Library.  If not, see https://gnu.org/licenses/lgpl-3.0
////////////////////////////////////////////////////////////////////////

#include "JsonReader.h"

namespace nw {

JsonReader::JsonReader(const String filepath)
{
        MarkupReader::init(filepath);
}

JsonReader::JsonReader(std::istream& input) {
        MarkupReader::init();
        parseJson(input);
}

JsonReader::~JsonReader()
{
        //Don't need 2 close the file, as we only opened it only for readWholeDocument
}

int JsonReader::readWholeDocument(std::istream& file) {
        return parseJson(file);
}

int JsonReader::parseJson(std::istream& file) {

        char c;
        char lastChar = '0'; //avoid using random value from memory
        char quoteType = 'x';
        char unicodeEscapeSequence[4];
        unsigned int unicodeEscapeSequenceIndex = 0;
        std::stringstream tagname;
        std::stringstream tagvalue;
        Tag* selectedTag = NULL;
        parentTag = motherTag;
        unsigned int level = 0;
        unsigned int lineCount = 1;
        ReaderState state = lookingForObjects;
        while(!file.eof())
        {
                file.get(c);

                if(c == '\n')
                        ++lineCount;

                if(c == '\r')
                {
                        if(state != ReadingValueObject && state != ReadingString)
                                continue;
            //else
                //errorMessage << "a caged \r appeared!\n";
                }

// Comments are not allowed in json, don't mix it up with javascript!
//              if(c == '/')
//              {
//                      if(state != ReadingValueObject && state != ReadingString && state != endOfLine && state != ReadingCppComment)
//                      {
//                              if(lastChar == '/')
//                              {
//                                      state = commentType;
//                                      break;
//                              }
//                              //C style Comment
//                              else if(lastChar == '*')
//                              {
//                                      state = commentType;
//                                      break;
//                              }
//                      }
//              }
                switch(state)
                {
                case lookingForObjects:
                {
                        if(c == '{' || c == '[')
                        {
                                if(level == 0)
                                {
                                        parentTag = motherTag;
                                        level++;
                                }
                                else
                                {
                                        parentTag->addNewChild("");
                                        parentTag = parentTag->getLastChild();
                                }
                                level++;
                        }
                        else if(c == '}' || c == ']')
                        {
                                if(level != 0 )
                                {
                                        level--;
                                        pop();
                                }
                                else
                                {
                                        return 89;
                                }
                        }
                        //is part of a name
                        else if(c != ',' && !nwIsSeparator(c) )
                        {
                                if(c == '"' || c == '\'')
                                {
                                        quoteType = c;
                                }
                                else
                                {
                                        quoteType = 'x';
                                        tagname << c;
                                }
                                state = ReadingName;
                        }
                }
                break;
                case ReadingName:
                {
                        // name:
                        if(quoteType == 'x')
                        {
                                if(c == ':')
                                {
                                        state = identifyingObject;
                                        selectedTag = parentTag->addNewChild(tagname.str());
                                        tagname.str("");
                                }
                                else if(c == ',')
                                {
                                        state = lookingForObjects;
                                        parentTag->addNewChild("", tagname.str());
                                        tagname.str("");
                                }
                                else if(c == ']' || c == '}')
                                {
                                        state = lookingForObjects;
                                        parentTag->addNewChild("", tagname.str());
                                        tagname.str("");
                                        if(level != 0 )
                                        {
                                                level--;
                                                pop();
                                        }
                                        else
                                        {
                                                return 89;
                                        }
                                }
                                else
                                {
                                        tagname << c;
                                }
                        }
                        // "name"...
                        else if(c == quoteType && lastChar != '\\')
                        {
                                //search for ':'
                                quoteType = 'x';
                                state = identifyingObject;
                        }
                        // nam...
                        else
                        {
                                tagname << c;
                        }
                }
                break;
                case identifyingObject:
                {
                        if(!nwIsSeparator(c))
                        {
                                if(c == ':')
                                {
                                        state = identifyingObject;
                                        selectedTag = parentTag->addNewChild(tagname.str());
                                        tagname.str("");
                                }
                                else if(c == '\"' || c == '\'')
                                {
                                        quoteType = c;
                                        state = ReadingString;
                                }
                                else if(c == '{' || c == '[')
                                {
                                        level++;
                                        parentTag = selectedTag;
                                        state = lookingForObjects;
                                }
                                else if(c == ',') // used for ValueArrays
                                {
                                        state = lookingForObjects;
                                        parentTag->addNewChild("", tagname.str());
                                        tagname.str("");
                                }
                else if (c == ']') // used for ValueArrays
                {
                    parentTag->addNewChild("", tagname.str());
                    tagname.str("");
                    if(level != 0 )
                    {
                        level--;
                        pop();
                        state = endOfLine;
                    }
                    else
                    {
                        return 89;
                    }
                }
                                else
                                {
                                        state = ReadingValueObject;
                                        tagvalue << c;
                                }
                        }
                }
                break;
                case ReadingString:
                {
                        //end of String "value"...
                        if(c == '\\')
                        {
                                state = ReadingStringEscapeSequence;
                        }
                        else if(c == quoteType && lastChar != '\\')
                        {
                                state = endOfLine;
                                selectedTag->setValue(tagvalue.str());
                                tagvalue.str("");
                        }
                        else
                        {
                                tagvalue << c;
                        }
                }
                break;
                case ReadingStringEscapeSequence:
                {
                    if (c == 'u')
                    {
                        state = ReadingUnicodeEscapeSequence;
                    }
                    else
                    {
                        if (c == '"' || c == '\\' || c == '/')
                            {
                                tagvalue << c;
                            }
                        else if (c == 't')
                            {
                                tagvalue << '\t';
                            }
                        else if (c == 'n')
                            {
                                tagvalue << '\n';
                            }
                        else if (c == 'r')
                            {
                                tagvalue << '\r';
                            }
                        state = ReadingString;
                        // TODO handle f b c
                    }
                }
                break;
                case ReadingUnicodeEscapeSequence:
                {
                    unicodeEscapeSequence[unicodeEscapeSequenceIndex] = c;
                    ++unicodeEscapeSequenceIndex;

                    if (unicodeEscapeSequenceIndex > 3)
                    {
                        const int w = -1;
                        static const int hexToValueTable[] = {
                            w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,
                            w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,
                            w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  0,  1,  2,  3,  4,  5,  6,  7,  8,
                            9,  w,  w,  w,  w,  w,  w,  w, 10, 11, 12, 13, 14, 15,  w,  w,  w,  w,  w,
                            w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,
                            w,  w, 10, 11, 12, 13, 14, 15,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,
                            w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w,  w
                        };

                        unsigned int value = 0;
                        for (int i=0; i<4; ++i) {
                            const char encodeChar = hexToValueTable[unicodeEscapeSequence[i]];
                            if (encodeChar == w) {
                                errorMessage << "error: invalid Hex char '" << c
                                             << "' in unicode escape sequence "
                                             << std::string(unicodeEscapeSequence, 4)
                                             << "\n";
                            }
                            value += encodeChar << ((3-i)*4);
                        }
                        tagvalue << nw::utils::utf8FromIntCode(value);
                        state = ReadingString;
                        unicodeEscapeSequenceIndex = 0;
                    }
                }
                break;
                case ReadingValueObject:
                {
                        if(c == ',')
                        {
                                if(!selectedTag)
                                        selectedTag = parentTag->addNewChild("");
                                selectedTag->setValue(tagvalue.str());
                                tagvalue.str("");
                                selectedTag = NULL; //#ifdef debug
                                state = lookingForObjects;
                        }
                        else if(nwIsSeparator(c))
                        {
                                if(!selectedTag)
                                        selectedTag = parentTag->addNewChild("");
                                selectedTag->setValue(tagvalue.str());
                                tagvalue.str("");
                                selectedTag = NULL; //#ifdef debug
                                state = endOfLine;
                        }
                        else if(c == '}' || c == ']')
                        {
                                if(level != 0 )
                                {
                                        if(!selectedTag)
                                                selectedTag = parentTag->addNewChild("");
                                        selectedTag->setValue(tagvalue.str());
                                        tagvalue.str("");
                                        selectedTag = NULL; //#ifdef debug

                                        level--;
                                        pop();
                                        state = endOfLine;
                                }
                                else
                                {
                                        return 89;
                                }
                        }
                        else
                        {
                                tagvalue << c;
                        }
                }
                break;
                case endOfLine:
                {
                        //wait 'til the end of the line
                        if(c == ',')
                        {
                                state = lookingForObjects;
                        }
                        else if(c == '}' || c == ']')
                        {
                                if(level != 0 )
                                {
                                        level--;
                                        pop();
                                        state = endOfLine;
                                }
                                else
                                {
                                        return 89;
                                }
                        }
                        else if( !nwIsSeparator(c) )
                        {
                                if(c == '/')
                                {
                                        state = commentType;
                                }
                                else
                                {
                                        tagvalue << c;
                                        Tag* lastTag = parentTag ? (parentTag->getLastChild() ? parentTag->getLastChild() : parentTag) : NULL;
                                        if (lastTag) {
                                                errorMessage << "Expected comma after \"" << lastTag->getName();
                                                if (lastTag->getParent()) {
                                                         errorMessage << "\" in \"" << parentTag->getName() << '\"';
                                                }
                                                errorMessage << "\n";
                                        } else {
                                                errorMessage << "Expected comma not found";
                                        }

                                        // assume there would be a comma
                                        state = lookingForObjects;
                                }
                        }
                }
                break;
                case commentType:
                {
                        //C++ style Comment
                        if(c == '/')
                        {
                                state = ReadingCppComment;
                        }
                        //C style Comment
                        else if(c == '*')
                        {
                                state = ReadingCcomment;
                        }
                        else
                        {
                                errorMessage << "Waring! Invalid Comment type!\n";
                                state = ReadingCppComment;
                        }
                }
                break;
                case ReadingCcomment:
                {
                        if(lastChar == '*' && c == '/')
                        {
                                state = endOfLine;
                        }
                }
                break;
                case ReadingCppComment:
                {
                        if(c == SYSTEM_NEW_LINE)
                                state = endOfLine;
                }
                break;
                default:
                break;
                }
        }

        if(state != endOfLine)
        {
                errorMessage << "Json file closed unexpectedly\n";
        }

        parentTag = motherTag;
        return 0;
}

void JsonReader::describeArray(const String arrayName, const String elementsName, unsigned int size)
{
        MarkupReaderArray array;
        //TODO push already inited in the motherta
    if (!arrayName.empty() && push(arrayName)) {
        array.hasWrapperTag = true;
    } else {
        array.hasWrapperTag = false;
    }

        if(parentTag != NULL)
        {
                std::vector<Tag*> children = this->parentTag->getChildren();
                int numberOfChildren = children.size();
                std::stack<Tag*> tags;
                for (int i = numberOfChildren-1; i > -1; --i)
                {
                        if(children.at(i)->getName() == "")
                        {
                                tags.push(children.at(i));
                        }
                }
                array.tags = tags;
                array.isValueArray = false;
        }
        this->tagArrays.push(array);
}

void JsonReader::describeValueArray(const String arrayName, unsigned int size)
{
        describeArray(arrayName, "", size);
        tagArrays.top().isValueArray = true;
}

}  // namespace nw