1/* 2 * "$Id: mxml-node.c 436 2011-01-22 01:02:05Z mike $" 3 * 4 * Node support code for Mini-XML, a small XML-like file parsing library. 5 * 6 * Copyright 2003-2011 by Michael R Sweet. 7 * 8 * These coded instructions, statements, and computer programs are the 9 * property of Michael R Sweet and are protected by Federal copyright 10 * law. Distribution and use rights are outlined in the file "COPYING" 11 * which should have been included with this file. If this file is 12 * missing or damaged, see the license at: 13 * 14 * http://www.minixml.org/ 15 * 16 * Contents: 17 * 18 * mxmlAdd() - Add a node to a tree. 19 * mxmlDelete() - Delete a node and all of its children. 20 * mxmlGetRefCount() - Get the current reference (use) count for a node. 21 * mxmlNewCDATA() - Create a new CDATA node. 22 * mxmlNewCustom() - Create a new custom data node. 23 * mxmlNewElement() - Create a new element node. 24 * mxmlNewInteger() - Create a new integer node. 25 * mxmlNewOpaque() - Create a new opaque string. 26 * mxmlNewReal() - Create a new real number node. 27 * mxmlNewText() - Create a new text fragment node. 28 * mxmlNewTextf() - Create a new formatted text fragment node. 29 * mxmlRemove() - Remove a node from its parent. 30 * mxmlNewXML() - Create a new XML document tree. 31 * mxmlRelease() - Release a node. 32 * mxmlRetain() - Retain a node. 33 * mxml_new() - Create a new node. 34 */ 35 36/* 37 * Include necessary headers... 38 */ 39 40#include "config.h" 41#include "mxml.h" 42 43 44/* 45 * Local functions... 46 */ 47 48static mxml_node_t *mxml_new(mxml_node_t *parent, mxml_type_t type); 49 50 51/* 52 * 'mxmlAdd()' - Add a node to a tree. 53 * 54 * Adds the specified node to the parent. If the child argument is not 55 * NULL, puts the new node before or after the specified child depending 56 * on the value of the where argument. If the child argument is NULL, 57 * puts the new node at the beginning of the child list (MXML_ADD_BEFORE) 58 * or at the end of the child list (MXML_ADD_AFTER). The constant 59 * MXML_ADD_TO_PARENT can be used to specify a NULL child pointer. 60 */ 61 62void 63mxmlAdd(mxml_node_t *parent, /* I - Parent node */ 64 int where, /* I - Where to add, MXML_ADD_BEFORE or MXML_ADD_AFTER */ 65 mxml_node_t *child, /* I - Child node for where or MXML_ADD_TO_PARENT */ 66 mxml_node_t *node) /* I - Node to add */ 67{ 68#ifdef DEBUG 69 fprintf(stderr, "mxmlAdd(parent=%p, where=%d, child=%p, node=%p)\n", parent, 70 where, child, node); 71#endif /* DEBUG */ 72 73 /* 74 * Range check input... 75 */ 76 77 if (!parent || !node) 78 return; 79 80#if DEBUG > 1 81 fprintf(stderr, " BEFORE: node->parent=%p\n", node->parent); 82 if (parent) 83 { 84 fprintf(stderr, " BEFORE: parent->child=%p\n", parent->child); 85 fprintf(stderr, " BEFORE: parent->last_child=%p\n", parent->last_child); 86 fprintf(stderr, " BEFORE: parent->prev=%p\n", parent->prev); 87 fprintf(stderr, " BEFORE: parent->next=%p\n", parent->next); 88 } 89#endif /* DEBUG > 1 */ 90 91 /* 92 * Remove the node from any existing parent... 93 */ 94 95 if (node->parent) 96 mxmlRemove(node); 97 98 /* 99 * Reset pointers... 100 */ 101 102 node->parent = parent; 103 104 switch (where) 105 { 106 case MXML_ADD_BEFORE : 107 if (!child || child == parent->child || child->parent != parent) 108 { 109 /* 110 * Insert as first node under parent... 111 */ 112 113 node->next = parent->child; 114 115 if (parent->child) 116 parent->child->prev = node; 117 else 118 parent->last_child = node; 119 120 parent->child = node; 121 } 122 else 123 { 124 /* 125 * Insert node before this child... 126 */ 127 128 node->next = child; 129 node->prev = child->prev; 130 131 if (child->prev) 132 child->prev->next = node; 133 else 134 parent->child = node; 135 136 child->prev = node; 137 } 138 break; 139 140 case MXML_ADD_AFTER : 141 if (!child || child == parent->last_child || child->parent != parent) 142 { 143 /* 144 * Insert as last node under parent... 145 */ 146 147 node->parent = parent; 148 node->prev = parent->last_child; 149 150 if (parent->last_child) 151 parent->last_child->next = node; 152 else 153 parent->child = node; 154 155 parent->last_child = node; 156 } 157 else 158 { 159 /* 160 * Insert node after this child... 161 */ 162 163 node->prev = child; 164 node->next = child->next; 165 166 if (child->next) 167 child->next->prev = node; 168 else 169 parent->last_child = node; 170 171 child->next = node; 172 } 173 break; 174 } 175 176#if DEBUG > 1 177 fprintf(stderr, " AFTER: node->parent=%p\n", node->parent); 178 if (parent) 179 { 180 fprintf(stderr, " AFTER: parent->child=%p\n", parent->child); 181 fprintf(stderr, " AFTER: parent->last_child=%p\n", parent->last_child); 182 fprintf(stderr, " AFTER: parent->prev=%p\n", parent->prev); 183 fprintf(stderr, " AFTER: parent->next=%p\n", parent->next); 184 } 185#endif /* DEBUG > 1 */ 186} 187 188 189/* 190 * 'mxmlDelete()' - Delete a node and all of its children. 191 * 192 * If the specified node has a parent, this function first removes the 193 * node from its parent using the mxmlRemove() function. 194 */ 195 196void 197mxmlDelete(mxml_node_t *node) /* I - Node to delete */ 198{ 199 int i; /* Looping var */ 200 201 202#ifdef DEBUG 203 fprintf(stderr, "mxmlDelete(node=%p)\n", node); 204#endif /* DEBUG */ 205 206 /* 207 * Range check input... 208 */ 209 210 if (!node) 211 return; 212 213 /* 214 * Remove the node from its parent, if any... 215 */ 216 217 mxmlRemove(node); 218 219 /* 220 * Delete children... 221 */ 222 223 while (node->child) 224 mxmlDelete(node->child); 225 226 /* 227 * Now delete any node data... 228 */ 229 230 switch (node->type) 231 { 232 case MXML_ELEMENT : 233 if (node->value.element.name) 234 free(node->value.element.name); 235 236 if (node->value.element.num_attrs) 237 { 238 for (i = 0; i < node->value.element.num_attrs; i ++) 239 { 240 if (node->value.element.attrs[i].name) 241 free(node->value.element.attrs[i].name); 242 if (node->value.element.attrs[i].value) 243 free(node->value.element.attrs[i].value); 244 } 245 246 free(node->value.element.attrs); 247 } 248 break; 249 case MXML_INTEGER : 250 /* Nothing to do */ 251 break; 252 case MXML_OPAQUE : 253 if (node->value.opaque) 254 free(node->value.opaque); 255 break; 256 case MXML_REAL : 257 /* Nothing to do */ 258 break; 259 case MXML_TEXT : 260 if (node->value.text.string) 261 free(node->value.text.string); 262 break; 263 case MXML_CUSTOM : 264 if (node->value.custom.data && 265 node->value.custom.destroy) 266 (*(node->value.custom.destroy))(node->value.custom.data); 267 break; 268 default : 269 break; 270 } 271 272 /* 273 * Free this node... 274 */ 275 276 free(node); 277} 278 279 280/* 281 * 'mxmlGetRefCount()' - Get the current reference (use) count for a node. 282 * 283 * The initial reference count of new nodes is 1. Use the @link mxmlRetain@ 284 * and @link mxmlRelease@ functions to increment and decrement a node's 285 * reference count. 286 * 287 * @since Mini-XML 2.7@. 288 */ 289 290int /* O - Reference count */ 291mxmlGetRefCount(mxml_node_t *node) /* I - Node */ 292{ 293 /* 294 * Range check input... 295 */ 296 297 if (!node) 298 return (0); 299 300 /* 301 * Return the reference count... 302 */ 303 304 return (node->ref_count); 305} 306 307 308/* 309 * 'mxmlNewCDATA()' - Create a new CDATA node. 310 * 311 * The new CDATA node is added to the end of the specified parent's child 312 * list. The constant MXML_NO_PARENT can be used to specify that the new 313 * CDATA node has no parent. The data string must be nul-terminated and 314 * is copied into the new node. CDATA nodes use the MXML_ELEMENT type. 315 * 316 * @since Mini-XML 2.3@ 317 */ 318 319mxml_node_t * /* O - New node */ 320mxmlNewCDATA(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ 321 const char *data) /* I - Data string */ 322{ 323 mxml_node_t *node; /* New node */ 324 325 326#ifdef DEBUG 327 fprintf(stderr, "mxmlNewCDATA(parent=%p, data=\"%s\")\n", 328 parent, data ? data : "(null)"); 329#endif /* DEBUG */ 330 331 /* 332 * Range check input... 333 */ 334 335 if (!data) 336 return (NULL); 337 338 /* 339 * Create the node and set the name value... 340 */ 341 342 if ((node = mxml_new(parent, MXML_ELEMENT)) != NULL) 343 node->value.element.name = _mxml_strdupf("![CDATA[%s]]", data); 344 345 return (node); 346} 347 348 349/* 350 * 'mxmlNewCustom()' - Create a new custom data node. 351 * 352 * The new custom node is added to the end of the specified parent's child 353 * list. The constant MXML_NO_PARENT can be used to specify that the new 354 * element node has no parent. NULL can be passed when the data in the 355 * node is not dynamically allocated or is separately managed. 356 * 357 * @since Mini-XML 2.1@ 358 */ 359 360mxml_node_t * /* O - New node */ 361mxmlNewCustom( 362 mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ 363 void *data, /* I - Pointer to data */ 364 mxml_custom_destroy_cb_t destroy) /* I - Function to destroy data */ 365{ 366 mxml_node_t *node; /* New node */ 367 368 369#ifdef DEBUG 370 fprintf(stderr, "mxmlNewCustom(parent=%p, data=%p, destroy=%p)\n", parent, 371 data, destroy); 372#endif /* DEBUG */ 373 374 /* 375 * Create the node and set the value... 376 */ 377 378 if ((node = mxml_new(parent, MXML_CUSTOM)) != NULL) 379 { 380 node->value.custom.data = data; 381 node->value.custom.destroy = destroy; 382 } 383 384 return (node); 385} 386 387 388/* 389 * 'mxmlNewElement()' - Create a new element node. 390 * 391 * The new element node is added to the end of the specified parent's child 392 * list. The constant MXML_NO_PARENT can be used to specify that the new 393 * element node has no parent. 394 */ 395 396mxml_node_t * /* O - New node */ 397mxmlNewElement(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ 398 const char *name) /* I - Name of element */ 399{ 400 mxml_node_t *node; /* New node */ 401 402 403#ifdef DEBUG 404 fprintf(stderr, "mxmlNewElement(parent=%p, name=\"%s\")\n", parent, 405 name ? name : "(null)"); 406#endif /* DEBUG */ 407 408 /* 409 * Range check input... 410 */ 411 412 if (!name) 413 return (NULL); 414 415 /* 416 * Create the node and set the element name... 417 */ 418 419 if ((node = mxml_new(parent, MXML_ELEMENT)) != NULL) 420 node->value.element.name = strdup(name); 421 422 return (node); 423} 424 425 426/* 427 * 'mxmlNewInteger()' - Create a new integer node. 428 * 429 * The new integer node is added to the end of the specified parent's child 430 * list. The constant MXML_NO_PARENT can be used to specify that the new 431 * integer node has no parent. 432 */ 433 434mxml_node_t * /* O - New node */ 435mxmlNewInteger(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ 436 int integer) /* I - Integer value */ 437{ 438 mxml_node_t *node; /* New node */ 439 440 441#ifdef DEBUG 442 fprintf(stderr, "mxmlNewInteger(parent=%p, integer=%d)\n", parent, integer); 443#endif /* DEBUG */ 444 445 /* 446 * Create the node and set the element name... 447 */ 448 449 if ((node = mxml_new(parent, MXML_INTEGER)) != NULL) 450 node->value.integer = integer; 451 452 return (node); 453} 454 455 456/* 457 * 'mxmlNewOpaque()' - Create a new opaque string. 458 * 459 * The new opaque node is added to the end of the specified parent's child 460 * list. The constant MXML_NO_PARENT can be used to specify that the new 461 * opaque node has no parent. The opaque string must be nul-terminated and 462 * is copied into the new node. 463 */ 464 465mxml_node_t * /* O - New node */ 466mxmlNewOpaque(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ 467 const char *opaque) /* I - Opaque string */ 468{ 469 mxml_node_t *node; /* New node */ 470 471 472#ifdef DEBUG 473 fprintf(stderr, "mxmlNewOpaque(parent=%p, opaque=\"%s\")\n", parent, 474 opaque ? opaque : "(null)"); 475#endif /* DEBUG */ 476 477 /* 478 * Range check input... 479 */ 480 481 if (!opaque) 482 return (NULL); 483 484 /* 485 * Create the node and set the element name... 486 */ 487 488 if ((node = mxml_new(parent, MXML_OPAQUE)) != NULL) 489 node->value.opaque = strdup(opaque); 490 491 return (node); 492} 493 494 495/* 496 * 'mxmlNewReal()' - Create a new real number node. 497 * 498 * The new real number node is added to the end of the specified parent's 499 * child list. The constant MXML_NO_PARENT can be used to specify that 500 * the new real number node has no parent. 501 */ 502 503mxml_node_t * /* O - New node */ 504mxmlNewReal(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ 505 double real) /* I - Real number value */ 506{ 507 mxml_node_t *node; /* New node */ 508 509 510#ifdef DEBUG 511 fprintf(stderr, "mxmlNewReal(parent=%p, real=%g)\n", parent, real); 512#endif /* DEBUG */ 513 514 /* 515 * Create the node and set the element name... 516 */ 517 518 if ((node = mxml_new(parent, MXML_REAL)) != NULL) 519 node->value.real = real; 520 521 return (node); 522} 523 524 525/* 526 * 'mxmlNewText()' - Create a new text fragment node. 527 * 528 * The new text node is added to the end of the specified parent's child 529 * list. The constant MXML_NO_PARENT can be used to specify that the new 530 * text node has no parent. The whitespace parameter is used to specify 531 * whether leading whitespace is present before the node. The text 532 * string must be nul-terminated and is copied into the new node. 533 */ 534 535mxml_node_t * /* O - New node */ 536mxmlNewText(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ 537 int whitespace, /* I - 1 = leading whitespace, 0 = no whitespace */ 538 const char *string) /* I - String */ 539{ 540 mxml_node_t *node; /* New node */ 541 542 543#ifdef DEBUG 544 fprintf(stderr, "mxmlNewText(parent=%p, whitespace=%d, string=\"%s\")\n", 545 parent, whitespace, string ? string : "(null)"); 546#endif /* DEBUG */ 547 548 /* 549 * Range check input... 550 */ 551 552 if (!string) 553 return (NULL); 554 555 /* 556 * Create the node and set the text value... 557 */ 558 559 if ((node = mxml_new(parent, MXML_TEXT)) != NULL) 560 { 561 node->value.text.whitespace = whitespace; 562 node->value.text.string = strdup(string); 563 } 564 565 return (node); 566} 567 568 569/* 570 * 'mxmlNewTextf()' - Create a new formatted text fragment node. 571 * 572 * The new text node is added to the end of the specified parent's child 573 * list. The constant MXML_NO_PARENT can be used to specify that the new 574 * text node has no parent. The whitespace parameter is used to specify 575 * whether leading whitespace is present before the node. The format 576 * string must be nul-terminated and is formatted into the new node. 577 */ 578 579mxml_node_t * /* O - New node */ 580mxmlNewTextf(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ 581 int whitespace, /* I - 1 = leading whitespace, 0 = no whitespace */ 582 const char *format, /* I - Printf-style frmat string */ 583 ...) /* I - Additional args as needed */ 584{ 585 mxml_node_t *node; /* New node */ 586 va_list ap; /* Pointer to arguments */ 587 588 589#ifdef DEBUG 590 fprintf(stderr, "mxmlNewTextf(parent=%p, whitespace=%d, format=\"%s\", ...)\n", 591 parent, whitespace, format ? format : "(null)"); 592#endif /* DEBUG */ 593 594 /* 595 * Range check input... 596 */ 597 598 if (!format) 599 return (NULL); 600 601 /* 602 * Create the node and set the text value... 603 */ 604 605 if ((node = mxml_new(parent, MXML_TEXT)) != NULL) 606 { 607 va_start(ap, format); 608 609 node->value.text.whitespace = whitespace; 610 node->value.text.string = _mxml_vstrdupf(format, ap); 611 612 va_end(ap); 613 } 614 615 return (node); 616} 617 618 619/* 620 * 'mxmlRemove()' - Remove a node from its parent. 621 * 622 * Does not free memory used by the node - use mxmlDelete() for that. 623 * This function does nothing if the node has no parent. 624 */ 625 626void 627mxmlRemove(mxml_node_t *node) /* I - Node to remove */ 628{ 629#ifdef DEBUG 630 fprintf(stderr, "mxmlRemove(node=%p)\n", node); 631#endif /* DEBUG */ 632 633 /* 634 * Range check input... 635 */ 636 637 if (!node || !node->parent) 638 return; 639 640 /* 641 * Remove from parent... 642 */ 643 644#if DEBUG > 1 645 fprintf(stderr, " BEFORE: node->parent=%p\n", node->parent); 646 if (node->parent) 647 { 648 fprintf(stderr, " BEFORE: node->parent->child=%p\n", node->parent->child); 649 fprintf(stderr, " BEFORE: node->parent->last_child=%p\n", node->parent->last_child); 650 } 651 fprintf(stderr, " BEFORE: node->child=%p\n", node->child); 652 fprintf(stderr, " BEFORE: node->last_child=%p\n", node->last_child); 653 fprintf(stderr, " BEFORE: node->prev=%p\n", node->prev); 654 fprintf(stderr, " BEFORE: node->next=%p\n", node->next); 655#endif /* DEBUG > 1 */ 656 657 if (node->prev) 658 node->prev->next = node->next; 659 else 660 node->parent->child = node->next; 661 662 if (node->next) 663 node->next->prev = node->prev; 664 else 665 node->parent->last_child = node->prev; 666 667 node->parent = NULL; 668 node->prev = NULL; 669 node->next = NULL; 670 671#if DEBUG > 1 672 fprintf(stderr, " AFTER: node->parent=%p\n", node->parent); 673 if (node->parent) 674 { 675 fprintf(stderr, " AFTER: node->parent->child=%p\n", node->parent->child); 676 fprintf(stderr, " AFTER: node->parent->last_child=%p\n", node->parent->last_child); 677 } 678 fprintf(stderr, " AFTER: node->child=%p\n", node->child); 679 fprintf(stderr, " AFTER: node->last_child=%p\n", node->last_child); 680 fprintf(stderr, " AFTER: node->prev=%p\n", node->prev); 681 fprintf(stderr, " AFTER: node->next=%p\n", node->next); 682#endif /* DEBUG > 1 */ 683} 684 685 686/* 687 * 'mxmlNewXML()' - Create a new XML document tree. 688 * 689 * The "version" argument specifies the version number to put in the 690 * ?xml element node. If NULL, version 1.0 is assumed. 691 * 692 * @since Mini-XML 2.3@ 693 */ 694 695mxml_node_t * /* O - New ?xml node */ 696mxmlNewXML(const char *version) /* I - Version number to use */ 697{ 698 char element[1024]; /* Element text */ 699 700 701 snprintf(element, sizeof(element), "?xml version=\"%s\" encoding=\"utf-8\"?", 702 version ? version : "1.0"); 703 704 return (mxmlNewElement(NULL, element)); 705} 706 707 708/* 709 * 'mxmlRelease()' - Release a node. 710 * 711 * When the reference count reaches zero, the node (and any children) 712 * is deleted via mxmlDelete(). 713 * 714 * @since Mini-XML 2.3@ 715 */ 716 717int /* O - New reference count */ 718mxmlRelease(mxml_node_t *node) /* I - Node */ 719{ 720 if (node) 721 { 722 if ((-- node->ref_count) <= 0) 723 { 724 mxmlDelete(node); 725 return (0); 726 } 727 else 728 return (node->ref_count); 729 } 730 else 731 return (-1); 732} 733 734 735/* 736 * 'mxmlRetain()' - Retain a node. 737 * 738 * @since Mini-XML 2.3@ 739 */ 740 741int /* O - New reference count */ 742mxmlRetain(mxml_node_t *node) /* I - Node */ 743{ 744 if (node) 745 return (++ node->ref_count); 746 else 747 return (-1); 748} 749 750 751/* 752 * 'mxml_new()' - Create a new node. 753 */ 754 755static mxml_node_t * /* O - New node */ 756mxml_new(mxml_node_t *parent, /* I - Parent node */ 757 mxml_type_t type) /* I - Node type */ 758{ 759 mxml_node_t *node; /* New node */ 760 761 762#if DEBUG > 1 763 fprintf(stderr, "mxml_new(parent=%p, type=%d)\n", parent, type); 764#endif /* DEBUG > 1 */ 765 766 /* 767 * Allocate memory for the node... 768 */ 769 770 if ((node = calloc(1, sizeof(mxml_node_t))) == NULL) 771 { 772#if DEBUG > 1 773 fputs(" returning NULL\n", stderr); 774#endif /* DEBUG > 1 */ 775 776 return (NULL); 777 } 778 779#if DEBUG > 1 780 fprintf(stderr, " returning %p\n", node); 781#endif /* DEBUG > 1 */ 782 783 /* 784 * Set the node type... 785 */ 786 787 node->type = type; 788 node->ref_count = 1; 789 790 /* 791 * Add to the parent if present... 792 */ 793 794 if (parent) 795 mxmlAdd(parent, MXML_ADD_AFTER, MXML_ADD_TO_PARENT, node); 796 797 /* 798 * Return the new node... 799 */ 800 801 return (node); 802} 803 804 805/* 806 * End of "$Id: mxml-node.c 436 2011-01-22 01:02:05Z mike $". 807 */ 808