VirtualBox

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

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

Main: do not include include/VBox/settings.h from other header files but only from implementations that need it (save compile time)

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