Changeset 21077 in vbox for trunk/src/VBox/Main/xml/xml.cpp
- Timestamp:
- Jun 30, 2009 3:19:12 PM (16 years ago)
- svn:sync-xref-src-repo-rev:
- 49335
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Main/xml/xml.cpp
r21073 r21077 1 /** @file 2 * VirtualBox XML Manipulation API. 3 */ 4 5 /* 6 * Copyright (C) 2007-2009 Sun Microsystems, Inc. 7 * 8 * This file is part of VirtualBox Open Source Edition (OSE), as 9 * available from http://www.215389.xyz. This file is free software; 10 * you can redistribute it and/or modify it under the terms of the GNU 11 * General Public License (GPL) as published by the Free Software 12 * Foundation, in version 2 as it comes in the "COPYING" file of the 13 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the 14 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. 15 * 16 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa 17 * Clara, CA 95054 USA or visit http://www.sun.com if you need 18 * additional information or have any questions. 19 */ 20 21 #include "Logging.h" 22 23 #include <iprt/cdefs.h> 24 #include <iprt/err.h> 25 #include <iprt/file.h> 26 #include <iprt/lock.h> 27 #include <iprt/string.h> 28 29 #include <libxml/tree.h> 30 #include <libxml/parser.h> 31 #include <libxml/globals.h> 32 #include <libxml/xmlIO.h> 33 #include <libxml/xmlsave.h> 34 #include <libxml/uri.h> 35 36 #include <libxml/xmlschemas.h> 37 38 #include <list> 39 #include <map> 40 #include <boost/shared_ptr.hpp> 41 42 #include "VBox/xml.h" 43 44 //////////////////////////////////////////////////////////////////////////////// 45 // 46 // globals 47 // 48 //////////////////////////////////////////////////////////////////////////////// 49 50 /** 51 * Global module initialization structure. This is to wrap non-reentrant bits 52 * of libxml, among other things. 53 * 54 * The constructor and destructor of this structure are used to perform global 55 * module initiaizaton and cleanup. Thee must be only one global variable of 56 * this structure. 57 */ 58 static 59 class Global 60 { 61 public: 62 63 Global() 64 { 65 /* Check the parser version. The docs say it will kill the app if 66 * there is a serious version mismatch, but I couldn't find it in the 67 * source code (it only prints the error/warning message to the console) so 68 * let's leave it as is for informational purposes. */ 69 LIBXML_TEST_VERSION 70 71 /* Init libxml */ 72 xmlInitParser(); 73 74 /* Save the default entity resolver before someone has replaced it */ 75 sxml.defaultEntityLoader = xmlGetExternalEntityLoader(); 76 } 77 78 ~Global() 79 { 80 /* Shutdown libxml */ 81 xmlCleanupParser(); 82 } 83 84 struct 85 { 86 xmlExternalEntityLoader defaultEntityLoader; 87 88 /** Used to provide some thread safety missing in libxml2 (see e.g. 89 * XmlTreeBackend::read()) */ 90 RTLockMtx lock; 91 } 92 sxml; /* XXX naming this xml will break with gcc-3.3 */ 93 } 94 gGlobal; 95 96 97 98 namespace xml 99 { 100 101 //////////////////////////////////////////////////////////////////////////////// 102 // 103 // Exceptions 104 // 105 //////////////////////////////////////////////////////////////////////////////// 106 107 LogicError::LogicError(RT_SRC_POS_DECL) 108 : Error(NULL) 109 { 110 char *msg = NULL; 111 RTStrAPrintf(&msg, "In '%s', '%s' at #%d", 112 pszFunction, pszFile, iLine); 113 setWhat(msg); 114 RTStrFree(msg); 115 } 116 117 XmlError::XmlError(xmlErrorPtr aErr) 118 { 119 if (!aErr) 120 throw EInvalidArg(RT_SRC_POS); 121 122 char *msg = Format(aErr); 123 setWhat(msg); 124 RTStrFree(msg); 125 } 126 127 /** 128 * Composes a single message for the given error. The caller must free the 129 * returned string using RTStrFree() when no more necessary. 130 */ 131 // static 132 char *XmlError::Format(xmlErrorPtr aErr) 133 { 134 const char *msg = aErr->message ? aErr->message : "<none>"; 135 size_t msgLen = strlen(msg); 136 /* strip spaces, trailing EOLs and dot-like char */ 137 while (msgLen && strchr(" \n.?!", msg [msgLen - 1])) 138 --msgLen; 139 140 char *finalMsg = NULL; 141 RTStrAPrintf(&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d", 142 msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2); 143 144 return finalMsg; 145 } 146 147 EIPRTFailure::EIPRTFailure(int aRC) 148 : RuntimeError(NULL), 149 mRC(aRC) 150 { 151 char *newMsg = NULL; 152 RTStrAPrintf(&newMsg, "Runtime error: %d (%s)", aRC, RTErrGetShort(aRC)); 153 setWhat(newMsg); 154 RTStrFree(newMsg); 155 } 156 157 //////////////////////////////////////////////////////////////////////////////// 158 // 159 // File Class 160 // 161 ////////////////////////////////////////////////////////////////////////////// 162 163 struct File::Data 164 { 165 Data() 166 : fileName (NULL), handle (NIL_RTFILE), opened (false) {} 167 168 char *fileName; 169 RTFILE handle; 170 bool opened : 1; 171 }; 172 173 File::File(Mode aMode, const char *aFileName) 174 : m (new Data()) 175 { 176 m->fileName = RTStrDup (aFileName); 177 if (m->fileName == NULL) 178 throw ENoMemory(); 179 180 unsigned flags = 0; 181 switch (aMode) 182 { 183 case Mode_Read: 184 flags = RTFILE_O_READ; 185 break; 186 case Mode_WriteCreate: // fail if file exists 187 flags = RTFILE_O_WRITE | RTFILE_O_CREATE; 188 break; 189 case Mode_Overwrite: // overwrite if file exists 190 flags = RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE; 191 break; 192 case Mode_ReadWrite: 193 flags = RTFILE_O_READ | RTFILE_O_WRITE; 194 } 195 196 int vrc = RTFileOpen (&m->handle, aFileName, flags); 197 if (RT_FAILURE (vrc)) 198 throw EIPRTFailure (vrc); 199 200 m->opened = true; 201 } 202 203 File::File (RTFILE aHandle, const char *aFileName /* = NULL */) 204 : m (new Data()) 205 { 206 if (aHandle == NIL_RTFILE) 207 throw EInvalidArg (RT_SRC_POS); 208 209 m->handle = aHandle; 210 211 if (aFileName) 212 { 213 m->fileName = RTStrDup (aFileName); 214 if (m->fileName == NULL) 215 throw ENoMemory(); 216 } 217 218 setPos (0); 219 } 220 221 File::~File() 222 { 223 if (m->opened) 224 RTFileClose (m->handle); 225 226 RTStrFree (m->fileName); 227 } 228 229 const char *File::uri() const 230 { 231 return m->fileName; 232 } 233 234 uint64_t File::pos() const 235 { 236 uint64_t p = 0; 237 int vrc = RTFileSeek (m->handle, 0, RTFILE_SEEK_CURRENT, &p); 238 if (RT_SUCCESS (vrc)) 239 return p; 240 241 throw EIPRTFailure (vrc); 242 } 243 244 void File::setPos (uint64_t aPos) 245 { 246 uint64_t p = 0; 247 unsigned method = RTFILE_SEEK_BEGIN; 248 int vrc = VINF_SUCCESS; 249 250 /* check if we overflow int64_t and move to INT64_MAX first */ 251 if (((int64_t) aPos) < 0) 252 { 253 vrc = RTFileSeek (m->handle, INT64_MAX, method, &p); 254 aPos -= (uint64_t) INT64_MAX; 255 method = RTFILE_SEEK_CURRENT; 256 } 257 /* seek the rest */ 258 if (RT_SUCCESS (vrc)) 259 vrc = RTFileSeek (m->handle, (int64_t) aPos, method, &p); 260 if (RT_SUCCESS (vrc)) 261 return; 262 263 throw EIPRTFailure (vrc); 264 } 265 266 int File::read (char *aBuf, int aLen) 267 { 268 size_t len = aLen; 269 int vrc = RTFileRead (m->handle, aBuf, len, &len); 270 if (RT_SUCCESS (vrc)) 271 return (int)len; 272 273 throw EIPRTFailure (vrc); 274 } 275 276 int File::write (const char *aBuf, int aLen) 277 { 278 size_t len = aLen; 279 int vrc = RTFileWrite (m->handle, aBuf, len, &len); 280 if (RT_SUCCESS (vrc)) 281 return (int)len; 282 283 throw EIPRTFailure (vrc); 284 285 return -1 /* failure */; 286 } 287 288 void File::truncate() 289 { 290 int vrc = RTFileSetSize (m->handle, pos()); 291 if (RT_SUCCESS (vrc)) 292 return; 293 294 throw EIPRTFailure (vrc); 295 } 296 297 //////////////////////////////////////////////////////////////////////////////// 298 // 299 // MemoryBuf Class 300 // 301 ////////////////////////////////////////////////////////////////////////////// 302 303 struct MemoryBuf::Data 304 { 305 Data() 306 : buf (NULL), len (0), uri (NULL), pos (0) {} 307 308 const char *buf; 309 size_t len; 310 char *uri; 311 312 size_t pos; 313 }; 314 315 MemoryBuf::MemoryBuf (const char *aBuf, size_t aLen, const char *aURI /* = NULL */) 316 : m (new Data()) 317 { 318 if (aBuf == NULL) 319 throw EInvalidArg (RT_SRC_POS); 320 321 m->buf = aBuf; 322 m->len = aLen; 323 m->uri = RTStrDup (aURI); 324 } 325 326 MemoryBuf::~MemoryBuf() 327 { 328 RTStrFree (m->uri); 329 } 330 331 const char *MemoryBuf::uri() const 332 { 333 return m->uri; 334 } 335 336 uint64_t MemoryBuf::pos() const 337 { 338 return m->pos; 339 } 340 341 void MemoryBuf::setPos (uint64_t aPos) 342 { 343 size_t pos = (size_t) aPos; 344 if ((uint64_t) pos != aPos) 345 throw EInvalidArg(); 346 347 if (pos > m->len) 348 throw EInvalidArg(); 349 350 m->pos = pos; 351 } 352 353 int MemoryBuf::read (char *aBuf, int aLen) 354 { 355 if (m->pos >= m->len) 356 return 0 /* nothing to read */; 357 358 size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos; 359 memcpy (aBuf, m->buf + m->pos, len); 360 m->pos += len; 361 362 return (int)len; 363 } 364 365 //////////////////////////////////////////////////////////////////////////////// 366 // 367 // GlobalLock class 368 // 369 //////////////////////////////////////////////////////////////////////////////// 370 371 struct GlobalLock::Data 372 { 373 PFNEXTERNALENTITYLOADER pOldLoader; 374 RTLock lock; 375 376 Data() 377 : pOldLoader(NULL), 378 lock(gGlobal.sxml.lock) 379 { 380 } 381 }; 382 383 GlobalLock::GlobalLock() 384 : m(new Data()) 385 { 386 } 387 388 GlobalLock::~GlobalLock() 389 { 390 if (m->pOldLoader) 391 xmlSetExternalEntityLoader(m->pOldLoader); 392 delete m; 393 m = NULL; 394 } 395 396 void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pLoader) 397 { 398 m->pOldLoader = xmlGetExternalEntityLoader(); 399 xmlSetExternalEntityLoader(pLoader); 400 } 401 402 // static 403 xmlParserInput* GlobalLock::callDefaultLoader(const char *aURI, 404 const char *aID, 405 xmlParserCtxt *aCtxt) 406 { 407 return gGlobal.sxml.defaultEntityLoader(aURI, aID, aCtxt); 408 } 409 410 //////////////////////////////////////////////////////////////////////////////// 411 // 412 // Node class 413 // 414 //////////////////////////////////////////////////////////////////////////////// 415 416 struct Node::Data 417 { 418 xmlNode *plibNode; // != NULL if this is an element or content node 419 xmlAttr *plibAttr; // != NULL if this is an attribute node 420 421 Node *pParent; // NULL only for the root element 422 const char *pcszName; // element or attribute name, points either into plibNode or plibAttr; 423 // NULL if this is a content node 424 425 struct compare_const_char 426 { 427 bool operator()(const char* s1, const char* s2) const 428 { 429 return strcmp(s1, s2) < 0; 430 } 431 }; 432 433 // attributes, if this is an element; can be empty 434 typedef std::map<const char*, boost::shared_ptr<AttributeNode>, compare_const_char > AttributesMap; 435 AttributesMap attribs; 436 437 // child elements, if this is an element; can be empty 438 typedef std::list< boost::shared_ptr<Node> > InternalNodesList; 439 InternalNodesList children; 440 }; 441 442 Node::Node(EnumType type) 443 : mType(type), 444 m(new Data) 445 { 446 m->plibNode = NULL; 447 m->plibAttr = NULL; 448 m->pParent = NULL; 449 } 450 451 Node::~Node() 452 { 453 delete m; 454 } 455 456 void Node::buildChildren() // private 457 { 458 // go thru this element's attributes 459 xmlAttr *plibAttr = m->plibNode->properties; 460 while (plibAttr) 461 { 462 const char *pcszAttribName = (const char*)plibAttr->name; 463 boost::shared_ptr<AttributeNode> pNew(new AttributeNode); 464 pNew->m->plibAttr = plibAttr; 465 pNew->m->pcszName = (const char*)plibAttr->name; 466 pNew->m->pParent = this; 467 // store 468 m->attribs[pcszAttribName] = pNew; 469 470 plibAttr = plibAttr->next; 471 } 472 473 // go thru this element's child elements 474 xmlNodePtr plibNode = m->plibNode->children; 475 while (plibNode) 476 { 477 boost::shared_ptr<Node> pNew; 478 479 if (plibNode->name) 480 pNew = boost::shared_ptr<Node>(new ElementNode); 481 else 482 pNew = boost::shared_ptr<Node>(new ContentNode); 483 484 pNew->m->plibNode = plibNode; 485 pNew->m->pcszName = (const char*)plibNode->name; 486 pNew->m->pParent = this; 487 // store 488 m->children.push_back(pNew); 489 490 // recurse for this child element to get its own children 491 pNew->buildChildren(); 492 493 plibNode = plibNode->next; 494 } 495 } 496 497 const char* Node::getName() const 498 { 499 return m->pcszName; 500 } 501 502 /** 503 * Returns the value of a node. If this node is an attribute, returns 504 * the attribute value; if this node is an element, then this returns 505 * the element text content. 506 * @return 507 */ 508 const char* Node::getValue() const 509 { 510 if ( (m->plibAttr) 511 && (m->plibAttr->children) 512 ) 513 // libxml hides attribute values in another node created as a 514 // single child of the attribute node, and it's in the content field 515 return (const char*)m->plibAttr->children->content; 516 517 if ( (m->plibNode) 518 && (m->plibNode->children) 519 ) 520 return (const char*)m->plibNode->children->content; 521 522 return NULL; 523 } 524 525 /** 526 * Copies the value of a node into the given integer variable. 527 * Returns TRUE only if a value was found and was actually an 528 * integer of the given type. 529 * @return 530 */ 531 bool Node::copyValue(int32_t &i) const 532 { 533 const char *pcsz; 534 if ( ((pcsz = getValue())) 535 && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i)) 536 ) 537 return true; 538 539 return false; 540 } 541 542 /** 543 * Copies the value of a node into the given integer variable. 544 * Returns TRUE only if a value was found and was actually an 545 * integer of the given type. 546 * @return 547 */ 548 bool Node::copyValue(uint32_t &i) const 549 { 550 const char *pcsz; 551 if ( ((pcsz = getValue())) 552 && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i)) 553 ) 554 return true; 555 556 return false; 557 } 558 559 /** 560 * Copies the value of a node into the given integer variable. 561 * Returns TRUE only if a value was found and was actually an 562 * integer of the given type. 563 * @return 564 */ 565 bool Node::copyValue(int64_t &i) const 566 { 567 const char *pcsz; 568 if ( ((pcsz = getValue())) 569 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i)) 570 ) 571 return true; 572 573 return false; 574 } 575 576 /** 577 * Copies the value of a node into the given integer variable. 578 * Returns TRUE only if a value was found and was actually an 579 * integer of the given type. 580 * @return 581 */ 582 bool Node::copyValue(uint64_t &i) const 583 { 584 const char *pcsz; 585 if ( ((pcsz = getValue())) 586 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i)) 587 ) 588 return true; 589 590 return false; 591 } 592 593 /** 594 * Returns the line number of the current node in the source XML file. 595 * Useful for error messages. 596 * @return 597 */ 598 int Node::getLineNumber() const 599 { 600 if (m->plibAttr) 601 return m->pParent->m->plibNode->line; 602 603 return m->plibNode->line; 604 } 605 606 ElementNode::ElementNode() 607 : Node(IsElement) 608 { 609 } 610 611 /** 612 * Builds a list of direct child elements of the current element that 613 * match the given string; if pcszMatch is NULL, all direct child 614 * elements are returned. 615 * @param children out: list of nodes to which children will be appended. 616 * @param pcszMatch in: match string, or NULL to return all children. 617 * @return Number of items appended to the list (0 if none). 618 */ 619 int ElementNode::getChildElements(ElementNodesList &children, 620 const char *pcszMatch /*= NULL*/) 621 const 622 { 623 int i = 0; 624 Data::InternalNodesList::const_iterator 625 it, 626 last = m->children.end(); 627 for (it = m->children.begin(); 628 it != last; 629 ++it) 630 { 631 // export this child node if ... 632 if ( (!pcszMatch) // the caller wants all nodes or 633 || (!strcmp(pcszMatch, (**it).getName())) // the element name matches 634 ) 635 { 636 Node *pNode = (*it).get(); 637 if (pNode->isElement()) 638 children.push_back(static_cast<ElementNode*>(pNode)); 639 ++i; 640 } 641 } 642 return i; 643 } 644 645 /** 646 * Returns the first child element whose name matches pcszMatch. 647 * @param pcszMatch 648 * @return 649 */ 650 const ElementNode* ElementNode::findChildElement(const char *pcszMatch) 651 const 652 { 653 Data::InternalNodesList::const_iterator 654 it, 655 last = m->children.end(); 656 for (it = m->children.begin(); 657 it != last; 658 ++it) 659 { 660 if ((**it).isElement()) 661 { 662 ElementNode *pelm = static_cast<ElementNode*>((*it).get()); 663 if (!strcmp(pcszMatch, pelm->getName())) // the element name matches 664 return pelm; 665 } 666 } 667 668 return NULL; 669 } 670 671 /** 672 * Returns the first child element whose "id" attribute matches pcszId. 673 * @param pcszId identifier to look for. 674 * @return child element or NULL if not found. 675 */ 676 const ElementNode* ElementNode::findChildElementFromId(const char *pcszId) const 677 { 678 Data::InternalNodesList::const_iterator 679 it, 680 last = m->children.end(); 681 for (it = m->children.begin(); 682 it != last; 683 ++it) 684 { 685 if ((**it).isElement()) 686 { 687 ElementNode *pelm = static_cast<ElementNode*>((*it).get()); 688 const AttributeNode *pAttr; 689 if ( ((pAttr = pelm->findAttribute("id"))) 690 && (!strcmp(pAttr->getValue(), pcszId)) 691 ) 692 return pelm; 693 } 694 } 695 696 return NULL; 697 } 698 699 /** 700 * 701 * @param pcszMatch 702 * @return 703 */ 704 const AttributeNode* ElementNode::findAttribute(const char *pcszMatch) const 705 { 706 Data::AttributesMap::const_iterator it; 707 708 it = m->attribs.find(pcszMatch); 709 if (it != m->attribs.end()) 710 return it->second.get(); 711 712 return NULL; 713 } 714 715 /** 716 * Convenience method which attempts to find the attribute with the given 717 * name and returns its value as a string. 718 * 719 * @param pcszMatch name of attribute to find. 720 * @param str out: attribute value 721 * @return TRUE if attribute was found and str was thus updated. 722 */ 723 bool ElementNode::getAttributeValue(const char *pcszMatch, const char *&ppcsz) const 724 { 725 const Node* pAttr; 726 if ((pAttr = findAttribute(pcszMatch))) 727 { 728 ppcsz = pAttr->getValue(); 729 return true; 730 } 731 732 return false; 733 } 734 735 /** 736 * Convenience method which attempts to find the attribute with the given 737 * name and returns its value as a signed long integer. This calls 738 * RTStrToInt64Ex internally and will only output the integer if that 739 * function returns no error. 740 * 741 * @param pcszMatch name of attribute to find. 742 * @param i out: attribute value 743 * @return TRUE if attribute was found and str was thus updated. 744 */ 745 bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t &i) const 746 { 747 const char *pcsz; 748 if ( (getAttributeValue(pcszMatch, pcsz)) 749 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i)) 750 ) 751 return true; 752 753 return false; 754 } 755 756 /** 757 * Convenience method which attempts to find the attribute with the given 758 * name and returns its value as an unsigned long integer.This calls 759 * RTStrToUInt64Ex internally and will only output the integer if that 760 * function returns no error. 761 * 762 * @param pcszMatch name of attribute to find. 763 * @param i out: attribute value 764 * @return TRUE if attribute was found and str was thus updated. 765 */ 766 bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t &i) const 767 { 768 const char *pcsz; 769 if ( (getAttributeValue(pcszMatch, pcsz)) 770 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i)) 771 ) 772 return true; 773 774 return false; 775 } 776 777 /** 778 * Creates a new child element node and appends it to the list 779 * of children in "this". 780 * 781 * @param pcszElementName 782 * @return 783 */ 784 ElementNode* ElementNode::createChild(const char *pcszElementName) 785 { 786 // we must be an element, not an attribute 787 if (!m->plibNode) 788 throw ENodeIsNotElement(RT_SRC_POS); 789 790 // libxml side: create new node 791 xmlNode *plibNode; 792 if (!(plibNode = xmlNewNode(NULL, // namespace 793 (const xmlChar*)pcszElementName))) 794 throw ENoMemory(); 795 xmlAddChild(m->plibNode, plibNode); 796 797 // now wrap this in C++ 798 ElementNode *p = new ElementNode; 799 boost::shared_ptr<ElementNode> pNew(p); 800 pNew->m->plibNode = plibNode; 801 pNew->m->pcszName = (const char*)plibNode->name; 802 803 m->children.push_back(pNew); 804 805 return p; 806 } 807 808 809 /** 810 * Creates a content node and appends it to the list of children 811 * in "this". 812 * 813 * @param pcszElementName 814 * @return 815 */ 816 ContentNode* ElementNode::addContent(const char *pcszContent) 817 { 818 // libxml side: create new node 819 xmlNode *plibNode; 820 if (!(plibNode = xmlNewText((const xmlChar*)pcszContent))) 821 throw ENoMemory(); 822 xmlAddChild(m->plibNode, plibNode); 823 824 // now wrap this in C++ 825 ContentNode *p = new ContentNode; 826 boost::shared_ptr<ContentNode> pNew(p); 827 pNew->m->plibNode = plibNode; 828 pNew->m->pcszName = NULL; 829 830 m->children.push_back(pNew); 831 832 return p; 833 } 834 835 /** 836 * Sets the given attribute. 837 * 838 * If an attribute with the given name exists, it is overwritten, 839 * otherwise a new attribute is created. Returns the attribute node 840 * that was either created or changed. 841 * 842 * @param pcszName 843 * @param pcszValue 844 * @return 845 */ 846 AttributeNode* ElementNode::setAttribute(const char *pcszName, const char *pcszValue) 847 { 848 Data::AttributesMap::const_iterator it; 849 850 it = m->attribs.find(pcszName); 851 if (it == m->attribs.end()) 852 { 853 // libxml side: xmlNewProp creates an attribute 854 xmlAttr *plibAttr = xmlNewProp(m->plibNode, (xmlChar*)pcszName, (xmlChar*)pcszValue); 855 const char *pcszAttribName = (const char*)plibAttr->name; 856 857 // C++ side: create an attribute node around it 858 boost::shared_ptr<AttributeNode> pNew(new AttributeNode); 859 pNew->m->plibAttr = plibAttr; 860 pNew->m->pcszName = (const char*)plibAttr->name; 861 pNew->m->pParent = this; 862 // store 863 m->attribs[pcszAttribName] = pNew; 864 } 865 else 866 { 867 // @todo 868 throw LogicError("Attribute exists"); 869 } 870 871 return NULL; 872 873 } 874 875 876 AttributeNode::AttributeNode() 877 : Node(IsAttribute) 878 { 879 } 880 881 ContentNode::ContentNode() 882 : Node(IsContent) 883 { 884 } 885 886 /* 887 * NodesLoop 888 * 889 */ 890 891 struct NodesLoop::Data 892 { 893 ElementNodesList listElements; 894 ElementNodesList::const_iterator it; 895 }; 896 897 NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */) 898 { 899 m = new Data; 900 node.getChildElements(m->listElements, pcszMatch); 901 m->it = m->listElements.begin(); 902 } 903 904 NodesLoop::~NodesLoop() 905 { 906 delete m; 907 } 908 909 910 /** 911 * Handy convenience helper for looping over all child elements. Create an 912 * instance of NodesLoop on the stack and call this method until it returns 913 * NULL, like this: 914 * <code> 915 * xml::Node node; // should point to an element 916 * xml::NodesLoop loop(node, "child"); // find all "child" elements under node 917 * const xml::Node *pChild = NULL; 918 * while (pChild = loop.forAllNodes()) 919 * ...; 920 * </code> 921 * @param node 922 * @param pcszMatch 923 * @return 924 */ 925 const ElementNode* NodesLoop::forAllNodes() const 926 { 927 const ElementNode *pNode = NULL; 928 929 if (m->it != m->listElements.end()) 930 { 931 pNode = *(m->it); 932 ++(m->it); 933 } 934 935 return pNode; 936 } 937 938 //////////////////////////////////////////////////////////////////////////////// 939 // 940 // Document class 941 // 942 //////////////////////////////////////////////////////////////////////////////// 943 944 struct Document::Data 945 { 946 xmlDocPtr plibDocument; 947 ElementNode *pRootElement; 948 949 Data() 950 { 951 plibDocument = NULL; 952 pRootElement = NULL; 953 } 954 955 ~Data() 956 { 957 reset(); 958 } 959 960 void reset() 961 { 962 if (plibDocument) 963 { 964 xmlFreeDoc(plibDocument); 965 plibDocument = NULL; 966 } 967 if (pRootElement) 968 { 969 delete pRootElement; 970 pRootElement = NULL; 971 } 972 } 973 974 void copyFrom(const Document::Data *p) 975 { 976 if (p->plibDocument) 977 { 978 plibDocument = xmlCopyDoc(p->plibDocument, 979 1); // recursive == copy all 980 } 981 } 982 }; 983 984 Document::Document() 985 : m(new Data) 986 { 987 } 988 989 Document::Document(const Document &x) 990 : m(new Data) 991 { 992 m->copyFrom(x.m); 993 }; 994 995 Document& Document::operator=(const Document &x) 996 { 997 m->reset(); 998 m->copyFrom(x.m); 999 return *this; 1000 }; 1001 1002 Document::~Document() 1003 { 1004 delete m; 1005 } 1006 1007 /** 1008 * private method to refresh all internal structures after the internal pDocument 1009 * has changed. Called from XmlFileParser::read(). m->reset() must have been 1010 * called before to make sure all members except the internal pDocument are clean. 1011 */ 1012 void Document::refreshInternals() // private 1013 { 1014 m->pRootElement = new ElementNode(); 1015 m->pRootElement->m->plibNode = xmlDocGetRootElement(m->plibDocument); 1016 m->pRootElement->m->pcszName = (const char*)m->pRootElement->m->plibNode->name; 1017 1018 m->pRootElement->buildChildren(); 1019 } 1020 1021 /** 1022 * Returns the root element of the document, or NULL if the document is empty. 1023 * @return 1024 */ 1025 const ElementNode* Document::getRootElement() const 1026 { 1027 return m->pRootElement; 1028 } 1029 1030 /** 1031 * Creates a new element node and sets it as the root element. This will 1032 * only work if the document is empty; otherwise EDocumentNotEmpty is thrown. 1033 */ 1034 ElementNode* Document::createRootElement(const char *pcszRootElementName) 1035 { 1036 if (m->pRootElement || m->plibDocument) 1037 throw EDocumentNotEmpty(RT_SRC_POS); 1038 1039 // libxml side: create document, create root node 1040 m->plibDocument = xmlNewDoc((const xmlChar*)"1.0"); 1041 xmlNode *plibRootNode; 1042 if (!(plibRootNode = xmlNewNode(NULL, // namespace 1043 (const xmlChar*)pcszRootElementName))) 1044 throw ENoMemory(); 1045 xmlDocSetRootElement(m->plibDocument, plibRootNode); 1046 1047 // now wrap this in C++ 1048 m->pRootElement = new ElementNode(); 1049 m->pRootElement->m->plibNode = plibRootNode; 1050 m->pRootElement->m->pcszName = (const char*)plibRootNode->name; 1051 1052 return m->pRootElement; 1053 } 1054 1055 //////////////////////////////////////////////////////////////////////////////// 1056 // 1057 // XmlParserBase class 1058 // 1059 //////////////////////////////////////////////////////////////////////////////// 1060 1061 XmlParserBase::XmlParserBase() 1062 { 1063 m_ctxt = xmlNewParserCtxt(); 1064 if (m_ctxt == NULL) 1065 throw ENoMemory(); 1066 } 1067 1068 XmlParserBase::~XmlParserBase() 1069 { 1070 xmlFreeParserCtxt (m_ctxt); 1071 m_ctxt = NULL; 1072 } 1073 1074 //////////////////////////////////////////////////////////////////////////////// 1075 // 1076 // XmlFileParser class 1077 // 1078 //////////////////////////////////////////////////////////////////////////////// 1079 1080 struct XmlFileParser::Data 1081 { 1082 xmlParserCtxtPtr ctxt; 1083 ministring strXmlFilename; 1084 1085 Data() 1086 { 1087 if (!(ctxt = xmlNewParserCtxt())) 1088 throw xml::ENoMemory(); 1089 } 1090 1091 ~Data() 1092 { 1093 xmlFreeParserCtxt(ctxt); 1094 ctxt = NULL; 1095 } 1096 }; 1097 1098 XmlFileParser::XmlFileParser() 1099 : XmlParserBase(), 1100 m(new Data()) 1101 { 1102 } 1103 1104 XmlFileParser::~XmlFileParser() 1105 { 1106 delete m; 1107 m = NULL; 1108 } 1109 1110 struct IOContext 1111 { 1112 File file; 1113 ministring error; 1114 1115 IOContext(const char *pcszFilename, File::Mode mode) 1116 : file(mode, pcszFilename) 1117 { 1118 } 1119 1120 void setError(const xml::Error &x) 1121 { 1122 error = x.what(); 1123 } 1124 1125 void setError(const std::exception &x) 1126 { 1127 error = x.what(); 1128 } 1129 }; 1130 1131 struct ReadContext : IOContext 1132 { 1133 ReadContext(const char *pcszFilename) 1134 : IOContext(pcszFilename, File::Mode_Read) 1135 { 1136 } 1137 }; 1138 1139 struct WriteContext : IOContext 1140 { 1141 WriteContext(const char *pcszFilename) 1142 : IOContext(pcszFilename, File::Mode_Overwrite) 1143 { 1144 } 1145 }; 1146 1147 /** 1148 * Reads the given file and fills the given Document object with its contents. 1149 * Throws XmlError on parsing errors. 1150 * 1151 * The document that is passed in will be reset before being filled if not empty. 1152 * 1153 * @param pcszFilename in: name fo file to parse. 1154 * @param doc out: document to be reset and filled with data according to file contents. 1155 */ 1156 void XmlFileParser::read(const char *pcszFilename, 1157 Document &doc) 1158 { 1159 GlobalLock lock; 1160 // global.setExternalEntityLoader(ExternalEntityLoader); 1161 1162 m->strXmlFilename = pcszFilename; 1163 1164 ReadContext context(pcszFilename); 1165 doc.m->reset(); 1166 if (!(doc.m->plibDocument = xmlCtxtReadIO(m->ctxt, 1167 ReadCallback, 1168 CloseCallback, 1169 &context, 1170 pcszFilename, 1171 NULL, // encoding = auto 1172 XML_PARSE_NOBLANKS))) 1173 throw XmlError(xmlCtxtGetLastError(m->ctxt)); 1174 1175 doc.refreshInternals(); 1176 } 1177 1178 // static 1179 int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen) 1180 { 1181 ReadContext *pContext = static_cast<ReadContext*>(aCtxt); 1182 1183 /* To prevent throwing exceptions while inside libxml2 code, we catch 1184 * them and forward to our level using a couple of variables. */ 1185 1186 try 1187 { 1188 return pContext->file.read(aBuf, aLen); 1189 } 1190 catch (const xml::EIPRTFailure &err) { pContext->setError(err); } 1191 catch (const xml::Error &err) { pContext->setError(err); } 1192 catch (const std::exception &err) { pContext->setError(err); } 1193 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); } 1194 1195 return -1 /* failure */; 1196 } 1197 1198 int XmlFileParser::CloseCallback(void *aCtxt) 1199 { 1200 /// @todo to be written 1201 1202 return -1; 1203 } 1204 1205 //////////////////////////////////////////////////////////////////////////////// 1206 // 1207 // XmlFileWriter class 1208 // 1209 //////////////////////////////////////////////////////////////////////////////// 1210 1211 struct XmlFileWriter::Data 1212 { 1213 Document *pDoc; 1214 }; 1215 1216 XmlFileWriter::XmlFileWriter(Document &doc) 1217 { 1218 m = new Data(); 1219 m->pDoc = &doc; 1220 } 1221 1222 XmlFileWriter::~XmlFileWriter() 1223 { 1224 delete m; 1225 } 1226 1227 void XmlFileWriter::write(const char *pcszFilename) 1228 { 1229 WriteContext context(pcszFilename); 1230 1231 GlobalLock lock; 1232 1233 /* serialize to the stream */ 1234 xmlIndentTreeOutput = 1; 1235 xmlTreeIndentString = " "; 1236 xmlSaveNoEmptyTags = 0; 1237 1238 xmlSaveCtxtPtr saveCtxt; 1239 if (!(saveCtxt = xmlSaveToIO(WriteCallback, 1240 CloseCallback, 1241 &context, 1242 NULL, 1243 XML_SAVE_FORMAT))) 1244 throw xml::LogicError(RT_SRC_POS); 1245 1246 long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument); 1247 if (rc == -1) 1248 { 1249 /* look if there was a forwared exception from the lower level */ 1250 // if (m->trappedErr.get() != NULL) 1251 // m->trappedErr->rethrow(); 1252 1253 /* there must be an exception from the Output implementation, 1254 * otherwise the save operation must always succeed. */ 1255 throw xml::LogicError(RT_SRC_POS); 1256 } 1257 1258 xmlSaveClose(saveCtxt); 1259 } 1260 1261 int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen) 1262 { 1263 WriteContext *pContext = static_cast<WriteContext*>(aCtxt); 1264 1265 /* To prevent throwing exceptions while inside libxml2 code, we catch 1266 * them and forward to our level using a couple of variables. */ 1267 try 1268 { 1269 return pContext->file.write(aBuf, aLen); 1270 } 1271 catch (const xml::EIPRTFailure &err) { pContext->setError(err); } 1272 catch (const xml::Error &err) { pContext->setError(err); } 1273 catch (const std::exception &err) { pContext->setError(err); } 1274 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); } 1275 1276 return -1 /* failure */; 1277 } 1278 1279 int XmlFileWriter::CloseCallback(void *aCtxt) 1280 { 1281 /// @todo to be written 1282 1283 return -1; 1284 } 1285 1286 1287 } // end namespace xml 1288 1289
Note:
See TracChangeset
for help on using the changeset viewer.