]> git.sesse.net Git - vlc/blob - modules/codec/cmml/xtag.c
Useless test before a free()
[vlc] / modules / codec / cmml / xtag.c
1 /*****************************************************************************
2  * xlist.c : a trivial parser for XML-like tags
3  *****************************************************************************
4  * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
5  *                         Organisation (CSIRO) Australia
6  * Copyright (C) 2000-2004 the VideoLAN team
7  *
8  * $Id$
9  *
10  * Authors: Conrad Parker <Conrad.Parker@csiro.au>
11  *          Andre Pang <Andre.Pang@csiro.au>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <ctype.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include <xlist.h>
38
39 #undef XTAG_DEBUG
40
41 #undef FALSE
42 #undef TRUE
43
44 #define FALSE (0)
45 #define TRUE (!FALSE)
46
47 #undef MIN
48 #define MIN(a,b) ((a)<(b)?(a):(b))
49
50 #undef MAX
51 #define MAX(a,b) ((a)>(b)?(a):(b))
52
53 typedef struct _XTag XTag;
54 typedef struct _XAttribute XAttribute;
55 typedef struct _XTagParser XTagParser;
56
57 /*
58  * struct _XTag is kind of a union ... it normally represents a whole
59  * tag (and its children), but it could alternatively represent some
60  * PCDATA. Basically, if tag->pcdata is non-NULL, interpret only it and
61  * ignore the name, attributes and inner_tags.
62  */
63 struct _XTag {
64   char * name;
65   char * pcdata;
66   XTag * parent;
67   XList * attributes;
68   XList * children;
69   XList * current_child;
70 };
71
72 struct _XAttribute {
73   char * name;
74   char * value;
75 };
76
77 struct _XTagParser {
78   int valid; /* boolean */
79   XTag * current_tag;
80   char * start;
81   char * end;
82 };
83
84 XTag * xtag_free (XTag * xtag);
85 XTag * xtag_new_parse (const char * s, int n);
86 char * xtag_get_name (XTag * xtag);
87 char * xtag_get_pcdata (XTag * xtag);
88 char * xtag_get_attribute (XTag * xtag, char * attribute);
89 XTag * xtag_first_child (XTag * xtag, char * name);
90 XTag * xtag_next_child (XTag * xtag, char * name);
91 int    xtag_snprint (char * buf, int n, XTag * xtag);
92
93 /* Character classes */
94 #define X_NONE           0
95 #define X_WHITESPACE  1<<0
96 #define X_OPENTAG     1<<1
97 #define X_CLOSETAG    1<<2
98 #define X_DQUOTE      1<<3
99 #define X_SQUOTE      1<<4
100 #define X_EQUAL       1<<5
101 #define X_SLASH       1<<6
102
103 static int
104 xtag_cin (char c, int char_class)
105 {
106   if (char_class & X_WHITESPACE)
107     if (isspace(c)) return TRUE;
108
109   if (char_class & X_OPENTAG)
110     if (c == '<') return TRUE;
111
112   if (char_class & X_CLOSETAG)
113     if (c == '>') return TRUE;
114
115   if (char_class & X_DQUOTE)
116     if (c == '"') return TRUE;
117
118   if (char_class & X_SQUOTE)
119     if (c == '\'') return TRUE;
120
121   if (char_class & X_EQUAL)
122     if (c == '=') return TRUE;
123
124   if (char_class & X_SLASH)
125     if (c == '/') return TRUE;
126
127   return FALSE;
128 }
129
130 static int
131 xtag_index (XTagParser * parser, int char_class)
132 {
133   char * s;
134   int i;
135
136   s = parser->start;
137
138   for (i = 0; s[i] && s != parser->end; i++) {
139     if (xtag_cin(s[i], char_class)) return i;
140   }
141
142   return -1;
143 }
144
145 static void
146 xtag_skip_over (XTagParser * parser, int char_class)
147 {
148   char * s;
149   int i;
150
151   if (!parser->valid) return;
152
153   s = (char *)parser->start;
154
155   for (i = 0; s[i] && s != parser->end; i++) {
156     if (!xtag_cin(s[i], char_class)) {
157       parser->start = &s[i];
158       return;
159     }
160   }
161
162   return;
163 }
164
165 static void
166 xtag_skip_whitespace (XTagParser * parser)
167 {
168   xtag_skip_over (parser, X_WHITESPACE);
169 }
170
171 #if 0
172 static void
173 xtag_skip_to (XTagParser * parser, int char_class)
174 {
175   char * s;
176   int i;
177
178   if (!parser->valid) return;
179
180   s = (char *)parser->start;
181
182   for (i = 0; s[i] && s != parser->end; i++) {
183     if (xtag_cin(s[i], char_class)) {
184       parser->start = &s[i];
185       return;
186     }
187   }
188
189   return;
190 }
191 #endif
192
193 static char *
194 xtag_slurp_to (XTagParser * parser, int good_end, int bad_end)
195 {
196   char * s, * ret;
197   int xi;
198
199   if (!parser->valid) return NULL;
200
201   s = parser->start;
202
203   xi = xtag_index (parser, good_end | bad_end);
204
205   if (xi > 0 && xtag_cin (s[xi], good_end)) {
206     ret = malloc ((xi+1) * sizeof(char));
207     strncpy (ret, s, xi);
208     ret[xi] = '\0';
209     parser->start = &s[xi];
210     return ret;
211   }
212
213   return NULL;
214 }
215
216 static int
217 xtag_assert_and_pass (XTagParser * parser, int char_class)
218 {
219   char * s;
220
221   if (!parser->valid) return FALSE;
222
223   s = parser->start;
224
225   if (!xtag_cin (s[0], char_class)) {
226     parser->valid = FALSE;
227     return FALSE;
228   }
229
230   parser->start = &s[1];
231
232   return TRUE;
233 }
234
235 static char *
236 xtag_slurp_quoted (XTagParser * parser)
237 {
238   char * s, * ret;
239   int quote = X_DQUOTE; /* quote char to match on */
240   int xi;
241
242   if (!parser->valid) return NULL;
243
244   xtag_skip_whitespace (parser);
245
246   s = parser->start;
247
248   if (xtag_cin (s[0], X_SQUOTE)) quote = X_SQUOTE;
249
250   if (!xtag_assert_and_pass (parser, quote)) return NULL;
251
252   s = parser->start;
253
254   for (xi = 0; s[xi]; xi++) {
255     if (xtag_cin (s[xi], quote)) {
256       if (!(xi > 1 && s[xi-1] == '\\')) break;
257     }
258   }
259
260   ret = malloc ((xi+1) * sizeof(char));
261   strncpy (ret, s, xi);
262   ret[xi] = '\0';
263   parser->start = &s[xi];
264
265   if (!xtag_assert_and_pass (parser, quote)) return NULL;
266
267   return ret;
268 }
269
270 static XAttribute *
271 xtag_parse_attribute (XTagParser * parser)
272 {
273   XAttribute * attr;
274   char * name, * value;
275   char * s;
276
277   if (!parser->valid) return NULL;
278
279   xtag_skip_whitespace (parser);
280  
281   name = xtag_slurp_to (parser, X_WHITESPACE | X_EQUAL, X_SLASH | X_CLOSETAG);
282
283   if (name == NULL) return NULL;
284
285   xtag_skip_whitespace (parser);
286   s = parser->start;
287
288   if (!xtag_assert_and_pass (parser, X_EQUAL)) {
289 #ifdef XTAG_DEBUG
290     printf ("xtag: attr failed EQUAL on <%s>\n", name);
291 #endif
292     goto err_free_name;
293   }
294
295   xtag_skip_whitespace (parser);
296
297   value = xtag_slurp_quoted (parser);
298
299   if (value == NULL) {
300 #ifdef XTAG_DEBUG
301     printf ("Got NULL quoted attribute value\n");
302 #endif
303     goto err_free_name;
304   }
305
306   attr = malloc (sizeof (*attr));
307   attr->name = name;
308   attr->value = value;
309
310   return attr;
311
312  err_free_name:
313   free (name);
314
315   parser->valid = FALSE;
316
317   return NULL;
318 }
319
320 static XTag *
321 xtag_parse_tag (XTagParser * parser)
322 {
323   XTag * tag, * inner;
324   XAttribute * attr;
325   char * name;
326   char * pcdata;
327   char * s;
328
329   if (!parser->valid) return NULL;
330
331   if ((pcdata = xtag_slurp_to (parser, X_OPENTAG, X_NONE)) != NULL) {
332     tag = malloc (sizeof (*tag));
333     tag->name = NULL;
334     tag->pcdata = pcdata;
335     tag->parent = parser->current_tag;
336     tag->attributes = NULL;
337     tag->children = NULL;
338     tag->current_child = NULL;
339
340     return tag;
341   }
342
343   s = parser->start;
344
345   /* if this starts a close tag, return NULL and let the parent take it */
346   if (xtag_cin (s[0], X_OPENTAG) && xtag_cin (s[1], X_SLASH))
347     return NULL;
348
349   if (!xtag_assert_and_pass (parser, X_OPENTAG)) return NULL;
350
351   name = xtag_slurp_to (parser, X_WHITESPACE | X_SLASH | X_CLOSETAG, X_NONE);
352
353   if (name == NULL) return NULL;
354
355 #ifdef XTAG_DEBUG
356   printf ("<%s ...\n", name);
357 #endif
358
359   tag = malloc (sizeof (*tag));
360   tag->name = name;
361   tag->pcdata = NULL;
362   tag->parent = parser->current_tag;
363   tag->attributes = NULL;
364   tag->children = NULL;
365   tag->current_child = NULL;
366
367   s = parser->start;
368
369   if (xtag_cin (s[0], X_WHITESPACE)) {
370     while ((attr = xtag_parse_attribute (parser)) != NULL) {
371       tag->attributes = xlist_append (tag->attributes, attr);
372     }
373   }
374
375   xtag_skip_whitespace (parser);
376
377   s = parser->start;
378
379   if (xtag_cin (s[0], X_CLOSETAG)) {
380     parser->current_tag = tag;
381
382     xtag_assert_and_pass (parser, X_CLOSETAG);
383
384     while ((inner = xtag_parse_tag (parser)) != NULL) {
385       tag->children = xlist_append (tag->children, inner);
386     }
387
388     xtag_skip_whitespace (parser);
389
390     xtag_assert_and_pass (parser, X_OPENTAG);
391     xtag_assert_and_pass (parser, X_SLASH);
392     name = xtag_slurp_to (parser, X_WHITESPACE | X_CLOSETAG, X_NONE);
393     if (name) {
394       if (name && tag->name && strcmp (name, tag->name)) {
395 #ifdef XTAG_DEBUG
396         printf ("got %s expected %s\n", name, tag->name);
397 #endif
398         parser->valid = FALSE;
399       }
400       free (name);
401     }
402
403     xtag_skip_whitespace (parser);
404     xtag_assert_and_pass (parser, X_CLOSETAG);
405
406   } else {
407     xtag_assert_and_pass (parser, X_SLASH);
408     xtag_assert_and_pass (parser, X_CLOSETAG);
409   }
410
411
412   return tag;
413 }
414
415 XTag *
416 xtag_free (XTag * xtag)
417 {
418   XList * l;
419   XAttribute * attr;
420   XTag * child;
421
422   if (xtag == NULL) return NULL;
423
424   free( xtag->name );
425   free( xtag->pcdata );
426
427   for (l = xtag->attributes; l; l = l->next) {
428     if ((attr = (XAttribute *)l->data) != NULL) {
429       free( attr->name );
430       free( attr->value );
431       free( attr );
432     }
433   }
434   xlist_free (xtag->attributes);
435
436   for (l = xtag->children; l; l = l->next) {
437     child = (XTag *)l->data;
438     xtag_free (child);
439   }
440   xlist_free (xtag->children);
441
442   free (xtag);
443
444   return NULL;
445 }
446
447 XTag *
448 xtag_new_parse (const char * s, int n)
449 {
450   XTagParser parser;
451   XTag * tag, * ttag, * wrapper;
452
453   parser.valid = TRUE;
454   parser.current_tag = NULL;
455   parser.start = (char *)s;
456
457   if (n == -1)
458     parser.end = NULL;
459   else if (n == 0)
460     return NULL;
461   else
462     parser.end = (char *)&s[n];
463
464   tag = xtag_parse_tag (&parser);
465
466   if (!parser.valid) {
467     xtag_free (tag);
468     return NULL;
469   }
470
471   if ((ttag = xtag_parse_tag (&parser)) != NULL) {
472
473     if (!parser.valid) {
474       xtag_free (ttag);
475       return tag;
476     }
477
478     wrapper = malloc (sizeof (XTag));
479     wrapper->name = NULL;
480     wrapper->pcdata = NULL;
481     wrapper->parent = NULL;
482     wrapper->attributes = NULL;
483     wrapper->children = NULL;
484     wrapper->current_child = NULL;
485
486     wrapper->children = xlist_append (wrapper->children, tag);
487     wrapper->children = xlist_append (wrapper->children, ttag);
488
489     while ((ttag = xtag_parse_tag (&parser)) != NULL) {
490
491       if (!parser.valid) {
492         xtag_free (ttag);
493         return wrapper;
494       }
495
496       wrapper->children = xlist_append (wrapper->children, ttag);
497     }
498     return wrapper;
499   }
500
501   return tag;
502 }
503
504 char *
505 xtag_get_name (XTag * xtag)
506 {
507   return xtag ? xtag->name : NULL;
508 }
509
510 char *
511 xtag_get_pcdata (XTag * xtag)
512 {
513   XList * l;
514   XTag * child;
515
516   if (xtag == NULL) return NULL;
517
518   for (l = xtag->children; l; l = l->next) {
519     child = (XTag *)l->data;
520     if (child->pcdata != NULL) {
521       return child->pcdata;
522     }
523   }
524
525   return NULL;
526 }
527
528 char *
529 xtag_get_attribute (XTag * xtag, char * attribute)
530 {
531   XList * l;
532   XAttribute * attr;
533
534   if (xtag == NULL) return NULL;
535
536   for (l = xtag->attributes; l; l = l->next) {
537     if ((attr = (XAttribute *)l->data) != NULL) {
538       if (attr->name && attribute && !strcmp (attr->name, attribute))
539         return attr->value;
540     }
541   }
542
543   return NULL;
544 }
545
546 XTag *
547 xtag_first_child (XTag * xtag, char * name)
548 {
549   XList * l;
550   XTag * child;
551
552   if (xtag == NULL) return NULL;
553
554   if ((l = xtag->children) == NULL) return NULL;
555
556   if (name == NULL) {
557     xtag->current_child = l;
558     return (XTag *)l->data;
559   }
560
561   for (; l; l = l->next) {
562     child = (XTag *)l->data;
563
564     if (child->name && name && !strcmp(child->name, name)) {
565       xtag->current_child = l;
566       return child;
567     }
568   }
569
570   xtag->current_child = NULL;
571
572   return NULL;
573 }
574
575 XTag *
576 xtag_next_child (XTag * xtag, char * name)
577 {
578   XList * l;
579   XTag * child;
580
581   if (xtag == NULL) return NULL;
582
583   if ((l = xtag->current_child) == NULL)
584     return xtag_first_child (xtag, name);
585
586   if ((l = l->next) == NULL)
587     return NULL;
588
589   if (name == NULL) {
590     xtag->current_child = l;
591     return (XTag *)l->data;
592   }
593
594   for (; l; l = l->next) {
595     child = (XTag *)l->data;
596
597     if (child->name && name && !strcmp(child->name, name)) {
598       xtag->current_child = l;
599       return child;
600     }
601   }
602
603   xtag->current_child = NULL;
604
605   return NULL;
606 }
607
608 /*
609  * This snprints function takes a variable list of char *, the last of
610  * which must be NULL, and prints each in turn to buf.
611  * Returns C99-style total length that would have been written, even if
612  * this is larger than n.
613  */
614 static int
615 xtag_snprints (char * buf, int n, ...)
616 {
617   va_list ap;
618   char * s;
619   int len, to_copy, total = 0;
620
621   va_start (ap, n);
622  
623   for (s = va_arg (ap, char *); s; s = va_arg (ap, char *)) {
624     len = strlen (s);
625
626     if ((to_copy = MIN (n, len)) > 0) {
627       memcpy (buf, s, to_copy);
628       buf += to_copy;
629       n -= to_copy;
630     }
631
632     total += len;
633   }
634
635   va_end (ap);
636
637   return total;
638 }
639
640 int
641 xtag_snprint (char * buf, int n, XTag * xtag)
642 {
643   int nn, written = 0;
644   XList * l;
645   XAttribute * attr;
646   XTag * child;
647
648 #define FORWARD(N) \
649   buf += MIN (n, N); \
650   n = MAX (n-N, 0);  \
651   written += N;
652
653   if (xtag == NULL) {
654     if (n > 0) buf[0] = '\0';
655     return 0;
656   }
657
658   if (xtag->pcdata) {
659     nn = xtag_snprints (buf, n, xtag->pcdata, NULL);
660     FORWARD(nn);
661
662     return written;
663   }
664
665   if (xtag->name) {
666     nn = xtag_snprints (buf, n, "<", xtag->name, NULL);
667     FORWARD(nn);
668
669     for (l = xtag->attributes; l; l = l->next) {
670       attr = (XAttribute *)l->data;
671  
672       nn = xtag_snprints (buf, n, " ", attr->name, "=\"", attr->value, "\"",
673                           NULL);
674       FORWARD(nn);
675     }
676  
677     if (xtag->children == NULL) {
678       nn = xtag_snprints (buf, n, "/>", NULL);
679       FORWARD(nn);
680
681       return written;
682     }
683  
684     nn = xtag_snprints (buf, n, ">", NULL);
685     FORWARD(nn);
686   }
687
688   for (l = xtag->children; l; l = l->next) {
689     child = (XTag *)l->data;
690
691     nn = xtag_snprint (buf, n, child);
692     FORWARD(nn);
693   }
694
695   if (xtag->name) {
696     nn = xtag_snprints (buf, n, "</", xtag->name, ">", NULL);
697     FORWARD(nn);
698   }
699
700   return written;
701 }
702