VirtualBox

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

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

Main/Settings: Added XSLT-based auto-conversion functionality to the XmlTree backend.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 38.9 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.virtualbox.org. 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
801 xmlParserCtxtPtr ctxt;
802 xmlDocPtr doc;
803
804 Key root;
805
806 InputResolver *inputResolver;
807
808 std::auto_ptr <stdx::exception_trap_base> trappedErr;
809
810 struct AutoConv
811 {
812 AutoConv() : root (NULL), attr (NULL), version (NULL), xslt (NULL) {}
813 ~AutoConv() { uninit(); }
814
815 void uninit()
816 {
817 RTStrFree (xslt); xslt = NULL;
818 RTStrFree (version); version = NULL;
819 RTStrFree (attr); attr = NULL;
820 RTStrFree (root); root = NULL;
821 }
822
823 bool isNull() { return xslt == NULL; }
824
825 char *root;
826 char *attr;
827 char *version;
828 char *xslt;
829 }
830 autoConv;
831
832 /**
833 * This is to avoid throwing exceptions while in libxml2 code and
834 * redirect them to our level instead. Also used to perform clean up
835 * by deleting the I/O stream instance and self when requested.
836 */
837 struct IOCtxt
838 {
839 IOCtxt (Stream *aStream, std::auto_ptr <stdx::exception_trap_base> &aErr)
840 : stream (aStream), deleteStreamOnClose (false)
841 , err (aErr) {}
842
843 template <typename T>
844 void setErr (const T& aErr) { err.reset (new stdx::exception_trap <T> (aErr)); }
845
846 void resetErr() { err.reset(); }
847
848 Stream *stream;
849 bool deleteStreamOnClose;
850
851 std::auto_ptr <stdx::exception_trap_base> &err;
852 };
853
854 struct InputCtxt : public IOCtxt
855 {
856 InputCtxt (Input *aInput, std::auto_ptr <stdx::exception_trap_base> &aErr)
857 : IOCtxt (aInput, aErr), input (aInput) {}
858
859 Input *input;
860 };
861
862 struct OutputCtxt : public IOCtxt
863 {
864 OutputCtxt (Output *aOutput, std::auto_ptr <stdx::exception_trap_base> &aErr)
865 : IOCtxt (aOutput, aErr), output (aOutput) {}
866
867 Output *output;
868 };
869};
870
871XmlTreeBackend::XmlTreeBackend()
872 : m (new Data())
873{
874 /* create a parser context */
875 m->ctxt = xmlNewParserCtxt();
876 if (m->ctxt == NULL)
877 throw ENoMemory();
878}
879
880XmlTreeBackend::~XmlTreeBackend()
881{
882 reset();
883
884 xmlFreeParserCtxt (m->ctxt);
885 m->ctxt = NULL;
886}
887
888void XmlTreeBackend::setInputResolver (InputResolver &aResolver)
889{
890 m->inputResolver = &aResolver;
891}
892
893void XmlTreeBackend::resetInputResolver()
894{
895 m->inputResolver = NULL;
896}
897
898void XmlTreeBackend::setAutoConversion (const char *aRoot, const char *aAttr,
899 const char *aVersion, const char *aTemplate)
900{
901 if (aRoot == NULL && aAttr == NULL && aVersion == NULL && aTemplate == NULL)
902 {
903 m->autoConv.uninit();
904 return;
905 }
906
907 if (aRoot == NULL || aAttr == NULL || aVersion == NULL || aTemplate == NULL)
908 throw EInvalidArg (RT_SRC_POS);
909
910 m->autoConv.root = RTStrDup (aRoot);
911 m->autoConv.attr = RTStrDup (aAttr);
912 m->autoConv.version = RTStrDup (aVersion);
913 m->autoConv.xslt = RTStrDup (aTemplate);
914}
915
916void XmlTreeBackend::rawRead (Input &aInput, const char *aSchema /* = NULL */,
917 int aFlags /* = 0 */)
918{
919 /* Reset error variables used to memorize exceptions while inside the
920 * libxml2 code. */
921 m->trappedErr.reset();
922
923 /* Set up the external entity resolver. Note that we do it in a
924 * thread-unsafe fashion because this stuff is not thread-safe in libxml2.
925 * Making it thread-safe would require a) guarding this method with a
926 * mutex and b) requiring our API caller not to use libxml2 on some other
927 * thread (which is not practically possible). So, our API is not
928 * thread-safe for now. */
929 xmlExternalEntityLoader oldEntityLoader = xmlGetExternalEntityLoader();
930 sThat = this;
931 xmlSetExternalEntityLoader (ExternalEntityLoader);
932
933 /* Note: when parsing we use XML_PARSE_NOBLANKS to instruct libxml2 to
934 * remove text nodes that contain only blanks. This is important because
935 * otherwise xmlSaveDoc() won't be able to do proper indentation on
936 * output. */
937
938 /* parse the stream */
939 /* NOTE: new InputCtxt instance will be deleted when the stream is closed by
940 * the libxml2 API (e.g. when calling xmlFreeParserCtxt()) */
941 xmlDocPtr doc = xmlCtxtReadIO (m->ctxt,
942 ReadCallback, CloseCallback,
943 new Data::InputCtxt (&aInput, m->trappedErr),
944 aInput.uri(), NULL,
945 XML_PARSE_NOBLANKS);
946 if (doc == NULL)
947 {
948 /* restore the previous entity resolver */
949 xmlSetExternalEntityLoader (oldEntityLoader);
950 sThat = NULL;
951
952 /* look if there was a forwared exception from the lower level */
953 if (m->trappedErr.get() != NULL)
954 m->trappedErr->rethrow();
955
956 throw XmlError (xmlCtxtGetLastError (m->ctxt));
957 }
958
959 /* perform automatic document transformation if necessary */
960 if (!m->autoConv.isNull())
961 {
962 Key root = Key (new XmlKeyBackend (xmlDocGetRootElement (doc)));
963 if (!strcmp (root.name(), m->autoConv.root))
964 {
965 const char *ver = root.stringValue (m->autoConv.attr);
966 if (strcmp (ver, m->autoConv.version))
967 {
968 /* version mismatch */
969
970 xmlDocPtr xsltDoc = NULL;
971 xsltStylesheetPtr xslt = NULL;
972 xsltTransformContextPtr tranCtxt = NULL;
973 char *errorStr = NULL;
974
975 try
976 {
977 /* parse the XSLT */
978 {
979 Input *xsltInput =
980 m->inputResolver->resolveEntity (m->autoConv.xslt, NULL);
981 /* NOTE: new InputCtxt instance will be deleted when the
982 * stream is closed by the libxml2 API */
983 xsltDoc = xmlCtxtReadIO (m->ctxt,
984 ReadCallback, CloseCallback,
985 new Data::InputCtxt (xsltInput, m->trappedErr),
986 m->autoConv.xslt, NULL,
987 0);
988 delete xsltInput;
989 }
990
991 if (xsltDoc == NULL)
992 {
993 /* look if there was a forwared exception from the lower level */
994 if (m->trappedErr.get() != NULL)
995 m->trappedErr->rethrow();
996
997 throw XmlError (xmlCtxtGetLastError (m->ctxt));
998 }
999
1000 xslt = xsltParseStylesheetDoc (xsltDoc);
1001 if (xslt == NULL)
1002 throw LogicError (RT_SRC_POS);
1003
1004 /* setup transformation error reporting */
1005 tranCtxt = xsltNewTransformContext (xslt, xsltDoc);
1006 if (tranCtxt == NULL)
1007 throw LogicError (RT_SRC_POS);
1008 xsltSetTransformErrorFunc (tranCtxt, &errorStr, ValidityErrorCallback);
1009
1010 xmlDocPtr newDoc = xsltApplyStylesheetUser (xslt, doc, NULL,
1011 NULL, NULL, tranCtxt);
1012 if (newDoc == NULL)
1013 throw LogicError (RT_SRC_POS);
1014
1015 if (errorStr != NULL)
1016 {
1017 xmlFreeDoc (newDoc);
1018 throw Error (errorStr);
1019 /* errorStr is freed in catch(...) below */
1020 }
1021
1022 /* replace the old document on success */
1023 xmlFreeDoc (doc);
1024 doc = newDoc;
1025
1026 xsltFreeTransformContext (tranCtxt);
1027
1028 /* NOTE: xsltFreeStylesheet() also fress the document
1029 * passed to xsltParseStylesheetDoc(). */
1030 xsltFreeStylesheet (xslt);
1031 }
1032 catch (...)
1033 {
1034 /* restore the previous entity resolver */
1035 xmlSetExternalEntityLoader (oldEntityLoader);
1036 sThat = NULL;
1037
1038 RTStrFree (errorStr);
1039
1040 if (tranCtxt != NULL)
1041 xsltFreeTransformContext (tranCtxt);
1042
1043 /* NOTE: xsltFreeStylesheet() also fress the document
1044 * passed to xsltParseStylesheetDoc(). */
1045 if (xslt != NULL)
1046 xsltFreeStylesheet (xslt);
1047 else if (xsltDoc != NULL)
1048 xmlFreeDoc (xsltDoc);
1049
1050 throw;
1051 }
1052 }
1053 }
1054 }
1055
1056 /* validate the document */
1057 if (aSchema != NULL)
1058 {
1059 xmlSchemaParserCtxtPtr schemaCtxt = NULL;
1060 xmlSchemaPtr schema = NULL;
1061 xmlSchemaValidCtxtPtr validCtxt = NULL;
1062 char *errorStr = NULL;
1063
1064 try
1065 {
1066 bool valid = false;
1067
1068 schemaCtxt = xmlSchemaNewParserCtxt (aSchema);
1069 if (schemaCtxt == NULL)
1070 throw LogicError (RT_SRC_POS);
1071
1072 /* set our error handlers */
1073 xmlSchemaSetParserErrors (schemaCtxt, ValidityErrorCallback,
1074 ValidityWarningCallback, &errorStr);
1075 xmlSchemaSetParserStructuredErrors (schemaCtxt,
1076 StructuredErrorCallback,
1077 &errorStr);
1078 /* load schema */
1079 schema = xmlSchemaParse (schemaCtxt);
1080 if (schema != NULL)
1081 {
1082 validCtxt = xmlSchemaNewValidCtxt (schema);
1083 if (validCtxt == NULL)
1084 throw LogicError (RT_SRC_POS);
1085
1086 /* instruct to create default attribute's values in the document */
1087 if (aFlags & Read_AddDefaults)
1088 xmlSchemaSetValidOptions (validCtxt, XML_SCHEMA_VAL_VC_I_CREATE);
1089
1090 /* set our error handlers */
1091 xmlSchemaSetValidErrors (validCtxt, ValidityErrorCallback,
1092 ValidityWarningCallback, &errorStr);
1093
1094 /* finally, validate */
1095 valid = xmlSchemaValidateDoc (validCtxt, doc) == 0;
1096 }
1097
1098 if (!valid)
1099 {
1100 /* look if there was a forwared exception from the lower level */
1101 if (m->trappedErr.get() != NULL)
1102 m->trappedErr->rethrow();
1103
1104 if (errorStr == NULL)
1105 throw LogicError (RT_SRC_POS);
1106
1107 throw Error (errorStr);
1108 /* errorStr is freed in catch(...) below */
1109 }
1110
1111 RTStrFree (errorStr);
1112
1113 xmlSchemaFreeValidCtxt (validCtxt);
1114 xmlSchemaFree (schema);
1115 xmlSchemaFreeParserCtxt (schemaCtxt);
1116 }
1117 catch (...)
1118 {
1119 /* restore the previous entity resolver */
1120 xmlSetExternalEntityLoader (oldEntityLoader);
1121 sThat = NULL;
1122
1123 RTStrFree (errorStr);
1124
1125 if (validCtxt)
1126 xmlSchemaFreeValidCtxt (validCtxt);
1127 if (schema)
1128 xmlSchemaFree (schema);
1129 if (schemaCtxt)
1130 xmlSchemaFreeParserCtxt (schemaCtxt);
1131
1132 throw;
1133 }
1134 }
1135
1136 /* restore the previous entity resolver */
1137 xmlSetExternalEntityLoader (oldEntityLoader);
1138 sThat = NULL;
1139
1140 /* reset the previous tree on success */
1141 reset();
1142
1143 m->doc = doc;
1144 /* assign the root key */
1145 m->root = Key (new XmlKeyBackend (xmlDocGetRootElement (m->doc)));
1146}
1147
1148void XmlTreeBackend::rawWrite (Output &aOutput)
1149{
1150 /* reset error variables used to memorize exceptions while inside the
1151 * libxml2 code */
1152 m->trappedErr.reset();
1153
1154 /* set up an input stream for parsing the document. This will be deleted
1155 * when the stream is closed by the libxml2 API (e.g. when calling
1156 * xmlFreeParserCtxt()). */
1157 Data::OutputCtxt *outputCtxt =
1158 new Data::OutputCtxt (&aOutput, m->trappedErr);
1159
1160 /* serialize to the stream */
1161
1162 xmlIndentTreeOutput = 1;
1163 xmlTreeIndentString = " ";
1164 xmlSaveNoEmptyTags = 0;
1165
1166 xmlSaveCtxtPtr saveCtxt = xmlSaveToIO (WriteCallback, CloseCallback,
1167 outputCtxt, NULL,
1168 XML_SAVE_FORMAT);
1169 if (saveCtxt == NULL)
1170 throw LogicError (RT_SRC_POS);
1171
1172 long rc = xmlSaveDoc (saveCtxt, m->doc);
1173 if (rc == -1)
1174 {
1175 /* look if there was a forwared exception from the lower level */
1176 if (m->trappedErr.get() != NULL)
1177 m->trappedErr->rethrow();
1178
1179 /* there must be an exception from the Output implementation,
1180 * otherwise the save operation must always succeed. */
1181 throw LogicError (RT_SRC_POS);
1182 }
1183
1184 xmlSaveClose (saveCtxt);
1185}
1186
1187void XmlTreeBackend::reset()
1188{
1189 if (m->doc)
1190 {
1191 /* reset the root key's node */
1192 GetKeyBackend (m->root)->mNode = NULL;
1193 /* free the document*/
1194 xmlFreeDoc (m->doc);
1195 m->doc = NULL;
1196 }
1197}
1198
1199Key &XmlTreeBackend::rootKey() const
1200{
1201 return m->root;
1202}
1203
1204/* static */
1205int XmlTreeBackend::ReadCallback (void *aCtxt, char *aBuf, int aLen)
1206{
1207 AssertReturn (aCtxt != NULL, 0);
1208
1209 Data::InputCtxt *ctxt = static_cast <Data::InputCtxt *> (aCtxt);
1210
1211 /* To prevent throwing exceptions while inside libxml2 code, we catch
1212 * them and forward to our level using a couple of variables. */
1213 try
1214 {
1215 return ctxt->input->read (aBuf, aLen);
1216 }
1217 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1218 catch (const settings::Error &err) { ctxt->setErr (err); }
1219 catch (const std::exception &err) { ctxt->setErr (err); }
1220 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1221
1222 return -1 /* failure */;
1223}
1224
1225/* static */
1226int XmlTreeBackend::WriteCallback (void *aCtxt, const char *aBuf, int aLen)
1227{
1228 AssertReturn (aCtxt != NULL, 0);
1229
1230 Data::OutputCtxt *ctxt = static_cast <Data::OutputCtxt *> (aCtxt);
1231
1232 /* To prevent throwing exceptions while inside libxml2 code, we catch
1233 * them and forward to our level using a couple of variables. */
1234 try
1235 {
1236 return ctxt->output->write (aBuf, aLen);
1237 }
1238 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1239 catch (const settings::Error &err) { ctxt->setErr (err); }
1240 catch (const std::exception &err) { ctxt->setErr (err); }
1241 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1242
1243 return -1 /* failure */;
1244}
1245
1246/* static */
1247int XmlTreeBackend::CloseCallback (void *aCtxt)
1248{
1249 AssertReturn (aCtxt != NULL, 0);
1250
1251 Data::IOCtxt *ctxt = static_cast <Data::IOCtxt *> (aCtxt);
1252
1253 /* To prevent throwing exceptions while inside libxml2 code, we catch
1254 * them and forward to our level using a couple of variables. */
1255 try
1256 {
1257 /// @todo there is no explicit close semantics in Stream yet
1258#if 0
1259 ctxt->stream->close();
1260#endif
1261
1262 /* perform cleanup when necessary */
1263 if (ctxt->deleteStreamOnClose)
1264 delete ctxt->stream;
1265
1266 delete ctxt;
1267
1268 return 0 /* success */;
1269 }
1270 catch (const EIPRTFailure &err) { ctxt->setErr (err); }
1271 catch (const settings::Error &err) { ctxt->setErr (err); }
1272 catch (const std::exception &err) { ctxt->setErr (err); }
1273 catch (...) { ctxt->setErr (LogicError (RT_SRC_POS)); }
1274
1275 return -1 /* failure */;
1276}
1277
1278/* static */
1279void XmlTreeBackend::ValidityErrorCallback (void *aCtxt, const char *aMsg, ...)
1280{
1281 AssertReturnVoid (aCtxt != NULL);
1282 AssertReturnVoid (aMsg != NULL);
1283
1284 char * &str = *(char * *) aCtxt;
1285
1286 char *newMsg = NULL;
1287 {
1288 va_list args;
1289 va_start (args, aMsg);
1290 RTStrAPrintfV (&newMsg, aMsg, args);
1291 va_end (args);
1292 }
1293
1294 AssertReturnVoid (newMsg != NULL);
1295
1296 /* strip spaces, trailing EOLs and dot-like char */
1297 size_t newMsgLen = strlen (newMsg);
1298 while (newMsgLen && strchr (" \n.?!", newMsg [newMsgLen - 1]))
1299 -- newMsgLen;
1300
1301 /* anything left? */
1302 if (newMsgLen > 0)
1303 {
1304 if (str == NULL)
1305 {
1306 str = newMsg;
1307 newMsg [newMsgLen] = '\0';
1308 }
1309 else
1310 {
1311 /* append to the existing string */
1312 size_t strLen = strlen (str);
1313 char *newStr = (char *) RTMemRealloc (str, strLen + 2 + newMsgLen + 1);
1314 AssertReturnVoid (newStr != NULL);
1315
1316 memcpy (newStr + strLen, ".\n", 2);
1317 memcpy (newStr + strLen + 2, newMsg, newMsgLen);
1318 newStr [strLen + 2 + newMsgLen] = '\0';
1319 str = newStr;
1320 RTStrFree (newMsg);
1321 }
1322 }
1323}
1324
1325/* static */
1326void XmlTreeBackend::ValidityWarningCallback (void *aCtxt, const char *aMsg, ...)
1327{
1328 NOREF (aCtxt);
1329 NOREF (aMsg);
1330}
1331
1332/* static */
1333void XmlTreeBackend::StructuredErrorCallback (void *aCtxt, xmlErrorPtr aErr)
1334{
1335 AssertReturnVoid (aCtxt != NULL);
1336 AssertReturnVoid (aErr != NULL);
1337
1338 char * &str = *(char * *) aCtxt;
1339
1340 char *newMsg = XmlError::Format (aErr);
1341 AssertReturnVoid (newMsg != NULL);
1342
1343 if (str == NULL)
1344 str = newMsg;
1345 else
1346 {
1347 /* append to the existing string */
1348 size_t newMsgLen = strlen (newMsg);
1349 size_t strLen = strlen (str);
1350 char *newStr = (char *) RTMemRealloc (str, strLen + newMsgLen + 2);
1351 AssertReturnVoid (newStr != NULL);
1352
1353 memcpy (newStr + strLen, ".\n", 2);
1354 memcpy (newStr + strLen + 2, newMsg, newMsgLen);
1355 str = newStr;
1356 RTStrFree (newMsg);
1357 }
1358}
1359
1360/* static */
1361XmlTreeBackend *XmlTreeBackend::sThat = NULL;
1362
1363/* static */
1364xmlParserInputPtr XmlTreeBackend::ExternalEntityLoader (const char *aURI,
1365 const char *aID,
1366 xmlParserCtxtPtr aCtxt)
1367{
1368 AssertReturn (sThat != NULL, NULL);
1369
1370 if (sThat->m->inputResolver == NULL)
1371 return gGlobal.xml.defaultEntityLoader (aURI, aID, aCtxt);
1372
1373 /* To prevent throwing exceptions while inside libxml2 code, we catch
1374 * them and forward to our level using a couple of variables. */
1375 try
1376 {
1377 Input *input = sThat->m->inputResolver->resolveEntity (aURI, aID);
1378 if (input == NULL)
1379 return NULL;
1380
1381 Data::InputCtxt *ctxt = new Data::InputCtxt (input, sThat->m->trappedErr);
1382 ctxt->deleteStreamOnClose = true;
1383
1384 /* create an input buffer with custom hooks */
1385 xmlParserInputBufferPtr bufPtr =
1386 xmlParserInputBufferCreateIO (ReadCallback, CloseCallback,
1387 ctxt, XML_CHAR_ENCODING_NONE);
1388 if (bufPtr)
1389 {
1390 /* create an input stream */
1391 xmlParserInputPtr inputPtr =
1392 xmlNewIOInputStream (aCtxt, bufPtr, XML_CHAR_ENCODING_NONE);
1393
1394 if (inputPtr != NULL)
1395 {
1396 /* pass over the URI to the stream struct (it's NULL by
1397 * default) */
1398 inputPtr->filename =
1399 (char *) xmlCanonicPath ((const xmlChar *) input->uri());
1400 return inputPtr;
1401 }
1402 }
1403
1404 /* either of libxml calls failed */
1405
1406 if (bufPtr)
1407 xmlFreeParserInputBuffer (bufPtr);
1408
1409 delete input;
1410 delete ctxt;
1411
1412 throw ENoMemory();
1413 }
1414 catch (const EIPRTFailure &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1415 catch (const settings::Error &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1416 catch (const std::exception &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1417 catch (...) { sThat->m->trappedErr.reset (stdx::new_exception_trap (LogicError (RT_SRC_POS))); }
1418
1419 return NULL;
1420}
1421
1422} /* namespace settings */
Note: See TracBrowser for help on using the repository browser.

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