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