initial commit, lordsawar source, slightly modified
[lordsawar] / src / xmlhelper.cpp
1 // Copyright (C) 2002, 2003 Michael Bartl
2 // Copyright (C) 2002, 2003, 2004, 2005, 2006 Ulf Lorenz
3 // Copyright (C) 2003, 2004, 2005 Andrea Paternesi
4 //
5 //  This program is free software; you can redistribute it and/or modify
6 //  it under the terms of the GNU General Public License as published by
7 //  the Free Software Foundation; either version 3 of the License, or
8 //  (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU Library General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
18 //  02110-1301, USA.
19
20 #include <iostream>
21 #include <sstream>
22 #include <algorithm>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <zlib.h>
26 #include "xmlhelper.h"
27 #include "defs.h"
28
29 //#define debug(x) {std::cerr<<__FILE__<<": "<<__LINE__<<": "<<x<<std::endl<<std::flush;}
30 #define debug(x)
31
32 //they are only needed later for the expat callbacks
33 std::string my_cdata;
34 bool error = false;
35
36 // forward declarations of the internally used functions
37 void start_handler(void* udata, const XML_Char* name, const XML_Char** atts);
38 void character_handler(void* udata, const XML_Char* s, int len);
39 void end_handler(void* udata, const XML_Char* name);
40
41
42
43 XML_Helper::XML_Helper(std::string filename, std::ios::openmode mode, bool zip)
44   : d_inbuf(0), d_outbuf(0), d_fout(0), d_fin(0), d_out(0), d_in(0),
45     d_last_opened(""), d_version(""), d_failed(false), d_zip(zip)
46 {
47     debug("Constructor called  -- " << zip)
48         
49     // always use a helper either for reading or for writing. Doing both
50     // is propably possible, but there is little point in using it anyway.
51     if ((mode & std::ios::in) && (mode & std::ios::out))
52     {
53         std::cerr << "XML_Helper: Either open file for reading or writing, not both, exiting\n";
54         exit(-1);
55     }
56
57     //open input stream if required
58     if (mode & std::ios::in)
59     {
60         d_fin = new std::ifstream(filename.c_str(), std::ios::in);
61
62         if (!(*d_fin))
63         //error opening
64         {
65             std::cerr <<filename << ": Error opening file for reading. Exiting\n";
66             exit(-1);
67         }
68
69         char tmp;
70         d_fin->read(&tmp,1);
71         debug("IS ZIPPED ->" << tmp)                
72
73         if (d_fin->eof())
74         {
75             std::cerr << "File too short (<=1 byte), propably broken. Skipping load\n";
76             d_failed = true;
77         }
78
79         if (tmp=='Z') 
80         {  
81             std::cout <<filename << ": The file is obfuscated, attempting to read it....\n"; 
82             d_fin->seekg (0,std::ios::end);
83             long length = d_fin->tellg();
84             d_fin->seekg (1, std::ios::beg);    
85
86             uLongf destlen1=0;
87             (*d_fin) >> destlen1;
88
89             debug(destlen1 << " -- "<< length);
90
91             length--;
92             length-=sizeof(uLongf);
93
94             char * buffer = (char*) malloc(length);        
95             char * buf1 = (char *) malloc(destlen1);
96
97             d_fin->read(buffer,length);
98             d_fin->close();
99
100             uncompress((Bytef*)buf1,&destlen1,(Bytef*)buffer,length);
101
102             free(buffer);
103             debug(destlen1 << " -- "<< length);
104         
105             d_inbuf = new std::istringstream(buf1);
106
107             free(buf1);
108             d_in=d_inbuf;
109         }
110         else 
111         {
112             //std::cout <<filename <<_(": The file is not obfuscated, attempting to read it....\n"); 
113             d_fin->seekg(0, std::ios::beg);
114             d_in = d_fin;
115         }
116     }
117
118     if (mode & std::ios::out)
119     {
120         d_fout = new std::ofstream(filename.c_str(),
121                                    std::ios::out & std::ios::trunc);
122         if (!(*d_fout))
123         {
124             std::cerr <<filename << ": Error opening file for writing. Exiting\n";
125             exit(-1);
126         }
127
128         d_outbuf = new std::ostringstream();
129         d_out = d_outbuf;
130     }
131 }
132
133 XML_Helper::XML_Helper(std::ostream* output)
134   : d_inbuf(0), d_outbuf(0), d_fout(0), d_fin(0), d_out(0), d_in(0),
135     d_last_opened(""), d_version(""), d_failed(false), d_zip(false)
136 {
137   d_out = output;
138 }
139
140 XML_Helper::XML_Helper(std::istream* input)
141   : d_inbuf(0), d_outbuf(0), d_fout(0), d_fin(0), d_out(0), d_in(0),
142     d_last_opened(""), d_version(""), d_failed(false), d_zip(false)
143 {
144   d_in = input;
145 }
146
147 XML_Helper::~XML_Helper()
148 {
149     if (d_tags.size() != 0)
150     // should never happen unless there was an error
151         std::cerr << "XML_Helper: dtags not empty!!\n";
152     
153     debug("Called destructor\n")    
154     close();
155 }
156
157 bool XML_Helper::begin(std::string version)
158 {
159     d_version = version;
160     (*d_out) <<"<?xml version=\"1.0\"?>\n";
161
162     return true;
163 }
164
165 bool XML_Helper::openTag(std::string name)
166 {
167     if (!d_out)
168     {
169         std::cerr << "XML_Helper: no output stream given\n";
170         return false;
171     }
172
173     if ((name[0] == 'd') && (name[1] == '_'))
174     {
175         std::cerr <<name << ": The tag name starts with a \"d\". Not creating tag!\n";
176         return false;
177     }
178
179     addTabs();
180
181     // append the version strin got the first opened tag
182     if (d_tags.empty())
183         (*d_out) <<"<" <<name <<" version=\"" <<d_version <<"\">\n";
184     else
185         (*d_out) <<"<" <<name <<">\n";
186         
187     d_tags.push_front(name);
188     return true;
189 }
190
191 bool XML_Helper::closeTag()
192 {
193     if (!d_out)
194     {
195         std::cerr << "XML_Helper: no output stream given.\n";
196         return false;
197     }
198
199     std::string name = (*d_tags.begin());
200     
201     //remove tag from list
202     d_tags.pop_front();
203
204     addTabs();
205     (*d_out) <<"</" <<name <<">\n";
206
207     return true;
208 }
209
210         
211 bool XML_Helper::saveData(std::string name, const Gdk::Color value)
212 {
213     //prepend a "d_" to show that this is a data tag
214     name = "d_" + name;
215
216     if (name.empty())
217     {
218         std::cerr << "XML_Helper: save_data with empty name\n";
219         return false;
220     }
221     if (!d_out)
222     {
223         std::cerr << "XML_Helper: no output stream given.\n";
224         return false;
225     }
226
227     addTabs();
228     char buf[3];
229     guint32 r, g, b;
230     r = value.get_red_p() * 255;
231     g = value.get_green_p() * 255;
232     b = value.get_blue_p() * 255;
233     snprintf(buf, sizeof(buf), "%02X", r);
234     std::string red = buf;
235     snprintf(buf, sizeof(buf), "%02X", g);
236     std::string green = buf;
237     snprintf(buf, sizeof(buf), "%02X", b);
238     std::string blue = buf;
239
240     (*d_out) <<"<" <<name <<">#" <<red <<green<<blue <<"</" <<name <<">\n";
241   return true;
242 }
243
244 bool XML_Helper::saveData(std::string name, std::string value)
245 {
246     //prepend a "d_" to show that this is a data tag
247     name = "d_" + name;
248
249     if (name.empty())
250     {
251         std::cerr << "XML_Helper: save_data with empty name\n";
252         return false;
253     }
254     if (!d_out)
255     {
256         std::cerr << "XML_Helper: no output stream given.\n";
257         return false;
258     }
259
260     addTabs();
261     (*d_out) <<"<" <<name <<">" <<value <<"</" <<name <<">\n";
262     return true;
263 }
264
265 bool XML_Helper::saveData(std::string name, int value)
266 {
267     //prepend a "d_" to show that this is a data tag
268     name = "d_" + name;
269
270     if (name.empty())
271     {
272         std::cerr << "XML_Helper: save_data with empty name\n";
273         return false;
274     }
275     if (!d_out)
276     {
277         std::cerr << "XML_Helper: no output stream given.\n";
278         return false;
279     }
280
281     addTabs();
282     (*d_out) <<"<" <<name <<">" <<value <<"</" <<name <<">\n";
283     return true;
284 }
285
286 bool XML_Helper::saveData(std::string name, guint32 value)
287 {
288     //prepend a "d_" to show that this is a data tag
289     name = "d_" + name;
290
291     if (name.empty())
292     {
293         std::cerr << "XML_Helper: save_data with empty name\n";
294         return false;
295     }
296     if (!d_out)
297     {
298         std::cerr << "XML_Helper: no output stream given.\n";
299         return false;
300     }
301
302     addTabs();
303     (*d_out) <<"<" <<name <<">" <<value <<"</" <<name <<">\n";
304     return true;
305 }
306
307 bool XML_Helper::saveData(std::string name, bool value)
308 {
309     //prepend a "d_" to show that this is a data tag
310     name = "d_" + name;
311
312     if (name.empty())
313     {
314         std::cerr << "XML_Helper: save_data with empty name\n";
315         return false;
316     }
317     if (!d_out)
318     {
319         std::cerr << "XML_Helper: no output stream given.\n";
320         return false;
321     }
322
323     std::string s;
324     s = (value? "true" : "false");
325
326     addTabs();
327     (*d_out) <<"<" <<name <<">" <<s <<"</" <<name <<">\n";
328     return true;
329 }
330
331 bool XML_Helper::saveData(std::string name, double value)
332 {
333     //prepend a "d_" to show that this is a data tag
334     name = "d_" + name;
335
336     if (name.empty())
337     {
338         std::cerr << "XML_Helper: save_data with empty name\n";
339         return false;
340     }
341     if (!d_out)
342     {
343         std::cerr << "XML_Helper: no output stream given.\n";
344         return false;
345     }
346
347     addTabs();
348     (*d_out) <<"<" <<name <<">" <<value <<"</" <<name <<">\n";
349     return true;
350 }
351
352 /* This is a wrapper for the AMD64 platform */
353 bool XML_Helper::saveData(std::string name, unsigned long int value)
354 {
355     return saveData(name, static_cast<guint32>(value));
356 }
357 /* End wrapper AMD64 */
358
359 bool XML_Helper::close()
360 {
361     if (d_outbuf)        
362     {
363         if (d_zip)        
364         {
365             debug("I zip IT")
366             debug("Saving game and obfuscating the Savefile.\n") 
367             std::string tmp = d_outbuf->str();
368             tmp+='\0';
369
370             long origlength = tmp.length();
371             uLongf ziplength = static_cast<uLongf>(origlength + (12+0.01*origlength));
372             char *buf1= (char*) malloc(ziplength);
373          
374             int ret = compress2((Bytef*)buf1, &ziplength,
375                                 (Bytef*)tmp.c_str(), (uLong)origlength, 9);
376             debug("RET="<<ret<<" length=" << origlength << " destlen1=" << ziplength)
377
378             (*d_fout) << 'Z';
379             (*d_fout) << origlength;
380
381             d_fout->write(buf1, ziplength);
382             d_fout->flush();
383
384             free(buf1);
385             delete d_outbuf;
386
387             d_outbuf = 0;
388             debug("destroyed d_outbuf")
389             ret=0;// to avoid warning 
390         }
391         else 
392         {
393             debug("I do not zip IT")
394             debug(_("Saving game without obfuscation.\n")) 
395
396             std::string tmp = d_outbuf->str();
397             d_fout->write(tmp.c_str(), tmp.length());
398             d_fout->flush();
399
400             delete d_outbuf;
401             d_outbuf = 0;
402             debug("destroyed d_outbuf")
403         }
404     }
405
406     if (d_inbuf)        
407     {
408         delete d_inbuf;
409         d_inbuf = 0;
410         debug("destroyed d_inbuf")
411     }
412
413     if (d_fout)
414     {
415         d_fout->close();
416         delete d_fout;
417         d_fout = 0;
418     }
419
420     if (d_fin)
421     {
422         d_fin->close();
423         delete d_fin;
424         d_fin = 0;
425     }
426
427     d_out = 0;
428     d_in = 0;
429         
430     return true;
431 }
432
433 void XML_Helper::addTabs()
434 {
435     for (unsigned int i = d_tags.size(); i > 0; i--)
436         (*d_out)<<"\t";
437 }
438
439 //loading
440 bool XML_Helper::registerTag(std::string tag, XML_Slot callback)
441 {
442     //register tag as important
443     d_callbacks[tag] = callback;
444
445     return true;
446 }
447
448
449 bool XML_Helper::unregisterTag(std::string tag)
450 {
451     std::map<std::string, XML_Slot>::iterator it = d_callbacks.find(tag);
452
453     if (it == d_callbacks.end())
454     //item doesn't exist
455         return false;
456     
457     d_callbacks.erase(it);
458     return true;
459 }
460
461 bool XML_Helper::getData(Gdk::Color& data, std::string name)
462 {
463     //the data tags are stored with leading "d_", so prepend it here
464     name = "d_" + name;
465
466     std::map<std::string, std::string>::const_iterator it;
467
468     it = d_data.find(name);
469     
470     if (it == d_data.end())
471     {
472         data.set_rgb_p(0,0,0);
473         std::cerr<<"XML_Helper::getData(Gdk::Color, \"" <<name <<"\") failed\n";
474         d_failed = true;
475         return false;
476     }
477     
478     std::string value = (*it).second;
479     char buf[15];
480     int retval = sscanf(value.c_str(), "%s", buf);
481     if (retval == -1)
482       return false;
483     buf[14] = '\0';
484     int red = 0, green = 0, blue = 0;
485     if (buf[0] == '#')
486       {
487         char hash;
488         //must look like "#00FF33"
489         retval = sscanf(buf, "%c%02X%02X%02X", &hash, &red, &green, &blue);
490         if (retval != 4)
491           return false;
492       }
493     else
494       {
495         //must look like "123 255 000"
496         retval = sscanf(value.c_str(), "%d%d%d", &red, &green, &blue);
497         if (retval != 3)
498           return false;
499         if (red > 255 || red < 0 || green > 255 || green < 0 || 
500             blue > 255 || blue < 0)
501           return false;
502       }
503     data.set_rgb_p((float)red / 255.0, (float)green / 255.0,
504                    (float)blue / 255.0);
505   return true;
506 }
507
508 bool XML_Helper::getData(std::string& data, std::string name)
509 {
510     //the data tags are stored with leading "d_", so prepend it here
511     name = "d_" + name;
512
513     std::map<std::string, std::string>::const_iterator it;
514
515     it = d_data.find(name);
516     
517     if (it == d_data.end())
518     {
519         data = "";
520         std::cerr<<"XML_Helper::getData(std::string, \"" <<name <<"\") failed\n";
521         d_failed = true;
522         return false;
523     }
524     
525     data = (*it).second;
526
527     return true;
528 }
529
530 bool XML_Helper::getData(bool& data, std::string name)
531 {
532     //the data tags are stored with leading "d_", so prepend it here
533     name = "d_" + name;
534
535     std::map<std::string, std::string>::const_iterator it;
536     it = d_data.find(name);
537
538     if (it == d_data.end())
539     {
540         std::cerr<<"XML_Helper::getData(bool, \"" <<name <<"\") failed\n";
541         d_failed = true;
542         return false;
543     }
544     
545     if ((*it).second == "true")
546     {
547         data = true;
548         return true;
549     }
550
551     if ((*it).second == "false")
552     {
553         data = false;
554         return true;
555     }
556     
557     return false;
558 }
559
560 bool XML_Helper::getData(int& data, std::string name)
561 {
562     //the data tags are stored with leading "d_", so prepend it here
563     name = "d_" + name;
564
565     std::map<std::string, std::string>::const_iterator it;
566     it = d_data.find(name);
567
568     if (it == d_data.end())
569     {
570         std::cerr<<"XML_Helper::getData(int, \"" <<name <<"\") failed\n";
571         d_failed = true;
572         return false;
573     }
574     
575     data = atoi((*it).second.c_str());
576     return true;
577 }
578
579 bool XML_Helper::getData(guint32& data, std::string name)
580 {
581     //the data tags are stored with leading "d_", so prepend it here
582     name = "d_" + name;
583
584     std::map<std::string, std::string>::const_iterator it;
585     it = d_data.find(name);
586
587     if (it == d_data.end())
588     {
589         std::cerr<<"XML_Helper::getData(guint32, \"" <<name <<"\") failed\n";
590         d_failed = true;
591         return false;
592     }
593     
594     data = static_cast<guint32>(atoi((*it).second.c_str()));
595     return true;
596
597     
598 }
599
600 bool XML_Helper::getData(double& data, std::string name)
601 {
602     //the data tags are stored with leading "d_", so prepend it here
603     name = "d_" + name;
604
605     std::map<std::string, std::string>::const_iterator it;
606     it = d_data.find(name);
607
608     if (it == d_data.end())
609     {
610         std::cerr<<"XML_Helper::getData(double, \"" <<name <<"\") failed\n";
611         d_failed = true;
612         return false;
613     }
614
615     data = strtod((*it).second.c_str(), 0);
616     return true;
617 }
618
619 bool XML_Helper::parse()
620 {
621     if (!d_in || d_failed)
622         return false;
623         //what's the use of parsing no or incorrect input?
624
625     d_parser = XML_ParserCreate("utf-8");
626
627     //set handlers and data
628     XML_SetElementHandler(d_parser, start_handler, end_handler);
629     XML_SetCharacterDataHandler(d_parser, character_handler);
630     XML_SetUserData(d_parser, static_cast<void*>(this));
631
632     while (!d_in->eof() && !d_failed)
633     {
634         char* buffer = static_cast<char*>(XML_GetBuffer(d_parser,1000));
635         d_in->getline(buffer, 1000);
636         bool my_eof = d_in->eof();
637
638         if (!XML_ParseBuffer(d_parser, strlen(buffer), my_eof))
639         //error parsing
640         {
641             std::cerr <<"XML_Helper: error parsing xml document.\n";
642             std::cerr <<"Line " <<XML_GetCurrentLineNumber(d_parser);
643             std::cerr <<": " <<XML_ErrorString(XML_GetErrorCode(d_parser)) <<"\n";
644             std::cerr <<"Buffercontent = " << buffer << std::endl;
645             d_failed = true;
646         }
647     }
648
649     XML_ParserFree(d_parser);
650
651     return (!d_failed);
652 }
653
654 //beginning with here is only internal stuff. Continue reading only if you are
655 //interested in the xml parsing. :)
656
657 /* Parsing works like this: We have three expat callback functions,
658  * startelementhandler, endelementhandler and cdatahandler.
659  * The startelement handler just calls XML_Helper::tag_open (an object is passed
660  * as data), the cdata element handler just sums up the cdata, and the end
661  * element handler calls XML_Helper::tag_close, giving it also the final cdata
662  * string (the string between opened tag and closed tag) to the XML_Helper.
663  * Since data is always stored like "<mydata>data_value</mydata>", having
664  * a startelementhandler encounter a non-null summed up cdata string is a
665  * serious error and results in a fail.
666  *
667  * Now the XML_Helper functions: 
668  * tag_open looks if another important tag has already been opened last (and not
669  * called back). If so, it assumes that all important data has already been
670  * stored and calls the callback for the former tag. If not, it just goes on.
671  * last_opened is always set to the last opened tag marked as important.
672  * If tag_close is called, it is mostly for data. If cdata is != 0 it is some
673  * saved data. If the last_opened tag is the same as the closed tag (we disallow
674  * and thus ignore constructions like "<mytag> <mytag> </mytag> </mytag>" here,
675  * they are IMO pointless), we suppose that the callback has not been called yet
676  * and do it now. If not, then there has been another important tag on the way
677  * which has led tag_open to already call the callback.
678  */
679
680 bool XML_Helper::tag_open(std::string tag, std::string version, std::string lang)
681 {
682     if (d_failed)
683         return false;
684         
685     //first of all, register the tag as opened
686     d_tags.push_front(tag);
687
688     if (version != "")
689         d_version = version;
690
691     //look if the tag starts with "d_". If so, it is a data tag without anything
692     //important in between
693     if ((tag[0] == 'd') && (tag[1] == '_'))
694       {
695         d_lang[tag] = lang;
696         return true;
697       }
698     
699     //first of all, look if another important tag has already been opened
700     //and call the appropriate callback if so
701     std::list<std::string>::iterator ls_it;
702     ls_it = d_tags.begin();
703     ls_it++;
704
705     if ((ls_it != d_tags.end()) && (d_last_opened == (*ls_it)))
706     {
707         std::map<std::string, XML_Slot>::iterator it;
708         it = d_callbacks.find(*ls_it);
709
710         
711         if (it != d_callbacks.end())
712         {
713             //make the callback (yes that is the syntax, overloaded "()")
714             bool retval = (it->second)(*ls_it, this);
715             if (retval == false)
716             {
717                 std::cerr <<(*ls_it) << ": Callback for tag returned false. Stop parsing document.\n";
718                 error = true;
719                 d_failed = true;
720             }
721         }
722
723         //clear d_data (we are just setting up a new tag)
724         d_data.clear();
725         d_lang.clear();
726     }
727
728     d_last_opened = tag;
729
730     return true;
731 }
732
733 bool XML_Helper::lang_check(std::string lang)
734 {
735   static char *envlang = getenv("LANG");
736   if (lang == "")
737     return true;
738   if (lang == envlang)
739     return true;
740   //try harder
741   char *first_underscore = strchr (envlang, '_');
742   if (first_underscore)
743     {
744       if (strncmp (lang.c_str(), envlang, first_underscore - envlang) == 0)
745         return true;
746     }
747   return false;
748 }
749
750 bool XML_Helper::tag_close(std::string tag, std::string cdata)
751 {
752     if (d_failed)
753         return false;
754
755     //remove tag entry, there is nothing more to be done
756     d_tags.pop_front();
757     
758     if ((tag[0] == 'd') && (tag[1] == '_'))
759     {
760         // save the data (we close a data tag)
761         if (lang_check(d_lang[tag]))
762           d_data[tag] = cdata;
763         return true;    //data tags end here with their execution
764     }
765
766     if ((d_last_opened == tag))
767     //callback hasn't been called yet
768     {
769         std::map<std::string, XML_Slot>::iterator it;
770         it = d_callbacks.find(tag);
771         
772         if (it != d_callbacks.end())
773         {
774             //make the callback (yes that is the syntax, overloaded "()")
775             bool retval = it->second(tag, this);
776             
777             if (retval == false)
778             {
779                 std::cerr <<tag <<": Callback for tag returned false. Stop parsing document.\n";
780                 error = true;
781                 d_failed = true;
782             }
783         }
784     }
785
786     //clear d_data (we are just setting up a new tag)
787     d_data.clear();
788     d_lang.clear();
789     
790     return true;
791 }
792
793 //these functions are the expat callbacks. Their main purpose is to set up
794 //the parametres and call the tag_open and tag_close functions in XML_Helper.
795
796 //the start handler, call open_tag and do misc init stuff
797 void start_handler(void* udata, const XML_Char* name, const XML_Char** atts)
798 {
799     if (error)
800         return;
801     
802     XML_Helper* helper = static_cast<XML_Helper*>(udata);
803     std::string version = "";
804     std::string lang = "";
805
806     //the only attribute we know and handle are version strings
807     if ((atts[0] != 0) && (std::string(atts[0]) == "version"))
808         version = std::string(atts[1]);
809
810     if ((atts[0] != 0) && (std::string(atts[0]) == "xml:lang"))
811         lang = std::string(atts[1]);
812
813     my_cdata = "";
814
815     error = !helper->tag_open(std::string(name), version, lang);
816 }
817
818 //the cdata handler, just sums up the string  s
819 void character_handler(void* udata, const XML_Char* s, int len)
820 {
821     if (error)
822         return;
823
824     char buffer[len+1];     //TODO: this is a gcc extension, very handy, but
825                             //not neccessarily portable
826     
827     strncpy(buffer, s, len);
828     buffer[len] = '\0';
829
830     //now add the string to the other one
831     my_cdata += std::string(buffer);
832 }
833
834 //the end handler: call tag_close and dosome cleanup
835 void end_handler(void* udata, const XML_Char* name)
836 {
837     if (error)
838         return;
839     
840     XML_Helper* helper = static_cast<XML_Helper*>(udata);
841
842     error = !helper->tag_close(std::string(name), my_cdata);
843
844     my_cdata = "";
845 }