]> git.saurik.com Git - wxWidgets.git/blob - src/xml/xml.cpp
6e2dbf6c96d0cc0aa59cbcb60be547c64ceb59dd
[wxWidgets.git] / src / xml / xml.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/xml/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 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #if wxUSE_XML
19
20 #include "wx/xml/xml.h"
21
22 #ifndef WX_PRECOMP
23 #include "wx/intl.h"
24 #include "wx/log.h"
25 #include "wx/app.h"
26 #endif
27
28 #include "wx/wfstream.h"
29 #include "wx/datstrm.h"
30 #include "wx/zstream.h"
31 #include "wx/strconv.h"
32 #include "wx/scopedptr.h"
33 #include "wx/versioninfo.h"
34
35 #include "expat.h" // from Expat
36
37 // DLL options compatibility check:
38 WX_CHECK_BUILD_OPTIONS("wxXML")
39
40
41 IMPLEMENT_CLASS(wxXmlDocument, wxObject)
42
43
44 // a private utility used by wxXML
45 static bool wxIsWhiteOnly(const wxString& buf);
46
47
48 //-----------------------------------------------------------------------------
49 // wxXmlNode
50 //-----------------------------------------------------------------------------
51
52 wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type,
53 const wxString& name, const wxString& content,
54 wxXmlAttribute *attrs, wxXmlNode *next, int lineNo)
55 : m_type(type), m_name(name), m_content(content),
56 m_attrs(attrs), m_parent(parent),
57 m_children(NULL), m_next(next),
58 m_lineNo(lineNo),
59 m_noConversion(false)
60 {
61 wxASSERT_MSG ( type != wxXML_ELEMENT_NODE || content.empty(), "element nodes can't have content" );
62
63 if (m_parent)
64 {
65 if (m_parent->m_children)
66 {
67 m_next = m_parent->m_children;
68 m_parent->m_children = this;
69 }
70 else
71 m_parent->m_children = this;
72 }
73 }
74
75 wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name,
76 const wxString& content,
77 int lineNo)
78 : m_type(type), m_name(name), m_content(content),
79 m_attrs(NULL), m_parent(NULL),
80 m_children(NULL), m_next(NULL),
81 m_lineNo(lineNo), m_noConversion(false)
82 {
83 wxASSERT_MSG ( type != wxXML_ELEMENT_NODE || content.empty(), "element nodes can't have content" );
84 }
85
86 wxXmlNode::wxXmlNode(const wxXmlNode& node)
87 {
88 m_next = NULL;
89 m_parent = NULL;
90 DoCopy(node);
91 }
92
93 wxXmlNode::~wxXmlNode()
94 {
95 DoFree();
96 }
97
98 wxXmlNode& wxXmlNode::operator=(const wxXmlNode& node)
99 {
100 DoFree();
101 DoCopy(node);
102 return *this;
103 }
104
105 void wxXmlNode::DoFree()
106 {
107 wxXmlNode *c, *c2;
108 for (c = m_children; c; c = c2)
109 {
110 c2 = c->m_next;
111 delete c;
112 }
113
114 wxXmlAttribute *p, *p2;
115 for (p = m_attrs; p; p = p2)
116 {
117 p2 = p->GetNext();
118 delete p;
119 }
120 }
121
122 void wxXmlNode::DoCopy(const wxXmlNode& node)
123 {
124 m_type = node.m_type;
125 m_name = node.m_name;
126 m_content = node.m_content;
127 m_lineNo = node.m_lineNo;
128 m_noConversion = node.m_noConversion;
129 m_children = NULL;
130
131 wxXmlNode *n = node.m_children;
132 while (n)
133 {
134 AddChild(new wxXmlNode(*n));
135 n = n->GetNext();
136 }
137
138 m_attrs = NULL;
139 wxXmlAttribute *p = node.m_attrs;
140 while (p)
141 {
142 AddAttribute(p->GetName(), p->GetValue());
143 p = p->GetNext();
144 }
145 }
146
147 bool wxXmlNode::HasAttribute(const wxString& attrName) const
148 {
149 wxXmlAttribute *attr = GetAttributes();
150
151 while (attr)
152 {
153 if (attr->GetName() == attrName) return true;
154 attr = attr->GetNext();
155 }
156
157 return false;
158 }
159
160 bool wxXmlNode::GetAttribute(const wxString& attrName, wxString *value) const
161 {
162 wxCHECK_MSG( value, false, "value argument must not be NULL" );
163
164 wxXmlAttribute *attr = GetAttributes();
165
166 while (attr)
167 {
168 if (attr->GetName() == attrName)
169 {
170 *value = attr->GetValue();
171 return true;
172 }
173 attr = attr->GetNext();
174 }
175
176 return false;
177 }
178
179 wxString wxXmlNode::GetAttribute(const wxString& attrName, const wxString& defaultVal) const
180 {
181 wxString tmp;
182 if (GetAttribute(attrName, &tmp))
183 return tmp;
184
185 return defaultVal;
186 }
187
188 void wxXmlNode::AddChild(wxXmlNode *child)
189 {
190 if (m_children == NULL)
191 m_children = child;
192 else
193 {
194 wxXmlNode *ch = m_children;
195 while (ch->m_next) ch = ch->m_next;
196 ch->m_next = child;
197 }
198 child->m_next = NULL;
199 child->m_parent = this;
200 }
201
202 // inserts a new node in front of 'followingNode'
203 bool wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *followingNode)
204 {
205 wxCHECK_MSG( child, false, "cannot insert a NULL node!" );
206 wxCHECK_MSG( child->m_parent == NULL, false, "node already has a parent" );
207 wxCHECK_MSG( child->m_next == NULL, false, "node already has m_next" );
208 wxCHECK_MSG( followingNode == NULL || followingNode->GetParent() == this,
209 false,
210 "wxXmlNode::InsertChild - followingNode has incorrect parent" );
211
212 // this is for backward compatibility, NULL was allowed here thanks to
213 // the confusion about followingNode's meaning
214 if ( followingNode == NULL )
215 followingNode = m_children;
216
217 if ( m_children == followingNode )
218 {
219 child->m_next = m_children;
220 m_children = child;
221 }
222 else
223 {
224 wxXmlNode *ch = m_children;
225 while ( ch && ch->m_next != followingNode )
226 ch = ch->m_next;
227 if ( !ch )
228 {
229 wxFAIL_MSG( "followingNode has this node as parent, but couldn't be found among children" );
230 return false;
231 }
232
233 child->m_next = followingNode;
234 ch->m_next = child;
235 }
236
237 child->m_parent = this;
238 return true;
239 }
240
241 // inserts a new node right after 'precedingNode'
242 bool wxXmlNode::InsertChildAfter(wxXmlNode *child, wxXmlNode *precedingNode)
243 {
244 wxCHECK_MSG( child, false, "cannot insert a NULL node!" );
245 wxCHECK_MSG( child->m_parent == NULL, false, "node already has a parent" );
246 wxCHECK_MSG( child->m_next == NULL, false, "node already has m_next" );
247 wxCHECK_MSG( precedingNode == NULL || precedingNode->m_parent == this, false,
248 "precedingNode has wrong parent" );
249
250 if ( precedingNode )
251 {
252 child->m_next = precedingNode->m_next;
253 precedingNode->m_next = child;
254 }
255 else // precedingNode == NULL
256 {
257 wxCHECK_MSG( m_children == NULL, false,
258 "NULL precedingNode only makes sense when there are no children" );
259
260 child->m_next = m_children;
261 m_children = child;
262 }
263
264 child->m_parent = this;
265 return true;
266 }
267
268 bool wxXmlNode::RemoveChild(wxXmlNode *child)
269 {
270 if (m_children == NULL)
271 return false;
272 else if (m_children == child)
273 {
274 m_children = child->m_next;
275 child->m_parent = NULL;
276 child->m_next = NULL;
277 return true;
278 }
279 else
280 {
281 wxXmlNode *ch = m_children;
282 while (ch->m_next)
283 {
284 if (ch->m_next == child)
285 {
286 ch->m_next = child->m_next;
287 child->m_parent = NULL;
288 child->m_next = NULL;
289 return true;
290 }
291 ch = ch->m_next;
292 }
293 return false;
294 }
295 }
296
297 void wxXmlNode::AddAttribute(const wxString& name, const wxString& value)
298 {
299 AddProperty(name, value);
300 }
301
302 void wxXmlNode::AddAttribute(wxXmlAttribute *attr)
303 {
304 AddProperty(attr);
305 }
306
307 bool wxXmlNode::DeleteAttribute(const wxString& name)
308 {
309 return DeleteProperty(name);
310 }
311
312 void wxXmlNode::AddProperty(const wxString& name, const wxString& value)
313 {
314 AddProperty(new wxXmlAttribute(name, value, NULL));
315 }
316
317 void wxXmlNode::AddProperty(wxXmlAttribute *attr)
318 {
319 if (m_attrs == NULL)
320 m_attrs = attr;
321 else
322 {
323 wxXmlAttribute *p = m_attrs;
324 while (p->GetNext()) p = p->GetNext();
325 p->SetNext(attr);
326 }
327 }
328
329 bool wxXmlNode::DeleteProperty(const wxString& name)
330 {
331 wxXmlAttribute *attr;
332
333 if (m_attrs == NULL)
334 return false;
335
336 else if (m_attrs->GetName() == name)
337 {
338 attr = m_attrs;
339 m_attrs = attr->GetNext();
340 attr->SetNext(NULL);
341 delete attr;
342 return true;
343 }
344
345 else
346 {
347 wxXmlAttribute *p = m_attrs;
348 while (p->GetNext())
349 {
350 if (p->GetNext()->GetName() == name)
351 {
352 attr = p->GetNext();
353 p->SetNext(attr->GetNext());
354 attr->SetNext(NULL);
355 delete attr;
356 return true;
357 }
358 p = p->GetNext();
359 }
360 return false;
361 }
362 }
363
364 wxString wxXmlNode::GetNodeContent() const
365 {
366 wxXmlNode *n = GetChildren();
367
368 while (n)
369 {
370 if (n->GetType() == wxXML_TEXT_NODE ||
371 n->GetType() == wxXML_CDATA_SECTION_NODE)
372 return n->GetContent();
373 n = n->GetNext();
374 }
375 return wxEmptyString;
376 }
377
378 int wxXmlNode::GetDepth(wxXmlNode *grandparent) const
379 {
380 const wxXmlNode *n = this;
381 int ret = -1;
382
383 do
384 {
385 ret++;
386 n = n->GetParent();
387 if (n == grandparent)
388 return ret;
389
390 } while (n);
391
392 return wxNOT_FOUND;
393 }
394
395 bool wxXmlNode::IsWhitespaceOnly() const
396 {
397 return wxIsWhiteOnly(m_content);
398 }
399
400
401
402 //-----------------------------------------------------------------------------
403 // wxXmlDocument
404 //-----------------------------------------------------------------------------
405
406 wxXmlDocument::wxXmlDocument()
407 : m_version(wxS("1.0")), m_fileEncoding(wxS("UTF-8")), m_docNode(NULL)
408 {
409 #if !wxUSE_UNICODE
410 m_encoding = wxS("UTF-8");
411 #endif
412 }
413
414 wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding)
415 :wxObject(), m_docNode(NULL)
416 {
417 if ( !Load(filename, encoding) )
418 {
419 wxDELETE(m_docNode);
420 }
421 }
422
423 wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding)
424 :wxObject(), m_docNode(NULL)
425 {
426 if ( !Load(stream, encoding) )
427 {
428 wxDELETE(m_docNode);
429 }
430 }
431
432 wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc)
433 :wxObject()
434 {
435 DoCopy(doc);
436 }
437
438 wxXmlDocument& wxXmlDocument::operator=(const wxXmlDocument& doc)
439 {
440 wxDELETE(m_docNode);
441 DoCopy(doc);
442 return *this;
443 }
444
445 void wxXmlDocument::DoCopy(const wxXmlDocument& doc)
446 {
447 m_version = doc.m_version;
448 #if !wxUSE_UNICODE
449 m_encoding = doc.m_encoding;
450 #endif
451 m_fileEncoding = doc.m_fileEncoding;
452
453 if (doc.m_docNode)
454 m_docNode = new wxXmlNode(*doc.m_docNode);
455 else
456 m_docNode = NULL;
457 }
458
459 bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding, int flags)
460 {
461 wxFileInputStream stream(filename);
462 if (!stream.IsOk())
463 return false;
464 return Load(stream, encoding, flags);
465 }
466
467 bool wxXmlDocument::Save(const wxString& filename, int indentstep) const
468 {
469 wxFileOutputStream stream(filename);
470 if (!stream.IsOk())
471 return false;
472 return Save(stream, indentstep);
473 }
474
475 wxXmlNode *wxXmlDocument::GetRoot() const
476 {
477 wxXmlNode *node = m_docNode;
478 if (node)
479 {
480 node = m_docNode->GetChildren();
481 while (node != NULL && node->GetType() != wxXML_ELEMENT_NODE)
482 node = node->GetNext();
483 }
484 return node;
485 }
486
487 wxXmlNode *wxXmlDocument::DetachRoot()
488 {
489 wxXmlNode *node = m_docNode;
490 if (node)
491 {
492 node = m_docNode->GetChildren();
493 wxXmlNode *prev = NULL;
494 while (node != NULL && node->GetType() != wxXML_ELEMENT_NODE)
495 {
496 prev = node;
497 node = node->GetNext();
498 }
499 if (node)
500 {
501 if (node == m_docNode->GetChildren())
502 m_docNode->SetChildren(node->GetNext());
503
504 if (prev)
505 prev->SetNext(node->GetNext());
506
507 node->SetParent(NULL);
508 node->SetNext(NULL);
509 }
510 }
511 return node;
512 }
513
514 void wxXmlDocument::SetRoot(wxXmlNode *root)
515 {
516 if (root)
517 {
518 wxASSERT_MSG( root->GetType() == wxXML_ELEMENT_NODE,
519 "Can only set an element type node as root" );
520 }
521
522 wxXmlNode *node = m_docNode;
523 if (node)
524 {
525 node = m_docNode->GetChildren();
526 wxXmlNode *prev = NULL;
527 while (node != NULL && node->GetType() != wxXML_ELEMENT_NODE)
528 {
529 prev = node;
530 node = node->GetNext();
531 }
532 if (node && root)
533 {
534 root->SetNext( node->GetNext() );
535 wxDELETE(node);
536 }
537 if (prev)
538 prev->SetNext(root);
539 else
540 m_docNode->SetChildren(root);
541 }
542 else
543 {
544 m_docNode = new wxXmlNode(wxXML_DOCUMENT_NODE, wxEmptyString);
545 m_docNode->SetChildren(root);
546 }
547 if (root)
548 root->SetParent(m_docNode);
549 }
550
551 void wxXmlDocument::AppendToProlog(wxXmlNode *node)
552 {
553 if (!m_docNode)
554 m_docNode = new wxXmlNode(wxXML_DOCUMENT_NODE, wxEmptyString);
555 if (IsOk())
556 m_docNode->InsertChild( node, GetRoot() );
557 else
558 m_docNode->AddChild( node );
559 }
560
561 //-----------------------------------------------------------------------------
562 // wxXmlDocument loading routines
563 //-----------------------------------------------------------------------------
564
565 // converts Expat-produced string in UTF-8 into wxString using the specified
566 // conv or keep in UTF-8 if conv is NULL
567 static wxString CharToString(wxMBConv *conv,
568 const char *s, size_t len = wxString::npos)
569 {
570 #if !wxUSE_UNICODE
571 if ( conv )
572 {
573 // there can be no embedded NULs in this string so we don't need the
574 // output length, it will be NUL-terminated
575 const wxWCharBuffer wbuf(
576 wxConvUTF8.cMB2WC(s, len == wxString::npos ? wxNO_LEN : len, NULL));
577
578 return wxString(wbuf, *conv);
579 }
580 // else: the string is wanted in UTF-8
581 #endif // !wxUSE_UNICODE
582
583 wxUnusedVar(conv);
584 return wxString::FromUTF8Unchecked(s, len);
585 }
586
587 // returns true if the given string contains only whitespaces
588 bool wxIsWhiteOnly(const wxString& buf)
589 {
590 for ( wxString::const_iterator i = buf.begin(); i != buf.end(); ++i )
591 {
592 wxChar c = *i;
593 if ( c != wxS(' ') && c != wxS('\t') && c != wxS('\n') && c != wxS('\r'))
594 return false;
595 }
596 return true;
597 }
598
599
600 struct wxXmlParsingContext
601 {
602 wxXmlParsingContext()
603 : conv(NULL),
604 node(NULL),
605 lastChild(NULL),
606 lastAsText(NULL),
607 removeWhiteOnlyNodes(false)
608 {}
609
610 XML_Parser parser;
611 wxMBConv *conv;
612 wxXmlNode *node; // the node being parsed
613 wxXmlNode *lastChild; // the last child of "node"
614 wxXmlNode *lastAsText; // the last _text_ child of "node"
615 wxString encoding;
616 wxString version;
617 bool removeWhiteOnlyNodes;
618 };
619
620 // checks that ctx->lastChild is in consistent state
621 #define ASSERT_LAST_CHILD_OK(ctx) \
622 wxASSERT( ctx->lastChild == NULL || \
623 ctx->lastChild->GetNext() == NULL ); \
624 wxASSERT( ctx->lastChild == NULL || \
625 ctx->lastChild->GetParent() == ctx->node )
626
627 extern "C" {
628 static void StartElementHnd(void *userData, const char *name, const char **atts)
629 {
630 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
631 wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE,
632 CharToString(ctx->conv, name),
633 wxEmptyString,
634 XML_GetCurrentLineNumber(ctx->parser));
635 const char **a = atts;
636
637 // add node attributes
638 while (*a)
639 {
640 node->AddAttribute(CharToString(ctx->conv, a[0]), CharToString(ctx->conv, a[1]));
641 a += 2;
642 }
643
644 ASSERT_LAST_CHILD_OK(ctx);
645 ctx->node->InsertChildAfter(node, ctx->lastChild);
646 ctx->lastAsText = NULL;
647 ctx->lastChild = NULL; // our new node "node" has no children yet
648
649 ctx->node = node;
650 }
651
652 static void EndElementHnd(void *userData, const char* WXUNUSED(name))
653 {
654 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
655
656 // we're exiting the last children of ctx->node->GetParent() and going
657 // back one level up, so current value of ctx->node points to the last
658 // child of ctx->node->GetParent()
659 ctx->lastChild = ctx->node;
660
661 ctx->node = ctx->node->GetParent();
662 ctx->lastAsText = NULL;
663 }
664
665 static void TextHnd(void *userData, const char *s, int len)
666 {
667 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
668 wxString str = CharToString(ctx->conv, s, len);
669
670 if (ctx->lastAsText)
671 {
672 ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() + str);
673 }
674 else
675 {
676 bool whiteOnly = false;
677 if (ctx->removeWhiteOnlyNodes)
678 whiteOnly = wxIsWhiteOnly(str);
679
680 if (!whiteOnly)
681 {
682 wxXmlNode *textnode =
683 new wxXmlNode(wxXML_TEXT_NODE, wxS("text"), str,
684 XML_GetCurrentLineNumber(ctx->parser));
685
686 ASSERT_LAST_CHILD_OK(ctx);
687 ctx->node->InsertChildAfter(textnode, ctx->lastChild);
688 ctx->lastChild= ctx->lastAsText = textnode;
689 }
690 }
691 }
692
693 static void StartCdataHnd(void *userData)
694 {
695 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
696
697 wxXmlNode *textnode =
698 new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxS("cdata"), wxS(""),
699 XML_GetCurrentLineNumber(ctx->parser));
700
701 ASSERT_LAST_CHILD_OK(ctx);
702 ctx->node->InsertChildAfter(textnode, ctx->lastChild);
703 ctx->lastChild= ctx->lastAsText = textnode;
704 }
705
706 static void EndCdataHnd(void *userData)
707 {
708 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
709
710 // we need to reset this pointer so that subsequent text nodes don't append
711 // their contents to this one but create new wxXML_TEXT_NODE objects (or
712 // not create anything at all if only white space follows the CDATA section
713 // and wxXMLDOC_KEEP_WHITESPACE_NODES is not used as is commonly the case)
714 ctx->lastAsText = NULL;
715 }
716
717 static void CommentHnd(void *userData, const char *data)
718 {
719 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
720
721 wxXmlNode *commentnode =
722 new wxXmlNode(wxXML_COMMENT_NODE,
723 wxS("comment"), CharToString(ctx->conv, data),
724 XML_GetCurrentLineNumber(ctx->parser));
725
726 ASSERT_LAST_CHILD_OK(ctx);
727 ctx->node->InsertChildAfter(commentnode, ctx->lastChild);
728 ctx->lastChild = commentnode;
729 ctx->lastAsText = NULL;
730 }
731
732 static void PIHnd(void *userData, const char *target, const char *data)
733 {
734 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
735
736 wxXmlNode *pinode =
737 new wxXmlNode(wxXML_PI_NODE, CharToString(ctx->conv, target),
738 CharToString(ctx->conv, data),
739 XML_GetCurrentLineNumber(ctx->parser));
740
741 ASSERT_LAST_CHILD_OK(ctx);
742 ctx->node->InsertChildAfter(pinode, ctx->lastChild);
743 ctx->lastChild = pinode;
744 ctx->lastAsText = NULL;
745 }
746
747 static void DefaultHnd(void *userData, const char *s, int len)
748 {
749 // XML header:
750 if (len > 6 && memcmp(s, "<?xml ", 6) == 0)
751 {
752 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
753
754 wxString buf = CharToString(ctx->conv, s, (size_t)len);
755 int pos;
756 pos = buf.Find(wxS("encoding="));
757 if (pos != wxNOT_FOUND)
758 ctx->encoding = buf.Mid(pos + 10).BeforeFirst(buf[(size_t)pos+9]);
759 pos = buf.Find(wxS("version="));
760 if (pos != wxNOT_FOUND)
761 ctx->version = buf.Mid(pos + 9).BeforeFirst(buf[(size_t)pos+8]);
762 }
763 }
764
765 static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData),
766 const XML_Char *name, XML_Encoding *info)
767 {
768 // We must build conversion table for expat. The easiest way to do so
769 // is to let wxCSConv convert as string containing all characters to
770 // wide character representation:
771 wxCSConv conv(name);
772 char mbBuf[2];
773 wchar_t wcBuf[10];
774 size_t i;
775
776 mbBuf[1] = 0;
777 info->map[0] = 0;
778 for (i = 0; i < 255; i++)
779 {
780 mbBuf[0] = (char)(i+1);
781 if (conv.MB2WC(wcBuf, mbBuf, 2) == (size_t)-1)
782 {
783 // invalid/undefined byte in the encoding:
784 info->map[i+1] = -1;
785 }
786 info->map[i+1] = (int)wcBuf[0];
787 }
788
789 info->data = NULL;
790 info->convert = NULL;
791 info->release = NULL;
792
793 return 1;
794 }
795
796 } // extern "C"
797
798 bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int flags)
799 {
800 #if wxUSE_UNICODE
801 (void)encoding;
802 #else
803 m_encoding = encoding;
804 #endif
805
806 const size_t BUFSIZE = 1024;
807 char buf[BUFSIZE];
808 wxXmlParsingContext ctx;
809 bool done;
810 XML_Parser parser = XML_ParserCreate(NULL);
811 wxXmlNode *root = new wxXmlNode(wxXML_DOCUMENT_NODE, wxEmptyString);
812
813 ctx.encoding = wxS("UTF-8"); // default in absence of encoding=""
814 ctx.conv = NULL;
815 #if !wxUSE_UNICODE
816 if ( encoding.CmpNoCase(wxS("UTF-8")) != 0 )
817 ctx.conv = new wxCSConv(encoding);
818 #endif
819 ctx.removeWhiteOnlyNodes = (flags & wxXMLDOC_KEEP_WHITESPACE_NODES) == 0;
820 ctx.parser = parser;
821 ctx.node = root;
822
823 XML_SetUserData(parser, (void*)&ctx);
824 XML_SetElementHandler(parser, StartElementHnd, EndElementHnd);
825 XML_SetCharacterDataHandler(parser, TextHnd);
826 XML_SetCdataSectionHandler(parser, StartCdataHnd, EndCdataHnd);;
827 XML_SetCommentHandler(parser, CommentHnd);
828 XML_SetProcessingInstructionHandler(parser, PIHnd);
829 XML_SetDefaultHandler(parser, DefaultHnd);
830 XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL);
831
832 bool ok = true;
833 do
834 {
835 size_t len = stream.Read(buf, BUFSIZE).LastRead();
836 done = (len < BUFSIZE);
837 if (!XML_Parse(parser, buf, len, done))
838 {
839 wxString error(XML_ErrorString(XML_GetErrorCode(parser)),
840 *wxConvCurrent);
841 wxLogError(_("XML parsing error: '%s' at line %d"),
842 error.c_str(),
843 (int)XML_GetCurrentLineNumber(parser));
844 ok = false;
845 break;
846 }
847 } while (!done);
848
849 if (ok)
850 {
851 if (!ctx.version.empty())
852 SetVersion(ctx.version);
853 if (!ctx.encoding.empty())
854 SetFileEncoding(ctx.encoding);
855 SetDocumentNode(root);
856 }
857 else
858 {
859 delete root;
860 }
861
862 XML_ParserFree(parser);
863 #if !wxUSE_UNICODE
864 if ( ctx.conv )
865 delete ctx.conv;
866 #endif
867
868 return ok;
869
870 }
871
872
873
874 //-----------------------------------------------------------------------------
875 // wxXmlDocument saving routines
876 //-----------------------------------------------------------------------------
877
878 // helpers for XML generation
879 namespace
880 {
881
882 // write string to output:
883 bool OutputString(wxOutputStream& stream,
884 const wxString& str,
885 wxMBConv *convMem,
886 wxMBConv *convFile)
887 {
888 if (str.empty())
889 return true;
890
891 #if wxUSE_UNICODE
892 wxUnusedVar(convMem);
893 if ( !convFile )
894 convFile = &wxConvUTF8;
895
896 const wxScopedCharBuffer buf(str.mb_str(*convFile));
897 if ( !buf.length() )
898 {
899 // conversion failed, can't write this string in an XML file in this
900 // (presumably non-UTF-8) encoding
901 return false;
902 }
903
904 stream.Write(buf, buf.length());
905 #else // !wxUSE_UNICODE
906 if ( convFile && convMem )
907 {
908 wxString str2(str.wc_str(*convMem), *convFile);
909 stream.Write(str2.mb_str(), str2.length());
910 }
911 else // no conversions to do
912 {
913 stream.Write(str.mb_str(), str.length());
914 }
915 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
916
917 return stream.IsOk();
918 }
919
920 enum EscapingMode
921 {
922 Escape_Text,
923 Escape_Attribute
924 };
925
926 // Same as above, but create entities first.
927 // Translates '<' to "&lt;", '>' to "&gt;" and so on, according to the spec:
928 // http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping
929 bool OutputEscapedString(wxOutputStream& stream,
930 const wxString& str,
931 wxMBConv *convMem,
932 wxMBConv *convFile,
933 EscapingMode mode)
934 {
935 wxString escaped;
936 escaped.reserve(str.length());
937
938 for ( wxString::const_iterator i = str.begin(); i != str.end(); ++i )
939 {
940 const wxChar c = *i;
941
942 switch ( c )
943 {
944 case wxS('<'):
945 escaped.append(wxS("&lt;"));
946 break;
947 case wxS('>'):
948 escaped.append(wxS("&gt;"));
949 break;
950 case wxS('&'):
951 escaped.append(wxS("&amp;"));
952 break;
953 case wxS('\r'):
954 escaped.append(wxS("&#xD;"));
955 break;
956 default:
957 if ( mode == Escape_Attribute )
958 {
959 switch ( c )
960 {
961 case wxS('"'):
962 escaped.append(wxS("&quot;"));
963 break;
964 case wxS('\t'):
965 escaped.append(wxS("&#x9;"));
966 break;
967 case wxS('\n'):
968 escaped.append(wxS("&#xA;"));
969 break;
970 default:
971 escaped.append(c);
972 }
973
974 }
975 else
976 {
977 escaped.append(c);
978 }
979 }
980 }
981
982 return OutputString(stream, escaped, convMem, convFile);
983 }
984
985 bool OutputIndentation(wxOutputStream& stream,
986 int indent,
987 wxMBConv *convMem,
988 wxMBConv *convFile)
989 {
990 wxString str(wxS("\n"));
991 str += wxString(indent, wxS(' '));
992 return OutputString(stream, str, convMem, convFile);
993 }
994
995 bool OutputNode(wxOutputStream& stream,
996 wxXmlNode *node,
997 int indent,
998 wxMBConv *convMem,
999 wxMBConv *convFile,
1000 int indentstep)
1001 {
1002 bool rc;
1003 switch (node->GetType())
1004 {
1005 case wxXML_CDATA_SECTION_NODE:
1006 rc = OutputString(stream, wxS("<![CDATA["), convMem, convFile) &&
1007 OutputString(stream, node->GetContent(), convMem, convFile) &&
1008 OutputString(stream, wxS("]]>"), convMem, convFile);
1009 break;
1010
1011 case wxXML_TEXT_NODE:
1012 if (node->GetNoConversion())
1013 {
1014 stream.Write(node->GetContent().c_str(), node->GetContent().Length());
1015 rc = true;
1016 }
1017 else
1018 rc = OutputEscapedString(stream, node->GetContent(),
1019 convMem, convFile,
1020 Escape_Text);
1021 break;
1022
1023 case wxXML_ELEMENT_NODE:
1024 rc = OutputString(stream, wxS("<"), convMem, convFile) &&
1025 OutputString(stream, node->GetName(), convMem, convFile);
1026
1027 if ( rc )
1028 {
1029 for ( wxXmlAttribute *attr = node->GetAttributes();
1030 attr && rc;
1031 attr = attr->GetNext() )
1032 {
1033 rc = OutputString(stream,
1034 wxS(" ") + attr->GetName() + wxS("=\""),
1035 convMem, convFile) &&
1036 OutputEscapedString(stream, attr->GetValue(),
1037 convMem, convFile,
1038 Escape_Attribute) &&
1039 OutputString(stream, wxS("\""), convMem, convFile);
1040 }
1041 }
1042
1043 if ( node->GetChildren() )
1044 {
1045 rc = OutputString(stream, wxS(">"), convMem, convFile);
1046
1047 wxXmlNode *prev = NULL;
1048 for ( wxXmlNode *n = node->GetChildren();
1049 n && rc;
1050 n = n->GetNext() )
1051 {
1052 if ( indentstep >= 0 && n->GetType() != wxXML_TEXT_NODE )
1053 {
1054 rc = OutputIndentation(stream, indent + indentstep,
1055 convMem, convFile);
1056 }
1057
1058 if ( rc )
1059 rc = OutputNode(stream, n, indent + indentstep,
1060 convMem, convFile, indentstep);
1061
1062 prev = n;
1063 }
1064
1065 if ( rc && indentstep >= 0 &&
1066 prev && prev->GetType() != wxXML_TEXT_NODE )
1067 {
1068 rc = OutputIndentation(stream, indent, convMem, convFile);
1069 }
1070
1071 if ( rc )
1072 {
1073 rc = OutputString(stream, wxS("</"), convMem, convFile) &&
1074 OutputString(stream, node->GetName(),
1075 convMem, convFile) &&
1076 OutputString(stream, wxS(">"), convMem, convFile);
1077 }
1078 }
1079 else // no children, output "<foo/>"
1080 {
1081 rc = OutputString(stream, wxS("/>"), convMem, convFile);
1082 }
1083 break;
1084
1085 case wxXML_COMMENT_NODE:
1086 rc = OutputString(stream, wxS("<!--"), convMem, convFile) &&
1087 OutputString(stream, node->GetContent(), convMem, convFile) &&
1088 OutputString(stream, wxS("-->"), convMem, convFile);
1089 break;
1090
1091 case wxXML_PI_NODE:
1092 rc = OutputString(stream, wxT("<?"), convMem, convFile) &&
1093 OutputString(stream, node->GetName(), convMem, convFile) &&
1094 OutputString(stream, wxT(" "), convMem, convFile) &&
1095 OutputString(stream, node->GetContent(), convMem, convFile) &&
1096 OutputString(stream, wxT("?>"), convMem, convFile);
1097 break;
1098
1099 default:
1100 wxFAIL_MSG("unsupported node type");
1101 rc = false;
1102 }
1103
1104 return rc;
1105 }
1106
1107 } // anonymous namespace
1108
1109 bool wxXmlDocument::Save(wxOutputStream& stream, int indentstep) const
1110 {
1111 if ( !IsOk() )
1112 return false;
1113
1114 wxScopedPtr<wxMBConv> convMem, convFile;
1115
1116 #if wxUSE_UNICODE
1117 convFile.reset(new wxCSConv(GetFileEncoding()));
1118 #else
1119 if ( GetFileEncoding().CmpNoCase(GetEncoding()) != 0 )
1120 {
1121 convFile.reset(new wxCSConv(GetFileEncoding()));
1122 convMem.reset(new wxCSConv(GetEncoding()));
1123 }
1124 //else: file and in-memory encodings are the same, no conversion needed
1125 #endif
1126
1127 wxString dec = wxString::Format(
1128 wxS("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
1129 GetVersion(), GetFileEncoding()
1130 );
1131 bool rc = OutputString(stream, dec, convMem.get(), convFile.get());
1132
1133 wxXmlNode *node = GetDocumentNode();
1134 if ( node )
1135 node = node->GetChildren();
1136
1137 while( rc && node )
1138 {
1139 rc = OutputNode(stream, node, 0, convMem.get(),
1140 convFile.get(), indentstep) &&
1141 OutputString(stream, wxS("\n"), convMem.get(), convFile.get());
1142 node = node->GetNext();
1143 }
1144 return rc;
1145 }
1146
1147 /*static*/ wxVersionInfo wxXmlDocument::GetLibraryVersionInfo()
1148 {
1149 return wxVersionInfo("expat",
1150 XML_MAJOR_VERSION,
1151 XML_MINOR_VERSION,
1152 XML_MICRO_VERSION);
1153 }
1154
1155 #endif // wxUSE_XML