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;
275 const wxWX2MBbuf
buf(str
.mb_str(convFile
? *convFile
: wxConvUTF8
));
276 stream
.Write((const char*)buf
, strlen((const char*)buf
));
278 if ( convFile
== NULL
)
279 stream
.Write(str
.mb_str(), str
.Len());
282 wxString
str2(str
.wc_str(*convMem
), *convFile
);
283 stream
.Write(str2
.mb_str(), str2
.Len());
288 // Same as above, but create entities first.
289 // Translates '<' to "<", '>' to ">" and '&' to "&"
290 static void OutputStringEnt(wxOutputStream
& stream
, const wxString
& str
,
291 wxMBConv
*convMem
= NULL
, wxMBConv
*convFile
= NULL
)
299 for (i
= 0; i
< len
; i
++)
302 if (c
== wxT('<') || c
== wxT('>') || c
== wxT('"') ||
303 (c
== wxT('&') && (str
.Mid(i
+1, 4) != wxT("amp;"))))
305 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
309 OutputString(stream
, wxT("<"), NULL
, NULL
);
312 OutputString(stream
, wxT(">"), NULL
, NULL
);
315 OutputString(stream
, wxT("&"), NULL
, NULL
);
318 OutputString(stream
, wxT("""), NULL
, NULL
);
325 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
328 inline static void OutputIndentation(wxOutputStream
& stream
, int indent
)
330 wxString str
= wxT("\n");
331 for (int i
= 0; i
< indent
; i
++)
332 str
<< wxT(' ') << wxT(' ');
333 OutputString(stream
, str
, NULL
, NULL
);
336 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const wxString
& s
)
338 stream
.Write(s
, s
.Length());
343 static wxOutputStream
& operator <<(wxOutputStream
& stream
, long l
)
346 str
.Printf(wxT("%ld"), l
);
347 return stream
<< str
;
350 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const char c
)
353 str
.Printf(wxT("%c"), c
);
354 return stream
<< str
;
358 // Convert a colour to a 6-digit hex string
359 static wxString
ColourToHexString(const wxColour
& col
)
363 hex
+= wxDecToHex(col
.Red());
364 hex
+= wxDecToHex(col
.Green());
365 hex
+= wxDecToHex(col
.Blue());
370 // Convert 6-digit hex string to a colour
371 wxColour
HexStringToColour(const wxString
& hex
)
373 unsigned char r
= (unsigned char)wxHexToDec(hex
.Mid(0, 2));
374 unsigned char g
= (unsigned char)wxHexToDec(hex
.Mid(2, 2));
375 unsigned char b
= (unsigned char)wxHexToDec(hex
.Mid(4, 2));
377 return wxColour(r
, g
, b
);
380 bool wxRichTextXMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
385 wxString
version(wxT("1.0") ) ;
387 wxString
fileencoding(wxT("UTF-8")) ;
388 wxString
memencoding(wxT("UTF-8")) ;
390 wxString
fileencoding(wxT("ISO-8859-1")) ;
391 wxString
memencoding(wxT("ISO-8859-1")) ;
395 wxMBConv
*convMem
= NULL
, *convFile
= NULL
;
397 convFile
= new wxCSConv(fileencoding
);
399 if ( fileencoding
!= memencoding
)
401 convFile
= new wxCSConv(fileencoding
);
402 convMem
= new wxCSConv(memencoding
);
406 s
.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
407 (const wxChar
*) version
, (const wxChar
*) fileencoding
);
408 OutputString(stream
, s
, NULL
, NULL
);
409 OutputString(stream
, wxT("<richtext version=\"1.0.0.0\" xmlns=\"http://www.wxwidgets.org\">") , NULL
, NULL
);
412 ExportXML(stream
, convMem
, convFile
, *buffer
, level
);
414 OutputString(stream
, wxT("\n</richtext>") , NULL
, NULL
);
415 OutputString(stream
, wxT("\n"), NULL
, NULL
);
423 /// Recursively export an object
424 bool wxRichTextXMLHandler::ExportXML(wxOutputStream
& stream
, wxMBConv
* convMem
, wxMBConv
* convFile
, wxRichTextObject
& obj
, int indent
)
427 if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraphLayoutBox
)))
428 objectName
= wxT("paragraphlayout");
429 else if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraph
)))
430 objectName
= wxT("paragraph");
431 else if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
432 objectName
= wxT("text");
433 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
434 objectName
= wxT("image");
436 objectName
= wxT("object");
438 if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
440 wxRichTextPlainText
& text
= (wxRichTextPlainText
&) obj
;
442 OutputIndentation(stream
, indent
);
443 stream
<< wxT("<") << objectName
;
445 wxString style
= CreateStyle(obj
.GetAttributes(), false);
447 stream
<< style
<< wxT(">");
449 wxString str
= text
.GetText();
450 if (str
.Length() > 0 && (str
[0] == wxT(' ') || str
[str
.Length()-1] == wxT(' ')))
453 OutputStringEnt(stream
, str
, convMem
, convFile
);
457 OutputStringEnt(stream
, str
, convMem
, convFile
);
459 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
461 wxRichTextImage
& imageObj
= (wxRichTextImage
&) obj
;
463 if (imageObj
.GetImage().Ok() && !imageObj
.GetImageBlock().Ok())
464 imageObj
.MakeBlock();
466 OutputIndentation(stream
, indent
);
467 stream
<< wxT("<") << objectName
;
468 if (!imageObj
.GetImageBlock().Ok())
475 stream
<< wxString::Format(wxT(" imagetype=\"%d\""), (int) imageObj
.GetImageBlock().GetImageType()) << wxT(">");
478 OutputIndentation(stream
, indent
+1);
479 stream
<< wxT("<data>");
481 imageObj
.GetImageBlock().WriteHex(stream
);
483 stream
<< wxT("</data>");
485 else if (obj
.IsKindOf(CLASSINFO(wxRichTextCompositeObject
)))
487 OutputIndentation(stream
, indent
);
488 stream
<< wxT("<") << objectName
;
491 if (objectName
== wxT("paragraph") || objectName
== wxT("paragraphlayout"))
494 wxString style
= CreateStyle(obj
.GetAttributes(), isPara
);
496 stream
<< style
<< wxT(">");
498 wxRichTextCompositeObject
& composite
= (wxRichTextCompositeObject
&) obj
;
500 for (i
= 0; i
< composite
.GetChildCount(); i
++)
502 wxRichTextObject
* child
= composite
.GetChild(i
);
503 ExportXML(stream
, convMem
, convFile
, *child
, indent
+1);
507 if (objectName
!= wxT("text"))
508 OutputIndentation(stream
, indent
);
510 stream
<< wxT("</") << objectName
<< wxT(">");
515 /// Create style parameters
516 wxString
wxRichTextXMLHandler::CreateStyle(const wxTextAttrEx
& attr
, bool isPara
)
519 if (attr
.GetTextColour().Ok())
521 str
<< wxT(" textcolor=\"#") << ColourToHexString(attr
.GetTextColour()) << wxT("\"");
523 if (attr
.GetBackgroundColour().Ok())
525 str
<< wxT(" bgcolor=\"#") << ColourToHexString(attr
.GetBackgroundColour()) << wxT("\"");
528 if (attr
.GetFont().Ok())
530 str
<< wxT(" fontsize=\"") << attr
.GetFont().GetPointSize() << wxT("\"");
531 str
<< wxT(" fontfamily=\"") << attr
.GetFont().GetFamily() << wxT("\"");
532 str
<< wxT(" fontstyle=\"") << attr
.GetFont().GetStyle() << wxT("\"");
533 str
<< wxT(" fontweight=\"") << attr
.GetFont().GetWeight() << wxT("\"");
534 str
<< wxT(" fontunderlined=\"") << (int) attr
.GetFont().GetUnderlined() << wxT("\"");
535 str
<< wxT(" fontface=\"") << attr
.GetFont().GetFaceName() << wxT("\"");
538 if (!attr
.GetCharacterStyleName().empty())
539 str
<< wxT(" charactertyle=\"") << wxString(attr
.GetCharacterStyleName()) << wxT("\"");
543 str
<< wxT(" alignment=\"") << (int) attr
.GetAlignment() << wxT("\"");
544 str
<< wxT(" leftindent=\"") << (int) attr
.GetLeftIndent() << wxT("\"");
545 str
<< wxT(" leftsubindent=\"") << (int) attr
.GetLeftSubIndent() << wxT("\"");
546 str
<< wxT(" rightindent=\"") << (int) attr
.GetRightIndent() << wxT("\"");
547 str
<< wxT(" parspacingafter=\"") << (int) attr
.GetParagraphSpacingAfter() << wxT("\"");
548 str
<< wxT(" parspacingbefore=\"") << (int) attr
.GetParagraphSpacingBefore() << wxT("\"");
549 str
<< wxT(" linespacing=\"") << (int) attr
.GetLineSpacing() << wxT("\"");
550 str
<< wxT(" bulletstyle=\"") << (int) attr
.GetBulletStyle() << wxT("\"");
551 str
<< wxT(" bulletnumber=\"") << (int) attr
.GetBulletNumber() << wxT("\"");
552 str
<< wxT(" bulletsymbol=\"") << wxString(attr
.GetBulletSymbol()) << wxT("\"");
554 if (!attr
.GetParagraphStyleName().empty())
555 str
<< wxT(" parstyle=\"") << wxString(attr
.GetParagraphStyleName()) << wxT("\"");
561 /// Get style parameters
562 bool wxRichTextXMLHandler::GetStyle(wxTextAttrEx
& attr
, wxXmlNode
* node
, bool isPara
)
564 wxString fontFacename
;
566 int fontFamily
= wxDEFAULT
;
567 int fontWeight
= wxNORMAL
;
568 int fontStyle
= wxNORMAL
;
569 bool fontUnderlined
= false;
571 fontFacename
= node
->GetPropVal(wxT("fontface"), wxEmptyString
);
573 wxString value
= node
->GetPropVal(wxT("fontfamily"), wxEmptyString
);
575 fontFamily
= wxAtoi(value
);
577 value
= node
->GetPropVal(wxT("fontstyle"), wxEmptyString
);
579 fontStyle
= wxAtoi(value
);
581 value
= node
->GetPropVal(wxT("fontsize"), wxEmptyString
);
583 fontSize
= wxAtoi(value
);
585 value
= node
->GetPropVal(wxT("fontweight"), wxEmptyString
);
587 fontWeight
= wxAtoi(value
);
589 value
= node
->GetPropVal(wxT("fontunderlined"), wxEmptyString
);
591 fontUnderlined
= wxAtoi(value
) != 0;
593 attr
.SetFont(* wxTheFontList
->FindOrCreateFont(fontSize
, fontFamily
, fontStyle
, fontWeight
, fontUnderlined
, fontFacename
));
595 value
= node
->GetPropVal(wxT("textcolor"), wxEmptyString
);
598 if (value
[0] == wxT('#'))
599 attr
.SetTextColour(HexStringToColour(value
.Mid(1)));
601 attr
.SetTextColour(value
);
604 value
= node
->GetPropVal(wxT("backgroundcolor"), wxEmptyString
);
607 if (value
[0] == wxT('#'))
608 attr
.SetBackgroundColour(HexStringToColour(value
.Mid(1)));
610 attr
.SetBackgroundColour(value
);
613 value
= node
->GetPropVal(wxT("characterstyle"), wxEmptyString
);
615 attr
.SetCharacterStyleName(value
);
617 // Set paragraph attributes
620 value
= node
->GetPropVal(wxT("alignment"), wxEmptyString
);
622 attr
.SetAlignment((wxTextAttrAlignment
) wxAtoi(value
));
624 int leftSubIndent
= 0;
626 value
= node
->GetPropVal(wxT("leftindent"), wxEmptyString
);
628 leftIndent
= wxAtoi(value
);
629 value
= node
->GetPropVal(wxT("leftsubindent"), wxEmptyString
);
631 leftSubIndent
= wxAtoi(value
);
632 attr
.SetLeftIndent(leftIndent
, leftSubIndent
);
634 value
= node
->GetPropVal(wxT("rightindent"), wxEmptyString
);
636 attr
.SetRightIndent(wxAtoi(value
));
638 value
= node
->GetPropVal(wxT("parspacingbefore"), wxEmptyString
);
640 attr
.SetParagraphSpacingBefore(wxAtoi(value
));
642 value
= node
->GetPropVal(wxT("parspacingafter"), wxEmptyString
);
644 attr
.SetParagraphSpacingAfter(wxAtoi(value
));
646 value
= node
->GetPropVal(wxT("linespacing"), wxEmptyString
);
648 attr
.SetLineSpacing(wxAtoi(value
));
650 value
= node
->GetPropVal(wxT("bulletstyle"), wxEmptyString
);
652 attr
.SetBulletStyle(wxAtoi(value
));
654 value
= node
->GetPropVal(wxT("bulletnumber"), wxEmptyString
);
656 attr
.SetBulletNumber(wxAtoi(value
));
658 value
= node
->GetPropVal(wxT("bulletsymbol"), wxEmptyString
);
660 attr
.SetBulletSymbol(value
[0]);
662 value
= node
->GetPropVal(wxT("parstyle"), wxEmptyString
);
664 attr
.SetParagraphStyleName(value
);
672 IMPLEMENT_DYNAMIC_CLASS(wxRichTextHTMLHandler
, wxRichTextFileHandler
)
674 /// Can we handle this filename (if using files)? By default, checks the extension.
675 bool wxRichTextHTMLHandler::CanHandle(const wxString
& filename
) const
677 wxString path
, file
, ext
;
678 wxSplitPath(filename
, & path
, & file
, & ext
);
680 return (ext
.Lower() == wxT("html") || ext
.Lower() == wxT("htm"));
685 bool wxRichTextHTMLHandler::DoLoadFile(wxRichTextBuffer
*WXUNUSED(buffer
), wxInputStream
& WXUNUSED(stream
))
691 * We need to output only _changes_ in character formatting.
694 bool wxRichTextHTMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
696 buffer
->Defragment();
698 wxTextOutputStream
str(stream
);
700 wxTextAttrEx currentParaStyle
= buffer
->GetAttributes();
701 wxTextAttrEx currentCharStyle
= buffer
->GetAttributes();
703 str
<< wxT("<html><head></head><body>\n");
705 wxRichTextObjectList::compatibility_iterator node
= buffer
->GetChildren().GetFirst();
708 wxRichTextParagraph
* para
= wxDynamicCast(node
->GetData(), wxRichTextParagraph
);
709 wxASSERT (para
!= NULL
);
713 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, true);
715 wxRichTextObjectList::compatibility_iterator node2
= para
->GetChildren().GetFirst();
718 wxRichTextObject
* obj
= node2
->GetData();
719 wxRichTextPlainText
* textObj
= wxDynamicCast(obj
, wxRichTextPlainText
);
720 if (textObj
&& !textObj
->IsEmpty())
722 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, true);
724 str
<< textObj
->GetText();
726 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, false);
729 node2
= node2
->GetNext();
732 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, false);
737 node
= node
->GetNext();
740 str
<< wxT("</body></html>\n");
745 /// Output character formatting
746 void wxRichTextHTMLHandler::OutputCharacterFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
748 wxTextOutputStream
str(stream
);
751 bool isItalic
= false;
752 bool isUnderline
= false;
755 if (thisStyle
.GetFont().Ok())
757 if (thisStyle
.GetFont().GetWeight() == wxBOLD
)
759 if (thisStyle
.GetFont().GetStyle() == wxITALIC
)
761 if (thisStyle
.GetFont().GetUnderlined())
764 faceName
= thisStyle
.GetFont().GetFaceName();
787 /// Output paragraph formatting
788 void wxRichTextHTMLHandler::OutputParagraphFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
790 // TODO: lists, indentation (using tables), fonts, right-align, ...
792 wxTextOutputStream
str(stream
);
793 bool isCentered
= false;
795 if (thisStyle
.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE
)
803 str
<< wxT("<center>");
808 str
<< wxT("</center>");