Classdesc 3.44
xml_unpack_base.h
Go to the documentation of this file.
1/*
2 @copyright Russell Standish 2000-2013
3 @author Russell Standish
4 This file is part of Classdesc
5
6 Open source licensed under the MIT license. See LICENSE for details.
7*/
8
12
13#ifndef CLASSDESC_XML_UNPACK_BASE_H
14#define CLASSDESC_XML_UNPACK_BASE_H
15#include <map>
16#include <iostream>
17#include <sstream>
18#include <fstream>
19#include <limits>
20#include <cstdlib>
21#include <cctype>
22#include <stdarg.h>
23
24#include "xml_common.h"
25#include "classdesc.h"
26#include "classdesc_access.h"
27// for xml_unpack_t serialisation support
28#include "pack_base.h"
29#include "pack_stl.h"
30
31namespace classdesc_access
32{
33 template <class T> struct access_pack;
34 template <class T> struct access_unpack;
35}
36
37namespace classdesc
38{
39
40 namespace
41 {
43 inline bool isspace(std::string s)
44 {
45 if (s.empty()) return false;
46 for (size_t i=0; i<s.size(); i++)
47 if (!std::isspace(s[i]))
48 return false;
49 return true;
50 }
51 }
52
53 // for remove() below
54 inline bool Isspace(char c) {return std::isspace(c)!=0;}
55
56#ifdef _CLASSDESC
57#pragma omit pack classdesc::XMLtoken
58#pragma omit pack classdesc::xml_pack_error
59#pragma omit unpack classdesc::XMLtoken
60#pragma omit unpack classdesc::xml_pack_error
61#pragma omit xml_pack classdesc::XMLtoken
62#pragma omit xml_pack classdesc::xml_pack_error
63#pragma omit xml_unpack classdesc::XMLtoken
64#pragma omit xml_unpack classdesc::xml_pack_error
65#pragma omit json_pack classdesc::xml_pack_error
66#pragma omit json_unpack classdesc::xml_pack_error
67#pragma omit dump classdesc::xml_pack_error
68#endif
69
70 class xml_pack_error : public std::exception
71 {
72 std::string msg;
73 public:
74 xml_pack_error(const char *s): msg("xml_pack:") {msg+=s;}
75 xml_pack_error(std::string s): msg("xml_pack:") {msg+=s;}
76 virtual ~xml_pack_error() throw() {}
77 virtual const char* what() const throw() {return msg.c_str();}
78 };
79
80 // character accessor functions: istream and FILE* defined here.
81 inline bool get(std::istream& i, char& c) {return i.get(c).good();}
82 inline bool get(FILE*& i, char& c)
83 {int cc=fgetc(i); c=char(cc); return cc!=EOF;}
84 inline void unget(std::istream& i, char c) {i.putback(c);}
85 inline void unget(FILE*& i, char c) {ungetc(c,i);}
86
87 template <class Stream>
88 class XMLtoken
89 {
90 Stream& i;
91 char nexttok;
92
93 // basic I/O operations
94 bool get(char& c) {return classdesc::get(i,c);}
95 void unget(char c) {classdesc::unget(i,c);}
97 char getNoEOF() {
98 char r;
99 if (!get(r)) throw xml_pack_error("invalid XML");
100 return r;
101 }
102
103 void gobble_comment();
104 void gobble_whitespace() {
105 char c;
106 bool notEof=get(c);
107 while (notEof && std::isspace(c)) notEof=get(c);
108 if (notEof) unget(c);
109 }
110 char parse_entity();
111 std::string retval(char c, const std::string& tok);
112 public:
113 XMLtoken(Stream& i): i(i), nexttok('\0') {}
114 std::string token();
115 std::string tokenNoEOF() {
116 std::string tok=token();
117 if (tok.empty()) throw xml_pack_error("XML token expected");
118 else return tok;
119 }
121 std::string processBang(std::string& tok,char c);
123 std::string processOpenXMLTag(std::string&);
124 };
125
126 template <class Stream>
127 void XMLtoken<Stream>::gobble_comment()
128 {
129 int level=1;
130 bool inString=false;
131 char c;
132 while (level)
133 {
134 c=getNoEOF();
135 if (c=='"') inString=!inString;
136 if (inString) continue;
137 switch(c)
138 {
139 case '<': level++; break;
140 case '>': level--; break;
141 }
142 }
143 gobble_whitespace();
144 }
145
146 template <class Stream>
147 char XMLtoken<Stream>::parse_entity()
148 {
149 std::string name;
150 char c;
151 for (c=getNoEOF(); c!=';'; c=getNoEOF())
152 name+=c;
153 if (name=="amp") return '&';
154 if (name=="lt") return '<';
155 if (name=="gt") return '>';
156 if (name=="quot") return '"';
157 if (name=="apos") return '\'';
158 const char* cname=name.c_str();
159 if (cname[0]=='#') //character code supplied
160 {
161 if (cname[1]=='x') //is hex
162 {
163 //TODO - should we be doing this all in wide chars?
164 long r=std::strtol(cname+2,NULL,16);
165 if (r>std::numeric_limits<char>::max() || r<std::numeric_limits<char>::min())
166 throw xml_pack_error("XML numeric character reference out of range");
167 return char(r);
168 }
169 else
170 {
171 //TODO - should we be doing this all in wide chars?
172 long r=std::strtol(cname+1,NULL,10);
173 if (r>std::numeric_limits<char>::max() || r<std::numeric_limits<char>::min())
174 throw xml_pack_error("XML numeric character reference out of range");
175 return char(r);
176 }
177 }
178 // not sure what to do about user defined entities - throw, or issue a warning
179 throw xml_pack_error("Unidentified entity encountered");
180 }
181
182 // This allows a previous token to be return when a single character token in parsed
183 template <class Stream>
184 std::string XMLtoken<Stream>::retval(char c, const std::string& tok)
185 {
186 if (tok.empty())
187 {
188 nexttok='\0';
189 switch (c)
190 {
191 case '/': return "</";
192 case '\\': return "/>";
193 default: return std::string(1,c);
194 }
195 }
196 else
197 {
198 nexttok=c;
199 return tok;
200 }
201 }
202
203 template <class Stream>
204 std::string XMLtoken<Stream>::processBang(std::string& tok,char c)
205 {
206 // look ahead for [CDATA[
207 std::string leadin;
208 for (int i=0; i<7; i++)
209 {
210 if (!get(c) || c=='>') break; // in case of EOF, or end of tag
211 leadin+=c;
212 }
213 if (leadin=="[CDATA[")
214 { // CDATA processing, add CDATA contents verbatim
215 leadin.clear();
216 while ((c=getNoEOF()))
217 {
218 if (c==']')
219 if (leadin=="]")
220 leadin+=c;
221 else
222 leadin=c;
223 else if (c=='>' && leadin=="]]")
224 return tok;
225 else
226 {
227 tok+=leadin+c;
228 leadin.clear();
229 }
230 }
231 }
232 else if (c!='>')
233 gobble_comment();
234 return "";
235 }
236
237 template <class Stream>
238 std::string XMLtoken<Stream>::processOpenXMLTag(std::string& tok)
239 {
240 char c=getNoEOF();
241 switch (c)
242 {
243 case '!': return processBang(tok,c);
244 case '?': //we have a comment or XML declaration, which we ignore, except for CDATA
245 gobble_comment(); return "";
246 case '/': //we have begin end tag token
247 return retval('/',tok);
248 default:
249 unget(c);
250 return retval('<',tok);
251 }
252 }
253
254 template <class Stream>
255 std::string XMLtoken<Stream>::token()
256 {
257 std::string tok;
258 char c;
259
260 // handle any tokens left over from previous parsing
261 if (nexttok)
262 return retval(nexttok,tok);
263
264 while (get(c))
265 {
266 // return white space as a separate token
267 if (std::isspace(c)) return retval(c,tok);
268
269 switch (c)
270 {
271 case '&':
272 tok+=parse_entity();
273 continue;
274 case '\'':
275 case '"': //process string literal as single token
276 {
277 char term=c;
278 while ((c=getNoEOF())!=term)
279 if (c=='&')
280 tok+=parse_entity();
281 else
282 tok+=c;
283 return tok;
284 }
285 case '<':
286 {
287 std::string r=processOpenXMLTag(tok);
288 if (r.empty()) continue;
289 return r;
290 }
291 case '/':
292 if ((c=getNoEOF())=='>') //we have end empty tag token
293 return retval('\\',tok);
294 else //TODO is a / in the middle of a token acceptible XML?
295 {
296 tok+='/';
297 unget(c);
298 break;
299 }
300 case '>':
301 case '=':
302 return retval(c,tok);
303 default:
304 tok+=c;
305 }
306 }
307 if (tok.empty())
308 return tok; //empty token returned on end of file
309 else
310 throw xml_pack_error("XML file truncated?");
311 }
312
316 class xml_unpack_t
317 {
318 public:
319 typedef std::map<std::string,std::string> ContentMap;
320 CLASSDESC_ACCESS(xml_unpack_t);
321 private:
322 ContentMap contentMap;
323 std::map<std::string,unsigned> tokenCount;
324
325 void checkKey(const std::string& key)
326 {
327 if (missingException && !contentMap.count(key))
328 throw xml_pack_error(key+" is missing in XML data stream");
329 }
330
331 // add "#0" to components if no # label present
332 std::string addHashNoughts(const std::string& key)
333 {
334 std::string r;
335 std::string::size_type start=0, end;
336 bool hash_read=false;
337 for (end=0; end<=key.length(); end++)
338 if (key[end]=='#')
339 hash_read=true;
340 else if (key[end]=='.')
341 {
342 if (hash_read)
343 hash_read=false;
344 else // no hash read, so insert "#0"
345 {
346 r+=key.substr(start,end-start)+"#0";
347 start=end;
348 }
349 }
350 r+=key.substr(start,end-start);
351 if (!hash_read)
352 r+="#0";
353 return r;
354 }
355
356 friend struct classdesc_access::access_pack<xml_unpack_t>;
357 friend struct classdesc_access::access_unpack<xml_unpack_t>;
358 public:
362 xml_unpack_t(): missingException(false) {}
363 xml_unpack_t(const char* fname): missingException(false) {std::ifstream i(fname); parse(i);}
364 template <class Stream> xml_unpack_t(Stream& i): missingException(false) {parse(i);}
365 template <class Stream> void process_attribute(XMLtoken<Stream>& i, const std::string& scope);
366 template <class Stream> void parse(Stream& i);
367 template <class Stream> void parse(XMLtoken<Stream>& stream, const std::string& scope);
368
370 ContentMap::const_iterator firstToken(const std::string& prefix) const {
371 return contentMap.lower_bound(prefix);
372 }
373 ContentMap::const_iterator endToken(const std::string& prefix) const {
374 return contentMap.upper_bound(prefix);
375 }
376
378 void printContentMap() const {
379 for (std::map<std::string,std::string>::const_iterator i=contentMap.begin();
380 i!=contentMap.end(); i++)
381 std::cout << "["<<i->first<<"]="<<i->second<<std::endl;
382 std::cout << std::endl;
383 for (std::map<std::string,unsigned>::const_iterator i=tokenCount.begin();
384 i!=tokenCount.end(); i++)
385 std::cout << "Count["<<i->first<<"]="<<i->second<<std::endl;
386 }
387
388 // specialise floating point processing to handle special values (NaN, Inf etc).
389 // fallback to regular iostream processing
390 template <class T> void istoT(const std::string& s, T& x)
391 {
392 std::istringstream is(s);
393 is>>x;
394 }
395#if defined(__cplusplus) && __cplusplus>=201103L
396 void stoT(const std::string& s, float& x)
397 try {x=std::stof(s);}
398 catch(...){istoT(s,x);}
399 void stoT(const std::string& s, double& x)
400 try {x=std::stod(s);}
401 catch(...){istoT(s,x);}
402 void stoT(const std::string& s, long double& x)
403 try {x=std::stold(s);}
404 catch(...){istoT(s,x);}
405#endif
406 template <class T> void stoT(const std::string& s, T& x)
407 {istoT(s,x);}
408
410 template <class T> void unpack(std::string key, T& var) {
411 key=addHashNoughts(key); checkKey(key);
412 std::map<std::string,std::string>::const_iterator it=contentMap.find(key);
413 if (it != contentMap.end()) stoT(it->second, var);
414 }
415 // specialisation to handle boolean values
416 void unpack(std::string key, bool& var) {
417 key=addHashNoughts(key); checkKey(key);
418 std::map<std::string,std::string>::const_iterator it=contentMap.find(key);
419 if (it != contentMap.end())
420 {
421 std::string val=it->second;
422 // strip any white space
423 val.erase(remove_if(val.begin(), val.end(), Isspace), val.end());
424 for (size_t i=0; i<val.length(); ++i) val[i]=char(tolower(val[i]));
425 var = val=="1" || val=="t" || val=="true"|| val=="y"|| val=="yes" ||
426 val=="on";
427 }
428 }
430 void unpack(std::string key, std::string& var) {
431 key=addHashNoughts(key); checkKey(key);
432 std::map<std::string,std::string>::const_iterator it=contentMap.find(key);
433 if (it != contentMap.end())
434 var=it->second;
435 }
436 void unpack(std::string key, CDATA& a)
437 {unpack(key,static_cast<std::string&>(a));}
438
440 bool exists(const std::string& key) {return count(key)>0;}
442 size_t count(std::string key) {
443 key=addHashNoughts(key);
444 key=key.substr(0,key.rfind('#')); //strip final # marker
445 return tokenCount[key];
446 }
447 void clear() {contentMap.clear(); tokenCount.clear();}
448 };
449
453 template <class Stream>
454 void xml_unpack_t::process_attribute(XMLtoken<Stream>& stream, const std::string& scope)
455 {
456 std::string tok;
457 while (isspace(tok=stream.tokenNoEOF()));
458 if (tok!="=") throw xml_pack_error("ill-formed attribute");
459 while (isspace(tok=stream.tokenNoEOF()));
460 contentMap[scope]=tok;
461 }
462
467 template <class Stream>
468 void xml_unpack_t::parse(Stream& i)
469 {
470 XMLtoken<Stream> stream(i);
471 std::string tok;
472 while (isspace(tok=stream.token()));
473 if (tok.empty()) return;
474 if (tok=="<")
475 parse(stream,stream.tokenNoEOF());
476 else
477 throw xml_pack_error("no root element found");
478 }
479
480 template <class Stream>
481 void xml_unpack_t::parse(XMLtoken<Stream>& stream, const std::string& scope)
482 {
483 //count the number of times this token has been read, and append this to database key
484 std::string scope_idx=idx(scope,tokenCount[scope]++);
485
486 std::string tok;
487 //parse attributes
488 for (tok=stream.tokenNoEOF(); tok!=">" && tok!="/>"; tok=stream.tokenNoEOF())
489 if (!isspace(tok)) process_attribute(stream, scope_idx+"."+tok);
490
491 if (tok=="/>") return;
492
493 //parse content. We assume element is either just content, or just has child elements
494 std::string content;
495 for (tok=stream.tokenNoEOF(); tok!="</"; tok=stream.tokenNoEOF())
496 if (tok=="<")
497 parse(stream,scope_idx+"."+stream.tokenNoEOF()); //parse child element
498 else
499 content+=tok;
500
501 if (content.size())
502 contentMap[scope_idx]=content; //override content (to handle masked private members)
503
504 // finish parsing end tag
505 tok=stream.tokenNoEOF();
506 if (scope.length()-scope.rfind(tok)!=tok.length()) //tok matches last part of scope
507 throw xml_pack_error("unexpected end tag");
508 for (; tok!=">"; tok=stream.tokenNoEOF()); //skip rest of end tag
509 }
510
511
512
513
514
515 template <class T> void xml_unpack(xml_unpack_t&,const string&,T&);
516
517 template <class T> xml_unpack_t& operator>>(xml_unpack_t& t, T& a);
518
519 /*
520 base type implementations
521 */
522 template <class T>
523 void xml_unpack_onbase(xml_unpack_t& x,const string& d,T& a)
524 {xml_unpack(x,d+basename<T>(),a);}
525
526 template <class T>
527 typename enable_if<is_fundamental<T>, void>::T
528 xml_unpackp(xml_unpack_t& x,const string& d,T& a)
529 {x.unpack(d,a);}
530
531 /* now define the array version */
532 template <class T> void xml_unpack(xml_unpack_t& x,const string& d,is_array ia,
533 T& a, int dims,size_t ncopies,...)
534 {
535 va_list ap;
536 va_start(ap,ncopies);
537 for (int i=1; i<dims; i++) ncopies*=va_arg(ap,int); //assume that 2 and higher D arrays dimensions are int
538 va_end(ap);
539
540 classdesc::string eName=classdesc::typeName<T>().c_str();
541 // strip leading namespace and qualifiers
542 const char *e=eName.c_str()+eName.length();
543 while (e!=eName.c_str() && *(e-1)!=' ' && *(e-1)!=':') e--;
544
545 for (size_t i=0; i<ncopies; i++)
546 xml_unpack(x,classdesc::idx(d+"."+e,i),(&a)[i]);
547 }
548
549 //Enum_handles have reference semantics
550 template <class T> void xml_unpack(xml_unpack_t& x,const string& d,Enum_handle<T> arg)
551{
552 std::string tmp;
553 xml_unpack(x,d,tmp);
554 // remove extraneous white space
555 int (*isspace)(int)=std::isspace;
556 std::string::iterator end=std::remove_if(tmp.begin(),tmp.end(),isspace);
557 arg=tmp.substr(0,end-tmp.begin());
558}
559
560 template <class T1, class T2>
561 void xml_unpack(xml_unpack_t& x, const string& d, std::pair<T1,T2>& arg)
562{
563 xml_unpack(x,d+".first",arg.first);
564 xml_unpack(x,d+".second",arg.second);
565}
566
567 template <class T> typename
569 xml_unpackp(xml_unpack_t& x, const string& d, T& arg, dummy<1> dum=0)
570 {
571 string eName=typeName<typename T::value_type>().c_str();
572 eName=eName.substr(0,eName.find('<')); //trim off any template args
573 // strip leading namespace and qualifiers
574 const char *e=eName.c_str()+eName.length();
575 while (e!=eName.c_str() && *(e-1)!=' ' && *(e-1)!=':') e--;
576
577 size_t cnt=x.count(d+"."+e);
578 resize(arg,0); // clear container if resizable
579 resize(arg,cnt);
580 size_t i=0;
581 for (typename T::iterator j=arg.begin(); i<cnt && j!=arg.end(); ++i, ++j)
582 xml_unpack(x,idx(d+"."+e,i),*j);
583 }
584
585 template <class T> typename
587 xml_unpackp(xml_unpack_t& x, const string& d, T& arg, dummy<2> dum=0)
588 {
589 string eName=typeName<typename T::value_type>().c_str();
590 eName=eName.substr(0,eName.find('<')); //trim off any template args
591 // strip leading namespace and qualifiers
592 const char *e=eName.c_str()+eName.length();
593 while (e!=eName.c_str() && *(e-1)!=' ' && *(e-1)!=':') e--;
594
595 arg.clear();
596 string prefix=d.empty()? e: d+"."+e;
597 for (size_t i=0; i<x.count(prefix); ++i)
598 {
599 typename NonConstKeyValueType<typename T::value_type>::T v;
600 xml_unpack(x,idx(prefix,i),v);
601 arg.insert(v);
602 }
603 }
604
605 template<class T>
606 void//typename enable_if<Not<is_pointer<T> >,void>::T
607 xml_unpack(xml_unpack_t& targ, const string& desc, is_const_static i, T arg)
608 {}
609
610 template<class T>
611 void xml_unpack(xml_unpack_t& targ, const string& desc,
612 Exclude<T>&) {}
613
614 template<class T>
615 void xml_unpack(xml_unpack_t& targ, const string& desc,
616 CDATA& a)
617 {targ.unpack(desc,a);}
618
619 template<class T>
620 void xml_unpack(xml_unpack_t& targ, const string& desc, is_graphnode, T&)
621 {
622 throw exception("xml_unpack of arbitrary graphs not supported");
623 }
624
625
626}
627
628#include "use_mbr_pointers.h"
629CLASSDESC_USE_OLDSTYLE_MEMBER_OBJECTS(xml_unpack)
630CLASSDESC_FUNCTION_NOP(xml_unpack)
631
632using classdesc::xml_unpack;
633using classdesc::xml_unpack_onbase;
634
635#endif
Definition classdesc.h:868
Definition xml_unpack_base.h:89
std::string processOpenXMLTag(std::string &)
handle XML tags ('<' case)
Definition xml_unpack_base.h:238
std::string processBang(std::string &tok, char c)
handle tags starting with ! - comments and CDATAs
Definition xml_unpack_base.h:204
Definition classdesc.h:920
Definition classdesc.h:923
Definition classdesc.h:931
Definition xml_unpack_base.h:71
Definition xml_unpack_base.h:317
void printContentMap() const
dump XML contents for debugging
Definition xml_unpack_base.h:378
ContentMap::const_iterator firstToken(const std::string &prefix) const
first token starting with prefix
Definition xml_unpack_base.h:370
void process_attribute(XMLtoken< Stream > &i, const std::string &scope)
Definition xml_unpack_base.h:454
void parse(Stream &i)
Definition xml_unpack_base.h:468
void unpack(std::string key, std::string &var)
string deserialisation
Definition xml_unpack_base.h:430
bool missingException
Definition xml_unpack_base.h:361
size_t count(std::string key)
returns number of array elements with prefix key
Definition xml_unpack_base.h:442
void unpack(std::string key, T &var)
simple data type deserialisation
Definition xml_unpack_base.h:410
bool exists(const std::string &key)
checks for existence of token unpacked from XML stream
Definition xml_unpack_base.h:440
descriptor access to a class's privates
#define CLASSDESC_ACCESS(type)
add friend statements for each accessor function
Definition classdesc_access.h:36
Contains access_* structs, and nothing else. These structs are used to gain access to private members...
Definition classdesc_access.h:20
Contains definitions related to classdesc functionality.
std::string idx(const std::string &prefix, size_t i)
utility for generating index keys (for use with arrays)
Definition xml_common.h:14
string basename()
returns a valid identifier to append to the descriptor of a base class
Definition classdesc.h:1127
serialisation descriptor
serialisation for standard containers
used to transfer contents of CDATA sections.
Definition xml_common.h:23
Definition classdesc.h:1012
Definition classdesc.h:299
controlled template specialisation: stolen from boost::enable_if.
Definition classdesc.h:282
base class for exceptions thrown by classdesc
Definition classdesc.h:546
class to allow access to private members
Definition classdesc_access.h:21
class to allow access to private members
Definition classdesc_access.h:22