/* * \brief XML parser * \author Norman Feske * \date 2007-08-21 */ /* * Copyright (C) 2007-2012 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #ifndef _INCLUDE__UTIL__XML_NODE_H_ #define _INCLUDE__UTIL__XML_NODE_H_ #include #include namespace Genode { /** * Representation of an XML node */ class Xml_node { /** * Scanner policy that accepts hyphens in identifiers */ struct Scanner_policy_xml_identifier { static bool identifier_char(char c, unsigned i) { return is_letter(c) || c == '_' || c == ':' || (i && (c == '-' || c == '.' || is_digit(c))); } }; /** * Define tokenizer that matches XML tags (with hyphens) as identifiers */ typedef ::Genode::Token Token; /** * Forward declaration needed for befriending Tag with Attribut */ class Tag; public: /********************* ** Exception types ** *********************/ class Exception : public ::Genode::Exception { }; class Invalid_syntax : public Exception { }; class Nonexistent_sub_node : public Exception { }; class Nonexistent_attribute : public Exception { }; /** * Representation of an XML-node attribute * * An attribute has the form 'name="value"'. */ class Attribute { private: Token _name; Token _value; friend class Xml_node; /* * Even though 'Tag' is part of 'Xml_node', the friendship * to 'Xml_node' does not apply for 'Tag' when compiling * the code with 'gcc-3.4'. Hence, we need to add an * explicit friendship to 'Tag'. */ friend class Tag; /** * Constructor * * This constructor is meant to be used as implicitly to * construct an 'Xml_attribute' from a token sequence via an * assignment from the leading 'Token'. */ Attribute(Token t) : _name(t.eat_whitespace()), _value(_name.next().next()) { if (_name.type() != Token::IDENT) throw Nonexistent_attribute(); if (_name.next()[0] != '=' || _value.type() != Token::STRING) throw Invalid_syntax(); } /** * Return token following the attribute declaration */ Token _next() const { return _name.next().next().next(); } public: /** * Return attribute type as null-terminated string */ void type(char *dst, size_t max_len) const { /* * Limit number of characters by token length, take * null-termination into account. */ max_len = min(max_len, _name.len() + 1); strncpy(dst, _name.start(), max_len); } /** * Return true if attribute has specified type */ bool has_type(const char *type) { return strlen(type) == _name.len() && strcmp(type, _name.start(), _name.len()) == 0; } /** * Return attribute value as null-terminated string * * \return true on success, or * false if attribute is invalid */ bool value(char *dst, size_t max_len) const { /* * The value of 'max_len' denotes the maximum number of * characters to be written to 'dst' including the null * termination. From the quoted value string, we strip * both quote characters and add a null-termination * character. */ max_len = min(max_len, _value.len() - 2 + 1); strncpy(dst, _value.start() + 1, max_len); return true; } /** * Return true if attribute has the specified value */ bool has_value(const char *value) const { return strlen(value) == (_value.len() - 2) && !strcmp(value, _value.start() + 1, _value.len() - 2); } /** * Return attribute value as typed value * * \param T type of value to read * \return true on success, or * false if attribute is invalid or value * conversion failed */ template bool value(T *out) const { /* * The '_value' token starts with a quote, which we * need to skip to access the string. For validating * the length, we have to consider both the starting * and the trailing quote character. */ return ascii_to(_value.start() + 1, out) == _value.len() - 2; } /** * Return next attribute in attribute list */ Attribute next() const { return Attribute(_next()); } }; private: class Tag { public: enum Type { START, END, EMPTY, INVALID }; private: Token _token; Token _name; Type _type; public: /** * Constructor * * \param start first token of the tag * * At construction time, the validity of the tag is checked and * the tag type is determined. A valid tag consists of: * # Leading '<' tag delimiter * # '/' for marking an end tag * # Tag name * # Optional attribute sequence (if tag is no end tag) * # '/' for marking an empty-element tag (if tag is no end tag) * # Closing '>' tag delimiter */ Tag(Token start) : _token(start), _type(INVALID) { Type supposed_type = START; if (_token[0] != '<') return; if (_token.next()[0] == '/') supposed_type = END; if (_token.next().type() != Token::IDENT && _token.next()[0] != '/') return; _name = _token.next()[0] == '/' ? _token.next().next() : _token.next(); if (_name.type() != Token::IDENT) return; /* skip attributes to find tag delimiter */ Token delimiter = _name.next(); if (supposed_type != END) try { for (Attribute a = _name.next(); ; a = a._next()) delimiter = a._next(); } catch (Nonexistent_attribute) { } delimiter = delimiter.eat_whitespace(); /* * Now we expect the '>' delimiter. For empty-element tags, * the delimiter is prefixed with a '/'. */ if (delimiter[0] == '/') { /* if a '/' was already at the start, the tag is invalid */ if (supposed_type == END) return; supposed_type = EMPTY; /* skip '/' */ delimiter = delimiter.next(); } if (delimiter[0] != '>') return; _type = supposed_type; } /** * Default constructor produces invalid Tag */ Tag() : _type(INVALID) { } /** * Return type of tag */ Type type() const { return _type; } /** * Return true if tag is the start of a valid XML node */ bool is_node() const { return _type == START || _type == EMPTY; } /** * Return first token of tag */ Token token() const { return _token; } /** * Return name of tag */ Token name() const { return _name; } /** * Return token after the closing tag delimiter */ Token next_token() const { /* * Search for next closing delimiter, skip potential * attributes and '/' delimiter prefix of empty-element * tags. */ Token t = _name; for (; t && t[0] != '>'; t = t.next()); /* if 't' is invalid, 't.next()' is invalid too */ return t.next(); } /** * Return first attribute of tag */ Attribute attribute() const { return Attribute(_name.next()); } }; class Comment { private: Token _next; /* token following the comment */ bool _valid; /* true if comment is well formed */ /** * Check if token sequence matches specified character sequence * * \param t start of token sequence * \param s null-terminated character sequence */ static bool _match(Token t, const char *s) { for (int i = 0; s[i]; t = t.next(), i++) if (t[0] != s[i]) return false; return true; } public: /** * Constructor * * \param start first token of the comment tag */ Comment(Token t) : _valid(false) { /* check for comment-start tag */ if (!_match(t, ""); t = t.next()); if (t.type() == Token::END) return; _next = t.next().next().next(); _valid = true; } /** * Default constructor produces invalid Comment */ Comment() : _valid(false) { } /** * Return true if comment is valid */ bool valid() const { return _valid; } /** * Return token after the closing comment delimiter */ Token next_token() const { return _next; } }; const char *_addr; /* first character of XML data */ size_t _max_len; /* length of XML data in characters */ int _num_sub_nodes; /* number of immediate sub nodes */ Tag _start_tag; Tag _end_tag; /** * Search for end tag of XML node and initialize '_num_sub_nodes' * * \return end tag or invalid tag * * The function searches for a end tag that matches the same * depth level and the same name as the start tag of the XML * node. If the XML structure is invalid, the search results * is an invalid Tag. * * During the search, the function also counts the number of * immediate sub nodes. */ Tag _init_end_tag() { /* * If the start tag is invalid or an empty-element tag, * we use the same tag as end tag. */ if (_start_tag.type() != Tag::START) return _start_tag; int depth = 1; Token curr_token = _start_tag.next_token(); while (curr_token.type() != Token::END) { /* eat XML comment */ Comment curr_comment(curr_token); if (curr_comment.valid()) { curr_token = curr_comment.next_token(); continue; } /* skip all tokens that are no tags */ Tag curr_tag(curr_token); if (curr_tag.type() == Tag::INVALID) { curr_token = curr_token.next(); continue; } /* count sub nodes at depth 1 */ if (depth == 1 && curr_tag.is_node()) _num_sub_nodes++; /* keep track of the current depth */ depth += (curr_tag.type() == Tag::START); depth -= (curr_tag.type() == Tag::END); /* within sub nodes, continue after current token */ if (depth > 0) { /* continue search with token after current tag */ curr_token = curr_tag.next_token(); continue; } /* reaching the same depth as the start tag */ const char *start_name = _start_tag.name().start(); size_t start_len = _start_tag.name().len(); const char *curr_name = curr_tag.name().start(); size_t curr_len = curr_tag.name().len(); /* on mismatch of start tag and end tag, return invalid tag */ if (start_len != curr_len || strcmp(start_name, curr_name, curr_len)) return Tag(); /* end tag corresponds to start tag */ return curr_tag; } return Tag(); } /** * Find next non-whitespace and non-comment token */ static Token eat_whitespaces_and_comments(Token t) { while (true) { t = t.eat_whitespace(); /* eat comment */ Comment comment(t); if (comment.valid()) { t = comment.next_token(); continue; } break; } return t; } /** * Create sub node from XML node * * \throw Nonexistent_sub_node * \throw Invalid_syntax */ Xml_node _sub_node(const char *at) const { if (at < addr() || (size_t)(at - addr()) >= _max_len) throw Nonexistent_sub_node(); return Xml_node(at, _max_len - (at - addr())); } public: /** * Constructor * * The constructor validates if the start tag has a * matching end tag of the same depth and counts * the number of immediate sub nodes. */ Xml_node(const char *addr, size_t max_len = ~0UL) : _addr(addr), _max_len(max_len), _num_sub_nodes(0), _start_tag(eat_whitespaces_and_comments(Token(addr, max_len))), _end_tag(_init_end_tag()) { /* check validity of XML node */ if (_start_tag.type() == Tag::EMPTY) return; if (_start_tag.type() == Tag::START && _end_tag.type() == Tag::END) return; throw Invalid_syntax(); } /** * Request type name of XML node as null-terminated string */ void type_name(char *dst, size_t max_len) const { _start_tag.name().string(dst, max_len); } /** * Return true if tag is of specified type */ bool has_type(const char *type) const { return (!strcmp(type, _start_tag.name().start(), _start_tag.name().len()) && strlen(type) == _start_tag.name().len()); } /** * Request content of XML node as null-terminated string */ void value(char *dst, size_t max_len) const { max_len = min(content_size() + 1, min(max_len, _max_len)); strncpy(dst, content_addr(), max_len); } /** * Read content as typed value from XML node * * \param T type of value to read from XML node * \param out resulting value * \return true on success */ template bool value(T *out) const { return ascii_to(content_addr(), out) == content_size(); } /** * Return begin of node including the start tag */ const char *addr() const { return _addr; } /** * Return size of node including start and end tags */ size_t size() const { return _end_tag.next_token().start() - addr(); } /** * Return begin of node content as an opaque string * * Note that the returned string is not null-terminated as it * points directly into a sub range of the unmodified Xml_node * address range. */ char *content_addr() const { return _start_tag.next_token().start(); } /** * Return size of node content */ size_t content_size() const { if (_start_tag.type() == Tag::EMPTY) return 0; return _end_tag.token().start() - content_addr(); } /** * Return the number of the XML node's immediate sub nodes */ size_t num_sub_nodes() const { return _num_sub_nodes; } /** * Return XML node following the current one * * \throw Nonexistent_sub_node sub sequent node does not exist */ Xml_node next() const { Token after_node = _end_tag.next_token(); after_node = eat_whitespaces_and_comments(after_node); try { return _sub_node(after_node.start()); } catch (Invalid_syntax) { throw Nonexistent_sub_node(); } } /** * Return next XML node of specified type * * \param type type of XML node, or * 0 for matching any type */ Xml_node next(const char *type) const { Xml_node node = next(); for (; type && !node.has_type(type); node = node.next()); return node; } /** * Return true if node is the last of a node sequence */ bool is_last(const char *type = 0) const { try { next(type); return false; } catch (Nonexistent_sub_node) { return true; } } /** * Return sub node with specified index * * \param idx index of sub node, * default is the first node * \throw Nonexistent_sub_node no such sub node exists */ Xml_node sub_node(unsigned idx = 0U) const { /* look up node at specified index */ try { Xml_node curr_node = _sub_node(content_addr()); for (; idx > 0; idx--) curr_node = curr_node.next(); return curr_node; } catch (Invalid_syntax) { } throw Nonexistent_sub_node(); } /** * Return first sub node that matches the specified type * * \throw Nonexistent_sub_node no such sub_node exists */ Xml_node sub_node(const char *type) const { /* search for sub node of specified type */ try { Xml_node curr_node = _sub_node(content_addr()); for ( ; true; curr_node = curr_node.next()) if (curr_node.has_type(type)) return curr_node; } catch (...) { } throw Nonexistent_sub_node(); } /** * Return Nth attribute of XML node * * \param idx attribute index, * first attribute has index 0 * \throw Nonexistent_attribute no such attribute exists * \return XML attribute */ Attribute attribute(unsigned idx) const { /* get first attribute of the node */ Attribute a = _start_tag.attribute(); /* skip attributes until we reach the target index */ for (; idx > 0; idx--) a = a._next(); return a; } /** * Return attribute of specified type * * \param type name of attribute type * \throw Nonexistent_attribute no such attribute exists * \return XML attribute */ Attribute attribute(const char *type) const { /* iterate, beginning with the first attribute of the node */ for (Attribute a = _start_tag.attribute(); ; a = a.next()) if (a.has_type(type)) return a; } }; } #endif /* _INCLUDE__UTIL__XML_NODE_H_ */