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: xml.cpp 65726 2010-10-02 15:47:41Z TIK $
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
33 #include "expat.h" // from Expat
34
35 // DLL options compatibility check:
36 WX_CHECK_BUILD_OPTIONS("wxXML")
37
38
39 IMPLEMENT_CLASS(wxXmlDocument, wxObject)
40
41
42 // a private utility used by wxXML
43 static bool wxIsWhiteOnly(const wxChar *buf);
44
45
46 //-----------------------------------------------------------------------------
47 // wxXmlNode
48 //-----------------------------------------------------------------------------
49
wxXmlNode(wxXmlNode * parent,wxXmlNodeType type,const wxString & name,const wxString & content,wxXmlProperty * props,wxXmlNode * next)50 wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type,
51 const wxString& name, const wxString& content,
52 wxXmlProperty *props, wxXmlNode *next)
53 : m_type(type), m_name(name), m_content(content),
54 m_properties(props), m_parent(parent),
55 m_children(NULL), m_next(next)
56 {
57 if (m_parent)
58 {
59 if (m_parent->m_children)
60 {
61 m_next = m_parent->m_children;
62 m_parent->m_children = this;
63 }
64 else
65 m_parent->m_children = this;
66 }
67 }
68
wxXmlNode(wxXmlNodeType type,const wxString & name,const wxString & content)69 wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name,
70 const wxString& content)
71 : m_type(type), m_name(name), m_content(content),
72 m_properties(NULL), m_parent(NULL),
73 m_children(NULL), m_next(NULL)
74 {}
75
wxXmlNode(const wxXmlNode & node)76 wxXmlNode::wxXmlNode(const wxXmlNode& node)
77 {
78 m_next = NULL;
79 m_parent = NULL;
80 DoCopy(node);
81 }
82
~wxXmlNode()83 wxXmlNode::~wxXmlNode()
84 {
85 wxXmlNode *c, *c2;
86 for (c = m_children; c; c = c2)
87 {
88 c2 = c->m_next;
89 delete c;
90 }
91
92 wxXmlProperty *p, *p2;
93 for (p = m_properties; p; p = p2)
94 {
95 p2 = p->GetNext();
96 delete p;
97 }
98 }
99
operator =(const wxXmlNode & node)100 wxXmlNode& wxXmlNode::operator=(const wxXmlNode& node)
101 {
102 wxDELETE(m_properties);
103 wxDELETE(m_children);
104 DoCopy(node);
105 return *this;
106 }
107
DoCopy(const wxXmlNode & node)108 void wxXmlNode::DoCopy(const wxXmlNode& node)
109 {
110 m_type = node.m_type;
111 m_name = node.m_name;
112 m_content = node.m_content;
113 m_children = NULL;
114
115 wxXmlNode *n = node.m_children;
116 while (n)
117 {
118 AddChild(new wxXmlNode(*n));
119 n = n->GetNext();
120 }
121
122 m_properties = NULL;
123 wxXmlProperty *p = node.m_properties;
124 while (p)
125 {
126 AddProperty(p->GetName(), p->GetValue());
127 p = p->GetNext();
128 }
129 }
130
HasProp(const wxString & propName) const131 bool wxXmlNode::HasProp(const wxString& propName) const
132 {
133 wxXmlProperty *prop = GetProperties();
134
135 while (prop)
136 {
137 if (prop->GetName() == propName) return true;
138 prop = prop->GetNext();
139 }
140
141 return false;
142 }
143
GetPropVal(const wxString & propName,wxString * value) const144 bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const
145 {
146 wxCHECK_MSG( value, false, wxT("value argument must not be NULL") );
147
148 wxXmlProperty *prop = GetProperties();
149
150 while (prop)
151 {
152 if (prop->GetName() == propName)
153 {
154 *value = prop->GetValue();
155 return true;
156 }
157 prop = prop->GetNext();
158 }
159
160 return false;
161 }
162
GetPropVal(const wxString & propName,const wxString & defaultVal) const163 wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& defaultVal) const
164 {
165 wxString tmp;
166 if (GetPropVal(propName, &tmp))
167 return tmp;
168
169 return defaultVal;
170 }
171
AddChild(wxXmlNode * child)172 void wxXmlNode::AddChild(wxXmlNode *child)
173 {
174 if (m_children == NULL)
175 m_children = child;
176 else
177 {
178 wxXmlNode *ch = m_children;
179 while (ch->m_next) ch = ch->m_next;
180 ch->m_next = child;
181 }
182 child->m_next = NULL;
183 child->m_parent = this;
184 }
185
InsertChild(wxXmlNode * child,wxXmlNode * before_node)186 bool wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node)
187 {
188 wxCHECK_MSG(before_node == NULL || before_node->GetParent() == this, false,
189 wxT("wxXmlNode::InsertChild - the node has incorrect parent"));
190 wxCHECK_MSG(child, false, wxT("Cannot insert a NULL pointer!"));
191
192 if (m_children == before_node)
193 m_children = child;
194 else if (m_children == NULL)
195 {
196 if (before_node != NULL)
197 return false; // we have no children so we don't need to search
198 m_children = child;
199 }
200 else if (before_node == NULL)
201 {
202 // prepend child
203 child->m_parent = this;
204 child->m_next = m_children;
205 m_children = child;
206 return true;
207 }
208 else
209 {
210 wxXmlNode *ch = m_children;
211 while (ch && ch->m_next != before_node) ch = ch->m_next;
212 if (!ch)
213 return false; // before_node not found
214 ch->m_next = child;
215 }
216
217 child->m_parent = this;
218 child->m_next = before_node;
219 return true;
220 }
221
222 // inserts a new node right after 'precedingNode'
InsertChildAfter(wxXmlNode * child,wxXmlNode * precedingNode)223 bool wxXmlNode::InsertChildAfter(wxXmlNode *child, wxXmlNode *precedingNode)
224 {
225 wxCHECK_MSG( child, false, wxT("cannot insert a NULL node!") );
226 wxCHECK_MSG( child->m_parent == NULL, false, wxT("node already has a parent") );
227 wxCHECK_MSG( child->m_next == NULL, false, wxT("node already has m_next") );
228 wxCHECK_MSG( precedingNode == NULL || precedingNode->m_parent == this, false,
229 wxT("precedingNode has wrong parent") );
230
231 if ( precedingNode )
232 {
233 child->m_next = precedingNode->m_next;
234 precedingNode->m_next = child;
235 }
236 else // precedingNode == NULL
237 {
238 wxCHECK_MSG( m_children == NULL, false,
239 wxT("NULL precedingNode only makes sense when there are no children") );
240
241 child->m_next = m_children;
242 m_children = child;
243 }
244
245 child->m_parent = this;
246 return true;
247 }
248
249
RemoveChild(wxXmlNode * child)250 bool wxXmlNode::RemoveChild(wxXmlNode *child)
251 {
252 if (m_children == NULL)
253 return false;
254 else if (m_children == child)
255 {
256 m_children = child->m_next;
257 child->m_parent = NULL;
258 child->m_next = NULL;
259 return true;
260 }
261 else
262 {
263 wxXmlNode *ch = m_children;
264 while (ch->m_next)
265 {
266 if (ch->m_next == child)
267 {
268 ch->m_next = child->m_next;
269 child->m_parent = NULL;
270 child->m_next = NULL;
271 return true;
272 }
273 ch = ch->m_next;
274 }
275 return false;
276 }
277 }
278
AddProperty(const wxString & name,const wxString & value)279 void wxXmlNode::AddProperty(const wxString& name, const wxString& value)
280 {
281 AddProperty(new wxXmlProperty(name, value, NULL));
282 }
283
AddProperty(wxXmlProperty * prop)284 void wxXmlNode::AddProperty(wxXmlProperty *prop)
285 {
286 if (m_properties == NULL)
287 m_properties = prop;
288 else
289 {
290 wxXmlProperty *p = m_properties;
291 while (p->GetNext()) p = p->GetNext();
292 p->SetNext(prop);
293 }
294 }
295
DeleteProperty(const wxString & name)296 bool wxXmlNode::DeleteProperty(const wxString& name)
297 {
298 wxXmlProperty *prop;
299
300 if (m_properties == NULL)
301 return false;
302
303 else if (m_properties->GetName() == name)
304 {
305 prop = m_properties;
306 m_properties = prop->GetNext();
307 prop->SetNext(NULL);
308 delete prop;
309 return true;
310 }
311
312 else
313 {
314 wxXmlProperty *p = m_properties;
315 while (p->GetNext())
316 {
317 if (p->GetNext()->GetName() == name)
318 {
319 prop = p->GetNext();
320 p->SetNext(prop->GetNext());
321 prop->SetNext(NULL);
322 delete prop;
323 return true;
324 }
325 p = p->GetNext();
326 }
327 return false;
328 }
329 }
330
GetNodeContent() const331 wxString wxXmlNode::GetNodeContent() const
332 {
333 wxXmlNode *n = GetChildren();
334
335 while (n)
336 {
337 if (n->GetType() == wxXML_TEXT_NODE ||
338 n->GetType() == wxXML_CDATA_SECTION_NODE)
339 return n->GetContent();
340 n = n->GetNext();
341 }
342 return wxEmptyString;
343 }
344
GetDepth(wxXmlNode * grandparent) const345 int wxXmlNode::GetDepth(wxXmlNode *grandparent) const
346 {
347 const wxXmlNode *n = this;
348 int ret = -1;
349
350 do
351 {
352 ret++;
353 n = n->GetParent();
354 if (n == grandparent)
355 return ret;
356
357 } while (n);
358
359 return wxNOT_FOUND;
360 }
361
IsWhitespaceOnly() const362 bool wxXmlNode::IsWhitespaceOnly() const
363 {
364 return wxIsWhiteOnly(m_content);
365 }
366
367
368
369 //-----------------------------------------------------------------------------
370 // wxXmlDocument
371 //-----------------------------------------------------------------------------
372
wxXmlDocument()373 wxXmlDocument::wxXmlDocument()
374 : m_version(wxT("1.0")), m_fileEncoding(wxT("utf-8")), m_root(NULL)
375 {
376 #if !wxUSE_UNICODE
377 m_encoding = wxT("UTF-8");
378 #endif
379 }
380
wxXmlDocument(const wxString & filename,const wxString & encoding)381 wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding)
382 :wxObject(), m_root(NULL)
383 {
384 if ( !Load(filename, encoding) )
385 {
386 wxDELETE(m_root);
387 }
388 }
389
wxXmlDocument(wxInputStream & stream,const wxString & encoding)390 wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding)
391 :wxObject(), m_root(NULL)
392 {
393 if ( !Load(stream, encoding) )
394 {
395 wxDELETE(m_root);
396 }
397 }
398
wxXmlDocument(const wxXmlDocument & doc)399 wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc)
400 :wxObject()
401 {
402 DoCopy(doc);
403 }
404
operator =(const wxXmlDocument & doc)405 wxXmlDocument& wxXmlDocument::operator=(const wxXmlDocument& doc)
406 {
407 wxDELETE(m_root);
408 DoCopy(doc);
409 return *this;
410 }
411
DoCopy(const wxXmlDocument & doc)412 void wxXmlDocument::DoCopy(const wxXmlDocument& doc)
413 {
414 m_version = doc.m_version;
415 #if !wxUSE_UNICODE
416 m_encoding = doc.m_encoding;
417 #endif
418 m_fileEncoding = doc.m_fileEncoding;
419
420 if (doc.m_root)
421 m_root = new wxXmlNode(*doc.m_root);
422 else
423 m_root = NULL;
424 }
425
Load(const wxString & filename,const wxString & encoding,int flags)426 bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding, int flags)
427 {
428 wxFileInputStream stream(filename);
429 if (!stream.Ok())
430 return false;
431 return Load(stream, encoding, flags);
432 }
433
Save(const wxString & filename,int indentstep) const434 bool wxXmlDocument::Save(const wxString& filename, int indentstep) const
435 {
436 wxFileOutputStream stream(filename);
437 if (!stream.Ok())
438 return false;
439 return Save(stream, indentstep);
440 }
441
442
443
444 //-----------------------------------------------------------------------------
445 // wxXmlDocument loading routines
446 //-----------------------------------------------------------------------------
447
448 // converts Expat-produced string in UTF-8 into wxString using the specified
449 // conv or keep in UTF-8 if conv is NULL
CharToString(wxMBConv * conv,const char * s,size_t len=wxString::npos)450 static wxString CharToString(wxMBConv *conv,
451 const char *s, size_t len = wxString::npos)
452 {
453 #if wxUSE_UNICODE
454 wxUnusedVar(conv);
455
456 return wxString(s, wxConvUTF8, len);
457 #else // !wxUSE_UNICODE
458 if ( conv )
459 {
460 // there can be no embedded NULs in this string so we don't need the
461 // output length, it will be NUL-terminated
462 const wxWCharBuffer wbuf(
463 wxConvUTF8.cMB2WC(s, len == wxString::npos ? wxNO_LEN : len, NULL));
464
465 return wxString(wbuf, *conv);
466 }
467 else // already in UTF-8, no conversion needed
468 {
469 return wxString(s, len != wxString::npos ? len : strlen(s));
470 }
471 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
472 }
473
474 // returns true if the given string contains only whitespaces
wxIsWhiteOnly(const wxChar * buf)475 bool wxIsWhiteOnly(const wxChar *buf)
476 {
477 for (const wxChar *c = buf; *c != wxT('\0'); c++)
478 if (*c != wxT(' ') && *c != wxT('\t') && *c != wxT('\n') && *c != wxT('\r'))
479 return false;
480 return true;
481 }
482
483
484 struct wxXmlParsingContext
485 {
wxXmlParsingContextwxXmlParsingContext486 wxXmlParsingContext()
487 : conv(NULL),
488 root(NULL),
489 node(NULL),
490 lastChild(NULL),
491 lastAsText(NULL),
492 removeWhiteOnlyNodes(false)
493 {}
494
495 wxMBConv *conv;
496 wxXmlNode *root;
497 wxXmlNode *node; // the node being parsed
498 wxXmlNode *lastChild; // the last child of "node"
499 wxXmlNode *lastAsText; // the last _text_ child of "node"
500 wxString encoding;
501 wxString version;
502 bool removeWhiteOnlyNodes;
503 };
504
505 // checks that ctx->lastChild is in consistent state
506 #define ASSERT_LAST_CHILD_OK(ctx) \
507 wxASSERT( ctx->lastChild == NULL || \
508 ctx->lastChild->GetNext() == NULL ); \
509 wxASSERT( ctx->lastChild == NULL || \
510 ctx->lastChild->GetParent() == ctx->node )
511
512 extern "C" {
StartElementHnd(void * userData,const char * name,const char ** atts)513 static void StartElementHnd(void *userData, const char *name, const char **atts)
514 {
515 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
516 wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, CharToString(ctx->conv, name));
517 const char **a = atts;
518 while (*a)
519 {
520 node->AddProperty(CharToString(ctx->conv, a[0]), CharToString(ctx->conv, a[1]));
521 a += 2;
522 }
523 if (ctx->root == NULL)
524 {
525 ctx->root = node;
526 }
527 else
528 {
529 ASSERT_LAST_CHILD_OK(ctx);
530 ctx->node->InsertChildAfter(node, ctx->lastChild);
531 }
532
533 ctx->lastAsText = NULL;
534 ctx->lastChild = NULL; // our new node "node" has no children yet
535
536 ctx->node = node;
537 }
538 }
539
540 extern "C" {
EndElementHnd(void * userData,const char * WXUNUSED (name))541 static void EndElementHnd(void *userData, const char* WXUNUSED(name))
542 {
543 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
544
545 // we're exiting the last children of ctx->node->GetParent() and going
546 // back one level up, so current value of ctx->node points to the last
547 // child of ctx->node->GetParent()
548 ctx->lastChild = ctx->node;
549
550 ctx->node = ctx->node->GetParent();
551 ctx->lastAsText = NULL;
552 }
553 }
554
555 extern "C" {
TextHnd(void * userData,const char * s,int len)556 static void TextHnd(void *userData, const char *s, int len)
557 {
558 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
559 wxString str = CharToString(ctx->conv, s, len);
560
561 if (ctx->lastAsText)
562 {
563 ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() + str);
564 }
565 else
566 {
567 bool whiteOnly = false;
568 if (ctx->removeWhiteOnlyNodes)
569 whiteOnly = wxIsWhiteOnly(str);
570
571 if (!whiteOnly)
572 {
573 wxXmlNode *textnode =
574 new wxXmlNode(wxXML_TEXT_NODE, wxT("text"), str);
575
576 ASSERT_LAST_CHILD_OK(ctx);
577 ctx->node->InsertChildAfter(textnode, ctx->lastChild);
578 ctx->lastChild= ctx->lastAsText = textnode;
579 }
580 }
581 }
582 }
583
584 extern "C" {
StartCdataHnd(void * userData)585 static void StartCdataHnd(void *userData)
586 {
587 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
588
589 wxXmlNode *textnode =
590 new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxT("cdata"),wxT(""));
591
592 ASSERT_LAST_CHILD_OK(ctx);
593 ctx->node->InsertChildAfter(textnode, ctx->lastChild);
594 ctx->lastChild= ctx->lastAsText = textnode;
595 }
596 }
597
598 extern "C" {
CommentHnd(void * userData,const char * data)599 static void CommentHnd(void *userData, const char *data)
600 {
601 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
602
603 if (ctx->node)
604 {
605 // VS: ctx->node == NULL happens if there is a comment before
606 // the root element (e.g. wxDesigner's output). We ignore such
607 // comments, no big deal...
608 wxXmlNode *commentnode =
609 new wxXmlNode(wxXML_COMMENT_NODE,
610 wxT("comment"), CharToString(ctx->conv, data));
611 ASSERT_LAST_CHILD_OK(ctx);
612 ctx->node->InsertChildAfter(commentnode, ctx->lastChild);
613 ctx->lastChild = commentnode;
614 }
615 ctx->lastAsText = NULL;
616 }
617 }
618
619 extern "C" {
DefaultHnd(void * userData,const char * s,int len)620 static void DefaultHnd(void *userData, const char *s, int len)
621 {
622 // XML header:
623 if (len > 6 && memcmp(s, "<?xml ", 6) == 0)
624 {
625 wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData;
626
627 wxString buf = CharToString(ctx->conv, s, (size_t)len);
628 int pos;
629 pos = buf.Find(wxT("encoding="));
630 if (pos != wxNOT_FOUND)
631 ctx->encoding = buf.Mid(pos + 10).BeforeFirst(buf[(size_t)pos+9]);
632 pos = buf.Find(wxT("version="));
633 if (pos != wxNOT_FOUND)
634 ctx->version = buf.Mid(pos + 9).BeforeFirst(buf[(size_t)pos+8]);
635 }
636 }
637 }
638
639 extern "C" {
UnknownEncodingHnd(void * WXUNUSED (encodingHandlerData),const XML_Char * name,XML_Encoding * info)640 static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData),
641 const XML_Char *name, XML_Encoding *info)
642 {
643 // We must build conversion table for expat. The easiest way to do so
644 // is to let wxCSConv convert as string containing all characters to
645 // wide character representation:
646 wxString str(name, wxConvLibc);
647 wxCSConv conv(str);
648 char mbBuf[2];
649 wchar_t wcBuf[10];
650 size_t i;
651
652 mbBuf[1] = 0;
653 info->map[0] = 0;
654 for (i = 0; i < 255; i++)
655 {
656 mbBuf[0] = (char)(i+1);
657 if (conv.MB2WC(wcBuf, mbBuf, 2) == (size_t)-1)
658 {
659 // invalid/undefined byte in the encoding:
660 info->map[i+1] = -1;
661 }
662 info->map[i+1] = (int)wcBuf[0];
663 }
664
665 info->data = NULL;
666 info->convert = NULL;
667 info->release = NULL;
668
669 return 1;
670 }
671 }
672
Load(wxInputStream & stream,const wxString & encoding,int flags)673 bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int flags)
674 {
675 #if wxUSE_UNICODE
676 (void)encoding;
677 #else
678 m_encoding = encoding;
679 #endif
680
681 const size_t BUFSIZE = 1024;
682 char buf[BUFSIZE];
683 wxXmlParsingContext ctx;
684 bool done;
685 XML_Parser parser = XML_ParserCreate(NULL);
686
687 ctx.root = ctx.node = NULL;
688 ctx.encoding = wxT("UTF-8"); // default in absence of encoding=""
689 ctx.conv = NULL;
690 #if !wxUSE_UNICODE
691 if ( encoding.CmpNoCase(wxT("UTF-8")) != 0 )
692 ctx.conv = new wxCSConv(encoding);
693 #endif
694 ctx.removeWhiteOnlyNodes = (flags & wxXMLDOC_KEEP_WHITESPACE_NODES) == 0;
695
696 XML_SetUserData(parser, (void*)&ctx);
697 XML_SetElementHandler(parser, StartElementHnd, EndElementHnd);
698 XML_SetCharacterDataHandler(parser, TextHnd);
699 XML_SetStartCdataSectionHandler(parser, StartCdataHnd);
700 XML_SetCommentHandler(parser, CommentHnd);
701 XML_SetDefaultHandler(parser, DefaultHnd);
702 XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL);
703
704 bool ok = true;
705 do
706 {
707 size_t len = stream.Read(buf, BUFSIZE).LastRead();
708 done = (len < BUFSIZE);
709 if (!XML_Parse(parser, buf, len, done))
710 {
711 wxString error(XML_ErrorString(XML_GetErrorCode(parser)),
712 *wxConvCurrent);
713 wxLogError(_("XML parsing error: '%s' at line %d"),
714 error.c_str(),
715 XML_GetCurrentLineNumber(parser));
716 ok = false;
717 break;
718 }
719 } while (!done);
720
721 if (ok)
722 {
723 if (!ctx.version.empty())
724 SetVersion(ctx.version);
725 if (!ctx.encoding.empty())
726 SetFileEncoding(ctx.encoding);
727 SetRoot(ctx.root);
728 }
729 else
730 {
731 delete ctx.root;
732 }
733
734 XML_ParserFree(parser);
735 #if !wxUSE_UNICODE
736 if ( ctx.conv )
737 delete ctx.conv;
738 #endif
739
740 return ok;
741
742 }
743
744
745
746 //-----------------------------------------------------------------------------
747 // wxXmlDocument saving routines
748 //-----------------------------------------------------------------------------
749
750 // write string to output:
OutputString(wxOutputStream & stream,const wxString & str,wxMBConv * convMem=NULL,wxMBConv * convFile=NULL)751 inline static void OutputString(wxOutputStream& stream, const wxString& str,
752 wxMBConv *convMem = NULL,
753 wxMBConv *convFile = NULL)
754 {
755 if (str.empty())
756 return;
757
758 #if wxUSE_UNICODE
759 wxUnusedVar(convMem);
760
761 const wxWX2MBbuf buf(str.mb_str(*(convFile ? convFile : &wxConvUTF8)));
762 if ( !buf )
763 return;
764 stream.Write((const char*)buf, strlen((const char*)buf));
765 #else // !wxUSE_UNICODE
766 if ( convFile && convMem )
767 {
768 wxString str2(str.wc_str(*convMem), *convFile);
769 stream.Write(str2.mb_str(), str2.Len());
770 }
771 else // no conversions to do
772 {
773 stream.Write(str.mb_str(), str.Len());
774 }
775 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
776 }
777
778
779 enum EscapingMode
780 {
781 Escape_Text,
782 Escape_Attribute
783 };
784
785 // Same as above, but create entities first.
786 // Translates '<' to "<", '>' to ">" and so on, according to the spec:
787 // http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping
OutputEscapedString(wxOutputStream & stream,const wxString & str,wxMBConv * convMem,wxMBConv * convFile,EscapingMode mode)788 static void OutputEscapedString(wxOutputStream& stream,
789 const wxString& str,
790 wxMBConv *convMem,
791 wxMBConv *convFile,
792 EscapingMode mode)
793 {
794 const size_t len = str.Len();
795
796 wxString escaped;
797 escaped.reserve( len );
798
799 for (size_t i = 0; i < len; i++)
800 {
801 const wxChar c = str.GetChar(i);
802
803 switch ( c )
804 {
805 case '<':
806 escaped.append(wxT("<"));
807 break;
808 case '>':
809 escaped.append(wxT(">"));
810 break;
811 case '&':
812 escaped.append(wxT("&"));
813 break;
814 case '\r':
815 escaped.append(wxT("
"));
816 break;
817 default:
818 if ( mode == Escape_Attribute )
819 {
820 switch ( c )
821 {
822 case '"':
823 escaped.append(wxT("""));
824 break;
825 case '\t':
826 escaped.append(wxT("	"));
827 break;
828 case '\n':
829 escaped.append(wxT("
"));
830 break;
831 default:
832 escaped.append(c);
833 }
834 }
835 else
836 {
837 escaped.append(c);
838 }
839 }
840 }
841 OutputString(stream, escaped, convMem, convFile);
842 }
843
OutputIndentation(wxOutputStream & stream,int indent)844 inline static void OutputIndentation(wxOutputStream& stream, int indent)
845 {
846 wxString str = wxT("\n");
847 for (int i = 0; i < indent; i++)
848 str << wxT(' ') << wxT(' ');
849 OutputString(stream, str);
850 }
851
OutputNode(wxOutputStream & stream,wxXmlNode * node,int indent,wxMBConv * convMem,wxMBConv * convFile,int indentstep)852 static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent,
853 wxMBConv *convMem, wxMBConv *convFile, int indentstep)
854 {
855 wxXmlNode *n, *prev;
856 wxXmlProperty *prop;
857
858 switch (node->GetType())
859 {
860 case wxXML_CDATA_SECTION_NODE:
861 OutputString( stream, wxT("<![CDATA["));
862 OutputString( stream, node->GetContent() );
863 OutputString( stream, wxT("]]>") );
864 break;
865
866 case wxXML_TEXT_NODE:
867 OutputEscapedString(stream, node->GetContent(),
868 convMem, convFile,
869 Escape_Text);
870 break;
871
872 case wxXML_ELEMENT_NODE:
873 OutputString(stream, wxT("<"));
874 OutputString(stream, node->GetName());
875
876 prop = node->GetProperties();
877 while (prop)
878 {
879 OutputString(stream, wxT(" ") + prop->GetName() + wxT("=\""));
880 OutputEscapedString(stream, prop->GetValue(),
881 convMem, convFile,
882 Escape_Attribute);
883 OutputString(stream, wxT("\""));
884 prop = prop->GetNext();
885 }
886
887 if (node->GetChildren())
888 {
889 OutputString(stream, wxT(">"));
890 prev = NULL;
891 n = node->GetChildren();
892 while (n)
893 {
894 if (indentstep >= 0 && n && n->GetType() != wxXML_TEXT_NODE)
895 OutputIndentation(stream, indent + indentstep);
896 OutputNode(stream, n, indent + indentstep, convMem, convFile, indentstep);
897 prev = n;
898 n = n->GetNext();
899 }
900 if (indentstep >= 0 && prev && prev->GetType() != wxXML_TEXT_NODE)
901 OutputIndentation(stream, indent);
902 OutputString(stream, wxT("</"));
903 OutputString(stream, node->GetName());
904 OutputString(stream, wxT(">"));
905 }
906 else
907 OutputString(stream, wxT("/>"));
908 break;
909
910 case wxXML_COMMENT_NODE:
911 OutputString(stream, wxT("<!--"));
912 OutputString(stream, node->GetContent(), convMem, convFile);
913 OutputString(stream, wxT("-->"));
914 break;
915
916 default:
917 wxFAIL_MSG(wxT("unsupported node type"));
918 }
919 }
920
Save(wxOutputStream & stream,int indentstep) const921 bool wxXmlDocument::Save(wxOutputStream& stream, int indentstep) const
922 {
923 if ( !IsOk() )
924 return false;
925
926 wxString s;
927
928 wxMBConv *convMem = NULL,
929 *convFile;
930
931 #if wxUSE_UNICODE
932 convFile = new wxCSConv(GetFileEncoding());
933 convMem = NULL;
934 #else
935 if ( GetFileEncoding().CmpNoCase(GetEncoding()) != 0 )
936 {
937 convFile = new wxCSConv(GetFileEncoding());
938 convMem = new wxCSConv(GetEncoding());
939 }
940 else // file and in-memory encodings are the same, no conversion needed
941 {
942 convFile =
943 convMem = NULL;
944 }
945 #endif
946
947 s.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
948 GetVersion().c_str(), GetFileEncoding().c_str());
949 OutputString(stream, s);
950
951 OutputNode(stream, GetRoot(), 0, convMem, convFile, indentstep);
952 OutputString(stream, wxT("\n"));
953
954 delete convFile;
955 delete convMem;
956
957 return true;
958 }
959
960 #endif // wxUSE_XML
961