VirtualBox

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

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

IPRT: attribute namespace support

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