VirtualBox

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

Last change on this file since 64158 was 64158, checked in by vboxsync, 8 years ago

Runtime: cosmetic fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.9 KB
Line 
1/* $Id: xml.cpp 64158 2016-10-05 15:32:54Z vboxsync $ */
2/** @file
3 * IPRT - XML Manipulation API.
4 */
5
6/*
7 * Copyright (C) 2007-2016 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 RT_NOREF_PV(pElmRoot);
1474
1475 if ( pLibAttr->ns
1476 && pLibAttr->ns->prefix)
1477 {
1478 m_pcszNamespacePrefix = (const char *)pLibAttr->ns->prefix;
1479 m_pcszNamespaceHref = (const char *)pLibAttr->ns->href;
1480 }
1481}
1482
1483ContentNode::ContentNode(Node *pParent, PRTLISTANCHOR pListAnchor, xmlNode *pLibNode)
1484 : Node(IsContent,
1485 pParent,
1486 pListAnchor,
1487 pLibNode,
1488 NULL)
1489{
1490}
1491
1492/*
1493 * NodesLoop
1494 *
1495 */
1496
1497struct NodesLoop::Data
1498{
1499 ElementNodesList listElements;
1500 ElementNodesList::const_iterator it;
1501};
1502
1503NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */)
1504{
1505 m = new Data;
1506 node.getChildElements(m->listElements, pcszMatch);
1507 m->it = m->listElements.begin();
1508}
1509
1510NodesLoop::~NodesLoop()
1511{
1512 delete m;
1513}
1514
1515
1516/**
1517 * Handy convenience helper for looping over all child elements. Create an
1518 * instance of NodesLoop on the stack and call this method until it returns
1519 * NULL, like this:
1520 * <code>
1521 * xml::ElementNode node; // should point to an element
1522 * xml::NodesLoop loop(node, "child"); // find all "child" elements under node
1523 * const xml::ElementNode *pChild = NULL;
1524 * while (pChild = loop.forAllNodes())
1525 * ...;
1526 * </code>
1527 * @return
1528 */
1529const ElementNode* NodesLoop::forAllNodes() const
1530{
1531 const ElementNode *pNode = NULL;
1532
1533 if (m->it != m->listElements.end())
1534 {
1535 pNode = *(m->it);
1536 ++(m->it);
1537 }
1538
1539 return pNode;
1540}
1541
1542////////////////////////////////////////////////////////////////////////////////
1543//
1544// Document class
1545//
1546////////////////////////////////////////////////////////////////////////////////
1547
1548struct Document::Data
1549{
1550 xmlDocPtr plibDocument;
1551 ElementNode *pRootElement;
1552 ElementNode *pComment;
1553
1554 Data()
1555 {
1556 plibDocument = NULL;
1557 pRootElement = NULL;
1558 pComment = NULL;
1559 }
1560
1561 ~Data()
1562 {
1563 reset();
1564 }
1565
1566 void reset()
1567 {
1568 if (plibDocument)
1569 {
1570 xmlFreeDoc(plibDocument);
1571 plibDocument = NULL;
1572 }
1573 if (pRootElement)
1574 {
1575 delete pRootElement;
1576 pRootElement = NULL;
1577 }
1578 if (pComment)
1579 {
1580 delete pComment;
1581 pComment = NULL;
1582 }
1583 }
1584
1585 void copyFrom(const Document::Data *p)
1586 {
1587 if (p->plibDocument)
1588 {
1589 plibDocument = xmlCopyDoc(p->plibDocument,
1590 1); // recursive == copy all
1591 }
1592 }
1593};
1594
1595Document::Document()
1596 : m(new Data)
1597{
1598}
1599
1600Document::Document(const Document &x)
1601 : m(new Data)
1602{
1603 m->copyFrom(x.m);
1604}
1605
1606Document& Document::operator=(const Document &x)
1607{
1608 m->reset();
1609 m->copyFrom(x.m);
1610 return *this;
1611}
1612
1613Document::~Document()
1614{
1615 delete m;
1616}
1617
1618/**
1619 * private method to refresh all internal structures after the internal pDocument
1620 * has changed. Called from XmlFileParser::read(). m->reset() must have been
1621 * called before to make sure all members except the internal pDocument are clean.
1622 */
1623void Document::refreshInternals() // private
1624{
1625 m->pRootElement = new ElementNode(NULL, NULL, NULL, xmlDocGetRootElement(m->plibDocument));
1626
1627 ElementNode::buildChildren(m->pRootElement);
1628}
1629
1630/**
1631 * Returns the root element of the document, or NULL if the document is empty.
1632 * Const variant.
1633 * @return
1634 */
1635const ElementNode *Document::getRootElement() const
1636{
1637 return m->pRootElement;
1638}
1639
1640/**
1641 * Returns the root element of the document, or NULL if the document is empty.
1642 * Non-const variant.
1643 * @return
1644 */
1645ElementNode *Document::getRootElement()
1646{
1647 return m->pRootElement;
1648}
1649
1650/**
1651 * Creates a new element node and sets it as the root element.
1652 *
1653 * This will only work if the document is empty; otherwise EDocumentNotEmpty is
1654 * thrown.
1655 */
1656ElementNode *Document::createRootElement(const char *pcszRootElementName,
1657 const char *pcszComment /* = NULL */)
1658{
1659 if (m->pRootElement || m->plibDocument)
1660 throw EDocumentNotEmpty(RT_SRC_POS);
1661
1662 // libxml side: create document, create root node
1663 m->plibDocument = xmlNewDoc((const xmlChar *)"1.0");
1664 xmlNode *plibRootNode = xmlNewNode(NULL /*namespace*/ , (const xmlChar *)pcszRootElementName);
1665 if (!plibRootNode)
1666 throw std::bad_alloc();
1667 xmlDocSetRootElement(m->plibDocument, plibRootNode);
1668
1669 // now wrap this in C++
1670 m->pRootElement = new ElementNode(NULL, NULL, NULL, plibRootNode);
1671
1672 // add document global comment if specified
1673 if (pcszComment != NULL)
1674 {
1675 xmlNode *pComment = xmlNewDocComment(m->plibDocument, (const xmlChar *)pcszComment);
1676 if (!pComment)
1677 throw std::bad_alloc();
1678 xmlAddPrevSibling(plibRootNode, pComment);
1679
1680 // now wrap this in C++
1681 m->pComment = new ElementNode(NULL, NULL, NULL, pComment);
1682 }
1683
1684 return m->pRootElement;
1685}
1686
1687////////////////////////////////////////////////////////////////////////////////
1688//
1689// XmlParserBase class
1690//
1691////////////////////////////////////////////////////////////////////////////////
1692
1693XmlParserBase::XmlParserBase()
1694{
1695 m_ctxt = xmlNewParserCtxt();
1696 if (m_ctxt == NULL)
1697 throw std::bad_alloc();
1698}
1699
1700XmlParserBase::~XmlParserBase()
1701{
1702 xmlFreeParserCtxt (m_ctxt);
1703 m_ctxt = NULL;
1704}
1705
1706////////////////////////////////////////////////////////////////////////////////
1707//
1708// XmlMemParser class
1709//
1710////////////////////////////////////////////////////////////////////////////////
1711
1712XmlMemParser::XmlMemParser()
1713 : XmlParserBase()
1714{
1715}
1716
1717XmlMemParser::~XmlMemParser()
1718{
1719}
1720
1721/**
1722 * Parse the given buffer and fills the given Document object with its contents.
1723 * Throws XmlError on parsing errors.
1724 *
1725 * The document that is passed in will be reset before being filled if not empty.
1726 *
1727 * @param pvBuf Memory buffer to parse.
1728 * @param cbSize Size of the memory buffer.
1729 * @param strFilename Refernece to the name of the file we're parsing.
1730 * @param doc Reference to the output document. This will be reset
1731 * and filled with data according to file contents.
1732 */
1733void XmlMemParser::read(const void *pvBuf, size_t cbSize,
1734 const RTCString &strFilename,
1735 Document &doc)
1736{
1737 GlobalLock lock;
1738// global.setExternalEntityLoader(ExternalEntityLoader);
1739
1740 const char *pcszFilename = strFilename.c_str();
1741
1742 doc.m->reset();
1743 const int options = XML_PARSE_NOBLANKS /* remove blank nodes */
1744 | XML_PARSE_NONET /* forbit any network access */
1745#if LIBXML_VERSION >= 20700
1746 | XML_PARSE_HUGE /* don't restrict the node depth
1747 to 256 (bad for snapshots!) */
1748#endif
1749 ;
1750 if (!(doc.m->plibDocument = xmlCtxtReadMemory(m_ctxt,
1751 (const char*)pvBuf,
1752 (int)cbSize,
1753 pcszFilename,
1754 NULL, // encoding = auto
1755 options)))
1756 throw XmlError(xmlCtxtGetLastError(m_ctxt));
1757
1758 doc.refreshInternals();
1759}
1760
1761////////////////////////////////////////////////////////////////////////////////
1762//
1763// XmlMemWriter class
1764//
1765////////////////////////////////////////////////////////////////////////////////
1766
1767XmlMemWriter::XmlMemWriter()
1768 : m_pBuf(0)
1769{
1770}
1771
1772XmlMemWriter::~XmlMemWriter()
1773{
1774 if (m_pBuf)
1775 xmlFree(m_pBuf);
1776}
1777
1778void XmlMemWriter::write(const Document &doc, void **ppvBuf, size_t *pcbSize)
1779{
1780 if (m_pBuf)
1781 {
1782 xmlFree(m_pBuf);
1783 m_pBuf = 0;
1784 }
1785 int size;
1786 xmlDocDumpFormatMemory(doc.m->plibDocument, (xmlChar**)&m_pBuf, &size, 1);
1787 *ppvBuf = m_pBuf;
1788 *pcbSize = size;
1789}
1790
1791////////////////////////////////////////////////////////////////////////////////
1792//
1793// XmlFileParser class
1794//
1795////////////////////////////////////////////////////////////////////////////////
1796
1797struct XmlFileParser::Data
1798{
1799 RTCString strXmlFilename;
1800
1801 Data()
1802 {
1803 }
1804
1805 ~Data()
1806 {
1807 }
1808};
1809
1810XmlFileParser::XmlFileParser()
1811 : XmlParserBase(),
1812 m(new Data())
1813{
1814}
1815
1816XmlFileParser::~XmlFileParser()
1817{
1818 delete m;
1819 m = NULL;
1820}
1821
1822struct IOContext
1823{
1824 File file;
1825 RTCString error;
1826
1827 IOContext(const char *pcszFilename, File::Mode mode, bool fFlush = false)
1828 : file(mode, pcszFilename, fFlush)
1829 {
1830 }
1831
1832 void setError(const RTCError &x)
1833 {
1834 error = x.what();
1835 }
1836
1837 void setError(const std::exception &x)
1838 {
1839 error = x.what();
1840 }
1841
1842private:
1843 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(IOContext); /* (shuts up C4626 and C4625 MSC warnings) */
1844};
1845
1846struct ReadContext : IOContext
1847{
1848 ReadContext(const char *pcszFilename)
1849 : IOContext(pcszFilename, File::Mode_Read)
1850 {
1851 }
1852
1853private:
1854 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(ReadContext); /* (shuts up C4626 and C4625 MSC warnings) */
1855};
1856
1857struct WriteContext : IOContext
1858{
1859 WriteContext(const char *pcszFilename, bool fFlush)
1860 : IOContext(pcszFilename, File::Mode_Overwrite, fFlush)
1861 {
1862 }
1863
1864private:
1865 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(WriteContext); /* (shuts up C4626 and C4625 MSC warnings) */
1866};
1867
1868/**
1869 * Reads the given file and fills the given Document object with its contents.
1870 * Throws XmlError on parsing errors.
1871 *
1872 * The document that is passed in will be reset before being filled if not empty.
1873 *
1874 * @param strFilename in: name fo file to parse.
1875 * @param doc out: document to be reset and filled with data according to file contents.
1876 */
1877void XmlFileParser::read(const RTCString &strFilename,
1878 Document &doc)
1879{
1880 GlobalLock lock;
1881// global.setExternalEntityLoader(ExternalEntityLoader);
1882
1883 m->strXmlFilename = strFilename;
1884 const char *pcszFilename = strFilename.c_str();
1885
1886 ReadContext context(pcszFilename);
1887 doc.m->reset();
1888 const int options = XML_PARSE_NOBLANKS /* remove blank nodes */
1889 | XML_PARSE_NONET /* forbit any network access */
1890#if LIBXML_VERSION >= 20700
1891 | XML_PARSE_HUGE /* don't restrict the node depth
1892 to 256 (bad for snapshots!) */
1893#endif
1894 ;
1895 if (!(doc.m->plibDocument = xmlCtxtReadIO(m_ctxt,
1896 ReadCallback,
1897 CloseCallback,
1898 &context,
1899 pcszFilename,
1900 NULL, // encoding = auto
1901 options)))
1902 throw XmlError(xmlCtxtGetLastError(m_ctxt));
1903
1904 doc.refreshInternals();
1905}
1906
1907// static
1908int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen)
1909{
1910 ReadContext *pContext = static_cast<ReadContext*>(aCtxt);
1911
1912 /* To prevent throwing exceptions while inside libxml2 code, we catch
1913 * them and forward to our level using a couple of variables. */
1914
1915 try
1916 {
1917 return pContext->file.read(aBuf, aLen);
1918 }
1919 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
1920 catch (const RTCError &err) { pContext->setError(err); }
1921 catch (const std::exception &err) { pContext->setError(err); }
1922 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
1923
1924 return -1 /* failure */;
1925}
1926
1927int XmlFileParser::CloseCallback(void *aCtxt)
1928{
1929 /// @todo to be written
1930 NOREF(aCtxt);
1931
1932 return -1;
1933}
1934
1935////////////////////////////////////////////////////////////////////////////////
1936//
1937// XmlFileWriter class
1938//
1939////////////////////////////////////////////////////////////////////////////////
1940
1941struct XmlFileWriter::Data
1942{
1943 Document *pDoc;
1944};
1945
1946XmlFileWriter::XmlFileWriter(Document &doc)
1947{
1948 m = new Data();
1949 m->pDoc = &doc;
1950}
1951
1952XmlFileWriter::~XmlFileWriter()
1953{
1954 delete m;
1955}
1956
1957void XmlFileWriter::writeInternal(const char *pcszFilename, bool fSafe)
1958{
1959 WriteContext context(pcszFilename, fSafe);
1960
1961 GlobalLock lock;
1962
1963 /* serialize to the stream */
1964 xmlIndentTreeOutput = 1;
1965 xmlTreeIndentString = " ";
1966 xmlSaveNoEmptyTags = 0;
1967
1968 xmlSaveCtxtPtr saveCtxt;
1969 if (!(saveCtxt = xmlSaveToIO(WriteCallback,
1970 CloseCallback,
1971 &context,
1972 NULL,
1973 XML_SAVE_FORMAT)))
1974 throw xml::LogicError(RT_SRC_POS);
1975
1976 long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument);
1977 if (rc == -1)
1978 {
1979 /* look if there was a forwarded exception from the lower level */
1980// if (m->trappedErr.get() != NULL)
1981// m->trappedErr->rethrow();
1982
1983 /* there must be an exception from the Output implementation,
1984 * otherwise the save operation must always succeed. */
1985 throw xml::LogicError(RT_SRC_POS);
1986 }
1987
1988 xmlSaveClose(saveCtxt);
1989}
1990
1991void XmlFileWriter::write(const char *pcszFilename, bool fSafe)
1992{
1993 if (!fSafe)
1994 writeInternal(pcszFilename, fSafe);
1995 else
1996 {
1997 /* Empty string and directory spec must be avoid. */
1998 if (RTPathFilename(pcszFilename) == NULL)
1999 throw xml::LogicError(RT_SRC_POS);
2000
2001 /* Construct both filenames first to ease error handling. */
2002 char szTmpFilename[RTPATH_MAX];
2003 int rc = RTStrCopy(szTmpFilename, sizeof(szTmpFilename) - strlen(s_pszTmpSuff), pcszFilename);
2004 if (RT_FAILURE(rc))
2005 throw EIPRTFailure(rc, "RTStrCopy");
2006 strcat(szTmpFilename, s_pszTmpSuff);
2007
2008 char szPrevFilename[RTPATH_MAX];
2009 rc = RTStrCopy(szPrevFilename, sizeof(szPrevFilename) - strlen(s_pszPrevSuff), pcszFilename);
2010 if (RT_FAILURE(rc))
2011 throw EIPRTFailure(rc, "RTStrCopy");
2012 strcat(szPrevFilename, s_pszPrevSuff);
2013
2014 /* Write the XML document to the temporary file. */
2015 writeInternal(szTmpFilename, fSafe);
2016
2017 /* Make a backup of any existing file (ignore failure). */
2018 uint64_t cbPrevFile;
2019 rc = RTFileQuerySize(pcszFilename, &cbPrevFile);
2020 if (RT_SUCCESS(rc) && cbPrevFile >= 16)
2021 RTFileRename(pcszFilename, szPrevFilename, RTPATHRENAME_FLAGS_REPLACE);
2022
2023 /* Commit the temporary file. Just leave the tmp file behind on failure. */
2024 rc = RTFileRename(szTmpFilename, pcszFilename, RTPATHRENAME_FLAGS_REPLACE);
2025 if (RT_FAILURE(rc))
2026 throw EIPRTFailure(rc, "Failed to replace '%s' with '%s'", pcszFilename, szTmpFilename);
2027
2028 /* Flush the directory changes (required on linux at least). */
2029 RTPathStripFilename(szTmpFilename);
2030 rc = RTDirFlush(szTmpFilename);
2031 AssertMsg(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED, ("%Rrc\n", rc));
2032 }
2033}
2034
2035int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen)
2036{
2037 WriteContext *pContext = static_cast<WriteContext*>(aCtxt);
2038
2039 /* To prevent throwing exceptions while inside libxml2 code, we catch
2040 * them and forward to our level using a couple of variables. */
2041 try
2042 {
2043 return pContext->file.write(aBuf, aLen);
2044 }
2045 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
2046 catch (const RTCError &err) { pContext->setError(err); }
2047 catch (const std::exception &err) { pContext->setError(err); }
2048 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
2049
2050 return -1 /* failure */;
2051}
2052
2053int XmlFileWriter::CloseCallback(void *aCtxt)
2054{
2055 /// @todo to be written
2056 NOREF(aCtxt);
2057
2058 return -1;
2059}
2060
2061/*static*/ const char * const XmlFileWriter::s_pszTmpSuff = "-tmp";
2062/*static*/ const char * const XmlFileWriter::s_pszPrevSuff = "-prev";
2063
2064
2065} // end namespace xml
2066
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