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 */
36 /*
37 * Include necessary headers...
38 */
40 #include "config.h"
41 #include "mxml.h"
44 /*
45 * Local functions...
46 */
48 static mxml_node_t *mxml_new(mxml_node_t *parent, mxml_type_t type);
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 */
62 void
63 mxmlAdd(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 */
73 /*
74 * Range check input...
75 */
77 if (!parent || !node)
78 return;
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 */
91 /*
92 * Remove the node from any existing parent...
93 */
95 if (node->parent)
96 mxmlRemove(node);
98 /*
99 * Reset pointers...
100 */
102 node->parent = parent;
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 */
113 node->next = parent->child;
115 if (parent->child)
116 parent->child->prev = node;
117 else
118 parent->last_child = node;
120 parent->child = node;
121 }
122 else
123 {
124 /*
125 * Insert node before this child...
126 */
128 node->next = child;
129 node->prev = child->prev;
131 if (child->prev)
132 child->prev->next = node;
133 else
134 parent->child = node;
136 child->prev = node;
137 }
138 break;
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 */
147 node->parent = parent;
148 node->prev = parent->last_child;
150 if (parent->last_child)
151 parent->last_child->next = node;
152 else
153 parent->child = node;
155 parent->last_child = node;
156 }
157 else
158 {
159 /*
160 * Insert node after this child...
161 */
163 node->prev = child;
164 node->next = child->next;
166 if (child->next)
167 child->next->prev = node;
168 else
169 parent->last_child = node;
171 child->next = node;
172 }
173 break;
174 }
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 }
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 */
196 void
197 mxmlDelete(mxml_node_t *node) /* I - Node to delete */
198 {
199 int i; /* Looping var */
202 #ifdef DEBUG
203 fprintf(stderr, "mxmlDelete(node=%p)\n", node);
204 #endif /* DEBUG */
206 /*
207 * Range check input...
208 */
210 if (!node)
211 return;
213 /*
214 * Remove the node from its parent, if any...
215 */
217 mxmlRemove(node);
219 /*
220 * Delete children...
221 */
223 while (node->child)
224 mxmlDelete(node->child);
226 /*
227 * Now delete any node data...
228 */
230 switch (node->type)
231 {
232 case MXML_ELEMENT :
233 if (node->value.element.name)
234 free(node->value.element.name);
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 }
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 }
272 /*
273 * Free this node...
274 */
276 free(node);
277 }
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 */
290 int /* O - Reference count */
291 mxmlGetRefCount(mxml_node_t *node) /* I - Node */
292 {
293 /*
294 * Range check input...
295 */
297 if (!node)
298 return (0);
300 /*
301 * Return the reference count...
302 */
304 return (node->ref_count);
305 }
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 */
319 mxml_node_t * /* O - New node */
320 mxmlNewCDATA(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 */
326 #ifdef DEBUG
327 fprintf(stderr, "mxmlNewCDATA(parent=%p, data=\"%s\")\n",
328 parent, data ? data : "(null)");
329 #endif /* DEBUG */
331 /*
332 * Range check input...
333 */
335 if (!data)
336 return (NULL);
338 /*
339 * Create the node and set the name value...
340 */
342 if ((node = mxml_new(parent, MXML_ELEMENT)) != NULL)
343 node->value.element.name = _mxml_strdupf("![CDATA[%s]]", data);
345 return (node);
346 }
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 */
360 mxml_node_t * /* O - New node */
361 mxmlNewCustom(
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 */
369 #ifdef DEBUG
370 fprintf(stderr, "mxmlNewCustom(parent=%p, data=%p, destroy=%p)\n", parent,
371 data, destroy);
372 #endif /* DEBUG */
374 /*
375 * Create the node and set the value...
376 */
378 if ((node = mxml_new(parent, MXML_CUSTOM)) != NULL)
379 {
380 node->value.custom.data = data;
381 node->value.custom.destroy = destroy;
382 }
384 return (node);
385 }
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 */
396 mxml_node_t * /* O - New node */
397 mxmlNewElement(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 */
403 #ifdef DEBUG
404 fprintf(stderr, "mxmlNewElement(parent=%p, name=\"%s\")\n", parent,
405 name ? name : "(null)");
406 #endif /* DEBUG */
408 /*
409 * Range check input...
410 */
412 if (!name)
413 return (NULL);
415 /*
416 * Create the node and set the element name...
417 */
419 if ((node = mxml_new(parent, MXML_ELEMENT)) != NULL)
420 node->value.element.name = strdup(name);
422 return (node);
423 }
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 */
434 mxml_node_t * /* O - New node */
435 mxmlNewInteger(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 */
441 #ifdef DEBUG
442 fprintf(stderr, "mxmlNewInteger(parent=%p, integer=%d)\n", parent, integer);
443 #endif /* DEBUG */
445 /*
446 * Create the node and set the element name...
447 */
449 if ((node = mxml_new(parent, MXML_INTEGER)) != NULL)
450 node->value.integer = integer;
452 return (node);
453 }
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 */
465 mxml_node_t * /* O - New node */
466 mxmlNewOpaque(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 */
472 #ifdef DEBUG
473 fprintf(stderr, "mxmlNewOpaque(parent=%p, opaque=\"%s\")\n", parent,
474 opaque ? opaque : "(null)");
475 #endif /* DEBUG */
477 /*
478 * Range check input...
479 */
481 if (!opaque)
482 return (NULL);
484 /*
485 * Create the node and set the element name...
486 */
488 if ((node = mxml_new(parent, MXML_OPAQUE)) != NULL)
489 node->value.opaque = strdup(opaque);
491 return (node);
492 }
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 */
503 mxml_node_t * /* O - New node */
504 mxmlNewReal(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 */
510 #ifdef DEBUG
511 fprintf(stderr, "mxmlNewReal(parent=%p, real=%g)\n", parent, real);
512 #endif /* DEBUG */
514 /*
515 * Create the node and set the element name...
516 */
518 if ((node = mxml_new(parent, MXML_REAL)) != NULL)
519 node->value.real = real;
521 return (node);
522 }
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 */
535 mxml_node_t * /* O - New node */
536 mxmlNewText(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 */
543 #ifdef DEBUG
544 fprintf(stderr, "mxmlNewText(parent=%p, whitespace=%d, string=\"%s\")\n",
545 parent, whitespace, string ? string : "(null)");
546 #endif /* DEBUG */
548 /*
549 * Range check input...
550 */
552 if (!string)
553 return (NULL);
555 /*
556 * Create the node and set the text value...
557 */
559 if ((node = mxml_new(parent, MXML_TEXT)) != NULL)
560 {
561 node->value.text.whitespace = whitespace;
562 node->value.text.string = strdup(string);
563 }
565 return (node);
566 }
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 */
579 mxml_node_t * /* O - New node */
580 mxmlNewTextf(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 */
589 #ifdef DEBUG
590 fprintf(stderr, "mxmlNewTextf(parent=%p, whitespace=%d, format=\"%s\", ...)\n",
591 parent, whitespace, format ? format : "(null)");
592 #endif /* DEBUG */
594 /*
595 * Range check input...
596 */
598 if (!format)
599 return (NULL);
601 /*
602 * Create the node and set the text value...
603 */
605 if ((node = mxml_new(parent, MXML_TEXT)) != NULL)
606 {
607 va_start(ap, format);
609 node->value.text.whitespace = whitespace;
610 node->value.text.string = _mxml_vstrdupf(format, ap);
612 va_end(ap);
613 }
615 return (node);
616 }
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 */
626 void
627 mxmlRemove(mxml_node_t *node) /* I - Node to remove */
628 {
629 #ifdef DEBUG
630 fprintf(stderr, "mxmlRemove(node=%p)\n", node);
631 #endif /* DEBUG */
633 /*
634 * Range check input...
635 */
637 if (!node || !node->parent)
638 return;
640 /*
641 * Remove from parent...
642 */
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 */
657 if (node->prev)
658 node->prev->next = node->next;
659 else
660 node->parent->child = node->next;
662 if (node->next)
663 node->next->prev = node->prev;
664 else
665 node->parent->last_child = node->prev;
667 node->parent = NULL;
668 node->prev = NULL;
669 node->next = NULL;
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 }
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 */
695 mxml_node_t * /* O - New ?xml node */
696 mxmlNewXML(const char *version) /* I - Version number to use */
697 {
698 char element[1024]; /* Element text */
701 snprintf(element, sizeof(element), "?xml version=\"%s\" encoding=\"utf-8\"?",
702 version ? version : "1.0");
704 return (mxmlNewElement(NULL, element));
705 }
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 */
717 int /* O - New reference count */
718 mxmlRelease(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 }
735 /*
736 * 'mxmlRetain()' - Retain a node.
737 *
738 * @since Mini-XML 2.3@
739 */
741 int /* O - New reference count */
742 mxmlRetain(mxml_node_t *node) /* I - Node */
743 {
744 if (node)
745 return (++ node->ref_count);
746 else
747 return (-1);
748 }
751 /*
752 * 'mxml_new()' - Create a new node.
753 */
755 static mxml_node_t * /* O - New node */
756 mxml_new(mxml_node_t *parent, /* I - Parent node */
757 mxml_type_t type) /* I - Node type */
758 {
759 mxml_node_t *node; /* New node */
762 #if DEBUG > 1
763 fprintf(stderr, "mxml_new(parent=%p, type=%d)\n", parent, type);
764 #endif /* DEBUG > 1 */
766 /*
767 * Allocate memory for the node...
768 */
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 */
776 return (NULL);
777 }
779 #if DEBUG > 1
780 fprintf(stderr, " returning %p\n", node);
781 #endif /* DEBUG > 1 */
783 /*
784 * Set the node type...
785 */
787 node->type = type;
788 node->ref_count = 1;
790 /*
791 * Add to the parent if present...
792 */
794 if (parent)
795 mxmlAdd(parent, MXML_ADD_AFTER, MXML_ADD_TO_PARENT, node);
797 /*
798 * Return the new node...
799 */
801 return (node);
802 }
805 /*
806 * End of "$Id: mxml-node.c 436 2011-01-22 01:02:05Z mike $".
807 */