VirtualBox

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

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

IPRT: xml optimizations, preps for better namespace support

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

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