////////////////////////////////////////////////////////////////////////
// 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