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"
19 #if wxUSE_RICHTEXT && wxUSE_XML
21 #include "wx/richtext/richtextxml.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 IMPLEMENT_DYNAMIC_CLASS(wxRichTextXMLHandler
, wxRichTextFileHandler
)
38 bool wxRichTextXMLHandler::DoLoadFile(wxRichTextBuffer
*buffer
, wxInputStream
& stream
)
45 wxXmlDocument
* xmlDoc
= new wxXmlDocument
;
48 if (!xmlDoc
->Load(stream
, wxT("ISO-8859-1")))
54 if (xmlDoc
->GetRoot() && xmlDoc
->GetRoot()->GetType() == wxXML_ELEMENT_NODE
&& xmlDoc
->GetRoot()->GetName() == wxT("richtext"))
56 wxXmlNode
* child
= xmlDoc
->GetRoot()->GetChildren();
59 if (child
->GetType() == wxXML_ELEMENT_NODE
)
61 wxString name
= child
->GetName();
62 if (name
== wxT("richtext-version"))
66 ImportXML(buffer
, child
);
69 child
= child
->GetNext();
80 buffer
->UpdateRanges();
85 /// Recursively import an object
86 bool wxRichTextXMLHandler::ImportXML(wxRichTextBuffer
* buffer
, wxXmlNode
* node
)
88 wxString name
= node
->GetName();
90 bool doneChildren
= false;
92 if (name
== wxT("paragraphlayout"))
95 else if (name
== wxT("paragraph"))
97 wxRichTextParagraph
* para
= new wxRichTextParagraph(buffer
);
98 buffer
->AppendChild(para
);
100 GetStyle(para
->GetAttributes(), node
, true);
102 wxXmlNode
* child
= node
->GetChildren();
105 wxString childName
= child
->GetName();
106 if (childName
== wxT("text"))
109 wxXmlNode
* textChild
= child
->GetChildren();
112 if (textChild
->GetType() == wxXML_TEXT_NODE
||
113 textChild
->GetType() == wxXML_CDATA_SECTION_NODE
)
115 wxString text2
= textChild
->GetContent();
117 // Strip whitespace from end
118 if (text2
.Length() > 0 && text2
[text2
.Length()-1] == wxT('\n'))
119 text2
= text2
.Mid(0, text2
.Length()-1);
121 if (text2
.Length() > 0 && text2
[0] == wxT('"'))
122 text2
= text2
.Mid(1);
123 if (text2
.Length() > 0 && text2
[text2
.Length()-1] == wxT('"'))
124 text2
= text2
.Mid(0, text2
.Length() - 1);
126 // TODO: further entity translation
127 text2
.Replace(wxT("<"), wxT("<"));
128 text2
.Replace(wxT(">"), wxT(">"));
129 text2
.Replace(wxT("&"), wxT("&"));
130 text2
.Replace(wxT("""), wxT("\""));
134 textChild
= textChild
->GetNext();
137 wxRichTextPlainText
* textObject
= new wxRichTextPlainText(text
, para
);
138 GetStyle(textObject
->GetAttributes(), child
, false);
140 para
->AppendChild(textObject
);
142 else if (childName
== wxT("image"))
144 int imageType
= wxBITMAP_TYPE_PNG
;
145 wxString value
= node
->GetPropVal(wxT("imagetype"), wxEmptyString
);
147 imageType
= wxAtoi(value
);
151 wxXmlNode
* imageChild
= child
->GetChildren();
154 wxString childName
= imageChild
->GetName();
155 if (childName
== wxT("data"))
157 wxXmlNode
* dataChild
= imageChild
->GetChildren();
160 data
= dataChild
->GetContent();
162 dataChild
= dataChild
->GetNext();
166 imageChild
= imageChild
->GetNext();
171 wxRichTextImage
* imageObj
= new wxRichTextImage(para
);
172 para
->AppendChild(imageObj
);
174 wxStringInputStream
strStream(data
);
176 imageObj
->GetImageBlock().ReadHex(strStream
, data
.Length(), imageType
);
179 child
= child
->GetNext();
187 wxXmlNode
* child
= node
->GetChildren();
190 ImportXML(buffer
, child
);
191 child
= child
->GetNext();
199 //-----------------------------------------------------------------------------
200 // xml support routines
201 //-----------------------------------------------------------------------------
203 bool wxRichTextXMLHandler::HasParam(wxXmlNode
* node
, const wxString
& param
)
205 return (GetParamNode(node
, param
) != NULL
);
208 wxXmlNode
*wxRichTextXMLHandler::GetParamNode(wxXmlNode
* node
, const wxString
& param
)
210 wxCHECK_MSG(node
, NULL
, wxT("You can't access node data before it was initialized!"));
212 wxXmlNode
*n
= node
->GetChildren();
216 if (n
->GetType() == wxXML_ELEMENT_NODE
&& n
->GetName() == param
)
224 wxString
wxRichTextXMLHandler::GetNodeContent(wxXmlNode
*node
)
227 if (n
== NULL
) return wxEmptyString
;
228 n
= n
->GetChildren();
232 if (n
->GetType() == wxXML_TEXT_NODE
||
233 n
->GetType() == wxXML_CDATA_SECTION_NODE
)
234 return n
->GetContent();
237 return wxEmptyString
;
241 wxString
wxRichTextXMLHandler::GetParamValue(wxXmlNode
*node
, const wxString
& param
)
244 return GetNodeContent(node
);
246 return GetNodeContent(GetParamNode(node
, param
));
249 wxString
wxRichTextXMLHandler::GetText(wxXmlNode
*node
, const wxString
& param
, bool WXUNUSED(translate
))
251 wxXmlNode
*parNode
= GetParamNode(node
, param
);
254 wxString
str1(GetNodeContent(parNode
));
258 // For use with earlier versions of wxWidgets
259 #ifndef WXUNUSED_IN_UNICODE
261 #define WXUNUSED_IN_UNICODE(x) WXUNUSED(x)
263 #define WXUNUSED_IN_UNICODE(x) x
267 // write string to output:
268 inline static void OutputString(wxOutputStream
& stream
, const wxString
& str
,
269 wxMBConv
*WXUNUSED_IN_UNICODE(convMem
) = NULL
, wxMBConv
*convFile
= NULL
)
271 if (str
.empty()) return;
275 const wxWX2MBbuf
buf(str
.mb_str(*convFile
));
276 stream
.Write((const char*)buf
, strlen((const char*)buf
));
280 const wxWX2MBbuf
buf(str
.mb_str(wxConvUTF8
));
281 stream
.Write((const char*)buf
, strlen((const char*)buf
));
284 if ( convFile
== NULL
)
285 stream
.Write(str
.mb_str(), str
.Len());
288 wxString
str2(str
.wc_str(*convMem
), *convFile
);
289 stream
.Write(str2
.mb_str(), str2
.Len());
294 // Same as above, but create entities first.
295 // Translates '<' to "<", '>' to ">" and '&' to "&"
296 static void OutputStringEnt(wxOutputStream
& stream
, const wxString
& str
,
297 wxMBConv
*convMem
= NULL
, wxMBConv
*convFile
= NULL
)
305 for (i
= 0; i
< len
; i
++)
308 if (c
== wxT('<') || c
== wxT('>') || c
== wxT('"') ||
309 (c
== wxT('&') && (str
.Mid(i
+1, 4) != wxT("amp;"))))
311 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
315 OutputString(stream
, wxT("<"), NULL
, NULL
);
318 OutputString(stream
, wxT(">"), NULL
, NULL
);
321 OutputString(stream
, wxT("&"), NULL
, NULL
);
324 OutputString(stream
, wxT("""), NULL
, NULL
);
331 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
334 inline static void OutputIndentation(wxOutputStream
& stream
, int indent
)
336 wxString str
= wxT("\n");
337 for (int i
= 0; i
< indent
; i
++)
338 str
<< wxT(' ') << wxT(' ');
339 OutputString(stream
, str
, NULL
, NULL
);
342 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const wxString
& s
)
344 stream
.Write(s
, s
.Length());
349 static wxOutputStream
& operator <<(wxOutputStream
& stream
, long l
)
352 str
.Printf(wxT("%ld"), l
);
353 return stream
<< str
;
356 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const char c
)
359 str
.Printf(wxT("%c"), c
);
360 return stream
<< str
;
364 // Convert a colour to a 6-digit hex string
365 static wxString
ColourToHexString(const wxColour
& col
)
369 hex
+= wxDecToHex(col
.Red());
370 hex
+= wxDecToHex(col
.Green());
371 hex
+= wxDecToHex(col
.Blue());
376 // Convert 6-digit hex string to a colour
377 wxColour
HexStringToColour(const wxString
& hex
)
379 unsigned char r
= (unsigned char)wxHexToDec(hex
.Mid(0, 2));
380 unsigned char g
= (unsigned char)wxHexToDec(hex
.Mid(2, 2));
381 unsigned char b
= (unsigned char)wxHexToDec(hex
.Mid(4, 2));
383 return wxColour(r
, g
, b
);
386 bool wxRichTextXMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
391 wxString
version(wxT("1.0") ) ;
393 wxString
fileencoding(wxT("UTF-8")) ;
394 wxString
memencoding(wxT("UTF-8")) ;
396 wxString
fileencoding(wxT("ISO-8859-1")) ;
397 wxString
memencoding(wxT("ISO-8859-1")) ;
401 wxMBConv
*convMem
= NULL
, *convFile
= NULL
;
403 convFile
= new wxCSConv(fileencoding
);
405 if ( fileencoding
!= memencoding
)
407 convFile
= new wxCSConv(fileencoding
);
408 convMem
= new wxCSConv(memencoding
);
412 s
.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
413 (const wxChar
*) version
, (const wxChar
*) fileencoding
);
414 OutputString(stream
, s
, NULL
, NULL
);
415 OutputString(stream
, wxT("<richtext version=\"1.0.0.0\" xmlns=\"http://www.wxwidgets.org\">") , NULL
, NULL
);
418 ExportXML(stream
, convMem
, convFile
, *buffer
, level
);
420 OutputString(stream
, wxT("\n</richtext>") , NULL
, NULL
);
421 OutputString(stream
, wxT("\n"), NULL
, NULL
);
429 /// Recursively export an object
430 bool wxRichTextXMLHandler::ExportXML(wxOutputStream
& stream
, wxMBConv
* convMem
, wxMBConv
* convFile
, wxRichTextObject
& obj
, int indent
)
433 if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraphLayoutBox
)))
434 objectName
= wxT("paragraphlayout");
435 else if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraph
)))
436 objectName
= wxT("paragraph");
437 else if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
438 objectName
= wxT("text");
439 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
440 objectName
= wxT("image");
442 objectName
= wxT("object");
444 if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
446 wxRichTextPlainText
& text
= (wxRichTextPlainText
&) obj
;
448 OutputIndentation(stream
, indent
);
449 stream
<< wxT("<") << objectName
;
451 wxString style
= CreateStyle(obj
.GetAttributes(), false);
453 stream
<< style
<< wxT(">");
455 wxString str
= text
.GetText();
456 if (str
.Length() > 0 && (str
[0] == wxT(' ') || str
[str
.Length()-1] == wxT(' ')))
459 OutputStringEnt(stream
, str
, convMem
, convFile
);
463 OutputStringEnt(stream
, str
, convMem
, convFile
);
465 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
467 wxRichTextImage
& imageObj
= (wxRichTextImage
&) obj
;
469 if (imageObj
.GetImage().Ok() && !imageObj
.GetImageBlock().Ok())
470 imageObj
.MakeBlock();
472 OutputIndentation(stream
, indent
);
473 stream
<< wxT("<") << objectName
;
474 if (!imageObj
.GetImageBlock().Ok())
481 stream
<< wxString::Format(wxT(" imagetype=\"%d\""), (int) imageObj
.GetImageBlock().GetImageType()) << wxT(">");
484 OutputIndentation(stream
, indent
+1);
485 stream
<< wxT("<data>");
487 imageObj
.GetImageBlock().WriteHex(stream
);
489 stream
<< wxT("</data>");
491 else if (obj
.IsKindOf(CLASSINFO(wxRichTextCompositeObject
)))
493 OutputIndentation(stream
, indent
);
494 stream
<< wxT("<") << objectName
;
497 if (objectName
== wxT("paragraph") || objectName
== wxT("paragraphlayout"))
500 wxString style
= CreateStyle(obj
.GetAttributes(), isPara
);
502 stream
<< style
<< wxT(">");
504 wxRichTextCompositeObject
& composite
= (wxRichTextCompositeObject
&) obj
;
506 for (i
= 0; i
< composite
.GetChildCount(); i
++)
508 wxRichTextObject
* child
= composite
.GetChild(i
);
509 ExportXML(stream
, convMem
, convFile
, *child
, indent
+1);
513 if (objectName
!= wxT("text"))
514 OutputIndentation(stream
, indent
);
516 stream
<< wxT("</") << objectName
<< wxT(">");
521 /// Create style parameters
522 wxString
wxRichTextXMLHandler::CreateStyle(const wxTextAttrEx
& attr
, bool isPara
)
525 if (attr
.GetTextColour().Ok())
527 str
<< wxT(" textcolor=\"#") << ColourToHexString(attr
.GetTextColour()) << wxT("\"");
529 if (attr
.GetBackgroundColour().Ok())
531 str
<< wxT(" bgcolor=\"#") << ColourToHexString(attr
.GetBackgroundColour()) << wxT("\"");
534 if (attr
.GetFont().Ok())
536 str
<< wxT(" fontsize=\"") << attr
.GetFont().GetPointSize() << wxT("\"");
537 str
<< wxT(" fontfamily=\"") << attr
.GetFont().GetFamily() << wxT("\"");
538 str
<< wxT(" fontstyle=\"") << attr
.GetFont().GetStyle() << wxT("\"");
539 str
<< wxT(" fontweight=\"") << attr
.GetFont().GetWeight() << wxT("\"");
540 str
<< wxT(" fontunderlined=\"") << (int) attr
.GetFont().GetUnderlined() << wxT("\"");
541 str
<< wxT(" fontface=\"") << attr
.GetFont().GetFaceName() << wxT("\"");
544 if (!attr
.GetCharacterStyleName().empty())
545 str
<< wxT(" charactertyle=\"") << wxString(attr
.GetCharacterStyleName()) << wxT("\"");
549 str
<< wxT(" alignment=\"") << (int) attr
.GetAlignment() << wxT("\"");
550 str
<< wxT(" leftindent=\"") << (int) attr
.GetLeftIndent() << wxT("\"");
551 str
<< wxT(" leftsubindent=\"") << (int) attr
.GetLeftSubIndent() << wxT("\"");
552 str
<< wxT(" rightindent=\"") << (int) attr
.GetRightIndent() << wxT("\"");
553 str
<< wxT(" parspacingafter=\"") << (int) attr
.GetParagraphSpacingAfter() << wxT("\"");
554 str
<< wxT(" parspacingbefore=\"") << (int) attr
.GetParagraphSpacingBefore() << wxT("\"");
555 str
<< wxT(" linespacing=\"") << (int) attr
.GetLineSpacing() << wxT("\"");
556 str
<< wxT(" bulletstyle=\"") << (int) attr
.GetBulletStyle() << wxT("\"");
557 str
<< wxT(" bulletnumber=\"") << (int) attr
.GetBulletNumber() << wxT("\"");
558 str
<< wxT(" bulletsymbol=\"") << wxString(attr
.GetBulletSymbol()) << wxT("\"");
560 if (!attr
.GetParagraphStyleName().empty())
561 str
<< wxT(" parstyle=\"") << wxString(attr
.GetParagraphStyleName()) << wxT("\"");
567 /// Get style parameters
568 bool wxRichTextXMLHandler::GetStyle(wxTextAttrEx
& attr
, wxXmlNode
* node
, bool isPara
)
570 wxString fontFacename
;
572 int fontFamily
= wxDEFAULT
;
573 int fontWeight
= wxNORMAL
;
574 int fontStyle
= wxNORMAL
;
575 bool fontUnderlined
= false;
577 fontFacename
= node
->GetPropVal(wxT("fontface"), wxEmptyString
);
579 wxString value
= node
->GetPropVal(wxT("fontfamily"), wxEmptyString
);
581 fontFamily
= wxAtoi(value
);
583 value
= node
->GetPropVal(wxT("fontstyle"), wxEmptyString
);
585 fontStyle
= wxAtoi(value
);
587 value
= node
->GetPropVal(wxT("fontsize"), wxEmptyString
);
589 fontSize
= wxAtoi(value
);
591 value
= node
->GetPropVal(wxT("fontweight"), wxEmptyString
);
593 fontWeight
= wxAtoi(value
);
595 value
= node
->GetPropVal(wxT("fontunderlined"), wxEmptyString
);
597 fontUnderlined
= wxAtoi(value
) != 0;
599 attr
.SetFont(* wxTheFontList
->FindOrCreateFont(fontSize
, fontFamily
, fontStyle
, fontWeight
, fontUnderlined
, fontFacename
));
601 value
= node
->GetPropVal(wxT("textcolor"), wxEmptyString
);
604 if (value
[0] == wxT('#'))
605 attr
.SetTextColour(HexStringToColour(value
.Mid(1)));
607 attr
.SetTextColour(value
);
610 value
= node
->GetPropVal(wxT("backgroundcolor"), wxEmptyString
);
613 if (value
[0] == wxT('#'))
614 attr
.SetBackgroundColour(HexStringToColour(value
.Mid(1)));
616 attr
.SetBackgroundColour(value
);
619 value
= node
->GetPropVal(wxT("characterstyle"), wxEmptyString
);
621 attr
.SetCharacterStyleName(value
);
623 // Set paragraph attributes
626 value
= node
->GetPropVal(wxT("alignment"), wxEmptyString
);
628 attr
.SetAlignment((wxTextAttrAlignment
) wxAtoi(value
));
630 int leftSubIndent
= 0;
632 value
= node
->GetPropVal(wxT("leftindent"), wxEmptyString
);
634 leftIndent
= wxAtoi(value
);
635 value
= node
->GetPropVal(wxT("leftsubindent"), wxEmptyString
);
637 leftSubIndent
= wxAtoi(value
);
638 attr
.SetLeftIndent(leftIndent
, leftSubIndent
);
640 value
= node
->GetPropVal(wxT("rightindent"), wxEmptyString
);
642 attr
.SetRightIndent(wxAtoi(value
));
644 value
= node
->GetPropVal(wxT("parspacingbefore"), wxEmptyString
);
646 attr
.SetParagraphSpacingBefore(wxAtoi(value
));
648 value
= node
->GetPropVal(wxT("parspacingafter"), wxEmptyString
);
650 attr
.SetParagraphSpacingAfter(wxAtoi(value
));
652 value
= node
->GetPropVal(wxT("linespacing"), wxEmptyString
);
654 attr
.SetLineSpacing(wxAtoi(value
));
656 value
= node
->GetPropVal(wxT("bulletstyle"), wxEmptyString
);
658 attr
.SetBulletStyle(wxAtoi(value
));
660 value
= node
->GetPropVal(wxT("bulletnumber"), wxEmptyString
);
662 attr
.SetBulletNumber(wxAtoi(value
));
664 value
= node
->GetPropVal(wxT("bulletsymbol"), wxEmptyString
);
666 attr
.SetBulletSymbol(value
[0]);
668 value
= node
->GetPropVal(wxT("parstyle"), wxEmptyString
);
670 attr
.SetParagraphStyleName(value
);
678 IMPLEMENT_DYNAMIC_CLASS(wxRichTextHTMLHandler
, wxRichTextFileHandler
)
680 /// Can we handle this filename (if using files)? By default, checks the extension.
681 bool wxRichTextHTMLHandler::CanHandle(const wxString
& filename
) const
683 wxString path
, file
, ext
;
684 wxSplitPath(filename
, & path
, & file
, & ext
);
686 return (ext
.Lower() == wxT("html") || ext
.Lower() == wxT("htm"));
691 bool wxRichTextHTMLHandler::DoLoadFile(wxRichTextBuffer
*WXUNUSED(buffer
), wxInputStream
& WXUNUSED(stream
))
697 * We need to output only _changes_ in character formatting.
700 bool wxRichTextHTMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
702 buffer
->Defragment();
704 wxTextOutputStream
str(stream
);
706 wxTextAttrEx currentParaStyle
= buffer
->GetAttributes();
707 wxTextAttrEx currentCharStyle
= buffer
->GetAttributes();
709 str
<< wxT("<html><head></head><body>\n");
711 wxRichTextObjectList::compatibility_iterator node
= buffer
->GetChildren().GetFirst();
714 wxRichTextParagraph
* para
= wxDynamicCast(node
->GetData(), wxRichTextParagraph
);
715 wxASSERT (para
!= NULL
);
719 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, true);
721 wxRichTextObjectList::compatibility_iterator node2
= para
->GetChildren().GetFirst();
724 wxRichTextObject
* obj
= node2
->GetData();
725 wxRichTextPlainText
* textObj
= wxDynamicCast(obj
, wxRichTextPlainText
);
726 if (textObj
&& !textObj
->IsEmpty())
728 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, true);
730 str
<< textObj
->GetText();
732 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, false);
735 node2
= node2
->GetNext();
738 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, false);
743 node
= node
->GetNext();
746 str
<< wxT("</body></html>\n");
751 /// Output character formatting
752 void wxRichTextHTMLHandler::OutputCharacterFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
754 wxTextOutputStream
str(stream
);
757 bool isItalic
= false;
758 bool isUnderline
= false;
761 if (thisStyle
.GetFont().Ok())
763 if (thisStyle
.GetFont().GetWeight() == wxBOLD
)
765 if (thisStyle
.GetFont().GetStyle() == wxITALIC
)
767 if (thisStyle
.GetFont().GetUnderlined())
770 faceName
= thisStyle
.GetFont().GetFaceName();
793 /// Output paragraph formatting
794 void wxRichTextHTMLHandler::OutputParagraphFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
796 // TODO: lists, indentation (using tables), fonts, right-align, ...
798 wxTextOutputStream
str(stream
);
799 bool isCentered
= false;
801 if (thisStyle
.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE
)
809 str
<< wxT("<center>");
814 str
<< wxT("</center>");
821 // wxUSE_RICHTEXT && wxUSE_XML