root/src/XmlReader.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 "XmlReader.h"

namespace nw {

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

XmlReader::XmlReader(std::istream& input)
{
    MarkupReader::init();
    readWholeDocument(input);
}

XmlReader::~XmlReader() {

}

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

int XmlReader::readWholeDocument(std::istream& input)
{
        int error = parseXml(input);
        parentTag = motherTag;

        if(error != 0)
        {
                errorMessage << "It fucked up!\n";
                switch (error) {
                        case 1:
                                errorMessage << "The file closed unexpectedly!\n";
                                errorMessage << "Seems like it's to short, broken, or just bad Xml\n";
                                break;
                        case 2:
                                errorMessage << "A CloseTag doesn't match!\n";//TODO say which close tag
                                errorMessage << "In a later " N0Slib_Name " version we will tell you which CloseTag\n";
                                errorMessage << "So, for now, please use a XmlValidator and keep looking for newer " N0Slib_Name " versions\n";
                                break;
                        default:
                                errorMessage << "The reason is unknown the Error code is " << error << '\n';
                                errorMessage << "Please Contact the " N0Slib_Name " developer \n";
                                errorMessage << "and report a bug sending an example Xml and your " N0Slib_Name " version\n";
                                break;
                }
        }
        return error;
}


//nearly 500 lines for 1 function ... 
//TODO some validation would be fine
int XmlReader::parseXml(std::istream&   file)
{
        char c;
        PARSER_STATE state = searchNewTags;
        std::stringstream tagname, value;
        Tag* currentTag = NULL;
        Tag* definitions = NULL;
        char quoteType = '"';
        char lastCharOfComment;
        unsigned int currentLine = 1;
        while(!file.eof())
        {
                file.get(c);

                if(c == '\n')
                {
                        currentLine++; //linecounter
                }

                if(c == '\r')
                {
                        if(state != searchNewTags && state != readAttributeValue)
                                continue; // skip this iteration and jump to the next char
            //else
                //errorMessage << "A caged \r appeared!\n";
                }

                switch (state)
                {
                        case searchNewTags:
                        {
                                //<tag>value</tag>
                                if(c == '<')
                                {
                                        state = readTagType;
                                }
                                else
                                {
                                        //TODO add boolean to filter newLines and tabs from Strings
                                        //Don't write a seperator, if the last char was a seperator
                                        //if(!this->trimTabsAndNewlines || (c == ' ' || !nwIsSeparator(c)))
                                                value << c;
                                }
                        }
                        break;
                        case readTagType:
                        {
                                //</closeTag>
                                if(c == '/')
                                {
                                        if(value.str().size() != 0)
                                        {
                                                if(currentTag != NULL)
                                                        currentTag->setValue(value.str());
                                                value.str("");
                                        }
                                        state = closeTag;
                                }
                                //<!-- comment -->
                                else if(c == '!')
                                {
                                        state = readCommentType;
                                }
                                //<?xml version="1.0"?>
                                else if(c == '?')
                                {
                                        state = readXmlVersion;
                                }
                                //<normalTag...
                                else if(!nwIsSeparator(c))
                                {
                                        //<hi>text<bye> other tag opens while the first one wasn't closed
                                        if(value.str().size() != 0)
                                        {
                                                //does the value only contain separator chars?
                                                String v = value.str();
                                                bool containsCharacterData;
                                                for (unsigned int i = 0; i < v.size(); ++i)
                                                {
                                                        if(!nwIsSeparator(v.at(i)))
                                                        {
                                                                containsCharacterData = true;
                                                                //Print Warning
                                                                String parentname;
                                                                String childname;
                                                                if(currentTag != NULL)
                                                                {
                                                                        currentTag->setValue(v);

                                                                        childname = currentTag->getName();
                                                                        if(currentTag->getParent() != NULL)
                                                                                parentname = currentTag->getParent()->getName();
                                                                }
                                                                errorMessage << "Warning: a new Tag opens in line " << currentLine
                                                                                << " directly after \"" << v << "\"\n";
                                                                errorMessage << "Warning! the tag <" << childname << "> in <" <<
                                                                                parentname << "> does not close properly\n";
                                                                errorMessage << "This file is maybe unreadable from here!\n";
                                                                break;
                                                        }
                                                }
                                                if(!containsCharacterData)
                                                {
                                                        value.str("");
                                                }
                                        }
                                        //no errors opening tag
                                        state = readTagName;
                                        tagname << c;
                                }
                        }
                        break;
                        case readTagName:
                        {
                                //<tag ...attributes
                                if(nwIsSeparator(c))
                                {
                                        //is first tag in file
                                        if(currentTag == NULL)
                                        {
                                                currentTag = new Tag(tagname.str());
                                        }
                                        else
                                        {
                                                currentTag->addNewChild(tagname.str());
                                                currentTag = currentTag->getLastChild();
                                        }
                                        tagname.str("");
                                        state = searchAttributes;
                                }
                                //<statement/>
                                else if(c == '/')
                                {
                                        state = closeTag;
                                }
                                //<nor...
                                else if(c != '>')
                                {
                                        tagname << c;
                                }
                                //<normalTag>
                                else
                                {
                                        //is first tag in file
                                        if(currentTag == NULL)
                                        {
                                                currentTag = new Tag(tagname.str());
                                        }
                                        else
                                        {
                                                currentTag->addNewChild(tagname.str());
                                                currentTag = currentTag->getLastChild();
                                        }
                                        tagname.str("");
                                        state = searchNewTags;
                                }
                        }
                        break;
                        case closeTag :
                        {
                                if(c == '>')
                                {
                                        state = searchNewTags;
                                        if(currentTag != NULL && currentTag->getParent() != NULL)
                                        {
                                                currentTag = currentTag->getParent();
                                        }
                                }
                        }
                        break;
                        //Read attributes as Tags, Xml style 2.0 baby!
                        case searchAttributes:
                        {
                                if(c == '/')
                                {
                                        state = closeTag;
                                }
                                else if(c == '>')
                                {
                                        state = searchNewTags;
                                }
                                else if(!nwIsSeparator(c))
                                {
                                        tagname << c;
                                        state = readAttributeName;
                                }
                        }
                        break;
                        case readAttributeName:
                        {
                                if(nwIsSeparator(c) || c == '=')
                                {
                                        //is first tag in file
                                        if(currentTag != NULL)
                                        {
                                                currentTag->addNewChild(tagname.str());
                                                currentTag = currentTag->getLastChild();
                                                //Don't enter this tag, as it can not have derived child
                                        }
                                        tagname.str("");
                                        state = assignAttribute;
                                }
                                else
                                {
                                        tagname << c;
                                }
                        }
                        break;
                        //TODO support CDATA in attributes
                        case assignAttribute:
                        {
                                if(c == '"' || c == '\'')
                                {
                                        state = readAttributeValue;
                                        quoteType = c;
                                }
                        }
                        break;
                        case readAttributeValue:
                        {
                                if(c == quoteType)
                                {
                                        currentTag->setValue(value.str());
                                        value.str("");
                                        //Enter the attribute-holding-tag
                                        currentTag = currentTag->getParent();
                                        state = searchAttributes;
                                }
                                else
                                {
                                        value << c;
                                }
                        }
                        break;
                        case readCommentType:
                        {
                                //<![CDATA[<br> and other content]]>
                                if(c == '[')
                                {
                                        state = cDataTagName;
                                }
                                //TODO read the second -, too.
                                //<!-- ...
                                else if (c == '-')
                                {
                                        state = xmlComment;
                                }
                                //<!DEFINITION...
                                else
                                {
                                        state = definitionTagName;
                                }
                        }
                        break;
                        case cDataTagName:
                        {
                                String cdataTagName = "CDATA[";
                                //We alread have the '['
                                bool failed = false;
                                for (int i = 0; i < 6; ++i)
                                {
                                        if(!file.eof()) {
                                                if(c != cdataTagName.at(i))
                                                {
                                                        state = xmlComment;
                                                        failed = true;
                                                        break;
                                                }
                                                if(i < 5) //prevent from fetching the first char after '['
                                                        file.get(c);
                                        }
                                }
                                if(!failed)
                                {
                                        state = cDataValue;
                                }
                                else
                                {
                                        // consider it as different type definition
                                        state = definitionTagName;
                                }
                        }
                        break;
                        case cDataValue:
                        {
                                //TODO horrible if else, close your eyes or turn to stone
                                if(c == ']')
                                {
                                        if(!file.eof())
                                        {
                                                file.get(c);
                                                if(c == ']')
                                                {
                                                        if(!file.eof())
                                                        {
                                                                file.get(c);
                                                                if(c == '>')
                                                                {
                                                                        state = searchNewTags;
                                                                }
                                                                else
                                                                {
                                                                        value << "]]" << c;
                                                                }
                                                        }
                                                }
                                                else
                                                {
                                                        value << ']' << c;
                                                }
                                        }
                                }
                                else
                                {
                                        value << c; //TODO don't add the last ]
                                }
                                lastCharOfComment = c;
                        }
                        break;
                        case xmlComment:
                        {
                                if(lastCharOfComment == '-' && c == '-')
                                {
                                        if(!file.eof())
                                        {
                                                file >> c;
                                                if(c == '>')
                                                {
                                                        state = searchNewTags;
                                                }
                                        }
                                }
                                lastCharOfComment = c;
                        }
                        break;
                        case readXmlVersion:
                        {
                                //TODO 3.0 may use the version somehow
                                if(quoteType == 'x' && (c == '\"' || c == '\''))
                                {
                                        quoteType = c;
                                }
                                else if(quoteType != 'x' && c == quoteType)
                                {
                                        quoteType = 'x';
                                }
                                //<?xml?>
                                else if(c == '>')
                                {
                                        state = searchNewTags;
                                }
                        }
                        break;
                        case definitionTagName:
                        {
                                if(nwIsSeparator(c))
                                {
                                        if(definitions == NULL)
                                        {
                                                definitions = new Tag(tagname.str());
                                        }
                                        else
                                        {
                                                definitions->addNewChild(tagname.str());
                                                definitions = definitions->getLastChild();
                                        }
                                        state = definitionValue;
                                        tagname.str("");
                                }
                                else
                                {
                                        tagname << c;
                                }
                        }
                        break;
                        case definitionValue:
                        {
                                //TODO call some parse doctype function
                                if(quoteType == 'x' && (c == '\"' || c == '\''))
                                {
                                        quoteType = c;
                                }
                                else if(quoteType != 'x' && c == quoteType)
                                {
                                        quoteType = 'x';
                                }
                                //<!DOCTYPE SOME [ <!THAT OTHER THING> ] VALUE>
                                else if(c == '<')
                                {
                                        definitions->setValue(value.str());
                                        value.str("");
                                        state = definitionTagName;
                                }
                                else if(c == '>')
                                {
                                        definitions->setValue(value.str());
                                        value.str("");
                                        if(definitions->getParent() == NULL)
                                        {
                                                state = searchNewTags;
                                        }
                                        else
                                        {
                                                definitions = definitions->getParent();
                                        }
                                }
                                else
                                {
                                        value << c;
                                }
                        }
                        break;
                        default:
                        break;
                }
        }
        //End of file
        if(state != searchNewTags)
        {
                errorMessage << "Warning! File ended unexpectedly\n";
        }
        if(currentTag != NULL)
        {
                while(currentTag->getParent() != NULL)
                {
                        currentTag = currentTag->getParent();
                }
        }
        if(motherTag != NULL)
        {
                delete motherTag;
        }
        motherTag = currentTag;

        //TODO do something with the doc type definitions
        delete definitions;
        return 0;
}

}  // namespace nw