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 // write string to output:
261 inline static void OutputString(wxOutputStream
& stream
, const wxString
& str
,
262 wxMBConv
*WXUNUSED_IN_UNICODE(convMem
) = NULL
, wxMBConv
*convFile
= NULL
)
264 if (str
.empty()) return;
266 const wxWX2MBbuf
buf(str
.mb_str(convFile
? *convFile
: wxConvUTF8
));
267 stream
.Write((const char*)buf
, strlen((const char*)buf
));
269 if ( convFile
== NULL
)
270 stream
.Write(str
.mb_str(), str
.Len());
273 wxString
str2(str
.wc_str(*convMem
), *convFile
);
274 stream
.Write(str2
.mb_str(), str2
.Len());
279 // Same as above, but create entities first.
280 // Translates '<' to "<", '>' to ">" and '&' to "&"
281 static void OutputStringEnt(wxOutputStream
& stream
, const wxString
& str
,
282 wxMBConv
*convMem
= NULL
, wxMBConv
*convFile
= NULL
)
290 for (i
= 0; i
< len
; i
++)
293 if (c
== wxT('<') || c
== wxT('>') || c
== wxT('"') ||
294 (c
== wxT('&') && (str
.Mid(i
+1, 4) != wxT("amp;"))))
296 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
300 OutputString(stream
, wxT("<"), NULL
, NULL
);
303 OutputString(stream
, wxT(">"), NULL
, NULL
);
306 OutputString(stream
, wxT("&"), NULL
, NULL
);
309 OutputString(stream
, wxT("""), NULL
, NULL
);
316 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
319 inline static void OutputIndentation(wxOutputStream
& stream
, int indent
)
321 wxString str
= wxT("\n");
322 for (int i
= 0; i
< indent
; i
++)
323 str
<< wxT(' ') << wxT(' ');
324 OutputString(stream
, str
, NULL
, NULL
);
327 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const wxString
& s
)
329 stream
.Write(s
, s
.Length());
333 static wxOutputStream
& operator <<(wxOutputStream
& stream
, long l
)
336 str
.Printf(wxT("%ld"), l
);
337 return stream
<< str
;
340 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const char c
)
343 str
.Printf(wxT("%c"), c
);
344 return stream
<< str
;
347 // Convert a colour to a 6-digit hex string
348 static wxString
ColourToHexString(const wxColour
& col
)
352 hex
+= wxDecToHex(col
.Red());
353 hex
+= wxDecToHex(col
.Green());
354 hex
+= wxDecToHex(col
.Blue());
359 // Convert 6-digit hex string to a colour
360 wxColour
HexStringToColour(const wxString
& hex
)
362 unsigned char r
= (unsigned char)wxHexToDec(hex
.Mid(0, 2));
363 unsigned char g
= (unsigned char)wxHexToDec(hex
.Mid(2, 2));
364 unsigned char b
= (unsigned char)wxHexToDec(hex
.Mid(4, 2));
366 return wxColour(r
, g
, b
);
369 bool wxRichTextXMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
374 wxString
version(wxT("1.0") ) ;
376 wxString
fileencoding(wxT("UTF-8")) ;
377 wxString
memencoding(wxT("UTF-8")) ;
379 wxString
fileencoding(wxT("ISO-8859-1")) ;
380 wxString
memencoding(wxT("ISO-8859-1")) ;
384 wxMBConv
*convMem
= NULL
, *convFile
= NULL
;
386 convFile
= new wxCSConv(fileencoding
);
388 if ( fileencoding
!= memencoding
)
390 convFile
= new wxCSConv(fileencoding
);
391 convMem
= new wxCSConv(memencoding
);
395 s
.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
396 (const wxChar
*) version
, (const wxChar
*) fileencoding
);
397 OutputString(stream
, s
, NULL
, NULL
);
398 OutputString(stream
, wxT("<richtext version=\"1.0.0.0\" xmlns=\"http://www.wxwidgets.org\">") , NULL
, NULL
);
401 ExportXML(stream
, convMem
, convFile
, *buffer
, level
);
403 OutputString(stream
, wxT("\n</richtext>") , NULL
, NULL
);
404 OutputString(stream
, wxT("\n"), NULL
, NULL
);
412 /// Recursively export an object
413 bool wxRichTextXMLHandler::ExportXML(wxOutputStream
& stream
, wxMBConv
* convMem
, wxMBConv
* convFile
, wxRichTextObject
& obj
, int indent
)
416 if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraphLayoutBox
)))
417 objectName
= wxT("paragraphlayout");
418 else if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraph
)))
419 objectName
= wxT("paragraph");
420 else if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
421 objectName
= wxT("text");
422 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
423 objectName
= wxT("image");
425 objectName
= wxT("object");
427 if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
429 wxRichTextPlainText
& text
= (wxRichTextPlainText
&) obj
;
431 OutputIndentation(stream
, indent
);
432 stream
<< wxT("<") << objectName
;
434 wxString style
= CreateStyle(obj
.GetAttributes(), false);
436 stream
<< style
<< wxT(">");
438 wxString str
= text
.GetText();
439 if (str
.Length() > 0 && (str
[0] == wxT(' ') || str
[str
.Length()-1] == wxT(' ')))
442 OutputStringEnt(stream
, str
, convMem
, convFile
);
446 OutputStringEnt(stream
, str
, convMem
, convFile
);
448 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
450 wxRichTextImage
& imageObj
= (wxRichTextImage
&) obj
;
452 if (imageObj
.GetImage().Ok() && !imageObj
.GetImageBlock().Ok())
453 imageObj
.MakeBlock();
455 OutputIndentation(stream
, indent
);
456 stream
<< wxT("<") << objectName
;
457 if (!imageObj
.GetImageBlock().Ok())
464 stream
<< wxString::Format(wxT(" imagetype=\"%d\""), (int) imageObj
.GetImageBlock().GetImageType()) << wxT(">");
467 OutputIndentation(stream
, indent
+1);
468 stream
<< wxT("<data>");
470 imageObj
.GetImageBlock().WriteHex(stream
);
472 stream
<< wxT("</data>");
474 else if (obj
.IsKindOf(CLASSINFO(wxRichTextCompositeObject
)))
476 OutputIndentation(stream
, indent
);
477 stream
<< wxT("<") << objectName
;
480 if (objectName
== wxT("paragraph") || objectName
== wxT("paragraphlayout"))
483 wxString style
= CreateStyle(obj
.GetAttributes(), isPara
);
485 stream
<< style
<< wxT(">");
487 wxRichTextCompositeObject
& composite
= (wxRichTextCompositeObject
&) obj
;
489 for (i
= 0; i
< composite
.GetChildCount(); i
++)
491 wxRichTextObject
* child
= composite
.GetChild(i
);
492 ExportXML(stream
, convMem
, convFile
, *child
, indent
+1);
496 if (objectName
!= wxT("text"))
497 OutputIndentation(stream
, indent
);
499 stream
<< wxT("</") << objectName
<< wxT(">");
504 /// Create style parameters
505 wxString
wxRichTextXMLHandler::CreateStyle(const wxTextAttrEx
& attr
, bool isPara
)
508 if (attr
.GetTextColour().Ok())
510 str
<< wxT(" textcolor=\"#") << ColourToHexString(attr
.GetTextColour()) << wxT("\"");
512 if (attr
.GetBackgroundColour().Ok())
514 str
<< wxT(" bgcolor=\"#") << ColourToHexString(attr
.GetBackgroundColour()) << wxT("\"");
517 if (attr
.GetFont().Ok())
519 str
<< wxT(" fontsize=\"") << attr
.GetFont().GetPointSize() << wxT("\"");
520 str
<< wxT(" fontfamily=\"") << attr
.GetFont().GetFamily() << wxT("\"");
521 str
<< wxT(" fontstyle=\"") << attr
.GetFont().GetStyle() << wxT("\"");
522 str
<< wxT(" fontweight=\"") << attr
.GetFont().GetWeight() << wxT("\"");
523 str
<< wxT(" fontunderlined=\"") << (int) attr
.GetFont().GetUnderlined() << wxT("\"");
524 str
<< wxT(" fontface=\"") << attr
.GetFont().GetFaceName() << wxT("\"");
527 if (!attr
.GetCharacterStyleName().empty())
528 str
<< wxT(" charactertyle=\"") << wxString(attr
.GetCharacterStyleName()) << wxT("\"");
532 str
<< wxT(" alignment=\"") << (int) attr
.GetAlignment() << wxT("\"");
533 str
<< wxT(" leftindent=\"") << (int) attr
.GetLeftIndent() << wxT("\"");
534 str
<< wxT(" leftsubindent=\"") << (int) attr
.GetLeftSubIndent() << wxT("\"");
535 str
<< wxT(" rightindent=\"") << (int) attr
.GetRightIndent() << wxT("\"");
536 str
<< wxT(" parspacingafter=\"") << (int) attr
.GetParagraphSpacingAfter() << wxT("\"");
537 str
<< wxT(" parspacingbefore=\"") << (int) attr
.GetParagraphSpacingBefore() << wxT("\"");
538 str
<< wxT(" linespacing=\"") << (int) attr
.GetLineSpacing() << wxT("\"");
539 str
<< wxT(" bulletstyle=\"") << (int) attr
.GetBulletStyle() << wxT("\"");
540 str
<< wxT(" bulletnumber=\"") << (int) attr
.GetBulletNumber() << wxT("\"");
541 str
<< wxT(" bulletsymbol=\"") << wxString(attr
.GetBulletSymbol()) << wxT("\"");
543 if (!attr
.GetParagraphStyleName().empty())
544 str
<< wxT(" parstyle=\"") << wxString(attr
.GetParagraphStyleName()) << wxT("\"");
550 /// Get style parameters
551 bool wxRichTextXMLHandler::GetStyle(wxTextAttrEx
& attr
, wxXmlNode
* node
, bool isPara
)
553 wxString fontFacename
;
555 int fontFamily
= wxDEFAULT
;
556 int fontWeight
= wxNORMAL
;
557 int fontStyle
= wxNORMAL
;
558 bool fontUnderlined
= false;
560 fontFacename
= node
->GetPropVal(wxT("fontface"), wxEmptyString
);
562 wxString value
= node
->GetPropVal(wxT("fontfamily"), wxEmptyString
);
564 fontFamily
= wxAtoi(value
);
566 value
= node
->GetPropVal(wxT("fontstyle"), wxEmptyString
);
568 fontStyle
= wxAtoi(value
);
570 value
= node
->GetPropVal(wxT("fontsize"), wxEmptyString
);
572 fontSize
= wxAtoi(value
);
574 value
= node
->GetPropVal(wxT("fontweight"), wxEmptyString
);
576 fontWeight
= wxAtoi(value
);
578 value
= node
->GetPropVal(wxT("fontunderlined"), wxEmptyString
);
580 fontUnderlined
= wxAtoi(value
) != 0;
582 attr
.SetFont(* wxTheFontList
->FindOrCreateFont(fontSize
, fontFamily
, fontStyle
, fontWeight
, fontUnderlined
, fontFacename
));
584 value
= node
->GetPropVal(wxT("textcolor"), wxEmptyString
);
587 if (value
[0] == wxT('#'))
588 attr
.SetTextColour(HexStringToColour(value
.Mid(1)));
590 attr
.SetTextColour(value
);
593 value
= node
->GetPropVal(wxT("backgroundcolor"), wxEmptyString
);
596 if (value
[0] == wxT('#'))
597 attr
.SetBackgroundColour(HexStringToColour(value
.Mid(1)));
599 attr
.SetBackgroundColour(value
);
602 value
= node
->GetPropVal(wxT("characterstyle"), wxEmptyString
);
604 attr
.SetCharacterStyleName(value
);
606 // Set paragraph attributes
609 value
= node
->GetPropVal(wxT("alignment"), wxEmptyString
);
611 attr
.SetAlignment((wxTextAttrAlignment
) wxAtoi(value
));
613 int leftSubIndent
= 0;
615 value
= node
->GetPropVal(wxT("leftindent"), wxEmptyString
);
617 leftIndent
= wxAtoi(value
);
618 value
= node
->GetPropVal(wxT("leftsubindent"), wxEmptyString
);
620 leftSubIndent
= wxAtoi(value
);
621 attr
.SetLeftIndent(leftIndent
, leftSubIndent
);
623 value
= node
->GetPropVal(wxT("rightindent"), wxEmptyString
);
625 attr
.SetRightIndent(wxAtoi(value
));
627 value
= node
->GetPropVal(wxT("parspacingbefore"), wxEmptyString
);
629 attr
.SetParagraphSpacingBefore(wxAtoi(value
));
631 value
= node
->GetPropVal(wxT("parspacingafter"), wxEmptyString
);
633 attr
.SetParagraphSpacingAfter(wxAtoi(value
));
635 value
= node
->GetPropVal(wxT("linespacing"), wxEmptyString
);
637 attr
.SetLineSpacing(wxAtoi(value
));
639 value
= node
->GetPropVal(wxT("bulletstyle"), wxEmptyString
);
641 attr
.SetBulletStyle(wxAtoi(value
));
643 value
= node
->GetPropVal(wxT("bulletnumber"), wxEmptyString
);
645 attr
.SetBulletNumber(wxAtoi(value
));
647 value
= node
->GetPropVal(wxT("bulletsymbol"), wxEmptyString
);
649 attr
.SetBulletSymbol(value
[0]);
651 value
= node
->GetPropVal(wxT("parstyle"), wxEmptyString
);
653 attr
.SetParagraphStyleName(value
);
661 IMPLEMENT_DYNAMIC_CLASS(wxRichTextHTMLHandler
, wxRichTextFileHandler
)
663 /// Can we handle this filename (if using files)? By default, checks the extension.
664 bool wxRichTextHTMLHandler::CanHandle(const wxString
& filename
) const
666 wxString path
, file
, ext
;
667 wxSplitPath(filename
, & path
, & file
, & ext
);
669 return (ext
.Lower() == wxT("html") || ext
.Lower() == wxT("htm"));
674 bool wxRichTextHTMLHandler::DoLoadFile(wxRichTextBuffer
*WXUNUSED(buffer
), wxInputStream
& WXUNUSED(stream
))
680 * We need to output only _changes_ in character formatting.
683 bool wxRichTextHTMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
685 buffer
->Defragment();
687 wxTextOutputStream
str(stream
);
689 wxTextAttrEx currentParaStyle
= buffer
->GetAttributes();
690 wxTextAttrEx currentCharStyle
= buffer
->GetAttributes();
692 str
<< wxT("<html><head></head><body>\n");
694 wxRichTextObjectList::compatibility_iterator node
= buffer
->GetChildren().GetFirst();
697 wxRichTextParagraph
* para
= wxDynamicCast(node
->GetData(), wxRichTextParagraph
);
698 wxASSERT (para
!= NULL
);
702 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, true);
704 wxRichTextObjectList::compatibility_iterator node2
= para
->GetChildren().GetFirst();
707 wxRichTextObject
* obj
= node2
->GetData();
708 wxRichTextPlainText
* textObj
= wxDynamicCast(obj
, wxRichTextPlainText
);
709 if (textObj
&& !textObj
->IsEmpty())
711 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, true);
713 str
<< textObj
->GetText();
715 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, false);
718 node2
= node2
->GetNext();
721 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, false);
726 node
= node
->GetNext();
729 str
<< wxT("</body></html>\n");
734 /// Output character formatting
735 void wxRichTextHTMLHandler::OutputCharacterFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
737 wxTextOutputStream
str(stream
);
740 bool isItalic
= false;
741 bool isUnderline
= false;
744 if (thisStyle
.GetFont().Ok())
746 if (thisStyle
.GetFont().GetWeight() == wxBOLD
)
748 if (thisStyle
.GetFont().GetStyle() == wxITALIC
)
750 if (thisStyle
.GetFont().GetUnderlined())
753 faceName
= thisStyle
.GetFont().GetFaceName();
776 /// Output paragraph formatting
777 void wxRichTextHTMLHandler::OutputParagraphFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
779 // TODO: lists, indentation (using tables), fonts, right-align, ...
781 wxTextOutputStream
str(stream
);
782 bool isCentered
= false;
784 if (thisStyle
.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE
)
792 str
<< wxT("<center>");
797 str
<< wxT("</center>");