1/*
2 * "$Id: mxml-file.c,v 1.10 2008/07/20 01:12:15 easysw Exp $"
3 *
4 * File loading code for mini-XML, a small XML-like file parsing library.
5 *
6 * Copyright 2003 by Michael Sweet.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * Contents:
19 *
20 *   stp_mxmlLoadFile()        - Load a file into an XML node tree.
21 *   stp_mxmlLoadFromFile()    - Load a file into an XML node tree.
22 *   stp_mxmlLoadString()      - Load a string into an XML node tree.
23 *   stp_mxmlSaveAllocString() - Save an XML node tree to an allocated string.
24 *   stp_mxmlSaveFile()        - Save an XML tree to a file.
25 *   stp_mxmlSaveString()      - Save an XML node tree to a string.
26 *   mxml_add_char()       - Add a character to a buffer, expanding as needed.
27 *   mxml_file_getc()      - Get a character from a file.
28 *   mxml_load_data()      - Load data into an XML node tree.
29 *   mxml_parse_element()  - Parse an element for any attributes...
30 *   mxml_string_getc()    - Get a character from a string.
31 *   mxml_write_node()     - Save an XML node to a file.
32 *   mxml_write_string()   - Write a string, escaping & and < as needed.
33 *   mxml_write_ws()       - Do whitespace callback...
34 */
35
36/*
37 * Include necessary headers...
38 */
39
40#include <gutenprint/mxml.h>
41#include "config.h"
42
43
44/*
45 * Local functions...
46 */
47
48static int		mxml_add_char(int ch, char **ptr, char **buffer,
49			              int *bufsize);
50static int		mxml_file_getc(void *p);
51static int		mxml_file_putc(int ch, void *p);
52static stp_mxml_node_t	*mxml_load_data(stp_mxml_node_t *top, void *p,
53			                stp_mxml_type_t (*cb)(stp_mxml_node_t *),
54			                int (*getc_cb)(void *));
55static int		mxml_parse_element(stp_mxml_node_t *node, void *p,
56			                   int (*getc_cb)(void *));
57static int		mxml_string_getc(void *p);
58static int		mxml_string_putc(int ch, void *p);
59static int		mxml_write_node(stp_mxml_node_t *node, void *p,
60			                int (*cb)(stp_mxml_node_t *, int),
61					int col,
62					int (*putc_cb)(int, void *));
63static int		mxml_write_string(const char *s, void *p,
64					  int (*putc_cb)(int, void *));
65static int		mxml_write_ws(stp_mxml_node_t *node, void *p,
66			              int (*cb)(stp_mxml_node_t *, int), int ws,
67				      int col, int (*putc_cb)(int, void *));
68
69
70/*
71 * 'stp_mxmlLoadFile()' - Load a file into an XML node tree.
72 *
73 * The nodes in the specified file are added to the specified top node.
74 * If no top node is provided, the XML file MUST be well-formed with a
75 * single parent node like <?xml> for the entire file. The callback
76 * function returns the value type that should be used for child nodes.
77 * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either
78 * STP_MXML_ELEMENT or STP_MXML_TEXT nodes.
79 */
80
81stp_mxml_node_t *				/* O - First node or NULL if the file could not be read. */
82stp_mxmlLoadFile(stp_mxml_node_t *top,		/* I - Top node */
83             FILE        *fp,		/* I - File to read from */
84             stp_mxml_type_t (*cb)(stp_mxml_node_t *))
85					/* I - Callback function or STP_MXML_NO_CALLBACK */
86{
87  return (mxml_load_data(top, fp, cb, mxml_file_getc));
88}
89
90/*
91 * 'stp_mxmlLoadFromFile()' - Load a named file into an XML node tree.
92 *
93 * The nodes in the specified file are added to the specified top node.
94 * If no top node is provided, the XML file MUST be well-formed with a
95 * single parent node like <?xml> for the entire file. The callback
96 * function returns the value type that should be used for child nodes.
97 * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either
98 * STP_MXML_ELEMENT or STP_MXML_TEXT nodes.
99 */
100
101stp_mxml_node_t *				/* O - First node or NULL if the file could not be read. */
102stp_mxmlLoadFromFile(stp_mxml_node_t *top,	/* I - Top node */
103		     const char *file,		/* I - File to read from */
104		     stp_mxml_type_t (*cb)(stp_mxml_node_t *))
105					/* I - Callback function or STP_MXML_NO_CALLBACK */
106{
107  FILE *fp = fopen(file, "r");
108  stp_mxml_node_t *doc;
109  if (! fp)
110    return NULL;
111  doc = stp_mxmlLoadFile(top, fp, cb);
112  fclose(fp);
113  return doc;
114}
115
116
117/*
118 * 'stp_mxmlLoadString()' - Load a string into an XML node tree.
119 *
120 * The nodes in the specified string are added to the specified top node.
121 * If no top node is provided, the XML string MUST be well-formed with a
122 * single parent node like <?xml> for the entire string. The callback
123 * function returns the value type that should be used for child nodes.
124 * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either
125 * STP_MXML_ELEMENT or STP_MXML_TEXT nodes.
126 */
127
128stp_mxml_node_t *				/* O - First node or NULL if the string has errors. */
129stp_mxmlLoadString(stp_mxml_node_t *top,	/* I - Top node */
130               const char  *s,		/* I - String to load */
131               stp_mxml_type_t (*cb)(stp_mxml_node_t *))
132					/* I - Callback function or STP_MXML_NO_CALLBACK */
133{
134  return (mxml_load_data(top, &s, cb, mxml_string_getc));
135}
136
137
138/*
139 * 'stp_mxmlSaveAllocString()' - Save an XML node tree to an allocated string.
140 *
141 * This function returns a pointer to a string containing the textual
142 * representation of the XML node tree.  The string should be freed
143 * using the free() function when you are done with it.  NULL is returned
144 * if the node would produce an empty string or if the string cannot be
145 * allocated.
146 */
147
148char *					/* O - Allocated string or NULL */
149stp_mxmlSaveAllocString(stp_mxml_node_t *node,	/* I - Node to write */
150                    int         (*cb)(stp_mxml_node_t *, int))
151					/* I - Whitespace callback or STP_MXML_NO_CALLBACK */
152{
153  int	bytes;				/* Required bytes */
154  char	buffer[8192];			/* Temporary buffer */
155  char	*s;				/* Allocated string */
156
157
158 /*
159  * Write the node to the temporary buffer...
160  */
161
162  bytes = stp_mxmlSaveString(node, buffer, sizeof(buffer), cb);
163
164  if (bytes <= 0)
165    return (NULL);
166
167  if (bytes < (int)(sizeof(buffer) - 1))
168  {
169   /*
170    * Node fit inside the buffer, so just duplicate that string and
171    * return...
172    */
173
174    return (strdup(buffer));
175  }
176
177 /*
178  * Allocate a buffer of the required size and save the node to the
179  * new buffer...
180  */
181
182  if ((s = malloc(bytes + 1)) == NULL)
183    return (NULL);
184
185  stp_mxmlSaveString(node, s, bytes + 1, cb);
186
187 /*
188  * Return the allocated string...
189  */
190
191  return (s);
192}
193
194
195/*
196 * 'stp_mxmlSaveFile()' - Save an XML tree to a file.
197 *
198 * The callback argument specifies a function that returns a whitespace
199 * character or nul (0) before and after each element. If STP_MXML_NO_CALLBACK
200 * is specified, whitespace will only be added before STP_MXML_TEXT nodes
201 * with leading whitespace and before attribute names inside opening
202 * element tags.
203 */
204
205int					/* O - 0 on success, -1 on error. */
206stp_mxmlSaveFile(stp_mxml_node_t *node,		/* I - Node to write */
207             FILE        *fp,		/* I - File to write to */
208	     int         (*cb)(stp_mxml_node_t *, int))
209					/* I - Whitespace callback or STP_MXML_NO_CALLBACK */
210{
211  int	col;				/* Final column */
212
213
214 /*
215  * Write the node...
216  */
217
218  if ((col = mxml_write_node(node, fp, cb, 0, mxml_file_putc)) < 0)
219    return (-1);
220
221  if (col > 0)
222    if (putc('\n', fp) < 0)
223      return (-1);
224
225 /*
226  * Return 0 (success)...
227  */
228
229  return (0);
230}
231
232int					/* O - 0 on success, -1 on error. */
233stp_mxmlSaveToFile(stp_mxml_node_t *node,	/* I - Node to write */
234		   const char      *file,	/* I - File to write to */
235		   int             (*cb)(stp_mxml_node_t *, int))
236					/* I - Whitespace callback or STP_MXML_NO_CALLBACK */
237{
238  FILE *fp = fopen(file, "w");
239  int answer;
240  int status;
241  if (!fp)
242    return -1;
243  answer = stp_mxmlSaveFile(node, fp, cb);
244  status = fclose(fp);
245  if (status != 0)
246    return -1;
247  else
248    return answer;
249}
250
251/*
252 * 'stp_mxmlSaveString()' - Save an XML node tree to a string.
253 *
254 * This function returns the total number of bytes that would be
255 * required for the string but only copies (bufsize - 1) characters
256 * into the specified buffer.
257 */
258
259int					/* O - Size of string */
260stp_mxmlSaveString(stp_mxml_node_t *node,	/* I - Node to write */
261               char        *buffer,	/* I - String buffer */
262               int         bufsize,	/* I - Size of string buffer */
263               int         (*cb)(stp_mxml_node_t *, int))
264					/* I - Whitespace callback or STP_MXML_NO_CALLBACK */
265{
266  int	col;				/* Final column */
267  char	*ptr[2];			/* Pointers for putc_cb */
268
269
270 /*
271  * Write the node...
272  */
273
274  ptr[0] = buffer;
275  ptr[1] = buffer + bufsize;
276
277  if ((col = mxml_write_node(node, ptr, cb, 0, mxml_string_putc)) < 0)
278    return (-1);
279
280  if (col > 0)
281    mxml_string_putc('\n', ptr);
282
283 /*
284  * Nul-terminate the buffer...
285  */
286
287  if (ptr[0] >= ptr[1])
288    buffer[bufsize - 1] = '\0';
289  else
290    ptr[0][0] = '\0';
291
292 /*
293  * Return the number of characters...
294  */
295
296  return (ptr[0] - buffer);
297}
298
299
300/*
301 * 'mxml_add_char()' - Add a character to a buffer, expanding as needed.
302 */
303
304static int				/* O  - 0 on success, -1 on error */
305mxml_add_char(int  ch,			/* I  - Character to add */
306              char **bufptr,		/* IO - Current position in buffer */
307	      char **buffer,		/* IO - Current buffer */
308	      int  *bufsize)		/* IO - Current buffer size */
309{
310  char	*newbuffer;			/* New buffer value */
311
312
313  if (*bufptr >= (*buffer + *bufsize - 1))
314  {
315   /*
316    * Increase the size of the buffer...
317    */
318
319    if (*bufsize < 1024)
320      (*bufsize) *= 2;
321    else
322      (*bufsize) += 1024;
323
324    if ((newbuffer = realloc(*buffer, *bufsize)) == NULL)
325    {
326      free(*buffer);
327
328      fprintf(stderr, "Unable to expand string buffer to %d bytes!\n",
329	      *bufsize);
330
331      return (-1);
332    }
333
334    *bufptr = newbuffer + (*bufptr - *buffer);
335    *buffer = newbuffer;
336  }
337
338  *(*bufptr)++ = ch;
339
340  return (0);
341}
342
343
344/*
345 * 'mxml_file_getc()' - Get a character from a file.
346 */
347
348static int				/* O - Character or EOF */
349mxml_file_getc(void *p)			/* I - Pointer to file */
350{
351  return (getc((FILE *)p));
352}
353
354
355/*
356 * 'mxml_file_putc()' - Write a character to a file.
357 */
358
359static int				/* O - 0 on success, -1 on failure */
360mxml_file_putc(int  ch,			/* I - Character to write */
361               void *p)			/* I - Pointer to file */
362{
363  return (putc(ch, (FILE *)p));
364}
365
366
367/*
368 * 'mxml_load_data()' - Load data into an XML node tree.
369 */
370
371static stp_mxml_node_t *			/* O - First node or NULL if the file could not be read. */
372mxml_load_data(stp_mxml_node_t *top,	/* I - Top node */
373               void        *p,		/* I - Pointer to data */
374               stp_mxml_type_t (*cb)(stp_mxml_node_t *),
375					/* I - Callback function or STP_MXML_NO_CALLBACK */
376               int         (*getc_cb)(void *))
377					/* I - Read function */
378{
379  stp_mxml_node_t	*node,			/* Current node */
380		*parent;		/* Current parent node */
381  int		ch,			/* Character from file */
382		whitespace;		/* Non-zero if whitespace seen */
383  char		*buffer,		/* String buffer */
384		*bufptr;		/* Pointer into buffer */
385  int		bufsize;		/* Size of buffer */
386  stp_mxml_type_t	type;			/* Current node type */
387
388
389 /*
390  * Read elements and other nodes from the file...
391  */
392
393  if ((buffer = malloc(64)) == NULL)
394  {
395    fputs("Unable to allocate string buffer!\n", stderr);
396    return (NULL);
397  }
398
399  bufsize    = 64;
400  bufptr     = buffer;
401  parent     = top;
402  whitespace = 0;
403
404  if (cb && parent)
405    type = (*cb)(parent);
406  else
407    type = STP_MXML_TEXT;
408
409  while ((ch = (*getc_cb)(p)) != EOF)
410  {
411    if ((ch == '<' || (isspace(ch) && type != STP_MXML_OPAQUE)) && bufptr > buffer)
412    {
413     /*
414      * Add a new value node...
415      */
416
417      *bufptr = '\0';
418
419      switch (type)
420      {
421	case STP_MXML_INTEGER :
422            node = stp_mxmlNewInteger(parent, strtol(buffer, &bufptr, 0));
423	    break;
424
425	case STP_MXML_OPAQUE :
426            node = stp_mxmlNewOpaque(parent, buffer);
427	    break;
428
429	case STP_MXML_REAL :
430            node = stp_mxmlNewReal(parent, strtod(buffer, &bufptr));
431	    break;
432
433	case STP_MXML_TEXT :
434            node = stp_mxmlNewText(parent, whitespace, buffer);
435	    break;
436
437        default : /* Should never happen... */
438	    node = NULL;
439	    break;
440      }
441
442      if (*bufptr)
443      {
444       /*
445        * Bad integer/real number value...
446	*/
447
448        fprintf(stderr, "Bad %s value '%s' in parent <%s>!\n",
449	        type == STP_MXML_INTEGER ? "integer" : "real", buffer,
450		parent ? parent->value.element.name : "null");
451	break;
452      }
453
454      bufptr     = buffer;
455      whitespace = isspace(ch) && type == STP_MXML_TEXT;
456
457      if (!node)
458      {
459       /*
460	* Just print error for now...
461	*/
462
463	fprintf(stderr, "Unable to add value node of type %d to parent <%s>!\n",
464	        type, parent ? parent->value.element.name : "null");
465	break;
466      }
467    }
468    else if (isspace(ch) && type == STP_MXML_TEXT)
469      whitespace = 1;
470
471   /*
472    * Add lone whitespace node if we have an element and existing
473    * whitespace...
474    */
475
476    if (ch == '<' && whitespace && type == STP_MXML_TEXT)
477    {
478      stp_mxmlNewText(parent, whitespace, "");
479      whitespace = 0;
480    }
481
482    if (ch == '<')
483    {
484     /*
485      * Start of open/close tag...
486      */
487
488      bufptr = buffer;
489
490      while ((ch = (*getc_cb)(p)) != EOF)
491        if (isspace(ch) || ch == '>' || (ch == '/' && bufptr > buffer))
492	  break;
493	else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
494	{
495          free(buffer);
496	  return (NULL);
497	}
498	else if ((bufptr - buffer) == 3 && !strncmp(buffer, "!--", 3))
499	  break;
500
501      *bufptr = '\0';
502
503      if (!strcmp(buffer, "!--"))
504      {
505       /*
506        * Gather rest of comment...
507	*/
508
509	while ((ch = (*getc_cb)(p)) != EOF)
510	{
511	  if (ch == '>' && bufptr > (buffer + 4) &&
512	      !strncmp(bufptr - 2, "--", 2))
513	    break;
514	  else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
515	  {
516            free(buffer);
517	    return (NULL);
518	  }
519	}
520
521       /*
522        * Error out if we didn't get the whole comment...
523	*/
524
525        if (ch != '>')
526	  break;
527
528       /*
529        * Otherwise add this as an element under the current parent...
530	*/
531
532	*bufptr = '\0';
533
534	if (!stp_mxmlNewElement(parent, buffer))
535	{
536	 /*
537	  * Just print error for now...
538	  */
539
540	  fprintf(stderr, "Unable to add comment node to parent <%s>!\n",
541	          parent ? parent->value.element.name : "null");
542	  break;
543	}
544      }
545      else if (buffer[0] == '!')
546      {
547       /*
548        * Gather rest of declaration...
549	*/
550
551	do
552	{
553	  if (ch == '>')
554	    break;
555	  else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
556	  {
557            free(buffer);
558	    return (NULL);
559	  }
560	}
561        while ((ch = (*getc_cb)(p)) != EOF);
562
563       /*
564        * Error out if we didn't get the whole declaration...
565	*/
566
567        if (ch != '>')
568	  break;
569
570       /*
571        * Otherwise add this as an element under the current parent...
572	*/
573
574	*bufptr = '\0';
575
576	node = stp_mxmlNewElement(parent, buffer);
577	if (!node)
578	{
579	 /*
580	  * Just print error for now...
581	  */
582
583	  fprintf(stderr, "Unable to add declaration node to parent <%s>!\n",
584	          parent ? parent->value.element.name : "null");
585	  break;
586	}
587
588       /*
589	* Descend into this node, setting the value type as needed...
590	*/
591
592	parent = node;
593
594	if (cb && parent)
595	  type = (*cb)(parent);
596      }
597      else if (buffer[0] == '/')
598      {
599       /*
600        * Handle close tag...
601	*/
602
603        if (!parent || strcmp(buffer + 1, parent->value.element.name))
604	{
605	 /*
606	  * Close tag doesn't match tree; print an error for now...
607	  */
608
609	  fprintf(stderr, "Mismatched close tag <%s> under parent <%s>!\n",
610	          buffer, parent ? parent->value.element.name : "(null)");
611          break;
612	}
613
614       /*
615        * Keep reading until we see >...
616	*/
617
618        while (ch != '>' && ch != EOF)
619	  ch = (*getc_cb)(p);
620
621       /*
622	* Ascend into the parent and set the value type as needed...
623	*/
624
625	parent = parent->parent;
626
627	if (cb && parent)
628	  type = (*cb)(parent);
629      }
630      else
631      {
632       /*
633        * Handle open tag...
634	*/
635
636        node = stp_mxmlNewElement(parent, buffer);
637
638	if (!node)
639	{
640	 /*
641	  * Just print error for now...
642	  */
643
644	  fprintf(stderr, "Unable to add element node to parent <%s>!\n",
645	          parent ? parent->value.element.name : "null");
646	  break;
647	}
648
649        if (isspace(ch))
650          ch = mxml_parse_element(node, p, getc_cb);
651        else if (ch == '/')
652	{
653	  if ((ch = (*getc_cb)(p)) != '>')
654	  {
655	    fprintf(stderr, "Expected > but got '%c' instead for element <%s/>!\n",
656	            ch, buffer);
657            break;
658	  }
659
660	  ch = '/';
661	}
662
663	if (ch == EOF)
664	  break;
665
666        if (ch != '/')
667	{
668	 /*
669	  * Descend into this node, setting the value type as needed...
670	  */
671
672	  parent = node;
673
674	  if (cb && parent)
675	    type = (*cb)(parent);
676	}
677      }
678
679      bufptr  = buffer;
680    }
681    else if (ch == '&')
682    {
683     /*
684      * Add character entity to current buffer...  Currently we only
685      * support &lt;, &amp;, &gt;, &nbsp;, &quot;, &#nnn;, and &#xXXXX;...
686      */
687
688      char	entity[64],		/* Entity string */
689		*entptr;		/* Pointer into entity */
690
691
692      entity[0] = ch;
693      entptr    = entity + 1;
694
695      while ((ch = (*getc_cb)(p)) != EOF)
696        if (!isalnum(ch) && ch != '#')
697	  break;
698	else if (entptr < (entity + sizeof(entity) - 1))
699	  *entptr++ = ch;
700	else
701	{
702	  fprintf(stderr, "Entity name too long under parent <%s>!\n",
703	          parent ? parent->value.element.name : "null");
704          break;
705	}
706
707      *entptr = '\0';
708
709      if (ch != ';')
710      {
711	fprintf(stderr, "Entity name \"%s\" not terminated under parent <%s>!\n",
712	        entity, parent ? parent->value.element.name : "null");
713        break;
714      }
715
716      if (entity[1] == '#')
717      {
718	if (entity[2] == 'x')
719	  ch = strtol(entity + 3, NULL, 16);
720	else
721	  ch = strtol(entity + 2, NULL, 10);
722      }
723      else if (!strcmp(entity, "&amp"))
724        ch = '&';
725      else if (!strcmp(entity, "&gt"))
726        ch = '>';
727      else if (!strcmp(entity, "&lt"))
728        ch = '<';
729      else if (!strcmp(entity, "&nbsp"))
730        ch = 0xa0;
731      else if (!strcmp(entity, "&quot"))
732        ch = '\"';
733      else
734      {
735	fprintf(stderr, "Entity name \"%s;\" not supported under parent <%s>!\n",
736	        entity, parent ? parent->value.element.name : "null");
737        break;
738      }
739
740      if (ch < 128)
741      {
742       /*
743        * Plain ASCII doesn't need special encoding...
744	*/
745
746	if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
747	{
748          free(buffer);
749	  return (NULL);
750	}
751      }
752      else
753      {
754       /*
755        * Use UTF-8 encoding for the Unicode char...
756	*/
757
758	if (ch < 2048)
759	{
760	  if (mxml_add_char(0xc0 | (ch >> 6), &bufptr, &buffer, &bufsize))
761	  {
762            free(buffer);
763	    return (NULL);
764	  }
765	  if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize))
766	  {
767            free(buffer);
768	    return (NULL);
769	  }
770        }
771	else if (ch < 65536)
772	{
773	  if (mxml_add_char(0xe0 | (ch >> 12), &bufptr, &buffer, &bufsize))
774	  {
775            free(buffer);
776	    return (NULL);
777	  }
778	  if (mxml_add_char(0x80 | ((ch >> 6) & 63), &bufptr, &buffer, &bufsize))
779	  {
780            free(buffer);
781	    return (NULL);
782	  }
783	  if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize))
784	  {
785            free(buffer);
786	    return (NULL);
787	  }
788	}
789	else
790	{
791	  if (mxml_add_char(0xf0 | (ch >> 18), &bufptr, &buffer, &bufsize))
792	  {
793            free(buffer);
794	    return (NULL);
795	  }
796	  if (mxml_add_char(0x80 | ((ch >> 12) & 63), &bufptr, &buffer, &bufsize))
797	  {
798            free(buffer);
799	    return (NULL);
800	  }
801	  if (mxml_add_char(0x80 | ((ch >> 6) & 63), &bufptr, &buffer, &bufsize))
802	  {
803            free(buffer);
804	    return (NULL);
805	  }
806	  if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize))
807	  {
808            free(buffer);
809	    return (NULL);
810	  }
811	}
812      }
813    }
814    else if (type == STP_MXML_OPAQUE || !isspace(ch))
815    {
816     /*
817      * Add character to current buffer...
818      */
819
820      if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
821      {
822        free(buffer);
823	return (NULL);
824      }
825    }
826  }
827
828 /*
829  * Free the string buffer - we don't need it anymore...
830  */
831
832  free(buffer);
833
834 /*
835  * Find the top element and return it...
836  */
837
838  if (parent)
839  {
840    while (parent->parent != top)
841      parent = parent->parent;
842  }
843
844  return (parent);
845}
846
847
848/*
849 * 'mxml_parse_element()' - Parse an element for any attributes...
850 */
851
852static int				/* O - Terminating character */
853mxml_parse_element(stp_mxml_node_t *node,	/* I - Element node */
854                   void        *p,	/* I - Data to read from */
855                   int         (*getc_cb)(void *))
856					/* I - Data callback */
857{
858  int	ch,				/* Current character in file */
859	quote;				/* Quoting character */
860  char	*name,				/* Attribute name */
861	*value,				/* Attribute value */
862	*ptr;				/* Pointer into name/value */
863  int	namesize,			/* Size of name string */
864	valsize;			/* Size of value string */
865
866
867 /*
868  * Initialize the name and value buffers...
869  */
870
871  if ((name = malloc(64)) == NULL)
872  {
873    fputs("Unable to allocate memory for name!\n", stderr);
874    return (EOF);
875  }
876
877  namesize = 64;
878
879  if ((value = malloc(64)) == NULL)
880  {
881    free(name);
882    fputs("Unable to allocate memory for value!\n", stderr);
883    return (EOF);
884  }
885
886  valsize = 64;
887
888 /*
889  * Loop until we hit a >, /, ?, or EOF...
890  */
891
892  while ((ch = (*getc_cb)(p)) != EOF)
893  {
894#ifdef DEBUG
895    fprintf(stderr, "parse_element: ch='%c'\n", ch);
896#endif /* DEBUG */
897
898   /*
899    * Skip leading whitespace...
900    */
901
902    if (isspace(ch))
903      continue;
904
905   /*
906    * Stop at /, ?, or >...
907    */
908
909    if (ch == '/' || ch == '?')
910    {
911     /*
912      * Grab the > character and print an error if it isn't there...
913      */
914
915      quote = (*getc_cb)(p);
916
917      if (quote != '>')
918      {
919        fprintf(stderr, "Expected '>' after '%c' for element %s, but got '%c'!\n",
920	        ch, node->value.element.name, quote);
921        ch = EOF;
922      }
923
924      break;
925    }
926    else if (ch == '>')
927      break;
928
929   /*
930    * Read the attribute name...
931    */
932
933    name[0] = ch;
934    ptr     = name + 1;
935
936    while ((ch = (*getc_cb)(p)) != EOF)
937      if (isspace(ch) || ch == '=' || ch == '/' || ch == '>' || ch == '?')
938        break;
939      else if (mxml_add_char(ch, &ptr, &name, &namesize))
940      {
941        free(name);
942	free(value);
943	return (EOF);
944      }
945
946    *ptr = '\0';
947
948    if (ch == '=')
949    {
950     /*
951      * Read the attribute value...
952      */
953
954      if ((ch = (*getc_cb)(p)) == EOF)
955      {
956        fprintf(stderr, "Missing value for attribute '%s' in element %s!\n",
957	        name, node->value.element.name);
958        return (EOF);
959      }
960
961      if (ch == '\'' || ch == '\"')
962      {
963       /*
964        * Read quoted value...
965	*/
966
967        quote = ch;
968	ptr   = value;
969
970        while ((ch = (*getc_cb)(p)) != EOF)
971	  if (ch == quote)
972	    break;
973	  else if (mxml_add_char(ch, &ptr, &value, &valsize))
974	  {
975            free(name);
976	    free(value);
977	    return (EOF);
978	  }
979
980        *ptr = '\0';
981      }
982      else
983      {
984       /*
985        * Read unquoted value...
986	*/
987
988	value[0] = ch;
989	ptr      = value + 1;
990
991	while ((ch = (*getc_cb)(p)) != EOF)
992	  if (isspace(ch) || ch == '=' || ch == '/' || ch == '>')
993            break;
994	  else if (mxml_add_char(ch, &ptr, &value, &valsize))
995	  {
996            free(name);
997	    free(value);
998	    return (EOF);
999	  }
1000
1001        *ptr = '\0';
1002      }
1003    }
1004    else
1005      value[0] = '\0';
1006
1007   /*
1008    * Save last character in case we need it...
1009    */
1010
1011    if (ch == '/' || ch == '?')
1012    {
1013     /*
1014      * Grab the > character and print an error if it isn't there...
1015      */
1016
1017      quote = (*getc_cb)(p);
1018
1019      if (quote != '>')
1020      {
1021        fprintf(stderr, "Expected '>' after '%c' for element %s, but got '%c'!\n",
1022	        ch, node->value.element.name, quote);
1023        ch = EOF;
1024      }
1025
1026      break;
1027    }
1028    else if (ch == '>')
1029      break;
1030
1031   /*
1032    * Set the attribute...
1033    */
1034
1035    stp_mxmlElementSetAttr(node, name, value);
1036  }
1037
1038 /*
1039  * Free the name and value buffers and return...
1040  */
1041
1042  free(name);
1043  free(value);
1044
1045  return (ch);
1046}
1047
1048
1049/*
1050 * 'mxml_string_getc()' - Get a character from a string.
1051 */
1052
1053static int				/* O - Character or EOF */
1054mxml_string_getc(void *p)		/* I - Pointer to file */
1055{
1056  int		ch;			/* Character */
1057  const char	**s;			/* Pointer to string pointer */
1058
1059
1060  s = (const char **)p;
1061
1062  if ((ch = *s[0]) != 0)
1063  {
1064    (*s)++;
1065    return (ch);
1066  }
1067  else
1068    return (EOF);
1069}
1070
1071
1072/*
1073 * 'mxml_string_putc()' - Write a character to a string.
1074 */
1075
1076static int				/* O - 0 on success, -1 on failure */
1077mxml_string_putc(int  ch,		/* I - Character to write */
1078                 void *p)		/* I - Pointer to string pointers */
1079{
1080  char	**pp;				/* Pointer to string pointers */
1081
1082
1083  pp = (char **)p;
1084
1085  if (pp[0] < pp[1])
1086    pp[0][0] = ch;
1087
1088  pp[0] ++;
1089
1090  return (0);
1091}
1092
1093
1094/*
1095 * 'mxml_write_node()' - Save an XML node to a file.
1096 */
1097
1098static int				/* O - Column or -1 on error */
1099mxml_write_node(stp_mxml_node_t *node,	/* I - Node to write */
1100                void        *p,		/* I - File to write to */
1101	        int         (*cb)(stp_mxml_node_t *, int),
1102					/* I - Whitespace callback */
1103		int         col,	/* I - Current column */
1104		int         (*putc_cb)(int, void *))
1105{
1106  int		i;			/* Looping var */
1107  stp_mxml_attr_t	*attr;			/* Current attribute */
1108  char		s[255];			/* Temporary string */
1109
1110
1111  while (node != NULL)
1112  {
1113   /*
1114    * Print the node value...
1115    */
1116
1117    switch (node->type)
1118    {
1119      case STP_MXML_ELEMENT :
1120          col = mxml_write_ws(node, p, cb, STP_MXML_WS_BEFORE_OPEN, col, putc_cb);
1121
1122          if ((*putc_cb)('<', p) < 0)
1123	    return (-1);
1124	  if (mxml_write_string(node->value.element.name, p, putc_cb) < 0)
1125	    return (-1);
1126
1127          col += strlen(node->value.element.name) + 1;
1128
1129	  for (i = node->value.element.num_attrs, attr = node->value.element.attrs;
1130	       i > 0;
1131	       i --, attr ++)
1132	  {
1133	    if ((col + strlen(attr->name) + strlen(attr->value) + 3) > STP_MXML_WRAP)
1134	    {
1135	      if ((*putc_cb)('\n', p) < 0)
1136	        return (-1);
1137
1138	      col = 0;
1139	    }
1140	    else
1141	    {
1142	      if ((*putc_cb)(' ', p) < 0)
1143	        return (-1);
1144
1145	      col ++;
1146	    }
1147
1148            if (mxml_write_string(attr->name, p, putc_cb) < 0)
1149	      return (-1);
1150            if ((*putc_cb)('=', p) < 0)
1151	      return (-1);
1152            if ((*putc_cb)('\"', p) < 0)
1153	      return (-1);
1154	    if (mxml_write_string(attr->value, p, putc_cb) < 0)
1155	      return (-1);
1156            if ((*putc_cb)('\"', p) < 0)
1157	      return (-1);
1158
1159            col += strlen(attr->name) + strlen(attr->value) + 3;
1160	  }
1161
1162	  if (node->child)
1163	  {
1164           /*
1165	    * The ? and ! elements are special-cases and have no end tags...
1166	    */
1167
1168	    if (node->value.element.name[0] == '?')
1169	    {
1170              if ((*putc_cb)('?', p) < 0)
1171		return (-1);
1172              if ((*putc_cb)('>', p) < 0)
1173		return (-1);
1174              if ((*putc_cb)('\n', p) < 0)
1175		return (-1);
1176
1177              col = 0;
1178            }
1179	    else if ((*putc_cb)('>', p) < 0)
1180	      return (-1);
1181	    else
1182	      col ++;
1183
1184            col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb);
1185
1186	    if ((col = mxml_write_node(node->child, p, cb, col, putc_cb)) < 0)
1187	      return (-1);
1188
1189            if (node->value.element.name[0] != '?' &&
1190	        node->value.element.name[0] != '!')
1191	    {
1192              col = mxml_write_ws(node, p, cb, STP_MXML_WS_BEFORE_CLOSE, col, putc_cb);
1193
1194              if ((*putc_cb)('<', p) < 0)
1195		return (-1);
1196              if ((*putc_cb)('/', p) < 0)
1197		return (-1);
1198              if (mxml_write_string(node->value.element.name, p, putc_cb) < 0)
1199		return (-1);
1200              if ((*putc_cb)('>', p) < 0)
1201		return (-1);
1202
1203              col += strlen(node->value.element.name) + 3;
1204
1205              col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_CLOSE, col, putc_cb);
1206	    }
1207	  }
1208	  else if (node->value.element.name[0] == '!')
1209	  {
1210	    if ((*putc_cb)('>', p) < 0)
1211	      return (-1);
1212	    else
1213	      col ++;
1214
1215            col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb);
1216          }
1217	  else
1218	  {
1219            if ((*putc_cb)('/', p) < 0)
1220	      return (-1);
1221            if ((*putc_cb)('>', p) < 0)
1222	      return (-1);
1223
1224	    col += 2;
1225
1226            col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb);
1227	  }
1228          break;
1229
1230      case STP_MXML_INTEGER :
1231	  if (node->prev)
1232	  {
1233	    if (col > STP_MXML_WRAP)
1234	    {
1235	      if ((*putc_cb)('\n', p) < 0)
1236	        return (-1);
1237
1238	      col = 0;
1239	    }
1240	    else if ((*putc_cb)(' ', p) < 0)
1241	      return (-1);
1242	    else
1243	      col ++;
1244          }
1245
1246          sprintf(s, "%d", node->value.integer);
1247	  if (mxml_write_string(s, p, putc_cb) < 0)
1248	    return (-1);
1249
1250	  col += strlen(s);
1251          break;
1252
1253      case STP_MXML_OPAQUE :
1254          if (mxml_write_string(node->value.opaque, p, putc_cb) < 0)
1255	    return (-1);
1256
1257          col += strlen(node->value.opaque);
1258          break;
1259
1260      case STP_MXML_REAL :
1261	  if (node->prev)
1262	  {
1263	    if (col > STP_MXML_WRAP)
1264	    {
1265	      if ((*putc_cb)('\n', p) < 0)
1266	        return (-1);
1267
1268	      col = 0;
1269	    }
1270	    else if ((*putc_cb)(' ', p) < 0)
1271	      return (-1);
1272	    else
1273	      col ++;
1274          }
1275
1276          sprintf(s, "%f", node->value.real);
1277	  if (mxml_write_string(s, p, putc_cb) < 0)
1278	    return (-1);
1279
1280	  col += strlen(s);
1281          break;
1282
1283      case STP_MXML_TEXT :
1284	  if (node->value.text.whitespace && col > 0)
1285	  {
1286	    if (col > STP_MXML_WRAP)
1287	    {
1288	      if ((*putc_cb)('\n', p) < 0)
1289	        return (-1);
1290
1291	      col = 0;
1292	    }
1293	    else if ((*putc_cb)(' ', p) < 0)
1294	      return (-1);
1295	    else
1296	      col ++;
1297          }
1298
1299          if (mxml_write_string(node->value.text.string, p, putc_cb) < 0)
1300	    return (-1);
1301
1302	  col += strlen(node->value.text.string);
1303          break;
1304    }
1305
1306   /*
1307    * Next node...
1308    */
1309
1310    node = node->next;
1311  }
1312
1313  return (col);
1314}
1315
1316
1317/*
1318 * 'mxml_write_string()' - Write a string, escaping & and < as needed.
1319 */
1320
1321static int				/* O - 0 on success, -1 on failure */
1322mxml_write_string(const char *s,	/* I - String to write */
1323                  void       *p,	/* I - Write pointer */
1324		  int        (*putc_cb)(int, void *))
1325					/* I - Write callback */
1326{
1327  char	buf[255],			/* Buffer */
1328	*bufptr;			/* Pointer into buffer */
1329
1330
1331  while (*s)
1332  {
1333    if (*s == '&')
1334    {
1335      if ((*putc_cb)('&', p) < 0)
1336        return (-1);
1337      if ((*putc_cb)('a', p) < 0)
1338        return (-1);
1339      if ((*putc_cb)('m', p) < 0)
1340        return (-1);
1341      if ((*putc_cb)('p', p) < 0)
1342        return (-1);
1343      if ((*putc_cb)(';', p) < 0)
1344        return (-1);
1345    }
1346    else if (*s == '<')
1347    {
1348      if ((*putc_cb)('&', p) < 0)
1349        return (-1);
1350      if ((*putc_cb)('l', p) < 0)
1351        return (-1);
1352      if ((*putc_cb)('t', p) < 0)
1353        return (-1);
1354      if ((*putc_cb)(';', p) < 0)
1355        return (-1);
1356    }
1357    else if (*s == '>')
1358    {
1359      if ((*putc_cb)('&', p) < 0)
1360        return (-1);
1361      if ((*putc_cb)('g', p) < 0)
1362        return (-1);
1363      if ((*putc_cb)('t', p) < 0)
1364        return (-1);
1365      if ((*putc_cb)(';', p) < 0)
1366        return (-1);
1367    }
1368    else if (*s == '\"')
1369    {
1370      if ((*putc_cb)('&', p) < 0)
1371        return (-1);
1372      if ((*putc_cb)('q', p) < 0)
1373        return (-1);
1374      if ((*putc_cb)('u', p) < 0)
1375        return (-1);
1376      if ((*putc_cb)('o', p) < 0)
1377        return (-1);
1378      if ((*putc_cb)('t', p) < 0)
1379        return (-1);
1380      if ((*putc_cb)(';', p) < 0)
1381        return (-1);
1382    }
1383    else if (*s & 128)
1384    {
1385     /*
1386      * Convert UTF-8 to Unicode constant...
1387      */
1388
1389      int	ch;			/* Unicode character */
1390
1391
1392      ch = *s & 255;
1393
1394      if ((ch & 0xe0) == 0xc0)
1395      {
1396        ch = ((ch & 0x1f) << 6) | (s[1] & 0x3f);
1397	s ++;
1398      }
1399      else if ((ch & 0xf0) == 0xe0)
1400      {
1401        ch = ((((ch * 0x0f) << 6) | (s[1] & 0x3f)) << 6) | (s[2] & 0x3f);
1402	s += 2;
1403      }
1404
1405      if (ch == 0xa0)
1406      {
1407       /*
1408        * Handle non-breaking space as-is...
1409	*/
1410
1411	if ((*putc_cb)('&', p) < 0)
1412          return (-1);
1413	if ((*putc_cb)('n', p) < 0)
1414          return (-1);
1415	if ((*putc_cb)('b', p) < 0)
1416          return (-1);
1417	if ((*putc_cb)('s', p) < 0)
1418          return (-1);
1419	if ((*putc_cb)('p', p) < 0)
1420          return (-1);
1421	if ((*putc_cb)(';', p) < 0)
1422          return (-1);
1423      }
1424      else
1425      {
1426        sprintf(buf, "&#x%x;", ch);
1427
1428	for (bufptr = buf; *bufptr; bufptr ++)
1429	  if ((*putc_cb)(*bufptr, p) < 0)
1430	    return (-1);
1431      }
1432    }
1433    else if ((*putc_cb)(*s, p) < 0)
1434      return (-1);
1435
1436    s ++;
1437  }
1438
1439  return (0);
1440}
1441
1442
1443/*
1444 * 'mxml_write_ws()' - Do whitespace callback...
1445 */
1446
1447static int				/* O - New column */
1448mxml_write_ws(stp_mxml_node_t *node,	/* I - Current node */
1449              void        *p,		/* I - Write pointer */
1450              int         (*cb)(stp_mxml_node_t *, int),
1451					/* I - Callback function */
1452	      int         ws,		/* I - Where value */
1453	      int         col,		/* I - Current column */
1454              int         (*putc_cb)(int, void *))
1455					/* I - Write callback */
1456{
1457  int	ch;				/* Whitespace character */
1458
1459
1460  if (cb && (ch = (*cb)(node, ws)) != 0)
1461  {
1462    if ((*putc_cb)(ch, p) < 0)
1463      return (-1);
1464    else if (ch == '\n')
1465      col = 0;
1466    else if (ch == '\t')
1467    {
1468      col += STP_MXML_TAB;
1469      col = col - (col % STP_MXML_TAB);
1470    }
1471    else
1472      col ++;
1473  }
1474
1475  return (col);
1476}
1477
1478
1479/*
1480 * End of "$Id: mxml-file.c,v 1.10 2008/07/20 01:12:15 easysw Exp $".
1481 */
1482