VirtualBox

source: vbox/trunk/src/VBox/Main/xml/Settings.cpp@ 7315

Last change on this file since 7315 was 7315, checked in by vboxsync, 17 years ago

Main/Settings: Added XmlTreeBackend::AutoConverter subckasss and old settings tree version memorizing.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 38.2 KB
Line 
1/** @file
2 * Settings File Manipulation API.
3 */
4
5/*
6 * Copyright (C) 2007 innotek GmbH
7 *
8 * This file is part of VirtualBox Open Source Edition (OSE), as
9 * available from http://www.215389.xyz. This file is free software;
10 * you can redistribute it and/or modify it under the terms of the GNU
11 * General Public License as published by the Free Software Foundation,
12 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
13 * distribution. VirtualBox OSE is distributed in the hope that it will
14 * be useful, but WITHOUT ANY WARRANTY of any kind.
15 */
16
17#include "VBox/settings.h"
18
19#include "Logging.h"
20
21#include <iprt/err.h>
22#include <iprt/file.h>
23
24#include <libxml/tree.h>
25#include <libxml/parser.h>
26#include <libxml/globals.h>
27#include <libxml/xmlIO.h>
28#include <libxml/xmlsave.h>
29#include <libxml/uri.h>
30
31#include <libxml/xmlschemas.h>
32
33#include <libxslt/xsltInternals.h>
34#include <libxslt/transform.h>
35#include <libxslt/xsltutils.h>
36
37#include <string.h>
38
39
40/**
41 * Global module initialization structure.
42 *
43 * The constructor and destructor of this structure are used to perform global
44 * module initiaizaton and cleanup. Thee must be only one global variable of
45 * this structure.
46 */
47static
48class Global
49{
50public:
51
52 Global()
53 {
54 /* Check the parser version. The docs say it will kill the app if
55 * there is a serious version mismatch, but I couldn't find it in the
56 * source code (it only prints the error/warning message to the console) so
57 * let's leave it as is for informational purposes. */
58 LIBXML_TEST_VERSION
59
60 /* Init libxml */
61 xmlInitParser();
62
63 /* Save the default entity resolver before someone has replaced it */
64 xml.defaultEntityLoader = xmlGetExternalEntityLoader();
65 }
66
67 ~Global()
68 {
69 /* Shutdown libxml */
70 xmlCleanupParser();
71 }
72
73 struct
74 {
75 xmlExternalEntityLoader defaultEntityLoader;
76 }
77 xml;
78}
79gGlobal;
80
81
82namespace settings
83{
84
85// Helpers
86////////////////////////////////////////////////////////////////////////////////
87
88inline int sFromHex (char aChar)
89{
90 if (aChar >= '0' && aChar <= '9')
91 return aChar - '0';
92 if (aChar >= 'A' && aChar <= 'F')
93 return aChar - 'A' + 0xA;
94 if (aChar >= 'a' && aChar <= 'f')
95 return aChar - 'a' + 0xA;
96
97 throw ENoConversion (FmtStr ("'%c' (0x%02X) is not hex", aChar, aChar));
98}
99
100inline char sToHex (int aDigit)
101{
102 return (aDigit < 0xA) ? aDigit + '0' : aDigit - 0xA + 'A';
103}
104
105static char *duplicate_chars (const char *that)
106{
107 char *result = NULL;
108 if (that != NULL)
109 {
110 size_t len = strlen (that) + 1;
111 result = new char [len];
112 if (result != NULL)
113 memcpy (result, that, len);
114 }
115 return result;
116}
117
118//////////////////////////////////////////////////////////////////////////////
119// string -> type conversions
120//////////////////////////////////////////////////////////////////////////////
121
122uint64_t FromStringInteger (const char *aValue, bool aSigned,
123 int aBits, uint64_t aMin, uint64_t aMax)
124{
125 if (aValue == NULL)
126 throw ENoValue();
127
128 switch (aBits)
129 {
130 case 8:
131 case 16:
132 case 32:
133 case 64:
134 break;
135 default:
136 throw ENotImplemented (RT_SRC_POS);
137 }
138
139 if (aSigned)
140 {
141 int64_t result;
142 int vrc = RTStrToInt64Full (aValue, 0, &result);
143 if (RT_SUCCESS (vrc))
144 {
145 if (result >= (int64_t) aMin && result <= (int64_t) aMax)
146 return (uint64_t) result;
147 }
148 }
149 else
150 {
151 uint64_t result;
152 int vrc = RTStrToUInt64Full (aValue, 0, &result);
153 if (RT_SUCCESS (vrc))
154 {
155 if (result >= aMin && result <= aMax)
156 return result;
157 }
158 }
159
160 throw ENoConversion (FmtStr ("'%s' is not integer", aValue));
161}
162
163template<> bool FromString <bool> (const char *aValue)
164{
165 if (aValue == NULL)
166 throw ENoValue();
167
168 if (strcmp (aValue, "true") == 0 ||
169 strcmp (aValue, "1") == 0)
170 /* This contradicts the XML Schema's interpretation of boolean: */
171 //strcmp (aValue, "yes") == 0 ||
172 //strcmp (aValue, "on") == 0)
173 return true;
174 else if (strcmp (aValue, "false") == 0 ||
175 strcmp (aValue, "0") == 0)
176 /* This contradicts the XML Schema's interpretation of boolean: */
177 //strcmp (aValue, "no") == 0 ||
178 //strcmp (aValue, "off") == 0)
179 return false;
180
181 throw ENoConversion (FmtStr ("'%s' is not bool", aValue));
182}
183
184template<> RTTIMESPEC FromString <RTTIMESPEC> (const char *aValue)
185{
186 if (aValue == NULL)
187 throw ENoValue();
188
189 /* Parse ISO date (xsd:dateTime). The format is:
190 * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
191 * where zzzzzz is: (('+' | '-') hh ':' mm) | 'Z' */
192 uint32_t yyyy = 0;
193 uint16_t mm = 0, dd = 0, hh = 0, mi = 0, ss = 0;
194 char buf [256];
195 if (strlen (aValue) > ELEMENTS (buf) - 1 ||
196 sscanf (aValue, "%d-%hu-%huT%hu:%hu:%hu%s",
197 &yyyy, &mm, &dd, &hh, &mi, &ss, buf) == 7)
198 {
199 /* currently, we accept only the UTC timezone ('Z'),
200 * ignoring fractional seconds, if present */
201 if (buf [0] == 'Z' ||
202 (buf [0] == '.' && buf [strlen (buf) - 1] == 'Z'))
203 {
204 RTTIME time = { yyyy, (uint8_t) mm, 0, 0, (uint8_t) dd,
205 (uint8_t) hh, (uint8_t) mi, (uint8_t) ss, 0,
206 RTTIME_FLAGS_TYPE_UTC };
207 if (RTTimeNormalize (&time))
208 {
209 RTTIMESPEC timeSpec;
210 if (RTTimeImplode (&timeSpec, &time))
211 return timeSpec;
212 }
213 }
214 else
215 throw ENoConversion (FmtStr ("'%s' is not UTC date", aValue));
216 }
217
218 throw ENoConversion (FmtStr ("'%s' is not ISO date", aValue));
219}
220
221stdx::char_auto_ptr FromString (const char *aValue, size_t *aLen)
222{
223 if (aValue == NULL)
224 throw ENoValue();
225
226 /* each two chars produce one byte */
227 size_t len = strlen (aValue) / 2;
228
229 /* therefore, the original length must be even */
230 if (len % 2 != 0)
231 throw ENoConversion (FmtStr ("'%.*s' is not binary data",
232 aLen, aValue));
233
234 stdx::char_auto_ptr result (new char [len]);
235
236 const char *src = aValue;
237 char *dst = result.get();
238
239 for (size_t i = 0; i < len; ++ i, ++ dst)
240 {
241 *dst = sFromHex (*src ++) << 4;
242 *dst |= sFromHex (*src ++);
243 }
244
245 if (aLen != NULL)
246 *aLen = len;
247
248 return result;
249}
250
251//////////////////////////////////////////////////////////////////////////////
252// type -> string conversions
253//////////////////////////////////////////////////////////////////////////////
254
255stdx::char_auto_ptr ToStringInteger (uint64_t aValue, unsigned int aBase,
256 bool aSigned, int aBits)
257{
258 unsigned int flags = RTSTR_F_SPECIAL;
259 if (aSigned)
260 flags |= RTSTR_F_VALSIGNED;
261
262 /* maximum is binary representation + terminator */
263 size_t len = aBits + 1;
264
265 switch (aBits)
266 {
267 case 8:
268 flags |= RTSTR_F_8BIT;
269 break;
270 case 16:
271 flags |= RTSTR_F_16BIT;
272 break;
273 case 32:
274 flags |= RTSTR_F_32BIT;
275 break;
276 case 64:
277 flags |= RTSTR_F_64BIT;
278 break;
279 default:
280 throw ENotImplemented (RT_SRC_POS);
281 }
282
283 stdx::char_auto_ptr result (new char [len]);
284 int vrc = RTStrFormatNumber (result.get(), aValue, aBase, 0, 0, flags);
285 if (RT_SUCCESS (vrc))
286 return result;
287
288 throw EIPRTFailure (vrc);
289}
290
291template<> stdx::char_auto_ptr ToString <bool> (const bool &aValue,
292 unsigned int aExtra /* = 0 */)
293{
294 /* Convert to the canonical form according to XML Schema */
295 stdx::char_auto_ptr result (duplicate_chars (aValue ? "true" : "false"));
296 return result;
297}
298
299template<> stdx::char_auto_ptr ToString <RTTIMESPEC> (const RTTIMESPEC &aValue,
300 unsigned int aExtra /* = 0 */)
301{
302 RTTIME time;
303 if (!RTTimeExplode (&time, &aValue))
304 throw ENoConversion (FmtStr ("timespec %lld ms is invalid",
305 RTTimeSpecGetMilli (&aValue)));
306
307 /* Store ISO date (xsd:dateTime). The format is:
308 * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
309 * where zzzzzz is: (('+' | '-') hh ':' mm) | 'Z' */
310 char buf [256];
311 RTStrPrintf (buf, sizeof (buf),
312 "%04ld-%02hd-%02hdT%02hd:%02hd:%02hdZ",
313 time.i32Year, (uint16_t) time.u8Month, (uint16_t) time.u8MonthDay,
314 (uint16_t) time.u8Hour, (uint16_t) time.u8Minute, (uint16_t) time.u8Second);
315
316 stdx::char_auto_ptr result (duplicate_chars (buf));
317 return result;
318}
319
320stdx::char_auto_ptr ToString (const char *aData, size_t aLen)
321{
322 /* each byte will produce two hex digits and there will be a null
323 * terminator */
324 stdx::char_auto_ptr result (new char [aLen * 2 + 1]);
325
326 const char *src = aData;
327 char *dst = result.get();
328
329 for (size_t i = 0; i < aLen; ++ i, ++ src)
330 {
331 *dst++ = sToHex ((*src) >> 4);
332 *dst++ = sToHex ((*src) & 0xF);
333 }
334
335 *dst = '\0';
336
337 return result;
338}
339
340//////////////////////////////////////////////////////////////////////////////
341// File Class
342//////////////////////////////////////////////////////////////////////////////
343
344struct File::Data
345{
346 Data()
347 : fileName (NULL), handle (NIL_RTFILE), opened (false) {}
348
349 Mode mode;
350 char *fileName;
351 RTFILE handle;
352 bool opened : 1;
353};
354
355File::File (Mode aMode, const char *aFileName)
356 : m (new Data())
357{
358 m->mode = aMode;
359
360 m->fileName = RTStrDup (aFileName);
361 if (m->fileName == NULL)
362 throw ENoMemory();
363
364 unsigned flags = 0;
365 switch (aMode)
366 {
367 case Read:
368 flags = RTFILE_O_READ;
369 break;
370 case Write:
371 flags = RTFILE_O_WRITE | RTFILE_O_CREATE;
372 break;
373 case ReadWrite:
374 flags = RTFILE_O_READ | RTFILE_O_WRITE;
375 }
376
377 int vrc = RTFileOpen (&m->handle, aFileName, flags);
378 if (RT_FAILURE (vrc))
379 throw EIPRTFailure (vrc);
380
381 m->opened = true;
382}
383
384File::File (Mode aMode, RTFILE aHandle, const char *aFileName /* = NULL */ )
385 : m (new Data())
386{
387 if (aHandle == NIL_RTFILE)
388 throw EInvalidArg (RT_SRC_POS);
389
390 m->mode = aMode;
391 m->handle = aHandle;
392
393 if (aFileName)
394 {
395 m->fileName = RTStrDup (aFileName);
396 if (m->fileName == NULL)
397 throw ENoMemory();
398 }
399
400 setPos (0);
401}
402
403File::~File()
404{
405 if (m->opened)
406 RTFileClose (m->handle);
407
408 RTStrFree (m->fileName);
409}
410
411const char *File::uri() const
412{
413 return m->fileName;
414}
415
416uint64_t File::pos() const
417{
418 uint64_t p = 0;
419 int vrc = RTFileSeek (m->handle, 0, RTFILE_SEEK_CURRENT, &p);
420 if (RT_SUCCESS (vrc))
421 return p;
422
423 throw EIPRTFailure (vrc);
424}
425
426void File::setPos (uint64_t aPos)
427{
428 uint64_t p = 0;
429 unsigned method = RTFILE_SEEK_BEGIN;
430 int vrc = VINF_SUCCESS;
431
432 /* check if we overflow int64_t and move to INT64_MAX first */
433 if (((int64_t) aPos) < 0)
434 {
435 vrc = RTFileSeek (m->handle, INT64_MAX, method, &p);
436 aPos -= (uint64_t) INT64_MAX;
437 method = RTFILE_SEEK_CURRENT;
438 }
439 /* seek the rest */
440 if (RT_SUCCESS (vrc))
441 vrc = RTFileSeek (m->handle, (int64_t) aPos, method, &p);
442 if (RT_SUCCESS (vrc))
443 return;
444
445 throw EIPRTFailure (vrc);
446}
447
448int File::read (char *aBuf, int aLen)
449{
450 size_t len = aLen;
451 int vrc = RTFileRead (m->handle, aBuf, len, &len);
452 if (RT_SUCCESS (vrc))
453 return len;
454
455 throw EIPRTFailure (vrc);
456}
457
458int File::write (const char *aBuf, int aLen)
459{
460 size_t len = aLen;
461 int vrc = RTFileWrite (m->handle, aBuf, len, &len);
462 if (RT_SUCCESS (vrc))
463 return len;
464
465 throw EIPRTFailure (vrc);
466
467 return -1 /* failure */;
468}
469
470void File::truncate()
471{
472 int vrc = RTFileSetSize (m->handle, pos());
473 if (RT_SUCCESS (vrc))
474 return;
475
476 throw EIPRTFailure (vrc);
477}
478
479//////////////////////////////////////////////////////////////////////////////
480// MemoryBuf Class
481//////////////////////////////////////////////////////////////////////////////
482
483struct MemoryBuf::Data
484{
485 Data()
486 : buf (NULL), len (0), uri (NULL), pos (0) {}
487
488 const char *buf;
489 size_t len;
490 char *uri;
491
492 size_t pos;
493};
494
495MemoryBuf::MemoryBuf (const char *aBuf, size_t aLen, const char *aURI /* = NULL */)
496 : m (new Data())
497{
498 if (aBuf == NULL)
499 throw EInvalidArg (RT_SRC_POS);
500
501 m->buf = aBuf;
502 m->len = aLen;
503 m->uri = RTStrDup (aURI);
504}
505
506MemoryBuf::~MemoryBuf()
507{
508 RTStrFree (m->uri);
509}
510
511const char *MemoryBuf::uri() const
512{
513 return m->uri;
514}
515
516uint64_t MemoryBuf::pos() const
517{
518 return m->pos;
519}
520
521void MemoryBuf::setPos (uint64_t aPos)
522{
523 size_t pos = (size_t) aPos;
524 if ((uint64_t) pos != aPos)
525 throw EInvalidArg();
526
527 if (pos > m->len)
528 throw EInvalidArg();
529
530 m->pos = pos;
531}
532
533int MemoryBuf::read (char *aBuf, int aLen)
534{
535 if (m->pos >= m->len)
536 return 0 /* nothing to read */;
537
538 size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos;
539 memcpy (aBuf, m->buf + m->pos, len);
540 m->pos += len;
541
542 return len;
543}
544
545//////////////////////////////////////////////////////////////////////////////
546// XmlKeyBackend Class
547//////////////////////////////////////////////////////////////////////////////
548
549class XmlKeyBackend : public Key::Backend
550{
551public:
552
553 XmlKeyBackend (xmlNodePtr aNode);
554 ~XmlKeyBackend();
555
556 const char *name() const;
557 void setName (const char *aName);
558 const char *value (const char *aName) const;
559 void setValue (const char *aName, const char *aValue);
560
561 Key::List keys (const char *aName = NULL) const;
562 Key findKey (const char *aName) const;
563
564 Key appendKey (const char *aName);
565 void zap();
566
567 void *position() const { return mNode; }
568
569private:
570
571 xmlNodePtr mNode;
572
573 xmlChar *mNodeText;
574
575 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (XmlKeyBackend);
576
577 friend class XmlTreeBackend;
578};
579
580XmlKeyBackend::XmlKeyBackend (xmlNodePtr aNode)
581 : mNode (aNode), mNodeText (NULL)
582{
583 AssertReturnVoid (mNode);
584 AssertReturnVoid (mNode->type == XML_ELEMENT_NODE);
585}
586
587XmlKeyBackend::~XmlKeyBackend()
588{
589 xmlFree (mNodeText);
590}
591
592const char *XmlKeyBackend::name() const
593{
594 return mNode ? (char *) mNode->name : NULL;
595}
596
597void XmlKeyBackend::setName (const char *aName)
598{
599 throw ENotImplemented (RT_SRC_POS);
600}
601
602const char *XmlKeyBackend::value (const char *aName) const
603{
604 if (!mNode)
605 return NULL;
606
607 if (aName == NULL)
608 {
609 /* @todo xmlNodeListGetString (,,1) returns NULL for things like
610 * <Foo></Foo> and may want to return "" in this case to distinguish
611 * from <Foo/> (where NULL is pretty much expected). */
612 if (!mNodeText)
613 unconst (mNodeText) =
614 xmlNodeListGetString (mNode->doc, mNode->children, 0);
615 return (char *) mNodeText;
616 }
617
618 xmlAttrPtr attr = xmlHasProp (mNode, (const xmlChar *) aName);
619 if (!attr)
620 return NULL;
621
622 if (attr->type == XML_ATTRIBUTE_NODE)
623 {
624 /* @todo for now, we only understand the most common case: only 1 text
625 * node comprises the attribute's contents. Otherwise we'd need to
626 * return a newly allocated string buffer to the caller that
627 * concatenates all text nodes and obey him to free it or provide our
628 * own internal map of attribute=value pairs and return const pointers
629 * to values from this map. */
630 if (attr->children != NULL &&
631 attr->children->next == NULL &&
632 (attr->children->type == XML_TEXT_NODE ||
633 attr->children->type == XML_CDATA_SECTION_NODE))
634 return (char *) attr->children->content;
635 }
636 else if (attr->type == XML_ATTRIBUTE_DECL)
637 {
638 return (char *) ((xmlAttributePtr) attr)->defaultValue;
639 }
640
641 return NULL;
642}
643
644void XmlKeyBackend::setValue (const char *aName, const char *aValue)
645{
646 if (!mNode)
647 return;
648
649 if (aName == NULL)
650 {
651 xmlChar *value = (xmlChar *) aValue;
652 if (value != NULL)
653 {
654 value = xmlEncodeSpecialChars (mNode->doc, value);
655 if (value == NULL)
656 throw ENoMemory();
657 }
658
659 xmlNodeSetContent (mNode, value);
660
661 if (value != (xmlChar *) aValue)
662 xmlFree (value);
663
664 /* outdate the node text holder */
665 if (mNodeText != NULL)
666 {
667 xmlFree (mNodeText);
668 mNodeText = NULL;
669 }
670
671 return;
672 }
673
674 if (aValue == NULL)
675 {
676 /* remove the attribute if it exists */
677 xmlAttrPtr attr = xmlHasProp (mNode, (const xmlChar *) aName);
678 if (attr != NULL)
679 {
680 int rc = xmlRemoveProp (attr);
681 if (rc != 0)
682 throw EInvalidArg (RT_SRC_POS);
683 }
684 return;
685 }
686
687 xmlAttrPtr attr = xmlSetProp (mNode, (const xmlChar *) aName,
688 (const xmlChar *) aValue);
689 if (attr == NULL)
690 throw ENoMemory();
691}
692
693Key::List XmlKeyBackend::keys (const char *aName /* = NULL */) const
694{
695 Key::List list;
696
697 if (!mNode)
698 return list;
699
700 for (xmlNodePtr node = mNode->children; node; node = node->next)
701 {
702 if (node->type == XML_ELEMENT_NODE)
703 {
704 if (aName == NULL ||
705 strcmp (aName, (char *) node->name) == 0)
706 list.push_back (Key (new XmlKeyBackend (node)));
707 }
708 }
709
710 return list;
711}
712
713Key XmlKeyBackend::findKey (const char *aName) const
714{
715 Key key;
716
717 if (!mNode)
718 return key;
719
720 for (xmlNodePtr node = mNode->children; node; node = node->next)
721 {
722 if (node->type == XML_ELEMENT_NODE)
723 {
724 if (aName == NULL ||
725 strcmp (aName, (char *) node->name) == 0)
726 {
727 key = Key (new XmlKeyBackend (node));
728 break;
729 }
730 }
731 }
732
733 return key;
734}
735
736Key XmlKeyBackend::appendKey (const char *aName)
737{
738 if (!mNode)
739 return Key();
740
741 xmlNodePtr node = xmlNewChild (mNode, NULL, (const xmlChar *) aName, NULL);
742 if (node == NULL)
743 throw ENoMemory();
744
745 return Key (new XmlKeyBackend (node));
746}
747
748void XmlKeyBackend::zap()
749{
750 if (!mNode)
751 return;
752
753 xmlUnlinkNode (mNode);
754 xmlFreeNode (mNode);
755 mNode = NULL;
756}
757
758//////////////////////////////////////////////////////////////////////////////
759// XmlTreeBackend Class
760//////////////////////////////////////////////////////////////////////////////
761
762class XmlTreeBackend::XmlError : public XmlTreeBackend::Error
763{
764public:
765
766 XmlError (xmlErrorPtr aErr)
767 {
768 if (!aErr)
769 throw EInvalidArg (RT_SRC_POS);
770
771 char *msg = Format (aErr);
772 setWhat (msg);
773 RTStrFree (msg);
774 }
775
776 /**
777 * Composes a single message for the given error. The caller must free the
778 * returned string using RTStrFree() when no more necessary.
779 */
780 static char *Format (xmlErrorPtr aErr)
781 {
782 const char *msg = aErr->message ? aErr->message : "<none>";
783 size_t msgLen = strlen (msg);
784 /* strip spaces, trailing EOLs and dot-like char */
785 while (msgLen && strchr (" \n.?!", msg [msgLen - 1]))
786 -- msgLen;
787
788 char *finalMsg = NULL;
789 RTStrAPrintf (&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d",
790 msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2);
791
792 return finalMsg;
793 }
794};
795
796struct XmlTreeBackend::Data
797{
798 Data() : ctxt (NULL), doc (NULL)
799 , inputResolver (NULL)
800 , autoConverter (NULL), oldVersion (NULL) {}
801
802 xmlParserCtxtPtr ctxt;
803 xmlDocPtr doc;
804
805 Key root;
806
807 InputResolver *inputResolver;
808
809 AutoConverter *autoConverter;
810 char *oldVersion;
811
812 std::auto_ptr <stdx::exception_trap_base> trappedErr;
813
814 /**
815 * This is to avoid throwing exceptions while in libxml2 code and
816 * redirect them to our level instead. Also used to perform clean up
817 * by deleting the I/O stream instance and self when requested.
818 */
819 struct IOCtxt
820 {
821 IOCtxt (Stream *aStream, std::auto_ptr <stdx::exception_trap_base> &aErr)
822 : stream (aStream), deleteStreamOnClose (false)
823 , err (aErr) {}
824
825 template <typename T>
826 void setErr (const T& aErr) { err.reset (new stdx::exception_trap <T> (aErr)); }
827
828 void resetErr() { err.reset(); }
829
830 Stream *stream;
831 bool deleteStreamOnClose;
832
833 std::auto_ptr <stdx::exception_trap_base> &err;
834 };
835
836 struct InputCtxt : public IOCtxt
837 {
838 InputCtxt (Input *aInput, std::auto_ptr <stdx::exception_trap_base> &aErr)
839 : IOCtxt (aInput, aErr), input (aInput) {}
840
841 Input *input;
842 };
843
844 struct OutputCtxt : public IOCtxt
845 {
846 OutputCtxt (Output *aOutput, std::auto_ptr <stdx::exception_trap_base> &aErr)
847 : IOCtxt (aOutput, aErr), output (aOutput) {}
848
849 Output *output;
850 };
851};
852
853XmlTreeBackend::XmlTreeBackend()
854 : m (new Data())
855{
856 /* create a parser context */
857 m->ctxt = xmlNewParserCtxt();
858 if (m->ctxt == NULL)
859 throw ENoMemory();
860}
861
862XmlTreeBackend::~XmlTreeBackend()
863{
864 reset();
865
866 xmlFreeParserCtxt (m->ctxt);
867 m->ctxt = NULL;
868}
869
870void XmlTreeBackend::setInputResolver (InputResolver &aResolver)
871{
872 m->inputResolver = &aResolver;
873}
874
875void XmlTreeBackend::resetInputResolver()
876{
877 m->inputResolver = NULL;
878}
879
880void XmlTreeBackend::setAutoConverter (AutoConverter &aConverter)
881{
882 m->autoConverter = &aConverter;
883}
884
885void XmlTreeBackend::resetAutoConverter()
886{
887 m->autoConverter = NULL;
888}
889
890const char *XmlTreeBackend::oldVersion() const
891{
892 return m->oldVersion;
893}
894
895void XmlTreeBackend::rawRead (Input &aInput, const char *aSchema /* = NULL */,
896 int aFlags /* = 0 */)
897{
898 /* Reset error variables used to memorize exceptions while inside the
899 * libxml2 code. */
900 m->trappedErr.reset();
901
902 /* Set up the external entity resolver. Note that we do it in a
903 * thread-unsafe fashion because this stuff is not thread-safe in libxml2.
904 * Making it thread-safe would require a) guarding this method with a
905 * mutex and b) requiring our API caller not to use libxml2 on some other
906 * thread (which is not practically possible). So, our API is not
907 * thread-safe for now. */
908 xmlExternalEntityLoader oldEntityLoader = xmlGetExternalEntityLoader();
909 sThat = this;
910 xmlSetExternalEntityLoader (ExternalEntityLoader);
911
912 /* Note: when parsing we use XML_PARSE_NOBLANKS to instruct libxml2 to
913 * remove text nodes that contain only blanks. This is important because
914 * otherwise xmlSaveDoc() won't be able to do proper indentation on
915 * output. */
916
917 /* parse the stream */
918 /* NOTE: new InputCtxt instance will be deleted when the stream is closed by
919 * the libxml2 API (e.g. when calling xmlFreeParserCtxt()) */
920 xmlDocPtr doc = xmlCtxtReadIO (m->ctxt,
921 ReadCallback, CloseCallback,
922 new Data::InputCtxt (&aInput, m->trappedErr),
923 aInput.uri(), NULL,
924 XML_PARSE_NOBLANKS);
925 if (doc == NULL)
926 {
927 /* restore the previous entity resolver */
928 xmlSetExternalEntityLoader (oldEntityLoader);
929 sThat = NULL;
930
931 /* look if there was a forwared exception from the lower level */
932 if (m->trappedErr.get() != NULL)
933 m->trappedErr->rethrow();
934
935 throw XmlError (xmlCtxtGetLastError (m->ctxt));
936 }
937
938 char *oldVersion = NULL;
939
940 /* perform automatic document transformation if necessary */
941 if (m->autoConverter != NULL)
942 {
943 Key root = Key (new XmlKeyBackend (xmlDocGetRootElement (doc)));
944 if (m->autoConverter->needsConversion (root, oldVersion))
945 {
946 xmlDocPtr xsltDoc = NULL;
947 xsltStylesheetPtr xslt = NULL;
948 xsltTransformContextPtr tranCtxt = NULL;
949 char *errorStr = NULL;
950
951 try
952 {
953 /* parse the XSLT template */
954 {
955 Input *xsltInput =
956 m->inputResolver->resolveEntity
957 (m->autoConverter->templateUri(), NULL);
958 /* NOTE: new InputCtxt instance will be deleted when the
959 * stream is closed by the libxml2 API */
960 xsltDoc = xmlCtxtReadIO (m->ctxt,
961 ReadCallback, CloseCallback,
962 new Data::InputCtxt (xsltInput, m->trappedErr),
963 m->autoConverter->templateUri(),
964 NULL, 0);
965 delete xsltInput;
966 }
967
968 if (xsltDoc == NULL)
969 {
970 /* look if there was a forwared exception from the lower level */
971 if (m->trappedErr.get() != NULL)
972 m->trappedErr->rethrow();
973
974 throw XmlError (xmlCtxtGetLastError (m->ctxt));
975 }
976
977 xslt = xsltParseStylesheetDoc (xsltDoc);
978 if (xslt == NULL)
979 throw LogicError (RT_SRC_POS);
980
981 /* setup transformation error reporting */
982 tranCtxt = xsltNewTransformContext (xslt, xsltDoc);
983 if (tranCtxt == NULL)
984 throw LogicError (RT_SRC_POS);
985 xsltSetTransformErrorFunc (tranCtxt, &errorStr, ValidityErrorCallback);
986
987 xmlDocPtr newDoc = xsltApplyStylesheetUser (xslt, doc, NULL,
988 NULL, NULL, tranCtxt);
989 if (newDoc == NULL)
990 throw LogicError (RT_SRC_POS);
991
992 if (errorStr != NULL)
993 {
994 xmlFreeDoc (newDoc);
995 throw Error (errorStr);
996 /* errorStr is freed in catch(...) below */
997 }
998
999 /* replace the old document on success */
1000 xmlFreeDoc (doc);
1001 doc = newDoc;
1002
1003 xsltFreeTransformContext (tranCtxt);
1004
1005 /* NOTE: xsltFreeStylesheet() also fress the document
1006 * passed to xsltParseStylesheetDoc(). */
1007 xsltFreeStylesheet (xslt);
1008 }
1009 catch (...)
1010 {
1011 /* restore the previous entity resolver */
1012 xmlSetExternalEntityLoader (oldEntityLoader);
1013 sThat = NULL;
1014
1015 RTStrFree (errorStr);
1016
1017 if (tranCtxt != NULL)
1018 xsltFreeTransformContext (tranCtxt);
1019
1020 /* NOTE: xsltFreeStylesheet() also fress the document
1021 * passed to xsltParseStylesheetDoc(). */
1022 if (xslt != NULL)
1023 xsltFreeStylesheet (xslt);
1024 else if (xsltDoc != NULL)
1025 xmlFreeDoc (xsltDoc);
1026
1027 RTStrFree (oldVersion);
1028
1029 throw;
1030 }
1031 }
1032 }
1033
1034 /* validate the document */
1035 if (aSchema != NULL)
1036 {
1037 xmlSchemaParserCtxtPtr schemaCtxt = NULL;
1038 xmlSchemaPtr schema = NULL;
1039 xmlSchemaValidCtxtPtr validCtxt = NULL;
1040 char *errorStr = NULL;
1041
1042 try
1043 {
1044 bool valid = false;
1045
1046 schemaCtxt = xmlSchemaNewParserCtxt (aSchema);
1047 if (schemaCtxt == NULL)
1048 throw LogicError (RT_SRC_POS);
1049
1050 /* set our error handlers */
1051 xmlSchemaSetParserErrors (schemaCtxt, ValidityErrorCallback,
1052 ValidityWarningCallback, &errorStr);
1053 xmlSchemaSetParserStructuredErrors (schemaCtxt,
1054 StructuredErrorCallback,
1055 &errorStr);
1056 /* load schema */
1057 schema = xmlSchemaParse (schemaCtxt);
1058 if (schema != NULL)
1059 {
1060 validCtxt = xmlSchemaNewValidCtxt (schema);
1061 if (validCtxt == NULL)
1062 throw LogicError (RT_SRC_POS);
1063
1064 /* instruct to create default attribute's values in the document */
1065 if (aFlags & Read_AddDefaults)
1066 xmlSchemaSetValidOptions (validCtxt, XML_SCHEMA_VAL_VC_I_CREATE);
1067
1068 /* set our error handlers */
1069 xmlSchemaSetValidErrors (validCtxt, ValidityErrorCallback,
1070 ValidityWarningCallback, &errorStr);
1071
1072 /* finally, validate */
1073 valid = xmlSchemaValidateDoc (validCtxt, doc) == 0;
1074 }
1075
1076 if (!valid)
1077 {
1078 /* look if there was a forwared exception from the lower level */
1079 if (m->trappedErr.get() != NULL)
1080 m->trappedErr->rethrow();
1081
1082 if (errorStr == NULL)
1083 throw LogicError (RT_SRC_POS);
1084
1085 throw Error (errorStr);
1086 /* errorStr is freed in catch(...) below */
1087 }
1088
1089 RTStrFree (errorStr);
1090
1091 xmlSchemaFreeValidCtxt (validCtxt);
1092 xmlSchemaFree (schema);
1093 xmlSchemaFreeParserCtxt (schemaCtxt);
1094 }
1095 catch (...)
1096 {
1097 /* restore the previous entity resolver */
1098 xmlSetExternalEntityLoader (oldEntityLoader);
1099 sThat = NULL;
1100
1101 RTStrFree (errorStr);
1102
1103 if (validCtxt)
1104 xmlSchemaFreeValidCtxt (validCtxt);
1105 if (schema)
1106 xmlSchemaFree (schema);
1107 if (schemaCtxt)
1108 xmlSchemaFreeParserCtxt (schemaCtxt);
1109
1110 RTStrFree (oldVersion);
1111
1112 throw;
1113 }
1114 }
1115
1116 /* restore the previous entity resolver */
1117 xmlSetExternalEntityLoader (oldEntityLoader);
1118 sThat = NULL;
1119
1120 /* reset the previous tree on success */
1121 reset();
1122
1123 m->doc = doc;
1124 /* assign the root key */
1125 m->root = Key (new XmlKeyBackend (xmlDocGetRootElement (m->doc)));
1126
1127 /* memorize the old version string also used as a flag that
1128 * the conversion has been performed (transfers ownership) */
1129 m->oldVersion = oldVersion;
1130}
1131
1132void XmlTreeBackend::rawWrite (Output &aOutput)
1133{
1134 /* reset error variables used to memorize exceptions while inside the
1135 * libxml2 code */
1136 m->trappedErr.reset();
1137
1138 /* set up an input stream for parsing the document. This will be deleted
1139 * when the stream is closed by the libxml2 API (e.g. when calling
1140 * xmlFreeParserCtxt()). */
1141 Data::OutputCtxt *outputCtxt =
1142 new Data::OutputCtxt (&aOutput, m->trappedErr);
1143
1144 /* serialize to the stream */
1145
1146 xmlIndentTreeOutput = 1;
1147 xmlTreeIndentString = " ";
1148 xmlSaveNoEmptyTags = 0;
1149
1150 xmlSaveCtxtPtr saveCtxt = xmlSaveToIO (WriteCallback, CloseCallback,
1151 outputCtxt, NULL,
1152 XML_SAVE_FORMAT);
1153 if (saveCtxt == NULL)
1154 throw LogicError (RT_SRC_POS);
1155
1156 long rc = xmlSaveDoc (saveCtxt, m->doc);
1157 if (rc == -1)
1158 {
1159 /* look if there was a forwared exception from the lower level */
1160 if (m->trappedErr.get() != NULL)
1161 m->trappedErr->rethrow();
1162
1163 /* there must be an exception from the Output implementation,
1164 * otherwise the save operation must always succeed. */
1165 throw LogicError (RT_SRC_POS);
1166 }
1167
1168 xmlSaveClose (saveCtxt);
1169}
1170
1171void XmlTreeBackend::reset()
1172{
1173 RTStrFree (m->oldVersion);
1174 m->oldVersion = NULL;
1175
1176 if (m->doc)
1177 {
1178 /* reset the root key's node */
1179 GetKeyBackend (m->root)->mNode = NULL;
1180 /* free the document*/
1181 xmlFreeDoc (m->doc);
1182 m->doc = NULL;
1183 }
1184}
1185
1186Key &XmlTreeBackend::rootKey() const
1187{
1188 return m->root;
1189}
1190
1191/* static */
1192int XmlTreeBackend::ReadCallback (void *aCtxt, char *aBuf, int aLen)
1193{
1194 AssertReturn (aCtxt != NULL, 0);
1195
1196 Data::InputCtxt *ctxt = static_cast <Data::InputCtxt *> (aCtxt);
1197
1198 /* To prevent throwing exceptions while inside libxml2 code, we catch
1199 * them and forward to our level using a couple of variables. */
1200 try
1201 {
1202 return ctxt->input->read (aBuf, aLen);
1203 }
1204 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1205 catch (const settings::Error &err) { ctxt->setErr (err); }
1206 catch (const std::exception &err) { ctxt->setErr (err); }
1207 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1208
1209 return -1 /* failure */;
1210}
1211
1212/* static */
1213int XmlTreeBackend::WriteCallback (void *aCtxt, const char *aBuf, int aLen)
1214{
1215 AssertReturn (aCtxt != NULL, 0);
1216
1217 Data::OutputCtxt *ctxt = static_cast <Data::OutputCtxt *> (aCtxt);
1218
1219 /* To prevent throwing exceptions while inside libxml2 code, we catch
1220 * them and forward to our level using a couple of variables. */
1221 try
1222 {
1223 return ctxt->output->write (aBuf, aLen);
1224 }
1225 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1226 catch (const settings::Error &err) { ctxt->setErr (err); }
1227 catch (const std::exception &err) { ctxt->setErr (err); }
1228 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1229
1230 return -1 /* failure */;
1231}
1232
1233/* static */
1234int XmlTreeBackend::CloseCallback (void *aCtxt)
1235{
1236 AssertReturn (aCtxt != NULL, 0);
1237
1238 Data::IOCtxt *ctxt = static_cast <Data::IOCtxt *> (aCtxt);
1239
1240 /* To prevent throwing exceptions while inside libxml2 code, we catch
1241 * them and forward to our level using a couple of variables. */
1242 try
1243 {
1244 /// @todo there is no explicit close semantics in Stream yet
1245#if 0
1246 ctxt->stream->close();
1247#endif
1248
1249 /* perform cleanup when necessary */
1250 if (ctxt->deleteStreamOnClose)
1251 delete ctxt->stream;
1252
1253 delete ctxt;
1254
1255 return 0 /* success */;
1256 }
1257 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1258 catch (const settings::Error &err) { ctxt->setErr (err); }
1259 catch (const std::exception &err) { ctxt->setErr (err); }
1260 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1261
1262 return -1 /* failure */;
1263}
1264
1265/* static */
1266void XmlTreeBackend::ValidityErrorCallback (void *aCtxt, const char *aMsg, ...)
1267{
1268 AssertReturnVoid (aCtxt != NULL);
1269 AssertReturnVoid (aMsg != NULL);
1270
1271 char * &str = *(char * *) aCtxt;
1272
1273 char *newMsg = NULL;
1274 {
1275 va_list args;
1276 va_start (args, aMsg);
1277 RTStrAPrintfV (&newMsg, aMsg, args);
1278 va_end (args);
1279 }
1280
1281 AssertReturnVoid (newMsg != NULL);
1282
1283 /* strip spaces, trailing EOLs and dot-like char */
1284 size_t newMsgLen = strlen (newMsg);
1285 while (newMsgLen && strchr (" \n.?!", newMsg [newMsgLen - 1]))
1286 -- newMsgLen;
1287
1288 /* anything left? */
1289 if (newMsgLen > 0)
1290 {
1291 if (str == NULL)
1292 {
1293 str = newMsg;
1294 newMsg [newMsgLen] = '\0';
1295 }
1296 else
1297 {
1298 /* append to the existing string */
1299 size_t strLen = strlen (str);
1300 char *newStr = (char *) RTMemRealloc (str, strLen + 2 + newMsgLen + 1);
1301 AssertReturnVoid (newStr != NULL);
1302
1303 memcpy (newStr + strLen, ".\n", 2);
1304 memcpy (newStr + strLen + 2, newMsg, newMsgLen);
1305 newStr [strLen + 2 + newMsgLen] = '\0';
1306 str = newStr;
1307 RTStrFree (newMsg);
1308 }
1309 }
1310}
1311
1312/* static */
1313void XmlTreeBackend::ValidityWarningCallback (void *aCtxt, const char *aMsg, ...)
1314{
1315 NOREF (aCtxt);
1316 NOREF (aMsg);
1317}
1318
1319/* static */
1320void XmlTreeBackend::StructuredErrorCallback (void *aCtxt, xmlErrorPtr aErr)
1321{
1322 AssertReturnVoid (aCtxt != NULL);
1323 AssertReturnVoid (aErr != NULL);
1324
1325 char * &str = *(char * *) aCtxt;
1326
1327 char *newMsg = XmlError::Format (aErr);
1328 AssertReturnVoid (newMsg != NULL);
1329
1330 if (str == NULL)
1331 str = newMsg;
1332 else
1333 {
1334 /* append to the existing string */
1335 size_t newMsgLen = strlen (newMsg);
1336 size_t strLen = strlen (str);
1337 char *newStr = (char *) RTMemRealloc (str, strLen + newMsgLen + 2);
1338 AssertReturnVoid (newStr != NULL);
1339
1340 memcpy (newStr + strLen, ".\n", 2);
1341 memcpy (newStr + strLen + 2, newMsg, newMsgLen);
1342 str = newStr;
1343 RTStrFree (newMsg);
1344 }
1345}
1346
1347/* static */
1348XmlTreeBackend *XmlTreeBackend::sThat = NULL;
1349
1350/* static */
1351xmlParserInputPtr XmlTreeBackend::ExternalEntityLoader (const char *aURI,
1352 const char *aID,
1353 xmlParserCtxtPtr aCtxt)
1354{
1355 AssertReturn (sThat != NULL, NULL);
1356
1357 if (sThat->m->inputResolver == NULL)
1358 return gGlobal.xml.defaultEntityLoader (aURI, aID, aCtxt);
1359
1360 /* To prevent throwing exceptions while inside libxml2 code, we catch
1361 * them and forward to our level using a couple of variables. */
1362 try
1363 {
1364 Input *input = sThat->m->inputResolver->resolveEntity (aURI, aID);
1365 if (input == NULL)
1366 return NULL;
1367
1368 Data::InputCtxt *ctxt = new Data::InputCtxt (input, sThat->m->trappedErr);
1369 ctxt->deleteStreamOnClose = true;
1370
1371 /* create an input buffer with custom hooks */
1372 xmlParserInputBufferPtr bufPtr =
1373 xmlParserInputBufferCreateIO (ReadCallback, CloseCallback,
1374 ctxt, XML_CHAR_ENCODING_NONE);
1375 if (bufPtr)
1376 {
1377 /* create an input stream */
1378 xmlParserInputPtr inputPtr =
1379 xmlNewIOInputStream (aCtxt, bufPtr, XML_CHAR_ENCODING_NONE);
1380
1381 if (inputPtr != NULL)
1382 {
1383 /* pass over the URI to the stream struct (it's NULL by
1384 * default) */
1385 inputPtr->filename =
1386 (char *) xmlCanonicPath ((const xmlChar *) input->uri());
1387 return inputPtr;
1388 }
1389 }
1390
1391 /* either of libxml calls failed */
1392
1393 if (bufPtr)
1394 xmlFreeParserInputBuffer (bufPtr);
1395
1396 delete input;
1397 delete ctxt;
1398
1399 throw ENoMemory();
1400 }
1401 catch (const EIPRTFailure &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1402 catch (const settings::Error &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1403 catch (const std::exception &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1404 catch (...) { sThat->m->trappedErr.reset (stdx::new_exception_trap (LogicError (RT_SRC_POS))); }
1405
1406 return NULL;
1407}
1408
1409} /* namespace settings */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette