yipo
7/11/2017 - 5:50 PM

Document Object Model

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;
}