VirtualBox

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

Last change on this file since 96407 was 96407, checked in by vboxsync, 22 months ago

scm copyright and license note update

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 65.2 KB
Line 
1/* $Id: xml.cpp 96407 2022-08-22 17:43:14Z vboxsync $ */
2/** @file
3 * IPRT - XML Manipulation API.
4 */
5
6/*
7 * Copyright (C) 2007-2022 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/dir.h>
42#include <iprt/file.h>
43#include <iprt/err.h>
44#include <iprt/log.h>
45#include <iprt/param.h>
46#include <iprt/path.h>
47#include <iprt/cpp/lock.h>
48#include <iprt/cpp/xml.h>
49
50#include <libxml/tree.h>
51#include <libxml/parser.h>
52#include <libxml/globals.h>
53#include <libxml/xmlIO.h>
54#include <libxml/xmlsave.h>
55#include <libxml/uri.h>
56
57#include <libxml/xmlschemas.h>
58
59#include <map>
60
61
62/*********************************************************************************************************************************
63* Global Variables *
64*********************************************************************************************************************************/
65/**
66 * Global module initialization structure. This is to wrap non-reentrant bits
67 * of libxml, among other things.
68 *
69 * The constructor and destructor of this structure are used to perform global
70 * module initialization and cleanup. There must be only one global variable of
71 * this structure.
72 */
73static class Global
74{
75public:
76
77 Global()
78 {
79 /* Check the parser version. The docs say it will kill the app if
80 * there is a serious version mismatch, but I couldn't find it in the
81 * source code (it only prints the error/warning message to the console) so
82 * let's leave it as is for informational purposes. */
83 LIBXML_TEST_VERSION
84
85 /* Init libxml */
86 xmlInitParser();
87
88 /* Save the default entity resolver before someone has replaced it */
89 sxml.defaultEntityLoader = xmlGetExternalEntityLoader();
90 }
91
92 ~Global()
93 {
94 /* Shutdown libxml */
95 xmlCleanupParser();
96 }
97
98 struct
99 {
100 xmlExternalEntityLoader defaultEntityLoader;
101
102 /** Used to provide some thread safety missing in libxml2 (see e.g.
103 * XmlTreeBackend::read()) */
104 RTCLockMtx lock;
105 }
106 sxml; /* XXX naming this xml will break with gcc-3.3 */
107} gGlobal;
108
109
110
111namespace xml
112{
113
114////////////////////////////////////////////////////////////////////////////////
115//
116// Exceptions
117//
118////////////////////////////////////////////////////////////////////////////////
119
120LogicError::LogicError(RT_SRC_POS_DECL)
121 : RTCError(NULL)
122{
123 char *msg = NULL;
124 RTStrAPrintf(&msg, "In '%s', '%s' at #%d",
125 pszFunction, pszFile, iLine);
126 setWhat(msg);
127 RTStrFree(msg);
128}
129
130XmlError::XmlError(xmlErrorPtr aErr)
131{
132 if (!aErr)
133 throw EInvalidArg(RT_SRC_POS);
134
135 char *msg = Format(aErr);
136 setWhat(msg);
137 RTStrFree(msg);
138}
139
140/**
141 * Composes a single message for the given error. The caller must free the
142 * returned string using RTStrFree() when no more necessary.
143 */
144/* static */ char *XmlError::Format(xmlErrorPtr aErr)
145{
146 const char *msg = aErr->message ? aErr->message : "<none>";
147 size_t msgLen = strlen(msg);
148 /* strip spaces, trailing EOLs and dot-like char */
149 while (msgLen && strchr(" \n.?!", msg [msgLen - 1]))
150 --msgLen;
151
152 char *finalMsg = NULL;
153 RTStrAPrintf(&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d",
154 msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2);
155
156 return finalMsg;
157}
158
159EIPRTFailure::EIPRTFailure(int aRC, const char *pszContextFmt, ...)
160 : RuntimeError(NULL)
161 , mRC(aRC)
162{
163 va_list va;
164 va_start(va, pszContextFmt);
165 m_strMsg.printfVNoThrow(pszContextFmt, va);
166 va_end(va);
167 m_strMsg.appendPrintfNoThrow(" %Rrc (%Rrs)", aRC, aRC);
168}
169
170////////////////////////////////////////////////////////////////////////////////
171//
172// File Class
173//
174//////////////////////////////////////////////////////////////////////////////
175
176struct File::Data
177{
178 Data(RTFILE a_hHandle, const char *a_pszFilename, bool a_fFlushOnClose)
179 : strFileName(a_pszFilename)
180 , handle(a_hHandle)
181 , opened(a_hHandle != NIL_RTFILE)
182 , flushOnClose(a_fFlushOnClose)
183 { }
184
185 ~Data()
186 {
187 if (flushOnClose)
188 {
189 RTFileFlush(handle);
190 if (!strFileName.isEmpty())
191 RTDirFlushParent(strFileName.c_str());
192 }
193
194 if (opened)
195 {
196 RTFileClose(handle);
197 handle = NIL_RTFILE;
198 opened = false;
199 }
200 }
201
202 RTCString strFileName;
203 RTFILE handle;
204 bool opened : 1;
205 bool flushOnClose : 1;
206};
207
208File::File(Mode aMode, const char *aFileName, bool aFlushIt /* = false */)
209 : m(NULL)
210{
211 /* Try open the file first, as the destructor will not be invoked if we throw anything from here. For details see:
212 https://stackoverflow.com/questions/9971782/destructor-not-invoked-when-an-exception-is-thrown-in-the-constructor */
213 uint32_t flags = 0;
214 const char *pcszMode = "???";
215 switch (aMode)
216 {
217 /** @todo change to RTFILE_O_DENY_WRITE where appropriate. */
218 case Mode_Read:
219 flags = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
220 pcszMode = "reading";
221 break;
222 case Mode_WriteCreate: // fail if file exists
223 flags = RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE;
224 pcszMode = "writing";
225 break;
226 case Mode_Overwrite: // overwrite if file exists
227 flags = RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE;
228 pcszMode = "overwriting";
229 break;
230 case Mode_ReadWrite:
231 flags = RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
232 pcszMode = "reading/writing";
233 break;
234 }
235 RTFILE hFile = NIL_RTFILE;
236 int vrc = RTFileOpen(&hFile, aFileName, flags);
237 if (RT_FAILURE(vrc))
238 throw EIPRTFailure(vrc, "Runtime error opening '%s' for %s", aFileName, pcszMode);
239
240 /* Now we can create the data and stuff: */
241 try
242 {
243 m = new Data(hFile, aFileName, aFlushIt && (flags & RTFILE_O_ACCESS_MASK) != RTFILE_O_READ);
244 }
245 catch (std::bad_alloc &)
246 {
247 RTFileClose(hFile);
248 throw;
249 }
250}
251
252File::File(RTFILE aHandle, const char *aFileName /* = NULL */, bool aFlushIt /* = false */)
253 : m(NULL)
254{
255 if (aHandle == NIL_RTFILE)
256 throw EInvalidArg(RT_SRC_POS);
257
258 m = new Data(aHandle, aFileName, aFlushIt);
259
260 setPos(0);
261}
262
263File::~File()
264{
265 if (m)
266 {
267 delete m;
268 m = NULL;
269 }
270}
271
272const char *File::uri() const
273{
274 return m->strFileName.c_str();
275}
276
277uint64_t File::pos() const
278{
279 uint64_t p = 0;
280 int vrc = RTFileSeek(m->handle, 0, RTFILE_SEEK_CURRENT, &p);
281 if (RT_SUCCESS(vrc))
282 return p;
283
284 throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
285}
286
287void File::setPos(uint64_t aPos)
288{
289 uint64_t p = 0;
290 unsigned method = RTFILE_SEEK_BEGIN;
291 int vrc = VINF_SUCCESS;
292
293 /* check if we overflow int64_t and move to INT64_MAX first */
294 if ((int64_t)aPos < 0)
295 {
296 vrc = RTFileSeek(m->handle, INT64_MAX, method, &p);
297 aPos -= (uint64_t)INT64_MAX;
298 method = RTFILE_SEEK_CURRENT;
299 }
300 /* seek the rest */
301 if (RT_SUCCESS(vrc))
302 vrc = RTFileSeek(m->handle, (int64_t) aPos, method, &p);
303 if (RT_SUCCESS(vrc))
304 return;
305
306 throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
307}
308
309int File::read(char *aBuf, int aLen)
310{
311 size_t len = aLen;
312 int vrc = RTFileRead(m->handle, aBuf, len, &len);
313 if (RT_SUCCESS(vrc))
314 return (int)len;
315
316 throw EIPRTFailure(vrc, "Runtime error reading from file '%s'", m->strFileName.c_str());
317}
318
319int File::write(const char *aBuf, int aLen)
320{
321 size_t len = aLen;
322 int vrc = RTFileWrite(m->handle, aBuf, len, &len);
323 if (RT_SUCCESS(vrc))
324 return (int)len;
325
326 throw EIPRTFailure(vrc, "Runtime error writing to file '%s'", m->strFileName.c_str());
327}
328
329void File::truncate()
330{
331 int vrc = RTFileSetSize(m->handle, pos());
332 if (RT_SUCCESS(vrc))
333 return;
334
335 throw EIPRTFailure(vrc, "Runtime error truncating file '%s'", m->strFileName.c_str());
336}
337
338////////////////////////////////////////////////////////////////////////////////
339//
340// MemoryBuf Class
341//
342//////////////////////////////////////////////////////////////////////////////
343
344struct MemoryBuf::Data
345{
346 Data()
347 : buf(NULL), len(0), uri(NULL), pos(0) {}
348
349 const char *buf;
350 size_t len;
351 char *uri;
352
353 size_t pos;
354};
355
356MemoryBuf::MemoryBuf(const char *aBuf, size_t aLen, const char *aURI /* = NULL */)
357 : m(new Data())
358{
359 if (aBuf == NULL)
360 throw EInvalidArg(RT_SRC_POS);
361
362 m->buf = aBuf;
363 m->len = aLen;
364 m->uri = RTStrDup(aURI);
365}
366
367MemoryBuf::~MemoryBuf()
368{
369 RTStrFree(m->uri);
370}
371
372const char *MemoryBuf::uri() const
373{
374 return m->uri;
375}
376
377uint64_t MemoryBuf::pos() const
378{
379 return m->pos;
380}
381
382void MemoryBuf::setPos(uint64_t aPos)
383{
384 size_t off = (size_t)aPos;
385 if ((uint64_t) off != aPos)
386 throw EInvalidArg();
387
388 if (off > m->len)
389 throw EInvalidArg();
390
391 m->pos = off;
392}
393
394int MemoryBuf::read(char *aBuf, int aLen)
395{
396 if (m->pos >= m->len)
397 return 0 /* nothing to read */;
398
399 size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos;
400 memcpy(aBuf, m->buf + m->pos, len);
401 m->pos += len;
402
403 return (int)len;
404}
405
406////////////////////////////////////////////////////////////////////////////////
407//
408// GlobalLock class
409//
410////////////////////////////////////////////////////////////////////////////////
411
412struct GlobalLock::Data
413{
414 PFNEXTERNALENTITYLOADER pfnOldLoader;
415 RTCLock lock;
416
417 Data()
418 : pfnOldLoader(NULL)
419 , lock(gGlobal.sxml.lock)
420 {
421 }
422};
423
424GlobalLock::GlobalLock()
425 : m(new Data())
426{
427}
428
429GlobalLock::~GlobalLock()
430{
431 if (m->pfnOldLoader)
432 xmlSetExternalEntityLoader(m->pfnOldLoader);
433 delete m;
434 m = NULL;
435}
436
437void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pfnLoader)
438{
439 m->pfnOldLoader = (PFNEXTERNALENTITYLOADER)xmlGetExternalEntityLoader();
440 xmlSetExternalEntityLoader(pfnLoader);
441}
442
443// static
444xmlParserInput* GlobalLock::callDefaultLoader(const char *aURI,
445 const char *aID,
446 xmlParserCtxt *aCtxt)
447{
448 return gGlobal.sxml.defaultEntityLoader(aURI, aID, aCtxt);
449}
450
451
452
453////////////////////////////////////////////////////////////////////////////////
454//
455// Node class
456//
457////////////////////////////////////////////////////////////////////////////////
458
459Node::Node(EnumType type,
460 Node *pParent,
461 PRTLISTANCHOR pListAnchor,
462 xmlNode *pLibNode,
463 xmlAttr *pLibAttr)
464 : m_Type(type)
465 , m_pParent(pParent)
466 , m_pLibNode(pLibNode)
467 , m_pLibAttr(pLibAttr)
468 , m_pcszNamespacePrefix(NULL)
469 , m_pcszNamespaceHref(NULL)
470 , m_pcszName(NULL)
471 , m_pParentListAnchor(pListAnchor)
472{
473 RTListInit(&m_listEntry);
474}
475
476Node::~Node()
477{
478}
479
480/**
481 * Returns the name of the node, which is either the element name or
482 * the attribute name. For other node types it probably returns NULL.
483 * @return
484 */
485const char *Node::getName() const
486{
487 return m_pcszName;
488}
489
490/**
491 * Returns the name of the node, which is either the element name or
492 * the attribute name. For other node types it probably returns NULL.
493 * @return
494 */
495const char *Node::getPrefix() const
496{
497 return m_pcszNamespacePrefix;
498}
499
500/**
501 * Returns the XML namespace URI, which is the attribute name. For other node types it probably
502 * returns NULL.
503 * @return
504 */
505const char *Node::getNamespaceURI() const
506{
507 return m_pcszNamespaceHref;
508}
509
510/**
511 * Variant of nameEquals that checks the namespace as well.
512 * @param pcszNamespace
513 * @param pcsz
514 * @return
515 */
516bool Node::nameEqualsNS(const char *pcszNamespace, const char *pcsz) const
517{
518 if (m_pcszName == pcsz)
519 return true;
520 if (m_pcszName == NULL)
521 return false;
522 if (pcsz == NULL)
523 return false;
524 if (strcmp(m_pcszName, pcsz))
525 return false;
526
527 // name matches: then check namespaces as well
528 if (!pcszNamespace)
529 return true;
530 // caller wants namespace:
531 if (!m_pcszNamespacePrefix)
532 // but node has no namespace:
533 return false;
534 return !strcmp(m_pcszNamespacePrefix, pcszNamespace);
535}
536
537/**
538 * Variant of nameEquals that checks the namespace as well.
539 *
540 * @returns true if equal, false if not.
541 * @param pcsz The element name.
542 * @param cchMax The maximum number of character from @a pcsz to
543 * match.
544 * @param pcszNamespace The name space prefix or NULL (default).
545 */
546bool Node::nameEqualsN(const char *pcsz, size_t cchMax, const char *pcszNamespace /* = NULL*/) const
547{
548 /* Match the name. */
549 if (!m_pcszName)
550 return false;
551 if (!pcsz || cchMax == 0)
552 return false;
553 if (strncmp(m_pcszName, pcsz, cchMax))
554 return false;
555 if (strlen(m_pcszName) > cchMax)
556 return false;
557
558 /* Match name space. */
559 if (!pcszNamespace)
560 return true; /* NULL, anything goes. */
561 if (!m_pcszNamespacePrefix)
562 return false; /* Element has no namespace. */
563 return !strcmp(m_pcszNamespacePrefix, pcszNamespace);
564}
565
566/**
567 * Returns the value of a node. If this node is an attribute, returns
568 * the attribute value; if this node is an element, then this returns
569 * the element text content.
570 * @return
571 */
572const char *Node::getValue() const
573{
574 if ( m_pLibAttr
575 && m_pLibAttr->children
576 )
577 // libxml hides attribute values in another node created as a
578 // single child of the attribute node, and it's in the content field
579 return (const char *)m_pLibAttr->children->content;
580
581 if ( m_pLibNode
582 && m_pLibNode->children)
583 return (const char *)m_pLibNode->children->content;
584
585 return NULL;
586}
587
588/**
589 * Returns the value of a node. If this node is an attribute, returns
590 * the attribute value; if this node is an element, then this returns
591 * the element text content.
592 * @return
593 * @param cchValueLimit If the length of the returned value exceeds this
594 * limit a EIPRTFailure exception will be thrown.
595 */
596const char *Node::getValueN(size_t cchValueLimit) const
597{
598 if ( m_pLibAttr
599 && m_pLibAttr->children
600 )
601 {
602 // libxml hides attribute values in another node created as a
603 // single child of the attribute node, and it's in the content field
604 AssertStmt(strlen((const char *)m_pLibAttr->children->content) <= cchValueLimit, throw EIPRTFailure(VERR_BUFFER_OVERFLOW, "Attribute '%s' exceeds limit of %zu bytes", m_pcszName, cchValueLimit));
605 return (const char *)m_pLibAttr->children->content;
606 }
607
608 if ( m_pLibNode
609 && m_pLibNode->children)
610 {
611 AssertStmt(strlen((const char *)m_pLibNode->children->content) <= cchValueLimit, throw EIPRTFailure(VERR_BUFFER_OVERFLOW, "Element '%s' exceeds limit of %zu bytes", m_pcszName, cchValueLimit));
612 return (const char *)m_pLibNode->children->content;
613 }
614
615 return NULL;
616}
617
618/**
619 * Copies the value of a node into the given integer variable.
620 * Returns TRUE only if a value was found and was actually an
621 * integer of the given type.
622 * @return
623 */
624bool Node::copyValue(int32_t &i) const
625{
626 const char *pcsz;
627 if ( ((pcsz = getValue()))
628 && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i))
629 )
630 return true;
631
632 return false;
633}
634
635/**
636 * Copies the value of a node into the given integer variable.
637 * Returns TRUE only if a value was found and was actually an
638 * integer of the given type.
639 * @return
640 */
641bool Node::copyValue(uint32_t &i) const
642{
643 const char *pcsz;
644 if ( ((pcsz = getValue()))
645 && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i))
646 )
647 return true;
648
649 return false;
650}
651
652/**
653 * Copies the value of a node into the given integer variable.
654 * Returns TRUE only if a value was found and was actually an
655 * integer of the given type.
656 * @return
657 */
658bool Node::copyValue(int64_t &i) const
659{
660 const char *pcsz;
661 if ( ((pcsz = getValue()))
662 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i))
663 )
664 return true;
665
666 return false;
667}
668
669/**
670 * Copies the value of a node into the given integer variable.
671 * Returns TRUE only if a value was found and was actually an
672 * integer of the given type.
673 * @return
674 */
675bool Node::copyValue(uint64_t &i) const
676{
677 const char *pcsz;
678 if ( ((pcsz = getValue()))
679 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i))
680 )
681 return true;
682
683 return false;
684}
685
686/**
687 * Returns the line number of the current node in the source XML file.
688 * Useful for error messages.
689 * @return
690 */
691int Node::getLineNumber() const
692{
693 if (m_pLibAttr)
694 return m_pParent->m_pLibNode->line;
695
696 return m_pLibNode->line;
697}
698
699/**
700 * Private element constructor.
701 *
702 * @param pElmRoot Pointer to the root element.
703 * @param pParent Pointer to the parent element (always an ElementNode,
704 * despite the type). NULL for the root node.
705 * @param pListAnchor Pointer to the m_children member of the parent. NULL
706 * for the root node.
707 * @param pLibNode Pointer to the libxml2 node structure.
708 */
709ElementNode::ElementNode(const ElementNode *pElmRoot,
710 Node *pParent,
711 PRTLISTANCHOR pListAnchor,
712 xmlNode *pLibNode)
713 : Node(IsElement,
714 pParent,
715 pListAnchor,
716 pLibNode,
717 NULL)
718{
719 m_pElmRoot = pElmRoot ? pElmRoot : this; // If NULL is passed, then this is the root element.
720 m_pcszName = (const char *)pLibNode->name;
721
722 if (pLibNode->ns)
723 {
724 m_pcszNamespacePrefix = (const char *)m_pLibNode->ns->prefix;
725 m_pcszNamespaceHref = (const char *)m_pLibNode->ns->href;
726 }
727
728 RTListInit(&m_children);
729 RTListInit(&m_attributes);
730}
731
732ElementNode::~ElementNode()
733{
734 Node *pCur, *pNext;
735 RTListForEachSafeCpp(&m_children, pCur, pNext, Node, m_listEntry)
736 {
737 delete pCur;
738 }
739 RTListInit(&m_children);
740
741 RTListForEachSafeCpp(&m_attributes, pCur, pNext, Node, m_listEntry)
742 {
743 delete pCur;
744 }
745 RTListInit(&m_attributes);
746}
747
748
749/**
750 * Gets the next tree element in a full tree enumeration.
751 *
752 * @returns Pointer to the next element in the tree, NULL if we're done.
753 * @param pElmRoot The root of the tree we're enumerating. NULL if
754 * it's the entire tree.
755 */
756ElementNode const *ElementNode::getNextTreeElement(ElementNode const *pElmRoot /*= NULL */) const
757{
758 /*
759 * Consider children first.
760 */
761 ElementNode const *pChild = getFirstChildElement();
762 if (pChild)
763 return pChild;
764
765 /*
766 * Then siblings, aunts and uncles.
767 */
768 ElementNode const *pCur = this;
769 do
770 {
771 ElementNode const *pSibling = pCur->getNextSibilingElement();
772 if (pSibling != NULL)
773 return pSibling;
774
775 pCur = static_cast<const xml::ElementNode *>(pCur->m_pParent);
776 Assert(pCur || pCur == pElmRoot);
777 } while (pCur != pElmRoot);
778
779 return NULL;
780}
781
782
783/**
784 * Private implementation.
785 *
786 * @param pElmRoot The root element.
787 */
788/*static*/ void ElementNode::buildChildren(ElementNode *pElmRoot) // protected
789{
790 for (ElementNode *pCur = pElmRoot; pCur; pCur = pCur->getNextTreeElement(pElmRoot))
791 {
792 /*
793 * Go thru this element's attributes creating AttributeNodes for them.
794 */
795 for (xmlAttr *pLibAttr = pCur->m_pLibNode->properties; pLibAttr; pLibAttr = pLibAttr->next)
796 {
797 AttributeNode *pNew = new AttributeNode(pElmRoot, pCur, &pCur->m_attributes, pLibAttr);
798 RTListAppend(&pCur->m_attributes, &pNew->m_listEntry);
799 }
800
801 /*
802 * Go thru this element's child elements (element and text nodes).
803 */
804 for (xmlNodePtr pLibNode = pCur->m_pLibNode->children; pLibNode; pLibNode = pLibNode->next)
805 {
806 Node *pNew;
807 if (pLibNode->type == XML_ELEMENT_NODE)
808 pNew = new ElementNode(pElmRoot, pCur, &pCur->m_children, pLibNode);
809 else if (pLibNode->type == XML_TEXT_NODE)
810 pNew = new ContentNode(pCur, &pCur->m_children, pLibNode);
811 else
812 continue;
813 RTListAppend(&pCur->m_children, &pNew->m_listEntry);
814 }
815 }
816}
817
818
819/**
820 * Builds a list of direct child elements of the current element that
821 * match the given string; if pcszMatch is NULL, all direct child
822 * elements are returned.
823 * @param children out: list of nodes to which children will be appended.
824 * @param pcszMatch in: match string, or NULL to return all children.
825 * @return Number of items appended to the list (0 if none).
826 */
827int ElementNode::getChildElements(ElementNodesList &children,
828 const char *pcszMatch /*= NULL*/)
829 const
830{
831 int i = 0;
832 Node *p;
833 RTListForEachCpp(&m_children, p, Node, m_listEntry)
834 {
835 // export this child node if ...
836 if (p->isElement())
837 if ( !pcszMatch // ... the caller wants all nodes or ...
838 || !strcmp(pcszMatch, p->getName()) // ... the element name matches.
839 )
840 {
841 children.push_back(static_cast<ElementNode *>(p));
842 ++i;
843 }
844 }
845 return i;
846}
847
848/**
849 * Returns the first child element whose name matches pcszMatch.
850 *
851 * @param pcszNamespace Namespace prefix (e.g. "vbox") or NULL to match any namespace.
852 * @param pcszMatch Element name to match.
853 * @return
854 */
855const ElementNode *ElementNode::findChildElementNS(const char *pcszNamespace, const char *pcszMatch) const
856{
857 Node *p;
858 RTListForEachCpp(&m_children, p, Node, m_listEntry)
859 {
860 if (p->isElement())
861 {
862 ElementNode *pelm = static_cast<ElementNode*>(p);
863 if (pelm->nameEqualsNS(pcszNamespace, pcszMatch))
864 return pelm;
865 }
866 }
867 return NULL;
868}
869
870/**
871 * Returns the first child element whose "id" attribute matches pcszId.
872 * @param pcszId identifier to look for.
873 * @return child element or NULL if not found.
874 */
875const ElementNode *ElementNode::findChildElementFromId(const char *pcszId) const
876{
877 const Node *p;
878 RTListForEachCpp(&m_children, p, Node, m_listEntry)
879 {
880 if (p->isElement())
881 {
882 const ElementNode *pElm = static_cast<const ElementNode *>(p);
883 const AttributeNode *pAttr = pElm->findAttribute("id");
884 if (pAttr && !strcmp(pAttr->getValue(), pcszId))
885 return pElm;
886 }
887 }
888 return NULL;
889}
890
891
892const ElementNode *ElementNode::findChildElementP(const char *pcszPath, const char *pcszNamespace /*= NULL*/) const
893{
894 size_t cchThis = strchr(pcszPath, '/') - pcszPath;
895 if (cchThis == (size_t)((const char *)0 - pcszPath))
896 return findChildElementNS(pcszNamespace, pcszPath);
897
898 /** @todo Can be done without recursion as we have both sibling lists and parent
899 * pointers in this variant. */
900 const Node *p;
901 RTListForEachCpp(&m_children, p, Node, m_listEntry)
902 {
903 if (p->isElement())
904 {
905 const ElementNode *pElm = static_cast<const ElementNode *>(p);
906 if (pElm->nameEqualsN(pcszPath, cchThis, pcszNamespace))
907 {
908 pElm = findChildElementP(pcszPath + cchThis, pcszNamespace);
909 if (pElm)
910 return pElm;
911 }
912 }
913 }
914
915 return NULL;
916}
917
918const ElementNode *ElementNode::getFirstChildElement() const
919{
920 const Node *p;
921 RTListForEachCpp(&m_children, p, Node, m_listEntry)
922 {
923 if (p->isElement())
924 return static_cast<const ElementNode *>(p);
925 }
926 return NULL;
927}
928
929const ElementNode *ElementNode::getLastChildElement() const
930{
931 const Node *p;
932 RTListForEachReverseCpp(&m_children, p, Node, m_listEntry)
933 {
934 if (p->isElement())
935 return static_cast<const ElementNode *>(p);
936 }
937 return NULL;
938}
939
940const ElementNode *ElementNode::getPrevSibilingElement() const
941{
942 if (!m_pParent)
943 return NULL;
944 const Node *pSibling = this;
945 for (;;)
946 {
947 pSibling = RTListGetPrevCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
948 if (!pSibling)
949 return NULL;
950 if (pSibling->isElement())
951 return static_cast<const ElementNode *>(pSibling);
952 }
953}
954
955const ElementNode *ElementNode::getNextSibilingElement() const
956{
957 if (!m_pParent)
958 return NULL;
959 const Node *pSibling = this;
960 for (;;)
961 {
962 pSibling = RTListGetNextCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
963 if (!pSibling)
964 return NULL;
965 if (pSibling->isElement())
966 return static_cast<const ElementNode *>(pSibling);
967 }
968}
969
970const ElementNode *ElementNode::findPrevSibilingElement(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
971{
972 if (!m_pParent)
973 return NULL;
974 const Node *pSibling = this;
975 for (;;)
976 {
977 pSibling = RTListGetPrevCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
978 if (!pSibling)
979 return NULL;
980 if (pSibling->isElement())
981 {
982 const ElementNode *pElem = static_cast<const ElementNode *>(pSibling);
983 if (pElem->nameEqualsNS(pcszNamespace, pcszMatch))
984 return pElem;
985 }
986 }
987}
988
989const ElementNode *ElementNode::findNextSibilingElement(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
990{
991 if (!m_pParent)
992 return NULL;
993 const Node *pSibling = this;
994 for (;;)
995 {
996 pSibling = RTListGetNextCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
997 if (!pSibling)
998 return NULL;
999 if (pSibling->isElement())
1000 {
1001 const ElementNode *pElem = static_cast<const ElementNode *>(pSibling);
1002 if (pElem->nameEqualsNS(pcszNamespace, pcszMatch))
1003 return pElem;
1004 }
1005 }
1006}
1007
1008
1009/**
1010 * Looks up the given attribute node in this element's attribute map.
1011 *
1012 * @param pcszMatch The name of the attribute to find.
1013 * @param pcszNamespace The attribute name space prefix or NULL.
1014 */
1015const AttributeNode *ElementNode::findAttribute(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
1016{
1017 AttributeNode *p;
1018 RTListForEachCpp(&m_attributes, p, AttributeNode, m_listEntry)
1019 {
1020 if (p->nameEqualsNS(pcszNamespace, pcszMatch))
1021 return p;
1022 }
1023 return NULL;
1024}
1025
1026/**
1027 * Convenience method which attempts to find the attribute with the given
1028 * name and returns its value as a string.
1029 *
1030 * @param pcszMatch Name of attribute to find.
1031 * @param ppcsz Where to return the attribute.
1032 * @param pcszNamespace The attribute name space prefix or NULL.
1033 * @returns Boolean success indicator.
1034 */
1035bool ElementNode::getAttributeValue(const char *pcszMatch, const char **ppcsz, const char *pcszNamespace /*= NULL*/) const
1036{
1037 const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
1038 if (pAttr)
1039 {
1040 *ppcsz = pAttr->getValue();
1041 return true;
1042 }
1043 return false;
1044}
1045
1046/**
1047 * Convenience method which attempts to find the attribute with the given
1048 * name and returns its value as a string.
1049 *
1050 * @param pcszMatch Name of attribute to find.
1051 * @param pStr Pointer to the string object that should receive the
1052 * attribute value.
1053 * @param pcszNamespace The attribute name space prefix or NULL.
1054 * @returns Boolean success indicator.
1055 *
1056 * @throws Whatever the string class may throw on assignment.
1057 */
1058bool ElementNode::getAttributeValue(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace /*= NULL*/) const
1059{
1060 const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
1061 if (pAttr)
1062 {
1063 *pStr = pAttr->getValue();
1064 return true;
1065 }
1066
1067 return false;
1068}
1069
1070/**
1071 * Like getAttributeValue (ministring variant), but makes sure that all backslashes
1072 * are converted to forward slashes.
1073 *
1074 * @param pcszMatch Name of attribute to find.
1075 * @param pStr Pointer to the string object that should
1076 * receive the attribute path value.
1077 * @param pcszNamespace The attribute name space prefix or NULL.
1078 * @returns Boolean success indicator.
1079 */
1080bool ElementNode::getAttributeValuePath(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace /*= NULL*/) const
1081{
1082 if (getAttributeValue(pcszMatch, pStr, pcszNamespace))
1083 {
1084 pStr->findReplace('\\', '/');
1085 return true;
1086 }
1087
1088 return false;
1089}
1090
1091/**
1092 * Convenience method which attempts to find the attribute with the given
1093 * name and returns its value as a signed 32-bit integer.
1094 *
1095 * @param pcszMatch Name of attribute to find.
1096 * @param piValue Where to return the value.
1097 * @param pcszNamespace The attribute name space prefix or NULL.
1098 * @returns Boolean success indicator.
1099 */
1100bool ElementNode::getAttributeValue(const char *pcszMatch, int32_t *piValue, const char *pcszNamespace /*= NULL*/) const
1101{
1102 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1103 if (pcsz)
1104 {
1105 int rc = RTStrToInt32Ex(pcsz, NULL, 0, piValue);
1106 if (rc == VINF_SUCCESS)
1107 return true;
1108 }
1109 return false;
1110}
1111
1112/**
1113 * Convenience method which attempts to find the attribute with the given
1114 * name and returns its value as an unsigned 32-bit integer.
1115 *
1116 * @param pcszMatch Name of attribute to find.
1117 * @param puValue Where to return the value.
1118 * @param pcszNamespace The attribute name space prefix or NULL.
1119 * @returns Boolean success indicator.
1120 */
1121bool ElementNode::getAttributeValue(const char *pcszMatch, uint32_t *puValue, const char *pcszNamespace /*= NULL*/) const
1122{
1123 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1124 if (pcsz)
1125 {
1126 int rc = RTStrToUInt32Ex(pcsz, NULL, 0, puValue);
1127 if (rc == VINF_SUCCESS)
1128 return true;
1129 }
1130 return false;
1131}
1132
1133/**
1134 * Convenience method which attempts to find the attribute with the given
1135 * name and returns its value as a signed 64-bit integer.
1136 *
1137 * @param pcszMatch Name of attribute to find.
1138 * @param piValue Where to return the value.
1139 * @param pcszNamespace The attribute name space prefix or NULL.
1140 * @returns Boolean success indicator.
1141 */
1142bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t *piValue, const char *pcszNamespace /*= NULL*/) const
1143{
1144 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1145 if (pcsz)
1146 {
1147 int rc = RTStrToInt64Ex(pcsz, NULL, 0, piValue);
1148 if (rc == VINF_SUCCESS)
1149 return true;
1150 }
1151 return false;
1152}
1153
1154/**
1155 * Convenience method which attempts to find the attribute with the given
1156 * name and returns its value as an unsigned 64-bit integer.
1157 *
1158 * @param pcszMatch Name of attribute to find.
1159 * @param puValue Where to return the value.
1160 * @param pcszNamespace The attribute name space prefix or NULL.
1161 * @returns Boolean success indicator.
1162 */
1163bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t *puValue, const char *pcszNamespace /*= NULL*/) const
1164{
1165 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1166 if (pcsz)
1167 {
1168 int rc = RTStrToUInt64Ex(pcsz, NULL, 0, puValue);
1169 if (rc == VINF_SUCCESS)
1170 return true;
1171 }
1172 return false;
1173}
1174
1175/**
1176 * Convenience method which attempts to find the attribute with the given
1177 * name and returns its value as a boolean. This accepts "true", "false",
1178 * "yes", "no", "1" or "0" as valid values.
1179 *
1180 * @param pcszMatch Name of attribute to find.
1181 * @param pfValue Where to return the value.
1182 * @param pcszNamespace The attribute name space prefix or NULL.
1183 * @returns Boolean success indicator.
1184 */
1185bool ElementNode::getAttributeValue(const char *pcszMatch, bool *pfValue, const char *pcszNamespace /*= NULL*/) const
1186{
1187 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1188 if (pcsz)
1189 {
1190 if ( !strcmp(pcsz, "true")
1191 || !strcmp(pcsz, "yes")
1192 || !strcmp(pcsz, "1")
1193 )
1194 {
1195 *pfValue = true;
1196 return true;
1197 }
1198 if ( !strcmp(pcsz, "false")
1199 || !strcmp(pcsz, "no")
1200 || !strcmp(pcsz, "0")
1201 )
1202 {
1203 *pfValue = false;
1204 return true;
1205 }
1206 }
1207
1208 return false;
1209}
1210
1211/**
1212 * Convenience method which attempts to find the attribute with the given
1213 * name and returns its value as a string.
1214 *
1215 * @param pcszMatch Name of attribute to find.
1216 * @param ppcsz Where to return the attribute.
1217 * @param cchValueLimit If the length of the returned value exceeds this
1218 * limit a EIPRTFailure exception will be thrown.
1219 * @param pcszNamespace The attribute name space prefix or NULL.
1220 * @returns Boolean success indicator.
1221 */
1222bool ElementNode::getAttributeValueN(const char *pcszMatch, const char **ppcsz, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const
1223{
1224 const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
1225 if (pAttr)
1226 {
1227 *ppcsz = pAttr->getValueN(cchValueLimit);
1228 return true;
1229 }
1230 return false;
1231}
1232
1233/**
1234 * Convenience method which attempts to find the attribute with the given
1235 * name and returns its value as a string.
1236 *
1237 * @param pcszMatch Name of attribute to find.
1238 * @param pStr Pointer to the string object that should receive the
1239 * attribute value.
1240 * @param cchValueLimit If the length of the returned value exceeds this
1241 * limit a EIPRTFailure exception will be thrown.
1242 * @param pcszNamespace The attribute name space prefix or NULL.
1243 * @returns Boolean success indicator.
1244 *
1245 * @throws Whatever the string class may throw on assignment.
1246 */
1247bool ElementNode::getAttributeValueN(const char *pcszMatch, RTCString *pStr, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const
1248{
1249 const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
1250 if (pAttr)
1251 {
1252 *pStr = pAttr->getValueN(cchValueLimit);
1253 return true;
1254 }
1255
1256 return false;
1257}
1258
1259/**
1260 * Like getAttributeValue (ministring variant), but makes sure that all backslashes
1261 * are converted to forward slashes.
1262 *
1263 * @param pcszMatch Name of attribute to find.
1264 * @param pStr Pointer to the string object that should
1265 * receive the attribute path value.
1266 * @param cchValueLimit If the length of the returned value exceeds this
1267 * limit a EIPRTFailure exception will be thrown.
1268 * @param pcszNamespace The attribute name space prefix or NULL.
1269 * @returns Boolean success indicator.
1270 */
1271bool ElementNode::getAttributeValuePathN(const char *pcszMatch, RTCString *pStr, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const
1272{
1273 if (getAttributeValueN(pcszMatch, pStr, cchValueLimit, pcszNamespace))
1274 {
1275 pStr->findReplace('\\', '/');
1276 return true;
1277 }
1278
1279 return false;
1280}
1281
1282
1283bool ElementNode::getElementValue(int32_t *piValue) const
1284{
1285 const char *pszValue = getValue();
1286 if (pszValue)
1287 {
1288 int rc = RTStrToInt32Ex(pszValue, NULL, 0, piValue);
1289 if (rc == VINF_SUCCESS)
1290 return true;
1291 }
1292 return false;
1293}
1294
1295bool ElementNode::getElementValue(uint32_t *puValue) const
1296{
1297 const char *pszValue = getValue();
1298 if (pszValue)
1299 {
1300 int rc = RTStrToUInt32Ex(pszValue, NULL, 0, puValue);
1301 if (rc == VINF_SUCCESS)
1302 return true;
1303 }
1304 return false;
1305}
1306
1307bool ElementNode::getElementValue(int64_t *piValue) const
1308{
1309 const char *pszValue = getValue();
1310 if (pszValue)
1311 {
1312 int rc = RTStrToInt64Ex(pszValue, NULL, 0, piValue);
1313 if (rc == VINF_SUCCESS)
1314 return true;
1315 }
1316 return false;
1317}
1318
1319bool ElementNode::getElementValue(uint64_t *puValue) const
1320{
1321 const char *pszValue = getValue();
1322 if (pszValue)
1323 {
1324 int rc = RTStrToUInt64Ex(pszValue, NULL, 0, puValue);
1325 if (rc == VINF_SUCCESS)
1326 return true;
1327 }
1328 return false;
1329}
1330
1331bool ElementNode::getElementValue(bool *pfValue) const
1332{
1333 const char *pszValue = getValue();
1334 if (pszValue)
1335 {
1336 if ( !strcmp(pszValue, "true")
1337 || !strcmp(pszValue, "yes")
1338 || !strcmp(pszValue, "1")
1339 )
1340 {
1341 *pfValue = true;
1342 return true;
1343 }
1344 if ( !strcmp(pszValue, "false")
1345 || !strcmp(pszValue, "no")
1346 || !strcmp(pszValue, "0")
1347 )
1348 {
1349 *pfValue = true;
1350 return true;
1351 }
1352 }
1353 return false;
1354}
1355
1356
1357/**
1358 * Creates a new child element node and appends it to the list
1359 * of children in "this".
1360 *
1361 * @param pcszElementName
1362 * @return
1363 */
1364ElementNode *ElementNode::createChild(const char *pcszElementName)
1365{
1366 // we must be an element, not an attribute
1367 if (!m_pLibNode)
1368 throw ENodeIsNotElement(RT_SRC_POS);
1369
1370 // libxml side: create new node
1371 xmlNode *pLibNode;
1372 if (!(pLibNode = xmlNewNode(NULL, // namespace
1373 (const xmlChar*)pcszElementName)))
1374 throw std::bad_alloc();
1375 xmlAddChild(m_pLibNode, pLibNode);
1376
1377 // now wrap this in C++
1378 ElementNode *p = new ElementNode(m_pElmRoot, this, &m_children, pLibNode);
1379 RTListAppend(&m_children, &p->m_listEntry);
1380
1381 return p;
1382}
1383
1384
1385/**
1386 * Creates a content node and appends it to the list of children
1387 * in "this".
1388 *
1389 * @param pcszContent
1390 * @return
1391 */
1392ContentNode *ElementNode::addContent(const char *pcszContent)
1393{
1394 // libxml side: create new node
1395 xmlNode *pLibNode = xmlNewText((const xmlChar*)pcszContent);
1396 if (!pLibNode)
1397 throw std::bad_alloc();
1398 xmlAddChild(m_pLibNode, pLibNode);
1399
1400 // now wrap this in C++
1401 ContentNode *p = new ContentNode(this, &m_children, pLibNode);
1402 RTListAppend(&m_children, &p->m_listEntry);
1403
1404 return p;
1405}
1406
1407/**
1408 * Changes the contents of node and appends it to the list of
1409 * children
1410 *
1411 * @param pcszContent
1412 * @return
1413 */
1414ContentNode *ElementNode::setContent(const char *pcszContent)
1415{
1416// 1. Update content
1417 xmlNodeSetContent(m_pLibNode, (const xmlChar*)pcszContent);
1418
1419// 2. Remove Content node from the list
1420 /* Check that the order is right. */
1421 xml::Node * pNode;
1422 RTListForEachCpp(&m_children, pNode, xml::Node, m_listEntry)
1423 {
1424 bool fLast = RTListNodeIsLast(&m_children, &pNode->m_listEntry);
1425
1426 if (pNode->isContent())
1427 {
1428 RTListNodeRemove(&pNode->m_listEntry);
1429 }
1430
1431 if (fLast)
1432 break;
1433 }
1434
1435// 3. Create a new node and append to the list
1436 // now wrap this in C++
1437 ContentNode *pCNode = new ContentNode(this, &m_children, m_pLibNode);
1438 RTListAppend(&m_children, &pCNode->m_listEntry);
1439
1440 return pCNode;
1441}
1442
1443/**
1444 * Sets the given attribute; overloaded version for const char *.
1445 *
1446 * If an attribute with the given name exists, it is overwritten,
1447 * otherwise a new attribute is created. Returns the attribute node
1448 * that was either created or changed.
1449 *
1450 * @param pcszName The attribute name.
1451 * @param pcszValue The attribute value.
1452 * @return Pointer to the attribute node that was created or modified.
1453 */
1454AttributeNode *ElementNode::setAttribute(const char *pcszName, const char *pcszValue)
1455{
1456 /*
1457 * Do we already have an attribute and should we just update it?
1458 */
1459 AttributeNode *pAttr;
1460 RTListForEachCpp(&m_attributes, pAttr, AttributeNode, m_listEntry)
1461 {
1462 if (pAttr->nameEquals(pcszName))
1463 {
1464 /* Overwrite existing libxml attribute node ... */
1465 xmlAttrPtr pLibAttr = xmlSetProp(m_pLibNode, (xmlChar *)pcszName, (xmlChar *)pcszValue);
1466
1467 /* ... and update our C++ wrapper in case the attrib pointer changed. */
1468 pAttr->m_pLibAttr = pLibAttr;
1469 return pAttr;
1470 }
1471 }
1472
1473 /*
1474 * No existing attribute, create a new one.
1475 */
1476 /* libxml side: xmlNewProp creates an attribute. */
1477 xmlAttr *pLibAttr = xmlNewProp(m_pLibNode, (xmlChar *)pcszName, (xmlChar *)pcszValue);
1478
1479 /* C++ side: Create an attribute node around it. */
1480 pAttr = new AttributeNode(m_pElmRoot, this, &m_attributes, pLibAttr);
1481 RTListAppend(&m_attributes, &pAttr->m_listEntry);
1482
1483 return pAttr;
1484}
1485
1486/**
1487 * Like setAttribute (ministring variant), but replaces all backslashes with forward slashes
1488 * before calling that one.
1489 * @param pcszName
1490 * @param strValue
1491 * @return
1492 */
1493AttributeNode* ElementNode::setAttributePath(const char *pcszName, const RTCString &strValue)
1494{
1495 RTCString strTemp(strValue);
1496 strTemp.findReplace('\\', '/');
1497 return setAttribute(pcszName, strTemp.c_str());
1498}
1499
1500/**
1501 * Sets the given attribute; overloaded version for int32_t.
1502 *
1503 * If an attribute with the given name exists, it is overwritten,
1504 * otherwise a new attribute is created. Returns the attribute node
1505 * that was either created or changed.
1506 *
1507 * @param pcszName
1508 * @param i
1509 * @return
1510 */
1511AttributeNode* ElementNode::setAttribute(const char *pcszName, int32_t i)
1512{
1513 char szValue[12]; // negative sign + 10 digits + \0
1514 RTStrPrintf(szValue, sizeof(szValue), "%RI32", i);
1515 AttributeNode *p = setAttribute(pcszName, szValue);
1516 return p;
1517}
1518
1519/**
1520 * Sets the given attribute; overloaded version for uint32_t.
1521 *
1522 * If an attribute with the given name exists, it is overwritten,
1523 * otherwise a new attribute is created. Returns the attribute node
1524 * that was either created or changed.
1525 *
1526 * @param pcszName
1527 * @param u
1528 * @return
1529 */
1530AttributeNode* ElementNode::setAttribute(const char *pcszName, uint32_t u)
1531{
1532 char szValue[11]; // 10 digits + \0
1533 RTStrPrintf(szValue, sizeof(szValue), "%RU32", u);
1534 AttributeNode *p = setAttribute(pcszName, szValue);
1535 return p;
1536}
1537
1538/**
1539 * Sets the given attribute; overloaded version for int64_t.
1540 *
1541 * If an attribute with the given name exists, it is overwritten,
1542 * otherwise a new attribute is created. Returns the attribute node
1543 * that was either created or changed.
1544 *
1545 * @param pcszName
1546 * @param i
1547 * @return
1548 */
1549AttributeNode* ElementNode::setAttribute(const char *pcszName, int64_t i)
1550{
1551 char szValue[21]; // negative sign + 19 digits + \0
1552 RTStrPrintf(szValue, sizeof(szValue), "%RI64", i);
1553 AttributeNode *p = setAttribute(pcszName, szValue);
1554 return p;
1555}
1556
1557/**
1558 * Sets the given attribute; overloaded version for uint64_t.
1559 *
1560 * If an attribute with the given name exists, it is overwritten,
1561 * otherwise a new attribute is created. Returns the attribute node
1562 * that was either created or changed.
1563 *
1564 * @param pcszName
1565 * @param u
1566 * @return
1567 */
1568AttributeNode* ElementNode::setAttribute(const char *pcszName, uint64_t u)
1569{
1570 char szValue[21]; // 20 digits + \0
1571 RTStrPrintf(szValue, sizeof(szValue), "%RU64", u);
1572 AttributeNode *p = setAttribute(pcszName, szValue);
1573 return p;
1574}
1575
1576/**
1577 * Sets the given attribute to the given uint32_t, outputs a hexadecimal string.
1578 *
1579 * If an attribute with the given name exists, it is overwritten,
1580 * otherwise a new attribute is created. Returns the attribute node
1581 * that was either created or changed.
1582 *
1583 * @param pcszName
1584 * @param u
1585 * @return
1586 */
1587AttributeNode* ElementNode::setAttributeHex(const char *pcszName, uint32_t u)
1588{
1589 char szValue[11]; // "0x" + 8 digits + \0
1590 RTStrPrintf(szValue, sizeof(szValue), "0x%RX32", u);
1591 AttributeNode *p = setAttribute(pcszName, szValue);
1592 return p;
1593}
1594
1595/**
1596 * Sets the given attribute; overloaded version for bool.
1597 *
1598 * If an attribute with the given name exists, it is overwritten,
1599 * otherwise a new attribute is created. Returns the attribute node
1600 * that was either created or changed.
1601 *
1602 * @param pcszName The attribute name.
1603 * @param f The attribute value.
1604 * @return
1605 */
1606AttributeNode* ElementNode::setAttribute(const char *pcszName, bool f)
1607{
1608 return setAttribute(pcszName, (f) ? "true" : "false");
1609}
1610
1611/**
1612 * Private constructor for a new attribute node.
1613 *
1614 * @param pElmRoot Pointer to the root element. Needed for getting the
1615 * default name space.
1616 * @param pParent Pointer to the parent element (always an ElementNode,
1617 * despite the type). NULL for the root node.
1618 * @param pListAnchor Pointer to the m_children member of the parent. NULL
1619 * for the root node.
1620 * @param pLibAttr Pointer to the libxml2 attribute structure.
1621 */
1622AttributeNode::AttributeNode(const ElementNode *pElmRoot,
1623 Node *pParent,
1624 PRTLISTANCHOR pListAnchor,
1625 xmlAttr *pLibAttr)
1626 : Node(IsAttribute,
1627 pParent,
1628 pListAnchor,
1629 NULL,
1630 pLibAttr)
1631{
1632 m_pcszName = (const char *)pLibAttr->name;
1633 RT_NOREF_PV(pElmRoot);
1634
1635 if ( pLibAttr->ns
1636 && pLibAttr->ns->prefix)
1637 {
1638 m_pcszNamespacePrefix = (const char *)pLibAttr->ns->prefix;
1639 m_pcszNamespaceHref = (const char *)pLibAttr->ns->href;
1640 }
1641}
1642
1643ContentNode::ContentNode(Node *pParent, PRTLISTANCHOR pListAnchor, xmlNode *pLibNode)
1644 : Node(IsContent,
1645 pParent,
1646 pListAnchor,
1647 pLibNode,
1648 NULL)
1649{
1650}
1651
1652/*
1653 * NodesLoop
1654 *
1655 */
1656
1657struct NodesLoop::Data
1658{
1659 ElementNodesList listElements;
1660 ElementNodesList::const_iterator it;
1661};
1662
1663NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */)
1664{
1665 m = new Data;
1666 node.getChildElements(m->listElements, pcszMatch);
1667 m->it = m->listElements.begin();
1668}
1669
1670NodesLoop::~NodesLoop()
1671{
1672 delete m;
1673}
1674
1675
1676/**
1677 * Handy convenience helper for looping over all child elements. Create an
1678 * instance of NodesLoop on the stack and call this method until it returns
1679 * NULL, like this:
1680 * <code>
1681 * xml::ElementNode node; // should point to an element
1682 * xml::NodesLoop loop(node, "child"); // find all "child" elements under node
1683 * const xml::ElementNode *pChild = NULL;
1684 * while (pChild = loop.forAllNodes())
1685 * ...;
1686 * </code>
1687 * @return
1688 */
1689const ElementNode* NodesLoop::forAllNodes() const
1690{
1691 const ElementNode *pNode = NULL;
1692
1693 if (m->it != m->listElements.end())
1694 {
1695 pNode = *(m->it);
1696 ++(m->it);
1697 }
1698
1699 return pNode;
1700}
1701
1702////////////////////////////////////////////////////////////////////////////////
1703//
1704// Document class
1705//
1706////////////////////////////////////////////////////////////////////////////////
1707
1708struct Document::Data
1709{
1710 xmlDocPtr plibDocument;
1711 ElementNode *pRootElement;
1712 ElementNode *pComment;
1713
1714 Data()
1715 {
1716 plibDocument = NULL;
1717 pRootElement = NULL;
1718 pComment = NULL;
1719 }
1720
1721 ~Data()
1722 {
1723 reset();
1724 }
1725
1726 void reset()
1727 {
1728 if (plibDocument)
1729 {
1730 xmlFreeDoc(plibDocument);
1731 plibDocument = NULL;
1732 }
1733 if (pRootElement)
1734 {
1735 delete pRootElement;
1736 pRootElement = NULL;
1737 }
1738 if (pComment)
1739 {
1740 delete pComment;
1741 pComment = NULL;
1742 }
1743 }
1744
1745 void copyFrom(const Document::Data *p)
1746 {
1747 if (p->plibDocument)
1748 {
1749 plibDocument = xmlCopyDoc(p->plibDocument,
1750 1); // recursive == copy all
1751 }
1752 }
1753};
1754
1755Document::Document()
1756 : m(new Data)
1757{
1758}
1759
1760Document::Document(const Document &x)
1761 : m(new Data)
1762{
1763 m->copyFrom(x.m);
1764}
1765
1766Document& Document::operator=(const Document &x)
1767{
1768 m->reset();
1769 m->copyFrom(x.m);
1770 return *this;
1771}
1772
1773Document::~Document()
1774{
1775 delete m;
1776}
1777
1778/**
1779 * private method to refresh all internal structures after the internal pDocument
1780 * has changed. Called from XmlFileParser::read(). m->reset() must have been
1781 * called before to make sure all members except the internal pDocument are clean.
1782 */
1783void Document::refreshInternals() // private
1784{
1785 m->pRootElement = new ElementNode(NULL, NULL, NULL, xmlDocGetRootElement(m->plibDocument));
1786
1787 ElementNode::buildChildren(m->pRootElement);
1788}
1789
1790/**
1791 * Returns the root element of the document, or NULL if the document is empty.
1792 * Const variant.
1793 * @return
1794 */
1795const ElementNode *Document::getRootElement() const
1796{
1797 return m->pRootElement;
1798}
1799
1800/**
1801 * Returns the root element of the document, or NULL if the document is empty.
1802 * Non-const variant.
1803 * @return
1804 */
1805ElementNode *Document::getRootElement()
1806{
1807 return m->pRootElement;
1808}
1809
1810/**
1811 * Creates a new element node and sets it as the root element.
1812 *
1813 * This will only work if the document is empty; otherwise EDocumentNotEmpty is
1814 * thrown.
1815 */
1816ElementNode *Document::createRootElement(const char *pcszRootElementName,
1817 const char *pcszComment /* = NULL */)
1818{
1819 if (m->pRootElement || m->plibDocument)
1820 throw EDocumentNotEmpty(RT_SRC_POS);
1821
1822 // libxml side: create document, create root node
1823 m->plibDocument = xmlNewDoc((const xmlChar *)"1.0");
1824 xmlNode *plibRootNode = xmlNewNode(NULL /*namespace*/ , (const xmlChar *)pcszRootElementName);
1825 if (!plibRootNode)
1826 throw std::bad_alloc();
1827 xmlDocSetRootElement(m->plibDocument, plibRootNode);
1828
1829 // now wrap this in C++
1830 m->pRootElement = new ElementNode(NULL, NULL, NULL, plibRootNode);
1831
1832 // add document global comment if specified
1833 if (pcszComment != NULL)
1834 {
1835 xmlNode *pComment = xmlNewDocComment(m->plibDocument, (const xmlChar *)pcszComment);
1836 if (!pComment)
1837 throw std::bad_alloc();
1838 xmlAddPrevSibling(plibRootNode, pComment);
1839
1840 // now wrap this in C++
1841 m->pComment = new ElementNode(NULL, NULL, NULL, pComment);
1842 }
1843
1844 return m->pRootElement;
1845}
1846
1847////////////////////////////////////////////////////////////////////////////////
1848//
1849// XmlParserBase class
1850//
1851////////////////////////////////////////////////////////////////////////////////
1852
1853static void xmlParserBaseGenericError(void *pCtx, const char *pszMsg, ...) RT_NOTHROW_DEF
1854{
1855 NOREF(pCtx);
1856 va_list args;
1857 va_start(args, pszMsg);
1858 RTLogRelPrintfV(pszMsg, args);
1859 va_end(args);
1860}
1861
1862static void xmlParserBaseStructuredError(void *pCtx, xmlErrorPtr error) RT_NOTHROW_DEF
1863{
1864 NOREF(pCtx);
1865 /* we expect that there is always a trailing NL */
1866 LogRel(("XML error at '%s' line %d: %s", error->file, error->line, error->message));
1867}
1868
1869XmlParserBase::XmlParserBase()
1870{
1871 m_ctxt = xmlNewParserCtxt();
1872 if (m_ctxt == NULL)
1873 throw std::bad_alloc();
1874 /* per-thread so it must be here */
1875 xmlSetGenericErrorFunc(NULL, xmlParserBaseGenericError);
1876 xmlSetStructuredErrorFunc(NULL, xmlParserBaseStructuredError);
1877}
1878
1879XmlParserBase::~XmlParserBase()
1880{
1881 xmlSetStructuredErrorFunc(NULL, NULL);
1882 xmlSetGenericErrorFunc(NULL, NULL);
1883 xmlFreeParserCtxt (m_ctxt);
1884 m_ctxt = NULL;
1885}
1886
1887////////////////////////////////////////////////////////////////////////////////
1888//
1889// XmlMemParser class
1890//
1891////////////////////////////////////////////////////////////////////////////////
1892
1893XmlMemParser::XmlMemParser()
1894 : XmlParserBase()
1895{
1896}
1897
1898XmlMemParser::~XmlMemParser()
1899{
1900}
1901
1902/**
1903 * Parse the given buffer and fills the given Document object with its contents.
1904 * Throws XmlError on parsing errors.
1905 *
1906 * The document that is passed in will be reset before being filled if not empty.
1907 *
1908 * @param pvBuf Memory buffer to parse.
1909 * @param cbSize Size of the memory buffer.
1910 * @param strFilename Refernece to the name of the file we're parsing.
1911 * @param doc Reference to the output document. This will be reset
1912 * and filled with data according to file contents.
1913 */
1914void XmlMemParser::read(const void *pvBuf, size_t cbSize,
1915 const RTCString &strFilename,
1916 Document &doc)
1917{
1918 GlobalLock lock;
1919// global.setExternalEntityLoader(ExternalEntityLoader);
1920
1921 const char *pcszFilename = strFilename.c_str();
1922
1923 doc.m->reset();
1924 const int options = XML_PARSE_NOBLANKS /* remove blank nodes */
1925 | XML_PARSE_NONET /* forbit any network access */
1926#if LIBXML_VERSION >= 20700
1927 | XML_PARSE_HUGE /* don't restrict the node depth
1928 to 256 (bad for snapshots!) */
1929#endif
1930 ;
1931 if (!(doc.m->plibDocument = xmlCtxtReadMemory(m_ctxt,
1932 (const char*)pvBuf,
1933 (int)cbSize,
1934 pcszFilename,
1935 NULL, // encoding = auto
1936 options)))
1937 throw XmlError(xmlCtxtGetLastError(m_ctxt));
1938
1939 doc.refreshInternals();
1940}
1941
1942////////////////////////////////////////////////////////////////////////////////
1943//
1944// XmlMemWriter class
1945//
1946////////////////////////////////////////////////////////////////////////////////
1947
1948XmlMemWriter::XmlMemWriter()
1949 : m_pBuf(0)
1950{
1951}
1952
1953XmlMemWriter::~XmlMemWriter()
1954{
1955 if (m_pBuf)
1956 xmlFree(m_pBuf);
1957}
1958
1959void XmlMemWriter::write(const Document &doc, void **ppvBuf, size_t *pcbSize)
1960{
1961 if (m_pBuf)
1962 {
1963 xmlFree(m_pBuf);
1964 m_pBuf = 0;
1965 }
1966 int size;
1967 xmlDocDumpFormatMemory(doc.m->plibDocument, (xmlChar**)&m_pBuf, &size, 1);
1968 *ppvBuf = m_pBuf;
1969 *pcbSize = size;
1970}
1971
1972
1973////////////////////////////////////////////////////////////////////////////////
1974//
1975// XmlStringWriter class
1976//
1977////////////////////////////////////////////////////////////////////////////////
1978
1979XmlStringWriter::XmlStringWriter()
1980 : m_pStrDst(NULL), m_fOutOfMemory(false)
1981{
1982}
1983
1984int XmlStringWriter::write(const Document &rDoc, RTCString *pStrDst)
1985{
1986 /*
1987 * Clear the output string and take the global libxml2 lock so we can
1988 * safely configure the output formatting.
1989 */
1990 pStrDst->setNull();
1991
1992 GlobalLock lock;
1993
1994 xmlIndentTreeOutput = 1;
1995 xmlTreeIndentString = " ";
1996 xmlSaveNoEmptyTags = 0;
1997
1998 /*
1999 * Do a pass to calculate the size.
2000 */
2001 size_t cbOutput = 1; /* zero term */
2002
2003 xmlSaveCtxtPtr pSaveCtx= xmlSaveToIO(WriteCallbackForSize, CloseCallback, &cbOutput, NULL /*pszEncoding*/, XML_SAVE_FORMAT);
2004 if (!pSaveCtx)
2005 return VERR_NO_MEMORY;
2006
2007 long rcXml = xmlSaveDoc(pSaveCtx, rDoc.m->plibDocument);
2008 xmlSaveClose(pSaveCtx);
2009 if (rcXml == -1)
2010 return VERR_GENERAL_FAILURE;
2011
2012 /*
2013 * Try resize the string.
2014 */
2015 int rc = pStrDst->reserveNoThrow(cbOutput);
2016 if (RT_SUCCESS(rc))
2017 {
2018 /*
2019 * Do the real run where we feed output to the string.
2020 */
2021 m_pStrDst = pStrDst;
2022 m_fOutOfMemory = false;
2023 pSaveCtx = xmlSaveToIO(WriteCallbackForReal, CloseCallback, this, NULL /*pszEncoding*/, XML_SAVE_FORMAT);
2024 if (pSaveCtx)
2025 {
2026 rcXml = xmlSaveDoc(pSaveCtx, rDoc.m->plibDocument);
2027 xmlSaveClose(pSaveCtx);
2028 m_pStrDst = NULL;
2029 if (rcXml != -1)
2030 {
2031 if (!m_fOutOfMemory)
2032 return VINF_SUCCESS;
2033
2034 rc = VERR_NO_STR_MEMORY;
2035 }
2036 else
2037 rc = VERR_GENERAL_FAILURE;
2038 }
2039 else
2040 rc = VERR_NO_MEMORY;
2041 pStrDst->setNull();
2042 m_pStrDst = NULL;
2043 }
2044 return rc;
2045}
2046
2047/*static*/ int XmlStringWriter::WriteCallbackForSize(void *pvUser, const char *pachBuf, int cbToWrite) RT_NOTHROW_DEF
2048{
2049 if (cbToWrite > 0)
2050 *(size_t *)pvUser += (unsigned)cbToWrite;
2051 RT_NOREF(pachBuf);
2052 return cbToWrite;
2053}
2054
2055/*static*/ int XmlStringWriter::WriteCallbackForReal(void *pvUser, const char *pachBuf, int cbToWrite) RT_NOTHROW_DEF
2056{
2057 XmlStringWriter *pThis = static_cast<XmlStringWriter*>(pvUser);
2058 if (!pThis->m_fOutOfMemory)
2059 {
2060 if (cbToWrite > 0)
2061 {
2062 try
2063 {
2064 pThis->m_pStrDst->append(pachBuf, (size_t)cbToWrite);
2065 }
2066 catch (std::bad_alloc &)
2067 {
2068 pThis->m_fOutOfMemory = true;
2069 return -1;
2070 }
2071 }
2072 return cbToWrite;
2073 }
2074 return -1; /* failure */
2075}
2076
2077/*static*/ int XmlStringWriter::CloseCallback(void *pvUser) RT_NOTHROW_DEF
2078{
2079 /* Nothing to do here. */
2080 RT_NOREF(pvUser);
2081 return 0;
2082}
2083
2084
2085
2086////////////////////////////////////////////////////////////////////////////////
2087//
2088// XmlFileParser class
2089//
2090////////////////////////////////////////////////////////////////////////////////
2091
2092struct XmlFileParser::Data
2093{
2094 RTCString strXmlFilename;
2095
2096 Data()
2097 {
2098 }
2099
2100 ~Data()
2101 {
2102 }
2103};
2104
2105XmlFileParser::XmlFileParser()
2106 : XmlParserBase(),
2107 m(new Data())
2108{
2109}
2110
2111XmlFileParser::~XmlFileParser()
2112{
2113 delete m;
2114 m = NULL;
2115}
2116
2117struct IOContext
2118{
2119 File file;
2120 RTCString error;
2121
2122 IOContext(const char *pcszFilename, File::Mode mode, bool fFlush = false)
2123 : file(mode, pcszFilename, fFlush)
2124 {
2125 }
2126
2127 void setError(const RTCError &x)
2128 {
2129 error = x.what();
2130 }
2131
2132 void setError(const std::exception &x)
2133 {
2134 error = x.what();
2135 }
2136
2137private:
2138 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(IOContext); /* (shuts up C4626 and C4625 MSC warnings) */
2139};
2140
2141struct ReadContext : IOContext
2142{
2143 ReadContext(const char *pcszFilename)
2144 : IOContext(pcszFilename, File::Mode_Read)
2145 {
2146 }
2147
2148private:
2149 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(ReadContext); /* (shuts up C4626 and C4625 MSC warnings) */
2150};
2151
2152struct WriteContext : IOContext
2153{
2154 WriteContext(const char *pcszFilename, bool fFlush)
2155 : IOContext(pcszFilename, File::Mode_Overwrite, fFlush)
2156 {
2157 }
2158
2159private:
2160 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(WriteContext); /* (shuts up C4626 and C4625 MSC warnings) */
2161};
2162
2163/**
2164 * Reads the given file and fills the given Document object with its contents.
2165 * Throws XmlError on parsing errors.
2166 *
2167 * The document that is passed in will be reset before being filled if not empty.
2168 *
2169 * @param strFilename in: name fo file to parse.
2170 * @param doc out: document to be reset and filled with data according to file contents.
2171 */
2172void XmlFileParser::read(const RTCString &strFilename,
2173 Document &doc)
2174{
2175 GlobalLock lock;
2176// global.setExternalEntityLoader(ExternalEntityLoader);
2177
2178 m->strXmlFilename = strFilename;
2179 const char *pcszFilename = strFilename.c_str();
2180
2181 ReadContext context(pcszFilename);
2182 doc.m->reset();
2183 const int options = XML_PARSE_NOBLANKS /* remove blank nodes */
2184 | XML_PARSE_NONET /* forbit any network access */
2185#if LIBXML_VERSION >= 20700
2186 | XML_PARSE_HUGE /* don't restrict the node depth
2187 to 256 (bad for snapshots!) */
2188#endif
2189 ;
2190 if (!(doc.m->plibDocument = xmlCtxtReadIO(m_ctxt,
2191 ReadCallback,
2192 CloseCallback,
2193 &context,
2194 pcszFilename,
2195 NULL, // encoding = auto
2196 options)))
2197 throw XmlError(xmlCtxtGetLastError(m_ctxt));
2198
2199 doc.refreshInternals();
2200}
2201
2202/*static*/ int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen) RT_NOTHROW_DEF
2203{
2204 ReadContext *pContext = static_cast<ReadContext*>(aCtxt);
2205
2206 /* To prevent throwing exceptions while inside libxml2 code, we catch
2207 * them and forward to our level using a couple of variables. */
2208
2209 try
2210 {
2211 return pContext->file.read(aBuf, aLen);
2212 }
2213 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
2214 catch (const RTCError &err) { pContext->setError(err); }
2215 catch (const std::exception &err) { pContext->setError(err); }
2216 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
2217
2218 return -1 /* failure */;
2219}
2220
2221/*static*/ int XmlFileParser::CloseCallback(void *aCtxt) RT_NOTHROW_DEF
2222{
2223 /// @todo to be written
2224 NOREF(aCtxt);
2225
2226 return -1;
2227}
2228
2229////////////////////////////////////////////////////////////////////////////////
2230//
2231// XmlFileWriter class
2232//
2233////////////////////////////////////////////////////////////////////////////////
2234
2235struct XmlFileWriter::Data
2236{
2237 Document *pDoc;
2238};
2239
2240XmlFileWriter::XmlFileWriter(Document &doc)
2241{
2242 m = new Data();
2243 m->pDoc = &doc;
2244}
2245
2246XmlFileWriter::~XmlFileWriter()
2247{
2248 delete m;
2249}
2250
2251void XmlFileWriter::writeInternal(const char *pcszFilename, bool fSafe)
2252{
2253 WriteContext context(pcszFilename, fSafe);
2254
2255 GlobalLock lock;
2256
2257 /* serialize to the stream */
2258 xmlIndentTreeOutput = 1;
2259 xmlTreeIndentString = " ";
2260 xmlSaveNoEmptyTags = 0;
2261
2262 xmlSaveCtxtPtr saveCtxt;
2263 if (!(saveCtxt = xmlSaveToIO(WriteCallback,
2264 CloseCallback,
2265 &context,
2266 NULL,
2267 XML_SAVE_FORMAT)))
2268 throw xml::LogicError(RT_SRC_POS);
2269
2270 long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument);
2271 if (rc == -1)
2272 {
2273 /* look if there was a forwarded exception from the lower level */
2274// if (m->trappedErr.get() != NULL)
2275// m->trappedErr->rethrow();
2276
2277 /* there must be an exception from the Output implementation,
2278 * otherwise the save operation must always succeed. */
2279 throw xml::LogicError(RT_SRC_POS);
2280 }
2281
2282 xmlSaveClose(saveCtxt);
2283}
2284
2285void XmlFileWriter::write(const char *pcszFilename, bool fSafe)
2286{
2287 if (!fSafe)
2288 writeInternal(pcszFilename, fSafe);
2289 else
2290 {
2291 /* Empty string and directory spec must be avoid. */
2292 if (RTPathFilename(pcszFilename) == NULL)
2293 throw xml::LogicError(RT_SRC_POS);
2294
2295 /* Construct both filenames first to ease error handling. */
2296 char szTmpFilename[RTPATH_MAX];
2297 int rc = RTStrCopy(szTmpFilename, sizeof(szTmpFilename) - strlen(s_pszTmpSuff), pcszFilename);
2298 if (RT_FAILURE(rc))
2299 throw EIPRTFailure(rc, "RTStrCopy");
2300 strcat(szTmpFilename, s_pszTmpSuff);
2301
2302 char szPrevFilename[RTPATH_MAX];
2303 rc = RTStrCopy(szPrevFilename, sizeof(szPrevFilename) - strlen(s_pszPrevSuff), pcszFilename);
2304 if (RT_FAILURE(rc))
2305 throw EIPRTFailure(rc, "RTStrCopy");
2306 strcat(szPrevFilename, s_pszPrevSuff);
2307
2308 /* Write the XML document to the temporary file. */
2309 writeInternal(szTmpFilename, fSafe);
2310
2311 /* Make a backup of any existing file (ignore failure). */
2312 uint64_t cbPrevFile;
2313 rc = RTFileQuerySizeByPath(pcszFilename, &cbPrevFile);
2314 if (RT_SUCCESS(rc) && cbPrevFile >= 16)
2315 RTFileRename(pcszFilename, szPrevFilename, RTPATHRENAME_FLAGS_REPLACE);
2316
2317 /* Commit the temporary file. Just leave the tmp file behind on failure. */
2318 rc = RTFileRename(szTmpFilename, pcszFilename, RTPATHRENAME_FLAGS_REPLACE);
2319 if (RT_FAILURE(rc))
2320 throw EIPRTFailure(rc, "Failed to replace '%s' with '%s'", pcszFilename, szTmpFilename);
2321
2322 /* Flush the directory changes (required on linux at least). */
2323 RTPathStripFilename(szTmpFilename);
2324 rc = RTDirFlush(szTmpFilename);
2325 AssertMsg(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED, ("%Rrc\n", rc));
2326 }
2327}
2328
2329/*static*/ int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen) RT_NOTHROW_DEF
2330{
2331 WriteContext *pContext = static_cast<WriteContext*>(aCtxt);
2332
2333 /* To prevent throwing exceptions while inside libxml2 code, we catch
2334 * them and forward to our level using a couple of variables. */
2335 try
2336 {
2337 return pContext->file.write(aBuf, aLen);
2338 }
2339 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
2340 catch (const RTCError &err) { pContext->setError(err); }
2341 catch (const std::exception &err) { pContext->setError(err); }
2342 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
2343
2344 return -1 /* failure */;
2345}
2346
2347/*static*/ int XmlFileWriter::CloseCallback(void *aCtxt) RT_NOTHROW_DEF
2348{
2349 /// @todo to be written
2350 NOREF(aCtxt);
2351
2352 return -1;
2353}
2354
2355/*static*/ const char * const XmlFileWriter::s_pszTmpSuff = "-tmp";
2356/*static*/ const char * const XmlFileWriter::s_pszPrevSuff = "-prev";
2357
2358
2359} // end namespace xml
2360
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use