VirtualBox

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

Last change on this file since 16188 was 16188, checked in by vboxsync, 16 years ago

Merge OVF branch.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 33.8 KB
Line 
1/** @file
2 * Settings File Manipulation API.
3 */
4
5/*
6 * Copyright (C) 2007 Sun Microsystems, Inc.
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 (GPL) as published by the Free Software
12 * Foundation, in version 2 as it comes in the "COPYING" file of the
13 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
14 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
15 *
16 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
17 * Clara, CA 95054 USA or visit http://www.sun.com if you need
18 * additional information or have any questions.
19 */
20
21#include "VBox/settings.h"
22
23#include "Logging.h"
24
25#include <iprt/err.h>
26#include <iprt/file.h>
27#include <iprt/lock.h>
28
29#include <libxml/tree.h>
30#include <libxml/parser.h>
31#include <libxml/globals.h>
32#include <libxml/xmlIO.h>
33#include <libxml/xmlsave.h>
34#include <libxml/uri.h>
35
36#include <libxml/xmlschemas.h>
37
38#include <libxslt/xsltInternals.h>
39#include <libxslt/transform.h>
40#include <libxslt/xsltutils.h>
41
42#include <string.h>
43
44
45namespace settings
46{
47
48// Helpers
49////////////////////////////////////////////////////////////////////////////////
50
51inline int sFromHex (char aChar)
52{
53 if (aChar >= '0' && aChar <= '9')
54 return aChar - '0';
55 if (aChar >= 'A' && aChar <= 'F')
56 return aChar - 'A' + 0xA;
57 if (aChar >= 'a' && aChar <= 'f')
58 return aChar - 'a' + 0xA;
59
60 throw ENoConversion(xml::FmtStr ("'%c' (0x%02X) is not hex", aChar, aChar));
61}
62
63inline char sToHex (int aDigit)
64{
65 return (aDigit < 0xA) ? aDigit + '0' : aDigit - 0xA + 'A';
66}
67
68static char *duplicate_chars (const char *that)
69{
70 char *result = NULL;
71 if (that != NULL)
72 {
73 size_t len = strlen (that) + 1;
74 result = new char [len];
75 if (result != NULL)
76 memcpy (result, that, len);
77 }
78 return result;
79}
80
81//////////////////////////////////////////////////////////////////////////////
82// string -> type conversions
83//////////////////////////////////////////////////////////////////////////////
84
85uint64_t FromStringInteger (const char *aValue, bool aSigned,
86 int aBits, uint64_t aMin, uint64_t aMax)
87{
88 if (aValue == NULL)
89 throw ENoValue();
90
91 switch (aBits)
92 {
93 case 8:
94 case 16:
95 case 32:
96 case 64:
97 break;
98 default:
99 throw xml::ENotImplemented (RT_SRC_POS);
100 }
101
102 if (aSigned)
103 {
104 int64_t result;
105 int vrc = RTStrToInt64Full (aValue, 0, &result);
106 if (RT_SUCCESS (vrc))
107 {
108 if (result >= (int64_t) aMin && result <= (int64_t) aMax)
109 return (uint64_t) result;
110 }
111 }
112 else
113 {
114 uint64_t result;
115 int vrc = RTStrToUInt64Full (aValue, 0, &result);
116 if (RT_SUCCESS (vrc))
117 {
118 if (result >= aMin && result <= aMax)
119 return result;
120 }
121 }
122
123 throw ENoConversion(xml::FmtStr("'%s' is not integer", aValue));
124}
125
126template<> bool FromString <bool> (const char *aValue)
127{
128 if (aValue == NULL)
129 throw ENoValue();
130
131 if (strcmp (aValue, "true") == 0 ||
132 strcmp (aValue, "1") == 0)
133 /* This contradicts the XML Schema's interpretation of boolean: */
134 //strcmp (aValue, "yes") == 0 ||
135 //strcmp (aValue, "on") == 0)
136 return true;
137 else if (strcmp (aValue, "false") == 0 ||
138 strcmp (aValue, "0") == 0)
139 /* This contradicts the XML Schema's interpretation of boolean: */
140 //strcmp (aValue, "no") == 0 ||
141 //strcmp (aValue, "off") == 0)
142 return false;
143
144 throw ENoConversion(xml::FmtStr("'%s' is not bool", aValue));
145}
146
147template<> RTTIMESPEC FromString <RTTIMESPEC> (const char *aValue)
148{
149 if (aValue == NULL)
150 throw ENoValue();
151
152 /* Parse ISO date (xsd:dateTime). The format is:
153 * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
154 * where zzzzzz is: (('+' | '-') hh ':' mm) | 'Z' */
155 uint32_t yyyy = 0;
156 uint16_t mm = 0, dd = 0, hh = 0, mi = 0, ss = 0;
157 char buf [256];
158 if (strlen (aValue) > RT_ELEMENTS (buf) - 1 ||
159 sscanf (aValue, "%d-%hu-%huT%hu:%hu:%hu%s",
160 &yyyy, &mm, &dd, &hh, &mi, &ss, buf) == 7)
161 {
162 /* currently, we accept only the UTC timezone ('Z'),
163 * ignoring fractional seconds, if present */
164 if (buf [0] == 'Z' ||
165 (buf [0] == '.' && buf [strlen (buf) - 1] == 'Z'))
166 {
167 RTTIME time = { yyyy, (uint8_t) mm, 0, 0, (uint8_t) dd,
168 (uint8_t) hh, (uint8_t) mi, (uint8_t) ss, 0,
169 RTTIME_FLAGS_TYPE_UTC };
170 if (RTTimeNormalize (&time))
171 {
172 RTTIMESPEC timeSpec;
173 if (RTTimeImplode (&timeSpec, &time))
174 return timeSpec;
175 }
176 }
177 else
178 throw ENoConversion(xml::FmtStr("'%s' is not UTC date", aValue));
179 }
180
181 throw ENoConversion(xml::FmtStr("'%s' is not ISO date", aValue));
182}
183
184stdx::char_auto_ptr FromString (const char *aValue, size_t *aLen)
185{
186 if (aValue == NULL)
187 throw ENoValue();
188
189 /* each two chars produce one byte */
190 size_t len = strlen (aValue) / 2;
191
192 /* therefore, the original length must be even */
193 if (len % 2 != 0)
194 throw ENoConversion(xml::FmtStr("'%.*s' is not binary data",
195 aLen, aValue));
196
197 stdx::char_auto_ptr result (new char [len]);
198
199 const char *src = aValue;
200 char *dst = result.get();
201
202 for (size_t i = 0; i < len; ++ i, ++ dst)
203 {
204 *dst = sFromHex (*src ++) << 4;
205 *dst |= sFromHex (*src ++);
206 }
207
208 if (aLen != NULL)
209 *aLen = len;
210
211 return result;
212}
213
214//////////////////////////////////////////////////////////////////////////////
215// type -> string conversions
216//////////////////////////////////////////////////////////////////////////////
217
218stdx::char_auto_ptr ToStringInteger (uint64_t aValue, unsigned int aBase,
219 bool aSigned, int aBits)
220{
221 unsigned int flags = RTSTR_F_SPECIAL;
222 if (aSigned)
223 flags |= RTSTR_F_VALSIGNED;
224
225 /* maximum is binary representation + terminator */
226 size_t len = aBits + 1;
227
228 switch (aBits)
229 {
230 case 8:
231 flags |= RTSTR_F_8BIT;
232 break;
233 case 16:
234 flags |= RTSTR_F_16BIT;
235 break;
236 case 32:
237 flags |= RTSTR_F_32BIT;
238 break;
239 case 64:
240 flags |= RTSTR_F_64BIT;
241 break;
242 default:
243 throw xml::ENotImplemented (RT_SRC_POS);
244 }
245
246 stdx::char_auto_ptr result (new char [len]);
247 int vrc = RTStrFormatNumber (result.get(), aValue, aBase, 0, 0, flags);
248 if (RT_SUCCESS (vrc))
249 return result;
250
251 throw xml::EIPRTFailure (vrc);
252}
253
254template<> stdx::char_auto_ptr ToString <bool> (const bool &aValue,
255 unsigned int aExtra /* = 0 */)
256{
257 /* Convert to the canonical form according to XML Schema */
258 stdx::char_auto_ptr result (duplicate_chars (aValue ? "true" : "false"));
259 return result;
260}
261
262template<> stdx::char_auto_ptr ToString <RTTIMESPEC> (const RTTIMESPEC &aValue,
263 unsigned int aExtra /* = 0 */)
264{
265 RTTIME time;
266 if (!RTTimeExplode (&time, &aValue))
267 throw ENoConversion(xml::FmtStr("timespec %lld ms is invalid",
268 RTTimeSpecGetMilli (&aValue)));
269
270 /* Store ISO date (xsd:dateTime). The format is:
271 * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
272 * where zzzzzz is: (('+' | '-') hh ':' mm) | 'Z' */
273 char buf [256];
274 RTStrPrintf (buf, sizeof (buf),
275 "%04ld-%02hd-%02hdT%02hd:%02hd:%02hdZ",
276 time.i32Year, (uint16_t) time.u8Month, (uint16_t) time.u8MonthDay,
277 (uint16_t) time.u8Hour, (uint16_t) time.u8Minute, (uint16_t) time.u8Second);
278
279 stdx::char_auto_ptr result (duplicate_chars (buf));
280 return result;
281}
282
283stdx::char_auto_ptr ToString (const char *aData, size_t aLen)
284{
285 /* each byte will produce two hex digits and there will be a null
286 * terminator */
287 stdx::char_auto_ptr result (new char [aLen * 2 + 1]);
288
289 const char *src = aData;
290 char *dst = result.get();
291
292 for (size_t i = 0; i < aLen; ++ i, ++ src)
293 {
294 *dst++ = sToHex ((*src) >> 4);
295 *dst++ = sToHex ((*src) & 0xF);
296 }
297
298 *dst = '\0';
299
300 return result;
301}
302
303//////////////////////////////////////////////////////////////////////////////
304// XmlKeyBackend Class
305//////////////////////////////////////////////////////////////////////////////
306
307class XmlKeyBackend : public Key::Backend
308{
309public:
310
311 XmlKeyBackend (xmlNodePtr aNode);
312 ~XmlKeyBackend();
313
314 const char *name() const;
315 void setName (const char *aName);
316 const char *value (const char *aName) const;
317 void setValue (const char *aName, const char *aValue);
318
319 Key::List keys (const char *aName = NULL) const;
320 Key findKey (const char *aName) const;
321
322 Key appendKey (const char *aName);
323 void zap();
324
325 void *position() const { return mNode; }
326
327private:
328
329 xmlNodePtr mNode;
330
331 xmlChar *mNodeText;
332
333 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (XmlKeyBackend);
334
335 friend class XmlTreeBackend;
336};
337
338XmlKeyBackend::XmlKeyBackend (xmlNodePtr aNode)
339 : mNode (aNode), mNodeText (NULL)
340{
341 AssertReturnVoid (mNode);
342 AssertReturnVoid (mNode->type == XML_ELEMENT_NODE);
343}
344
345XmlKeyBackend::~XmlKeyBackend()
346{
347 xmlFree (mNodeText);
348}
349
350const char *XmlKeyBackend::name() const
351{
352 return mNode ? (char *) mNode->name : NULL;
353}
354
355void XmlKeyBackend::setName (const char *aName)
356{
357 throw xml::ENotImplemented (RT_SRC_POS);
358}
359
360const char *XmlKeyBackend::value (const char *aName) const
361{
362 if (!mNode)
363 return NULL;
364
365 if (aName == NULL)
366 {
367 /* @todo xmlNodeListGetString (,,1) returns NULL for things like
368 * <Foo></Foo> and may want to return "" in this case to distinguish
369 * from <Foo/> (where NULL is pretty much expected). */
370 if (!mNodeText)
371 unconst (mNodeText) =
372 xmlNodeListGetString (mNode->doc, mNode->children, 0);
373 return (char *) mNodeText;
374 }
375
376 xmlAttrPtr attr = xmlHasProp (mNode, (const xmlChar *) aName);
377 if (!attr)
378 return NULL;
379
380 if (attr->type == XML_ATTRIBUTE_NODE)
381 {
382 /* @todo for now, we only understand the most common case: only 1 text
383 * node comprises the attribute's contents. Otherwise we'd need to
384 * return a newly allocated string buffer to the caller that
385 * concatenates all text nodes and obey him to free it or provide our
386 * own internal map of attribute=value pairs and return const pointers
387 * to values from this map. */
388 if (attr->children != NULL &&
389 attr->children->next == NULL &&
390 (attr->children->type == XML_TEXT_NODE ||
391 attr->children->type == XML_CDATA_SECTION_NODE))
392 return (char *) attr->children->content;
393 }
394 else if (attr->type == XML_ATTRIBUTE_DECL)
395 {
396 return (char *) ((xmlAttributePtr) attr)->defaultValue;
397 }
398
399 return NULL;
400}
401
402void XmlKeyBackend::setValue (const char *aName, const char *aValue)
403{
404 if (!mNode)
405 return;
406
407 if (aName == NULL)
408 {
409 xmlChar *value = (xmlChar *) aValue;
410 if (value != NULL)
411 {
412 value = xmlEncodeSpecialChars (mNode->doc, value);
413 if (value == NULL)
414 throw xml::ENoMemory();
415 }
416
417 xmlNodeSetContent (mNode, value);
418
419 if (value != (xmlChar *) aValue)
420 xmlFree (value);
421
422 /* outdate the node text holder */
423 if (mNodeText != NULL)
424 {
425 xmlFree (mNodeText);
426 mNodeText = NULL;
427 }
428
429 return;
430 }
431
432 if (aValue == NULL)
433 {
434 /* remove the attribute if it exists */
435 xmlAttrPtr attr = xmlHasProp (mNode, (const xmlChar *) aName);
436 if (attr != NULL)
437 {
438 int rc = xmlRemoveProp (attr);
439 if (rc != 0)
440 throw xml::EInvalidArg (RT_SRC_POS);
441 }
442 return;
443 }
444
445 xmlAttrPtr attr = xmlSetProp (mNode, (const xmlChar *) aName,
446 (const xmlChar *) aValue);
447 if (attr == NULL)
448 throw xml::ENoMemory();
449}
450
451Key::List XmlKeyBackend::keys (const char *aName /* = NULL */) const
452{
453 Key::List list;
454
455 if (!mNode)
456 return list;
457
458 for (xmlNodePtr node = mNode->children; node; node = node->next)
459 {
460 if (node->type == XML_ELEMENT_NODE)
461 {
462 if (aName == NULL ||
463 strcmp (aName, (char *) node->name) == 0)
464 list.push_back (Key (new XmlKeyBackend (node)));
465 }
466 }
467
468 return list;
469}
470
471Key XmlKeyBackend::findKey (const char *aName) const
472{
473 Key key;
474
475 if (!mNode)
476 return key;
477
478 for (xmlNodePtr node = mNode->children; node; node = node->next)
479 {
480 if (node->type == XML_ELEMENT_NODE)
481 {
482 if (aName == NULL ||
483 strcmp (aName, (char *) node->name) == 0)
484 {
485 key = Key (new XmlKeyBackend (node));
486 break;
487 }
488 }
489 }
490
491 return key;
492}
493
494Key XmlKeyBackend::appendKey (const char *aName)
495{
496 if (!mNode)
497 return Key();
498
499 xmlNodePtr node = xmlNewChild (mNode, NULL, (const xmlChar *) aName, NULL);
500 if (node == NULL)
501 throw xml::ENoMemory();
502
503 return Key (new XmlKeyBackend (node));
504}
505
506void XmlKeyBackend::zap()
507{
508 if (!mNode)
509 return;
510
511 xmlUnlinkNode (mNode);
512 xmlFreeNode (mNode);
513 mNode = NULL;
514}
515
516//////////////////////////////////////////////////////////////////////////////
517// XmlTreeBackend Class
518//////////////////////////////////////////////////////////////////////////////
519
520struct XmlTreeBackend::Data
521{
522 Data() : ctxt (NULL), doc (NULL)
523 , inputResolver (NULL)
524 , autoConverter (NULL), oldVersion (NULL) {}
525
526 xmlParserCtxtPtr ctxt;
527 xmlDocPtr doc;
528
529 Key root;
530
531 InputResolver *inputResolver;
532
533 AutoConverter *autoConverter;
534 char *oldVersion;
535
536 std::auto_ptr <stdx::exception_trap_base> trappedErr;
537
538 /**
539 * This is to avoid throwing exceptions while in libxml2 code and
540 * redirect them to our level instead. Also used to perform clean up
541 * by deleting the I/O stream instance and self when requested.
542 */
543 struct IOCtxt
544 {
545 IOCtxt (xml::Stream *aStream, std::auto_ptr <stdx::exception_trap_base> &aErr)
546 : stream (aStream), deleteStreamOnClose (false)
547 , err (aErr) {}
548
549 template <typename T>
550 void setErr (const T& aErr) { err.reset (new stdx::exception_trap <T> (aErr)); }
551
552 void resetErr() { err.reset(); }
553
554 xml::Stream *stream;
555 bool deleteStreamOnClose;
556
557 std::auto_ptr <stdx::exception_trap_base> &err;
558 };
559
560 struct InputCtxt : public IOCtxt
561 {
562 InputCtxt (xml::Input *aInput, std::auto_ptr <stdx::exception_trap_base> &aErr)
563 : IOCtxt (aInput, aErr), input (aInput) {}
564
565 xml::Input *input;
566 };
567
568 struct OutputCtxt : public IOCtxt
569 {
570 OutputCtxt (xml::Output *aOutput, std::auto_ptr <stdx::exception_trap_base> &aErr)
571 : IOCtxt (aOutput, aErr), output (aOutput) {}
572
573 xml::Output *output;
574 };
575};
576
577XmlTreeBackend::XmlTreeBackend()
578 : m (new Data())
579{
580 /* create a parser context */
581 m->ctxt = xmlNewParserCtxt();
582 if (m->ctxt == NULL)
583 throw xml::ENoMemory();
584}
585
586XmlTreeBackend::~XmlTreeBackend()
587{
588 reset();
589
590 xmlFreeParserCtxt (m->ctxt);
591 m->ctxt = NULL;
592}
593
594void XmlTreeBackend::setInputResolver (InputResolver &aResolver)
595{
596 m->inputResolver = &aResolver;
597}
598
599void XmlTreeBackend::resetInputResolver()
600{
601 m->inputResolver = NULL;
602}
603
604void XmlTreeBackend::setAutoConverter (AutoConverter &aConverter)
605{
606 m->autoConverter = &aConverter;
607}
608
609void XmlTreeBackend::resetAutoConverter()
610{
611 m->autoConverter = NULL;
612}
613
614const char *XmlTreeBackend::oldVersion() const
615{
616 return m->oldVersion;
617}
618
619extern "C" xmlGenericErrorFunc xsltGenericError;
620extern "C" void *xsltGenericErrorContext;
621
622void XmlTreeBackend::rawRead (xml::Input &aInput, const char *aSchema /* = NULL */,
623 int aFlags /* = 0 */)
624{
625 /* Reset error variables used to memorize exceptions while inside the
626 * libxml2 code. */
627 m->trappedErr.reset();
628
629 /* We use the global lock for the whole duration of this method to serialize
630 * access to thread-unsafe xmlGetExternalEntityLoader() and some other
631 * calls. It means that only one thread is able to parse an XML stream at a
632 * time but another choice would be to patch libxml2/libxslt which is
633 * unwanted now for several reasons. Search for "thread-safe" to find all
634 * unsafe cases. */
635 xml::GlobalLock global;
636 global.setExternalEntityLoader(ExternalEntityLoader);
637
638 sThat = this;
639 xmlDocPtr doc = NULL;
640
641 try
642 {
643 /* Note: when parsing we use XML_PARSE_NOBLANKS to instruct libxml2 to
644 * remove text nodes that contain only blanks. This is important because
645 * otherwise xmlSaveDoc() won't be able to do proper indentation on
646 * output. */
647 /* parse the stream */
648 /* NOTE: new InputCtxt instance will be deleted when the stream is closed by
649 * the libxml2 API (e.g. when calling xmlFreeParserCtxt()) */
650 doc = xmlCtxtReadIO (m->ctxt,
651 ReadCallback, CloseCallback,
652 new Data::InputCtxt (&aInput, m->trappedErr),
653 aInput.uri(), NULL,
654 XML_PARSE_NOBLANKS);
655 if (doc == NULL)
656 {
657 /* look if there was a forwared exception from the lower level */
658 if (m->trappedErr.get() != NULL)
659 m->trappedErr->rethrow();
660
661 throw xml::XmlError(xmlCtxtGetLastError (m->ctxt));
662 }
663
664 char *oldVersion = NULL;
665
666 /* perform automatic document transformation if necessary */
667 if (m->autoConverter != NULL &&
668 m->autoConverter->
669 needsConversion (Key (new XmlKeyBackend (xmlDocGetRootElement (doc))),
670 &oldVersion))
671 {
672 xmlDocPtr xsltDoc = NULL;
673 xsltStylesheetPtr xslt = NULL;
674 char *errorStr = NULL;
675
676 xmlGenericErrorFunc oldXsltGenericError = xsltGenericError;
677 void *oldXsltGenericErrorContext = xsltGenericErrorContext;
678
679 try
680 {
681 /* parse the XSLT template */
682 {
683 xml::Input *xsltInput =
684 m->inputResolver->resolveEntity
685 (m->autoConverter->templateUri(), NULL);
686 /* NOTE: new InputCtxt instance will be deleted when the
687 * stream is closed by the libxml2 API */
688 xsltDoc = xmlCtxtReadIO (m->ctxt,
689 ReadCallback, CloseCallback,
690 new Data::InputCtxt (xsltInput, m->trappedErr),
691 m->autoConverter->templateUri(),
692 NULL, 0);
693 delete xsltInput;
694 }
695
696 if (xsltDoc == NULL)
697 {
698 /* look if there was a forwared exception from the lower level */
699 if (m->trappedErr.get() != NULL)
700 m->trappedErr->rethrow();
701
702 throw xml::XmlError(xmlCtxtGetLastError (m->ctxt));
703 }
704
705 /* setup stylesheet compilation and transformation error
706 * reporting. Note that we could create a new transform context
707 * for doing xsltApplyStylesheetUser and use
708 * xsltSetTransformErrorFunc() on it to set a dedicated error
709 * handler but as long as we already do several non-thread-safe
710 * hacks, this is not really important. */
711
712 xsltGenericError = ValidityErrorCallback;
713 xsltGenericErrorContext = &errorStr;
714
715 xslt = xsltParseStylesheetDoc (xsltDoc);
716 if (xslt == NULL)
717 {
718 if (errorStr != NULL)
719 throw xml::LogicError (errorStr);
720 /* errorStr is freed in catch(...) below */
721
722 throw xml::LogicError (RT_SRC_POS);
723 }
724
725 /* repeat transformations until autoConverter is satisfied */
726 do
727 {
728 xmlDocPtr newDoc = xsltApplyStylesheet (xslt, doc, NULL);
729 if (newDoc == NULL && errorStr == NULL)
730 throw xml::LogicError (RT_SRC_POS);
731
732 if (errorStr != NULL)
733 {
734 xmlFreeDoc (newDoc);
735 throw xml::RuntimeError(errorStr);
736 /* errorStr is freed in catch(...) below */
737 }
738
739 /* replace the old document on success */
740 xmlFreeDoc (doc);
741 doc = newDoc;
742 }
743 while (m->autoConverter->
744 needsConversion (Key (new XmlKeyBackend (xmlDocGetRootElement (doc))),
745 NULL));
746
747 RTStrFree (errorStr);
748
749 /* NOTE: xsltFreeStylesheet() also fress the document
750 * passed to xsltParseStylesheetDoc(). */
751 xsltFreeStylesheet (xslt);
752
753 /* restore the previous generic error func */
754 xsltGenericError = oldXsltGenericError;
755 xsltGenericErrorContext = oldXsltGenericErrorContext;
756 }
757 catch (...)
758 {
759 RTStrFree (errorStr);
760
761 /* NOTE: xsltFreeStylesheet() also fress the document
762 * passed to xsltParseStylesheetDoc(). */
763 if (xslt != NULL)
764 xsltFreeStylesheet (xslt);
765 else if (xsltDoc != NULL)
766 xmlFreeDoc (xsltDoc);
767
768 /* restore the previous generic error func */
769 xsltGenericError = oldXsltGenericError;
770 xsltGenericErrorContext = oldXsltGenericErrorContext;
771
772 RTStrFree (oldVersion);
773
774 throw;
775 }
776 }
777
778 /* validate the document */
779 if (aSchema != NULL)
780 {
781 xmlSchemaParserCtxtPtr schemaCtxt = NULL;
782 xmlSchemaPtr schema = NULL;
783 xmlSchemaValidCtxtPtr validCtxt = NULL;
784 char *errorStr = NULL;
785
786 try
787 {
788 bool valid = false;
789
790 schemaCtxt = xmlSchemaNewParserCtxt (aSchema);
791 if (schemaCtxt == NULL)
792 throw xml::LogicError (RT_SRC_POS);
793
794 /* set our error handlers */
795 xmlSchemaSetParserErrors (schemaCtxt, ValidityErrorCallback,
796 ValidityWarningCallback, &errorStr);
797 xmlSchemaSetParserStructuredErrors (schemaCtxt,
798 StructuredErrorCallback,
799 &errorStr);
800 /* load schema */
801 schema = xmlSchemaParse (schemaCtxt);
802 if (schema != NULL)
803 {
804 validCtxt = xmlSchemaNewValidCtxt (schema);
805 if (validCtxt == NULL)
806 throw xml::LogicError (RT_SRC_POS);
807
808 /* instruct to create default attribute's values in the document */
809 if (aFlags & Read_AddDefaults)
810 xmlSchemaSetValidOptions (validCtxt, XML_SCHEMA_VAL_VC_I_CREATE);
811
812 /* set our error handlers */
813 xmlSchemaSetValidErrors (validCtxt, ValidityErrorCallback,
814 ValidityWarningCallback, &errorStr);
815
816 /* finally, validate */
817 valid = xmlSchemaValidateDoc (validCtxt, doc) == 0;
818 }
819
820 if (!valid)
821 {
822 /* look if there was a forwared exception from the lower level */
823 if (m->trappedErr.get() != NULL)
824 m->trappedErr->rethrow();
825
826 if (errorStr == NULL)
827 throw xml::LogicError (RT_SRC_POS);
828
829 throw xml::RuntimeError(errorStr);
830 /* errorStr is freed in catch(...) below */
831 }
832
833 RTStrFree (errorStr);
834
835 xmlSchemaFreeValidCtxt (validCtxt);
836 xmlSchemaFree (schema);
837 xmlSchemaFreeParserCtxt (schemaCtxt);
838 }
839 catch (...)
840 {
841 RTStrFree (errorStr);
842
843 if (validCtxt)
844 xmlSchemaFreeValidCtxt (validCtxt);
845 if (schema)
846 xmlSchemaFree (schema);
847 if (schemaCtxt)
848 xmlSchemaFreeParserCtxt (schemaCtxt);
849
850 RTStrFree (oldVersion);
851
852 throw;
853 }
854 }
855
856 /* reset the previous tree on success */
857 reset();
858
859 m->doc = doc;
860 /* assign the root key */
861 m->root = Key (new XmlKeyBackend (xmlDocGetRootElement (m->doc)));
862
863 /* memorize the old version string also used as a flag that
864 * the conversion has been performed (transfers ownership) */
865 m->oldVersion = oldVersion;
866
867 sThat = NULL;
868 }
869 catch (...)
870 {
871 if (doc != NULL)
872 xmlFreeDoc (doc);
873
874 sThat = NULL;
875
876 throw;
877 }
878}
879
880void XmlTreeBackend::rawWrite (xml::Output &aOutput)
881{
882 /* reset error variables used to memorize exceptions while inside the
883 * libxml2 code */
884 m->trappedErr.reset();
885
886 /* set up an input stream for parsing the document. This will be deleted
887 * when the stream is closed by the libxml2 API (e.g. when calling
888 * xmlFreeParserCtxt()). */
889 Data::OutputCtxt *outputCtxt =
890 new Data::OutputCtxt (&aOutput, m->trappedErr);
891
892 /* serialize to the stream */
893
894 xmlIndentTreeOutput = 1;
895 xmlTreeIndentString = " ";
896 xmlSaveNoEmptyTags = 0;
897
898 xmlSaveCtxtPtr saveCtxt = xmlSaveToIO (WriteCallback, CloseCallback,
899 outputCtxt, NULL,
900 XML_SAVE_FORMAT);
901 if (saveCtxt == NULL)
902 throw xml::LogicError (RT_SRC_POS);
903
904 long rc = xmlSaveDoc (saveCtxt, m->doc);
905 if (rc == -1)
906 {
907 /* look if there was a forwared exception from the lower level */
908 if (m->trappedErr.get() != NULL)
909 m->trappedErr->rethrow();
910
911 /* there must be an exception from the Output implementation,
912 * otherwise the save operation must always succeed. */
913 throw xml::LogicError (RT_SRC_POS);
914 }
915
916 xmlSaveClose (saveCtxt);
917}
918
919void XmlTreeBackend::reset()
920{
921 RTStrFree (m->oldVersion);
922 m->oldVersion = NULL;
923
924 if (m->doc)
925 {
926 /* reset the root key's node */
927 GetKeyBackend (m->root)->mNode = NULL;
928 /* free the document*/
929 xmlFreeDoc (m->doc);
930 m->doc = NULL;
931 }
932}
933
934Key &XmlTreeBackend::rootKey() const
935{
936 return m->root;
937}
938
939/* static */
940int XmlTreeBackend::ReadCallback (void *aCtxt, char *aBuf, int aLen)
941{
942 AssertReturn (aCtxt != NULL, 0);
943
944 Data::InputCtxt *ctxt = static_cast <Data::InputCtxt *> (aCtxt);
945
946 /* To prevent throwing exceptions while inside libxml2 code, we catch
947 * them and forward to our level using a couple of variables. */
948 try
949 {
950 return ctxt->input->read (aBuf, aLen);
951 }
952 catch (const xml::EIPRTFailure &err) { ctxt->setErr (err); }
953 catch (const xml::Error &err) { ctxt->setErr (err); }
954 catch (const std::exception &err) { ctxt->setErr (err); }
955 catch (...) { ctxt->setErr (xml::LogicError (RT_SRC_POS)); }
956
957 return -1 /* failure */;
958}
959
960/* static */
961int XmlTreeBackend::WriteCallback (void *aCtxt, const char *aBuf, int aLen)
962{
963 AssertReturn (aCtxt != NULL, 0);
964
965 Data::OutputCtxt *ctxt = static_cast <Data::OutputCtxt *> (aCtxt);
966
967 /* To prevent throwing exceptions while inside libxml2 code, we catch
968 * them and forward to our level using a couple of variables. */
969 try
970 {
971 return ctxt->output->write (aBuf, aLen);
972 }
973 catch (const xml::EIPRTFailure &err) { ctxt->setErr (err); }
974 catch (const xml::Error &err) { ctxt->setErr (err); }
975 catch (const std::exception &err) { ctxt->setErr (err); }
976 catch (...) { ctxt->setErr (xml::LogicError (RT_SRC_POS)); }
977
978 return -1 /* failure */;
979}
980
981/* static */
982int XmlTreeBackend::CloseCallback (void *aCtxt)
983{
984 AssertReturn (aCtxt != NULL, 0);
985
986 Data::IOCtxt *ctxt = static_cast <Data::IOCtxt *> (aCtxt);
987
988 /* To prevent throwing exceptions while inside libxml2 code, we catch
989 * them and forward to our level using a couple of variables. */
990 try
991 {
992 /// @todo there is no explicit close semantics in Stream yet
993#if 0
994 ctxt->stream->close();
995#endif
996
997 /* perform cleanup when necessary */
998 if (ctxt->deleteStreamOnClose)
999 delete ctxt->stream;
1000
1001 delete ctxt;
1002
1003 return 0 /* success */;
1004 }
1005 catch (const xml::EIPRTFailure &err) { ctxt->setErr (err); }
1006 catch (const xml::Error &err) { ctxt->setErr (err); }
1007 catch (const std::exception &err) { ctxt->setErr (err); }
1008 catch (...) { ctxt->setErr (xml::LogicError (RT_SRC_POS)); }
1009
1010 return -1 /* failure */;
1011}
1012
1013/* static */
1014void XmlTreeBackend::ValidityErrorCallback (void *aCtxt, const char *aMsg, ...)
1015{
1016 AssertReturnVoid (aCtxt != NULL);
1017 AssertReturnVoid (aMsg != NULL);
1018
1019 char * &str = *(char * *) aCtxt;
1020
1021 char *newMsg = NULL;
1022 {
1023 va_list args;
1024 va_start (args, aMsg);
1025 RTStrAPrintfV (&newMsg, aMsg, args);
1026 va_end (args);
1027 }
1028
1029 AssertReturnVoid (newMsg != NULL);
1030
1031 /* strip spaces, trailing EOLs and dot-like char */
1032 size_t newMsgLen = strlen (newMsg);
1033 while (newMsgLen && strchr (" \n.?!", newMsg [newMsgLen - 1]))
1034 -- newMsgLen;
1035
1036 /* anything left? */
1037 if (newMsgLen > 0)
1038 {
1039 if (str == NULL)
1040 {
1041 str = newMsg;
1042 newMsg [newMsgLen] = '\0';
1043 }
1044 else
1045 {
1046 /* append to the existing string */
1047 size_t strLen = strlen (str);
1048 char *newStr = (char *) RTMemRealloc (str, strLen + 2 + newMsgLen + 1);
1049 AssertReturnVoid (newStr != NULL);
1050
1051 memcpy (newStr + strLen, ".\n", 2);
1052 memcpy (newStr + strLen + 2, newMsg, newMsgLen);
1053 newStr [strLen + 2 + newMsgLen] = '\0';
1054 str = newStr;
1055 RTStrFree (newMsg);
1056 }
1057 }
1058}
1059
1060/* static */
1061void XmlTreeBackend::ValidityWarningCallback (void *aCtxt, const char *aMsg, ...)
1062{
1063 NOREF (aCtxt);
1064 NOREF (aMsg);
1065}
1066
1067/* static */
1068void XmlTreeBackend::StructuredErrorCallback (void *aCtxt, xmlErrorPtr aErr)
1069{
1070 AssertReturnVoid (aCtxt != NULL);
1071 AssertReturnVoid (aErr != NULL);
1072
1073 char * &str = *(char * *) aCtxt;
1074
1075 char *newMsg = xml::XmlError::Format (aErr);
1076 AssertReturnVoid (newMsg != NULL);
1077
1078 if (str == NULL)
1079 str = newMsg;
1080 else
1081 {
1082 /* append to the existing string */
1083 size_t newMsgLen = strlen (newMsg);
1084 size_t strLen = strlen (str);
1085 char *newStr = (char *) RTMemRealloc (str, strLen + newMsgLen + 2);
1086 AssertReturnVoid (newStr != NULL);
1087
1088 memcpy (newStr + strLen, ".\n", 2);
1089 memcpy (newStr + strLen + 2, newMsg, newMsgLen);
1090 str = newStr;
1091 RTStrFree (newMsg);
1092 }
1093}
1094
1095/* static */
1096XmlTreeBackend *XmlTreeBackend::sThat = NULL;
1097
1098/* static */
1099xmlParserInputPtr XmlTreeBackend::ExternalEntityLoader (const char *aURI,
1100 const char *aID,
1101 xmlParserCtxtPtr aCtxt)
1102{
1103 AssertReturn (sThat != NULL, NULL);
1104
1105 if (sThat->m->inputResolver == NULL)
1106 return xml::GlobalLock::callDefaultLoader(aURI, aID, aCtxt);
1107
1108 /* To prevent throwing exceptions while inside libxml2 code, we catch
1109 * them and forward to our level using a couple of variables. */
1110 try
1111 {
1112 xml::Input *input = sThat->m->inputResolver->resolveEntity (aURI, aID);
1113 if (input == NULL)
1114 return NULL;
1115
1116 Data::InputCtxt *ctxt = new Data::InputCtxt (input, sThat->m->trappedErr);
1117 ctxt->deleteStreamOnClose = true;
1118
1119 /* create an input buffer with custom hooks */
1120 xmlParserInputBufferPtr bufPtr =
1121 xmlParserInputBufferCreateIO (ReadCallback, CloseCallback,
1122 ctxt, XML_CHAR_ENCODING_NONE);
1123 if (bufPtr)
1124 {
1125 /* create an input stream */
1126 xmlParserInputPtr inputPtr =
1127 xmlNewIOInputStream (aCtxt, bufPtr, XML_CHAR_ENCODING_NONE);
1128
1129 if (inputPtr != NULL)
1130 {
1131 /* pass over the URI to the stream struct (it's NULL by
1132 * default) */
1133 inputPtr->filename =
1134 (char *) xmlCanonicPath ((const xmlChar *) input->uri());
1135 return inputPtr;
1136 }
1137 }
1138
1139 /* either of libxml calls failed */
1140
1141 if (bufPtr)
1142 xmlFreeParserInputBuffer (bufPtr);
1143
1144 delete input;
1145 delete ctxt;
1146
1147 throw xml::ENoMemory();
1148 }
1149 catch (const xml::EIPRTFailure &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1150 catch (const xml::Error &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1151 catch (const std::exception &err) { sThat->m->trappedErr.reset (stdx::new_exception_trap (err)); }
1152 catch (...) { sThat->m->trappedErr.reset (stdx::new_exception_trap (xml::LogicError (RT_SRC_POS))); }
1153
1154 return NULL;
1155}
1156
1157} /* namespace settings */
1158
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