]> git.saurik.com Git - wxWidgets.git/blob - src/xrc/xml.cpp
more std iostream + Unicode fixes; copy streams in blocks of 4Kb, not char by char
[wxWidgets.git] / 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 do
535 {
536 size_t len = stream.Read(buf, BUFSIZE).LastRead();
537 done = (len < BUFSIZE);
538 if (!XML_Parse(parser, buf, len, done))
539 {
540 wxLogError(_("XML parsing error: '%s' at line %d"),
541 XML_ErrorString(XML_GetErrorCode(parser)),
542 XML_GetCurrentLineNumber(parser));
543 return FALSE;
544 }
545 } while (!done);
546
547 SetVersion(ctx.version);
548 SetFileEncoding(ctx.encoding);
549 SetRoot(ctx.root);
550
551 XML_ParserFree(parser);
552 #if !wxUSE_UNICODE
553 if ( ctx.conv )
554 delete ctx.conv;
555 #endif
556
557 return TRUE;
558
559 }
560
561
562
563 //-----------------------------------------------------------------------------
564 // wxXmlDocument saving routines
565 //-----------------------------------------------------------------------------
566
567 // write string to output:
568 inline static void OutputString(wxOutputStream& stream, const wxString& str,
569 wxMBConv *convMem, wxMBConv *convFile)
570 {
571 if (str.IsEmpty()) return;
572 #if wxUSE_UNICODE
573 const wxWX2MBbuf buf(str.mb_str(convFile ? *convFile : wxConvUTF8));
574 stream.Write((const char*)buf, strlen((const char*)buf));
575 #else
576 if ( convFile == NULL )
577 stream.Write(str.mb_str(), str.Len());
578 else
579 {
580 wxString str2(str.wc_str(*convMem), *convFile);
581 stream.Write(str2.mb_str(), str2.Len());
582 }
583 #endif
584 }
585
586 // Same as above, but create entities first.
587 // Translates '<' to "&lt;", '>' to "&gt;" and '&' to "&amp;"
588 static void OutputStringEnt(wxOutputStream& stream, const wxString& str,
589 wxMBConv *convMem, wxMBConv *convFile)
590 {
591 wxString buf;
592 size_t i, last, len;
593 wxChar c;
594
595 len = str.Len();
596 last = 0;
597 for (i = 0; i < len; i++)
598 {
599 c = str.GetChar(i);
600 if (c == wxT('<') || c == wxT('>') ||
601 (c == wxT('&') && str.Mid(i+1, 4) != wxT("amp;")))
602 {
603 OutputString(stream, str.Mid(last, i - last), convMem, convFile);
604 switch (c)
605 {
606 case wxT('<'):
607 OutputString(stream, wxT("&lt;"), NULL, NULL);
608 break;
609 case wxT('>'):
610 OutputString(stream, wxT("&gt;"), NULL, NULL);
611 break;
612 case wxT('&'):
613 OutputString(stream, wxT("&amp;"), NULL, NULL);
614 break;
615 default: break;
616 }
617 last = i + 1;
618 }
619 }
620 OutputString(stream, str.Mid(last, i - last), convMem, convFile);
621 }
622
623 inline static void OutputIndentation(wxOutputStream& stream, int indent)
624 {
625 wxString str = wxT("\n");
626 for (int i = 0; i < indent; i++)
627 str << wxT(' ') << wxT(' ');
628 OutputString(stream, str, NULL, NULL);
629 }
630
631 static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent,
632 wxMBConv *convMem, wxMBConv *convFile)
633 {
634 wxXmlNode *n, *prev;
635 wxXmlProperty *prop;
636
637 switch (node->GetType())
638 {
639 case wxXML_TEXT_NODE:
640 OutputStringEnt(stream, node->GetContent(), convMem, convFile);
641 break;
642
643 case wxXML_ELEMENT_NODE:
644 OutputString(stream, wxT("<"), NULL, NULL);
645 OutputString(stream, node->GetName(), NULL, NULL);
646
647 prop = node->GetProperties();
648 while (prop)
649 {
650 OutputString(stream, wxT(" ") + prop->GetName() +
651 wxT("=\"") + prop->GetValue() + wxT("\""),
652 NULL, NULL);
653 // FIXME - what if prop contains '"'?
654 prop = prop->GetNext();
655 }
656
657 if (node->GetChildren())
658 {
659 OutputString(stream, wxT(">"), NULL, NULL);
660 prev = NULL;
661 n = node->GetChildren();
662 while (n)
663 {
664 if (n && n->GetType() != wxXML_TEXT_NODE)
665 OutputIndentation(stream, indent + 1);
666 OutputNode(stream, n, indent + 1, convMem, convFile);
667 prev = n;
668 n = n->GetNext();
669 }
670 if (prev && prev->GetType() != wxXML_TEXT_NODE)
671 OutputIndentation(stream, indent);
672 OutputString(stream, wxT("</"), NULL, NULL);
673 OutputString(stream, node->GetName(), NULL, NULL);
674 OutputString(stream, wxT(">"), NULL, NULL);
675 }
676 else
677 OutputString(stream, wxT("/>"), NULL, NULL);
678 break;
679
680 case wxXML_COMMENT_NODE:
681 OutputString(stream, wxT("<!--"), NULL, NULL);
682 OutputString(stream, node->GetContent(), convMem, convFile);
683 OutputString(stream, wxT("-->"), NULL, NULL);
684 break;
685
686 default:
687 wxFAIL_MSG(wxT("unsupported node type"));
688 }
689 }
690
691 bool wxXmlDocument::Save(wxOutputStream& stream) const
692 {
693 if ( !IsOk() )
694 return FALSE;
695
696 wxString s;
697
698 wxMBConv *convMem = NULL, *convFile = NULL;
699 #if wxUSE_UNICODE
700 convFile = new wxCSConv(GetFileEncoding());
701 #else
702 if ( GetFileEncoding() != GetEncoding() )
703 {
704 convFile = new wxCSConv(GetFileEncoding());
705 convMem = new wxCSConv(GetEncoding());
706 }
707 #endif
708
709 s.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
710 GetVersion().c_str(), GetFileEncoding().c_str());
711 OutputString(stream, s, NULL, NULL);
712
713 OutputNode(stream, GetRoot(), 0, convMem, convFile);
714 OutputString(stream, wxT("\n"), NULL, NULL);
715
716 if ( convFile )
717 delete convFile;
718 if ( convMem )
719 delete convMem;
720
721 return TRUE;
722 }