libupnpp 0.16.0
A C++ wrapper for the Portable UPnP reference library
expatmm.h
1/*
2 * ExpatMM - C++ Wrapper for Expat available at http://expat.sourceforge.net/
3 * Copyright (c) 2006, 2007, 2008, 2009 IntelliTree Solutions llc
4 * Author: Coleman Kane <ckane@intellitree.com>
5 *
6 * Mutilated and forced into single-file solution by <jf@dockes.org>
7 * Copyright (c) 2013-2023 J.F. Dockes
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General
20 * Public License along with this library; if not, please feel free
21 * to contact the author listed above.
22 */
23#ifndef _EXPATMM_EXPATXMLPARSER_H
24#define _EXPATMM_EXPATXMLPARSER_H
25
26#include <expat.h>
27
28#include <cstring>
29#include <map>
30#include <sstream>
31#include <string>
32#include <vector>
33
34#ifdef _MSC_VER
35#define EXPATMM_SSIZE_T int
36#else
37#define EXPATMM_SSIZE_T ssize_t
38#endif
39
41public:
42
43 /* Create a new parser, using the default Chunk Size */
44 ExpatXMLParser(void) {
45 init();
46 }
47
48 /* Create a new parser, using a user-supplied chunk size */
49 explicit ExpatXMLParser(size_t chunk_size) {
50 init(chunk_size);
51 }
52
53 /* Destructor that cleans up xml_buffer and parser */
54 virtual ~ExpatXMLParser(void) {
55 valid_parser = false;
56 if(expat_parser != nullptr) {
57 XML_ParserFree(expat_parser);
58 expat_parser = nullptr;
59 }
60 if(xml_buffer != nullptr) {
61 delete [] xml_buffer;
62 xml_buffer = nullptr;
63 }
64 }
65
66 ExpatXMLParser(const ExpatXMLParser&) = delete;
67 ExpatXMLParser& operator=(const ExpatXMLParser&) = delete;
68
69 /*
70 Generic Parser. Most derivations will simply call this, rather
71 than implement their own. This will loop, processing XML data
72 and calling the necessary handler code until an error is encountered.
73 */
74 virtual bool Parse(void) {
75 /* Ensure that the parser is ready */
76 if(!Ready())
77 return false;
78
79 EXPATMM_SSIZE_T bytes_read;
80 /* Loop, reading the XML source block by block */
81 while((bytes_read = read_block()) >= 0) {
82 if(bytes_read > 0) {
83 XML_Status local_status =
84 XML_Parse(expat_parser, getReadBuffer(), int(bytes_read), XML_FALSE);
85
86 if(local_status != XML_STATUS_OK) {
87 set_status(local_status);
88 break;
89 }
90
91 /* Break on successful "short read", in event of EOF */
92 if(getLastError() == XML_ERROR_FINISHED)
93 break;
94 }
95 }
96
97 /* Finalize the parser */
98 if((getStatus() == XML_STATUS_OK) || (getLastError() == XML_ERROR_FINISHED)) {
99 XML_Status local_status = XML_Parse(expat_parser, getBuffer(), 0, XML_TRUE);
100 if(local_status != XML_STATUS_OK) {
101 set_status(local_status);
102 return false;
103 }
104 return true;
105 }
106
107 /* Return false in the event of an error. The parser is
108 not finalized on error. */
109 return false;
110 }
111
112 /* Expose status, error, and control codes to users */
113 virtual bool Ready(void) const {
114 return valid_parser;
115 }
116 virtual XML_Error getLastError(void) const {
117 return last_error;
118 }
119 virtual XML_Status getStatus(void) const {
120 return status;
121 }
122 virtual XML_Size getLastErrorLine(void) const {
123 return last_error_line;
124 }
125 virtual XML_Size getLastErrorColumn(void) const {
126 return last_error_column;
127 }
128 virtual std::string getLastErrorMessage(void) const {
129 return last_error_message;
130 }
131
132protected:
133 class StackEl {
134 public:
135 explicit StackEl(const char* nm) : name(nm) {}
136 std::string name;
137 XML_Size start_index;
138 std::map<std::string,std::string> attributes;
139 std::string data;
140 };
141 std::vector<StackEl> m_path;
142
143 virtual XML_Char *getBuffer(void) {
144 return xml_buffer;
145 }
146 virtual const char *getReadBuffer(void) {
147 return xml_buffer;
148 }
149 virtual size_t getBlockSize(void) {
150 return xml_buffer_size;
151 }
152
153 /* Read XML data.
154 *
155 * Override this to implement your container-specific parser.
156 *
157 * You must:
158 * put new XML data into xml_buffer
159 * set status
160 * set last_error
161 * return the amount of XML_Char's written to xml_buffer
162 *
163 * on error, return < 0. The contents of xml_buffer will be
164 * thrown away in this event, so it is the derived class's
165 * responsibility to reseek the "data cursor" to re-get any
166 * data in the buffer on an error condition.
167 *
168 * Use setReadiness, setStatus, and setLastError to handle
169 * error, status, and control events and codes.
170 *
171 * The default implementation returns "no elements" if it is
172 * ever called. and should be overridden by the derived class.
173 *
174 * Note that, as the actual parser only uses
175 * getBuffer()/getBlockSize()/read_block() (no direct access
176 * to the buffer), you are free to use an entirely different
177 * I/O mechanism, like what does the inputRefXMLParser below.
178 */
179 virtual EXPATMM_SSIZE_T read_block(void) {
180 last_error = XML_ERROR_NO_ELEMENTS;
181 status = XML_STATUS_ERROR;
182 return -1;
183 }
184
185 virtual void setReadiness(bool ready) {
186 valid_parser = ready;
187 }
188 virtual void setStatus(XML_Status new_status) {
189 status = new_status;
190 }
191 virtual void setLastError(XML_Error new_last_error) {
192 last_error = new_last_error;
193 }
194
195 /* Methods to be overriden */
196 virtual void StartElement(const XML_Char *, const XML_Char **) {}
197 virtual void EndElement(const XML_Char *) {}
198 virtual void CharacterData(const XML_Char *, int) {}
199 virtual void ProcessingInstruction(const XML_Char *, const XML_Char *) {}
200 virtual void CommentData(const XML_Char *) {}
201 virtual void DefaultHandler(const XML_Char *, int) {}
202 virtual void CDataStart(void) {}
203 virtual void CDataEnd(void) {}
204
205 /* The handle for the parser (expat) */
206 XML_Parser expat_parser;
207
208private:
209
210 /* Temporary buffer where data is streamed in */
211 XML_Char *xml_buffer;
212 size_t xml_buffer_size;
213
214 /* Tells if the parser is ready to accept data */
215 bool valid_parser;
216
217 /* Status and Error codes in the event of unforseen events */
218 void set_status(XML_Status ls) {
219 status = ls;
220 last_error = XML_GetErrorCode(expat_parser);
221 last_error_line = XML_GetCurrentLineNumber(expat_parser);
222 last_error_column= XML_GetCurrentColumnNumber(expat_parser);
223 std::ostringstream oss;
224 oss << XML_ErrorString(last_error) <<
225 " at line " << last_error_line << " column " <<
226 last_error_column;
227 last_error_message = oss.str();
228 }
229
230 XML_Status status;
231 XML_Error last_error;
232 XML_Size last_error_line{0};
233 XML_Size last_error_column{0};
234 std::string last_error_message;
235
236 /* Expat callbacks.
237 * The expatmm protocol is to pass (this) as the userData argument
238 * in the XML_Parser structure. These static methods will convert
239 * handlers into upcalls to the instantiated class's virtual members
240 * to do the actual handling work. */
241 static void _element_start_handler(void *userData, const XML_Char *name,
242 const XML_Char **atts) {
243 auto me = static_cast<ExpatXMLParser*>(userData);
244 if(me != nullptr) {
245 me->m_path.emplace_back(name);
246 StackEl& lastelt = me->m_path.back();
247 lastelt.start_index = XML_GetCurrentByteIndex(me->expat_parser);
248 for (int i = 0; atts[i] != nullptr; i += 2) {
249 lastelt.attributes[atts[i]] = atts[i+1];
250 }
251 me->StartElement(name, atts);
252 }
253 }
254 static void _element_end_handler(void *userData, const XML_Char *name) {
255 auto me = static_cast<ExpatXMLParser*>(userData);
256 if(me != nullptr) {
257 me->EndElement(name);
258 me->m_path.pop_back();
259 }
260 }
261 static void _character_data_handler(void *userData,
262 const XML_Char *s, int len) {
263 auto me = static_cast<ExpatXMLParser*>(userData);
264 if(me != nullptr) me->CharacterData(s, len);
265 }
266 static void _processing_instr_handler(void *userData,
267 const XML_Char *target,
268 const XML_Char *data) {
269 auto me = static_cast<ExpatXMLParser*>(userData);
270 if(me != nullptr) me->ProcessingInstruction(target, data);
271 }
272 static void _comment_handler(void *userData, const XML_Char *data) {
273 auto me = static_cast<ExpatXMLParser*>(userData);
274 if(me != nullptr) me->CommentData(data);
275 }
276 static void _default_handler(void *userData, const XML_Char *s, int len) {
277 auto me = static_cast<ExpatXMLParser*>(userData);
278 if(me != nullptr) me->DefaultHandler(s, len);
279 }
280 static void _cdata_start_handler(void *userData) {
281 auto me = static_cast<ExpatXMLParser*>(userData);
282 if(me != nullptr) me->CDataStart();
283 }
284 static void _cdata_end_handler(void *userData) {
285 auto me = static_cast<ExpatXMLParser*>(userData);
286 if(me != nullptr) me->CDataEnd();
287 }
288 /* Register our static handlers with the Expat events. */
289 void register_default_handlers() {
290 XML_SetElementHandler(expat_parser, &_element_start_handler,
291 &_element_end_handler);
292 XML_SetCharacterDataHandler(expat_parser, &_character_data_handler);
293 XML_SetProcessingInstructionHandler(expat_parser,
294 &_processing_instr_handler);
295 XML_SetCommentHandler(expat_parser, &_comment_handler);
296 XML_SetCdataSectionHandler(expat_parser, &_cdata_start_handler,
297 &_cdata_end_handler);
298 XML_SetDefaultHandler(expat_parser, &_default_handler);
299 }
300 /* Constructor common code */
301 void init(size_t chunk_size = 0) {
302 valid_parser = false;
303 expat_parser = nullptr;
304 xml_buffer_size = chunk_size ? chunk_size : 10240;
305 xml_buffer = new XML_Char[xml_buffer_size];
306 if(xml_buffer == nullptr)
307 return;
308 expat_parser = XML_ParserCreate(nullptr);
309
310 if(expat_parser == nullptr) {
311 delete [] xml_buffer;
312 xml_buffer = nullptr;
313 return;
314 }
315 status = XML_STATUS_OK;
316 last_error = XML_ERROR_NONE;
317
318 memset(xml_buffer, 0, xml_buffer_size * sizeof(XML_Char));
319
320 /* Set the "ready" flag on this parser */
321 valid_parser = true;
322 XML_SetUserData(expat_parser, this);
323 register_default_handlers();
324 }
325};
326
329public:
330 // Beware: we only use a ref to input to minimize copying. This means
331 // that storage for the input parameter must persist until you are done
332 // with the parser object !
333 explicit inputRefXMLParser(const std::string& input)
334 : ExpatXMLParser(1), // Have to allocate a small buf even if not used.
335 m_input(input) {
336 }
337
338protected:
339 EXPATMM_SSIZE_T read_block(void) override {
340 if (getLastError() == XML_ERROR_FINISHED) {
341 setStatus(XML_STATUS_OK);
342 return -1;
343 }
344 setLastError(XML_ERROR_FINISHED);
345 return m_input.size();
346 }
347 const char *getReadBuffer() override {
348 return m_input.c_str();
349 }
350 size_t getBlockSize(void) override {
351 return m_input.size();
352 }
353protected:
354 const std::string& m_input;
355};
356
357#endif /* _EXPATMM_EXPATXMLPARSER_H */
Definition expatmm.h:133
Definition expatmm.h:40
A specialization of ExpatXMLParser that does not copy its input.
Definition expatmm.h:328