VirtualBox

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

Last change on this file since 40654 was 39933, checked in by vboxsync, 13 years ago

Runtime/xml: be paranoid

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 46.9 KB
Line 
1/* $Id: xml.cpp 39933 2012-02-01 13:45:02Z vboxsync $ */
2/** @file
3 * IPRT - XML Manipulation API.
4 */
5
6/*
7 * Copyright (C) 2007-2011 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27#include <iprt/dir.h>
28#include <iprt/file.h>
29#include <iprt/err.h>
30#include <iprt/param.h>
31#include <iprt/path.h>
32#include <iprt/cpp/lock.h>
33#include <iprt/cpp/xml.h>
34
35#include <libxml/tree.h>
36#include <libxml/parser.h>
37#include <libxml/globals.h>
38#include <libxml/xmlIO.h>
39#include <libxml/xmlsave.h>
40#include <libxml/uri.h>
41
42#include <libxml/xmlschemas.h>
43
44#include <map>
45#include <boost/shared_ptr.hpp>
46
47////////////////////////////////////////////////////////////////////////////////
48//
49// globals
50//
51////////////////////////////////////////////////////////////////////////////////
52
53/**
54 * Global module initialization structure. This is to wrap non-reentrant bits
55 * of libxml, among other things.
56 *
57 * The constructor and destructor of this structure are used to perform global
58 * module initialization and cleanup. There must be only one global variable of
59 * this structure.
60 */
61static
62class Global
63{
64public:
65
66 Global()
67 {
68 /* Check the parser version. The docs say it will kill the app if
69 * there is a serious version mismatch, but I couldn't find it in the
70 * source code (it only prints the error/warning message to the console) so
71 * let's leave it as is for informational purposes. */
72 LIBXML_TEST_VERSION
73
74 /* Init libxml */
75 xmlInitParser();
76
77 /* Save the default entity resolver before someone has replaced it */
78 sxml.defaultEntityLoader = xmlGetExternalEntityLoader();
79 }
80
81 ~Global()
82 {
83 /* Shutdown libxml */
84 xmlCleanupParser();
85 }
86
87 struct
88 {
89 xmlExternalEntityLoader defaultEntityLoader;
90
91 /** Used to provide some thread safety missing in libxml2 (see e.g.
92 * XmlTreeBackend::read()) */
93 RTCLockMtx lock;
94 }
95 sxml; /* XXX naming this xml will break with gcc-3.3 */
96}
97gGlobal;
98
99
100
101namespace xml
102{
103
104////////////////////////////////////////////////////////////////////////////////
105//
106// Exceptions
107//
108////////////////////////////////////////////////////////////////////////////////
109
110LogicError::LogicError(RT_SRC_POS_DECL)
111 : RTCError(NULL)
112{
113 char *msg = NULL;
114 RTStrAPrintf(&msg, "In '%s', '%s' at #%d",
115 pszFunction, pszFile, iLine);
116 setWhat(msg);
117 RTStrFree(msg);
118}
119
120XmlError::XmlError(xmlErrorPtr aErr)
121{
122 if (!aErr)
123 throw EInvalidArg(RT_SRC_POS);
124
125 char *msg = Format(aErr);
126 setWhat(msg);
127 RTStrFree(msg);
128}
129
130/**
131 * Composes a single message for the given error. The caller must free the
132 * returned string using RTStrFree() when no more necessary.
133 */
134// static
135char *XmlError::Format(xmlErrorPtr aErr)
136{
137 const char *msg = aErr->message ? aErr->message : "<none>";
138 size_t msgLen = strlen(msg);
139 /* strip spaces, trailing EOLs and dot-like char */
140 while (msgLen && strchr(" \n.?!", msg [msgLen - 1]))
141 --msgLen;
142
143 char *finalMsg = NULL;
144 RTStrAPrintf(&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d",
145 msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2);
146
147 return finalMsg;
148}
149
150EIPRTFailure::EIPRTFailure(int aRC, const char *pcszContext, ...)
151 : RuntimeError(NULL),
152 mRC(aRC)
153{
154 char *pszContext2;
155 va_list args;
156 va_start(args, pcszContext);
157 RTStrAPrintfV(&pszContext2, pcszContext, args);
158 char *newMsg;
159 RTStrAPrintf(&newMsg, "%s: %d (%s)", pszContext2, aRC, RTErrGetShort(aRC));
160 setWhat(newMsg);
161 RTStrFree(newMsg);
162 RTStrFree(pszContext2);
163}
164
165////////////////////////////////////////////////////////////////////////////////
166//
167// File Class
168//
169//////////////////////////////////////////////////////////////////////////////
170
171struct File::Data
172{
173 Data()
174 : handle(NIL_RTFILE), opened(false)
175 { }
176
177 RTCString strFileName;
178 RTFILE handle;
179 bool opened : 1;
180 bool flushOnClose : 1;
181};
182
183File::File(Mode aMode, const char *aFileName, bool aFlushIt /* = false */)
184 : m(new Data())
185{
186 m->strFileName = aFileName;
187 m->flushOnClose = aFlushIt;
188
189 uint32_t flags = 0;
190 switch (aMode)
191 {
192 /** @todo change to RTFILE_O_DENY_WRITE where appropriate. */
193 case Mode_Read:
194 flags = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
195 break;
196 case Mode_WriteCreate: // fail if file exists
197 flags = RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE;
198 break;
199 case Mode_Overwrite: // overwrite if file exists
200 flags = RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE;
201 break;
202 case Mode_ReadWrite:
203 flags = RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;;
204 }
205
206 int vrc = RTFileOpen(&m->handle, aFileName, flags);
207 if (RT_FAILURE(vrc))
208 throw EIPRTFailure(vrc, "Runtime error opening '%s' for reading", aFileName);
209
210 m->opened = true;
211 m->flushOnClose = aFlushIt && (flags & RTFILE_O_ACCESS_MASK) != RTFILE_O_READ;
212}
213
214File::File(RTFILE aHandle, const char *aFileName /* = NULL */, bool aFlushIt /* = false */)
215 : m(new Data())
216{
217 if (aHandle == NIL_RTFILE)
218 throw EInvalidArg(RT_SRC_POS);
219
220 m->handle = aHandle;
221
222 if (aFileName)
223 m->strFileName = aFileName;
224
225 m->flushOnClose = aFlushIt;
226
227 setPos(0);
228}
229
230File::~File()
231{
232 if (m->flushOnClose)
233 {
234 RTFileFlush(m->handle);
235 if (!m->strFileName.isEmpty())
236 RTDirFlushParent(m->strFileName.c_str());
237 }
238
239 if (m->opened)
240 RTFileClose(m->handle);
241 delete m;
242}
243
244const char* File::uri() const
245{
246 return m->strFileName.c_str();
247}
248
249uint64_t File::pos() const
250{
251 uint64_t p = 0;
252 int vrc = RTFileSeek(m->handle, 0, RTFILE_SEEK_CURRENT, &p);
253 if (RT_SUCCESS(vrc))
254 return p;
255
256 throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
257}
258
259void File::setPos(uint64_t aPos)
260{
261 uint64_t p = 0;
262 unsigned method = RTFILE_SEEK_BEGIN;
263 int vrc = VINF_SUCCESS;
264
265 /* check if we overflow int64_t and move to INT64_MAX first */
266 if ((int64_t)aPos < 0)
267 {
268 vrc = RTFileSeek(m->handle, INT64_MAX, method, &p);
269 aPos -= (uint64_t)INT64_MAX;
270 method = RTFILE_SEEK_CURRENT;
271 }
272 /* seek the rest */
273 if (RT_SUCCESS(vrc))
274 vrc = RTFileSeek(m->handle, (int64_t) aPos, method, &p);
275 if (RT_SUCCESS(vrc))
276 return;
277
278 throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
279}
280
281int File::read(char *aBuf, int aLen)
282{
283 size_t len = aLen;
284 int vrc = RTFileRead(m->handle, aBuf, len, &len);
285 if (RT_SUCCESS(vrc))
286 return (int)len;
287
288 throw EIPRTFailure(vrc, "Runtime error reading from file '%s'", m->strFileName.c_str());
289}
290
291int File::write(const char *aBuf, int aLen)
292{
293 size_t len = aLen;
294 int vrc = RTFileWrite (m->handle, aBuf, len, &len);
295 if (RT_SUCCESS (vrc))
296 return (int)len;
297
298 throw EIPRTFailure(vrc, "Runtime error writing to file '%s'", m->strFileName.c_str());
299
300 return -1 /* failure */;
301}
302
303void File::truncate()
304{
305 int vrc = RTFileSetSize (m->handle, pos());
306 if (RT_SUCCESS (vrc))
307 return;
308
309 throw EIPRTFailure(vrc, "Runtime error truncating file '%s'", m->strFileName.c_str());
310}
311
312////////////////////////////////////////////////////////////////////////////////
313//
314// MemoryBuf Class
315//
316//////////////////////////////////////////////////////////////////////////////
317
318struct MemoryBuf::Data
319{
320 Data()
321 : buf (NULL), len (0), uri (NULL), pos (0) {}
322
323 const char *buf;
324 size_t len;
325 char *uri;
326
327 size_t pos;
328};
329
330MemoryBuf::MemoryBuf (const char *aBuf, size_t aLen, const char *aURI /* = NULL */)
331 : m (new Data())
332{
333 if (aBuf == NULL)
334 throw EInvalidArg (RT_SRC_POS);
335
336 m->buf = aBuf;
337 m->len = aLen;
338 m->uri = RTStrDup (aURI);
339}
340
341MemoryBuf::~MemoryBuf()
342{
343 RTStrFree (m->uri);
344}
345
346const char *MemoryBuf::uri() const
347{
348 return m->uri;
349}
350
351uint64_t MemoryBuf::pos() const
352{
353 return m->pos;
354}
355
356void MemoryBuf::setPos (uint64_t aPos)
357{
358 size_t off = (size_t) aPos;
359 if ((uint64_t) off != aPos)
360 throw EInvalidArg();
361
362 if (off > m->len)
363 throw EInvalidArg();
364
365 m->pos = off;
366}
367
368int MemoryBuf::read (char *aBuf, int aLen)
369{
370 if (m->pos >= m->len)
371 return 0 /* nothing to read */;
372
373 size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos;
374 memcpy (aBuf, m->buf + m->pos, len);
375 m->pos += len;
376
377 return (int)len;
378}
379
380////////////////////////////////////////////////////////////////////////////////
381//
382// GlobalLock class
383//
384////////////////////////////////////////////////////////////////////////////////
385
386struct GlobalLock::Data
387{
388 PFNEXTERNALENTITYLOADER pOldLoader;
389 RTCLock lock;
390
391 Data()
392 : pOldLoader(NULL),
393 lock(gGlobal.sxml.lock)
394 {
395 }
396};
397
398GlobalLock::GlobalLock()
399 : m(new Data())
400{
401}
402
403GlobalLock::~GlobalLock()
404{
405 if (m->pOldLoader)
406 xmlSetExternalEntityLoader(m->pOldLoader);
407 delete m;
408 m = NULL;
409}
410
411void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pLoader)
412{
413 m->pOldLoader = xmlGetExternalEntityLoader();
414 xmlSetExternalEntityLoader(pLoader);
415}
416
417// static
418xmlParserInput* GlobalLock::callDefaultLoader(const char *aURI,
419 const char *aID,
420 xmlParserCtxt *aCtxt)
421{
422 return gGlobal.sxml.defaultEntityLoader(aURI, aID, aCtxt);
423}
424
425////////////////////////////////////////////////////////////////////////////////
426//
427// Node class
428//
429////////////////////////////////////////////////////////////////////////////////
430
431struct Node::Data
432{
433 struct compare_const_char
434 {
435 bool operator()(const char* s1, const char* s2) const
436 {
437 return strcmp(s1, s2) < 0;
438 }
439 };
440
441 // attributes, if this is an element; can be empty
442 typedef std::map<const char*, boost::shared_ptr<AttributeNode>, compare_const_char > AttributesMap;
443 AttributesMap attribs;
444
445 // child elements, if this is an element; can be empty
446 typedef std::list< boost::shared_ptr<Node> > InternalNodesList;
447 InternalNodesList children;
448};
449
450Node::Node(EnumType type,
451 Node *pParent,
452 xmlNode *plibNode,
453 xmlAttr *plibAttr)
454 : m_Type(type),
455 m_pParent(pParent),
456 m_plibNode(plibNode),
457 m_plibAttr(plibAttr),
458 m_pcszNamespacePrefix(NULL),
459 m_pcszNamespaceHref(NULL),
460 m_pcszName(NULL),
461 m(new Data)
462{
463}
464
465Node::~Node()
466{
467 delete m;
468}
469
470/**
471 * Private implementation.
472 * @param elmRoot
473 */
474void Node::buildChildren(const ElementNode &elmRoot) // private
475{
476 // go thru this element's attributes
477 xmlAttr *plibAttr = m_plibNode->properties;
478 while (plibAttr)
479 {
480 const char *pcszKey;
481 boost::shared_ptr<AttributeNode> pNew(new AttributeNode(elmRoot, this, plibAttr, &pcszKey));
482 // store
483 m->attribs[pcszKey] = pNew;
484
485 plibAttr = plibAttr->next;
486 }
487
488 // go thru this element's child elements
489 xmlNodePtr plibNode = m_plibNode->children;
490 while (plibNode)
491 {
492 boost::shared_ptr<Node> pNew;
493
494 if (plibNode->type == XML_ELEMENT_NODE)
495 pNew = boost::shared_ptr<Node>(new ElementNode(&elmRoot, this, plibNode));
496 else if (plibNode->type == XML_TEXT_NODE)
497 pNew = boost::shared_ptr<Node>(new ContentNode(this, plibNode));
498 if (pNew)
499 {
500 // store
501 m->children.push_back(pNew);
502
503 // recurse for this child element to get its own children
504 pNew->buildChildren(elmRoot);
505 }
506
507 plibNode = plibNode->next;
508 }
509}
510
511/**
512 * Returns the name of the node, which is either the element name or
513 * the attribute name. For other node types it probably returns NULL.
514 * @return
515 */
516const char* Node::getName() const
517{
518 return m_pcszName;
519}
520
521/**
522 * Variant of nameEquals that checks the namespace as well.
523 * @param pcszNamespace
524 * @param pcsz
525 * @return
526 */
527bool Node::nameEquals(const char *pcszNamespace, const char *pcsz) const
528{
529 if (m_pcszName == pcsz)
530 return true;
531 if (m_pcszName == NULL)
532 return false;
533 if (pcsz == NULL)
534 return false;
535 if (strcmp(m_pcszName, pcsz))
536 return false;
537
538 // name matches: then check namespaces as well
539 if (!pcszNamespace)
540 return true;
541 // caller wants namespace:
542 if (!m_pcszNamespacePrefix)
543 // but node has no namespace:
544 return false;
545 return !strcmp(m_pcszNamespacePrefix, pcszNamespace);
546}
547
548/**
549 * Returns the value of a node. If this node is an attribute, returns
550 * the attribute value; if this node is an element, then this returns
551 * the element text content.
552 * @return
553 */
554const char* Node::getValue() const
555{
556 if ( (m_plibAttr)
557 && (m_plibAttr->children)
558 )
559 // libxml hides attribute values in another node created as a
560 // single child of the attribute node, and it's in the content field
561 return (const char*)m_plibAttr->children->content;
562
563 if ( (m_plibNode)
564 && (m_plibNode->children)
565 )
566 return (const char*)m_plibNode->children->content;
567
568 return NULL;
569}
570
571/**
572 * Copies the value of a node into the given integer variable.
573 * Returns TRUE only if a value was found and was actually an
574 * integer of the given type.
575 * @return
576 */
577bool Node::copyValue(int32_t &i) const
578{
579 const char *pcsz;
580 if ( ((pcsz = getValue()))
581 && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i))
582 )
583 return true;
584
585 return false;
586}
587
588/**
589 * Copies the value of a node into the given integer variable.
590 * Returns TRUE only if a value was found and was actually an
591 * integer of the given type.
592 * @return
593 */
594bool Node::copyValue(uint32_t &i) const
595{
596 const char *pcsz;
597 if ( ((pcsz = getValue()))
598 && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i))
599 )
600 return true;
601
602 return false;
603}
604
605/**
606 * Copies the value of a node into the given integer variable.
607 * Returns TRUE only if a value was found and was actually an
608 * integer of the given type.
609 * @return
610 */
611bool Node::copyValue(int64_t &i) const
612{
613 const char *pcsz;
614 if ( ((pcsz = getValue()))
615 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i))
616 )
617 return true;
618
619 return false;
620}
621
622/**
623 * Copies the value of a node into the given integer variable.
624 * Returns TRUE only if a value was found and was actually an
625 * integer of the given type.
626 * @return
627 */
628bool Node::copyValue(uint64_t &i) const
629{
630 const char *pcsz;
631 if ( ((pcsz = getValue()))
632 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i))
633 )
634 return true;
635
636 return false;
637}
638
639/**
640 * Returns the line number of the current node in the source XML file.
641 * Useful for error messages.
642 * @return
643 */
644int Node::getLineNumber() const
645{
646 if (m_plibAttr)
647 return m_pParent->m_plibNode->line;
648
649 return m_plibNode->line;
650}
651
652/**
653 * Private element constructor.
654 * @param pelmRoot
655 * @param pParent
656 * @param plibNode
657 */
658ElementNode::ElementNode(const ElementNode *pelmRoot,
659 Node *pParent,
660 xmlNode *plibNode)
661 : Node(IsElement,
662 pParent,
663 plibNode,
664 NULL)
665{
666 if (!(m_pelmRoot = pelmRoot))
667 // NULL passed, then this is the root element
668 m_pelmRoot = this;
669
670 m_pcszName = (const char*)plibNode->name;
671
672 if (plibNode->ns)
673 {
674 m_pcszNamespacePrefix = (const char*)m_plibNode->ns->prefix;
675 m_pcszNamespaceHref = (const char*)m_plibNode->ns->href;
676 }
677}
678
679/**
680 * Builds a list of direct child elements of the current element that
681 * match the given string; if pcszMatch is NULL, all direct child
682 * elements are returned.
683 * @param children out: list of nodes to which children will be appended.
684 * @param pcszMatch in: match string, or NULL to return all children.
685 * @return Number of items appended to the list (0 if none).
686 */
687int ElementNode::getChildElements(ElementNodesList &children,
688 const char *pcszMatch /*= NULL*/)
689 const
690{
691 int i = 0;
692 for (Data::InternalNodesList::iterator it = m->children.begin();
693 it != m->children.end();
694 ++it)
695 {
696 // export this child node if ...
697 Node *p = it->get();
698 if (p->isElement())
699 if ( (!pcszMatch) // the caller wants all nodes or
700 || (!strcmp(pcszMatch, p->getName())) // the element name matches
701 )
702 {
703 children.push_back(static_cast<ElementNode*>(p));
704 ++i;
705 }
706 }
707 return i;
708}
709
710/**
711 * Returns the first child element whose name matches pcszMatch.
712 *
713 * @param pcszNamespace Namespace prefix (e.g. "vbox") or NULL to match any namespace.
714 * @param pcszMatch Element name to match.
715 * @return
716 */
717const ElementNode* ElementNode::findChildElement(const char *pcszNamespace,
718 const char *pcszMatch)
719 const
720{
721 Data::InternalNodesList::const_iterator
722 it,
723 last = m->children.end();
724 for (it = m->children.begin();
725 it != last;
726 ++it)
727 {
728 if ((**it).isElement())
729 {
730 ElementNode *pelm = static_cast<ElementNode*>((*it).get());
731 if (pelm->nameEquals(pcszNamespace, pcszMatch))
732 return pelm;
733 }
734 }
735
736 return NULL;
737}
738
739/**
740 * Returns the first child element whose "id" attribute matches pcszId.
741 * @param pcszId identifier to look for.
742 * @return child element or NULL if not found.
743 */
744const ElementNode* ElementNode::findChildElementFromId(const char *pcszId) const
745{
746 Data::InternalNodesList::const_iterator
747 it,
748 last = m->children.end();
749 for (it = m->children.begin();
750 it != last;
751 ++it)
752 {
753 if ((**it).isElement())
754 {
755 ElementNode *pelm = static_cast<ElementNode*>((*it).get());
756 const AttributeNode *pAttr;
757 if ( ((pAttr = pelm->findAttribute("id")))
758 && (!strcmp(pAttr->getValue(), pcszId))
759 )
760 return pelm;
761 }
762 }
763
764 return NULL;
765}
766
767/**
768 * Looks up the given attribute node in this element's attribute map.
769 *
770 * With respect to namespaces, the internal attributes map stores namespace
771 * prefixes with attribute names only if the attribute uses a non-default
772 * namespace. As a result, the following rules apply:
773 *
774 * -- To find attributes from a non-default namespace, pcszMatch must not
775 * be prefixed with a namespace.
776 *
777 * -- To find attributes from the default namespace (or if the document does
778 * not use namespaces), pcszMatch must be prefixed with the namespace
779 * prefix and a colon.
780 *
781 * For example, if the document uses the "vbox:" namespace by default, you
782 * must omit "vbox:" from pcszMatch to find such attributes, whether they
783 * are specifed in the xml or not.
784 *
785 * @param pcszMatch
786 * @return
787 */
788const AttributeNode* ElementNode::findAttribute(const char *pcszMatch) const
789{
790 Data::AttributesMap::const_iterator it;
791
792 it = m->attribs.find(pcszMatch);
793 if (it != m->attribs.end())
794 return it->second.get();
795
796 return NULL;
797}
798
799/**
800 * Convenience method which attempts to find the attribute with the given
801 * name and returns its value as a string.
802 *
803 * @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
804 * @param ppcsz out: attribute value
805 * @return TRUE if attribute was found and str was thus updated.
806 */
807bool ElementNode::getAttributeValue(const char *pcszMatch, const char *&ppcsz) const
808{
809 const Node* pAttr;
810 if ((pAttr = findAttribute(pcszMatch)))
811 {
812 ppcsz = pAttr->getValue();
813 return true;
814 }
815
816 return false;
817}
818
819/**
820 * Convenience method which attempts to find the attribute with the given
821 * name and returns its value as a string.
822 *
823 * @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
824 * @param str out: attribute value; overwritten only if attribute was found
825 * @return TRUE if attribute was found and str was thus updated.
826 */
827bool ElementNode::getAttributeValue(const char *pcszMatch, RTCString &str) const
828{
829 const Node* pAttr;
830 if ((pAttr = findAttribute(pcszMatch)))
831 {
832 str = pAttr->getValue();
833 return true;
834 }
835
836 return false;
837}
838
839/**
840 * Like getAttributeValue (ministring variant), but makes sure that all backslashes
841 * are converted to forward slashes.
842 * @param pcszMatch
843 * @param str
844 * @return
845 */
846bool ElementNode::getAttributeValuePath(const char *pcszMatch, RTCString &str) const
847{
848 if (getAttributeValue(pcszMatch, str))
849 {
850 str.findReplace('\\', '/');
851 return true;
852 }
853
854 return false;
855}
856
857/**
858 * Convenience method which attempts to find the attribute with the given
859 * name and returns its value as a signed integer. This calls
860 * RTStrToInt32Ex internally and will only output the integer if that
861 * function returns no error.
862 *
863 * @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
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, int32_t &i) const
868{
869 const char *pcsz;
870 if ( (getAttributeValue(pcszMatch, pcsz))
871 && (VINF_SUCCESS == RTStrToInt32Ex(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 an unsigned integer.This calls
881 * RTStrToUInt32Ex internally and will only output the integer if that
882 * function returns no error.
883 *
884 * @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
885 * @param i out: attribute value; overwritten only if attribute was found
886 * @return TRUE if attribute was found and str was thus updated.
887 */
888bool ElementNode::getAttributeValue(const char *pcszMatch, uint32_t &i) const
889{
890 const char *pcsz;
891 if ( (getAttributeValue(pcszMatch, pcsz))
892 && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 0, &i))
893 )
894 return true;
895
896 return false;
897}
898
899/**
900 * Convenience method which attempts to find the attribute with the given
901 * name and returns its value as a signed long integer. This calls
902 * RTStrToInt64Ex internally and will only output the integer if that
903 * function returns no error.
904 *
905 * @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
906 * @param i out: attribute value
907 * @return TRUE if attribute was found and str was thus updated.
908 */
909bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t &i) const
910{
911 const char *pcsz;
912 if ( (getAttributeValue(pcszMatch, pcsz))
913 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 0, &i))
914 )
915 return true;
916
917 return false;
918}
919
920/**
921 * Convenience method which attempts to find the attribute with the given
922 * name and returns its value as an unsigned long integer.This calls
923 * RTStrToUInt64Ex internally and will only output the integer if that
924 * function returns no error.
925 *
926 * @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
927 * @param i out: attribute value; overwritten only if attribute was found
928 * @return TRUE if attribute was found and str was thus updated.
929 */
930bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t &i) const
931{
932 const char *pcsz;
933 if ( (getAttributeValue(pcszMatch, pcsz))
934 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 0, &i))
935 )
936 return true;
937
938 return false;
939}
940
941/**
942 * Convenience method which attempts to find the attribute with the given
943 * name and returns its value as a boolean. This accepts "true", "false",
944 * "yes", "no", "1" or "0" as valid values.
945 *
946 * @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
947 * @param f out: attribute value; overwritten only if attribute was found
948 * @return TRUE if attribute was found and str was thus updated.
949 */
950bool ElementNode::getAttributeValue(const char *pcszMatch, bool &f) const
951{
952 const char *pcsz;
953 if (getAttributeValue(pcszMatch, pcsz))
954 {
955 if ( (!strcmp(pcsz, "true"))
956 || (!strcmp(pcsz, "yes"))
957 || (!strcmp(pcsz, "1"))
958 )
959 {
960 f = true;
961 return true;
962 }
963 if ( (!strcmp(pcsz, "false"))
964 || (!strcmp(pcsz, "no"))
965 || (!strcmp(pcsz, "0"))
966 )
967 {
968 f = false;
969 return true;
970 }
971 }
972
973 return false;
974}
975
976/**
977 * Creates a new child element node and appends it to the list
978 * of children in "this".
979 *
980 * @param pcszElementName
981 * @return
982 */
983ElementNode* ElementNode::createChild(const char *pcszElementName)
984{
985 // we must be an element, not an attribute
986 if (!m_plibNode)
987 throw ENodeIsNotElement(RT_SRC_POS);
988
989 // libxml side: create new node
990 xmlNode *plibNode;
991 if (!(plibNode = xmlNewNode(NULL, // namespace
992 (const xmlChar*)pcszElementName)))
993 throw std::bad_alloc();
994 xmlAddChild(m_plibNode, plibNode);
995
996 // now wrap this in C++
997 ElementNode *p = new ElementNode(m_pelmRoot, this, plibNode);
998 boost::shared_ptr<ElementNode> pNew(p);
999 m->children.push_back(pNew);
1000
1001 return p;
1002}
1003
1004
1005/**
1006 * Creates a content node and appends it to the list of children
1007 * in "this".
1008 *
1009 * @param pcszContent
1010 * @return
1011 */
1012ContentNode* ElementNode::addContent(const char *pcszContent)
1013{
1014 // libxml side: create new node
1015 xmlNode *plibNode;
1016 if (!(plibNode = xmlNewText((const xmlChar*)pcszContent)))
1017 throw std::bad_alloc();
1018 xmlAddChild(m_plibNode, plibNode);
1019
1020 // now wrap this in C++
1021 ContentNode *p = new ContentNode(this, plibNode);
1022 boost::shared_ptr<ContentNode> pNew(p);
1023 m->children.push_back(pNew);
1024
1025 return p;
1026}
1027
1028/**
1029 * Sets the given attribute; overloaded version for const char *.
1030 *
1031 * If an attribute with the given name exists, it is overwritten,
1032 * otherwise a new attribute is created. Returns the attribute node
1033 * that was either created or changed.
1034 *
1035 * @param pcszName
1036 * @param pcszValue
1037 * @return
1038 */
1039AttributeNode* ElementNode::setAttribute(const char *pcszName, const char *pcszValue)
1040{
1041 AttributeNode *pattrReturn;
1042 Data::AttributesMap::const_iterator it;
1043
1044 it = m->attribs.find(pcszName);
1045 if (it == m->attribs.end())
1046 {
1047 // libxml side: xmlNewProp creates an attribute
1048 xmlAttr *plibAttr = xmlNewProp(m_plibNode, (xmlChar*)pcszName, (xmlChar*)pcszValue);
1049
1050 // C++ side: create an attribute node around it
1051 const char *pcszKey;
1052 boost::shared_ptr<AttributeNode> pNew(new AttributeNode(*m_pelmRoot, this, plibAttr, &pcszKey));
1053 // store
1054 m->attribs[pcszKey] = pNew;
1055 pattrReturn = pNew.get();
1056 }
1057 else
1058 {
1059 // overwrite existing libxml attribute node
1060 xmlAttrPtr plibAttr = xmlSetProp(m_plibNode, (xmlChar*)pcszName, (xmlChar*)pcszValue);
1061
1062 // and fix our existing C++ side around it
1063 boost::shared_ptr<AttributeNode> pattr = it->second;
1064 pattr->m_plibAttr = plibAttr; // in case the xmlAttrPtr is different, I'm not sure
1065
1066 pattrReturn = pattr.get();
1067 }
1068
1069 return pattrReturn;
1070
1071}
1072
1073/**
1074 * Like setAttribute (ministring variant), but replaces all backslashes with forward slashes
1075 * before calling that one.
1076 * @param pcszName
1077 * @param strValue
1078 * @return
1079 */
1080AttributeNode* ElementNode::setAttributePath(const char *pcszName, const RTCString &strValue)
1081{
1082 RTCString strTemp(strValue);
1083 strTemp.findReplace('\\', '/');
1084 return setAttribute(pcszName, strTemp.c_str());
1085}
1086
1087/**
1088 * Sets the given attribute; overloaded version for int32_t.
1089 *
1090 * If an attribute with the given name exists, it is overwritten,
1091 * otherwise a new attribute is created. Returns the attribute node
1092 * that was either created or changed.
1093 *
1094 * @param pcszName
1095 * @param i
1096 * @return
1097 */
1098AttributeNode* ElementNode::setAttribute(const char *pcszName, int32_t i)
1099{
1100 char szValue[12]; // negative sign + 10 digits + \0
1101 RTStrPrintf(szValue, sizeof(szValue), "%RI32", i);
1102 AttributeNode *p = setAttribute(pcszName, szValue);
1103 return p;
1104}
1105
1106/**
1107 * Sets the given attribute; overloaded version for uint32_t.
1108 *
1109 * If an attribute with the given name exists, it is overwritten,
1110 * otherwise a new attribute is created. Returns the attribute node
1111 * that was either created or changed.
1112 *
1113 * @param pcszName
1114 * @param u
1115 * @return
1116 */
1117AttributeNode* ElementNode::setAttribute(const char *pcszName, uint32_t u)
1118{
1119 char szValue[11]; // 10 digits + \0
1120 RTStrPrintf(szValue, sizeof(szValue), "%RU32", u);
1121 AttributeNode *p = setAttribute(pcszName, szValue);
1122 return p;
1123}
1124
1125/**
1126 * Sets the given attribute; overloaded version for int64_t.
1127 *
1128 * If an attribute with the given name exists, it is overwritten,
1129 * otherwise a new attribute is created. Returns the attribute node
1130 * that was either created or changed.
1131 *
1132 * @param pcszName
1133 * @param i
1134 * @return
1135 */
1136AttributeNode* ElementNode::setAttribute(const char *pcszName, int64_t i)
1137{
1138 char szValue[21]; // negative sign + 19 digits + \0
1139 RTStrPrintf(szValue, sizeof(szValue), "%RI64", i);
1140 AttributeNode *p = setAttribute(pcszName, szValue);
1141 return p;
1142}
1143
1144/**
1145 * Sets the given attribute; overloaded version for uint64_t.
1146 *
1147 * If an attribute with the given name exists, it is overwritten,
1148 * otherwise a new attribute is created. Returns the attribute node
1149 * that was either created or changed.
1150 *
1151 * @param pcszName
1152 * @param u
1153 * @return
1154 */
1155AttributeNode* ElementNode::setAttribute(const char *pcszName, uint64_t u)
1156{
1157 char szValue[21]; // 20 digits + \0
1158 RTStrPrintf(szValue, sizeof(szValue), "%RU64", u);
1159 AttributeNode *p = setAttribute(pcszName, szValue);
1160 return p;
1161}
1162
1163/**
1164 * Sets the given attribute to the given uint32_t, outputs a hexadecimal string.
1165 *
1166 * If an attribute with the given name exists, it is overwritten,
1167 * otherwise a new attribute is created. Returns the attribute node
1168 * that was either created or changed.
1169 *
1170 * @param pcszName
1171 * @param u
1172 * @return
1173 */
1174AttributeNode* ElementNode::setAttributeHex(const char *pcszName, uint32_t u)
1175{
1176 char szValue[11]; // "0x" + 8 digits + \0
1177 RTStrPrintf(szValue, sizeof(szValue), "0x%RX32", u);
1178 AttributeNode *p = setAttribute(pcszName, szValue);
1179 return p;
1180}
1181
1182/**
1183 * Sets the given attribute; overloaded version for bool.
1184 *
1185 * If an attribute with the given name exists, it is overwritten,
1186 * otherwise a new attribute is created. Returns the attribute node
1187 * that was either created or changed.
1188 *
1189 * @param pcszName
1190 * @param i
1191 * @return
1192 */
1193AttributeNode* ElementNode::setAttribute(const char *pcszName, bool f)
1194{
1195 return setAttribute(pcszName, (f) ? "true" : "false");
1196}
1197
1198/**
1199 * Private constructor for a new attribute node. This one is special:
1200 * in ppcszKey, it returns a pointer to a string buffer that should be
1201 * used to index the attribute correctly with namespaces.
1202 *
1203 * @param pParent
1204 * @param elmRoot
1205 * @param plibAttr
1206 * @param ppcszKey
1207 */
1208AttributeNode::AttributeNode(const ElementNode &elmRoot,
1209 Node *pParent,
1210 xmlAttr *plibAttr,
1211 const char **ppcszKey)
1212 : Node(IsAttribute,
1213 pParent,
1214 NULL,
1215 plibAttr)
1216{
1217 m_pcszName = (const char*)plibAttr->name;
1218
1219 *ppcszKey = m_pcszName;
1220
1221 if ( plibAttr->ns
1222 && plibAttr->ns->prefix
1223 )
1224 {
1225 m_pcszNamespacePrefix = (const char*)plibAttr->ns->prefix;
1226 m_pcszNamespaceHref = (const char*)plibAttr->ns->href;
1227
1228 if ( !elmRoot.m_pcszNamespaceHref
1229 || (strcmp(m_pcszNamespaceHref, elmRoot.m_pcszNamespaceHref))
1230 )
1231 {
1232 // not default namespace:
1233 m_strKey = m_pcszNamespacePrefix;
1234 m_strKey.append(':');
1235 m_strKey.append(m_pcszName);
1236
1237 *ppcszKey = m_strKey.c_str();
1238 }
1239 }
1240}
1241
1242ContentNode::ContentNode(Node *pParent, xmlNode *plibNode)
1243 : Node(IsContent,
1244 pParent,
1245 plibNode,
1246 NULL)
1247{
1248}
1249
1250/*
1251 * NodesLoop
1252 *
1253 */
1254
1255struct NodesLoop::Data
1256{
1257 ElementNodesList listElements;
1258 ElementNodesList::const_iterator it;
1259};
1260
1261NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */)
1262{
1263 m = new Data;
1264 node.getChildElements(m->listElements, pcszMatch);
1265 m->it = m->listElements.begin();
1266}
1267
1268NodesLoop::~NodesLoop()
1269{
1270 delete m;
1271}
1272
1273
1274/**
1275 * Handy convenience helper for looping over all child elements. Create an
1276 * instance of NodesLoop on the stack and call this method until it returns
1277 * NULL, like this:
1278 * <code>
1279 * xml::ElementNode node; // should point to an element
1280 * xml::NodesLoop loop(node, "child"); // find all "child" elements under node
1281 * const xml::ElementNode *pChild = NULL;
1282 * while (pChild = loop.forAllNodes())
1283 * ...;
1284 * </code>
1285 * @return
1286 */
1287const ElementNode* NodesLoop::forAllNodes() const
1288{
1289 const ElementNode *pNode = NULL;
1290
1291 if (m->it != m->listElements.end())
1292 {
1293 pNode = *(m->it);
1294 ++(m->it);
1295 }
1296
1297 return pNode;
1298}
1299
1300////////////////////////////////////////////////////////////////////////////////
1301//
1302// Document class
1303//
1304////////////////////////////////////////////////////////////////////////////////
1305
1306struct Document::Data
1307{
1308 xmlDocPtr plibDocument;
1309 ElementNode *pRootElement;
1310 ElementNode *pComment;
1311
1312 Data()
1313 {
1314 plibDocument = NULL;
1315 pRootElement = NULL;
1316 pComment = NULL;
1317 }
1318
1319 ~Data()
1320 {
1321 reset();
1322 }
1323
1324 void reset()
1325 {
1326 if (plibDocument)
1327 {
1328 xmlFreeDoc(plibDocument);
1329 plibDocument = NULL;
1330 }
1331 if (pRootElement)
1332 {
1333 delete pRootElement;
1334 pRootElement = NULL;
1335 }
1336 if (pComment)
1337 {
1338 delete pComment;
1339 pComment = NULL;
1340 }
1341 }
1342
1343 void copyFrom(const Document::Data *p)
1344 {
1345 if (p->plibDocument)
1346 {
1347 plibDocument = xmlCopyDoc(p->plibDocument,
1348 1); // recursive == copy all
1349 }
1350 }
1351};
1352
1353Document::Document()
1354 : m(new Data)
1355{
1356}
1357
1358Document::Document(const Document &x)
1359 : m(new Data)
1360{
1361 m->copyFrom(x.m);
1362}
1363
1364Document& Document::operator=(const Document &x)
1365{
1366 m->reset();
1367 m->copyFrom(x.m);
1368 return *this;
1369}
1370
1371Document::~Document()
1372{
1373 delete m;
1374}
1375
1376/**
1377 * private method to refresh all internal structures after the internal pDocument
1378 * has changed. Called from XmlFileParser::read(). m->reset() must have been
1379 * called before to make sure all members except the internal pDocument are clean.
1380 */
1381void Document::refreshInternals() // private
1382{
1383 m->pRootElement = new ElementNode(NULL, NULL, xmlDocGetRootElement(m->plibDocument));
1384
1385 m->pRootElement->buildChildren(*m->pRootElement);
1386}
1387
1388/**
1389 * Returns the root element of the document, or NULL if the document is empty.
1390 * Const variant.
1391 * @return
1392 */
1393const ElementNode* Document::getRootElement() const
1394{
1395 return m->pRootElement;
1396}
1397
1398/**
1399 * Returns the root element of the document, or NULL if the document is empty.
1400 * Non-const variant.
1401 * @return
1402 */
1403ElementNode* Document::getRootElement()
1404{
1405 return m->pRootElement;
1406}
1407
1408/**
1409 * Creates a new element node and sets it as the root element. This will
1410 * only work if the document is empty; otherwise EDocumentNotEmpty is thrown.
1411 */
1412ElementNode* Document::createRootElement(const char *pcszRootElementName,
1413 const char *pcszComment /* = NULL */)
1414{
1415 if (m->pRootElement || m->plibDocument)
1416 throw EDocumentNotEmpty(RT_SRC_POS);
1417
1418 // libxml side: create document, create root node
1419 m->plibDocument = xmlNewDoc((const xmlChar*)"1.0");
1420 xmlNode *plibRootNode;
1421 if (!(plibRootNode = xmlNewNode(NULL, // namespace
1422 (const xmlChar*)pcszRootElementName)))
1423 throw std::bad_alloc();
1424 xmlDocSetRootElement(m->plibDocument, plibRootNode);
1425 // now wrap this in C++
1426 m->pRootElement = new ElementNode(NULL, NULL, plibRootNode);
1427
1428 // add document global comment if specified
1429 if (pcszComment != NULL)
1430 {
1431 xmlNode *pComment;
1432 if (!(pComment = xmlNewDocComment(m->plibDocument,
1433 (const xmlChar *)pcszComment)))
1434 throw std::bad_alloc();
1435 xmlAddPrevSibling(plibRootNode, pComment);
1436 // now wrap this in C++
1437 m->pComment = new ElementNode(NULL, NULL, pComment);
1438 }
1439
1440 return m->pRootElement;
1441}
1442
1443////////////////////////////////////////////////////////////////////////////////
1444//
1445// XmlParserBase class
1446//
1447////////////////////////////////////////////////////////////////////////////////
1448
1449XmlParserBase::XmlParserBase()
1450{
1451 m_ctxt = xmlNewParserCtxt();
1452 if (m_ctxt == NULL)
1453 throw std::bad_alloc();
1454}
1455
1456XmlParserBase::~XmlParserBase()
1457{
1458 xmlFreeParserCtxt (m_ctxt);
1459 m_ctxt = NULL;
1460}
1461
1462////////////////////////////////////////////////////////////////////////////////
1463//
1464// XmlMemParser class
1465//
1466////////////////////////////////////////////////////////////////////////////////
1467
1468XmlMemParser::XmlMemParser()
1469 : XmlParserBase()
1470{
1471}
1472
1473XmlMemParser::~XmlMemParser()
1474{
1475}
1476
1477/**
1478 * Parse the given buffer and fills the given Document object with its contents.
1479 * Throws XmlError on parsing errors.
1480 *
1481 * The document that is passed in will be reset before being filled if not empty.
1482 *
1483 * @param pvBuf in: memory buffer to parse.
1484 * @param cbSize in: size of the memory buffer.
1485 * @param strFilename in: name fo file to parse.
1486 * @param doc out: document to be reset and filled with data according to file contents.
1487 */
1488void XmlMemParser::read(const void* pvBuf, size_t cbSize,
1489 const RTCString &strFilename,
1490 Document &doc)
1491{
1492 GlobalLock lock;
1493// global.setExternalEntityLoader(ExternalEntityLoader);
1494
1495 const char *pcszFilename = strFilename.c_str();
1496
1497 doc.m->reset();
1498 if (!(doc.m->plibDocument = xmlCtxtReadMemory(m_ctxt,
1499 (const char*)pvBuf,
1500 (int)cbSize,
1501 pcszFilename,
1502 NULL, // encoding = auto
1503 XML_PARSE_NOBLANKS | XML_PARSE_NONET)))
1504 throw XmlError(xmlCtxtGetLastError(m_ctxt));
1505
1506 doc.refreshInternals();
1507}
1508
1509////////////////////////////////////////////////////////////////////////////////
1510//
1511// XmlMemWriter class
1512//
1513////////////////////////////////////////////////////////////////////////////////
1514
1515XmlMemWriter::XmlMemWriter()
1516 : m_pBuf(0)
1517{
1518}
1519
1520XmlMemWriter::~XmlMemWriter()
1521{
1522 if (m_pBuf)
1523 xmlFree(m_pBuf);
1524}
1525
1526void XmlMemWriter::write(const Document &doc, void **ppvBuf, size_t *pcbSize)
1527{
1528 if (m_pBuf)
1529 {
1530 xmlFree(m_pBuf);
1531 m_pBuf = 0;
1532 }
1533 int size;
1534 xmlDocDumpFormatMemory(doc.m->plibDocument, (xmlChar**)&m_pBuf, &size, 1);
1535 *ppvBuf = m_pBuf;
1536 *pcbSize = size;
1537}
1538
1539////////////////////////////////////////////////////////////////////////////////
1540//
1541// XmlFileParser class
1542//
1543////////////////////////////////////////////////////////////////////////////////
1544
1545struct XmlFileParser::Data
1546{
1547 RTCString strXmlFilename;
1548
1549 Data()
1550 {
1551 }
1552
1553 ~Data()
1554 {
1555 }
1556};
1557
1558XmlFileParser::XmlFileParser()
1559 : XmlParserBase(),
1560 m(new Data())
1561{
1562}
1563
1564XmlFileParser::~XmlFileParser()
1565{
1566 delete m;
1567 m = NULL;
1568}
1569
1570struct IOContext
1571{
1572 File file;
1573 RTCString error;
1574
1575 IOContext(const char *pcszFilename, File::Mode mode, bool fFlush = false)
1576 : file(mode, pcszFilename, fFlush)
1577 {
1578 }
1579
1580 void setError(const RTCError &x)
1581 {
1582 error = x.what();
1583 }
1584
1585 void setError(const std::exception &x)
1586 {
1587 error = x.what();
1588 }
1589};
1590
1591struct ReadContext : IOContext
1592{
1593 ReadContext(const char *pcszFilename)
1594 : IOContext(pcszFilename, File::Mode_Read)
1595 {
1596 }
1597};
1598
1599struct WriteContext : IOContext
1600{
1601 WriteContext(const char *pcszFilename, bool fFlush)
1602 : IOContext(pcszFilename, File::Mode_Overwrite, fFlush)
1603 {
1604 }
1605};
1606
1607/**
1608 * Reads the given file and fills the given Document object with its contents.
1609 * Throws XmlError on parsing errors.
1610 *
1611 * The document that is passed in will be reset before being filled if not empty.
1612 *
1613 * @param strFilename in: name fo file to parse.
1614 * @param doc out: document to be reset and filled with data according to file contents.
1615 */
1616void XmlFileParser::read(const RTCString &strFilename,
1617 Document &doc)
1618{
1619 GlobalLock lock;
1620// global.setExternalEntityLoader(ExternalEntityLoader);
1621
1622 m->strXmlFilename = strFilename;
1623 const char *pcszFilename = strFilename.c_str();
1624
1625 ReadContext context(pcszFilename);
1626 doc.m->reset();
1627 if (!(doc.m->plibDocument = xmlCtxtReadIO(m_ctxt,
1628 ReadCallback,
1629 CloseCallback,
1630 &context,
1631 pcszFilename,
1632 NULL, // encoding = auto
1633 XML_PARSE_NOBLANKS | XML_PARSE_NONET)))
1634 throw XmlError(xmlCtxtGetLastError(m_ctxt));
1635
1636 doc.refreshInternals();
1637}
1638
1639// static
1640int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen)
1641{
1642 ReadContext *pContext = static_cast<ReadContext*>(aCtxt);
1643
1644 /* To prevent throwing exceptions while inside libxml2 code, we catch
1645 * them and forward to our level using a couple of variables. */
1646
1647 try
1648 {
1649 return pContext->file.read(aBuf, aLen);
1650 }
1651 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
1652 catch (const RTCError &err) { pContext->setError(err); }
1653 catch (const std::exception &err) { pContext->setError(err); }
1654 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
1655
1656 return -1 /* failure */;
1657}
1658
1659int XmlFileParser::CloseCallback(void *aCtxt)
1660{
1661 /// @todo to be written
1662 NOREF(aCtxt);
1663
1664 return -1;
1665}
1666
1667////////////////////////////////////////////////////////////////////////////////
1668//
1669// XmlFileWriter class
1670//
1671////////////////////////////////////////////////////////////////////////////////
1672
1673struct XmlFileWriter::Data
1674{
1675 Document *pDoc;
1676};
1677
1678XmlFileWriter::XmlFileWriter(Document &doc)
1679{
1680 m = new Data();
1681 m->pDoc = &doc;
1682}
1683
1684XmlFileWriter::~XmlFileWriter()
1685{
1686 delete m;
1687}
1688
1689void XmlFileWriter::writeInternal(const char *pcszFilename, bool fSafe)
1690{
1691 WriteContext context(pcszFilename, fSafe);
1692
1693 GlobalLock lock;
1694
1695 /* serialize to the stream */
1696 xmlIndentTreeOutput = 1;
1697 xmlTreeIndentString = " ";
1698 xmlSaveNoEmptyTags = 0;
1699
1700 xmlSaveCtxtPtr saveCtxt;
1701 if (!(saveCtxt = xmlSaveToIO(WriteCallback,
1702 CloseCallback,
1703 &context,
1704 NULL,
1705 XML_SAVE_FORMAT)))
1706 throw xml::LogicError(RT_SRC_POS);
1707
1708 long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument);
1709 if (rc == -1)
1710 {
1711 /* look if there was a forwarded exception from the lower level */
1712// if (m->trappedErr.get() != NULL)
1713// m->trappedErr->rethrow();
1714
1715 /* there must be an exception from the Output implementation,
1716 * otherwise the save operation must always succeed. */
1717 throw xml::LogicError(RT_SRC_POS);
1718 }
1719
1720 xmlSaveClose(saveCtxt);
1721}
1722
1723void XmlFileWriter::write(const char *pcszFilename, bool fSafe)
1724{
1725 if (!fSafe)
1726 writeInternal(pcszFilename, fSafe);
1727 else
1728 {
1729 /* Empty string and directory spec must be avoid. */
1730 if (RTPathFilename(pcszFilename) == NULL)
1731 throw xml::LogicError(RT_SRC_POS);
1732
1733 /* Construct both filenames first to ease error handling. */
1734 char szTmpFilename[RTPATH_MAX];
1735 int rc = RTStrCopy(szTmpFilename, sizeof(szTmpFilename) - strlen(s_pszTmpSuff), pcszFilename);
1736 if (RT_FAILURE(rc))
1737 throw EIPRTFailure(rc, "RTStrCopy");
1738 strcat(szTmpFilename, s_pszTmpSuff);
1739
1740 char szPrevFilename[RTPATH_MAX];
1741 rc = RTStrCopy(szPrevFilename, sizeof(szPrevFilename) - strlen(s_pszPrevSuff), pcszFilename);
1742 if (RT_FAILURE(rc))
1743 throw EIPRTFailure(rc, "RTStrCopy");
1744 strcat(szPrevFilename, s_pszPrevSuff);
1745
1746 /* Write the XML document to the temporary file. */
1747 writeInternal(szTmpFilename, fSafe);
1748
1749 /* Make a backup of any existing file (ignore failure). */
1750 uint64_t cbPrevFile;
1751 rc = RTFileQuerySize(pcszFilename, &cbPrevFile);
1752 if (RT_SUCCESS(rc) && cbPrevFile >= 16)
1753 RTFileRename(pcszFilename, szPrevFilename, RTPATHRENAME_FLAGS_REPLACE);
1754
1755 /* Commit the temporary file. Just leave the tmp file behind on failure. */
1756 rc = RTFileRename(szTmpFilename, pcszFilename, RTPATHRENAME_FLAGS_REPLACE);
1757 if (RT_FAILURE(rc))
1758 throw EIPRTFailure(rc, "Failed to replace '%s' with '%s'", pcszFilename, szTmpFilename);
1759
1760 /* Flush the directory changes (required on linux at least). */
1761 RTPathStripFilename(szTmpFilename);
1762 rc = RTDirFlush(szTmpFilename);
1763 AssertMsg(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED, ("%Rrc\n", rc));
1764 }
1765}
1766
1767int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen)
1768{
1769 WriteContext *pContext = static_cast<WriteContext*>(aCtxt);
1770
1771 /* To prevent throwing exceptions while inside libxml2 code, we catch
1772 * them and forward to our level using a couple of variables. */
1773 try
1774 {
1775 return pContext->file.write(aBuf, aLen);
1776 }
1777 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
1778 catch (const RTCError &err) { pContext->setError(err); }
1779 catch (const std::exception &err) { pContext->setError(err); }
1780 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
1781
1782 return -1 /* failure */;
1783}
1784
1785int XmlFileWriter::CloseCallback(void *aCtxt)
1786{
1787 /// @todo to be written
1788 NOREF(aCtxt);
1789
1790 return -1;
1791}
1792
1793/*static*/ const char * const XmlFileWriter::s_pszTmpSuff = "-tmp";
1794/*static*/ const char * const XmlFileWriter::s_pszPrevSuff = "-prev";
1795
1796
1797} // end namespace xml
1798
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