// An XML file reader
//
// Copyright (C) 2004-2019 Sam Varner
//
// This file is part of Vamos Automotive Simulator.
//
// Vamos is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vamos 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vamos. If not, see <http://www.gnu.org/licenses/>.
#ifndef _XML_PARSER_H_
#define _XML_PARSER_H_
#include <fstream>
#include <string>
#include <vector>
namespace Vamos_Media
{
struct XML_Exception : std::exception
{
XML_Exception(std::string file, int line, std::string message)
: message("File: " + file + " Line: " + std::to_string(line) + ' ' + message) {}
virtual const char* what() const noexcept override { return message.c_str(); }
std::string message;
};
struct No_XML_File : public XML_Exception
{
No_XML_File(std::string file)
: XML_Exception(file, 0, "No such file.") {}
};
struct No_Declaration : public XML_Exception
{
No_Declaration(std::string file, int line, std::string message)
: XML_Exception(file, line, message) {}
};
struct Bad_Tag_Type : public XML_Exception
{
Bad_Tag_Type(std::string file, int line, std::string message)
: XML_Exception(file, line, message) {}
};
struct Tag_Mismatch : public XML_Exception
{
Tag_Mismatch(std::string file, int line, std::string message)
: XML_Exception(file, line, message) {}
};
struct XML_Unterminated
{
XML_Unterminated(int lines, std::string text, bool eof, char delimiter)
: lines(lines), text(text), eof(eof), delimiter(delimiter){};
int lines;
std::string text;
bool eof;
char delimiter;
};
struct Unterminated_Tag : public XML_Unterminated
{
Unterminated_Tag(int lines, std::string text, bool eof)
: XML_Unterminated(lines, text, eof, '>'){};
};
struct Unterminated_Attribute : public XML_Unterminated
{
Unterminated_Attribute(int lines, std::string text, bool eof)
: XML_Unterminated(lines, text, eof, '"'){};
};
class XML_Tag
{
public:
enum Tag_Type {NONE, START, END, EMPTY, PROCESSING_INSTRUCTION, COMMENT};
struct Attribute
{
Attribute(std::string name, std::string value) : name(name), value(value) {}
std::string name;
std::string value;
};
using V_Attribute = std::vector<Attribute>;
XML_Tag(std::ifstream& stream);
Tag_Type get_type() const { return m_type; }
int get_lines() const { return m_lines; }
const V_Attribute& get_attributes() const { return m_attributes; }
std::string get_data() const { return m_data; }
std::string get_text() const { return m_text; }
std::string get_label() const { return m_label; }
private:
using Str_Iter = std::string::iterator;
Tag_Type m_type;
int m_lines;
V_Attribute m_attributes;
std::string m_data;
std::string m_text;
std::string m_label;
// Read everything up to the next '<'. Return true if '<' was found.
bool read_to_tag_start(std::ifstream& stream);
// Read everything up to the next `>'. Return true if '>' was found.
bool read_to_tag_end(std::ifstream& stream);
// Determine the type of the tag.
Tag_Type find_tag_type(std::ifstream& stream);
// Determine the tag's label.
std::string find_label(Str_Iter text_start, Str_Iter text_end);
// Get the next character from the stream.
std::ifstream& get_next_char(std::ifstream& stream, char& ch);
// Parse attributes.
void find_attributes(Str_Iter attr_begin, Str_Iter attr_end);
// Throw out characters inside a comment.
void eat_comment(std::ifstream& stream);
bool find_comment_end(std::ifstream& stream);
void skip_spaces(Str_Iter& text_start);
Attribute get_attribute(Str_Iter text_start, Str_Iter text_end);
void get_text_boundries(Str_Iter& text_start, Str_Iter& text_end);
};
class XML_Path
{
public:
void push(std::string element) { m_path += '/' + element; }
void drop() { m_path = m_path.substr(0, m_path.find_last_of("/")); }
bool empty() const { return m_path.empty(); }
bool match(std::string pattern) const;
std::string path() const { return m_path; }
std::string subpath(size_t n) const;
std::string top() const { return subpath(1); }
private:
std::string m_path;
};
class XML_Parser
{
public:
XML_Parser();
virtual ~XML_Parser();
void read(std::string file);
void error(std::string message);
bool match(std::string pattern) const { return m_path.match(pattern); }
std::string path() const { return m_path.path(); }
std::string label() const { return m_path.top(); }
// Event handlers overridden by derived classes.
virtual void on_start_tag(const XML_Tag& tag) {};
virtual void on_end_tag(const XML_Tag& tag) {};
virtual void on_data(std::string data_string) = 0;
private:
void check_declaration();
void read_document();
bool run_callbacks(const XML_Tag& tag);
void add_tag(const XML_Tag& tag);
void remove_tag(const XML_Tag& tag);
void handle_unterminated(XML_Unterminated& unterminated);
std::string m_file;
std::ifstream* mp_stream;
int m_line;
XML_Path m_path;
};
} // namespace Vamos_Media
#endif // not _XML_PARSER_H_