Apply patch [ 1554746 ] wxXmlNode::InsertChild fix
[wxWidgets.git] / src / xml / xml.cpp
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$
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
43 //-----------------------------------------------------------------------------
44 // wxXmlNode
45 //-----------------------------------------------------------------------------
46
47 wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type,
48 const wxString& name, const wxString& content,
49 wxXmlProperty *props, wxXmlNode *next)
50 : m_type(type), m_name(name), m_content(content),
51 m_properties(props), m_parent(parent),
52 m_children(NULL), m_next(next)
53 {
54 if (m_parent)
55 {
56 if (m_parent->m_children)
57 {
58 m_next = m_parent->m_children;
59 m_parent->m_children = this;
60 }
61 else
62 m_parent->m_children = this;
63 }
64 }
65
66 wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name,
67 const wxString& content)
68 : m_type(type), m_name(name), m_content(content),
69 m_properties(NULL), m_parent(NULL),
70 m_children(NULL), m_next(NULL)
71 {}
72
73 wxXmlNode::wxXmlNode(const wxXmlNode& node)
74 {
75 m_next = NULL;
76 m_parent = NULL;
77 DoCopy(node);
78 }
79
80 wxXmlNode::~wxXmlNode()
81 {
82 wxXmlNode *c, *c2;
83 for (c = m_children; c; c = c2)
84 {
85 c2 = c->m_next;
86 delete c;
87 }
88
89 wxXmlProperty *p, *p2;
90 for (p = m_properties; p; p = p2)
91 {
92 p2 = p->GetNext();
93 delete p;
94 }
95 }
96
97 wxXmlNode& wxXmlNode::operator=(const wxXmlNode& node)
98 {
99 wxDELETE(m_properties);
100 wxDELETE(m_children);
101 DoCopy(node);
102 return *this;
103 }
104
105 void wxXmlNode::DoCopy(const wxXmlNode& node)
106 {
107 m_type = node.m_type;
108 m_name = node.m_name;
109 m_content = node.m_content;
110 m_children = NULL;
111
112 wxXmlNode *n = node.m_children;
113 while (n)
114 {
115 AddChild(new wxXmlNode(*n));
116 n = n->GetNext();
117 }
118
119 m_properties = NULL;
120 wxXmlProperty *p = node.m_properties;
121 while (p)
122 {
123 AddProperty(p->GetName(), p->GetValue());
124 p = p->GetNext();
125 }
126 }
127
128 bool wxXmlNode::HasProp(const wxString& propName) const
129 {
130 wxXmlProperty *prop = GetProperties();
131
132 while (prop)
133 {
134 if (prop->GetName() == propName) return true;
135 prop = prop->GetNext();
136 }
137
138 return false;
139 }
140
141 bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const
142 {
143 wxXmlProperty *prop = GetProperties();
144
145 while (prop)
146 {
147 if (prop->GetName() == propName)
148 {
149 *value = prop->GetValue();
150 return true;
151 }
152 prop = prop->GetNext();
153 }
154
155 return false;
156 }
157
158 wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& defaultVal) const
159 {
160 wxString tmp;
161 if (GetPropVal(propName, &tmp))
162 return tmp;
163
164 return defaultVal;
165 }
166
167 void wxXmlNode::AddChild(wxXmlNode *child)
168 {
169 if (m_children == NULL)
170 m_children = child;
171 else
172 {
173 wxXmlNode *ch = m_children;
174 while (ch->m_next) ch = ch->m_next;
175 ch->m_next = child;
176 }
177 child->m_next = NULL;
178 child->m_parent = this;
179 }
180
181 bool wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node)
182 {
183 wxCHECK_MSG(before_node == NULL || before_node->GetParent() == this, false,
184 wxT("wxXmlNode::InsertChild - the node has incorrect parent"));
185 wxCHECK_MSG(child, false, wxT("Cannot insert a NULL pointer!"));
186
187 if (m_children == before_node)
188 m_children = child;
189 else if (m_children == NULL)
190 {
191 if (before_node != NULL)
192 return false; // we have no children so we don't need to search
193 m_children = child;
194 }
195 else if (before_node == NULL)
196 {
197 // prepend child
198 child->m_parent = this;
199 child->m_next = m_children;
200 m_children = child;
201 return true;
202 }
203 else
204 {
205 wxXmlNode *ch = m_children;
206 while (ch && ch->m_next != before_node) ch = ch->m_next;
207 if (!ch)
208 return false; // before_node not found
209 ch->m_next = child;
210 }
211
212 child->m_parent = this;
213 child->m_next = before_node;
214 return true;
215 }
216
217 bool wxXmlNode::RemoveChild(wxXmlNode *child)
218 {
219 if (m_children == NULL)
220 return false;
221 else if (m_children == child)
222 {
223 m_children = child->m_next;
224 child->m_parent = NULL;
225 child->m_next = NULL;
226 return true;
227 }
228 else
229 {
230 wxXmlNode *ch = m_children;
231 while (ch->m_next)
232 {
233 if (ch->m_next == child)
234 {
235 ch->m_next = child->m_next;
236 child->m_parent = NULL;
237 child->m_next = NULL;
238 return true;
239 }
240 ch = ch->m_next;
241 }
242 return false;
243 }
244 }
245
246 void wxXmlNode::AddProperty(const wxString& name, const wxString& value)
247 {
248 AddProperty(new wxXmlProperty(name, value, NULL));
249 }
250
251 void wxXmlNode::AddProperty(wxXmlProperty *prop)
252 {
253 if (m_properties == NULL)
254 m_properties = prop;
255 else
256 {
257 wxXmlProperty *p = m_properties;
258 while (p->GetNext()) p = p->GetNext();
259 p->SetNext(prop);
260 }
261 }
262
263 bool wxXmlNode::DeleteProperty(const wxString& name)
264 {
265 wxXmlProperty *prop;
266
267 if (m_properties == NULL)
268 return false;
269
270 else if (m_properties->GetName() == name)
271 {
272 prop = m_properties;
273 m_properties = prop->GetNext();
274 prop->SetNext(NULL);
275 delete prop;
276 return true;
277 }
278
279 else
280 {
281 wxXmlProperty *p = m_properties;
282 while (p->GetNext())
283 {
284 if (p->GetNext()->GetName() == name)
285 {
286 prop = p->GetNext();
287 p->SetNext(prop->GetNext());
288 prop->SetNext(NULL);
289 delete prop;
290 return true;
291 }
292 p = p->GetNext();
293 }
294 return false;
295 }
296 }
297
298 wxString wxXmlNode::GetNodeContent() const
299 {
300 wxXmlNode *n = GetChildren();
301
302 while (n)
303 {
304 if (n->GetType() == wxXML_TEXT_NODE ||
305 n->GetType() == wxXML_CDATA_SECTION_NODE)
306 return n->GetContent();
307 n = n->GetNext();
308 }
309 return wxEmptyString;
310 }
311
312
313
314 //-----------------------------------------------------------------------------
315 // wxXmlDocument
316 //-----------------------------------------------------------------------------
317
318 wxXmlDocument::wxXmlDocument()
319 : m_version(wxT("1.0")), m_fileEncoding(wxT("utf-8")), m_root(NULL)
320 {
321 #if !wxUSE_UNICODE
322 m_encoding = wxT("UTF-8");
323 #endif
324 }
325
326 wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding)
327 :wxObject(), m_root(NULL)
328 {
329 if ( !Load(filename, encoding) )
330 {
331 wxDELETE(m_root);
332 }
333 }
334
335 wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding)
336 :wxObject(), m_root(NULL)
337 {
338 if ( !Load(stream, encoding) )
339 {
340 wxDELETE(m_root);
341 }
342 }
343
344 wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc)
345 :wxObject()
346 {
347 DoCopy(doc);
348 }
349
350 wxXmlDocument& wxXmlDocument::operator=(const wxXmlDocument& doc)
351 {
352 wxDELETE(m_root);
353 DoCopy(doc);
354 return *this;
355 }
356
357 void wxXmlDocument::DoCopy(const wxXmlDocument& doc)
358 {
359 m_version = doc.m_version;
360 #if !wxUSE_UNICODE
361 m_encoding = doc.m_encoding;
362 #endif
363 m_fileEncoding = doc.m_fileEncoding;
364 m_root = new wxXmlNode(*doc.m_root);
365 }
366
367 bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding)
368 {
369 wxFileInputStream stream(filename);
370 if (!stream.Ok())
371 return false;
372 return Load(stream, encoding);
373 }
374
375 bool wxXmlDocument::Save(const wxString& filename) const
376 {
377 wxFileOutputStream stream(filename);
378 if (!stream.Ok())
379 return false;
380 return Save(stream);
381 }
382
383
384
385 //-----------------------------------------------------------------------------
386 // wxXmlDocument loading routines
387 //-----------------------------------------------------------------------------
388
389 /*
390 FIXME:
391 - process all elements, including CDATA
392 */
393
394 // converts Expat-produced string in UTF-8 into wxString using the specified
395 // conv or keep in UTF-8 if conv is NULL
396 static wxString CharToString(wxMBConv *conv,
397 const char *s, size_t len = wxSTRING_MAXLEN)
398 {
399 #if wxUSE_UNICODE
400 wxUnusedVar(conv);
401
402 return wxString(s, wxConvUTF8, len);
403 #else // !wxUSE_UNICODE
404 if ( conv )
405 {
406 // there can be no embedded NULs in this string so we don't need the
407 // output length, it will be NUL-terminated
408 const wxWCharBuffer wbuf(
409 wxConvUTF8.cMB2WC(s, len == wxSTRING_MAXLEN ? wxNO_LEN : len, NULL));
410
411 return wxString(wbuf, *conv);
412 }
413 else // already in UTF-8, no conversion needed
414 {
415 return wxString(s, len != wxSTRING_MAXLEN ? len : strlen(s));
416 }
417 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
418 }
419
420 struct wxXmlParsingContext
421 {
422 wxMBConv *conv;
423 wxXmlNode *root;
424 wxXmlNode *node;
425 wxXmlNode *lastAsText;
426 wxString encoding;
427 wxString version;
428 bool bLastCdata;
429 };
430
431 extern "C" {
432 static void StartElementHnd(void *userData, const char *name, const char **atts)
433 {
434 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
435 wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, CharToString(ctx->conv, name));
436 const char **a = atts;
437 while (*a)
438 {
439 node->AddProperty(CharToString(ctx->conv, a[0]), CharToString(ctx->conv, a[1]));
440 a += 2;
441 }
442 if (ctx->root == NULL)
443 ctx->root = node;
444 else
445 ctx->node->AddChild(node);
446 ctx->node = node;
447 ctx->lastAsText = NULL;
448 }
449 }
450
451 extern "C" {
452 static void EndElementHnd(void *userData, const char* WXUNUSED(name))
453 {
454 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
455
456 ctx->node = ctx->node->GetParent();
457 ctx->lastAsText = NULL;
458 }
459 }
460
461 extern "C" {
462 static void TextHnd(void *userData, const char *s, int len)
463 {
464 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
465 char *buf = new char[len + 1];
466
467 buf[len] = '\0';
468 memcpy(buf, s, (size_t)len);
469
470 if (ctx->lastAsText)
471 {
472 if ( ctx->bLastCdata )
473 {
474 ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() +
475 CharToString(NULL, buf));
476 }
477 else
478 {
479 ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() +
480 CharToString(ctx->conv, buf));
481 }
482 }
483 else
484 {
485 bool whiteOnly = true;
486 for (char *c = buf; *c != '\0'; c++)
487 if (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\r')
488 {
489 whiteOnly = false;
490 break;
491 }
492 if (!whiteOnly)
493 {
494 ctx->lastAsText = new wxXmlNode(wxXML_TEXT_NODE, wxT("text"),
495 CharToString(ctx->conv, buf));
496 ctx->node->AddChild(ctx->lastAsText);
497 }
498 }
499
500 delete[] buf;
501 }
502 }
503
504 extern "C" {
505 static void StartCdataHnd(void *userData)
506 {
507 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
508
509 ctx->bLastCdata = true;
510
511 ctx->lastAsText = new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxT("cdata"),wxT(""));
512 ctx->node->AddChild(ctx->lastAsText);
513 }
514 }
515
516 extern "C" {
517 static void EndCdataHnd(void *userData)
518 {
519 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
520
521 ctx->bLastCdata = false;
522 }
523 }
524
525 extern "C" {
526 static void CommentHnd(void *userData, const char *data)
527 {
528 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
529
530 if (ctx->node)
531 {
532 // VS: ctx->node == NULL happens if there is a comment before
533 // the root element (e.g. wxDesigner's output). We ignore such
534 // comments, no big deal...
535 ctx->node->AddChild(new wxXmlNode(wxXML_COMMENT_NODE,
536 wxT("comment"), CharToString(ctx->conv, data)));
537 }
538 ctx->lastAsText = NULL;
539 }
540 }
541
542 extern "C" {
543 static void DefaultHnd(void *userData, const char *s, int len)
544 {
545 // XML header:
546 if (len > 6 && memcmp(s, "<?xml ", 6) == 0)
547 {
548 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
549
550 wxString buf = CharToString(ctx->conv, s, (size_t)len);
551 int pos;
552 pos = buf.Find(wxT("encoding="));
553 if (pos != wxNOT_FOUND)
554 ctx->encoding = buf.Mid(pos + 10).BeforeFirst(buf[(size_t)pos+9]);
555 pos = buf.Find(wxT("version="));
556 if (pos != wxNOT_FOUND)
557 ctx->version = buf.Mid(pos + 9).BeforeFirst(buf[(size_t)pos+8]);
558 }
559 }
560 }
561
562 extern "C" {
563 static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData),
564 const XML_Char *name, XML_Encoding *info)
565 {
566 // We must build conversion table for expat. The easiest way to do so
567 // is to let wxCSConv convert as string containing all characters to
568 // wide character representation:
569 wxString str(name, wxConvLibc);
570 wxCSConv conv(str);
571 char mbBuf[2];
572 wchar_t wcBuf[10];
573 size_t i;
574
575 mbBuf[1] = 0;
576 info->map[0] = 0;
577 for (i = 0; i < 255; i++)
578 {
579 mbBuf[0] = (char)(i+1);
580 if (conv.MB2WC(wcBuf, mbBuf, 2) == (size_t)-1)
581 {
582 // invalid/undefined byte in the encoding:
583 info->map[i+1] = -1;
584 }
585 info->map[i+1] = (int)wcBuf[0];
586 }
587
588 info->data = NULL;
589 info->convert = NULL;
590 info->release = NULL;
591
592 return 1;
593 }
594 }
595
596 bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding)
597 {
598 #if wxUSE_UNICODE
599 (void)encoding;
600 #else
601 m_encoding = encoding;
602 #endif
603
604 const size_t BUFSIZE = 1024;
605 char buf[BUFSIZE];
606 wxXmlParsingContext ctx;
607 bool done;
608 XML_Parser parser = XML_ParserCreate(NULL);
609
610 ctx.root = ctx.node = NULL;
611 ctx.encoding = wxT("UTF-8"); // default in absence of encoding=""
612 ctx.conv = NULL;
613 #if !wxUSE_UNICODE
614 if ( encoding != wxT("UTF-8") && encoding != wxT("utf-8") )
615 ctx.conv = new wxCSConv(encoding);
616 #endif
617 ctx.bLastCdata = false;
618
619 XML_SetUserData(parser, (void*)&ctx);
620 XML_SetElementHandler(parser, StartElementHnd, EndElementHnd);
621 XML_SetCharacterDataHandler(parser, TextHnd);
622 XML_SetCdataSectionHandler(parser, StartCdataHnd, EndCdataHnd );
623 XML_SetCommentHandler(parser, CommentHnd);
624 XML_SetDefaultHandler(parser, DefaultHnd);
625 XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL);
626
627 bool ok = true;
628 do
629 {
630 size_t len = stream.Read(buf, BUFSIZE).LastRead();
631 done = (len < BUFSIZE);
632 if (!XML_Parse(parser, buf, len, done))
633 {
634 wxString error(XML_ErrorString(XML_GetErrorCode(parser)),
635 *wxConvCurrent);
636 wxLogError(_("XML parsing error: '%s' at line %d"),
637 error.c_str(),
638 XML_GetCurrentLineNumber(parser));
639 ok = false;
640 break;
641 }
642 } while (!done);
643
644 if (ok)
645 {
646 if (!ctx.version.empty())
647 SetVersion(ctx.version);
648 if (!ctx.encoding.empty())
649 SetFileEncoding(ctx.encoding);
650 SetRoot(ctx.root);
651 }
652 else
653 {
654 delete ctx.root;
655 }
656
657 XML_ParserFree(parser);
658 #if !wxUSE_UNICODE
659 if ( ctx.conv )
660 delete ctx.conv;
661 #endif
662
663 return ok;
664
665 }
666
667
668
669 //-----------------------------------------------------------------------------
670 // wxXmlDocument saving routines
671 //-----------------------------------------------------------------------------
672
673 // write string to output:
674 inline static void OutputString(wxOutputStream& stream, const wxString& str,
675 wxMBConv *convMem = NULL,
676 wxMBConv *convFile = NULL)
677 {
678 if (str.empty())
679 return;
680
681 #if wxUSE_UNICODE
682 wxUnusedVar(convMem);
683
684 const wxWX2MBbuf buf(str.mb_str(*(convFile ? convFile : &wxConvUTF8)));
685 stream.Write((const char*)buf, strlen((const char*)buf));
686 #else // !wxUSE_UNICODE
687 if ( convFile && convMem )
688 {
689 wxString str2(str.wc_str(*convMem), *convFile);
690 stream.Write(str2.mb_str(), str2.Len());
691 }
692 else // no conversions to do
693 {
694 stream.Write(str.mb_str(), str.Len());
695 }
696 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
697 }
698
699 // flags for OutputStringEnt()
700 enum
701 {
702 XML_ESCAPE_QUOTES = 1
703 };
704
705 // Same as above, but create entities first.
706 // Translates '<' to "&lt;", '>' to "&gt;" and '&' to "&amp;"
707 static void OutputStringEnt(wxOutputStream& stream, const wxString& str,
708 wxMBConv *convMem = NULL,
709 wxMBConv *convFile = NULL,
710 int flags = 0)
711 {
712 wxString buf;
713 size_t i, last, len;
714 wxChar c;
715
716 len = str.Len();
717 last = 0;
718 for (i = 0; i < len; i++)
719 {
720 c = str.GetChar(i);
721 if (c == wxT('<') || c == wxT('>') ||
722 (c == wxT('&') && str.Mid(i+1, 4) != wxT("amp;")) ||
723 ((flags & XML_ESCAPE_QUOTES) && c == wxT('"')))
724 {
725 OutputString(stream, str.Mid(last, i - last), convMem, convFile);
726 switch (c)
727 {
728 case wxT('<'):
729 OutputString(stream, wxT("&lt;"));
730 break;
731 case wxT('>'):
732 OutputString(stream, wxT("&gt;"));
733 break;
734 case wxT('&'):
735 OutputString(stream, wxT("&amp;"));
736 break;
737 case wxT('"'):
738 OutputString(stream, wxT("&quot;"));
739 break;
740 default:
741 break;
742 }
743 last = i + 1;
744 }
745 }
746 OutputString(stream, str.Mid(last, i - last), convMem, convFile);
747 }
748
749 inline static void OutputIndentation(wxOutputStream& stream, int indent)
750 {
751 wxString str = wxT("\n");
752 for (int i = 0; i < indent; i++)
753 str << wxT(' ') << wxT(' ');
754 OutputString(stream, str);
755 }
756
757 static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent,
758 wxMBConv *convMem, wxMBConv *convFile)
759 {
760 wxXmlNode *n, *prev;
761 wxXmlProperty *prop;
762
763 switch (node->GetType())
764 {
765 case wxXML_CDATA_SECTION_NODE:
766 OutputString( stream, wxT("<![CDATA["));
767 OutputString( stream, node->GetContent() );
768 OutputString( stream, wxT("]]>") );
769 break;
770
771 case wxXML_TEXT_NODE:
772 OutputStringEnt(stream, node->GetContent(), convMem, convFile);
773 break;
774
775 case wxXML_ELEMENT_NODE:
776 OutputString(stream, wxT("<"));
777 OutputString(stream, node->GetName());
778
779 prop = node->GetProperties();
780 while (prop)
781 {
782 OutputString(stream, wxT(" ") + prop->GetName() + wxT("=\""));
783 OutputStringEnt(stream, prop->GetValue(), convMem, convFile,
784 XML_ESCAPE_QUOTES);
785 OutputString(stream, wxT("\""));
786 prop = prop->GetNext();
787 }
788
789 if (node->GetChildren())
790 {
791 OutputString(stream, wxT(">"));
792 prev = NULL;
793 n = node->GetChildren();
794 while (n)
795 {
796 if (n && n->GetType() != wxXML_TEXT_NODE)
797 OutputIndentation(stream, indent + 1);
798 OutputNode(stream, n, indent + 1, convMem, convFile);
799 prev = n;
800 n = n->GetNext();
801 }
802 if (prev && prev->GetType() != wxXML_TEXT_NODE)
803 OutputIndentation(stream, indent);
804 OutputString(stream, wxT("</"));
805 OutputString(stream, node->GetName());
806 OutputString(stream, wxT(">"));
807 }
808 else
809 OutputString(stream, wxT("/>"));
810 break;
811
812 case wxXML_COMMENT_NODE:
813 OutputString(stream, wxT("<!--"));
814 OutputString(stream, node->GetContent(), convMem, convFile);
815 OutputString(stream, wxT("-->"));
816 break;
817
818 default:
819 wxFAIL_MSG(wxT("unsupported node type"));
820 }
821 }
822
823 bool wxXmlDocument::Save(wxOutputStream& stream) const
824 {
825 if ( !IsOk() )
826 return false;
827
828 wxString s;
829
830 wxMBConv *convMem = NULL;
831
832 #if wxUSE_UNICODE
833 wxMBConv *convFile = new wxCSConv(GetFileEncoding());
834 #else
835 wxMBConv *convFile = NULL;
836 if ( GetFileEncoding() != GetEncoding() )
837 {
838 convFile = new wxCSConv(GetFileEncoding());
839 convMem = new wxCSConv(GetEncoding());
840 }
841 #endif
842
843 s.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
844 GetVersion().c_str(), GetFileEncoding().c_str());
845 OutputString(stream, s);
846
847 OutputNode(stream, GetRoot(), 0, convMem, convFile);
848 OutputString(stream, wxT("\n"));
849
850 if ( convFile )
851 delete convFile;
852 if ( convMem )
853 delete convMem;
854
855 return true;
856 }
857
858 #endif // wxUSE_XML