1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/xml/xml.cpp
3 // Purpose:     wxXmlDocument - XML parser & data holder class
4 // Author:      Vaclav Slavik
5 // Created:     2000/03/05
6 // RCS-ID:      $Id: xml.cpp 65726 2010-10-02 15:47:41Z TIK $
7 // Copyright:   (c) 2000 Vaclav Slavik
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13 
14 #ifdef __BORLANDC__
15     #pragma hdrstop
16 #endif
17 
18 #if wxUSE_XML
19 
20 #include "wx/xml/xml.h"
21 
22 #ifndef WX_PRECOMP
23     #include "wx/intl.h"
24     #include "wx/log.h"
25     #include "wx/app.h"
26 #endif
27 
28 #include "wx/wfstream.h"
29 #include "wx/datstrm.h"
30 #include "wx/zstream.h"
31 #include "wx/strconv.h"
32 
33 #include "expat.h" // from Expat
34 
35 // DLL options compatibility check:
36 WX_CHECK_BUILD_OPTIONS("wxXML")
37 
38 
39 IMPLEMENT_CLASS(wxXmlDocument, wxObject)
40 
41 
42 // a private utility used by wxXML
43 static bool wxIsWhiteOnly(const wxChar *buf);
44 
45 
46 //-----------------------------------------------------------------------------
47 //  wxXmlNode
48 //-----------------------------------------------------------------------------
49 
wxXmlNode(wxXmlNode * parent,wxXmlNodeType type,const wxString & name,const wxString & content,wxXmlProperty * props,wxXmlNode * next)50 wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type,
51                      const wxString& name, const wxString& content,
52                      wxXmlProperty *props, wxXmlNode *next)
53     : m_type(type), m_name(name), m_content(content),
54       m_properties(props), m_parent(parent),
55       m_children(NULL), m_next(next)
56 {
57     if (m_parent)
58     {
59         if (m_parent->m_children)
60         {
61             m_next = m_parent->m_children;
62             m_parent->m_children = this;
63         }
64         else
65             m_parent->m_children = this;
66     }
67 }
68 
wxXmlNode(wxXmlNodeType type,const wxString & name,const wxString & content)69 wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name,
70                      const wxString& content)
71     : m_type(type), m_name(name), m_content(content),
72       m_properties(NULL), m_parent(NULL),
73       m_children(NULL), m_next(NULL)
74 {}
75 
wxXmlNode(const wxXmlNode & node)76 wxXmlNode::wxXmlNode(const wxXmlNode& node)
77 {
78     m_next = NULL;
79     m_parent = NULL;
80     DoCopy(node);
81 }
82 
~wxXmlNode()83 wxXmlNode::~wxXmlNode()
84 {
85     wxXmlNode *c, *c2;
86     for (c = m_children; c; c = c2)
87     {
88         c2 = c->m_next;
89         delete c;
90     }
91 
92     wxXmlProperty *p, *p2;
93     for (p = m_properties; p; p = p2)
94     {
95         p2 = p->GetNext();
96         delete p;
97     }
98 }
99 
operator =(const wxXmlNode & node)100 wxXmlNode& wxXmlNode::operator=(const wxXmlNode& node)
101 {
102     wxDELETE(m_properties);
103     wxDELETE(m_children);
104     DoCopy(node);
105     return *this;
106 }
107 
DoCopy(const wxXmlNode & node)108 void wxXmlNode::DoCopy(const wxXmlNode& node)
109 {
110     m_type = node.m_type;
111     m_name = node.m_name;
112     m_content = node.m_content;
113     m_children = NULL;
114 
115     wxXmlNode *n = node.m_children;
116     while (n)
117     {
118         AddChild(new wxXmlNode(*n));
119         n = n->GetNext();
120     }
121 
122     m_properties = NULL;
123     wxXmlProperty *p = node.m_properties;
124     while (p)
125     {
126        AddProperty(p->GetName(), p->GetValue());
127        p = p->GetNext();
128     }
129 }
130 
HasProp(const wxString & propName) const131 bool wxXmlNode::HasProp(const wxString& propName) const
132 {
133     wxXmlProperty *prop = GetProperties();
134 
135     while (prop)
136     {
137         if (prop->GetName() == propName) return true;
138         prop = prop->GetNext();
139     }
140 
141     return false;
142 }
143 
GetPropVal(const wxString & propName,wxString * value) const144 bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const
145 {
146     wxCHECK_MSG( value, false, wxT("value argument must not be NULL") );
147 
148     wxXmlProperty *prop = GetProperties();
149 
150     while (prop)
151     {
152         if (prop->GetName() == propName)
153         {
154             *value = prop->GetValue();
155             return true;
156         }
157         prop = prop->GetNext();
158     }
159 
160     return false;
161 }
162 
GetPropVal(const wxString & propName,const wxString & defaultVal) const163 wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& defaultVal) const
164 {
165     wxString tmp;
166     if (GetPropVal(propName, &tmp))
167         return tmp;
168 
169     return defaultVal;
170 }
171 
AddChild(wxXmlNode * child)172 void wxXmlNode::AddChild(wxXmlNode *child)
173 {
174     if (m_children == NULL)
175         m_children = child;
176     else
177     {
178         wxXmlNode *ch = m_children;
179         while (ch->m_next) ch = ch->m_next;
180         ch->m_next = child;
181     }
182     child->m_next = NULL;
183     child->m_parent = this;
184 }
185 
InsertChild(wxXmlNode * child,wxXmlNode * before_node)186 bool wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node)
187 {
188     wxCHECK_MSG(before_node == NULL || before_node->GetParent() == this, false,
189                  wxT("wxXmlNode::InsertChild - the node has incorrect parent"));
190     wxCHECK_MSG(child, false, wxT("Cannot insert a NULL pointer!"));
191 
192     if (m_children == before_node)
193        m_children = child;
194     else if (m_children == NULL)
195     {
196         if (before_node != NULL)
197             return false;       // we have no children so we don't need to search
198         m_children = child;
199     }
200     else if (before_node == NULL)
201     {
202         // prepend child
203         child->m_parent = this;
204         child->m_next = m_children;
205         m_children = child;
206         return true;
207     }
208     else
209     {
210         wxXmlNode *ch = m_children;
211         while (ch && ch->m_next != before_node) ch = ch->m_next;
212         if (!ch)
213             return false;       // before_node not found
214         ch->m_next = child;
215     }
216 
217     child->m_parent = this;
218     child->m_next = before_node;
219     return true;
220 }
221 
222 // inserts a new node right after 'precedingNode'
InsertChildAfter(wxXmlNode * child,wxXmlNode * precedingNode)223 bool wxXmlNode::InsertChildAfter(wxXmlNode *child, wxXmlNode *precedingNode)
224 {
225     wxCHECK_MSG( child, false, wxT("cannot insert a NULL node!") );
226     wxCHECK_MSG( child->m_parent == NULL, false, wxT("node already has a parent") );
227     wxCHECK_MSG( child->m_next == NULL, false, wxT("node already has m_next") );
228     wxCHECK_MSG( precedingNode == NULL || precedingNode->m_parent == this, false,
229                  wxT("precedingNode has wrong parent") );
230 
231     if ( precedingNode )
232     {
233         child->m_next = precedingNode->m_next;
234         precedingNode->m_next = child;
235     }
236     else // precedingNode == NULL
237     {
238         wxCHECK_MSG( m_children == NULL, false,
239                      wxT("NULL precedingNode only makes sense when there are no children") );
240 
241         child->m_next = m_children;
242         m_children = child;
243     }
244 
245     child->m_parent = this;
246     return true;
247 }
248 
249 
RemoveChild(wxXmlNode * child)250 bool wxXmlNode::RemoveChild(wxXmlNode *child)
251 {
252     if (m_children == NULL)
253         return false;
254     else if (m_children == child)
255     {
256         m_children = child->m_next;
257         child->m_parent = NULL;
258         child->m_next = NULL;
259         return true;
260     }
261     else
262     {
263         wxXmlNode *ch = m_children;
264         while (ch->m_next)
265         {
266             if (ch->m_next == child)
267             {
268                 ch->m_next = child->m_next;
269                 child->m_parent = NULL;
270                 child->m_next = NULL;
271                 return true;
272             }
273             ch = ch->m_next;
274         }
275         return false;
276     }
277 }
278 
AddProperty(const wxString & name,const wxString & value)279 void wxXmlNode::AddProperty(const wxString& name, const wxString& value)
280 {
281     AddProperty(new wxXmlProperty(name, value, NULL));
282 }
283 
AddProperty(wxXmlProperty * prop)284 void wxXmlNode::AddProperty(wxXmlProperty *prop)
285 {
286     if (m_properties == NULL)
287         m_properties = prop;
288     else
289     {
290         wxXmlProperty *p = m_properties;
291         while (p->GetNext()) p = p->GetNext();
292         p->SetNext(prop);
293     }
294 }
295 
DeleteProperty(const wxString & name)296 bool wxXmlNode::DeleteProperty(const wxString& name)
297 {
298     wxXmlProperty *prop;
299 
300     if (m_properties == NULL)
301         return false;
302 
303     else if (m_properties->GetName() == name)
304     {
305         prop = m_properties;
306         m_properties = prop->GetNext();
307         prop->SetNext(NULL);
308         delete prop;
309         return true;
310     }
311 
312     else
313     {
314         wxXmlProperty *p = m_properties;
315         while (p->GetNext())
316         {
317             if (p->GetNext()->GetName() == name)
318             {
319                 prop = p->GetNext();
320                 p->SetNext(prop->GetNext());
321                 prop->SetNext(NULL);
322                 delete prop;
323                 return true;
324             }
325             p = p->GetNext();
326         }
327         return false;
328     }
329 }
330 
GetNodeContent() const331 wxString wxXmlNode::GetNodeContent() const
332 {
333     wxXmlNode *n = GetChildren();
334 
335     while (n)
336     {
337         if (n->GetType() == wxXML_TEXT_NODE ||
338             n->GetType() == wxXML_CDATA_SECTION_NODE)
339             return n->GetContent();
340         n = n->GetNext();
341     }
342     return wxEmptyString;
343 }
344 
GetDepth(wxXmlNode * grandparent) const345 int wxXmlNode::GetDepth(wxXmlNode *grandparent) const
346 {
347     const wxXmlNode *n = this;
348     int ret = -1;
349 
350     do
351     {
352         ret++;
353         n = n->GetParent();
354         if (n == grandparent)
355             return ret;
356 
357     } while (n);
358 
359     return wxNOT_FOUND;
360 }
361 
IsWhitespaceOnly() const362 bool wxXmlNode::IsWhitespaceOnly() const
363 {
364     return wxIsWhiteOnly(m_content);
365 }
366 
367 
368 
369 //-----------------------------------------------------------------------------
370 //  wxXmlDocument
371 //-----------------------------------------------------------------------------
372 
wxXmlDocument()373 wxXmlDocument::wxXmlDocument()
374     : m_version(wxT("1.0")), m_fileEncoding(wxT("utf-8")), m_root(NULL)
375 {
376 #if !wxUSE_UNICODE
377     m_encoding = wxT("UTF-8");
378 #endif
379 }
380 
wxXmlDocument(const wxString & filename,const wxString & encoding)381 wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding)
382               :wxObject(), m_root(NULL)
383 {
384     if ( !Load(filename, encoding) )
385     {
386         wxDELETE(m_root);
387     }
388 }
389 
wxXmlDocument(wxInputStream & stream,const wxString & encoding)390 wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding)
391               :wxObject(), m_root(NULL)
392 {
393     if ( !Load(stream, encoding) )
394     {
395         wxDELETE(m_root);
396     }
397 }
398 
wxXmlDocument(const wxXmlDocument & doc)399 wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc)
400               :wxObject()
401 {
402     DoCopy(doc);
403 }
404 
operator =(const wxXmlDocument & doc)405 wxXmlDocument& wxXmlDocument::operator=(const wxXmlDocument& doc)
406 {
407     wxDELETE(m_root);
408     DoCopy(doc);
409     return *this;
410 }
411 
DoCopy(const wxXmlDocument & doc)412 void wxXmlDocument::DoCopy(const wxXmlDocument& doc)
413 {
414     m_version = doc.m_version;
415 #if !wxUSE_UNICODE
416     m_encoding = doc.m_encoding;
417 #endif
418     m_fileEncoding = doc.m_fileEncoding;
419 
420     if (doc.m_root)
421         m_root = new wxXmlNode(*doc.m_root);
422     else
423         m_root = NULL;
424 }
425 
Load(const wxString & filename,const wxString & encoding,int flags)426 bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding, int flags)
427 {
428     wxFileInputStream stream(filename);
429     if (!stream.Ok())
430         return false;
431     return Load(stream, encoding, flags);
432 }
433 
Save(const wxString & filename,int indentstep) const434 bool wxXmlDocument::Save(const wxString& filename, int indentstep) const
435 {
436     wxFileOutputStream stream(filename);
437     if (!stream.Ok())
438         return false;
439     return Save(stream, indentstep);
440 }
441 
442 
443 
444 //-----------------------------------------------------------------------------
445 //  wxXmlDocument loading routines
446 //-----------------------------------------------------------------------------
447 
448 // converts Expat-produced string in UTF-8 into wxString using the specified
449 // conv or keep in UTF-8 if conv is NULL
CharToString(wxMBConv * conv,const char * s,size_t len=wxString::npos)450 static wxString CharToString(wxMBConv *conv,
451                                     const char *s, size_t len = wxString::npos)
452 {
453 #if wxUSE_UNICODE
454     wxUnusedVar(conv);
455 
456     return wxString(s, wxConvUTF8, len);
457 #else // !wxUSE_UNICODE
458     if ( conv )
459     {
460         // there can be no embedded NULs in this string so we don't need the
461         // output length, it will be NUL-terminated
462         const wxWCharBuffer wbuf(
463             wxConvUTF8.cMB2WC(s, len == wxString::npos ? wxNO_LEN : len, NULL));
464 
465         return wxString(wbuf, *conv);
466     }
467     else // already in UTF-8, no conversion needed
468     {
469         return wxString(s, len != wxString::npos ? len : strlen(s));
470     }
471 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
472 }
473 
474 // returns true if the given string contains only whitespaces
wxIsWhiteOnly(const wxChar * buf)475 bool wxIsWhiteOnly(const wxChar *buf)
476 {
477     for (const wxChar *c = buf; *c != wxT('\0'); c++)
478         if (*c != wxT(' ') && *c != wxT('\t') && *c != wxT('\n') && *c != wxT('\r'))
479             return false;
480     return true;
481 }
482 
483 
484 struct wxXmlParsingContext
485 {
wxXmlParsingContextwxXmlParsingContext486     wxXmlParsingContext()
487         : conv(NULL),
488           root(NULL),
489           node(NULL),
490           lastChild(NULL),
491           lastAsText(NULL),
492           removeWhiteOnlyNodes(false)
493     {}
494 
495     wxMBConv  *conv;
496     wxXmlNode *root;
497     wxXmlNode *node;                    // the node being parsed
498     wxXmlNode *lastChild;               // the last child of "node"
499     wxXmlNode *lastAsText;              // the last _text_ child of "node"
500     wxString   encoding;
501     wxString   version;
502     bool       removeWhiteOnlyNodes;
503 };
504 
505 // checks that ctx->lastChild is in consistent state
506 #define ASSERT_LAST_CHILD_OK(ctx)                                   \
507     wxASSERT( ctx->lastChild == NULL ||                             \
508               ctx->lastChild->GetNext() == NULL );                  \
509     wxASSERT( ctx->lastChild == NULL ||                             \
510               ctx->lastChild->GetParent() == ctx->node )
511 
512 extern "C" {
StartElementHnd(void * userData,const char * name,const char ** atts)513 static void StartElementHnd(void *userData, const char *name, const char **atts)
514 {
515     wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
516     wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, CharToString(ctx->conv, name));
517     const char **a = atts;
518     while (*a)
519     {
520         node->AddProperty(CharToString(ctx->conv, a[0]), CharToString(ctx->conv, a[1]));
521         a += 2;
522     }
523     if (ctx->root == NULL)
524     {
525         ctx->root = node;
526     }
527     else
528     {
529         ASSERT_LAST_CHILD_OK(ctx);
530         ctx->node->InsertChildAfter(node, ctx->lastChild);
531     }
532 
533     ctx->lastAsText = NULL;
534     ctx->lastChild = NULL; // our new node "node" has no children yet
535 
536     ctx->node = node;
537 }
538 }
539 
540 extern "C" {
EndElementHnd(void * userData,const char * WXUNUSED (name))541 static void EndElementHnd(void *userData, const char* WXUNUSED(name))
542 {
543     wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
544 
545     // we're exiting the last children of ctx->node->GetParent() and going
546     // back one level up, so current value of ctx->node points to the last
547     // child of ctx->node->GetParent()
548     ctx->lastChild = ctx->node;
549 
550     ctx->node = ctx->node->GetParent();
551     ctx->lastAsText = NULL;
552 }
553 }
554 
555 extern "C" {
TextHnd(void * userData,const char * s,int len)556 static void TextHnd(void *userData, const char *s, int len)
557 {
558     wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
559     wxString str = CharToString(ctx->conv, s, len);
560 
561     if (ctx->lastAsText)
562     {
563         ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() + str);
564     }
565     else
566     {
567         bool whiteOnly = false;
568         if (ctx->removeWhiteOnlyNodes)
569             whiteOnly = wxIsWhiteOnly(str);
570 
571         if (!whiteOnly)
572         {
573             wxXmlNode *textnode =
574                 new wxXmlNode(wxXML_TEXT_NODE, wxT("text"), str);
575 
576             ASSERT_LAST_CHILD_OK(ctx);
577             ctx->node->InsertChildAfter(textnode, ctx->lastChild);
578             ctx->lastChild= ctx->lastAsText = textnode;
579         }
580     }
581 }
582 }
583 
584 extern "C" {
StartCdataHnd(void * userData)585 static void StartCdataHnd(void *userData)
586 {
587     wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
588 
589     wxXmlNode *textnode =
590         new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxT("cdata"),wxT(""));
591 
592     ASSERT_LAST_CHILD_OK(ctx);
593     ctx->node->InsertChildAfter(textnode, ctx->lastChild);
594     ctx->lastChild= ctx->lastAsText = textnode;
595 }
596 }
597 
598 extern "C" {
CommentHnd(void * userData,const char * data)599 static void CommentHnd(void *userData, const char *data)
600 {
601     wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
602 
603     if (ctx->node)
604     {
605         // VS: ctx->node == NULL happens if there is a comment before
606         //     the root element (e.g. wxDesigner's output). We ignore such
607         //     comments, no big deal...
608         wxXmlNode *commentnode =
609             new wxXmlNode(wxXML_COMMENT_NODE,
610                           wxT("comment"), CharToString(ctx->conv, data));
611         ASSERT_LAST_CHILD_OK(ctx);
612         ctx->node->InsertChildAfter(commentnode, ctx->lastChild);
613         ctx->lastChild = commentnode;
614     }
615     ctx->lastAsText = NULL;
616 }
617 }
618 
619 extern "C" {
DefaultHnd(void * userData,const char * s,int len)620 static void DefaultHnd(void *userData, const char *s, int len)
621 {
622     // XML header:
623     if (len > 6 && memcmp(s, "<?xml ", 6) == 0)
624     {
625         wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
626 
627         wxString buf = CharToString(ctx->conv, s, (size_t)len);
628         int pos;
629         pos = buf.Find(wxT("encoding="));
630         if (pos != wxNOT_FOUND)
631             ctx->encoding = buf.Mid(pos + 10).BeforeFirst(buf[(size_t)pos+9]);
632         pos = buf.Find(wxT("version="));
633         if (pos != wxNOT_FOUND)
634             ctx->version = buf.Mid(pos + 9).BeforeFirst(buf[(size_t)pos+8]);
635     }
636 }
637 }
638 
639 extern "C" {
UnknownEncodingHnd(void * WXUNUSED (encodingHandlerData),const XML_Char * name,XML_Encoding * info)640 static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData),
641                               const XML_Char *name, XML_Encoding *info)
642 {
643     // We must build conversion table for expat. The easiest way to do so
644     // is to let wxCSConv convert as string containing all characters to
645     // wide character representation:
646     wxString str(name, wxConvLibc);
647     wxCSConv conv(str);
648     char mbBuf[2];
649     wchar_t wcBuf[10];
650     size_t i;
651 
652     mbBuf[1] = 0;
653     info->map[0] = 0;
654     for (i = 0; i < 255; i++)
655     {
656         mbBuf[0] = (char)(i+1);
657         if (conv.MB2WC(wcBuf, mbBuf, 2) == (size_t)-1)
658         {
659             // invalid/undefined byte in the encoding:
660             info->map[i+1] = -1;
661         }
662         info->map[i+1] = (int)wcBuf[0];
663     }
664 
665     info->data = NULL;
666     info->convert = NULL;
667     info->release = NULL;
668 
669     return 1;
670 }
671 }
672 
Load(wxInputStream & stream,const wxString & encoding,int flags)673 bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int flags)
674 {
675 #if wxUSE_UNICODE
676     (void)encoding;
677 #else
678     m_encoding = encoding;
679 #endif
680 
681     const size_t BUFSIZE = 1024;
682     char buf[BUFSIZE];
683     wxXmlParsingContext ctx;
684     bool done;
685     XML_Parser parser = XML_ParserCreate(NULL);
686 
687     ctx.root = ctx.node = NULL;
688     ctx.encoding = wxT("UTF-8"); // default in absence of encoding=""
689     ctx.conv = NULL;
690 #if !wxUSE_UNICODE
691     if ( encoding.CmpNoCase(wxT("UTF-8")) != 0 )
692         ctx.conv = new wxCSConv(encoding);
693 #endif
694     ctx.removeWhiteOnlyNodes = (flags & wxXMLDOC_KEEP_WHITESPACE_NODES) == 0;
695 
696     XML_SetUserData(parser, (void*)&ctx);
697     XML_SetElementHandler(parser, StartElementHnd, EndElementHnd);
698     XML_SetCharacterDataHandler(parser, TextHnd);
699     XML_SetStartCdataSectionHandler(parser, StartCdataHnd);
700     XML_SetCommentHandler(parser, CommentHnd);
701     XML_SetDefaultHandler(parser, DefaultHnd);
702     XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL);
703 
704     bool ok = true;
705     do
706     {
707         size_t len = stream.Read(buf, BUFSIZE).LastRead();
708         done = (len < BUFSIZE);
709         if (!XML_Parse(parser, buf, len, done))
710         {
711             wxString error(XML_ErrorString(XML_GetErrorCode(parser)),
712                            *wxConvCurrent);
713             wxLogError(_("XML parsing error: '%s' at line %d"),
714                        error.c_str(),
715                        XML_GetCurrentLineNumber(parser));
716             ok = false;
717             break;
718         }
719     } while (!done);
720 
721     if (ok)
722     {
723         if (!ctx.version.empty())
724             SetVersion(ctx.version);
725         if (!ctx.encoding.empty())
726             SetFileEncoding(ctx.encoding);
727         SetRoot(ctx.root);
728     }
729     else
730     {
731         delete ctx.root;
732     }
733 
734     XML_ParserFree(parser);
735 #if !wxUSE_UNICODE
736     if ( ctx.conv )
737         delete ctx.conv;
738 #endif
739 
740     return ok;
741 
742 }
743 
744 
745 
746 //-----------------------------------------------------------------------------
747 //  wxXmlDocument saving routines
748 //-----------------------------------------------------------------------------
749 
750 // write string to output:
OutputString(wxOutputStream & stream,const wxString & str,wxMBConv * convMem=NULL,wxMBConv * convFile=NULL)751 inline static void OutputString(wxOutputStream& stream, const wxString& str,
752                                 wxMBConv *convMem = NULL,
753                                 wxMBConv *convFile = NULL)
754 {
755     if (str.empty())
756         return;
757 
758 #if wxUSE_UNICODE
759     wxUnusedVar(convMem);
760 
761     const wxWX2MBbuf buf(str.mb_str(*(convFile ? convFile : &wxConvUTF8)));
762     if ( !buf )
763         return;
764     stream.Write((const char*)buf, strlen((const char*)buf));
765 #else // !wxUSE_UNICODE
766     if ( convFile && convMem )
767     {
768         wxString str2(str.wc_str(*convMem), *convFile);
769         stream.Write(str2.mb_str(), str2.Len());
770     }
771     else // no conversions to do
772     {
773         stream.Write(str.mb_str(), str.Len());
774     }
775 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
776 }
777 
778 
779 enum EscapingMode
780 {
781     Escape_Text,
782     Escape_Attribute
783 };
784 
785 // Same as above, but create entities first.
786 // Translates '<' to "&lt;", '>' to "&gt;" and so on, according to the spec:
787 // http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping
OutputEscapedString(wxOutputStream & stream,const wxString & str,wxMBConv * convMem,wxMBConv * convFile,EscapingMode mode)788 static void OutputEscapedString(wxOutputStream& stream,
789                                 const wxString& str,
790                                 wxMBConv *convMem,
791                                 wxMBConv *convFile,
792                                 EscapingMode mode)
793 {
794     const size_t len = str.Len();
795 
796     wxString escaped;
797     escaped.reserve( len );
798 
799     for (size_t i = 0; i < len; i++)
800     {
801         const wxChar c = str.GetChar(i);
802 
803         switch ( c )
804         {
805             case '<':
806                 escaped.append(wxT("&lt;"));
807                 break;
808             case '>':
809                 escaped.append(wxT("&gt;"));
810                 break;
811             case '&':
812                 escaped.append(wxT("&amp;"));
813                 break;
814             case '\r':
815                 escaped.append(wxT("&#xD;"));
816                 break;
817             default:
818                 if ( mode == Escape_Attribute )
819                 {
820                     switch ( c )
821                     {
822                         case '"':
823                             escaped.append(wxT("&quot;"));
824                             break;
825                         case '\t':
826                             escaped.append(wxT("&#x9;"));
827                             break;
828                         case '\n':
829                             escaped.append(wxT("&#xA;"));
830                             break;
831                         default:
832                             escaped.append(c);
833                     }
834                 }
835                 else
836                 {
837                     escaped.append(c);
838                 }
839         }
840     }
841     OutputString(stream, escaped, convMem, convFile);
842 }
843 
OutputIndentation(wxOutputStream & stream,int indent)844 inline static void OutputIndentation(wxOutputStream& stream, int indent)
845 {
846     wxString str = wxT("\n");
847     for (int i = 0; i < indent; i++)
848         str << wxT(' ') << wxT(' ');
849     OutputString(stream, str);
850 }
851 
OutputNode(wxOutputStream & stream,wxXmlNode * node,int indent,wxMBConv * convMem,wxMBConv * convFile,int indentstep)852 static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent,
853                        wxMBConv *convMem, wxMBConv *convFile, int indentstep)
854 {
855     wxXmlNode *n, *prev;
856     wxXmlProperty *prop;
857 
858     switch (node->GetType())
859     {
860         case wxXML_CDATA_SECTION_NODE:
861             OutputString( stream, wxT("<![CDATA["));
862             OutputString( stream, node->GetContent() );
863             OutputString( stream, wxT("]]>") );
864             break;
865 
866         case wxXML_TEXT_NODE:
867             OutputEscapedString(stream, node->GetContent(),
868                                 convMem, convFile,
869                                 Escape_Text);
870             break;
871 
872         case wxXML_ELEMENT_NODE:
873             OutputString(stream, wxT("<"));
874             OutputString(stream, node->GetName());
875 
876             prop = node->GetProperties();
877             while (prop)
878             {
879                 OutputString(stream, wxT(" ") + prop->GetName() +  wxT("=\""));
880                 OutputEscapedString(stream, prop->GetValue(),
881                                     convMem, convFile,
882                                     Escape_Attribute);
883                 OutputString(stream, wxT("\""));
884                 prop = prop->GetNext();
885             }
886 
887             if (node->GetChildren())
888             {
889                 OutputString(stream, wxT(">"));
890                 prev = NULL;
891                 n = node->GetChildren();
892                 while (n)
893                 {
894                     if (indentstep >= 0 && n && n->GetType() != wxXML_TEXT_NODE)
895                         OutputIndentation(stream, indent + indentstep);
896                     OutputNode(stream, n, indent + indentstep, convMem, convFile, indentstep);
897                     prev = n;
898                     n = n->GetNext();
899                 }
900                 if (indentstep >= 0 && prev && prev->GetType() != wxXML_TEXT_NODE)
901                     OutputIndentation(stream, indent);
902                 OutputString(stream, wxT("</"));
903                 OutputString(stream, node->GetName());
904                 OutputString(stream, wxT(">"));
905             }
906             else
907                 OutputString(stream, wxT("/>"));
908             break;
909 
910         case wxXML_COMMENT_NODE:
911             OutputString(stream, wxT("<!--"));
912             OutputString(stream, node->GetContent(), convMem, convFile);
913             OutputString(stream, wxT("-->"));
914             break;
915 
916         default:
917             wxFAIL_MSG(wxT("unsupported node type"));
918     }
919 }
920 
Save(wxOutputStream & stream,int indentstep) const921 bool wxXmlDocument::Save(wxOutputStream& stream, int indentstep) const
922 {
923     if ( !IsOk() )
924         return false;
925 
926     wxString s;
927 
928     wxMBConv *convMem = NULL,
929              *convFile;
930 
931 #if wxUSE_UNICODE
932     convFile = new wxCSConv(GetFileEncoding());
933     convMem = NULL;
934 #else
935     if ( GetFileEncoding().CmpNoCase(GetEncoding()) != 0 )
936     {
937         convFile = new wxCSConv(GetFileEncoding());
938         convMem = new wxCSConv(GetEncoding());
939     }
940     else // file and in-memory encodings are the same, no conversion needed
941     {
942         convFile =
943         convMem = NULL;
944     }
945 #endif
946 
947     s.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
948              GetVersion().c_str(), GetFileEncoding().c_str());
949     OutputString(stream, s);
950 
951     OutputNode(stream, GetRoot(), 0, convMem, convFile, indentstep);
952     OutputString(stream, wxT("\n"));
953 
954     delete convFile;
955     delete convMem;
956 
957     return true;
958 }
959 
960 #endif // wxUSE_XML
961