1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/richtext/richtextxml.cpp
3 // Purpose: XML and HTML I/O for wxRichTextCtrl
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
19 #if wxUSE_RICHTEXT && wxUSE_XML
21 #include "wx/richtext/richtextxml.h"
28 #include "wx/filename.h"
29 #include "wx/clipbrd.h"
30 #include "wx/wfstream.h"
31 #include "wx/sstream.h"
32 #include "wx/module.h"
33 #include "wx/txtstrm.h"
34 #include "wx/xml/xml.h"
36 IMPLEMENT_DYNAMIC_CLASS(wxRichTextXMLHandler
, wxRichTextFileHandler
)
39 bool wxRichTextXMLHandler::DoLoadFile(wxRichTextBuffer
*buffer
, wxInputStream
& stream
)
46 wxXmlDocument
* xmlDoc
= new wxXmlDocument
;
49 // This is the encoding to convert to (memory encoding rather than file encoding)
50 wxString
encoding(wxT("UTF-8"));
52 #if !wxUSE_UNICODE && wxUSE_INTL
53 encoding
= wxLocale::GetSystemEncodingName();
56 if (!xmlDoc
->Load(stream
, encoding
))
62 if (xmlDoc
->GetRoot() && xmlDoc
->GetRoot()->GetType() == wxXML_ELEMENT_NODE
&& xmlDoc
->GetRoot()->GetName() == wxT("richtext"))
64 wxXmlNode
* child
= xmlDoc
->GetRoot()->GetChildren();
67 if (child
->GetType() == wxXML_ELEMENT_NODE
)
69 wxString name
= child
->GetName();
70 if (name
== wxT("richtext-version"))
74 ImportXML(buffer
, child
);
77 child
= child
->GetNext();
88 buffer
->UpdateRanges();
93 /// Recursively import an object
94 bool wxRichTextXMLHandler::ImportXML(wxRichTextBuffer
* buffer
, wxXmlNode
* node
)
96 wxString name
= node
->GetName();
98 bool doneChildren
= false;
100 if (name
== wxT("paragraphlayout"))
103 else if (name
== wxT("paragraph"))
105 wxRichTextParagraph
* para
= new wxRichTextParagraph(buffer
);
106 buffer
->AppendChild(para
);
108 GetStyle(para
->GetAttributes(), node
, true);
110 wxXmlNode
* child
= node
->GetChildren();
113 wxString childName
= child
->GetName();
114 if (childName
== wxT("text"))
117 wxXmlNode
* textChild
= child
->GetChildren();
120 if (textChild
->GetType() == wxXML_TEXT_NODE
||
121 textChild
->GetType() == wxXML_CDATA_SECTION_NODE
)
123 wxString text2
= textChild
->GetContent();
125 // Strip whitespace from end
126 if (!text2
.empty() && text2
[text2
.length()-1] == wxT('\n'))
127 text2
= text2
.Mid(0, text2
.length()-1);
129 if (!text2
.empty() && text2
[0] == wxT('"'))
130 text2
= text2
.Mid(1);
131 if (!text2
.empty() && text2
[text2
.length()-1] == wxT('"'))
132 text2
= text2
.Mid(0, text2
.length() - 1);
136 textChild
= textChild
->GetNext();
139 wxRichTextPlainText
* textObject
= new wxRichTextPlainText(text
, para
);
140 GetStyle(textObject
->GetAttributes(), child
, false);
142 para
->AppendChild(textObject
);
144 else if (childName
== wxT("image"))
146 int imageType
= wxBITMAP_TYPE_PNG
;
147 wxString value
= node
->GetPropVal(wxT("imagetype"), wxEmptyString
);
149 imageType
= wxAtoi(value
);
153 wxXmlNode
* imageChild
= child
->GetChildren();
156 wxString childName
= imageChild
->GetName();
157 if (childName
== wxT("data"))
159 wxXmlNode
* dataChild
= imageChild
->GetChildren();
162 data
= dataChild
->GetContent();
164 dataChild
= dataChild
->GetNext();
168 imageChild
= imageChild
->GetNext();
173 wxRichTextImage
* imageObj
= new wxRichTextImage(para
);
174 para
->AppendChild(imageObj
);
176 wxStringInputStream
strStream(data
);
178 imageObj
->GetImageBlock().ReadHex(strStream
, data
.length(), imageType
);
181 child
= child
->GetNext();
189 wxXmlNode
* child
= node
->GetChildren();
192 ImportXML(buffer
, child
);
193 child
= child
->GetNext();
201 //-----------------------------------------------------------------------------
202 // xml support routines
203 //-----------------------------------------------------------------------------
205 bool wxRichTextXMLHandler::HasParam(wxXmlNode
* node
, const wxString
& param
)
207 return (GetParamNode(node
, param
) != NULL
);
210 wxXmlNode
*wxRichTextXMLHandler::GetParamNode(wxXmlNode
* node
, const wxString
& param
)
212 wxCHECK_MSG(node
, NULL
, wxT("You can't access node data before it was initialized!"));
214 wxXmlNode
*n
= node
->GetChildren();
218 if (n
->GetType() == wxXML_ELEMENT_NODE
&& n
->GetName() == param
)
226 wxString
wxRichTextXMLHandler::GetNodeContent(wxXmlNode
*node
)
229 if (n
== NULL
) return wxEmptyString
;
230 n
= n
->GetChildren();
234 if (n
->GetType() == wxXML_TEXT_NODE
||
235 n
->GetType() == wxXML_CDATA_SECTION_NODE
)
236 return n
->GetContent();
239 return wxEmptyString
;
243 wxString
wxRichTextXMLHandler::GetParamValue(wxXmlNode
*node
, const wxString
& param
)
246 return GetNodeContent(node
);
248 return GetNodeContent(GetParamNode(node
, param
));
251 wxString
wxRichTextXMLHandler::GetText(wxXmlNode
*node
, const wxString
& param
, bool WXUNUSED(translate
))
253 wxXmlNode
*parNode
= GetParamNode(node
, param
);
256 wxString
str1(GetNodeContent(parNode
));
260 // For use with earlier versions of wxWidgets
261 #ifndef WXUNUSED_IN_UNICODE
263 #define WXUNUSED_IN_UNICODE(x) WXUNUSED(x)
265 #define WXUNUSED_IN_UNICODE(x) x
269 // write string to output:
270 inline static void OutputString(wxOutputStream
& stream
, const wxString
& str
,
271 wxMBConv
*WXUNUSED_IN_UNICODE(convMem
) = NULL
, wxMBConv
*convFile
= NULL
)
273 if (str
.empty()) return;
277 const wxWX2MBbuf
buf(str
.mb_str(*convFile
));
278 stream
.Write((const char*)buf
, strlen((const char*)buf
));
282 const wxWX2MBbuf
buf(str
.mb_str(wxConvUTF8
));
283 stream
.Write((const char*)buf
, strlen((const char*)buf
));
286 if ( convFile
== NULL
)
287 stream
.Write(str
.mb_str(), str
.Len());
290 wxString
str2(str
.wc_str(*convMem
), *convFile
);
291 stream
.Write(str2
.mb_str(), str2
.Len());
296 // Same as above, but create entities first.
297 // Translates '<' to "<", '>' to ">" and '&' to "&"
298 static void OutputStringEnt(wxOutputStream
& stream
, const wxString
& str
,
299 wxMBConv
*convMem
= NULL
, wxMBConv
*convFile
= NULL
)
307 for (i
= 0; i
< len
; i
++)
311 // Original code excluded "&" but we _do_ want to convert
312 // the ampersand beginning & because otherwise when read in,
313 // the original "&" becomes "&".
315 if (c
== wxT('<') || c
== wxT('>') || c
== wxT('"') ||
316 (c
== wxT('&') /* && (str.Mid(i+1, 4) != wxT("amp;")) */ ))
318 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
322 OutputString(stream
, wxT("<"), NULL
, NULL
);
325 OutputString(stream
, wxT(">"), NULL
, NULL
);
328 OutputString(stream
, wxT("&"), NULL
, NULL
);
331 OutputString(stream
, wxT("""), NULL
, NULL
);
338 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
341 inline static void OutputIndentation(wxOutputStream
& stream
, int indent
)
343 wxString str
= wxT("\n");
344 for (int i
= 0; i
< indent
; i
++)
345 str
<< wxT(' ') << wxT(' ');
346 OutputString(stream
, str
, NULL
, NULL
);
349 // Convert a colour to a 6-digit hex string
350 static wxString
ColourToHexString(const wxColour
& col
)
354 hex
+= wxDecToHex(col
.Red());
355 hex
+= wxDecToHex(col
.Green());
356 hex
+= wxDecToHex(col
.Blue());
361 // Convert 6-digit hex string to a colour
362 static wxColour
HexStringToColour(const wxString
& hex
)
364 unsigned char r
= (unsigned char)wxHexToDec(hex
.Mid(0, 2));
365 unsigned char g
= (unsigned char)wxHexToDec(hex
.Mid(2, 2));
366 unsigned char b
= (unsigned char)wxHexToDec(hex
.Mid(4, 2));
368 return wxColour(r
, g
, b
);
371 bool wxRichTextXMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
376 wxString
version(wxT("1.0") ) ;
378 bool deleteConvFile
= false;
379 wxString fileEncoding
;
380 wxMBConv
* convFile
= NULL
;
383 fileEncoding
= wxT("UTF-8");
384 convFile
= & wxConvUTF8
;
386 fileEncoding
= wxT("ISO-8859-1");
387 convFile
= & wxConvISO8859_1
;
390 // If SetEncoding has been called, change the output encoding.
391 if (!m_encoding
.empty() && m_encoding
.Lower() != fileEncoding
.Lower())
393 if (m_encoding
== wxT("<System>"))
395 fileEncoding
= wxLocale::GetSystemEncodingName();
399 fileEncoding
= m_encoding
;
402 // GetSystemEncodingName may not have returned a name
403 if (fileEncoding
.empty())
405 fileEncoding
= wxT("UTF-8");
407 fileEncoding
= wxT("ISO-8859-1");
409 convFile
= new wxCSConv(fileEncoding
);
410 deleteConvFile
= true;
414 wxMBConv
* convMem
= wxConvCurrent
;
416 wxMBConv
* convMem
= NULL
;
420 s
.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
421 (const wxChar
*) version
, (const wxChar
*) fileEncoding
);
422 OutputString(stream
, s
, NULL
, NULL
);
423 OutputString(stream
, wxT("<richtext version=\"1.0.0.0\" xmlns=\"http://www.wxwidgets.org\">") , NULL
, NULL
);
426 bool success
= ExportXML(stream
, convMem
, convFile
, *buffer
, level
);
428 OutputString(stream
, wxT("\n</richtext>") , NULL
, NULL
);
429 OutputString(stream
, wxT("\n"), NULL
, NULL
);
437 /// Recursively export an object
438 bool wxRichTextXMLHandler::ExportXML(wxOutputStream
& stream
, wxMBConv
* convMem
, wxMBConv
* convFile
, wxRichTextObject
& obj
, int indent
)
441 if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraphLayoutBox
)))
442 objectName
= wxT("paragraphlayout");
443 else if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraph
)))
444 objectName
= wxT("paragraph");
445 else if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
446 objectName
= wxT("text");
447 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
448 objectName
= wxT("image");
450 objectName
= wxT("object");
452 if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
454 wxRichTextPlainText
& text
= (wxRichTextPlainText
&) obj
;
456 OutputIndentation(stream
, indent
);
457 OutputString(stream
, wxT("<") + objectName
, convMem
, convFile
);
459 wxString style
= CreateStyle(obj
.GetAttributes(), false);
461 OutputString(stream
, style
+ wxT(">"), convMem
, convFile
);
463 wxString str
= text
.GetText();
464 if (!str
.empty() && (str
[0] == wxT(' ') || str
[str
.length()-1] == wxT(' ')))
466 OutputString(stream
, wxT("\""), convMem
, convFile
);
467 OutputStringEnt(stream
, str
, convMem
, convFile
);
468 OutputString(stream
, wxT("\""), convMem
, convFile
);
471 OutputStringEnt(stream
, str
, convMem
, convFile
);
473 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
475 wxRichTextImage
& imageObj
= (wxRichTextImage
&) obj
;
477 if (imageObj
.GetImage().Ok() && !imageObj
.GetImageBlock().Ok())
478 imageObj
.MakeBlock();
480 OutputIndentation(stream
, indent
);
481 OutputString(stream
, wxT("<") + objectName
, convMem
, convFile
);
482 if (!imageObj
.GetImageBlock().Ok())
485 OutputString(stream
, wxT(">"), convMem
, convFile
);
489 OutputString(stream
, wxString::Format(wxT(" imagetype=\"%d\">"), (int) imageObj
.GetImageBlock().GetImageType()));
492 OutputIndentation(stream
, indent
+1);
493 OutputString(stream
, wxT("<data>"), convMem
, convFile
);
495 imageObj
.GetImageBlock().WriteHex(stream
);
497 OutputString(stream
, wxT("</data>"), convMem
, convFile
);
499 else if (obj
.IsKindOf(CLASSINFO(wxRichTextCompositeObject
)))
501 OutputIndentation(stream
, indent
);
502 OutputString(stream
, wxT("<") + objectName
, convMem
, convFile
);
505 if (objectName
== wxT("paragraph") || objectName
== wxT("paragraphlayout"))
508 wxString style
= CreateStyle(obj
.GetAttributes(), isPara
);
510 OutputString(stream
, style
+ wxT(">"), convMem
, convFile
);
512 wxRichTextCompositeObject
& composite
= (wxRichTextCompositeObject
&) obj
;
514 for (i
= 0; i
< composite
.GetChildCount(); i
++)
516 wxRichTextObject
* child
= composite
.GetChild(i
);
517 ExportXML(stream
, convMem
, convFile
, *child
, indent
+1);
521 if (objectName
!= wxT("text"))
522 OutputIndentation(stream
, indent
);
524 OutputString(stream
, wxT("</") + objectName
+ wxT(">"), convMem
, convFile
);
529 /// Create style parameters
530 wxString
wxRichTextXMLHandler::CreateStyle(const wxTextAttrEx
& attr
, bool isPara
)
533 if (attr
.GetTextColour().Ok())
535 str
<< wxT(" textcolor=\"#") << ColourToHexString(attr
.GetTextColour()) << wxT("\"");
537 if (attr
.GetBackgroundColour().Ok())
539 str
<< wxT(" bgcolor=\"#") << ColourToHexString(attr
.GetBackgroundColour()) << wxT("\"");
542 if (attr
.GetFont().Ok())
544 str
<< wxT(" fontsize=\"") << attr
.GetFont().GetPointSize() << wxT("\"");
545 str
<< wxT(" fontfamily=\"") << attr
.GetFont().GetFamily() << wxT("\"");
546 str
<< wxT(" fontstyle=\"") << attr
.GetFont().GetStyle() << wxT("\"");
547 str
<< wxT(" fontweight=\"") << attr
.GetFont().GetWeight() << wxT("\"");
548 str
<< wxT(" fontunderlined=\"") << (int) attr
.GetFont().GetUnderlined() << wxT("\"");
549 str
<< wxT(" fontface=\"") << attr
.GetFont().GetFaceName() << wxT("\"");
552 if (!attr
.GetCharacterStyleName().empty())
553 str
<< wxT(" charactertyle=\"") << wxString(attr
.GetCharacterStyleName()) << wxT("\"");
557 str
<< wxT(" alignment=\"") << (int) attr
.GetAlignment() << wxT("\"");
558 str
<< wxT(" leftindent=\"") << (int) attr
.GetLeftIndent() << wxT("\"");
559 str
<< wxT(" leftsubindent=\"") << (int) attr
.GetLeftSubIndent() << wxT("\"");
560 str
<< wxT(" rightindent=\"") << (int) attr
.GetRightIndent() << wxT("\"");
561 str
<< wxT(" parspacingafter=\"") << (int) attr
.GetParagraphSpacingAfter() << wxT("\"");
562 str
<< wxT(" parspacingbefore=\"") << (int) attr
.GetParagraphSpacingBefore() << wxT("\"");
563 str
<< wxT(" linespacing=\"") << (int) attr
.GetLineSpacing() << wxT("\"");
564 str
<< wxT(" bulletstyle=\"") << (int) attr
.GetBulletStyle() << wxT("\"");
565 str
<< wxT(" bulletnumber=\"") << (int) attr
.GetBulletNumber() << wxT("\"");
566 str
<< wxT(" bulletsymbol=\"") << wxString(attr
.GetBulletSymbol()) << wxT("\"");
568 if (!attr
.GetParagraphStyleName().empty())
569 str
<< wxT(" parstyle=\"") << wxString(attr
.GetParagraphStyleName()) << wxT("\"");
575 /// Get style parameters
576 bool wxRichTextXMLHandler::GetStyle(wxTextAttrEx
& attr
, wxXmlNode
* node
, bool isPara
)
578 wxString fontFacename
;
580 int fontFamily
= wxDEFAULT
;
581 int fontWeight
= wxNORMAL
;
582 int fontStyle
= wxNORMAL
;
583 bool fontUnderlined
= false;
585 fontFacename
= node
->GetPropVal(wxT("fontface"), wxEmptyString
);
587 wxString value
= node
->GetPropVal(wxT("fontfamily"), wxEmptyString
);
589 fontFamily
= wxAtoi(value
);
591 value
= node
->GetPropVal(wxT("fontstyle"), wxEmptyString
);
593 fontStyle
= wxAtoi(value
);
595 value
= node
->GetPropVal(wxT("fontsize"), wxEmptyString
);
597 fontSize
= wxAtoi(value
);
599 value
= node
->GetPropVal(wxT("fontweight"), wxEmptyString
);
601 fontWeight
= wxAtoi(value
);
603 value
= node
->GetPropVal(wxT("fontunderlined"), wxEmptyString
);
605 fontUnderlined
= wxAtoi(value
) != 0;
607 attr
.SetFont(* wxTheFontList
->FindOrCreateFont(fontSize
, fontFamily
, fontStyle
, fontWeight
, fontUnderlined
, fontFacename
));
609 value
= node
->GetPropVal(wxT("textcolor"), wxEmptyString
);
612 if (value
[0] == wxT('#'))
613 attr
.SetTextColour(HexStringToColour(value
.Mid(1)));
615 attr
.SetTextColour(value
);
618 value
= node
->GetPropVal(wxT("backgroundcolor"), wxEmptyString
);
621 if (value
[0] == wxT('#'))
622 attr
.SetBackgroundColour(HexStringToColour(value
.Mid(1)));
624 attr
.SetBackgroundColour(value
);
627 value
= node
->GetPropVal(wxT("characterstyle"), wxEmptyString
);
629 attr
.SetCharacterStyleName(value
);
631 // Set paragraph attributes
634 value
= node
->GetPropVal(wxT("alignment"), wxEmptyString
);
636 attr
.SetAlignment((wxTextAttrAlignment
) wxAtoi(value
));
638 int leftSubIndent
= 0;
640 value
= node
->GetPropVal(wxT("leftindent"), wxEmptyString
);
642 leftIndent
= wxAtoi(value
);
643 value
= node
->GetPropVal(wxT("leftsubindent"), wxEmptyString
);
645 leftSubIndent
= wxAtoi(value
);
646 attr
.SetLeftIndent(leftIndent
, leftSubIndent
);
648 value
= node
->GetPropVal(wxT("rightindent"), wxEmptyString
);
650 attr
.SetRightIndent(wxAtoi(value
));
652 value
= node
->GetPropVal(wxT("parspacingbefore"), wxEmptyString
);
654 attr
.SetParagraphSpacingBefore(wxAtoi(value
));
656 value
= node
->GetPropVal(wxT("parspacingafter"), wxEmptyString
);
658 attr
.SetParagraphSpacingAfter(wxAtoi(value
));
660 value
= node
->GetPropVal(wxT("linespacing"), wxEmptyString
);
662 attr
.SetLineSpacing(wxAtoi(value
));
664 value
= node
->GetPropVal(wxT("bulletstyle"), wxEmptyString
);
666 attr
.SetBulletStyle(wxAtoi(value
));
668 value
= node
->GetPropVal(wxT("bulletnumber"), wxEmptyString
);
670 attr
.SetBulletNumber(wxAtoi(value
));
672 value
= node
->GetPropVal(wxT("bulletsymbol"), wxEmptyString
);
674 attr
.SetBulletSymbol(value
[0]);
676 value
= node
->GetPropVal(wxT("parstyle"), wxEmptyString
);
678 attr
.SetParagraphStyleName(value
);
688 // wxUSE_RICHTEXT && wxUSE_XML