VirtualBox

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

Last change on this file since 58591 was 58169, checked in by vboxsync, 9 years ago

Runtime: fixed another XML version check

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

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