VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 17 months ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use