1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
#ifndef XMPP_PARSER_INCLUDED
# define XMPP_PARSER_INCLUDED
#include <xmpp/xmpp_stanza.hpp>
#include <functional>
#include <expat.h>
/**
* 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/closed (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 is never exhausted as long as each
* stanza is reasonnably short.
*
* The element names generated by expat contain the namespace of the
* element, a colon (':') and then the actual name of the element. To get
* an element "x" with a namespace of "http://jabber.org/protocol/muc", you
* just look for an XmlNode named "http://jabber.org/protocol/muc:x"
*
* 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:
explicit XmppParser();
~XmppParser();
public:
/**
* Feed the parser with some XML data
*/
void feed(const char* data, const int len, const bool is_final);
/**
* Add one callback for the various events that this parser can spawn.
*/
void add_stanza_callback(std::function<void(const Stanza&)>&& callback);
void add_stream_open_callback(std::function<void(const XmlNode&)>&& callback);
void add_stream_close_callback(std::function<void(const XmlNode&)>&& callback);
/**
* 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 start_element(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 end_element(const XML_Char* name);
/**
* Some inner or tail data has been parsed
*/
void char_data(const XML_Char* data, int len);
/**
* 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;
private:
/**
* Expat structure.
*/
XML_Parser parser;
/**
* 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<std::function<void(const Stanza&)>> stanza_callbacks;
std::vector<std::function<void(const XmlNode&)>> stream_open_callbacks;
std::vector<std::function<void(const XmlNode&)>> stream_close_callbacks;
};
#endif // XMPP_PARSER_INCLUDED
|