1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        tests/xml/xmltest.cpp
3 // Purpose:     XML classes unit test
4 // Author:      Vaclav Slavik
5 // Created:     2008-03-29
6 // Copyright:   (c) 2008 Vaclav Slavik
7 ///////////////////////////////////////////////////////////////////////////////
8 
9 // ----------------------------------------------------------------------------
10 // headers
11 // ----------------------------------------------------------------------------
12 
13 #include "testprec.h"
14 
15 
16 #ifndef WX_PRECOMP
17     #include "wx/wx.h"
18 #endif // WX_PRECOMP
19 
20 #include "wx/xml/xml.h"
21 #include "wx/scopedptr.h"
22 #include "wx/sstream.h"
23 
24 #include <stdarg.h>
25 
26 // ----------------------------------------------------------------------------
27 // helpers for testing XML tree
28 // ----------------------------------------------------------------------------
29 
30 namespace
31 {
32 
CheckXml(const wxXmlNode * n,...)33 void CheckXml(const wxXmlNode *n, ...)
34 {
35     va_list args;
36     va_start(args, n);
37 
38     wxXmlNode *child = n->GetChildren();
39 
40     for (;;)
41     {
42         const char *childName = va_arg(args, char*);
43         if ( childName == NULL )
44             break;
45 
46         CPPUNIT_ASSERT( child );
47         CPPUNIT_ASSERT_EQUAL( childName, child->GetName() );
48         CPPUNIT_ASSERT( child->GetChildren() == NULL );
49         CPPUNIT_ASSERT( child->GetParent() == n );
50 
51         child = child->GetNext();
52     }
53 
54     va_end(args);
55 
56     CPPUNIT_ASSERT( child == NULL ); // no more children
57 }
58 
59 } // anon namespace
60 
61 // ----------------------------------------------------------------------------
62 // test class
63 // ----------------------------------------------------------------------------
64 
65 class XmlTestCase : public CppUnit::TestCase
66 {
67 public:
XmlTestCase()68     XmlTestCase() {}
69 
70 private:
71     CPPUNIT_TEST_SUITE( XmlTestCase );
72         CPPUNIT_TEST( InsertChild );
73         CPPUNIT_TEST( InsertChildAfter );
74         CPPUNIT_TEST( LoadSave );
75         CPPUNIT_TEST( CDATA );
76         CPPUNIT_TEST( PI );
77         CPPUNIT_TEST( Escaping );
78         CPPUNIT_TEST( DetachRoot );
79         CPPUNIT_TEST( AppendToProlog );
80         CPPUNIT_TEST( SetRoot );
81         CPPUNIT_TEST( CopyNode );
82         CPPUNIT_TEST( CopyDocument );
83         CPPUNIT_TEST( Doctype );
84     CPPUNIT_TEST_SUITE_END();
85 
86     void InsertChild();
87     void InsertChildAfter();
88     void LoadSave();
89     void CDATA();
90     void PI();
91     void Escaping();
92     void DetachRoot();
93     void AppendToProlog();
94     void SetRoot();
95     void CopyNode();
96     void CopyDocument();
97     void Doctype();
98 
99     wxDECLARE_NO_COPY_CLASS(XmlTestCase);
100 };
101 
102 // register in the unnamed registry so that these tests are run by default
103 CPPUNIT_TEST_SUITE_REGISTRATION( XmlTestCase );
104 
105 // also include in its own registry so that these tests can be run alone
106 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( XmlTestCase, "XmlTestCase" );
107 
InsertChild()108 void XmlTestCase::InsertChild()
109 {
110     wxScopedPtr<wxXmlNode> root(new wxXmlNode(wxXML_ELEMENT_NODE, "root"));
111     root->AddChild(new wxXmlNode(wxXML_ELEMENT_NODE, "1"));
112     wxXmlNode *two = new wxXmlNode(wxXML_ELEMENT_NODE, "2");
113     root->AddChild(two);
114     root->AddChild(new wxXmlNode(wxXML_ELEMENT_NODE, "3"));
115     CheckXml(root.get(), "1", "2", "3", NULL);
116 
117     // check inserting in front:
118     root->InsertChild(new wxXmlNode(wxXML_ELEMENT_NODE, "A"), NULL);
119     CheckXml(root.get(), "A", "1", "2", "3", NULL);
120     root->InsertChild(new wxXmlNode(wxXML_ELEMENT_NODE, "B"), root->GetChildren());
121     CheckXml(root.get(), "B", "A", "1", "2", "3", NULL);
122 
123     // and in the middle:
124     root->InsertChild(new wxXmlNode(wxXML_ELEMENT_NODE, "C"), two);
125     CheckXml(root.get(), "B", "A", "1", "C", "2", "3", NULL);
126 }
127 
InsertChildAfter()128 void XmlTestCase::InsertChildAfter()
129 {
130     wxScopedPtr<wxXmlNode> root(new wxXmlNode(wxXML_ELEMENT_NODE, "root"));
131 
132     root->InsertChildAfter(new wxXmlNode(wxXML_ELEMENT_NODE, "1"), NULL);
133     CheckXml(root.get(), "1", NULL);
134 
135     wxXmlNode *two = new wxXmlNode(wxXML_ELEMENT_NODE, "2");
136     root->AddChild(two);
137     wxXmlNode *three = new wxXmlNode(wxXML_ELEMENT_NODE, "3");
138     root->AddChild(three);
139     CheckXml(root.get(), "1", "2", "3", NULL);
140 
141     // check inserting in the middle:
142     root->InsertChildAfter(new wxXmlNode(wxXML_ELEMENT_NODE, "A"), root->GetChildren());
143     CheckXml(root.get(), "1", "A", "2", "3", NULL);
144     root->InsertChildAfter(new wxXmlNode(wxXML_ELEMENT_NODE, "B"), two);
145     CheckXml(root.get(), "1", "A", "2", "B", "3", NULL);
146 
147     // and at the end:
148     root->InsertChildAfter(new wxXmlNode(wxXML_ELEMENT_NODE, "C"), three);
149     CheckXml(root.get(), "1", "A", "2", "B", "3", "C", NULL);
150 }
151 
LoadSave()152 void XmlTestCase::LoadSave()
153 {
154     // NB: this is not real XRC but rather some XRC-like XML fragment which
155     //     exercises different XML constructs to check that they're saved back
156     //     correctly
157     //
158     // Also note that there should be no blank lines here as they disappear
159     // after saving.
160     const char *xmlText =
161 "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
162 "<resource xmlns=\"http://www.wxwidgets.org/wxxrc\" version=\"2.3.0.1\">\n"
163 "  <!-- Test comment -->\n"
164 "  <object class=\"wxDialog\" name=\"my_dialog\">\n"
165 "    <children>\n"
166 "      <grandchild id=\"1\"/>\n"
167 "    </children>\n"
168 "    <subobject/>\n"
169 "  </object>\n"
170 "</resource>\n"
171     ;
172 
173     wxStringInputStream sis(xmlText);
174 
175     wxXmlDocument doc;
176     CPPUNIT_ASSERT( doc.Load(sis) );
177 
178     wxStringOutputStream sos;
179     CPPUNIT_ASSERT( doc.Save(sos) );
180 
181     CPPUNIT_ASSERT_EQUAL( xmlText, sos.GetString() );
182 
183 
184 #if wxUSE_UNICODE
185     const char *utf8xmlText =
186 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
187 "<word>\n"
188 "  <lang name=\"fr\">\xc3\xa9t\xc3\xa9</lang>\n"
189 "  <lang name=\"ru\">\xd0\xbb\xd0\xb5\xd1\x82\xd0\xbe</lang>\n"
190 "</word>\n"
191     ;
192 
193     wxStringInputStream sis8(wxString::FromUTF8(utf8xmlText));
194     CPPUNIT_ASSERT( doc.Load(sis8) );
195 
196     // this contents can't be represented in Latin-1 as it contains Cyrillic
197     // letters
198     doc.SetFileEncoding("ISO-8859-1");
199     CPPUNIT_ASSERT( !doc.Save(sos) );
200 
201     // but it should work in UTF-8
202     wxStringOutputStream sos8;
203     doc.SetFileEncoding("UTF-8");
204     CPPUNIT_ASSERT( doc.Save(sos8) );
205     CPPUNIT_ASSERT_EQUAL( wxString(utf8xmlText),
206                           wxString(sos8.GetString().ToUTF8()) );
207 #endif // wxUSE_UNICODE
208 
209     const char *xmlTextProlog =
210 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
211 "<!DOCTYPE resource PUBLIC \"Public-ID\" 'System\"ID\"'>\n"
212 "<!-- Prolog comment -->\n"
213 "<?xml-stylesheet href=\"style.css\" type=\"text/css\"?>\n"
214 "<resource xmlns=\"http://www.wxwidgets.org/wxxrc\" version=\"2.3.0.1\">\n"
215 "  <!-- Test comment -->\n"
216 "  <object class=\"wxDialog\" name=\"my_dialog\">\n"
217 "    <children>\n"
218 "      <grandchild id=\"1\"/>\n"
219 "    </children>\n"
220 "    <subobject/>\n"
221 "  </object>\n"
222 "</resource>\n"
223 "<!-- Trailing comment -->\n"
224     ;
225 
226     wxStringInputStream sisp(xmlTextProlog);
227     CPPUNIT_ASSERT( doc.Load(sisp, "UTF-8") );
228 
229     wxStringOutputStream sosp;
230     CPPUNIT_ASSERT( doc.Save(sosp) );
231 
232     CPPUNIT_ASSERT_EQUAL( xmlTextProlog, sosp.GetString() );
233 }
234 
CDATA()235 void XmlTestCase::CDATA()
236 {
237     const char *xmlText =
238         "<?xml version=\"1.0\" encoding=\"windows-1252\"?>\n"
239         "<name>\n"
240         "  <![CDATA[Giovanni Mittone]]>\n"
241         "</name>\n"
242     ;
243 
244     wxStringInputStream sis(xmlText);
245     wxXmlDocument doc;
246     CPPUNIT_ASSERT( doc.Load(sis) );
247 
248     wxXmlNode *n = doc.GetRoot();
249     CPPUNIT_ASSERT( n );
250 
251     n = n->GetChildren();
252     CPPUNIT_ASSERT( n );
253 
254     // check that both leading ("  ") and trailing white space is not part of
255     // the node contents when CDATA is used and wxXMLDOC_KEEP_WHITESPACE_NODES
256     // is not
257     CPPUNIT_ASSERT_EQUAL( "Giovanni Mittone", n->GetContent() );
258 }
259 
PI()260 void XmlTestCase::PI()
261 {
262     const char *xmlText =
263         "<?xml version=\"1.0\" encoding=\"windows-1252\"?>\n"
264         "<root>\n"
265         "  <?robot index=\"no\" follow=\"no\"?>\n"
266         "</root>\n"
267     ;
268 
269     wxStringInputStream sis(xmlText);
270     wxXmlDocument doc;
271     CPPUNIT_ASSERT( doc.Load(sis) );
272 
273     wxXmlNode *n = doc.GetRoot();
274     CPPUNIT_ASSERT( n );
275 
276     n = n->GetChildren();
277     CPPUNIT_ASSERT( n );
278 
279     CPPUNIT_ASSERT_EQUAL( "index=\"no\" follow=\"no\"", n->GetContent() );
280 }
281 
Escaping()282 void XmlTestCase::Escaping()
283 {
284     // Verify that attribute values are escaped correctly, see
285     // https://trac.wxwidgets.org/ticket/12275
286 
287     const char *xmlText =
288 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
289 "<root text=\"hello&#xD;&#xA;this is a new line\">\n"
290 "  <x/>\n"
291 "</root>\n"
292     ;
293 
294     wxStringInputStream sis(xmlText);
295 
296     wxXmlDocument doc;
297     CPPUNIT_ASSERT( doc.Load(sis) );
298 
299     wxStringOutputStream sos;
300     CPPUNIT_ASSERT( doc.Save(sos) );
301 
302     CPPUNIT_ASSERT_EQUAL( xmlText, sos.GetString() );
303 }
304 
DetachRoot()305 void XmlTestCase::DetachRoot()
306 {
307     const char *xmlTextProlog =
308 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
309 "<!-- Prolog comment -->\n"
310 "<?xml-stylesheet href=\"style.css\" type=\"text/css\"?>\n"
311 "<resource xmlns=\"http://www.wxwidgets.org/wxxrc\" version=\"2.3.0.1\">\n"
312 "  <!-- Test comment -->\n"
313 "  <object class=\"wxDialog\" name=\"my_dialog\">\n"
314 "    <children>\n"
315 "      <grandchild id=\"1\"/>\n"
316 "    </children>\n"
317 "    <subobject/>\n"
318 "  </object>\n"
319 "</resource>\n"
320 "<!-- Trailing comment -->\n"
321     ;
322     const char *xmlTextHtm =
323 "<?xml version=\"1.0\" encoding=\"windows-1252\"?>\n"
324 "<html>\n"
325 "  <head>\n"
326 "    <title>Testing wxXml</title>\n"
327 "  </head>\n"
328 "  <body>\n"
329 "    <p>Some body text</p>\n"
330 "  </body>\n"
331 "</html>\n"
332     ;
333     wxXmlDocument doc;
334 
335     wxStringInputStream sish(xmlTextHtm);
336     CPPUNIT_ASSERT( doc.Load(sish) );
337 
338     wxXmlNode *root = doc.DetachRoot();
339 
340     wxStringInputStream sisp(xmlTextProlog);
341     CPPUNIT_ASSERT( doc.Load(sisp) );
342 
343     doc.SetRoot(root);
344 
345     wxStringOutputStream sos;
346     CPPUNIT_ASSERT( doc.Save(sos) );
347 
348     const char *xmlTextResult1 =
349 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
350 "<!-- Prolog comment -->\n"
351 "<?xml-stylesheet href=\"style.css\" type=\"text/css\"?>\n"
352 "<html>\n"
353 "  <head>\n"
354 "    <title>Testing wxXml</title>\n"
355 "  </head>\n"
356 "  <body>\n"
357 "    <p>Some body text</p>\n"
358 "  </body>\n"
359 "</html>\n"
360 "<!-- Trailing comment -->\n"
361     ;
362     CPPUNIT_ASSERT_EQUAL( xmlTextResult1, sos.GetString() );
363 
364     wxStringInputStream sisp2(xmlTextProlog);
365     CPPUNIT_ASSERT( doc.Load(sisp2) );
366 
367     root = doc.DetachRoot();
368 
369     wxStringInputStream sish2(xmlTextHtm);
370     CPPUNIT_ASSERT( doc.Load(sish2) );
371 
372     doc.SetRoot(root);
373 
374     wxStringOutputStream sos2;
375     CPPUNIT_ASSERT( doc.Save(sos2) );
376 
377     const char *xmlTextResult2 =
378 "<?xml version=\"1.0\" encoding=\"windows-1252\"?>\n"
379 "<resource xmlns=\"http://www.wxwidgets.org/wxxrc\" version=\"2.3.0.1\">\n"
380 "  <!-- Test comment -->\n"
381 "  <object class=\"wxDialog\" name=\"my_dialog\">\n"
382 "    <children>\n"
383 "      <grandchild id=\"1\"/>\n"
384 "    </children>\n"
385 "    <subobject/>\n"
386 "  </object>\n"
387 "</resource>\n"
388     ;
389     CPPUNIT_ASSERT_EQUAL( xmlTextResult2, sos2.GetString() );
390 }
391 
AppendToProlog()392 void XmlTestCase::AppendToProlog()
393 {
394     const char *xmlText =
395 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
396 "<root>\n"
397 "  <p>Some text</p>\n"
398 "</root>\n"
399     ;
400     wxXmlDocument rootdoc;
401     wxStringInputStream sis(xmlText);
402     CPPUNIT_ASSERT( rootdoc.Load(sis) );
403     wxXmlNode *root = rootdoc.DetachRoot();
404 
405     wxXmlNode *comment1 = new wxXmlNode(wxXML_COMMENT_NODE, "comment",
406         " 1st prolog entry ");
407     wxXmlNode *pi = new wxXmlNode(wxXML_PI_NODE, "xml-stylesheet",
408         "href=\"style.css\" type=\"text/css\"");
409     wxXmlNode *comment2 = new wxXmlNode(wxXML_COMMENT_NODE, "comment",
410         " 3rd prolog entry ");
411 
412     wxXmlDocument doc;
413     doc.AppendToProlog( comment1 );
414     doc.AppendToProlog( pi );
415     doc.SetRoot( root );
416     doc.AppendToProlog( comment2 );
417 
418     wxStringOutputStream sos;
419     CPPUNIT_ASSERT( doc.Save(sos) );
420 
421     const char *xmlTextResult =
422 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
423 "<!-- 1st prolog entry -->\n"
424 "<?xml-stylesheet href=\"style.css\" type=\"text/css\"?>\n"
425 "<!-- 3rd prolog entry -->\n"
426 "<root>\n"
427 "  <p>Some text</p>\n"
428 "</root>\n"
429     ;
430     CPPUNIT_ASSERT_EQUAL( xmlTextResult, sos.GetString() );
431 }
432 
SetRoot()433 void XmlTestCase::SetRoot()
434 {
435     wxXmlDocument doc;
436     CPPUNIT_ASSERT( !doc.IsOk() );
437     wxXmlNode *root = new wxXmlNode(wxXML_ELEMENT_NODE, "root");
438 
439     // Test for the problem of https://trac.wxwidgets.org/ticket/13135
440     doc.SetRoot( root );
441     wxXmlNode *docNode = doc.GetDocumentNode();
442     CPPUNIT_ASSERT( docNode );
443     CPPUNIT_ASSERT( root == docNode->GetChildren() );
444     CPPUNIT_ASSERT( doc.IsOk() );
445 
446     // Other tests.
447     CPPUNIT_ASSERT( docNode == root->GetParent() );
448     doc.SetRoot(NULL); // Removes from doc but dosn't free mem, doc node left.
449     CPPUNIT_ASSERT( !doc.IsOk() );
450 
451     wxXmlNode *comment = new wxXmlNode(wxXML_COMMENT_NODE, "comment", "Prolog Comment");
452     wxXmlNode *pi = new wxXmlNode(wxXML_PI_NODE, "target", "PI instructions");
453     doc.AppendToProlog(comment);
454     doc.SetRoot( root );
455     doc.AppendToProlog(pi);
456     CPPUNIT_ASSERT( doc.IsOk() );
457     wxXmlNode *node = docNode->GetChildren();
458     CPPUNIT_ASSERT( node );
459     CPPUNIT_ASSERT( node->GetType() == wxXML_COMMENT_NODE );
460     CPPUNIT_ASSERT( node->GetParent() == docNode );
461     node = node->GetNext();
462     CPPUNIT_ASSERT( node );
463     CPPUNIT_ASSERT( node->GetType() == wxXML_PI_NODE );
464     CPPUNIT_ASSERT( node->GetParent() == docNode );
465     node = node->GetNext();
466     CPPUNIT_ASSERT( node );
467     CPPUNIT_ASSERT( node->GetType() == wxXML_ELEMENT_NODE );
468     CPPUNIT_ASSERT( node->GetParent() == docNode );
469     node = node->GetNext();
470     CPPUNIT_ASSERT( !node );
471     doc.SetRoot(NULL);
472     CPPUNIT_ASSERT( !doc.IsOk() );
473     doc.SetRoot(root);
474     CPPUNIT_ASSERT( doc.IsOk() );
475 }
476 
CopyNode()477 void XmlTestCase::CopyNode()
478 {
479     const char *xmlText =
480 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
481 "<root>\n"
482 "  <first><sub1/><sub2/><sub3/></first>\n"
483 "  <second/>\n"
484 "</root>\n"
485     ;
486     wxXmlDocument doc;
487     wxStringInputStream sis(xmlText);
488     CPPUNIT_ASSERT( doc.Load(sis) );
489 
490     wxXmlNode* const root = doc.GetRoot();
491     CPPUNIT_ASSERT( root );
492 
493     wxXmlNode* const first = root->GetChildren();
494     CPPUNIT_ASSERT( first );
495 
496     wxXmlNode* const second = first->GetNext();
497     CPPUNIT_ASSERT( second );
498 
499     *first = *second;
500 
501     wxStringOutputStream sos;
502     CPPUNIT_ASSERT( doc.Save(sos) );
503 
504     const char *xmlTextResult =
505 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
506 "<root>\n"
507 "  <second/>\n"
508 "  <second/>\n"
509 "</root>\n"
510     ;
511     CPPUNIT_ASSERT_EQUAL( xmlTextResult, sos.GetString() );
512 }
513 
CopyDocument()514 void XmlTestCase::CopyDocument()
515 {
516     const char *xmlText =
517 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
518 "<!DOCTYPE resource PUBLIC \"Public-ID\" \"System'ID'\">\n"
519 "<!-- 1st prolog entry -->\n"
520 "<root>\n"
521 "  <first>Text</first>\n"
522 "  <second/>\n"
523 "</root>\n"
524     ;
525     wxXmlDocument doc1;
526     wxStringInputStream sis(xmlText);
527     CPPUNIT_ASSERT( doc1.Load(sis) );
528 
529     wxXmlDocument doc2 = doc1;
530 
531     wxStringOutputStream sos;
532     CPPUNIT_ASSERT(doc2.Save(sos));
533 
534     CPPUNIT_ASSERT_EQUAL( xmlText, sos.GetString() );
535 }
536 
Doctype()537 void XmlTestCase::Doctype()
538 {
539     const char *xmlText =
540         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
541         "<!DOCTYPE root PUBLIC \"Public-ID\" 'System\"ID\"'>\n"
542         "<root>\n"
543         "  <content/>\n"
544         "</root>\n"
545     ;
546 
547     wxStringInputStream sis(xmlText);
548     wxXmlDocument doc;
549     CPPUNIT_ASSERT( doc.Load(sis) );
550 
551     wxXmlDoctype dt = doc.GetDoctype();
552 
553     CPPUNIT_ASSERT_EQUAL( "root", dt.GetRootName() );
554     CPPUNIT_ASSERT_EQUAL( "System\"ID\"", dt.GetSystemId() );
555     CPPUNIT_ASSERT_EQUAL( "Public-ID", dt.GetPublicId() );
556 
557     CPPUNIT_ASSERT( dt.IsValid() );
558     CPPUNIT_ASSERT_EQUAL( "root PUBLIC \"Public-ID\" 'System\"ID\"'", dt.GetFullString() );
559     dt = wxXmlDoctype( dt.GetRootName(), dt.GetSystemId() );
560     CPPUNIT_ASSERT( dt.IsValid() );
561     CPPUNIT_ASSERT_EQUAL( "root SYSTEM 'System\"ID\"'", dt.GetFullString() );
562     dt = wxXmlDoctype( dt.GetRootName() );
563     CPPUNIT_ASSERT( dt.IsValid() );
564     CPPUNIT_ASSERT_EQUAL( "root", dt.GetFullString() );
565 
566     doc.SetDoctype(dt);
567     wxStringOutputStream sos;
568     CPPUNIT_ASSERT(doc.Save(sos));
569     const char *xmlText1 =
570         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
571         "<!DOCTYPE root>\n"
572         "<root>\n"
573         "  <content/>\n"
574         "</root>\n"
575     ;
576     CPPUNIT_ASSERT_EQUAL( xmlText1, sos.GetString() );
577 
578     doc.SetDoctype(wxXmlDoctype());
579     wxStringOutputStream sos2;
580     CPPUNIT_ASSERT(doc.Save(sos2));
581     const char *xmlText2 =
582         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
583         "<root>\n"
584         "  <content/>\n"
585         "</root>\n"
586     ;
587     CPPUNIT_ASSERT_EQUAL( xmlText2, sos2.GetString() );
588 
589     doc.SetDoctype(wxXmlDoctype("root", "Sys'id"));
590     wxStringOutputStream sos3;
591     CPPUNIT_ASSERT(doc.Save(sos3));
592     const char *xmlText3 =
593         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
594         "<!DOCTYPE root SYSTEM \"Sys'id\">\n"
595         "<root>\n"
596         "  <content/>\n"
597         "</root>\n"
598     ;
599     CPPUNIT_ASSERT_EQUAL( xmlText3, sos3.GetString() );
600 
601     dt = wxXmlDoctype( "", "System\"ID\"", "Public-ID" );
602     CPPUNIT_ASSERT( !dt.IsValid() );
603     CPPUNIT_ASSERT_EQUAL( "", dt.GetFullString() );
604     // Strictly speaking, this is illegal for XML but is legal for SGML.
605     dt = wxXmlDoctype( "root", "", "Public-ID" );
606     CPPUNIT_ASSERT( dt.IsValid() );
607     CPPUNIT_ASSERT_EQUAL( "root PUBLIC \"Public-ID\"", dt.GetFullString() );
608 
609     // Using both single and double quotes in system ID is not allowed.
610     dt = wxXmlDoctype( "root", "O'Reilly (\"editor\")", "Public-ID" );
611     CPPUNIT_ASSERT( !dt.IsValid() );
612 }
613