VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/xml.cpp@ 27653

Last change on this file since 27653 was 27418, checked in by vboxsync, 15 years ago

IPRT: much better error messages for XML runtime errors (improves greatly the 'inaccessible' message when a machine XML is missing)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.2 KB
Line 
1/** @file
2 * VirtualBox XML Manipulation API.
3 */
4
5/*
6 * Copyright (C) 2007-2009 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 * The contents of this file may alternatively be used under the terms
17 * of the Common Development and Distribution License Version 1.0
18 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
19 * VirtualBox OSE distribution, in which case the provisions of the
20 * CDDL are applicable instead of those of the GPL.
21 *
22 * You may elect to license modified versions of this file under the
23 * terms and conditions of either the GPL or the CDDL or both.
24 *
25 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
26 * Clara, CA 95054 USA or visit http://www.sun.com if you need
27 * additional information or have any questions.
28 */
29
30#include <iprt/cdefs.h>
31#include <iprt/err.h>
32#include <iprt/file.h>
33#include <iprt/cpp/lock.h>
34#include <iprt/cpp/xml.h>
35
36#include <libxml/tree.h>
37#include <libxml/parser.h>
38#include <libxml/globals.h>
39#include <libxml/xmlIO.h>
40#include <libxml/xmlsave.h>
41#include <libxml/uri.h>
42
43#include <libxml/xmlschemas.h>
44
45#include <map>
46#include <boost/shared_ptr.hpp>
47
48////////////////////////////////////////////////////////////////////////////////
49//
50// globals
51//
52////////////////////////////////////////////////////////////////////////////////
53
54/**
55 * Global module initialization structure. This is to wrap non-reentrant bits
56 * of libxml, among other things.
57 *
58 * The constructor and destructor of this structure are used to perform global
59 * module initiaizaton and cleanup. There must be only one global variable of
60 * this structure.
61 */
62static
63class Global
64{
65public:
66
67 Global()
68 {
69 /* Check the parser version. The docs say it will kill the app if
70 * there is a serious version mismatch, but I couldn't find it in the
71 * source code (it only prints the error/warning message to the console) so
72 * let's leave it as is for informational purposes. */
73 LIBXML_TEST_VERSION
74
75 /* Init libxml */
76 xmlInitParser();
77
78 /* Save the default entity resolver before someone has replaced it */
79 sxml.defaultEntityLoader = xmlGetExternalEntityLoader();
80 }
81
82 ~Global()
83 {
84 /* Shutdown libxml */
85 xmlCleanupParser();
86 }
87
88 struct
89 {
90 xmlExternalEntityLoader defaultEntityLoader;
91
92 /** Used to provide some thread safety missing in libxml2 (see e.g.
93 * XmlTreeBackend::read()) */
94 RTLockMtx lock;
95 }
96 sxml; /* XXX naming this xml will break with gcc-3.3 */
97}
98gGlobal;
99
100
101
102namespace xml
103{
104
105////////////////////////////////////////////////////////////////////////////////
106//
107// Exceptions
108//
109////////////////////////////////////////////////////////////////////////////////
110
111LogicError::LogicError(RT_SRC_POS_DECL)
112 : Error(NULL)
113{
114 char *msg = NULL;
115 RTStrAPrintf(&msg, "In '%s', '%s' at #%d",
116 pszFunction, pszFile, iLine);
117 setWhat(msg);
118 RTStrFree(msg);
119}
120
121XmlError::XmlError(xmlErrorPtr aErr)
122{
123 if (!aErr)
124 throw EInvalidArg(RT_SRC_POS);
125
126 char *msg = Format(aErr);
127 setWhat(msg);
128 RTStrFree(msg);
129}
130
131/**
132 * Composes a single message for the given error. The caller must free the
133 * returned string using RTStrFree() when no more necessary.
134 */
135// static
136char *XmlError::Format(xmlErrorPtr aErr)
137{
138 const char *msg = aErr->message ? aErr->message : "<none>";
139 size_t msgLen = strlen(msg);
140 /* strip spaces, trailing EOLs and dot-like char */
141 while (msgLen && strchr(" \n.?!", msg [msgLen - 1]))
142 --msgLen;
143
144 char *finalMsg = NULL;
145 RTStrAPrintf(&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d",
146 msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2);
147
148 return finalMsg;
149}
150
151EIPRTFailure::EIPRTFailure(int aRC, const char *pcszContext, ...)
152 : RuntimeError(NULL),
153 mRC(aRC)
154{
155 char *pszContext2;
156 va_list args;
157 va_start(args, pcszContext);
158 RTStrAPrintfV(&pszContext2, pcszContext, args);
159 char *newMsg;
160 RTStrAPrintf(&newMsg, "%s: %d (%s)", pszContext2, aRC, RTErrGetShort(aRC));
161 setWhat(newMsg);
162 RTStrFree(newMsg);
163 RTStrFree(pszContext2);
164}
165
166////////////////////////////////////////////////////////////////////////////////
167//
168// File Class
169//
170//////////////////////////////////////////////////////////////////////////////
171
172struct File::Data
173{
174 Data()
175 : handle(NIL_RTFILE), opened(false)
176 { }
177
178 iprt::MiniString strFileName;
179 RTFILE handle;
180 bool opened : 1;
181};
182
183File::File(Mode aMode, const char *aFileName)
184 : m(new Data())
185{
186 m->strFileName = aFileName;
187
188 uint32_t flags = 0;
189 switch (aMode)
190 {
191 /** @todo change to RTFILE_O_DENY_WRITE where appropriate. */
192 case Mode_Read:
193 flags = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
194 break;
195 case Mode_WriteCreate: // fail if file exists
196 flags = RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE;
197 break;
198 case Mode_Overwrite: // overwrite if file exists
199 flags = RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE;
200 break;
201 case Mode_ReadWrite:
202 flags = RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;;
203 }
204
205 int vrc = RTFileOpen(&m->handle, aFileName, flags);
206 if (RT_FAILURE(vrc))
207 throw EIPRTFailure(vrc, "Runtime error opening '%s' for reading", aFileName);
208
209 m->opened = true;
210}
211
212File::File(RTFILE aHandle, const char *aFileName /* = NULL */)
213 : m(new Data())
214{
215 if (aHandle == NIL_RTFILE)
216 throw EInvalidArg (RT_SRC_POS);
217
218 m->handle = aHandle;
219
220 if (aFileName)
221 m->strFileName = aFileName;
222
223 setPos (0);
224}
225
226File::~File()
227{
228 if (m->opened)
229 RTFileClose(m->handle);
230 delete m;
231}
232
233const char* File::uri() const
234{
235 return m->strFileName.c_str();
236}
237
238uint64_t File::pos() const
239{
240 uint64_t p = 0;
241 int vrc = RTFileSeek(m->handle, 0, RTFILE_SEEK_CURRENT, &p);
242 if (RT_SUCCESS(vrc))
243 return p;
244
245 throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
246}
247
248void File::setPos(uint64_t aPos)
249{
250 uint64_t p = 0;
251 unsigned method = RTFILE_SEEK_BEGIN;
252 int vrc = VINF_SUCCESS;
253
254 /* check if we overflow int64_t and move to INT64_MAX first */
255 if (((int64_t) aPos) < 0)
256 {
257 vrc = RTFileSeek(m->handle, INT64_MAX, method, &p);
258 aPos -= (uint64_t)INT64_MAX;
259 method = RTFILE_SEEK_CURRENT;
260 }
261 /* seek the rest */
262 if (RT_SUCCESS(vrc))
263 vrc = RTFileSeek(m->handle, (int64_t) aPos, method, &p);
264 if (RT_SUCCESS(vrc))
265 return;
266
267 throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
268}
269
270int File::read(char *aBuf, int aLen)
271{
272 size_t len = aLen;
273 int vrc = RTFileRead(m->handle, aBuf, len, &len);
274 if (RT_SUCCESS(vrc))
275 return (int)len;
276
277 throw EIPRTFailure(vrc, "Runtime error reading from file '%s'", m->strFileName.c_str());
278}
279
280int File::write(const char *aBuf, int aLen)
281{
282 size_t len = aLen;
283 int vrc = RTFileWrite (m->handle, aBuf, len, &len);
284 if (RT_SUCCESS (vrc))
285 return (int)len;
286
287 throw EIPRTFailure(vrc, "Runtime error writing to file '%s'", m->strFileName.c_str());
288
289 return -1 /* failure */;
290}
291
292void File::truncate()
293{
294 int vrc = RTFileSetSize (m->handle, pos());
295 if (RT_SUCCESS (vrc))
296 return;
297
298 throw EIPRTFailure(vrc, "Runtime error truncating file '%s'", m->strFileName.c_str());
299}
300
301////////////////////////////////////////////////////////////////////////////////
302//
303// MemoryBuf Class
304//
305//////////////////////////////////////////////////////////////////////////////
306
307struct MemoryBuf::Data
308{
309 Data()
310 : buf (NULL), len (0), uri (NULL), pos (0) {}
311
312 const char *buf;
313 size_t len;
314 char *uri;
315
316 size_t pos;
317};
318
319MemoryBuf::MemoryBuf (const char *aBuf, size_t aLen, const char *aURI /* = NULL */)
320 : m (new Data())
321{
322 if (aBuf == NULL)
323 throw EInvalidArg (RT_SRC_POS);
324
325 m->buf = aBuf;
326 m->len = aLen;
327 m->uri = RTStrDup (aURI);
328}
329
330MemoryBuf::~MemoryBuf()
331{
332 RTStrFree (m->uri);
333}
334
335const char *MemoryBuf::uri() const
336{
337 return m->uri;
338}
339
340uint64_t MemoryBuf::pos() const
341{
342 return m->pos;
343}
344
345void MemoryBuf::setPos (uint64_t aPos)
346{
347 size_t off = (size_t) aPos;
348 if ((uint64_t) off != aPos)
349 throw EInvalidArg();
350
351 if (off > m->len)
352 throw EInvalidArg();
353
354 m->pos = off;
355}
356
357int MemoryBuf::read (char *aBuf, int aLen)
358{
359 if (m->pos >= m->len)
360 return 0 /* nothing to read */;
361
362 size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos;
363 memcpy (aBuf, m->buf + m->pos, len);
364 m->pos += len;
365
366 return (int)len;
367}
368
369////////////////////////////////////////////////////////////////////////////////
370//
371// GlobalLock class
372//
373////////////////////////////////////////////////////////////////////////////////
374
375struct GlobalLock::Data
376{
377 PFNEXTERNALENTITYLOADER pOldLoader;
378 RTLock lock;
379
380 Data()
381 : pOldLoader(NULL),
382 lock(gGlobal.sxml.lock)
383 {
384 }
385};
386
387GlobalLock::GlobalLock()
388 : m(new Data())
389{
390}
391
392GlobalLock::~GlobalLock()
393{
394 if (m->pOldLoader)
395 xmlSetExternalEntityLoader(m->pOldLoader);
396 delete m;
397 m = NULL;
398}
399
400void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pLoader)
401{
402 m->pOldLoader = xmlGetExternalEntityLoader();
403 xmlSetExternalEntityLoader(pLoader);
404}
405
406// static
407xmlParserInput* GlobalLock::callDefaultLoader(const char *aURI,
408 const char *aID,
409 xmlParserCtxt *aCtxt)
410{
411 return gGlobal.sxml.defaultEntityLoader(aURI, aID, aCtxt);
412}
413
414////////////////////////////////////////////////////////////////////////////////
415//
416// Node class
417//
418////////////////////////////////////////////////////////////////////////////////
419
420struct Node::Data
421{
422 xmlNode *plibNode; // != NULL if this is an element or content node
423 xmlAttr *plibAttr; // != NULL if this is an attribute node
424
425 Node *pParent; // NULL only for the root element
426 const char *pcszName; // element or attribute name, points either into plibNode or plibAttr;
427 // NULL if this is a content node
428
429 struct compare_const_char
430 {
431 bool operator()(const char* s1, const char* s2) const
432 {
433 return strcmp(s1, s2) < 0;
434 }
435 };
436
437 // attributes, if this is an element; can be empty
438 typedef std::map<const char*, boost::shared_ptr<AttributeNode>, compare_const_char > AttributesMap;
439 AttributesMap attribs;
440
441 // child elements, if this is an element; can be empty
442 typedef std::list< boost::shared_ptr<Node> > InternalNodesList;
443 InternalNodesList children;
444};
445
446Node::Node(EnumType type)
447 : mType(type),
448 m(new Data)
449{
450 m->plibNode = NULL;
451 m->plibAttr = NULL;
452 m->pParent = NULL;
453}
454
455Node::~Node()
456{
457 delete m;
458}
459
460void Node::buildChildren() // private
461{
462 // go thru this element's attributes
463 xmlAttr *plibAttr = m->plibNode->properties;
464 while (plibAttr)
465 {
466 const char *pcszAttribName = (const char*)plibAttr->name;
467 boost::shared_ptr<AttributeNode> pNew(new AttributeNode);
468 pNew->m->plibAttr = plibAttr;
469 pNew->m->pcszName = (const char*)plibAttr->name;
470 pNew->m->pParent = this;
471 // store
472 m->attribs[pcszAttribName] = pNew;
473
474 plibAttr = plibAttr->next;
475 }
476
477 // go thru this element's child elements
478 xmlNodePtr plibNode = m->plibNode->children;
479 while (plibNode)
480 {
481 boost::shared_ptr<Node> pNew;
482
483 if (plibNode->type == XML_ELEMENT_NODE)
484 pNew = boost::shared_ptr<Node>(new ElementNode);
485 else if (plibNode->type == XML_TEXT_NODE)
486 pNew = boost::shared_ptr<Node>(new ContentNode);
487 if (pNew)
488 {
489 pNew->m->plibNode = plibNode;
490 pNew->m->pcszName = (const char*)plibNode->name;
491 pNew->m->pParent = this;
492 // store
493 m->children.push_back(pNew);
494
495 // recurse for this child element to get its own children
496 pNew->buildChildren();
497 }
498
499 plibNode = plibNode->next;
500 }
501}
502
503const char* Node::getName() const
504{
505 return m->pcszName;
506}
507
508bool Node::nameEquals(const char *pcsz) const
509{
510 if (m->pcszName == pcsz)
511 return true;
512 if (m->pcszName == NULL)
513 return false;
514 if (pcsz == NULL)
515 return false;
516 return !strcmp(m->pcszName, pcsz);
517}
518
519
520/**
521 * Returns the value of a node. If this node is an attribute, returns
522 * the attribute value; if this node is an element, then this returns
523 * the element text content.
524 * @return
525 */
526const char* Node::getValue() const
527{
528 if ( (m->plibAttr)
529 && (m->plibAttr->children)
530 )
531 // libxml hides attribute values in another node created as a
532 // single child of the attribute node, and it's in the content field
533 return (const char*)m->plibAttr->children->content;
534
535 if ( (m->plibNode)
536 && (m->plibNode->children)
537 )
538 return (const char*)m->plibNode->children->content;
539
540 return NULL;
541}
542
543/**
544 * Copies the value of a node into the given integer variable.
545 * Returns TRUE only if a value was found and was actually an
546 * integer of the given type.
547 * @return
548 */
549bool Node::copyValue(int32_t &i) const
550{
551 const char *pcsz;
552 if ( ((pcsz = getValue()))
553 && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i))
554 )
555 return true;
556
557 return false;
558}
559
560/**
561 * Copies the value of a node into the given integer variable.
562 * Returns TRUE only if a value was found and was actually an
563 * integer of the given type.
564 * @return
565 */
566bool Node::copyValue(uint32_t &i) const
567{
568 const char *pcsz;
569 if ( ((pcsz = getValue()))
570 && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i))
571 )
572 return true;
573
574 return false;
575}
576
577/**
578 * Copies the value of a node into the given integer variable.
579 * Returns TRUE only if a value was found and was actually an
580 * integer of the given type.
581 * @return
582 */
583bool Node::copyValue(int64_t &i) const
584{
585 const char *pcsz;
586 if ( ((pcsz = getValue()))
587 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i))
588 )
589 return true;
590
591 return false;
592}
593
594/**
595 * Copies the value of a node into the given integer variable.
596 * Returns TRUE only if a value was found and was actually an
597 * integer of the given type.
598 * @return
599 */
600bool Node::copyValue(uint64_t &i) const
601{
602 const char *pcsz;
603 if ( ((pcsz = getValue()))
604 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i))
605 )
606 return true;
607
608 return false;
609}
610
611/**
612 * Returns the line number of the current node in the source XML file.
613 * Useful for error messages.
614 * @return
615 */
616int Node::getLineNumber() const
617{
618 if (m->plibAttr)
619 return m->pParent->m->plibNode->line;
620
621 return m->plibNode->line;
622}
623
624ElementNode::ElementNode()
625 : Node(IsElement)
626{
627}
628
629/**
630 * Builds a list of direct child elements of the current element that
631 * match the given string; if pcszMatch is NULL, all direct child
632 * elements are returned.
633 * @param children out: list of nodes to which children will be appended.
634 * @param pcszMatch in: match string, or NULL to return all children.
635 * @return Number of items appended to the list (0 if none).
636 */
637int ElementNode::getChildElements(ElementNodesList &children,
638 const char *pcszMatch /*= NULL*/)
639 const
640{
641 int i = 0;
642 Data::InternalNodesList::const_iterator
643 it,
644 last = m->children.end();
645 for (it = m->children.begin();
646 it != last;
647 ++it)
648 {
649 // export this child node if ...
650 if ( (!pcszMatch) // the caller wants all nodes or
651 || (!strcmp(pcszMatch, (**it).getName())) // the element name matches
652 )
653 {
654 Node *pNode = (*it).get();
655 if (pNode->isElement())
656 children.push_back(static_cast<ElementNode*>(pNode));
657 ++i;
658 }
659 }
660 return i;
661}
662
663/**
664 * Returns the first child element whose name matches pcszMatch.
665 * @param pcszMatch
666 * @return
667 */
668const ElementNode* ElementNode::findChildElement(const char *pcszMatch)
669 const
670{
671 Data::InternalNodesList::const_iterator
672 it,
673 last = m->children.end();
674 for (it = m->children.begin();
675 it != last;
676 ++it)
677 {
678 if ((**it).isElement())
679 {
680 ElementNode *pelm = static_cast<ElementNode*>((*it).get());
681 if (!strcmp(pcszMatch, pelm->getName())) // the element name matches
682 return pelm;
683 }
684 }
685
686 return NULL;
687}
688
689/**
690 * Returns the first child element whose "id" attribute matches pcszId.
691 * @param pcszId identifier to look for.
692 * @return child element or NULL if not found.
693 */
694const ElementNode* ElementNode::findChildElementFromId(const char *pcszId) const
695{
696 Data::InternalNodesList::const_iterator
697 it,
698 last = m->children.end();
699 for (it = m->children.begin();
700 it != last;
701 ++it)
702 {
703 if ((**it).isElement())
704 {
705 ElementNode *pelm = static_cast<ElementNode*>((*it).get());
706 const AttributeNode *pAttr;
707 if ( ((pAttr = pelm->findAttribute("id")))
708 && (!strcmp(pAttr->getValue(), pcszId))
709 )
710 return pelm;
711 }
712 }
713
714 return NULL;
715}
716
717/**
718 *
719 * @param pcszMatch
720 * @return
721 */
722const AttributeNode* ElementNode::findAttribute(const char *pcszMatch) const
723{
724 Data::AttributesMap::const_iterator it;
725
726 it = m->attribs.find(pcszMatch);
727 if (it != m->attribs.end())
728 return it->second.get();
729
730 return NULL;
731}
732
733/**
734 * Convenience method which attempts to find the attribute with the given
735 * name and returns its value as a string.
736 *
737 * @param pcszMatch name of attribute to find.
738 * @param ppcsz out: attribute value
739 * @return TRUE if attribute was found and str was thus updated.
740 */
741bool ElementNode::getAttributeValue(const char *pcszMatch, const char *&ppcsz) const
742{
743 const Node* pAttr;
744 if ((pAttr = findAttribute(pcszMatch)))
745 {
746 ppcsz = pAttr->getValue();
747 return true;
748 }
749
750 return false;
751}
752
753/**
754 * Convenience method which attempts to find the attribute with the given
755 * name and returns its value as a string.
756 *
757 * @param pcszMatch name of attribute to find.
758 * @param str out: attribute value; overwritten only if attribute was found
759 * @return TRUE if attribute was found and str was thus updated.
760 */
761bool ElementNode::getAttributeValue(const char *pcszMatch, iprt::MiniString &str) const
762{
763 const Node* pAttr;
764 if ((pAttr = findAttribute(pcszMatch)))
765 {
766 str = pAttr->getValue();
767 return true;
768 }
769
770 return false;
771}
772
773/**
774 * Convenience method which attempts to find the attribute with the given
775 * name and returns its value as a signed integer. This calls
776 * RTStrToInt32Ex internally and will only output the integer if that
777 * function returns no error.
778 *
779 * @param pcszMatch name of attribute to find.
780 * @param i out: attribute value; overwritten only if attribute was found
781 * @return TRUE if attribute was found and str was thus updated.
782 */
783bool ElementNode::getAttributeValue(const char *pcszMatch, int32_t &i) const
784{
785 const char *pcsz;
786 if ( (getAttributeValue(pcszMatch, pcsz))
787 && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 0, &i))
788 )
789 return true;
790
791 return false;
792}
793
794/**
795 * Convenience method which attempts to find the attribute with the given
796 * name and returns its value as an unsigned integer.This calls
797 * RTStrToUInt32Ex internally and will only output the integer if that
798 * function returns no error.
799 *
800 * @param pcszMatch name of attribute to find.
801 * @param i out: attribute value; overwritten only if attribute was found
802 * @return TRUE if attribute was found and str was thus updated.
803 */
804bool ElementNode::getAttributeValue(const char *pcszMatch, uint32_t &i) const
805{
806 const char *pcsz;
807 if ( (getAttributeValue(pcszMatch, pcsz))
808 && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 0, &i))
809 )
810 return true;
811
812 return false;
813}
814
815/**
816 * Convenience method which attempts to find the attribute with the given
817 * name and returns its value as a signed long integer. This calls
818 * RTStrToInt64Ex internally and will only output the integer if that
819 * function returns no error.
820 *
821 * @param pcszMatch name of attribute to find.
822 * @param i out: attribute value
823 * @return TRUE if attribute was found and str was thus updated.
824 */
825bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t &i) const
826{
827 const char *pcsz;
828 if ( (getAttributeValue(pcszMatch, pcsz))
829 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 0, &i))
830 )
831 return true;
832
833 return false;
834}
835
836/**
837 * Convenience method which attempts to find the attribute with the given
838 * name and returns its value as an unsigned long integer.This calls
839 * RTStrToUInt64Ex internally and will only output the integer if that
840 * function returns no error.
841 *
842 * @param pcszMatch name of attribute to find.
843 * @param i out: attribute value; overwritten only if attribute was found
844 * @return TRUE if attribute was found and str was thus updated.
845 */
846bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t &i) const
847{
848 const char *pcsz;
849 if ( (getAttributeValue(pcszMatch, pcsz))
850 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 0, &i))
851 )
852 return true;
853
854 return false;
855}
856
857/**
858 * Convenience method which attempts to find the attribute with the given
859 * name and returns its value as a boolean. This accepts "true", "false",
860 * "yes", "no", "1" or "0" as valid values.
861 *
862 * @param pcszMatch name of attribute to find.
863 * @param f out: attribute value; overwritten only if attribute was found
864 * @return TRUE if attribute was found and str was thus updated.
865 */
866bool ElementNode::getAttributeValue(const char *pcszMatch, bool &f) const
867{
868 const char *pcsz;
869 if (getAttributeValue(pcszMatch, pcsz))
870 {
871 if ( (!strcmp(pcsz, "true"))
872 || (!strcmp(pcsz, "yes"))
873 || (!strcmp(pcsz, "1"))
874 )
875 {
876 f = true;
877 return true;
878 }
879 if ( (!strcmp(pcsz, "false"))
880 || (!strcmp(pcsz, "no"))
881 || (!strcmp(pcsz, "0"))
882 )
883 {
884 f = false;
885 return true;
886 }
887 }
888
889 return false;
890}
891
892/**
893 * Creates a new child element node and appends it to the list
894 * of children in "this".
895 *
896 * @param pcszElementName
897 * @return
898 */
899ElementNode* ElementNode::createChild(const char *pcszElementName)
900{
901 // we must be an element, not an attribute
902 if (!m->plibNode)
903 throw ENodeIsNotElement(RT_SRC_POS);
904
905 // libxml side: create new node
906 xmlNode *plibNode;
907 if (!(plibNode = xmlNewNode(NULL, // namespace
908 (const xmlChar*)pcszElementName)))
909 throw std::bad_alloc();
910 xmlAddChild(m->plibNode, plibNode);
911
912 // now wrap this in C++
913 ElementNode *p = new ElementNode;
914 boost::shared_ptr<ElementNode> pNew(p);
915 pNew->m->plibNode = plibNode;
916 pNew->m->pcszName = (const char*)plibNode->name;
917
918 m->children.push_back(pNew);
919
920 return p;
921}
922
923
924/**
925 * Creates a content node and appends it to the list of children
926 * in "this".
927 *
928 * @param pcszContent
929 * @return
930 */
931ContentNode* ElementNode::addContent(const char *pcszContent)
932{
933 // libxml side: create new node
934 xmlNode *plibNode;
935 if (!(plibNode = xmlNewText((const xmlChar*)pcszContent)))
936 throw std::bad_alloc();
937 xmlAddChild(m->plibNode, plibNode);
938
939 // now wrap this in C++
940 ContentNode *p = new ContentNode;
941 boost::shared_ptr<ContentNode> pNew(p);
942 pNew->m->plibNode = plibNode;
943 pNew->m->pcszName = NULL;
944
945 m->children.push_back(pNew);
946
947 return p;
948}
949
950/**
951 * Sets the given attribute.
952 *
953 * If an attribute with the given name exists, it is overwritten,
954 * otherwise a new attribute is created. Returns the attribute node
955 * that was either created or changed.
956 *
957 * @param pcszName
958 * @param pcszValue
959 * @return
960 */
961AttributeNode* ElementNode::setAttribute(const char *pcszName, const char *pcszValue)
962{
963 Data::AttributesMap::const_iterator it;
964
965 it = m->attribs.find(pcszName);
966 if (it == m->attribs.end())
967 {
968 // libxml side: xmlNewProp creates an attribute
969 xmlAttr *plibAttr = xmlNewProp(m->plibNode, (xmlChar*)pcszName, (xmlChar*)pcszValue);
970 const char *pcszAttribName = (const char*)plibAttr->name;
971
972 // C++ side: create an attribute node around it
973 boost::shared_ptr<AttributeNode> pNew(new AttributeNode);
974 pNew->m->plibAttr = plibAttr;
975 pNew->m->pcszName = (const char*)plibAttr->name;
976 pNew->m->pParent = this;
977 // store
978 m->attribs[pcszAttribName] = pNew;
979 }
980 else
981 {
982 // @todo
983 throw LogicError("Attribute exists");
984 }
985
986 return NULL;
987
988}
989
990AttributeNode* ElementNode::setAttribute(const char *pcszName, int32_t i)
991{
992 char *psz = NULL;
993 RTStrAPrintf(&psz, "%RI32", i);
994 AttributeNode *p = setAttribute(pcszName, psz);
995 RTStrFree(psz);
996 return p;
997}
998
999AttributeNode* ElementNode::setAttribute(const char *pcszName, uint32_t i)
1000{
1001 char *psz = NULL;
1002 RTStrAPrintf(&psz, "%RU32", i);
1003 AttributeNode *p = setAttribute(pcszName, psz);
1004 RTStrFree(psz);
1005 return p;
1006}
1007
1008AttributeNode* ElementNode::setAttribute(const char *pcszName, int64_t i)
1009{
1010 char *psz = NULL;
1011 RTStrAPrintf(&psz, "%RI64", i);
1012 AttributeNode *p = setAttribute(pcszName, psz);
1013 RTStrFree(psz);
1014 return p;
1015}
1016
1017AttributeNode* ElementNode::setAttribute(const char *pcszName, uint64_t i)
1018{
1019 char *psz = NULL;
1020 RTStrAPrintf(&psz, "%RU64", i);
1021 AttributeNode *p = setAttribute(pcszName, psz);
1022 RTStrFree(psz);
1023 return p;
1024}
1025
1026AttributeNode* ElementNode::setAttributeHex(const char *pcszName, uint32_t i)
1027{
1028 char *psz = NULL;
1029 RTStrAPrintf(&psz, "0x%RX32", i);
1030 AttributeNode *p = setAttribute(pcszName, psz);
1031 RTStrFree(psz);
1032 return p;
1033}
1034
1035AttributeNode* ElementNode::setAttribute(const char *pcszName, bool f)
1036{
1037 return setAttribute(pcszName, (f) ? "true" : "false");
1038}
1039
1040AttributeNode::AttributeNode()
1041 : Node(IsAttribute)
1042{
1043}
1044
1045ContentNode::ContentNode()
1046 : Node(IsContent)
1047{
1048}
1049
1050/*
1051 * NodesLoop
1052 *
1053 */
1054
1055struct NodesLoop::Data
1056{
1057 ElementNodesList listElements;
1058 ElementNodesList::const_iterator it;
1059};
1060
1061NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */)
1062{
1063 m = new Data;
1064 node.getChildElements(m->listElements, pcszMatch);
1065 m->it = m->listElements.begin();
1066}
1067
1068NodesLoop::~NodesLoop()
1069{
1070 delete m;
1071}
1072
1073
1074/**
1075 * Handy convenience helper for looping over all child elements. Create an
1076 * instance of NodesLoop on the stack and call this method until it returns
1077 * NULL, like this:
1078 * <code>
1079 * xml::ElementNode node; // should point to an element
1080 * xml::NodesLoop loop(node, "child"); // find all "child" elements under node
1081 * const xml::ElementNode *pChild = NULL;
1082 * while (pChild = loop.forAllNodes())
1083 * ...;
1084 * </code>
1085 * @return
1086 */
1087const ElementNode* NodesLoop::forAllNodes() const
1088{
1089 const ElementNode *pNode = NULL;
1090
1091 if (m->it != m->listElements.end())
1092 {
1093 pNode = *(m->it);
1094 ++(m->it);
1095 }
1096
1097 return pNode;
1098}
1099
1100////////////////////////////////////////////////////////////////////////////////
1101//
1102// Document class
1103//
1104////////////////////////////////////////////////////////////////////////////////
1105
1106struct Document::Data
1107{
1108 xmlDocPtr plibDocument;
1109 ElementNode *pRootElement;
1110
1111 Data()
1112 {
1113 plibDocument = NULL;
1114 pRootElement = NULL;
1115 }
1116
1117 ~Data()
1118 {
1119 reset();
1120 }
1121
1122 void reset()
1123 {
1124 if (plibDocument)
1125 {
1126 xmlFreeDoc(plibDocument);
1127 plibDocument = NULL;
1128 }
1129 if (pRootElement)
1130 {
1131 delete pRootElement;
1132 pRootElement = NULL;
1133 }
1134 }
1135
1136 void copyFrom(const Document::Data *p)
1137 {
1138 if (p->plibDocument)
1139 {
1140 plibDocument = xmlCopyDoc(p->plibDocument,
1141 1); // recursive == copy all
1142 }
1143 }
1144};
1145
1146Document::Document()
1147 : m(new Data)
1148{
1149}
1150
1151Document::Document(const Document &x)
1152 : m(new Data)
1153{
1154 m->copyFrom(x.m);
1155}
1156
1157Document& Document::operator=(const Document &x)
1158{
1159 m->reset();
1160 m->copyFrom(x.m);
1161 return *this;
1162}
1163
1164Document::~Document()
1165{
1166 delete m;
1167}
1168
1169/**
1170 * private method to refresh all internal structures after the internal pDocument
1171 * has changed. Called from XmlFileParser::read(). m->reset() must have been
1172 * called before to make sure all members except the internal pDocument are clean.
1173 */
1174void Document::refreshInternals() // private
1175{
1176 m->pRootElement = new ElementNode();
1177 m->pRootElement->m->plibNode = xmlDocGetRootElement(m->plibDocument);
1178 m->pRootElement->m->pcszName = (const char*)m->pRootElement->m->plibNode->name;
1179
1180 m->pRootElement->buildChildren();
1181}
1182
1183/**
1184 * Returns the root element of the document, or NULL if the document is empty.
1185 * Const variant.
1186 * @return
1187 */
1188const ElementNode* Document::getRootElement() const
1189{
1190 return m->pRootElement;
1191}
1192
1193/**
1194 * Returns the root element of the document, or NULL if the document is empty.
1195 * Non-const variant.
1196 * @return
1197 */
1198ElementNode* Document::getRootElement()
1199{
1200 return m->pRootElement;
1201}
1202
1203/**
1204 * Creates a new element node and sets it as the root element. This will
1205 * only work if the document is empty; otherwise EDocumentNotEmpty is thrown.
1206 */
1207ElementNode* Document::createRootElement(const char *pcszRootElementName)
1208{
1209 if (m->pRootElement || m->plibDocument)
1210 throw EDocumentNotEmpty(RT_SRC_POS);
1211
1212 // libxml side: create document, create root node
1213 m->plibDocument = xmlNewDoc((const xmlChar*)"1.0");
1214 xmlNode *plibRootNode;
1215 if (!(plibRootNode = xmlNewNode(NULL, // namespace
1216 (const xmlChar*)pcszRootElementName)))
1217 throw std::bad_alloc();
1218 xmlDocSetRootElement(m->plibDocument, plibRootNode);
1219
1220 // now wrap this in C++
1221 m->pRootElement = new ElementNode();
1222 m->pRootElement->m->plibNode = plibRootNode;
1223 m->pRootElement->m->pcszName = (const char*)plibRootNode->name;
1224
1225 return m->pRootElement;
1226}
1227
1228////////////////////////////////////////////////////////////////////////////////
1229//
1230// XmlParserBase class
1231//
1232////////////////////////////////////////////////////////////////////////////////
1233
1234XmlParserBase::XmlParserBase()
1235{
1236 m_ctxt = xmlNewParserCtxt();
1237 if (m_ctxt == NULL)
1238 throw std::bad_alloc();
1239}
1240
1241XmlParserBase::~XmlParserBase()
1242{
1243 xmlFreeParserCtxt (m_ctxt);
1244 m_ctxt = NULL;
1245}
1246
1247////////////////////////////////////////////////////////////////////////////////
1248//
1249// XmlFileParser class
1250//
1251////////////////////////////////////////////////////////////////////////////////
1252
1253struct XmlFileParser::Data
1254{
1255 xmlParserCtxtPtr ctxt;
1256 iprt::MiniString strXmlFilename;
1257
1258 Data()
1259 {
1260 if (!(ctxt = xmlNewParserCtxt()))
1261 throw std::bad_alloc();
1262 }
1263
1264 ~Data()
1265 {
1266 xmlFreeParserCtxt(ctxt);
1267 ctxt = NULL;
1268 }
1269};
1270
1271XmlFileParser::XmlFileParser()
1272 : XmlParserBase(),
1273 m(new Data())
1274{
1275}
1276
1277XmlFileParser::~XmlFileParser()
1278{
1279 delete m;
1280 m = NULL;
1281}
1282
1283struct IOContext
1284{
1285 File file;
1286 iprt::MiniString error;
1287
1288 IOContext(const char *pcszFilename, File::Mode mode)
1289 : file(mode, pcszFilename)
1290 {
1291 }
1292
1293 void setError(const xml::Error &x)
1294 {
1295 error = x.what();
1296 }
1297
1298 void setError(const std::exception &x)
1299 {
1300 error = x.what();
1301 }
1302};
1303
1304struct ReadContext : IOContext
1305{
1306 ReadContext(const char *pcszFilename)
1307 : IOContext(pcszFilename, File::Mode_Read)
1308 {
1309 }
1310};
1311
1312struct WriteContext : IOContext
1313{
1314 WriteContext(const char *pcszFilename)
1315 : IOContext(pcszFilename, File::Mode_Overwrite)
1316 {
1317 }
1318};
1319
1320/**
1321 * Reads the given file and fills the given Document object with its contents.
1322 * Throws XmlError on parsing errors.
1323 *
1324 * The document that is passed in will be reset before being filled if not empty.
1325 *
1326 * @param strFilename in: name fo file to parse.
1327 * @param doc out: document to be reset and filled with data according to file contents.
1328 */
1329void XmlFileParser::read(const iprt::MiniString &strFilename,
1330 Document &doc)
1331{
1332 GlobalLock lock;
1333// global.setExternalEntityLoader(ExternalEntityLoader);
1334
1335 m->strXmlFilename = strFilename;
1336 const char *pcszFilename = strFilename.c_str();
1337
1338 ReadContext context(pcszFilename);
1339 doc.m->reset();
1340 if (!(doc.m->plibDocument = xmlCtxtReadIO(m->ctxt,
1341 ReadCallback,
1342 CloseCallback,
1343 &context,
1344 pcszFilename,
1345 NULL, // encoding = auto
1346 XML_PARSE_NOBLANKS)))
1347 throw XmlError(xmlCtxtGetLastError(m->ctxt));
1348
1349 doc.refreshInternals();
1350}
1351
1352// static
1353int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen)
1354{
1355 ReadContext *pContext = static_cast<ReadContext*>(aCtxt);
1356
1357 /* To prevent throwing exceptions while inside libxml2 code, we catch
1358 * them and forward to our level using a couple of variables. */
1359
1360 try
1361 {
1362 return pContext->file.read(aBuf, aLen);
1363 }
1364 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
1365 catch (const xml::Error &err) { pContext->setError(err); }
1366 catch (const std::exception &err) { pContext->setError(err); }
1367 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
1368
1369 return -1 /* failure */;
1370}
1371
1372int XmlFileParser::CloseCallback(void *aCtxt)
1373{
1374 /// @todo to be written
1375
1376 return -1;
1377}
1378
1379////////////////////////////////////////////////////////////////////////////////
1380//
1381// XmlFileWriter class
1382//
1383////////////////////////////////////////////////////////////////////////////////
1384
1385struct XmlFileWriter::Data
1386{
1387 Document *pDoc;
1388};
1389
1390XmlFileWriter::XmlFileWriter(Document &doc)
1391{
1392 m = new Data();
1393 m->pDoc = &doc;
1394}
1395
1396XmlFileWriter::~XmlFileWriter()
1397{
1398 delete m;
1399}
1400
1401void XmlFileWriter::write(const char *pcszFilename)
1402{
1403 WriteContext context(pcszFilename);
1404
1405 GlobalLock lock;
1406
1407 /* serialize to the stream */
1408 xmlIndentTreeOutput = 1;
1409 xmlTreeIndentString = " ";
1410 xmlSaveNoEmptyTags = 0;
1411
1412 xmlSaveCtxtPtr saveCtxt;
1413 if (!(saveCtxt = xmlSaveToIO(WriteCallback,
1414 CloseCallback,
1415 &context,
1416 NULL,
1417 XML_SAVE_FORMAT)))
1418 throw xml::LogicError(RT_SRC_POS);
1419
1420 long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument);
1421 if (rc == -1)
1422 {
1423 /* look if there was a forwared exception from the lower level */
1424// if (m->trappedErr.get() != NULL)
1425// m->trappedErr->rethrow();
1426
1427 /* there must be an exception from the Output implementation,
1428 * otherwise the save operation must always succeed. */
1429 throw xml::LogicError(RT_SRC_POS);
1430 }
1431
1432 xmlSaveClose(saveCtxt);
1433}
1434
1435int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen)
1436{
1437 WriteContext *pContext = static_cast<WriteContext*>(aCtxt);
1438
1439 /* To prevent throwing exceptions while inside libxml2 code, we catch
1440 * them and forward to our level using a couple of variables. */
1441 try
1442 {
1443 return pContext->file.write(aBuf, aLen);
1444 }
1445 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
1446 catch (const xml::Error &err) { pContext->setError(err); }
1447 catch (const std::exception &err) { pContext->setError(err); }
1448 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
1449
1450 return -1 /* failure */;
1451}
1452
1453int XmlFileWriter::CloseCallback(void *aCtxt)
1454{
1455 /// @todo to be written
1456
1457 return -1;
1458}
1459
1460
1461} // end namespace xml
1462
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