#pragma once
#include
#include
#include
#include
#include
#include
// TODO: add optional support for non-quoted attribute values, and attributes without a value, so i can parse most html too.
// TODO: support the nested type values.
// TODO: add method for searching attribute_list
/*
* XmlParser based upon how the python 'html.parser' works.
*/
class XmlParser {
// - processing instructions
// handler
// handler
// handler
// handler
// handler
// handler
// ... data handler
// &entity;
public:
using attribute_list = std::vector;
virtual ~XmlParser() { }
// implement these methods when subclassing this parser
virtual void handle_starttag(std::string tag, const attribute_list& attrs) { }
virtual void handle_endtag(std::string tag) { }
virtual void handle_element(std::string tag, const attribute_list& attrs)
{
handle_starttag(tag, attrs);
handle_endtag(tag);
}
virtual void handle_xml_decl(std::string tag, const attribute_list& attrs) { }
virtual void handle_comment(const char*first, const char*last) { }
virtual void handle_data(const char*first, const char*last) { }
enum {
TOKEN_LESS_THEN,
TOKEN_LT_QUESTION,
TOKEN_LT_EXCLAMATION,
TOKEN_LT_SLASH,
//TOKEN_GREATER_THEN,
//TOKEN_QUESTION_GT,
//TOKEN_SLASH_GT,
TOKEN_NAME,
TOKEN_STRING,
TOKEN_EQUALS,
// note: no token for comment, cdata
};
struct Token {
virtual ~Token() { }
virtual int type() = 0;
virtual std::string name() const { return {}; }
virtual std::string value() const { return {}; }
};
struct LT_Token : Token {
int type() override { return TOKEN_LESS_THEN; }
};
struct LTQ_Token : Token {
int type() override { return TOKEN_LT_QUESTION; }
};
struct LTX_Token : Token {
int type() override { return TOKEN_LT_EXCLAMATION; }
};
struct LTS_Token : Token {
int type() override { return TOKEN_LT_SLASH; }
};
// struct GT_Token : Token {
// int type() override { return TOKEN_GREATER_THEN; }
// };
// struct SGT_Token : Token {
// int type() override { return TOKEN_SLASH_GT; }
// };
// struct QGT_Token : Token {
// int type() override { return TOKEN_QUESTION_GT; }
// };
struct NAME_Token : Token {
const char *first;
const char *last;
NAME_Token(const char *first, const char *last)
: first(first), last(last)
{
}
int type() override { return TOKEN_NAME; }
std::string name() const override { return {first, last}; }
};
struct STRING_Token : Token {
const char *first;
const char *last;
STRING_Token(const char *first, const char *last)
: first(first), last(last)
{
}
int type() override { return TOKEN_STRING; }
std::string value() const override { return {first, last}; }
};
struct EQ_Token : Token {
int type() override { return TOKEN_EQUALS; }
};
private:
std::vector _stack;
public:
template
static std::string decode_entities(const STR& str)
{
// TODO - decode entities
return str;
}
template
static std::string encode_entities(const STR& str)
{
// TODO - encode entities
return str;
}
int stack_find(int token)
{
int i = _stack.size()-1;
while (i>=0) {
if (_stack[i]->type() == token)
return i;
}
return -1;
}
template
bool stack_match(int token, ARGS...args)
{
if (_stack.size() < 1 + sizeof...(ARGS))
return false;
if (_stack[_stack.size()-1-sizeof...(ARGS)]->type() != token)
return false;
if constexpr (sizeof...(ARGS) != 0)
return stack_match(args...);
else
return true;
}
auto stack_pop_attr()
{
attribute_list attrs;
while (stack_match(TOKEN_NAME, TOKEN_EQUALS, TOKEN_STRING)) {
auto value = _stack.back();
_stack.pop_back();
_stack.pop_back(); // '='
auto name = _stack.back();
_stack.pop_back();
attrs.emplace_back(std::string(name->name()), decode_entities(value->value()));
}
std::reverse(attrs.begin(), attrs.end());
return attrs;
}
// NOTE: this needs to be a templated function, otherwise the
// if-constexpr will not eliminate the always-true comparisons.
template
static bool isnamechar(CHAR c)
{
if constexpr (std::is_signed_v) {
if (c < 0) return true;
}
if (c
++q;
auto attrs = stack_pop_attr();
if (!stack_match(TOKEN_LT_QUESTION, TOKEN_NAME))
throw std::runtime_error("invalid xml#6");
auto tagname = _stack.back();
_stack.pop_back();
_stack.pop_back(); // '