]> git.saurik.com Git - wxWidgets.git/blob - src/richtext/richtextxml.cpp
don't reset the selection after event was vetoed if there is no old selection
[wxWidgets.git] / src / richtext / richtextxml.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/richtext/richtextxml.cpp
3 // Purpose: XML and HTML I/O for wxRichTextCtrl
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 2005-09-30
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
14
15 #ifdef __BORLANDC__
16 #pragma hdrstop
17 #endif
18
19 #if wxUSE_RICHTEXT && wxUSE_XML
20
21 #include "wx/richtext/richtextxml.h"
22
23 #ifndef WX_PRECOMP
24 #include "wx/intl.h"
25 #endif
26
27 #include "wx/filename.h"
28 #include "wx/clipbrd.h"
29 #include "wx/wfstream.h"
30 #include "wx/sstream.h"
31 #include "wx/module.h"
32 #include "wx/txtstrm.h"
33 #include "wx/xml/xml.h"
34
35 IMPLEMENT_DYNAMIC_CLASS(wxRichTextXMLHandler, wxRichTextFileHandler)
36
37 #if wxUSE_STREAMS
38 bool wxRichTextXMLHandler::DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream)
39 {
40 if (!stream.IsOk())
41 return false;
42
43 buffer->Clear();
44
45 wxXmlDocument* xmlDoc = new wxXmlDocument;
46 bool success = true;
47
48 // This is the encoding to convert to (memory encoding rather than file encoding)
49 wxString encoding(wxT("UTF-8"));
50
51 #if !wxUSE_UNICODE && wxUSE_INTL
52 encoding = wxLocale::GetSystemEncodingName();
53 #endif
54
55 if (!xmlDoc->Load(stream, encoding))
56 {
57 success = false;
58 }
59 else
60 {
61 if (xmlDoc->GetRoot() && xmlDoc->GetRoot()->GetType() == wxXML_ELEMENT_NODE && xmlDoc->GetRoot()->GetName() == wxT("richtext"))
62 {
63 wxXmlNode* child = xmlDoc->GetRoot()->GetChildren();
64 while (child)
65 {
66 if (child->GetType() == wxXML_ELEMENT_NODE)
67 {
68 wxString name = child->GetName();
69 if (name == wxT("richtext-version"))
70 {
71 }
72 else
73 ImportXML(buffer, child);
74 }
75
76 child = child->GetNext();
77 }
78 }
79 else
80 {
81 success = false;
82 }
83 }
84
85 delete xmlDoc;
86
87 buffer->UpdateRanges();
88
89 return success;
90 }
91
92 /// Recursively import an object
93 bool wxRichTextXMLHandler::ImportXML(wxRichTextBuffer* buffer, wxXmlNode* node)
94 {
95 wxString name = node->GetName();
96
97 bool doneChildren = false;
98
99 if (name == wxT("paragraphlayout"))
100 {
101 }
102 else if (name == wxT("paragraph"))
103 {
104 wxRichTextParagraph* para = new wxRichTextParagraph(buffer);
105 buffer->AppendChild(para);
106
107 GetStyle(para->GetAttributes(), node, true);
108
109 wxXmlNode* child = node->GetChildren();
110 while (child)
111 {
112 wxString childName = child->GetName();
113 if (childName == wxT("text"))
114 {
115 wxString text;
116 wxXmlNode* textChild = child->GetChildren();
117 while (textChild)
118 {
119 if (textChild->GetType() == wxXML_TEXT_NODE ||
120 textChild->GetType() == wxXML_CDATA_SECTION_NODE)
121 {
122 wxString text2 = textChild->GetContent();
123
124 // Strip whitespace from end
125 if (!text2.empty() && text2[text2.length()-1] == wxT('\n'))
126 text2 = text2.Mid(0, text2.length()-1);
127
128 if (!text2.empty() && text2[0] == wxT('"'))
129 text2 = text2.Mid(1);
130 if (!text2.empty() && text2[text2.length()-1] == wxT('"'))
131 text2 = text2.Mid(0, text2.length() - 1);
132
133 text += text2;
134 }
135 textChild = textChild->GetNext();
136 }
137
138 wxRichTextPlainText* textObject = new wxRichTextPlainText(text, para);
139 GetStyle(textObject->GetAttributes(), child, false);
140
141 para->AppendChild(textObject);
142 }
143 else if (childName == wxT("image"))
144 {
145 int imageType = wxBITMAP_TYPE_PNG;
146 wxString value = node->GetPropVal(wxT("imagetype"), wxEmptyString);
147 if (!value.empty())
148 imageType = wxAtoi(value);
149
150 wxString data;
151
152 wxXmlNode* imageChild = child->GetChildren();
153 while (imageChild)
154 {
155 wxString childName = imageChild->GetName();
156 if (childName == wxT("data"))
157 {
158 wxXmlNode* dataChild = imageChild->GetChildren();
159 while (dataChild)
160 {
161 data = dataChild->GetContent();
162 // wxLogDebug(data);
163 dataChild = dataChild->GetNext();
164 }
165
166 }
167 imageChild = imageChild->GetNext();
168 }
169
170 if (!data.empty())
171 {
172 wxRichTextImage* imageObj = new wxRichTextImage(para);
173 para->AppendChild(imageObj);
174
175 wxStringInputStream strStream(data);
176
177 imageObj->GetImageBlock().ReadHex(strStream, data.length(), imageType);
178 }
179 }
180 child = child->GetNext();
181 }
182
183 doneChildren = true;
184 }
185
186 if (!doneChildren)
187 {
188 wxXmlNode* child = node->GetChildren();
189 while (child)
190 {
191 ImportXML(buffer, child);
192 child = child->GetNext();
193 }
194 }
195
196 return true;
197 }
198
199
200 //-----------------------------------------------------------------------------
201 // xml support routines
202 //-----------------------------------------------------------------------------
203
204 bool wxRichTextXMLHandler::HasParam(wxXmlNode* node, const wxString& param)
205 {
206 return (GetParamNode(node, param) != NULL);
207 }
208
209 wxXmlNode *wxRichTextXMLHandler::GetParamNode(wxXmlNode* node, const wxString& param)
210 {
211 wxCHECK_MSG(node, NULL, wxT("You can't access node data before it was initialized!"));
212
213 wxXmlNode *n = node->GetChildren();
214
215 while (n)
216 {
217 if (n->GetType() == wxXML_ELEMENT_NODE && n->GetName() == param)
218 return n;
219 n = n->GetNext();
220 }
221 return NULL;
222 }
223
224
225 wxString wxRichTextXMLHandler::GetNodeContent(wxXmlNode *node)
226 {
227 wxXmlNode *n = node;
228 if (n == NULL) return wxEmptyString;
229 n = n->GetChildren();
230
231 while (n)
232 {
233 if (n->GetType() == wxXML_TEXT_NODE ||
234 n->GetType() == wxXML_CDATA_SECTION_NODE)
235 return n->GetContent();
236 n = n->GetNext();
237 }
238 return wxEmptyString;
239 }
240
241
242 wxString wxRichTextXMLHandler::GetParamValue(wxXmlNode *node, const wxString& param)
243 {
244 if (param.empty())
245 return GetNodeContent(node);
246 else
247 return GetNodeContent(GetParamNode(node, param));
248 }
249
250 wxString wxRichTextXMLHandler::GetText(wxXmlNode *node, const wxString& param, bool WXUNUSED(translate))
251 {
252 wxXmlNode *parNode = GetParamNode(node, param);
253 if (!parNode)
254 parNode = node;
255 wxString str1(GetNodeContent(parNode));
256 return str1;
257 }
258
259 // For use with earlier versions of wxWidgets
260 #ifndef WXUNUSED_IN_UNICODE
261 #if wxUSE_UNICODE
262 #define WXUNUSED_IN_UNICODE(x) WXUNUSED(x)
263 #else
264 #define WXUNUSED_IN_UNICODE(x) x
265 #endif
266 #endif
267
268 // write string to output:
269 inline static void OutputString(wxOutputStream& stream, const wxString& str,
270 wxMBConv *WXUNUSED_IN_UNICODE(convMem) = NULL, wxMBConv *convFile = NULL)
271 {
272 if (str.empty()) return;
273 #if wxUSE_UNICODE
274 if (convFile)
275 {
276 const wxWX2MBbuf buf(str.mb_str(*convFile));
277 stream.Write((const char*)buf, strlen((const char*)buf));
278 }
279 else
280 {
281 const wxWX2MBbuf buf(str.mb_str(wxConvUTF8));
282 stream.Write((const char*)buf, strlen((const char*)buf));
283 }
284 #else
285 if ( convFile == NULL )
286 stream.Write(str.mb_str(), str.Len());
287 else
288 {
289 wxString str2(str.wc_str(*convMem), *convFile);
290 stream.Write(str2.mb_str(), str2.Len());
291 }
292 #endif
293 }
294
295 // Same as above, but create entities first.
296 // Translates '<' to "&lt;", '>' to "&gt;" and '&' to "&amp;"
297 static void OutputStringEnt(wxOutputStream& stream, const wxString& str,
298 wxMBConv *convMem = NULL, wxMBConv *convFile = NULL)
299 {
300 wxString buf;
301 size_t i, last, len;
302 wxChar c;
303
304 len = str.Len();
305 last = 0;
306 for (i = 0; i < len; i++)
307 {
308 c = str.GetChar(i);
309
310 // Original code excluded "&amp;" but we _do_ want to convert
311 // the ampersand beginning &amp; because otherwise when read in,
312 // the original "&amp;" becomes "&".
313
314 if (c == wxT('<') || c == wxT('>') || c == wxT('"') ||
315 (c == wxT('&') /* && (str.Mid(i+1, 4) != wxT("amp;")) */ ))
316 {
317 OutputString(stream, str.Mid(last, i - last), convMem, convFile);
318 switch (c)
319 {
320 case wxT('<'):
321 OutputString(stream, wxT("&lt;"), NULL, NULL);
322 break;
323 case wxT('>'):
324 OutputString(stream, wxT("&gt;"), NULL, NULL);
325 break;
326 case wxT('&'):
327 OutputString(stream, wxT("&amp;"), NULL, NULL);
328 break;
329 case wxT('"'):
330 OutputString(stream, wxT("&quot;"), NULL, NULL);
331 break;
332 default: break;
333 }
334 last = i + 1;
335 }
336 }
337 OutputString(stream, str.Mid(last, i - last), convMem, convFile);
338 }
339
340 inline static void OutputIndentation(wxOutputStream& stream, int indent)
341 {
342 wxString str = wxT("\n");
343 for (int i = 0; i < indent; i++)
344 str << wxT(' ') << wxT(' ');
345 OutputString(stream, str, NULL, NULL);
346 }
347
348 // Convert a colour to a 6-digit hex string
349 static wxString ColourToHexString(const wxColour& col)
350 {
351 wxString hex;
352
353 hex += wxDecToHex(col.Red());
354 hex += wxDecToHex(col.Green());
355 hex += wxDecToHex(col.Blue());
356
357 return hex;
358 }
359
360 // Convert 6-digit hex string to a colour
361 static wxColour HexStringToColour(const wxString& hex)
362 {
363 unsigned char r = (unsigned char)wxHexToDec(hex.Mid(0, 2));
364 unsigned char g = (unsigned char)wxHexToDec(hex.Mid(2, 2));
365 unsigned char b = (unsigned char)wxHexToDec(hex.Mid(4, 2));
366
367 return wxColour(r, g, b);
368 }
369
370 bool wxRichTextXMLHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
371 {
372 if (!stream.IsOk())
373 return false;
374
375 wxString version(wxT("1.0") ) ;
376
377 bool deleteConvFile = false;
378 wxString fileEncoding;
379 wxMBConv* convFile = NULL;
380
381 #if wxUSE_UNICODE
382 fileEncoding = wxT("UTF-8");
383 convFile = & wxConvUTF8;
384 #else
385 fileEncoding = wxT("ISO-8859-1");
386 convFile = & wxConvISO8859_1;
387 #endif
388
389 // If SetEncoding has been called, change the output encoding.
390 if (!m_encoding.empty() && m_encoding.Lower() != fileEncoding.Lower())
391 {
392 if (m_encoding == wxT("<System>"))
393 {
394 fileEncoding = wxLocale::GetSystemEncodingName();
395 }
396 else
397 {
398 fileEncoding = m_encoding;
399 }
400
401 // GetSystemEncodingName may not have returned a name
402 if (fileEncoding.empty())
403 #if wxUSE_UNICODE
404 fileEncoding = wxT("UTF-8");
405 #else
406 fileEncoding = wxT("ISO-8859-1");
407 #endif
408 convFile = new wxCSConv(fileEncoding);
409 deleteConvFile = true;
410 }
411
412 #if !wxUSE_UNICODE
413 wxMBConv* convMem = wxConvCurrent;
414 #else
415 wxMBConv* convMem = NULL;
416 #endif
417
418 wxString s ;
419 s.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
420 (const wxChar*) version, (const wxChar*) fileEncoding );
421 OutputString(stream, s, NULL, NULL);
422 OutputString(stream, wxT("<richtext version=\"1.0.0.0\" xmlns=\"http://www.wxwidgets.org\">") , NULL, NULL);
423
424 int level = 1;
425 bool success = ExportXML(stream, convMem, convFile, *buffer, level);
426
427 OutputString(stream, wxT("\n</richtext>") , NULL, NULL);
428 OutputString(stream, wxT("\n"), NULL, NULL);
429
430 if (deleteConvFile)
431 delete convFile;
432
433 return success;
434 }
435
436 /// Recursively export an object
437 bool wxRichTextXMLHandler::ExportXML(wxOutputStream& stream, wxMBConv* convMem, wxMBConv* convFile, wxRichTextObject& obj, int indent)
438 {
439 wxString objectName;
440 if (obj.IsKindOf(CLASSINFO(wxRichTextParagraphLayoutBox)))
441 objectName = wxT("paragraphlayout");
442 else if (obj.IsKindOf(CLASSINFO(wxRichTextParagraph)))
443 objectName = wxT("paragraph");
444 else if (obj.IsKindOf(CLASSINFO(wxRichTextPlainText)))
445 objectName = wxT("text");
446 else if (obj.IsKindOf(CLASSINFO(wxRichTextImage)))
447 objectName = wxT("image");
448 else
449 objectName = wxT("object");
450
451 if (obj.IsKindOf(CLASSINFO(wxRichTextPlainText)))
452 {
453 wxRichTextPlainText& text = (wxRichTextPlainText&) obj;
454
455 OutputIndentation(stream, indent);
456 OutputString(stream, wxT("<") + objectName, convMem, convFile);
457
458 wxString style = CreateStyle(obj.GetAttributes(), false);
459
460 OutputString(stream, style + wxT(">"), convMem, convFile);
461
462 wxString str = text.GetText();
463 if (!str.empty() && (str[0] == wxT(' ') || str[str.length()-1] == wxT(' ')))
464 {
465 OutputString(stream, wxT("\""), convMem, convFile);
466 OutputStringEnt(stream, str, convMem, convFile);
467 OutputString(stream, wxT("\""), convMem, convFile);
468 }
469 else
470 OutputStringEnt(stream, str, convMem, convFile);
471 }
472 else if (obj.IsKindOf(CLASSINFO(wxRichTextImage)))
473 {
474 wxRichTextImage& imageObj = (wxRichTextImage&) obj;
475
476 if (imageObj.GetImage().Ok() && !imageObj.GetImageBlock().Ok())
477 imageObj.MakeBlock();
478
479 OutputIndentation(stream, indent);
480 OutputString(stream, wxT("<") + objectName, convMem, convFile);
481 if (!imageObj.GetImageBlock().Ok())
482 {
483 // No data
484 OutputString(stream, wxT(">"), convMem, convFile);
485 }
486 else
487 {
488 OutputString(stream, wxString::Format(wxT(" imagetype=\"%d\">"), (int) imageObj.GetImageBlock().GetImageType()));
489 }
490
491 OutputIndentation(stream, indent+1);
492 OutputString(stream, wxT("<data>"), convMem, convFile);
493
494 imageObj.GetImageBlock().WriteHex(stream);
495
496 OutputString(stream, wxT("</data>"), convMem, convFile);
497 }
498 else if (obj.IsKindOf(CLASSINFO(wxRichTextCompositeObject)))
499 {
500 OutputIndentation(stream, indent);
501 OutputString(stream, wxT("<") + objectName, convMem, convFile);
502
503 bool isPara = false;
504 if (objectName == wxT("paragraph") || objectName == wxT("paragraphlayout"))
505 isPara = true;
506
507 wxString style = CreateStyle(obj.GetAttributes(), isPara);
508
509 OutputString(stream, style + wxT(">"), convMem, convFile);
510
511 wxRichTextCompositeObject& composite = (wxRichTextCompositeObject&) obj;
512 size_t i;
513 for (i = 0; i < composite.GetChildCount(); i++)
514 {
515 wxRichTextObject* child = composite.GetChild(i);
516 ExportXML(stream, convMem, convFile, *child, indent+1);
517 }
518 }
519
520 if (objectName != wxT("text"))
521 OutputIndentation(stream, indent);
522
523 OutputString(stream, wxT("</") + objectName + wxT(">"), convMem, convFile);
524
525 return true;
526 }
527
528 /// Create style parameters
529 wxString wxRichTextXMLHandler::CreateStyle(const wxTextAttrEx& attr, bool isPara)
530 {
531 wxString str;
532 if (attr.GetTextColour().Ok())
533 {
534 str << wxT(" textcolor=\"#") << ColourToHexString(attr.GetTextColour()) << wxT("\"");
535 }
536 if (attr.GetBackgroundColour().Ok())
537 {
538 str << wxT(" bgcolor=\"#") << ColourToHexString(attr.GetBackgroundColour()) << wxT("\"");
539 }
540
541 if (attr.GetFont().Ok())
542 {
543 str << wxT(" fontsize=\"") << attr.GetFont().GetPointSize() << wxT("\"");
544 str << wxT(" fontfamily=\"") << attr.GetFont().GetFamily() << wxT("\"");
545 str << wxT(" fontstyle=\"") << attr.GetFont().GetStyle() << wxT("\"");
546 str << wxT(" fontweight=\"") << attr.GetFont().GetWeight() << wxT("\"");
547 str << wxT(" fontunderlined=\"") << (int) attr.GetFont().GetUnderlined() << wxT("\"");
548 str << wxT(" fontface=\"") << attr.GetFont().GetFaceName() << wxT("\"");
549 }
550
551 if (!attr.GetCharacterStyleName().empty())
552 str << wxT(" charactertyle=\"") << wxString(attr.GetCharacterStyleName()) << wxT("\"");
553
554 if (isPara)
555 {
556 str << wxT(" alignment=\"") << (int) attr.GetAlignment() << wxT("\"");
557 str << wxT(" leftindent=\"") << (int) attr.GetLeftIndent() << wxT("\"");
558 str << wxT(" leftsubindent=\"") << (int) attr.GetLeftSubIndent() << wxT("\"");
559 str << wxT(" rightindent=\"") << (int) attr.GetRightIndent() << wxT("\"");
560 str << wxT(" parspacingafter=\"") << (int) attr.GetParagraphSpacingAfter() << wxT("\"");
561 str << wxT(" parspacingbefore=\"") << (int) attr.GetParagraphSpacingBefore() << wxT("\"");
562 str << wxT(" linespacing=\"") << (int) attr.GetLineSpacing() << wxT("\"");
563 str << wxT(" bulletstyle=\"") << (int) attr.GetBulletStyle() << wxT("\"");
564 str << wxT(" bulletnumber=\"") << (int) attr.GetBulletNumber() << wxT("\"");
565 str << wxT(" bulletsymbol=\"") << wxString(attr.GetBulletSymbol()) << wxT("\"");
566
567 if (!attr.GetParagraphStyleName().empty())
568 str << wxT(" parstyle=\"") << wxString(attr.GetParagraphStyleName()) << wxT("\"");
569 }
570
571 return str;
572 }
573
574 /// Get style parameters
575 bool wxRichTextXMLHandler::GetStyle(wxTextAttrEx& attr, wxXmlNode* node, bool isPara)
576 {
577 wxString fontFacename;
578 int fontSize = 12;
579 int fontFamily = wxDEFAULT;
580 int fontWeight = wxNORMAL;
581 int fontStyle = wxNORMAL;
582 bool fontUnderlined = false;
583
584 fontFacename = node->GetPropVal(wxT("fontface"), wxEmptyString);
585
586 wxString value = node->GetPropVal(wxT("fontfamily"), wxEmptyString);
587 if (!value.empty())
588 fontFamily = wxAtoi(value);
589
590 value = node->GetPropVal(wxT("fontstyle"), wxEmptyString);
591 if (!value.empty())
592 fontStyle = wxAtoi(value);
593
594 value = node->GetPropVal(wxT("fontsize"), wxEmptyString);
595 if (!value.empty())
596 fontSize = wxAtoi(value);
597
598 value = node->GetPropVal(wxT("fontweight"), wxEmptyString);
599 if (!value.empty())
600 fontWeight = wxAtoi(value);
601
602 value = node->GetPropVal(wxT("fontunderlined"), wxEmptyString);
603 if (!value.empty())
604 fontUnderlined = wxAtoi(value) != 0;
605
606 attr.SetFont(* wxTheFontList->FindOrCreateFont(fontSize, fontFamily, fontStyle, fontWeight, fontUnderlined, fontFacename));
607
608 value = node->GetPropVal(wxT("textcolor"), wxEmptyString);
609 if (!value.empty())
610 {
611 if (value[0] == wxT('#'))
612 attr.SetTextColour(HexStringToColour(value.Mid(1)));
613 else
614 attr.SetTextColour(value);
615 }
616
617 value = node->GetPropVal(wxT("backgroundcolor"), wxEmptyString);
618 if (!value.empty())
619 {
620 if (value[0] == wxT('#'))
621 attr.SetBackgroundColour(HexStringToColour(value.Mid(1)));
622 else
623 attr.SetBackgroundColour(value);
624 }
625
626 value = node->GetPropVal(wxT("characterstyle"), wxEmptyString);
627 if (!value.empty())
628 attr.SetCharacterStyleName(value);
629
630 // Set paragraph attributes
631 if (isPara)
632 {
633 value = node->GetPropVal(wxT("alignment"), wxEmptyString);
634 if (!value.empty())
635 attr.SetAlignment((wxTextAttrAlignment) wxAtoi(value));
636
637 int leftSubIndent = 0;
638 int leftIndent = 0;
639 value = node->GetPropVal(wxT("leftindent"), wxEmptyString);
640 if (!value.empty())
641 leftIndent = wxAtoi(value);
642 value = node->GetPropVal(wxT("leftsubindent"), wxEmptyString);
643 if (!value.empty())
644 leftSubIndent = wxAtoi(value);
645 attr.SetLeftIndent(leftIndent, leftSubIndent);
646
647 value = node->GetPropVal(wxT("rightindent"), wxEmptyString);
648 if (!value.empty())
649 attr.SetRightIndent(wxAtoi(value));
650
651 value = node->GetPropVal(wxT("parspacingbefore"), wxEmptyString);
652 if (!value.empty())
653 attr.SetParagraphSpacingBefore(wxAtoi(value));
654
655 value = node->GetPropVal(wxT("parspacingafter"), wxEmptyString);
656 if (!value.empty())
657 attr.SetParagraphSpacingAfter(wxAtoi(value));
658
659 value = node->GetPropVal(wxT("linespacing"), wxEmptyString);
660 if (!value.empty())
661 attr.SetLineSpacing(wxAtoi(value));
662
663 value = node->GetPropVal(wxT("bulletstyle"), wxEmptyString);
664 if (!value.empty())
665 attr.SetBulletStyle(wxAtoi(value));
666
667 value = node->GetPropVal(wxT("bulletnumber"), wxEmptyString);
668 if (!value.empty())
669 attr.SetBulletNumber(wxAtoi(value));
670
671 value = node->GetPropVal(wxT("bulletsymbol"), wxEmptyString);
672 if (!value.empty())
673 attr.SetBulletSymbol(value[0]);
674
675 value = node->GetPropVal(wxT("parstyle"), wxEmptyString);
676 if (!value.empty())
677 attr.SetParagraphStyleName(value);
678 }
679
680 return true;
681 }
682
683 #endif
684 // wxUSE_STREAMS
685
686 #endif
687 // wxUSE_RICHTEXT && wxUSE_XML