From 7a627893824b3abd26bbc411fe38e59351450fe9 Mon Sep 17 00:00:00 2001 From: Thraix Date: Fri, 11 Oct 2019 22:00:20 +0200 Subject: [PATCH] Port XML from Greet-Engine --- Makefile | 22 ++- src/Utils.cpp | 29 ++++ src/Utils.h | 6 + src/xml/XML.cpp | 35 +++++ src/xml/XML.h | 9 ++ src/xml/XMLException.h | 22 +++ src/xml/XMLObject.cpp | 335 +++++++++++++++++++++++++++++++++++++++++ src/xml/XMLObject.h | 59 ++++++++ 8 files changed, 509 insertions(+), 8 deletions(-) create mode 100644 src/xml/XML.cpp create mode 100644 src/xml/XML.h create mode 100644 src/xml/XMLException.h create mode 100644 src/xml/XMLObject.cpp create mode 100644 src/xml/XMLObject.h diff --git a/Makefile b/Makefile index bc32c18..a63c9fc 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# This Makefile was generated using MakeGen v1.1.8 made by Tim HÃ¥kansson +# This Makefile was generated using MakeGen v1.2.0 made by Tim HÃ¥kansson # and is licensed under MIT. Full source of the project can be found at # https://github.com/Thraix/MakeGen CC=@g++ @@ -7,7 +7,7 @@ MKDIR_P=mkdir -p BIN=bin/ OBJPATH=$(BIN)intermediates INCLUDES= -OBJECTS=$(OBJPATH)/ConfigCLI.o $(OBJPATH)/ConfigFile.o $(OBJPATH)/HFileGen.o $(OBJPATH)/IncludeDeps.o $(OBJPATH)/Makefile.o $(OBJPATH)/Utils.o $(OBJPATH)/main.o +OBJECTS=$(OBJPATH)/ConfigCLI.o $(OBJPATH)/ConfigFile.o $(OBJPATH)/HFileGen.o $(OBJPATH)/IncludeDeps.o $(OBJPATH)/Makefile.o $(OBJPATH)/Utils.o $(OBJPATH)/main.o $(OBJPATH)/XML.o $(OBJPATH)/XMLObject.o CFLAGS=$(INCLUDES) -std=c++17 -c -w -g3 -D_DEBUG LIBDIR= LDFLAGS= @@ -34,23 +34,29 @@ install: all $(info Installing MakeGen to /usr/bin/) @cp $(OUTPUT) /usr/bin/makegen $(OBJPATH)/ConfigCLI.o : src/ConfigCLI.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h - $(info -[14%]- $<) + $(info -[11%]- $<) $(CC) $(CFLAGS) -o $@ $< $(OBJPATH)/ConfigFile.o : src/ConfigFile.cpp src/ConfigFile.h src/FileUtils.h src/Common.h src/Utils.h - $(info -[28%]- $<) + $(info -[22%]- $<) $(CC) $(CFLAGS) -o $@ $< $(OBJPATH)/HFileGen.o : src/HFileGen.cpp src/FileUtils.h src/Common.h src/Utils.h src/ConfigFile.h src/HFileGen.h - $(info -[42%]- $<) + $(info -[33%]- $<) $(CC) $(CFLAGS) -o $@ $< $(OBJPATH)/IncludeDeps.o : src/IncludeDeps.cpp src/Common.h src/IncludeDeps.h src/ConfigFile.h src/FileUtils.h src/Utils.h - $(info -[57%]- $<) + $(info -[44%]- $<) $(CC) $(CFLAGS) -o $@ $< $(OBJPATH)/Makefile.o : src/Makefile.cpp src/IncludeDeps.h src/ConfigFile.h src/FileUtils.h src/Common.h src/Utils.h src/Makefile.h - $(info -[71%]- $<) + $(info -[55%]- $<) $(CC) $(CFLAGS) -o $@ $< $(OBJPATH)/Utils.o : src/Utils.cpp src/FileUtils.h src/Common.h src/Utils.h src/ConfigFile.h - $(info -[85%]- $<) + $(info -[66%]- $<) $(CC) $(CFLAGS) -o $@ $< $(OBJPATH)/main.o : src/main.cpp src/Common.h src/ConfigCLI.h src/ConfigFile.h src/FileUtils.h src/Utils.h src/HFileGen.h src/Makefile.h src/Timer.h + $(info -[77%]- $<) + $(CC) $(CFLAGS) -o $@ $< +$(OBJPATH)/XML.o : src/xml/XML.cpp src/xml/XML.h src/xml/XMLObject.h src/xml/XMLException.h + $(info -[88%]- $<) + $(CC) $(CFLAGS) -o $@ $< +$(OBJPATH)/XMLObject.o : src/xml/XMLObject.cpp src/xml/XMLException.h src/xml/XMLObject.h $(info -[100%]- $<) $(CC) $(CFLAGS) -o $@ $< diff --git a/src/Utils.cpp b/src/Utils.cpp index 899559f..df74266 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -95,3 +95,32 @@ void Utils::GetHFiles(const std::string& dependencyDir, const ConfigFile& conf, } } } +bool Utils::IsWhiteSpace(char c) +{ + return c == '\n' || c == '\t' || c == '\r' || c == ' ' || c == '\t'; +} + +bool Utils::IsLetter(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +bool Utils::IsWord(const std::string& string) +{ + for(auto it{string.begin()}; it != string.end();++it) + { + if(!IsLetter(*it)) + return false; + } + return true; +} + +std::string Utils::GetWord(const std::string& string, int startPos) +{ + if (startPos >= string.length()) + throw std::runtime_error("start position out of bounds."); + + int endPos = startPos; + while (endPos < string.length() && IsLetter(string[endPos])) endPos++; + return string.substr(startPos, endPos - startPos); +} diff --git a/src/Utils.h b/src/Utils.h index cacedac..572f539 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -30,4 +30,10 @@ struct Utils static void GetCppFiles(const ConfigFile& conf, std::set& cppFiles); static void GetCppAndHFiles(const ConfigFile& conf, std::set& hFiles, std::set& cppFiles); static void GetHFiles(const std::string& dependencyDir, const ConfigFile& conf, std::set& hFiles); + + // Used for parsing xml + static bool IsWhiteSpace(char c); + static bool IsLetter(char c); + static bool IsWord(const std::string& string); + static std::string GetWord(const std::string& string, int startPos = 0); }; diff --git a/src/xml/XML.cpp b/src/xml/XML.cpp new file mode 100644 index 0000000..9e8b807 --- /dev/null +++ b/src/xml/XML.cpp @@ -0,0 +1,35 @@ +#include "XML.h" + +#include "XMLException.h" + +#include +#include + +XMLObject XML::FromString(const std::string& string, const std::string& filename="") +{ + int startLine = 1; + int startPos = 0; + // Remove version tag. + if(string.find("") + 3; + startLine = std::count(string.begin(), string.begin()+startPos, '\n') + 1; + } + return XMLObject(string, startPos, startLine,filename); +} + +XMLObject XML::FromFile(const std::string& filename) +{ + std::ifstream file(filename, std::ios::binary | std::ios::ate); + if(!file) + throw XMLException("Could not read file \""+filename+"\""); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::string buffer; + buffer.reserve(size); + while (!file.eof()) + { + buffer += file.get(); + } + return FromString(buffer, filename); +} diff --git a/src/xml/XML.h b/src/xml/XML.h new file mode 100644 index 0000000..2bf9b5c --- /dev/null +++ b/src/xml/XML.h @@ -0,0 +1,9 @@ +#pragma once + +#include "XMLObject.h" + +struct XML +{ + static XMLObject FromString(const std::string& string, const std::string& filename); + static XMLObject FromFile(const std::string& fileName); +}; diff --git a/src/xml/XMLException.h b/src/xml/XMLException.h new file mode 100644 index 0000000..ade26f9 --- /dev/null +++ b/src/xml/XMLException.h @@ -0,0 +1,22 @@ +#pragma once + +#include "XMLObject.h" + +#include +#include + +class XMLException : public std::exception +{ + private: + std::string m_message; + public: + explicit XMLException(const std::string& message) : m_message("XMLException: " + message) {} + explicit XMLException(const std::string& message, const XMLObject::XMLLoadData& data) + : m_message("XMLException(" + data.file + ":" + std::to_string(data.line) + "): " + message) + {} + + virtual const char* what() const throw() + { + return m_message.c_str(); + } +}; diff --git a/src/xml/XMLObject.cpp b/src/xml/XMLObject.cpp new file mode 100644 index 0000000..649214e --- /dev/null +++ b/src/xml/XMLObject.cpp @@ -0,0 +1,335 @@ +#include "XMLObject.h" + +#include "../Common.h" +#include "../Utils.h" + +#include +#include "XMLException.h" + +XMLObject::XMLObject(const std::string& string) +{ + int pos = 0; + int line = 1; + XMLLoadData data{pos, line, ""}; + if(!ReadHead(string, data)) + ReadBodyTail(string, data); +} + +XMLObject::XMLObject(const std::string& string, int pos, int line, const std::string& file) +{ + XMLLoadData data{pos, line, file}; + if (!ReadHead(string, data)) + ReadBodyTail(string, data); +} + +XMLObject::XMLObject(const std::string& string, XMLLoadData& data) +{ + if (!ReadHead(string, data)) + ReadBodyTail(string, data); +} + +XMLObject::XMLObject(const std::string& name, const std::map& attributes, const std::string& text) + :name(name), attributes(attributes), text(text) +{ + +} + +XMLObject::XMLObject(const std::string& name, const std::map& attributes, const std::vector& objects) + : name(name), attributes(attributes), objects(objects) +{ + +} + +bool XMLObject::HasAttribute(const std::string& property) const +{ + return attributes.find(property) != attributes.end(); +} + +const std::string& XMLObject::GetAttribute(const std::string& property) const +{ + auto it = attributes.find(property); + if (it == attributes.end()) + throw XMLException((std::string("Attribute could not be found \"") + property + "\".").c_str()); + return it->second; +} + +const std::string& XMLObject::GetAttribute(const std::string& property, const std::string& defaultValue) const +{ + auto it = attributes.find(property); + if (it == attributes.end()) + return defaultValue; + return it->second; +} + +unsigned int XMLObject::GetObjectCount() const +{ + return objects.size(); +} + +const XMLObject& XMLObject::GetObject(unsigned int i) const +{ + if (i >= objects.size()) + throw XMLException((std::string("XML index out of bounds \"") + std::to_string(i) + "\".").c_str()); + + return objects[i]; +} + +const std::vector& XMLObject::GetObjects() const +{ + return objects; +} + +const std::string& XMLObject::GetName() const +{ + return name; +} + +const std::string& XMLObject::GetText() const +{ + return text; +} + +void XMLObject::SetName(const std::string& name) +{ + if(Utils::IsWord(name)) + this->name = name; + else + LOG_ERROR("XML Head can only be made up of letters"); +} + +void XMLObject::SetText(const std::string& text) +{ + this->text = text; +} + +void XMLObject::AddAttribute(const std::string& property, const std::string& value) +{ + if(Utils::IsWord(property)) + attributes.emplace(property, value); + else + LOG_ERROR("XML property name can only be made up of letters"); + +} + +XMLObject XMLObject::GetStrippedXMLObject() const +{ + if(text == "") + return XMLObject(name, attributes, objects); + else + return XMLObject(name, attributes, text); +} + +//////////////////////////////////////////////////////////// +// // +// Everything below here handles the reading of xml files // +// // +//////////////////////////////////////////////////////////// + + +bool XMLObject::ReadHead(const std::string& string, XMLLoadData& data) +{ + // Check if the first character is the start of and xml tag. + ReadWhiteSpace(string, data); + if (string[data.pos] != '<') + throw XMLException("Not an XML Object.", data); + + // Check if there is a closing tag + size_t closing = string.find('>'); + if (closing == std::string::npos) + throw XMLException("No enclosing > for opening tag.", data); + + // Read the name of the tag + ReadName(string, data); + + // Read all attributes of the xml tag + ReadAttributes(string, data); + + // Read opening tag + if (string[data.pos] == '/') + { + data.pos++; + ReadWhiteSpace(string, data); + if (string[data.pos] != '>') + throw XMLException((std::string("Invalid character proceeding / in opening XML Tag \"") + string[data.pos] + "\".").c_str(), data); + data.pos++; + // nothing more to read. + return true; + } + + ReadWhiteSpace(string, data); + if (string[data.pos] != '>') + throw XMLException((std::string("Invalid character proceeding attributes in opening XML Tag \"") + string[data.pos] + "\".").c_str(), data); + (data.pos)++; + return false; +} + +void XMLObject::ReadName(const std::string& string, XMLLoadData& data) +{ + data.pos++; + ReadWhiteSpace(string, data); + if (!Utils::IsLetter(string[data.pos])) + throw XMLException("Invalid XML name. Can only contain letters.", data); + name = Utils::GetWord(string, data.pos); + data.pos += name.length(); + ReadWhiteSpace(string, data); + if (string[data.pos] != '/' && string[data.pos] != '>' && Utils::IsWhiteSpace(string[data.pos])) + { + throw XMLException((std::string("Invalid character proceeding name in XML Tag \"") + string[data.pos] + "\".").c_str(), data); + } +} + +void XMLObject::ReadAttributes(const std::string& string, XMLLoadData& data) +{ + ReadWhiteSpace(string, data); + + while (string[data.pos] != '>' && string[data.pos] != '/') + { + ReadAttribute(string, data); + } +} + +void XMLObject::ReadAttribute(const std::string& string, XMLLoadData& data) +{ + // Read property name + std::string property = ReadXMLName(string, data); + if (property.length() == 0) + throw XMLException((std::string("Invalid character proceeding name \"") + string[data.pos] + "\".").c_str(), data); + if (attributes.count(property) > 0) + throw XMLException((std::string("Duplicate property in XML tag \"") + property + "\".").c_str(), data); + data.pos += property.length(); + ReadWhiteSpace(string, data); + + // Read = + if (string[data.pos] != '=') + throw XMLException((std::string("Invalid character proceeding property name in XML Tag \"") + string[data.pos] + "\".").c_str(), data); + (data.pos)++; + ReadWhiteSpace(string, data); + + // Read value + if (string[data.pos] != '\"') + throw XMLException("XML property value is not inside enclosing quotes.", data); + (data.pos)++; + int valueStart = data.pos; + while (string[data.pos] != '\"') (data.pos)++; + std::string value = string.substr(valueStart, (data.pos) - valueStart); + ReplacePredefinedEntities(value, data); + (data.pos)++; + attributes.emplace(property, value); + ReadWhiteSpace(string, data); +} + +void XMLObject::ReadBodyTail(const std::string& string, XMLLoadData& data) +{ + ReadWhiteSpace(string, data); + if (string[data.pos] != '<') + { + ReadText(string, data); + ReadWhiteSpace(string, data); + std::string closeTag = GetClosingTag(string, data); + if (closeTag.length() == 0) + throw XMLException("Tag after XML Test was not a closing tag. XMLObject doesn't support text and other XMLObjects at the same time.", data); + return; + } + // Check if we can read the closing tag. + std::string closeTag = GetClosingTag(string, data); + while (closeTag.length() == 0) + { + XMLObject object = XMLObject(string, data); + objects.push_back(object); + ReadWhiteSpace(string, data); + closeTag = GetClosingTag(string, data); + } +} + +void XMLObject::ReadText(const std::string& string, XMLLoadData& data) +{ + int startPos = data.pos; + while (string[data.pos] != '<') (data.pos)++; + text = string.substr(startPos, (data.pos) - startPos); + ReplacePredefinedEntities(text, data); +} + +void XMLObject::ReadWhiteSpace(const std::string& string, XMLLoadData& data) +{ + while (Utils::IsWhiteSpace(string[data.pos])) { + if (string[data.pos] == '\n') + (data.line)++; + (data.pos)++; + } +} + + +std::string XMLObject::GetClosingTag(const std::string& string, XMLLoadData& data) +{ + int startPos = data.pos; + int startLine = data.line; + if (string[(data.pos)++] != '<') + { + data.pos = startPos; + data.line = startLine; + return ""; + } + ReadWhiteSpace(string, data); + if (string[(data.pos)++] != '/') + { + data.pos = startPos; + data.line = startLine; + return ""; + } + ReadWhiteSpace(string, data); + std::string tag = Utils::GetWord(string, data.pos); + if (tag != name) + throw XMLException((std::string("Closing tag doesn't match opening tag. (\"") + name + "\" != \"" + tag+ "\")").c_str(), data); + data.pos += tag.length(); + ReadWhiteSpace(string, data); + if (string[data.pos] != '>') + throw XMLException((std::string("Invalid character in closing tag \"") + string[data.pos] + "\".").c_str(), data); + (data.pos)++; + return string.substr(startPos, (data.pos) - startPos); +} + +void XMLObject::ReplacePredefinedEntities(std::string& string, XMLLoadData& data) +{ + std::vector> entities + { + {""","\""}, + {"'", "\'"}, + {"<", "<"}, + {">",">"}, + {"&", "&"} + }; + size_t pos = string.find('&'); + while(pos != std::string::npos) + { + bool found = false; + for(auto entity : entities) + { + if(strncmp(&string[pos], entity.first.c_str(), entity.first.length()) == 0) + { + string.replace(pos, entity.first.length(), entity.second); + found = true; + } + } + if(!found) + LOG_ERROR("(" + data.file + ":" + std::to_string(data.line) + "): ""Ampersand found in xml but isn't a predefined entity."); + pos = string.find('&', pos+1); + } +} + +std::string XMLObject::ReadXMLName(const std::string& string, XMLLoadData& data) +{ + if(!(Utils::IsLetter(string[data.pos]) || + string[data.pos] == '_' || + string[data.pos] == ':')) + throw XMLException(std::string("Name doesn't start with a letter."), data); + + int endPos = data.pos + 1; + while (endPos < string.length() && ( + Utils::IsLetter(string[endPos]) || + string[endPos] == '_' || + string[endPos] == '-' || + string[endPos] == ':' || + string[endPos] == '.')) + endPos++; + return string.substr(data.pos, endPos - data.pos); +} diff --git a/src/xml/XMLObject.h b/src/xml/XMLObject.h new file mode 100644 index 0000000..6a2d2ff --- /dev/null +++ b/src/xml/XMLObject.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +class XMLObject +{ + public: + friend class XMLexception; + struct XMLLoadData + { + int pos; + int line; + const std::string& file; + }; + private: + std::string name; + std::string text; + + std::map attributes; + std::vector objects; + + public: + XMLObject() {} + XMLObject(const std::string& string); + XMLObject(const std::string& string, int pos, int line, const std::string& file); + XMLObject(const std::string& string, XMLLoadData& data); + XMLObject(const std::string& name, const std::map& properties, const std::string& text); + XMLObject(const std::string& name, const std::map& properties, const std::vector& objects); + + bool HasAttribute(const std::string& property) const; + const std::string& GetAttribute(const std::string& property) const; + const std::string& GetAttribute(const std::string& property, const std::string& defaultValue) const; + + unsigned int GetObjectCount() const; + const XMLObject& GetObject(unsigned int i) const; + const std::vector& GetObjects() const; + const std::string& GetName() const; + const std::string& GetText() const; + XMLObject GetStrippedXMLObject() const; + + void SetName(const std::string& name); + void SetText(const std::string& text); + void AddAttribute(const std::string& property, const std::string& value); + + private: + std::string GetClosingTag(const std::string& string, XMLLoadData& data); + // Returns true if the head contained closing tag. + bool ReadHead(const std::string& string, XMLLoadData& data); + void ReadName(const std::string& string, XMLLoadData& data); + void ReadAttribute(const std::string& string, XMLLoadData& data); + void ReadAttributes(const std::string& string, XMLLoadData& data); + void ReadBodyTail(const std::string& string, XMLLoadData& data); + void ReadText(const std::string& string, XMLLoadData& data); + void ReadWhiteSpace(const std::string& string, XMLLoadData& data); + void ReplacePredefinedEntities(std::string& string, XMLLoadData& data); + std::string ReadXMLName(const std::string& string, XMLLoadData& data); +};