Document Object Model
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
using namespace std;
namespace lexeme
{
class lexical_error : public exception
{
// TODO: Maybe report the error position by istream::tellg().
};
class lexeme
{
virtual void read(istream &is) = 0;
friend istream &operator >>(istream &is, lexeme &lex)
{
lex.read(is);
return is;
}
};
class white_space : public lexeme
{
virtual void read(istream &is)
{
while (isspace(is.peek())) is.get();
}
};
class match : public lexeme
{
const char m_ch;
virtual void read(istream &is)
{
if (is.peek() == m_ch) is.get(); else throw lexical_error();
}
public:
match(const char ch) : m_ch(ch)
{
}
};
class token : public lexeme
{
string m_value;
virtual void read(istream &is)
{
m_value.clear();
while (isalnum(is.peek())) m_value.push_back(is.get());
if (m_value.empty()) throw lexical_error();
}
public:
operator string() const
{
return m_value;
}
};
class quoted : public lexeme
{
string m_value;
const char m_mark;
virtual void read(istream &is)
{
m_value.clear();
match mark(m_mark);
is >> mark;
while (is.peek() != EOF && is.peek() != m_mark) m_value.push_back(is.get());
is >> mark;
}
public:
quoted(const char mark = '\'') : m_mark(mark)
{
}
operator string() const
{
return m_value;
}
};
}
namespace html
{
class node
{
node(const string &name) : m_name(name)
{
}
public:
static shared_ptr<node> create(const string &name)
{
// The object must be created on the heap, and managed by shared_ptr,
// so as to be referenced safely by other objects.
return shared_ptr<node>(new node(name));
}
string attr(const string &key) const
{
return m_attr.at(key);
}
shared_ptr<node> child(size_t index)
{
return m_children.at(index);
}
void inner_html(const string &doc)
{
istringstream iss(doc);
parse_body(iss);
}
private:
void parse_attr(istream &is)
{
using namespace lexeme;
while (true)
{
white_space ws;
is >> ws;
if (is.peek() == EOF || is.peek() == '>') break;
token key;
match eq('=');
quoted value;
is >> key >> eq >> value;
m_attr[key] = value;
}
}
void parse_body(istream &is)
{
using namespace lexeme;
while (true)
{
white_space ws;
is >> ws;
if (is.peek() == EOF) break;
try
{
m_children.push_back(parse_node(is));
}
catch (const end_tag &tag)
{
if (tag.name != m_name); // TODO: Raise a syntax error.
break;
}
}
}
private:
class end_tag
{
public:
const string name;
end_tag(const string &name) : name(name)
{
}
};
static shared_ptr<node> parse_node(istream &is)
{
using namespace lexeme;
match lt('<'), slash('/'), gt('>');
token name;
is >> lt;
if (is.peek() == '/')
{
is >> slash >> name >> gt;
throw end_tag(name);
}
is >> name;
auto n = create(name);
n->parse_attr(is);
is >> gt;
n->parse_body(is);
return n;
}
private:
string m_name;
map<string, string> m_attr;
vector<shared_ptr<node>> m_children;
};
class spider
{
class step
{
public:
const shared_ptr<node> parent;
size_t index;
step(const shared_ptr<node> &parent, size_t index) : parent(parent), index(index)
{
}
};
public:
spider(const shared_ptr<node> &root) // TODO: Maybe support a const node.
{
m_trail.push(step(root, 0));
}
shared_ptr<node> get() const
{
auto &here = m_trail.top();
return here.parent->child(here.index);
}
void go_parent()
{
if (m_trail.size() > 1) m_trail.pop();
}
void go_child(size_t index)
{
if (has_child(get(), index)) m_trail.push(step(get(), index));
}
void go_next()
{
auto &here = m_trail.top();
if (has_child(here.parent, here.index + 1)) here.index++;
}
void go_prev()
{
auto &here = m_trail.top();
if (has_child(here.parent, here.index - 1)) here.index--;
}
private:
static bool has_child(const shared_ptr<node> &node, size_t index)
{
try
{
node->child(index);
}
catch (const out_of_range &)
{
return false;
}
return true;
}
private:
stack<step> m_trail;
};
}
istream &getlines(istream &is, size_t n, string &str)
{
str.clear();
while (is && n--)
{
string line;
getline(is, line);
str.append(line);
}
return is;
}
void answer_commands(const shared_ptr<html::node> &root)
{
html::spider spider(root);
const map<string, function<void()>> actions
{
{
"first_child",
[&spider] { spider.go_child(0); }
},
{
"next_sibling",
[&spider] { spider.go_next(); }
},
{
"previous_sibling",
[&spider] { spider.go_prev(); }
},
{
"parent",
[&spider] { spider.go_parent(); }
},
};
size_t n;
cin >> n;
while (n--)
{
string cmd;
cin >> cmd;
try
{
actions.at(cmd)();
cout << spider.get()->attr("value") << endl;
}
catch (const out_of_range &)
{
}
}
}
bool test_case(size_t test_num)
{
size_t n;
cin >> n;
if (n == 0) return false;
string doc;
cin.ignore(1, '\n');
getlines(cin, n, doc);
auto root = html::node::create("html");
root->inner_html(doc);
cout << "Case " << test_num << ":" << endl;
answer_commands(root);
return true;
}
int main()
{
for (size_t i = 1; test_case(i); i++);
return 0;
}