From 5bbd34a3a909fa904ee4402f01dac6bac59211b1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 3 Nov 2013 15:12:50 +0100 Subject: Add an XmppParser, and Stanza classes Generate events on stanza and stream open/close. Create Stanza and serialize them. Note: XML namespaces are not handled yet. --- src/xmpp/xmpp_parser.cpp | 103 ++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_parser.hpp | 107 +++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_stanza.cpp | 127 +++++++++++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_stanza.hpp | 106 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 443 insertions(+) create mode 100644 src/xmpp/xmpp_parser.cpp create mode 100644 src/xmpp/xmpp_parser.hpp create mode 100644 src/xmpp/xmpp_stanza.cpp create mode 100644 src/xmpp/xmpp_stanza.hpp (limited to 'src') diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp new file mode 100644 index 0000000..00714b3 --- /dev/null +++ b/src/xmpp/xmpp_parser.cpp @@ -0,0 +1,103 @@ +#include +#include + +#include + +XmppParser::XmppParser(): + level(0), + current_node(nullptr) +{ +} + +XmppParser::~XmppParser() +{ + if (this->current_node) + delete this->current_node; +} + +void XmppParser::startElement(const XML_Char* name, const XML_Char** attribute) +{ + level++; + + XmlNode* new_node = new XmlNode(name, this->current_node); + if (this->current_node) + this->current_node->add_child(new_node); + this->current_node = new_node; + for (size_t i = 0; attribute[i]; i += 2) + this->current_node->set_attribute(attribute[i], attribute[i+1]); + if (this->level == 1) + this->stream_open_event(*this->current_node); +} + +void XmppParser::endElement(const XML_Char* name) +{ + assert(name == this->current_node->get_name()); + level--; + this->current_node->close(); + if (level == 1) + { + this->stanza_event(*this->current_node); + } + if (level == 0) + { + this->stream_close_event(*this->current_node); + delete this->current_node; + this->current_node = nullptr; + } + else + this->current_node = this->current_node->get_parent(); + if (level == 1) + this->current_node->delete_all_children(); +} + +void XmppParser::charData(const XML_Char* data, int len) +{ + if (this->current_node->has_children()) + this->current_node->get_last_child()->set_tail(std::string(data, len)); + else + this->current_node->set_inner(std::string(data, len)); +} + +void XmppParser::startNamespace(const XML_Char* prefix, const XML_Char* uri) +{ + std::cout << "startNamespace: " << prefix << ":" << uri << std::endl; + this->namespaces.emplace(std::make_pair(prefix, uri)); +} + +void XmppParser::stanza_event(const Stanza& stanza) const +{ + for (const auto& callback: this->stanza_callbacks) + callback(stanza); +} + +void XmppParser::stream_open_event(const XmlNode& node) const +{ + for (const auto& callback: this->stream_open_callbacks) + callback(node); +} + +void XmppParser::stream_close_event(const XmlNode& node) const +{ + for (const auto& callback: this->stream_close_callbacks) + callback(node); +} + +void XmppParser::endNamespace(const XML_Char* coucou) +{ + std::cout << "endNamespace: " << coucou << std::endl; +} + +void XmppParser::add_stanza_callback(std::function&& callback) +{ + this->stanza_callbacks.emplace_back(std::move(callback)); +} + +void XmppParser::add_stream_open_callback(std::function&& callback) +{ + this->stream_open_callbacks.emplace_back(std::move(callback)); +} + +void XmppParser::add_stream_close_callback(std::function&& callback) +{ + this->stream_close_callbacks.emplace_back(std::move(callback)); +} diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp new file mode 100644 index 0000000..2e83bc3 --- /dev/null +++ b/src/xmpp/xmpp_parser.hpp @@ -0,0 +1,107 @@ +#ifndef XMPP_PARSER_INCLUDED +# define XMPP_PARSER_INCLUDED + +#include +#include + +#include + +#include + +/** + * A SAX XML parser that builds XML nodes and spawns events when a complete + * stanza is received (an element of level 2), or when the document is + * opened (an element of level 1) + * + * After a stanza_event has been spawned, we delete the whole stanza. This + * means that even with a very long document (in XMPP the document is + * potentially infinite), the memory then is never exhausted as long as each + * stanza is reasonnably short. + * + * TODO: enforce the size-limit for the stanza (limit the number of childs + * it can contain). For example forbid the parser going further than level + * 20 (arbitrary number here), and each XML node to have more than 15 childs + * (arbitrary number again). + */ +class XmppParser: public expatpp +{ +public: + explicit XmppParser(); + ~XmppParser(); + +public: + /** + * Add one callback for the various events that this parser can spawn. + */ + void add_stanza_callback(std::function&& callback); + void add_stream_open_callback(std::function&& callback); + void add_stream_close_callback(std::function&& callback); + +private: + /** + * Called when a new XML element has been opened. We instanciate a new + * XmlNode and set it as our current node. The parent of this new node is + * the previous "current" node. We have all the element's attributes in + * this event. + * + * We spawn a stream_event with this node if this is a level-1 element. + */ + void startElement(const XML_Char* name, const XML_Char** attribute); + /** + * Called when an XML element has been closed. We close the current_node, + * set our current_node as the parent of the current_node, and if that was + * a level-2 element we spawn a stanza_event with this node. + * + * And we then delete the stanza (and everything under it, its children, + * attribute, etc). + */ + void endElement(const XML_Char* name); + /** + * Some inner or tail data has been parsed + */ + void charData(const XML_Char* data, int len); + /** + * TODO use that. + */ + void startNamespace(const XML_Char* prefix, const XML_Char* uri); + /** + * TODO and that. + */ + void endNamespace(const XML_Char* prefix); + /** + * Calls all the stanza_callbacks one by one. + */ + void stanza_event(const Stanza& stanza) const; + /** + * Calls all the stream_open_callbacks one by one. Note: the passed node is not + * closed yet. + */ + void stream_open_event(const XmlNode& node) const; + /** + * Calls all the stream_close_callbacks one by one. + */ + void stream_close_event(const XmlNode& node) const; + + /** + * The current depth in the XML document + */ + size_t level; + /** + * The deepest XML node opened but not yet closed (to which we are adding + * new children, inner or tail) + */ + XmlNode* current_node; + /** + * A list of callbacks to be called on an *_event, receiving the + * concerned Stanza/XmlNode. + */ + std::vector> stanza_callbacks; + std::vector> stream_open_callbacks; + std::vector> stream_close_callbacks; + /** + * TODO: also use that. + */ + std::stack> namespaces; +}; + +#endif // XMPP_PARSER_INCLUDED diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp new file mode 100644 index 0000000..2c98acc --- /dev/null +++ b/src/xmpp/xmpp_stanza.cpp @@ -0,0 +1,127 @@ +#include + +#include + +XmlNode::XmlNode(const std::string& name, XmlNode* parent): + name(name), + parent(parent), + closed(false) +{ +} + +XmlNode::XmlNode(const std::string& name): + XmlNode(name, nullptr) +{ +} + +XmlNode::~XmlNode() +{ + this->delete_all_children(); +} + +void XmlNode::delete_all_children() +{ + for (auto& child: this->children) + { + delete child; + } + this->children.clear(); +} + +void XmlNode::set_attribute(const std::string& name, const std::string& value) +{ + this->attributes[name] = value; +} + +void XmlNode::set_tail(const std::string& data) +{ + this->tail = data; +} + +void XmlNode::set_inner(const std::string& data) +{ + this->inner = data; +} + +void XmlNode::add_child(XmlNode* child) +{ + this->children.push_back(child); +} + +void XmlNode::add_child(XmlNode&& child) +{ + XmlNode* new_node = new XmlNode(std::move(child)); + this->add_child(new_node); +} + +XmlNode* XmlNode::get_last_child() const +{ + return this->children.back(); +} + +void XmlNode::close() +{ + if (this->closed) + throw std::runtime_error("Closing an already closed XmlNode"); + this->closed = true; +} + +XmlNode* XmlNode::get_parent() const +{ + return this->parent; +} + +const std::string& XmlNode::get_name() const +{ + return this->name; +} + +std::string XmlNode::to_string() const +{ + std::string res("<"); + res += this->name; + for (const auto& it: this->attributes) + res += " " + it.first + "='" + it.second + "'"; + if (this->closed && !this->has_children() && this->inner.empty()) + res += "/>"; + else + { + res += ">" + this->inner; + for (const auto& child: this->children) + res += child->to_string(); + if (this->closed) + { + res += "name + ">"; + } + } + res += this->tail; + return res; +} + +void XmlNode::display() const +{ + std::cout << this->to_string() << std::endl; +} + +bool XmlNode::has_children() const +{ + return !this->children.empty(); +} + +const std::string& XmlNode::operator[](const std::string& name) const +{ + try + { + const auto& value = this->attributes.at(name); + return value; + } + catch (const std::out_of_range& e) + { + throw AttributeNotFound(); + } +} + +std::string& XmlNode::operator[](const std::string& name) +{ + return this->attributes[name]; +} diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp new file mode 100644 index 0000000..277b0db --- /dev/null +++ b/src/xmpp/xmpp_stanza.hpp @@ -0,0 +1,106 @@ +#ifndef XMPP_STANZA_INCLUDED +# define XMPP_STANZA_INCLUDED + +#include +#include +#include + +#include + +/** + * Raised on operator[] when the attribute does not exist + */ +class AttributeNotFound: public std::exception +{ +}; + +/** + * Represent an XML node. It has + * - A parent XML node (in the case of the first-level nodes, the parent is + nullptr) + * - zero, one or more children XML nodes + * - A name + * - attributes + * - inner data (inside the node) + * - tail data (just after the node) + */ +class XmlNode +{ +public: + explicit XmlNode(const std::string& name, XmlNode* parent); + explicit XmlNode(const std::string& name); + XmlNode(XmlNode&& node): + name(std::move(node.name)), + parent(std::move(node.parent)), + closed(std::move(node.closed)), + attributes(std::move(node.attributes)), + children(std::move(node.children)), + inner(std::move(node.inner)), + tail(std::move(node.tail)) + { + node.parent = nullptr; + } + + ~XmlNode(); + + void delete_all_children(); + void set_attribute(const std::string& name, const std::string& value); + /** + * Set the content of the tail, that is the text just after this node + */ + void set_tail(const std::string& data); + /** + * Set the content of the inner, that is the text inside this node + */ + void set_inner(const std::string& data); + void add_child(XmlNode* child); + void add_child(XmlNode&& child); + XmlNode* get_last_child() const; + /** + * Mark this node as closed, nothing else + */ + void close(); + XmlNode* get_parent() const; + const std::string& get_name() const; + /** + * Serialize the stanza into a string + */ + std::string to_string() const; + void display() const; + /** + * Whether or not this node has at least one child (if not, this is a leaf + * node) + */ + bool has_children() const; + /** + * Gets the value for the given attribute, raises AttributeNotFound if the + * node as no such attribute. + */ + const std::string& operator[](const std::string& name) const; + /** + * Use this to set an attribute's value, like node["id"] = "12"; + */ + std::string& operator[](const std::string& name); + +private: + std::string name; + XmlNode* parent; + bool closed; + std::unordered_map attributes; + std::vector children; + std::string inner; + std::string tail; + + XmlNode(const XmlNode&) = delete; + XmlNode& operator=(const XmlNode&) = delete; + XmlNode& operator=(XmlNode&&) = delete; +}; + +/** + * An XMPP stanza is just an XML node of level 2 in the XMPP document (the + * level 1 ones are the , and the ones about 2 are just the + * content of the stanzas) + */ +typedef XmlNode Stanza; + +#endif // XMPP_STANZA_INCLUDED -- cgit v1.2.3