1 /////////////////////////////////////////////////////////////////////////////
2 // Name: 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"
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"
35 #include "wx/richtext/richtextxml.h"
37 IMPLEMENT_DYNAMIC_CLASS(wxRichTextXMLHandler
, wxRichTextFileHandler
)
40 bool wxRichTextXMLHandler::DoLoadFile(wxRichTextBuffer
*buffer
, wxInputStream
& stream
)
47 wxXmlDocument
* xmlDoc
= new wxXmlDocument
;
50 if (!xmlDoc
->Load(stream
, wxT("ISO-8859-1")))
56 if (xmlDoc
->GetRoot() && xmlDoc
->GetRoot()->GetType() == wxXML_ELEMENT_NODE
&& xmlDoc
->GetRoot()->GetName() == wxT("richtext"))
58 wxXmlNode
* child
= xmlDoc
->GetRoot()->GetChildren();
61 if (child
->GetType() == wxXML_ELEMENT_NODE
)
63 wxString name
= child
->GetName();
64 if (name
== wxT("richtext-version"))
68 ImportXML(buffer
, child
);
71 child
= child
->GetNext();
82 buffer
->UpdateRanges();
87 /// Recursively import an object
88 bool wxRichTextXMLHandler::ImportXML(wxRichTextBuffer
* buffer
, wxXmlNode
* node
)
90 wxString name
= node
->GetName();
92 bool doneChildren
= false;
94 if (name
== wxT("paragraphlayout"))
97 else if (name
== wxT("paragraph"))
99 wxRichTextParagraph
* para
= new wxRichTextParagraph(buffer
);
100 buffer
->AppendChild(para
);
102 GetStyle(para
->GetAttributes(), node
, true);
104 wxXmlNode
* child
= node
->GetChildren();
107 wxString childName
= child
->GetName();
108 if (childName
== wxT("text"))
111 wxXmlNode
* textChild
= child
->GetChildren();
114 if (textChild
->GetType() == wxXML_TEXT_NODE
||
115 textChild
->GetType() == wxXML_CDATA_SECTION_NODE
)
117 wxString text2
= textChild
->GetContent();
119 // Strip whitespace from end
120 if (text2
.Length() > 0 && text2
[text2
.Length()-1] == wxT('\n'))
121 text2
= text2
.Mid(0, text2
.Length()-1);
123 if (text2
.Length() > 0 && text2
[0] == wxT('"'))
124 text2
= text2
.Mid(1);
125 if (text2
.Length() > 0 && text2
[text2
.Length()-1] == wxT('"'))
126 text2
= text2
.Mid(0, text2
.Length() - 1);
128 // TODO: further entity translation
129 text2
.Replace(wxT("<"), wxT("<"));
130 text2
.Replace(wxT(">"), wxT(">"));
131 text2
.Replace(wxT("&"), wxT("&"));
132 text2
.Replace(wxT("""), wxT("\""));
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
++)
310 if (c
== wxT('<') || c
== wxT('>') || c
== wxT('"') ||
311 (c
== wxT('&') && (str
.Mid(i
+1, 4) != wxT("amp;"))))
313 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
317 OutputString(stream
, wxT("<"), NULL
, NULL
);
320 OutputString(stream
, wxT(">"), NULL
, NULL
);
323 OutputString(stream
, wxT("&"), NULL
, NULL
);
326 OutputString(stream
, wxT("""), NULL
, NULL
);
333 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
336 inline static void OutputIndentation(wxOutputStream
& stream
, int indent
)
338 wxString str
= wxT("\n");
339 for (int i
= 0; i
< indent
; i
++)
340 str
<< wxT(' ') << wxT(' ');
341 OutputString(stream
, str
, NULL
, NULL
);
344 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const wxString
& s
)
346 stream
.Write(s
, s
.Length());
351 static wxOutputStream
& operator <<(wxOutputStream
& stream
, long l
)
354 str
.Printf(wxT("%ld"), l
);
355 return stream
<< str
;
358 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const char c
)
361 str
.Printf(wxT("%c"), c
);
362 return stream
<< str
;
366 // Convert a colour to a 6-digit hex string
367 static wxString
ColourToHexString(const wxColour
& col
)
371 hex
+= wxDecToHex(col
.Red());
372 hex
+= wxDecToHex(col
.Green());
373 hex
+= wxDecToHex(col
.Blue());
378 // Convert 6-digit hex string to a colour
379 wxColour
HexStringToColour(const wxString
& hex
)
381 unsigned char r
= (unsigned char)wxHexToDec(hex
.Mid(0, 2));
382 unsigned char g
= (unsigned char)wxHexToDec(hex
.Mid(2, 2));
383 unsigned char b
= (unsigned char)wxHexToDec(hex
.Mid(4, 2));
385 return wxColour(r
, g
, b
);
388 bool wxRichTextXMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
393 wxString
version(wxT("1.0") ) ;
395 wxString
fileencoding(wxT("UTF-8")) ;
396 wxString
memencoding(wxT("UTF-8")) ;
398 wxString
fileencoding(wxT("ISO-8859-1")) ;
399 wxString
memencoding(wxT("ISO-8859-1")) ;
403 wxMBConv
*convMem
= NULL
, *convFile
= NULL
;
405 convFile
= new wxCSConv(fileencoding
);
407 if ( fileencoding
!= memencoding
)
409 convFile
= new wxCSConv(fileencoding
);
410 convMem
= new wxCSConv(memencoding
);
414 s
.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
415 (const wxChar
*) version
, (const wxChar
*) fileencoding
);
416 OutputString(stream
, s
, NULL
, NULL
);
417 OutputString(stream
, wxT("<richtext version=\"1.0.0.0\" xmlns=\"http://www.wxwidgets.org\">") , NULL
, NULL
);
420 ExportXML(stream
, convMem
, convFile
, *buffer
, level
);
422 OutputString(stream
, wxT("\n</richtext>") , NULL
, NULL
);
423 OutputString(stream
, wxT("\n"), NULL
, NULL
);
431 /// Recursively export an object
432 bool wxRichTextXMLHandler::ExportXML(wxOutputStream
& stream
, wxMBConv
* convMem
, wxMBConv
* convFile
, wxRichTextObject
& obj
, int indent
)
435 if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraphLayoutBox
)))
436 objectName
= wxT("paragraphlayout");
437 else if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraph
)))
438 objectName
= wxT("paragraph");
439 else if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
440 objectName
= wxT("text");
441 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
442 objectName
= wxT("image");
444 objectName
= wxT("object");
446 if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
448 wxRichTextPlainText
& text
= (wxRichTextPlainText
&) obj
;
450 OutputIndentation(stream
, indent
);
451 stream
<< wxT("<") << objectName
;
453 wxString style
= CreateStyle(obj
.GetAttributes(), false);
455 stream
<< style
<< wxT(">");
457 wxString str
= text
.GetText();
458 if (str
.Length() > 0 && (str
[0] == wxT(' ') || str
[str
.Length()-1] == wxT(' ')))
461 OutputStringEnt(stream
, str
, convMem
, convFile
);
465 OutputStringEnt(stream
, str
, convMem
, convFile
);
467 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
469 wxRichTextImage
& imageObj
= (wxRichTextImage
&) obj
;
471 if (imageObj
.GetImage().Ok() && !imageObj
.GetImageBlock().Ok())
472 imageObj
.MakeBlock();
474 OutputIndentation(stream
, indent
);
475 stream
<< wxT("<") << objectName
;
476 if (!imageObj
.GetImageBlock().Ok())
483 stream
<< wxString::Format(wxT(" imagetype=\"%d\""), (int) imageObj
.GetImageBlock().GetImageType()) << wxT(">");
486 OutputIndentation(stream
, indent
+1);
487 stream
<< wxT("<data>");
489 imageObj
.GetImageBlock().WriteHex(stream
);
491 stream
<< wxT("</data>");
493 else if (obj
.IsKindOf(CLASSINFO(wxRichTextCompositeObject
)))
495 OutputIndentation(stream
, indent
);
496 stream
<< wxT("<") << objectName
;
499 if (objectName
== wxT("paragraph") || objectName
== wxT("paragraphlayout"))
502 wxString style
= CreateStyle(obj
.GetAttributes(), isPara
);
504 stream
<< style
<< wxT(">");
506 wxRichTextCompositeObject
& composite
= (wxRichTextCompositeObject
&) obj
;
508 for (i
= 0; i
< composite
.GetChildCount(); i
++)
510 wxRichTextObject
* child
= composite
.GetChild(i
);
511 ExportXML(stream
, convMem
, convFile
, *child
, indent
+1);
515 if (objectName
!= wxT("text"))
516 OutputIndentation(stream
, indent
);
518 stream
<< wxT("</") << objectName
<< wxT(">");
523 /// Create style parameters
524 wxString
wxRichTextXMLHandler::CreateStyle(const wxTextAttrEx
& attr
, bool isPara
)
527 if (attr
.GetTextColour().Ok())
529 str
<< wxT(" textcolor=\"#") << ColourToHexString(attr
.GetTextColour()) << wxT("\"");
531 if (attr
.GetBackgroundColour().Ok())
533 str
<< wxT(" bgcolor=\"#") << ColourToHexString(attr
.GetBackgroundColour()) << wxT("\"");
536 if (attr
.GetFont().Ok())
538 str
<< wxT(" fontsize=\"") << attr
.GetFont().GetPointSize() << wxT("\"");
539 str
<< wxT(" fontfamily=\"") << attr
.GetFont().GetFamily() << wxT("\"");
540 str
<< wxT(" fontstyle=\"") << attr
.GetFont().GetStyle() << wxT("\"");
541 str
<< wxT(" fontweight=\"") << attr
.GetFont().GetWeight() << wxT("\"");
542 str
<< wxT(" fontunderlined=\"") << (int) attr
.GetFont().GetUnderlined() << wxT("\"");
543 str
<< wxT(" fontface=\"") << attr
.GetFont().GetFaceName() << wxT("\"");
546 if (!attr
.GetCharacterStyleName().empty())
547 str
<< wxT(" charactertyle=\"") << wxString(attr
.GetCharacterStyleName()) << wxT("\"");
551 str
<< wxT(" alignment=\"") << (int) attr
.GetAlignment() << wxT("\"");
552 str
<< wxT(" leftindent=\"") << (int) attr
.GetLeftIndent() << wxT("\"");
553 str
<< wxT(" leftsubindent=\"") << (int) attr
.GetLeftSubIndent() << wxT("\"");
554 str
<< wxT(" rightindent=\"") << (int) attr
.GetRightIndent() << wxT("\"");
555 str
<< wxT(" parspacingafter=\"") << (int) attr
.GetParagraphSpacingAfter() << wxT("\"");
556 str
<< wxT(" parspacingbefore=\"") << (int) attr
.GetParagraphSpacingBefore() << wxT("\"");
557 str
<< wxT(" linespacing=\"") << (int) attr
.GetLineSpacing() << wxT("\"");
558 str
<< wxT(" bulletstyle=\"") << (int) attr
.GetBulletStyle() << wxT("\"");
559 str
<< wxT(" bulletnumber=\"") << (int) attr
.GetBulletNumber() << wxT("\"");
560 str
<< wxT(" bulletsymbol=\"") << wxString(attr
.GetBulletSymbol()) << wxT("\"");
562 if (!attr
.GetParagraphStyleName().empty())
563 str
<< wxT(" parstyle=\"") << wxString(attr
.GetParagraphStyleName()) << wxT("\"");
569 /// Get style parameters
570 bool wxRichTextXMLHandler::GetStyle(wxTextAttrEx
& attr
, wxXmlNode
* node
, bool isPara
)
572 wxString fontFacename
;
574 int fontFamily
= wxDEFAULT
;
575 int fontWeight
= wxNORMAL
;
576 int fontStyle
= wxNORMAL
;
577 bool fontUnderlined
= false;
579 fontFacename
= node
->GetPropVal(wxT("fontface"), wxEmptyString
);
581 wxString value
= node
->GetPropVal(wxT("fontfamily"), wxEmptyString
);
583 fontFamily
= wxAtoi(value
);
585 value
= node
->GetPropVal(wxT("fontstyle"), wxEmptyString
);
587 fontStyle
= wxAtoi(value
);
589 value
= node
->GetPropVal(wxT("fontsize"), wxEmptyString
);
591 fontSize
= wxAtoi(value
);
593 value
= node
->GetPropVal(wxT("fontweight"), wxEmptyString
);
595 fontWeight
= wxAtoi(value
);
597 value
= node
->GetPropVal(wxT("fontunderlined"), wxEmptyString
);
599 fontUnderlined
= wxAtoi(value
) != 0;
601 attr
.SetFont(* wxTheFontList
->FindOrCreateFont(fontSize
, fontFamily
, fontStyle
, fontWeight
, fontUnderlined
, fontFacename
));
603 value
= node
->GetPropVal(wxT("textcolor"), wxEmptyString
);
606 if (value
[0] == wxT('#'))
607 attr
.SetTextColour(HexStringToColour(value
.Mid(1)));
609 attr
.SetTextColour(value
);
612 value
= node
->GetPropVal(wxT("backgroundcolor"), wxEmptyString
);
615 if (value
[0] == wxT('#'))
616 attr
.SetBackgroundColour(HexStringToColour(value
.Mid(1)));
618 attr
.SetBackgroundColour(value
);
621 value
= node
->GetPropVal(wxT("characterstyle"), wxEmptyString
);
623 attr
.SetCharacterStyleName(value
);
625 // Set paragraph attributes
628 value
= node
->GetPropVal(wxT("alignment"), wxEmptyString
);
630 attr
.SetAlignment((wxTextAttrAlignment
) wxAtoi(value
));
632 int leftSubIndent
= 0;
634 value
= node
->GetPropVal(wxT("leftindent"), wxEmptyString
);
636 leftIndent
= wxAtoi(value
);
637 value
= node
->GetPropVal(wxT("leftsubindent"), wxEmptyString
);
639 leftSubIndent
= wxAtoi(value
);
640 attr
.SetLeftIndent(leftIndent
, leftSubIndent
);
642 value
= node
->GetPropVal(wxT("rightindent"), wxEmptyString
);
644 attr
.SetRightIndent(wxAtoi(value
));
646 value
= node
->GetPropVal(wxT("parspacingbefore"), wxEmptyString
);
648 attr
.SetParagraphSpacingBefore(wxAtoi(value
));
650 value
= node
->GetPropVal(wxT("parspacingafter"), wxEmptyString
);
652 attr
.SetParagraphSpacingAfter(wxAtoi(value
));
654 value
= node
->GetPropVal(wxT("linespacing"), wxEmptyString
);
656 attr
.SetLineSpacing(wxAtoi(value
));
658 value
= node
->GetPropVal(wxT("bulletstyle"), wxEmptyString
);
660 attr
.SetBulletStyle(wxAtoi(value
));
662 value
= node
->GetPropVal(wxT("bulletnumber"), wxEmptyString
);
664 attr
.SetBulletNumber(wxAtoi(value
));
666 value
= node
->GetPropVal(wxT("bulletsymbol"), wxEmptyString
);
668 attr
.SetBulletSymbol(value
[0]);
670 value
= node
->GetPropVal(wxT("parstyle"), wxEmptyString
);
672 attr
.SetParagraphStyleName(value
);
680 IMPLEMENT_DYNAMIC_CLASS(wxRichTextHTMLHandler
, wxRichTextFileHandler
)
682 /// Can we handle this filename (if using files)? By default, checks the extension.
683 bool wxRichTextHTMLHandler::CanHandle(const wxString
& filename
) const
685 wxString path
, file
, ext
;
686 wxSplitPath(filename
, & path
, & file
, & ext
);
688 return (ext
.Lower() == wxT("html") || ext
.Lower() == wxT("htm"));
693 bool wxRichTextHTMLHandler::DoLoadFile(wxRichTextBuffer
*WXUNUSED(buffer
), wxInputStream
& WXUNUSED(stream
))
699 * We need to output only _changes_ in character formatting.
702 bool wxRichTextHTMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
704 buffer
->Defragment();
706 wxTextOutputStream
str(stream
);
708 wxTextAttrEx currentParaStyle
= buffer
->GetAttributes();
709 wxTextAttrEx currentCharStyle
= buffer
->GetAttributes();
711 str
<< wxT("<html><head></head><body>\n");
713 wxRichTextObjectList::compatibility_iterator node
= buffer
->GetChildren().GetFirst();
716 wxRichTextParagraph
* para
= wxDynamicCast(node
->GetData(), wxRichTextParagraph
);
717 wxASSERT (para
!= NULL
);
721 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, true);
723 wxRichTextObjectList::compatibility_iterator node2
= para
->GetChildren().GetFirst();
726 wxRichTextObject
* obj
= node2
->GetData();
727 wxRichTextPlainText
* textObj
= wxDynamicCast(obj
, wxRichTextPlainText
);
728 if (textObj
&& !textObj
->IsEmpty())
730 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, true);
732 str
<< textObj
->GetText();
734 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, false);
737 node2
= node2
->GetNext();
740 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, false);
745 node
= node
->GetNext();
748 str
<< wxT("</body></html>\n");
753 /// Output character formatting
754 void wxRichTextHTMLHandler::OutputCharacterFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
756 wxTextOutputStream
str(stream
);
759 bool isItalic
= false;
760 bool isUnderline
= false;
763 if (thisStyle
.GetFont().Ok())
765 if (thisStyle
.GetFont().GetWeight() == wxBOLD
)
767 if (thisStyle
.GetFont().GetStyle() == wxITALIC
)
769 if (thisStyle
.GetFont().GetUnderlined())
772 faceName
= thisStyle
.GetFont().GetFaceName();
795 /// Output paragraph formatting
796 void wxRichTextHTMLHandler::OutputParagraphFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
798 // TODO: lists, indentation (using tables), fonts, right-align, ...
800 wxTextOutputStream
str(stream
);
801 bool isCentered
= false;
803 if (thisStyle
.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE
)
811 str
<< wxT("<center>");
816 str
<< wxT("</center>");