VirtualBox

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

Last change on this file since 27994 was 27918, checked in by vboxsync, 15 years ago

Main/OVF: import vbox:Machine XML within OVF, first batch (XML is parsed and machine config created, but COM Machine can't handle it yet)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.9 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
508/**
509 * Variant of nameEquals that checks the namespace as well.
510 * @param pcszNamespace
511 * @param pcsz
512 * @return
513 */
514bool Node::nameEquals(const char *pcszNamespace, const char *pcsz) const
515{
516 if (m->pcszName == pcsz)
517 return true;
518 if (m->pcszName == NULL)
519 return false;
520 if (pcsz == NULL)
521 return false;
522 if (strcmp(m->pcszName, pcsz))
523 return false;
524
525 // name matches: then check namespaces as well
526 if (!pcszNamespace)
527 return true;
528 // caller wants namespace:
529 if ( !m->plibNode->ns
530 || !m->plibNode->ns->prefix
531 || !*m->plibNode->ns->prefix
532 )
533 // but node has no namespace:
534 return false;
535 return !strcmp((const char*)m->plibNode->ns->prefix, pcszNamespace);
536}
537
538/**
539 * Returns the value of a node. If this node is an attribute, returns
540 * the attribute value; if this node is an element, then this returns
541 * the element text content.
542 * @return
543 */
544const char* Node::getValue() const
545{
546 if ( (m->plibAttr)
547 && (m->plibAttr->children)
548 )
549 // libxml hides attribute values in another node created as a
550 // single child of the attribute node, and it's in the content field
551 return (const char*)m->plibAttr->children->content;
552
553 if ( (m->plibNode)
554 && (m->plibNode->children)
555 )
556 return (const char*)m->plibNode->children->content;
557
558 return NULL;
559}
560
561/**
562 * Copies the value of a node into the given integer variable.
563 * Returns TRUE only if a value was found and was actually an
564 * integer of the given type.
565 * @return
566 */
567bool Node::copyValue(int32_t &i) const
568{
569 const char *pcsz;
570 if ( ((pcsz = getValue()))
571 && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i))
572 )
573 return true;
574
575 return false;
576}
577
578/**
579 * Copies the value of a node into the given integer variable.
580 * Returns TRUE only if a value was found and was actually an
581 * integer of the given type.
582 * @return
583 */
584bool Node::copyValue(uint32_t &i) const
585{
586 const char *pcsz;
587 if ( ((pcsz = getValue()))
588 && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i))
589 )
590 return true;
591
592 return false;
593}
594
595/**
596 * Copies the value of a node into the given integer variable.
597 * Returns TRUE only if a value was found and was actually an
598 * integer of the given type.
599 * @return
600 */
601bool Node::copyValue(int64_t &i) const
602{
603 const char *pcsz;
604 if ( ((pcsz = getValue()))
605 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i))
606 )
607 return true;
608
609 return false;
610}
611
612/**
613 * Copies the value of a node into the given integer variable.
614 * Returns TRUE only if a value was found and was actually an
615 * integer of the given type.
616 * @return
617 */
618bool Node::copyValue(uint64_t &i) const
619{
620 const char *pcsz;
621 if ( ((pcsz = getValue()))
622 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i))
623 )
624 return true;
625
626 return false;
627}
628
629/**
630 * Returns the line number of the current node in the source XML file.
631 * Useful for error messages.
632 * @return
633 */
634int Node::getLineNumber() const
635{
636 if (m->plibAttr)
637 return m->pParent->m->plibNode->line;
638
639 return m->plibNode->line;
640}
641
642ElementNode::ElementNode()
643 : Node(IsElement)
644{
645}
646
647/**
648 * Builds a list of direct child elements of the current element that
649 * match the given string; if pcszMatch is NULL, all direct child
650 * elements are returned.
651 * @param children out: list of nodes to which children will be appended.
652 * @param pcszMatch in: match string, or NULL to return all children.
653 * @return Number of items appended to the list (0 if none).
654 */
655int ElementNode::getChildElements(ElementNodesList &children,
656 const char *pcszMatch /*= NULL*/)
657 const
658{
659 int i = 0;
660 Data::InternalNodesList::const_iterator
661 it,
662 last = m->children.end();
663 for (it = m->children.begin();
664 it != last;
665 ++it)
666 {
667 // export this child node if ...
668 if ( (!pcszMatch) // the caller wants all nodes or
669 || (!strcmp(pcszMatch, (**it).getName())) // the element name matches
670 )
671 {
672 Node *pNode = (*it).get();
673 if (pNode->isElement())
674 children.push_back(static_cast<ElementNode*>(pNode));
675 ++i;
676 }
677 }
678 return i;
679}
680
681/**
682 * Returns the first child element whose name matches pcszMatch.
683 *
684 * @param pcszNamespace Namespace prefix (e.g. "vbox") or NULL to match any namespace.
685 * @param pcszMatch Element name to match.
686 * @return
687 */
688const ElementNode* ElementNode::findChildElement(const char *pcszNamespace,
689 const char *pcszMatch)
690 const
691{
692 Data::InternalNodesList::const_iterator
693 it,
694 last = m->children.end();
695 for (it = m->children.begin();
696 it != last;
697 ++it)
698 {
699 if ((**it).isElement())
700 {
701 ElementNode *pelm = static_cast<ElementNode*>((*it).get());
702 if (pelm->nameEquals(pcszNamespace, pcszMatch))
703 return pelm;
704 }
705 }
706
707 return NULL;
708}
709
710/**
711 * Returns the first child element whose "id" attribute matches pcszId.
712 * @param pcszId identifier to look for.
713 * @return child element or NULL if not found.
714 */
715const ElementNode* ElementNode::findChildElementFromId(const char *pcszId) const
716{
717 Data::InternalNodesList::const_iterator
718 it,
719 last = m->children.end();
720 for (it = m->children.begin();
721 it != last;
722 ++it)
723 {
724 if ((**it).isElement())
725 {
726 ElementNode *pelm = static_cast<ElementNode*>((*it).get());
727 const AttributeNode *pAttr;
728 if ( ((pAttr = pelm->findAttribute("id")))
729 && (!strcmp(pAttr->getValue(), pcszId))
730 )
731 return pelm;
732 }
733 }
734
735 return NULL;
736}
737
738/**
739 *
740 * @param pcszMatch
741 * @return
742 */
743const AttributeNode* ElementNode::findAttribute(const char *pcszMatch) const
744{
745 Data::AttributesMap::const_iterator it;
746
747 it = m->attribs.find(pcszMatch);
748 if (it != m->attribs.end())
749 return it->second.get();
750
751 return NULL;
752}
753
754/**
755 * Convenience method which attempts to find the attribute with the given
756 * name and returns its value as a string.
757 *
758 * @param pcszMatch name of attribute to find.
759 * @param ppcsz out: attribute value
760 * @return TRUE if attribute was found and str was thus updated.
761 */
762bool ElementNode::getAttributeValue(const char *pcszMatch, const char *&ppcsz) const
763{
764 const Node* pAttr;
765 if ((pAttr = findAttribute(pcszMatch)))
766 {
767 ppcsz = pAttr->getValue();
768 return true;
769 }
770
771 return false;
772}
773
774/**
775 * Convenience method which attempts to find the attribute with the given
776 * name and returns its value as a string.
777 *
778 * @param pcszMatch name of attribute to find.
779 * @param str out: attribute value; overwritten only if attribute was found
780 * @return TRUE if attribute was found and str was thus updated.
781 */
782bool ElementNode::getAttributeValue(const char *pcszMatch, iprt::MiniString &str) const
783{
784 const Node* pAttr;
785 if ((pAttr = findAttribute(pcszMatch)))
786 {
787 str = pAttr->getValue();
788 return true;
789 }
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 a signed integer. This calls
797 * RTStrToInt32Ex 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, int32_t &i) const
805{
806 const char *pcsz;
807 if ( (getAttributeValue(pcszMatch, pcsz))
808 && (VINF_SUCCESS == RTStrToInt32Ex(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 an unsigned integer.This calls
818 * RTStrToUInt32Ex 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; overwritten only if attribute was found
823 * @return TRUE if attribute was found and str was thus updated.
824 */
825bool ElementNode::getAttributeValue(const char *pcszMatch, uint32_t &i) const
826{
827 const char *pcsz;
828 if ( (getAttributeValue(pcszMatch, pcsz))
829 && (VINF_SUCCESS == RTStrToUInt32Ex(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 a signed long integer. This calls
839 * RTStrToInt64Ex 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
844 * @return TRUE if attribute was found and str was thus updated.
845 */
846bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t &i) const
847{
848 const char *pcsz;
849 if ( (getAttributeValue(pcszMatch, pcsz))
850 && (VINF_SUCCESS == RTStrToInt64Ex(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 an unsigned long integer.This calls
860 * RTStrToUInt64Ex internally and will only output the integer if that
861 * function returns no error.
862 *
863 * @param pcszMatch name of attribute to find.
864 * @param i out: attribute value; overwritten only if attribute was found
865 * @return TRUE if attribute was found and str was thus updated.
866 */
867bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t &i) const
868{
869 const char *pcsz;
870 if ( (getAttributeValue(pcszMatch, pcsz))
871 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 0, &i))
872 )
873 return true;
874
875 return false;
876}
877
878/**
879 * Convenience method which attempts to find the attribute with the given
880 * name and returns its value as a boolean. This accepts "true", "false",
881 * "yes", "no", "1" or "0" as valid values.
882 *
883 * @param pcszMatch name of attribute to find.
884 * @param f out: attribute value; overwritten only if attribute was found
885 * @return TRUE if attribute was found and str was thus updated.
886 */
887bool ElementNode::getAttributeValue(const char *pcszMatch, bool &f) const
888{
889 const char *pcsz;
890 if (getAttributeValue(pcszMatch, pcsz))
891 {
892 if ( (!strcmp(pcsz, "true"))
893 || (!strcmp(pcsz, "yes"))
894 || (!strcmp(pcsz, "1"))
895 )
896 {
897 f = true;
898 return true;
899 }
900 if ( (!strcmp(pcsz, "false"))
901 || (!strcmp(pcsz, "no"))
902 || (!strcmp(pcsz, "0"))
903 )
904 {
905 f = false;
906 return true;
907 }
908 }
909
910 return false;
911}
912
913/**
914 * Creates a new child element node and appends it to the list
915 * of children in "this".
916 *
917 * @param pcszElementName
918 * @return
919 */
920ElementNode* ElementNode::createChild(const char *pcszElementName)
921{
922 // we must be an element, not an attribute
923 if (!m->plibNode)
924 throw ENodeIsNotElement(RT_SRC_POS);
925
926 // libxml side: create new node
927 xmlNode *plibNode;
928 if (!(plibNode = xmlNewNode(NULL, // namespace
929 (const xmlChar*)pcszElementName)))
930 throw std::bad_alloc();
931 xmlAddChild(m->plibNode, plibNode);
932
933 // now wrap this in C++
934 ElementNode *p = new ElementNode;
935 boost::shared_ptr<ElementNode> pNew(p);
936 pNew->m->plibNode = plibNode;
937 pNew->m->pcszName = (const char*)plibNode->name;
938
939 m->children.push_back(pNew);
940
941 return p;
942}
943
944
945/**
946 * Creates a content node and appends it to the list of children
947 * in "this".
948 *
949 * @param pcszContent
950 * @return
951 */
952ContentNode* ElementNode::addContent(const char *pcszContent)
953{
954 // libxml side: create new node
955 xmlNode *plibNode;
956 if (!(plibNode = xmlNewText((const xmlChar*)pcszContent)))
957 throw std::bad_alloc();
958 xmlAddChild(m->plibNode, plibNode);
959
960 // now wrap this in C++
961 ContentNode *p = new ContentNode;
962 boost::shared_ptr<ContentNode> pNew(p);
963 pNew->m->plibNode = plibNode;
964 pNew->m->pcszName = NULL;
965
966 m->children.push_back(pNew);
967
968 return p;
969}
970
971/**
972 * Sets the given attribute.
973 *
974 * If an attribute with the given name exists, it is overwritten,
975 * otherwise a new attribute is created. Returns the attribute node
976 * that was either created or changed.
977 *
978 * @param pcszName
979 * @param pcszValue
980 * @return
981 */
982AttributeNode* ElementNode::setAttribute(const char *pcszName, const char *pcszValue)
983{
984 Data::AttributesMap::const_iterator it;
985
986 it = m->attribs.find(pcszName);
987 if (it == m->attribs.end())
988 {
989 // libxml side: xmlNewProp creates an attribute
990 xmlAttr *plibAttr = xmlNewProp(m->plibNode, (xmlChar*)pcszName, (xmlChar*)pcszValue);
991 const char *pcszAttribName = (const char*)plibAttr->name;
992
993 // C++ side: create an attribute node around it
994 boost::shared_ptr<AttributeNode> pNew(new AttributeNode);
995 pNew->m->plibAttr = plibAttr;
996 pNew->m->pcszName = (const char*)plibAttr->name;
997 pNew->m->pParent = this;
998 // store
999 m->attribs[pcszAttribName] = pNew;
1000 }
1001 else
1002 {
1003 // @todo
1004 throw LogicError("Attribute exists");
1005 }
1006
1007 return NULL;
1008
1009}
1010
1011AttributeNode* ElementNode::setAttribute(const char *pcszName, int32_t i)
1012{
1013 char *psz = NULL;
1014 RTStrAPrintf(&psz, "%RI32", i);
1015 AttributeNode *p = setAttribute(pcszName, psz);
1016 RTStrFree(psz);
1017 return p;
1018}
1019
1020AttributeNode* ElementNode::setAttribute(const char *pcszName, uint32_t i)
1021{
1022 char *psz = NULL;
1023 RTStrAPrintf(&psz, "%RU32", i);
1024 AttributeNode *p = setAttribute(pcszName, psz);
1025 RTStrFree(psz);
1026 return p;
1027}
1028
1029AttributeNode* ElementNode::setAttribute(const char *pcszName, int64_t i)
1030{
1031 char *psz = NULL;
1032 RTStrAPrintf(&psz, "%RI64", i);
1033 AttributeNode *p = setAttribute(pcszName, psz);
1034 RTStrFree(psz);
1035 return p;
1036}
1037
1038AttributeNode* ElementNode::setAttribute(const char *pcszName, uint64_t i)
1039{
1040 char *psz = NULL;
1041 RTStrAPrintf(&psz, "%RU64", i);
1042 AttributeNode *p = setAttribute(pcszName, psz);
1043 RTStrFree(psz);
1044 return p;
1045}
1046
1047AttributeNode* ElementNode::setAttributeHex(const char *pcszName, uint32_t i)
1048{
1049 char *psz = NULL;
1050 RTStrAPrintf(&psz, "0x%RX32", i);
1051 AttributeNode *p = setAttribute(pcszName, psz);
1052 RTStrFree(psz);
1053 return p;
1054}
1055
1056AttributeNode* ElementNode::setAttribute(const char *pcszName, bool f)
1057{
1058 return setAttribute(pcszName, (f) ? "true" : "false");
1059}
1060
1061AttributeNode::AttributeNode()
1062 : Node(IsAttribute)
1063{
1064}
1065
1066ContentNode::ContentNode()
1067 : Node(IsContent)
1068{
1069}
1070
1071/*
1072 * NodesLoop
1073 *
1074 */
1075
1076struct NodesLoop::Data
1077{
1078 ElementNodesList listElements;
1079 ElementNodesList::const_iterator it;
1080};
1081
1082NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */)
1083{
1084 m = new Data;
1085 node.getChildElements(m->listElements, pcszMatch);
1086 m->it = m->listElements.begin();
1087}
1088
1089NodesLoop::~NodesLoop()
1090{
1091 delete m;
1092}
1093
1094
1095/**
1096 * Handy convenience helper for looping over all child elements. Create an
1097 * instance of NodesLoop on the stack and call this method until it returns
1098 * NULL, like this:
1099 * <code>
1100 * xml::ElementNode node; // should point to an element
1101 * xml::NodesLoop loop(node, "child"); // find all "child" elements under node
1102 * const xml::ElementNode *pChild = NULL;
1103 * while (pChild = loop.forAllNodes())
1104 * ...;
1105 * </code>
1106 * @return
1107 */
1108const ElementNode* NodesLoop::forAllNodes() const
1109{
1110 const ElementNode *pNode = NULL;
1111
1112 if (m->it != m->listElements.end())
1113 {
1114 pNode = *(m->it);
1115 ++(m->it);
1116 }
1117
1118 return pNode;
1119}
1120
1121////////////////////////////////////////////////////////////////////////////////
1122//
1123// Document class
1124//
1125////////////////////////////////////////////////////////////////////////////////
1126
1127struct Document::Data
1128{
1129 xmlDocPtr plibDocument;
1130 ElementNode *pRootElement;
1131
1132 Data()
1133 {
1134 plibDocument = NULL;
1135 pRootElement = NULL;
1136 }
1137
1138 ~Data()
1139 {
1140 reset();
1141 }
1142
1143 void reset()
1144 {
1145 if (plibDocument)
1146 {
1147 xmlFreeDoc(plibDocument);
1148 plibDocument = NULL;
1149 }
1150 if (pRootElement)
1151 {
1152 delete pRootElement;
1153 pRootElement = NULL;
1154 }
1155 }
1156
1157 void copyFrom(const Document::Data *p)
1158 {
1159 if (p->plibDocument)
1160 {
1161 plibDocument = xmlCopyDoc(p->plibDocument,
1162 1); // recursive == copy all
1163 }
1164 }
1165};
1166
1167Document::Document()
1168 : m(new Data)
1169{
1170}
1171
1172Document::Document(const Document &x)
1173 : m(new Data)
1174{
1175 m->copyFrom(x.m);
1176}
1177
1178Document& Document::operator=(const Document &x)
1179{
1180 m->reset();
1181 m->copyFrom(x.m);
1182 return *this;
1183}
1184
1185Document::~Document()
1186{
1187 delete m;
1188}
1189
1190/**
1191 * private method to refresh all internal structures after the internal pDocument
1192 * has changed. Called from XmlFileParser::read(). m->reset() must have been
1193 * called before to make sure all members except the internal pDocument are clean.
1194 */
1195void Document::refreshInternals() // private
1196{
1197 m->pRootElement = new ElementNode();
1198 m->pRootElement->m->plibNode = xmlDocGetRootElement(m->plibDocument);
1199 m->pRootElement->m->pcszName = (const char*)m->pRootElement->m->plibNode->name;
1200
1201 m->pRootElement->buildChildren();
1202}
1203
1204/**
1205 * Returns the root element of the document, or NULL if the document is empty.
1206 * Const variant.
1207 * @return
1208 */
1209const ElementNode* Document::getRootElement() const
1210{
1211 return m->pRootElement;
1212}
1213
1214/**
1215 * Returns the root element of the document, or NULL if the document is empty.
1216 * Non-const variant.
1217 * @return
1218 */
1219ElementNode* Document::getRootElement()
1220{
1221 return m->pRootElement;
1222}
1223
1224/**
1225 * Creates a new element node and sets it as the root element. This will
1226 * only work if the document is empty; otherwise EDocumentNotEmpty is thrown.
1227 */
1228ElementNode* Document::createRootElement(const char *pcszRootElementName)
1229{
1230 if (m->pRootElement || m->plibDocument)
1231 throw EDocumentNotEmpty(RT_SRC_POS);
1232
1233 // libxml side: create document, create root node
1234 m->plibDocument = xmlNewDoc((const xmlChar*)"1.0");
1235 xmlNode *plibRootNode;
1236 if (!(plibRootNode = xmlNewNode(NULL, // namespace
1237 (const xmlChar*)pcszRootElementName)))
1238 throw std::bad_alloc();
1239 xmlDocSetRootElement(m->plibDocument, plibRootNode);
1240
1241 // now wrap this in C++
1242 m->pRootElement = new ElementNode();
1243 m->pRootElement->m->plibNode = plibRootNode;
1244 m->pRootElement->m->pcszName = (const char*)plibRootNode->name;
1245
1246 return m->pRootElement;
1247}
1248
1249////////////////////////////////////////////////////////////////////////////////
1250//
1251// XmlParserBase class
1252//
1253////////////////////////////////////////////////////////////////////////////////
1254
1255XmlParserBase::XmlParserBase()
1256{
1257 m_ctxt = xmlNewParserCtxt();
1258 if (m_ctxt == NULL)
1259 throw std::bad_alloc();
1260}
1261
1262XmlParserBase::~XmlParserBase()
1263{
1264 xmlFreeParserCtxt (m_ctxt);
1265 m_ctxt = NULL;
1266}
1267
1268////////////////////////////////////////////////////////////////////////////////
1269//
1270// XmlFileParser class
1271//
1272////////////////////////////////////////////////////////////////////////////////
1273
1274struct XmlFileParser::Data
1275{
1276 xmlParserCtxtPtr ctxt;
1277 iprt::MiniString strXmlFilename;
1278
1279 Data()
1280 {
1281 if (!(ctxt = xmlNewParserCtxt()))
1282 throw std::bad_alloc();
1283 }
1284
1285 ~Data()
1286 {
1287 xmlFreeParserCtxt(ctxt);
1288 ctxt = NULL;
1289 }
1290};
1291
1292XmlFileParser::XmlFileParser()
1293 : XmlParserBase(),
1294 m(new Data())
1295{
1296}
1297
1298XmlFileParser::~XmlFileParser()
1299{
1300 delete m;
1301 m = NULL;
1302}
1303
1304struct IOContext
1305{
1306 File file;
1307 iprt::MiniString error;
1308
1309 IOContext(const char *pcszFilename, File::Mode mode)
1310 : file(mode, pcszFilename)
1311 {
1312 }
1313
1314 void setError(const xml::Error &x)
1315 {
1316 error = x.what();
1317 }
1318
1319 void setError(const std::exception &x)
1320 {
1321 error = x.what();
1322 }
1323};
1324
1325struct ReadContext : IOContext
1326{
1327 ReadContext(const char *pcszFilename)
1328 : IOContext(pcszFilename, File::Mode_Read)
1329 {
1330 }
1331};
1332
1333struct WriteContext : IOContext
1334{
1335 WriteContext(const char *pcszFilename)
1336 : IOContext(pcszFilename, File::Mode_Overwrite)
1337 {
1338 }
1339};
1340
1341/**
1342 * Reads the given file and fills the given Document object with its contents.
1343 * Throws XmlError on parsing errors.
1344 *
1345 * The document that is passed in will be reset before being filled if not empty.
1346 *
1347 * @param strFilename in: name fo file to parse.
1348 * @param doc out: document to be reset and filled with data according to file contents.
1349 */
1350void XmlFileParser::read(const iprt::MiniString &strFilename,
1351 Document &doc)
1352{
1353 GlobalLock lock;
1354// global.setExternalEntityLoader(ExternalEntityLoader);
1355
1356 m->strXmlFilename = strFilename;
1357 const char *pcszFilename = strFilename.c_str();
1358
1359 ReadContext context(pcszFilename);
1360 doc.m->reset();
1361 if (!(doc.m->plibDocument = xmlCtxtReadIO(m->ctxt,
1362 ReadCallback,
1363 CloseCallback,
1364 &context,
1365 pcszFilename,
1366 NULL, // encoding = auto
1367 XML_PARSE_NOBLANKS)))
1368 throw XmlError(xmlCtxtGetLastError(m->ctxt));
1369
1370 doc.refreshInternals();
1371}
1372
1373// static
1374int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen)
1375{
1376 ReadContext *pContext = static_cast<ReadContext*>(aCtxt);
1377
1378 /* To prevent throwing exceptions while inside libxml2 code, we catch
1379 * them and forward to our level using a couple of variables. */
1380
1381 try
1382 {
1383 return pContext->file.read(aBuf, aLen);
1384 }
1385 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
1386 catch (const xml::Error &err) { pContext->setError(err); }
1387 catch (const std::exception &err) { pContext->setError(err); }
1388 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
1389
1390 return -1 /* failure */;
1391}
1392
1393int XmlFileParser::CloseCallback(void *aCtxt)
1394{
1395 /// @todo to be written
1396
1397 return -1;
1398}
1399
1400////////////////////////////////////////////////////////////////////////////////
1401//
1402// XmlFileWriter class
1403//
1404////////////////////////////////////////////////////////////////////////////////
1405
1406struct XmlFileWriter::Data
1407{
1408 Document *pDoc;
1409};
1410
1411XmlFileWriter::XmlFileWriter(Document &doc)
1412{
1413 m = new Data();
1414 m->pDoc = &doc;
1415}
1416
1417XmlFileWriter::~XmlFileWriter()
1418{
1419 delete m;
1420}
1421
1422void XmlFileWriter::write(const char *pcszFilename)
1423{
1424 WriteContext context(pcszFilename);
1425
1426 GlobalLock lock;
1427
1428 /* serialize to the stream */
1429 xmlIndentTreeOutput = 1;
1430 xmlTreeIndentString = " ";
1431 xmlSaveNoEmptyTags = 0;
1432
1433 xmlSaveCtxtPtr saveCtxt;
1434 if (!(saveCtxt = xmlSaveToIO(WriteCallback,
1435 CloseCallback,
1436 &context,
1437 NULL,
1438 XML_SAVE_FORMAT)))
1439 throw xml::LogicError(RT_SRC_POS);
1440
1441 long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument);
1442 if (rc == -1)
1443 {
1444 /* look if there was a forwared exception from the lower level */
1445// if (m->trappedErr.get() != NULL)
1446// m->trappedErr->rethrow();
1447
1448 /* there must be an exception from the Output implementation,
1449 * otherwise the save operation must always succeed. */
1450 throw xml::LogicError(RT_SRC_POS);
1451 }
1452
1453 xmlSaveClose(saveCtxt);
1454}
1455
1456int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen)
1457{
1458 WriteContext *pContext = static_cast<WriteContext*>(aCtxt);
1459
1460 /* To prevent throwing exceptions while inside libxml2 code, we catch
1461 * them and forward to our level using a couple of variables. */
1462 try
1463 {
1464 return pContext->file.write(aBuf, aLen);
1465 }
1466 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
1467 catch (const xml::Error &err) { pContext->setError(err); }
1468 catch (const std::exception &err) { pContext->setError(err); }
1469 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
1470
1471 return -1 /* failure */;
1472}
1473
1474int XmlFileWriter::CloseCallback(void *aCtxt)
1475{
1476 /// @todo to be written
1477
1478 return -1;
1479}
1480
1481
1482} // end namespace xml
1483
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