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());
342 static wxOutputStream
& operator <<(wxOutputStream
& stream
, long l
)
345 str
.Printf(wxT("%ld"), l
);
346 return stream
<< str
;
349 static wxOutputStream
& operator <<(wxOutputStream
& stream
, const char c
)
352 str
.Printf(wxT("%c"), c
);
353 return stream
<< str
;
356 // Convert a colour to a 6-digit hex string
357 static wxString
ColourToHexString(const wxColour
& col
)
361 hex
+= wxDecToHex(col
.Red());
362 hex
+= wxDecToHex(col
.Green());
363 hex
+= wxDecToHex(col
.Blue());
368 // Convert 6-digit hex string to a colour
369 wxColour
HexStringToColour(const wxString
& hex
)
371 unsigned char r
= (unsigned char)wxHexToDec(hex
.Mid(0, 2));
372 unsigned char g
= (unsigned char)wxHexToDec(hex
.Mid(2, 2));
373 unsigned char b
= (unsigned char)wxHexToDec(hex
.Mid(4, 2));
375 return wxColour(r
, g
, b
);
378 bool wxRichTextXMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
383 wxString
version(wxT("1.0") ) ;
385 wxString
fileencoding(wxT("UTF-8")) ;
386 wxString
memencoding(wxT("UTF-8")) ;
388 wxString
fileencoding(wxT("ISO-8859-1")) ;
389 wxString
memencoding(wxT("ISO-8859-1")) ;
393 wxMBConv
*convMem
= NULL
, *convFile
= NULL
;
395 convFile
= new wxCSConv(fileencoding
);
397 if ( fileencoding
!= memencoding
)
399 convFile
= new wxCSConv(fileencoding
);
400 convMem
= new wxCSConv(memencoding
);
404 s
.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
405 (const wxChar
*) version
, (const wxChar
*) fileencoding
);
406 OutputString(stream
, s
, NULL
, NULL
);
407 OutputString(stream
, wxT("<richtext version=\"1.0.0.0\" xmlns=\"http://www.wxwidgets.org\">") , NULL
, NULL
);
410 ExportXML(stream
, convMem
, convFile
, *buffer
, level
);
412 OutputString(stream
, wxT("\n</richtext>") , NULL
, NULL
);
413 OutputString(stream
, wxT("\n"), NULL
, NULL
);
421 /// Recursively export an object
422 bool wxRichTextXMLHandler::ExportXML(wxOutputStream
& stream
, wxMBConv
* convMem
, wxMBConv
* convFile
, wxRichTextObject
& obj
, int indent
)
425 if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraphLayoutBox
)))
426 objectName
= wxT("paragraphlayout");
427 else if (obj
.IsKindOf(CLASSINFO(wxRichTextParagraph
)))
428 objectName
= wxT("paragraph");
429 else if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
430 objectName
= wxT("text");
431 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
432 objectName
= wxT("image");
434 objectName
= wxT("object");
436 if (obj
.IsKindOf(CLASSINFO(wxRichTextPlainText
)))
438 wxRichTextPlainText
& text
= (wxRichTextPlainText
&) obj
;
440 OutputIndentation(stream
, indent
);
441 stream
<< wxT("<") << objectName
;
443 wxString style
= CreateStyle(obj
.GetAttributes(), false);
445 stream
<< style
<< wxT(">");
447 wxString str
= text
.GetText();
448 if (str
.Length() > 0 && (str
[0] == wxT(' ') || str
[str
.Length()-1] == wxT(' ')))
451 OutputStringEnt(stream
, str
, convMem
, convFile
);
455 OutputStringEnt(stream
, str
, convMem
, convFile
);
457 else if (obj
.IsKindOf(CLASSINFO(wxRichTextImage
)))
459 wxRichTextImage
& imageObj
= (wxRichTextImage
&) obj
;
461 if (imageObj
.GetImage().Ok() && !imageObj
.GetImageBlock().Ok())
462 imageObj
.MakeBlock();
464 OutputIndentation(stream
, indent
);
465 stream
<< wxT("<") << objectName
;
466 if (!imageObj
.GetImageBlock().Ok())
473 stream
<< wxString::Format(wxT(" imagetype=\"%d\""), (int) imageObj
.GetImageBlock().GetImageType()) << wxT(">");
476 OutputIndentation(stream
, indent
+1);
477 stream
<< wxT("<data>");
479 imageObj
.GetImageBlock().WriteHex(stream
);
481 stream
<< wxT("</data>");
483 else if (obj
.IsKindOf(CLASSINFO(wxRichTextCompositeObject
)))
485 OutputIndentation(stream
, indent
);
486 stream
<< wxT("<") << objectName
;
489 if (objectName
== wxT("paragraph") || objectName
== wxT("paragraphlayout"))
492 wxString style
= CreateStyle(obj
.GetAttributes(), isPara
);
494 stream
<< style
<< wxT(">");
496 wxRichTextCompositeObject
& composite
= (wxRichTextCompositeObject
&) obj
;
498 for (i
= 0; i
< composite
.GetChildCount(); i
++)
500 wxRichTextObject
* child
= composite
.GetChild(i
);
501 ExportXML(stream
, convMem
, convFile
, *child
, indent
+1);
505 if (objectName
!= wxT("text"))
506 OutputIndentation(stream
, indent
);
508 stream
<< wxT("</") << objectName
<< wxT(">");
513 /// Create style parameters
514 wxString
wxRichTextXMLHandler::CreateStyle(const wxTextAttrEx
& attr
, bool isPara
)
517 if (attr
.GetTextColour().Ok())
519 str
<< wxT(" textcolor=\"#") << ColourToHexString(attr
.GetTextColour()) << wxT("\"");
521 if (attr
.GetBackgroundColour().Ok())
523 str
<< wxT(" bgcolor=\"#") << ColourToHexString(attr
.GetBackgroundColour()) << wxT("\"");
526 if (attr
.GetFont().Ok())
528 str
<< wxT(" fontsize=\"") << attr
.GetFont().GetPointSize() << wxT("\"");
529 str
<< wxT(" fontfamily=\"") << attr
.GetFont().GetFamily() << wxT("\"");
530 str
<< wxT(" fontstyle=\"") << attr
.GetFont().GetStyle() << wxT("\"");
531 str
<< wxT(" fontweight=\"") << attr
.GetFont().GetWeight() << wxT("\"");
532 str
<< wxT(" fontunderlined=\"") << (int) attr
.GetFont().GetUnderlined() << wxT("\"");
533 str
<< wxT(" fontface=\"") << attr
.GetFont().GetFaceName() << wxT("\"");
536 if (!attr
.GetCharacterStyleName().empty())
537 str
<< wxT(" charactertyle=\"") << wxString(attr
.GetCharacterStyleName()) << wxT("\"");
541 str
<< wxT(" alignment=\"") << (int) attr
.GetAlignment() << wxT("\"");
542 str
<< wxT(" leftindent=\"") << (int) attr
.GetLeftIndent() << wxT("\"");
543 str
<< wxT(" leftsubindent=\"") << (int) attr
.GetLeftSubIndent() << wxT("\"");
544 str
<< wxT(" rightindent=\"") << (int) attr
.GetRightIndent() << wxT("\"");
545 str
<< wxT(" parspacingafter=\"") << (int) attr
.GetParagraphSpacingAfter() << wxT("\"");
546 str
<< wxT(" parspacingbefore=\"") << (int) attr
.GetParagraphSpacingBefore() << wxT("\"");
547 str
<< wxT(" linespacing=\"") << (int) attr
.GetLineSpacing() << wxT("\"");
548 str
<< wxT(" bulletstyle=\"") << (int) attr
.GetBulletStyle() << wxT("\"");
549 str
<< wxT(" bulletnumber=\"") << (int) attr
.GetBulletNumber() << wxT("\"");
550 str
<< wxT(" bulletsymbol=\"") << wxString(attr
.GetBulletSymbol()) << wxT("\"");
552 if (!attr
.GetParagraphStyleName().empty())
553 str
<< wxT(" parstyle=\"") << wxString(attr
.GetParagraphStyleName()) << wxT("\"");
559 /// Get style parameters
560 bool wxRichTextXMLHandler::GetStyle(wxTextAttrEx
& attr
, wxXmlNode
* node
, bool isPara
)
562 wxString fontFacename
;
564 int fontFamily
= wxDEFAULT
;
565 int fontWeight
= wxNORMAL
;
566 int fontStyle
= wxNORMAL
;
567 bool fontUnderlined
= false;
569 fontFacename
= node
->GetPropVal(wxT("fontface"), wxEmptyString
);
571 wxString value
= node
->GetPropVal(wxT("fontfamily"), wxEmptyString
);
573 fontFamily
= wxAtoi(value
);
575 value
= node
->GetPropVal(wxT("fontstyle"), wxEmptyString
);
577 fontStyle
= wxAtoi(value
);
579 value
= node
->GetPropVal(wxT("fontsize"), wxEmptyString
);
581 fontSize
= wxAtoi(value
);
583 value
= node
->GetPropVal(wxT("fontweight"), wxEmptyString
);
585 fontWeight
= wxAtoi(value
);
587 value
= node
->GetPropVal(wxT("fontunderlined"), wxEmptyString
);
589 fontUnderlined
= wxAtoi(value
) != 0;
591 attr
.SetFont(* wxTheFontList
->FindOrCreateFont(fontSize
, fontFamily
, fontStyle
, fontWeight
, fontUnderlined
, fontFacename
));
593 value
= node
->GetPropVal(wxT("textcolor"), wxEmptyString
);
596 if (value
[0] == wxT('#'))
597 attr
.SetTextColour(HexStringToColour(value
.Mid(1)));
599 attr
.SetTextColour(value
);
602 value
= node
->GetPropVal(wxT("backgroundcolor"), wxEmptyString
);
605 if (value
[0] == wxT('#'))
606 attr
.SetBackgroundColour(HexStringToColour(value
.Mid(1)));
608 attr
.SetBackgroundColour(value
);
611 value
= node
->GetPropVal(wxT("characterstyle"), wxEmptyString
);
613 attr
.SetCharacterStyleName(value
);
615 // Set paragraph attributes
618 value
= node
->GetPropVal(wxT("alignment"), wxEmptyString
);
620 attr
.SetAlignment((wxTextAttrAlignment
) wxAtoi(value
));
622 int leftSubIndent
= 0;
624 value
= node
->GetPropVal(wxT("leftindent"), wxEmptyString
);
626 leftIndent
= wxAtoi(value
);
627 value
= node
->GetPropVal(wxT("leftsubindent"), wxEmptyString
);
629 leftSubIndent
= wxAtoi(value
);
630 attr
.SetLeftIndent(leftIndent
, leftSubIndent
);
632 value
= node
->GetPropVal(wxT("rightindent"), wxEmptyString
);
634 attr
.SetRightIndent(wxAtoi(value
));
636 value
= node
->GetPropVal(wxT("parspacingbefore"), wxEmptyString
);
638 attr
.SetParagraphSpacingBefore(wxAtoi(value
));
640 value
= node
->GetPropVal(wxT("parspacingafter"), wxEmptyString
);
642 attr
.SetParagraphSpacingAfter(wxAtoi(value
));
644 value
= node
->GetPropVal(wxT("linespacing"), wxEmptyString
);
646 attr
.SetLineSpacing(wxAtoi(value
));
648 value
= node
->GetPropVal(wxT("bulletstyle"), wxEmptyString
);
650 attr
.SetBulletStyle(wxAtoi(value
));
652 value
= node
->GetPropVal(wxT("bulletnumber"), wxEmptyString
);
654 attr
.SetBulletNumber(wxAtoi(value
));
656 value
= node
->GetPropVal(wxT("bulletsymbol"), wxEmptyString
);
658 attr
.SetBulletSymbol(value
[0]);
660 value
= node
->GetPropVal(wxT("parstyle"), wxEmptyString
);
662 attr
.SetParagraphStyleName(value
);
670 IMPLEMENT_DYNAMIC_CLASS(wxRichTextHTMLHandler
, wxRichTextFileHandler
)
672 /// Can we handle this filename (if using files)? By default, checks the extension.
673 bool wxRichTextHTMLHandler::CanHandle(const wxString
& filename
) const
675 wxString path
, file
, ext
;
676 wxSplitPath(filename
, & path
, & file
, & ext
);
678 return (ext
.Lower() == wxT("html") || ext
.Lower() == wxT("htm"));
683 bool wxRichTextHTMLHandler::DoLoadFile(wxRichTextBuffer
*WXUNUSED(buffer
), wxInputStream
& WXUNUSED(stream
))
689 * We need to output only _changes_ in character formatting.
692 bool wxRichTextHTMLHandler::DoSaveFile(wxRichTextBuffer
*buffer
, wxOutputStream
& stream
)
694 buffer
->Defragment();
696 wxTextOutputStream
str(stream
);
698 wxTextAttrEx currentParaStyle
= buffer
->GetAttributes();
699 wxTextAttrEx currentCharStyle
= buffer
->GetAttributes();
701 str
<< wxT("<html><head></head><body>\n");
703 wxRichTextObjectList::compatibility_iterator node
= buffer
->GetChildren().GetFirst();
706 wxRichTextParagraph
* para
= wxDynamicCast(node
->GetData(), wxRichTextParagraph
);
707 wxASSERT (para
!= NULL
);
711 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, true);
713 wxRichTextObjectList::compatibility_iterator node2
= para
->GetChildren().GetFirst();
716 wxRichTextObject
* obj
= node2
->GetData();
717 wxRichTextPlainText
* textObj
= wxDynamicCast(obj
, wxRichTextPlainText
);
718 if (textObj
&& !textObj
->IsEmpty())
720 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, true);
722 str
<< textObj
->GetText();
724 OutputCharacterFormatting(currentCharStyle
, obj
->GetAttributes(), stream
, false);
727 node2
= node2
->GetNext();
730 OutputParagraphFormatting(currentParaStyle
, para
->GetAttributes(), stream
, false);
735 node
= node
->GetNext();
738 str
<< wxT("</body></html>\n");
743 /// Output character formatting
744 void wxRichTextHTMLHandler::OutputCharacterFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
746 wxTextOutputStream
str(stream
);
749 bool isItalic
= false;
750 bool isUnderline
= false;
753 if (thisStyle
.GetFont().Ok())
755 if (thisStyle
.GetFont().GetWeight() == wxBOLD
)
757 if (thisStyle
.GetFont().GetStyle() == wxITALIC
)
759 if (thisStyle
.GetFont().GetUnderlined())
762 faceName
= thisStyle
.GetFont().GetFaceName();
785 /// Output paragraph formatting
786 void wxRichTextHTMLHandler::OutputParagraphFormatting(const wxTextAttrEx
& WXUNUSED(currentStyle
), const wxTextAttrEx
& thisStyle
, wxOutputStream
& stream
, bool start
)
788 // TODO: lists, indentation (using tables), fonts, right-align, ...
790 wxTextOutputStream
str(stream
);
791 bool isCentered
= false;
793 if (thisStyle
.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE
)
801 str
<< wxT("<center>");
806 str
<< wxT("</center>");