]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/xrc/xml.cpp
added wxRTTI to XRC handlers (patch #752996 by David Falkinder)
[wxWidgets.git] / contrib / src / xrc / xml.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: xml.cpp
3 // Purpose: wxXmlDocument - XML parser & data holder class
4 // Author: Vaclav Slavik
5 // Created: 2000/03/05
6 // RCS-ID: $Id$
7 // Copyright: (c) 2000 Vaclav Slavik
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 #ifdef __GNUG__
12 #pragma implementation "xml.h"
13 #endif
14
15 // For compilers that support precompilation, includes "wx.h".
16 #include "wx/wxprec.h"
17
18 #ifdef __BORLANDC__
19 #pragma hdrstop
20 #endif
21
22
23 #include "wx/wfstream.h"
24 #include "wx/datstrm.h"
25 #include "wx/zstream.h"
26 #include "wx/log.h"
27 #include "wx/intl.h"
28 #include "wx/strconv.h"
29
30 #include "wx/xrc/xml.h"
31
32 #include "xmlparse.h" // from Expat
33
34 //-----------------------------------------------------------------------------
35 // wxXmlNode
36 //-----------------------------------------------------------------------------
37
38 wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type,
39 const wxString& name, const wxString& content,
40 wxXmlProperty *props, wxXmlNode *next)
41 : m_type(type), m_name(name), m_content(content),
42 m_properties(props), m_parent(parent),
43 m_children(NULL), m_next(next)
44 {
45 if (m_parent)
46 {
47 if (m_parent->m_children)
48 {
49 m_next = m_parent->m_children;
50 m_parent->m_children = this;
51 }
52 else
53 m_parent->m_children = this;
54 }
55 }
56
57 wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name,
58 const wxString& content)
59 : m_type(type), m_name(name), m_content(content),
60 m_properties(NULL), m_parent(NULL),
61 m_children(NULL), m_next(NULL)
62 {}
63
64 wxXmlNode::wxXmlNode(const wxXmlNode& node)
65 {
66 m_next = NULL;
67 m_parent = NULL;
68 DoCopy(node);
69 }
70
71 wxXmlNode::~wxXmlNode()
72 {
73 wxXmlNode *c, *c2;
74 for (c = m_children; c; c = c2)
75 {
76 c2 = c->m_next;
77 delete c;
78 }
79
80 wxXmlProperty *p, *p2;
81 for (p = m_properties; p; p = p2)
82 {
83 p2 = p->GetNext();
84 delete p;
85 }
86 }
87
88 wxXmlNode& wxXmlNode::operator=(const wxXmlNode& node)
89 {
90 wxDELETE(m_properties);
91 wxDELETE(m_children);
92 DoCopy(node);
93 return *this;
94 }
95
96 void wxXmlNode::DoCopy(const wxXmlNode& node)
97 {
98 m_type = node.m_type;
99 m_name = node.m_name;
100 m_content = node.m_content;
101 m_children = NULL;
102
103 wxXmlNode *n = node.m_children;
104 while (n)
105 {
106 AddChild(new wxXmlNode(*n));
107 n = n->GetNext();
108 }
109
110 m_properties = NULL;
111 wxXmlProperty *p = node.m_properties;
112 while (p)
113 {
114 AddProperty(p->GetName(), p->GetValue());
115 p = p->GetNext();
116 }
117 }
118
119 bool wxXmlNode::HasProp(const wxString& propName) const
120 {
121 wxXmlProperty *prop = GetProperties();
122
123 while (prop)
124 {
125 if (prop->GetName() == propName) return TRUE;
126 prop = prop->GetNext();
127 }
128
129 return FALSE;
130 }
131
132 bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const
133 {
134 wxXmlProperty *prop = GetProperties();
135
136 while (prop)
137 {
138 if (prop->GetName() == propName)
139 {
140 *value = prop->GetValue();
141 return TRUE;
142 }
143 prop = prop->GetNext();
144 }
145
146 return FALSE;
147 }
148
149 wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& defaultVal) const
150 {
151 wxString tmp;
152 if (GetPropVal(propName, &tmp))
153 return tmp;
154 else
155 return defaultVal;
156 }
157
158 void wxXmlNode::AddChild(wxXmlNode *child)
159 {
160 if (m_children == NULL)
161 m_children = child;
162 else
163 {
164 wxXmlNode *ch = m_children;
165 while (ch->m_next) ch = ch->m_next;
166 ch->m_next = child;
167 }
168 child->m_next = NULL;
169 child->m_parent = this;
170 }
171
172 void wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node)
173 {
174 wxASSERT_MSG(before_node->GetParent() == this, wxT("wxXmlNode::InsertChild - the node has incorrect parent"));
175
176 if (m_children == before_node)
177 m_children = child;
178 else
179 {
180 wxXmlNode *ch = m_children;
181 while (ch->m_next != before_node) ch = ch->m_next;
182 ch->m_next = child;
183 }
184
185 child->m_parent = this;
186 child->m_next = before_node;
187 }
188
189 bool wxXmlNode::RemoveChild(wxXmlNode *child)
190 {
191 if (m_children == NULL)
192 return FALSE;
193 else if (m_children == child)
194 {
195 m_children = child->m_next;
196 child->m_parent = NULL;
197 child->m_next = NULL;
198 return TRUE;
199 }
200 else
201 {
202 wxXmlNode *ch = m_children;
203 while (ch->m_next)
204 {
205 if (ch->m_next == child)
206 {
207 ch->m_next = child->m_next;
208 child->m_parent = NULL;
209 child->m_next = NULL;
210 return TRUE;
211 }
212 ch = ch->m_next;
213 }
214 return FALSE;
215 }
216 }
217
218 void wxXmlNode::AddProperty(const wxString& name, const wxString& value)
219 {
220 AddProperty(new wxXmlProperty(name, value, NULL));
221 }
222
223 void wxXmlNode::AddProperty(wxXmlProperty *prop)
224 {
225 if (m_properties == NULL)
226 m_properties = prop;
227 else
228 {
229 wxXmlProperty *p = m_properties;
230 while (p->GetNext()) p = p->GetNext();
231 p->SetNext(prop);
232 }
233 }
234
235 bool wxXmlNode::DeleteProperty(const wxString& name)
236 {
237 wxXmlProperty *prop;
238
239 if (m_properties == NULL)
240 return FALSE;
241
242 else if (m_properties->GetName() == name)
243 {
244 prop = m_properties;
245 m_properties = prop->GetNext();
246 prop->SetNext(NULL);
247 delete prop;
248 return TRUE;
249 }
250
251 else
252 {
253 wxXmlProperty *p = m_properties;
254 while (p->GetNext())
255 {
256 if (p->GetNext()->GetName() == name)
257 {
258 prop = p->GetNext();
259 p->SetNext(prop->GetNext());
260 prop->SetNext(NULL);
261 delete prop;
262 return TRUE;
263 }
264 p = p->GetNext();
265 }
266 return FALSE;
267 }
268 }
269
270
271
272 //-----------------------------------------------------------------------------
273 // wxXmlDocument
274 //-----------------------------------------------------------------------------
275
276 wxXmlDocument::wxXmlDocument()
277 : m_version(wxT("1.0")), m_fileEncoding(wxT("utf-8")), m_root(NULL)
278 {
279 #if !wxUSE_UNICODE
280 m_encoding = wxT("UTF-8");
281 #endif
282 }
283
284 wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding)
285 : wxObject(), m_root(NULL)
286 {
287 if ( !Load(filename, encoding) )
288 {
289 wxDELETE(m_root);
290 }
291 }
292
293 wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding)
294 : wxObject(), m_root(NULL)
295 {
296 if ( !Load(stream, encoding) )
297 {
298 wxDELETE(m_root);
299 }
300 }
301
302 wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc)
303 {
304 DoCopy(doc);
305 }
306
307 wxXmlDocument& wxXmlDocument::operator=(const wxXmlDocument& doc)
308 {
309 wxDELETE(m_root);
310 DoCopy(doc);
311 return *this;
312 }
313
314 void wxXmlDocument::DoCopy(const wxXmlDocument& doc)
315 {
316 m_version = doc.m_version;
317 #if !wxUSE_UNICODE
318 m_encoding = doc.m_encoding;
319 #endif
320 m_fileEncoding = doc.m_fileEncoding;
321 m_root = new wxXmlNode(*doc.m_root);
322 }
323
324 bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding)
325 {
326 wxFileInputStream stream(filename);
327 return Load(stream, encoding);
328 }
329
330 bool wxXmlDocument::Save(const wxString& filename) const
331 {
332 wxFileOutputStream stream(filename);
333 return Save(stream);
334 }
335
336
337
338 //-----------------------------------------------------------------------------
339 // wxXmlDocument loading routines
340 //-----------------------------------------------------------------------------
341
342 /*
343 FIXME:
344 - process all elements, including CDATA
345 */
346
347 // converts Expat-produced string in UTF-8 into wxString.
348 inline static wxString CharToString(wxMBConv *conv,
349 const char *s, size_t len = wxSTRING_MAXLEN)
350 {
351 #if wxUSE_UNICODE
352 (void)conv;
353 return wxString(s, wxConvUTF8, len);
354 #else
355 if ( conv )
356 {
357 size_t nLen = (len != wxSTRING_MAXLEN) ? len :
358 nLen = wxConvUTF8.MB2WC((wchar_t*) NULL, s, 0);
359
360 wchar_t *buf = new wchar_t[nLen+1];
361 wxConvUTF8.MB2WC(buf, s, nLen);
362 buf[nLen] = 0;
363 wxString str(buf, *conv, len);
364 delete[] buf;
365 return str;
366 }
367 else
368 return wxString(s, len);
369 #endif
370 }
371
372 struct wxXmlParsingContext
373 {
374 wxMBConv *conv;
375 wxXmlNode *root;
376 wxXmlNode *node;
377 wxXmlNode *lastAsText;
378 wxString encoding;
379 wxString version;
380 };
381
382 static void StartElementHnd(void *userData, const char *name, const char **atts)
383 {
384 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
385 wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, CharToString(ctx->conv, name));
386 const char **a = atts;
387 while (*a)
388 {
389 node->AddProperty(CharToString(ctx->conv, a[0]), CharToString(ctx->conv, a[1]));
390 a += 2;
391 }
392 if (ctx->root == NULL)
393 ctx->root = node;
394 else
395 ctx->node->AddChild(node);
396 ctx->node = node;
397 ctx->lastAsText = NULL;
398 }
399
400 static void EndElementHnd(void *userData, const char* WXUNUSED(name))
401 {
402 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
403
404 ctx->node = ctx->node->GetParent();
405 ctx->lastAsText = NULL;
406 }
407
408 static void TextHnd(void *userData, const char *s, int len)
409 {
410 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
411 char *buf = new char[len + 1];
412
413 buf[len] = '\0';
414 memcpy(buf, s, (size_t)len);
415
416 if (ctx->lastAsText)
417 {
418 ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() +
419 CharToString(ctx->conv, buf));
420 }
421 else
422 {
423 bool whiteOnly = TRUE;
424 for (char *c = buf; *c != '\0'; c++)
425 if (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\r')
426 {
427 whiteOnly = FALSE;
428 break;
429 }
430 if (!whiteOnly)
431 {
432 ctx->lastAsText = new wxXmlNode(wxXML_TEXT_NODE, wxT("text"),
433 CharToString(ctx->conv, buf));
434 ctx->node->AddChild(ctx->lastAsText);
435 }
436 }
437
438 delete[] buf;
439 }
440
441 static void CommentHnd(void *userData, const char *data)
442 {
443 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
444
445 if (ctx->node)
446 {
447 // VS: ctx->node == NULL happens if there is a comment before
448 // the root element (e.g. wxDesigner's output). We ignore such
449 // comments, no big deal...
450 ctx->node->AddChild(new wxXmlNode(wxXML_COMMENT_NODE,
451 wxT("comment"), CharToString(ctx->conv, data)));
452 }
453 ctx->lastAsText = NULL;
454 }
455
456 static void DefaultHnd(void *userData, const char *s, int len)
457 {
458 // XML header:
459 if (len > 6 && memcmp(s, "<?xml ", 6) == 0)
460 {
461 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
462
463 wxString buf = CharToString(ctx->conv, s, (size_t)len);
464 int pos;
465 pos = buf.Find(wxT("encoding="));
466 if (pos != wxNOT_FOUND)
467 ctx->encoding = buf.Mid(pos + 10).BeforeFirst(buf[(size_t)pos+9]);
468 pos = buf.Find(wxT("version="));
469 if (pos != wxNOT_FOUND)
470 ctx->version = buf.Mid(pos + 9).BeforeFirst(buf[(size_t)pos+8]);
471 }
472 }
473
474 static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData),
475 const XML_Char *name, XML_Encoding *info)
476 {
477 // We must build conversion table for expat. The easiest way to do so
478 // is to let wxCSConv convert as string containing all characters to
479 // wide character representation:
480 wxCSConv conv(wxString(name, wxConvLibc));
481 char mbBuf[2];
482 wchar_t wcBuf[10];
483 size_t i;
484
485 mbBuf[1] = 0;
486 info->map[0] = 0;
487 for (i = 0; i < 255; i++)
488 {
489 mbBuf[0] = (char)(i+1);
490 if (conv.MB2WC(wcBuf, mbBuf, 2) == (size_t)-1)
491 {
492 // invalid/undefined byte in the encoding:
493 info->map[i+1] = -1;
494 }
495 info->map[i+1] = (int)wcBuf[0];
496 }
497
498 info->data = NULL;
499 info->convert = NULL;
500 info->release = NULL;
501
502 return 1;
503 }
504
505 bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding)
506 {
507 #if wxUSE_UNICODE
508 (void)encoding;
509 #else
510 m_encoding = encoding;
511 #endif
512
513 const size_t BUFSIZE = 1024;
514 char buf[BUFSIZE];
515 wxXmlParsingContext ctx;
516 bool done;
517 XML_Parser parser = XML_ParserCreate(NULL);
518
519 ctx.root = ctx.node = NULL;
520 ctx.encoding = wxT("UTF-8"); // default in absence of encoding=""
521 ctx.conv = NULL;
522 #if !wxUSE_UNICODE
523 if ( encoding != wxT("UTF-8") && encoding != wxT("utf-8") )
524 ctx.conv = new wxCSConv(encoding);
525 #endif
526
527 XML_SetUserData(parser, (void*)&ctx);
528 XML_SetElementHandler(parser, StartElementHnd, EndElementHnd);
529 XML_SetCharacterDataHandler(parser, TextHnd);
530 XML_SetCommentHandler(parser, CommentHnd);
531 XML_SetDefaultHandler(parser, DefaultHnd);
532 XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL);
533
534 bool ok = true;
535 do
536 {
537 size_t len = stream.Read(buf, BUFSIZE).LastRead();
538 done = (len < BUFSIZE);
539 if (!XML_Parse(parser, buf, len, done))
540 {
541 wxLogError(_("XML parsing error: '%s' at line %d"),
542 XML_ErrorString(XML_GetErrorCode(parser)),
543 XML_GetCurrentLineNumber(parser));
544 ok = false;
545 break;
546 }
547 } while (!done);
548
549 if (ok)
550 {
551 SetVersion(ctx.version);
552 SetFileEncoding(ctx.encoding);
553 SetRoot(ctx.root);
554 }
555
556 XML_ParserFree(parser);
557 #if !wxUSE_UNICODE
558 if ( ctx.conv )
559 delete ctx.conv;
560 #endif
561
562 return ok;
563
564 }
565
566
567
568 //-----------------------------------------------------------------------------
569 // wxXmlDocument saving routines
570 //-----------------------------------------------------------------------------
571
572 // write string to output:
573 inline static void OutputString(wxOutputStream& stream, const wxString& str,
574 wxMBConv *convMem, wxMBConv *convFile)
575 {
576 if (str.IsEmpty()) return;
577 #if wxUSE_UNICODE
578 const wxWX2MBbuf buf(str.mb_str(convFile ? *convFile : wxConvUTF8));
579 stream.Write((const char*)buf, strlen((const char*)buf));
580 #else
581 if ( convFile == NULL )
582 stream.Write(str.mb_str(), str.Len());
583 else
584 {
585 wxString str2(str.wc_str(*convMem), *convFile);
586 stream.Write(str2.mb_str(), str2.Len());
587 }
588 #endif
589 }
590
591 // Same as above, but create entities first.
592 // Translates '<' to "&lt;", '>' to "&gt;" and '&' to "&amp;"
593 static void OutputStringEnt(wxOutputStream& stream, const wxString& str,
594 wxMBConv *convMem, wxMBConv *convFile)
595 {
596 wxString buf;
597 size_t i, last, len;
598 wxChar c;
599
600 len = str.Len();
601 last = 0;
602 for (i = 0; i < len; i++)
603 {
604 c = str.GetChar(i);
605 if (c == wxT('<') || c == wxT('>') ||
606 (c == wxT('&') && str.Mid(i+1, 4) != wxT("amp;")))
607 {
608 OutputString(stream, str.Mid(last, i - last), convMem, convFile);
609 switch (c)
610 {
611 case wxT('<'):
612 OutputString(stream, wxT("&lt;"), NULL, NULL);
613 break;
614 case wxT('>'):
615 OutputString(stream, wxT("&gt;"), NULL, NULL);
616 break;
617 case wxT('&'):
618 OutputString(stream, wxT("&amp;"), NULL, NULL);
619 break;
620 default: break;
621 }
622 last = i + 1;
623 }
624 }
625 OutputString(stream, str.Mid(last, i - last), convMem, convFile);
626 }
627
628 inline static void OutputIndentation(wxOutputStream& stream, int indent)
629 {
630 wxString str = wxT("\n");
631 for (int i = 0; i < indent; i++)
632 str << wxT(' ') << wxT(' ');
633 OutputString(stream, str, NULL, NULL);
634 }
635
636 static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent,
637 wxMBConv *convMem, wxMBConv *convFile)
638 {
639 wxXmlNode *n, *prev;
640 wxXmlProperty *prop;
641
642 switch (node->GetType())
643 {
644 case wxXML_TEXT_NODE:
645 OutputStringEnt(stream, node->GetContent(), convMem, convFile);
646 break;
647
648 case wxXML_ELEMENT_NODE:
649 OutputString(stream, wxT("<"), NULL, NULL);
650 OutputString(stream, node->GetName(), NULL, NULL);
651
652 prop = node->GetProperties();
653 while (prop)
654 {
655 OutputString(stream, wxT(" ") + prop->GetName() +
656 wxT("=\"") + prop->GetValue() + wxT("\""),
657 NULL, NULL);
658 // FIXME - what if prop contains '"'?
659 prop = prop->GetNext();
660 }
661
662 if (node->GetChildren())
663 {
664 OutputString(stream, wxT(">"), NULL, NULL);
665 prev = NULL;
666 n = node->GetChildren();
667 while (n)
668 {
669 if (n && n->GetType() != wxXML_TEXT_NODE)
670 OutputIndentation(stream, indent + 1);
671 OutputNode(stream, n, indent + 1, convMem, convFile);
672 prev = n;
673 n = n->GetNext();
674 }
675 if (prev && prev->GetType() != wxXML_TEXT_NODE)
676 OutputIndentation(stream, indent);
677 OutputString(stream, wxT("</"), NULL, NULL);
678 OutputString(stream, node->GetName(), NULL, NULL);
679 OutputString(stream, wxT(">"), NULL, NULL);
680 }
681 else
682 OutputString(stream, wxT("/>"), NULL, NULL);
683 break;
684
685 case wxXML_COMMENT_NODE:
686 OutputString(stream, wxT("<!--"), NULL, NULL);
687 OutputString(stream, node->GetContent(), convMem, convFile);
688 OutputString(stream, wxT("-->"), NULL, NULL);
689 break;
690
691 default:
692 wxFAIL_MSG(wxT("unsupported node type"));
693 }
694 }
695
696 bool wxXmlDocument::Save(wxOutputStream& stream) const
697 {
698 if ( !IsOk() )
699 return FALSE;
700
701 wxString s;
702
703 wxMBConv *convMem = NULL, *convFile = NULL;
704 #if wxUSE_UNICODE
705 convFile = new wxCSConv(GetFileEncoding());
706 #else
707 if ( GetFileEncoding() != GetEncoding() )
708 {
709 convFile = new wxCSConv(GetFileEncoding());
710 convMem = new wxCSConv(GetEncoding());
711 }
712 #endif
713
714 s.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
715 GetVersion().c_str(), GetFileEncoding().c_str());
716 OutputString(stream, s, NULL, NULL);
717
718 OutputNode(stream, GetRoot(), 0, convMem, convFile);
719 OutputString(stream, wxT("\n"), NULL, NULL);
720
721 if ( convFile )
722 delete convFile;
723 if ( convMem )
724 delete convMem;
725
726 return TRUE;
727 }