]> git.sesse.net Git - vlc/blob - modules/codec/cmml/xtag.c
3a661b906297e040940adeb7b4b30095315d55ab
[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 <vlc_common.h>
32
33 #include <ctype.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include "xlist.h"
40
41 #include <assert.h>
42
43 #undef XTAG_DEBUG
44
45 #undef MIN
46 #define MIN(a,b) ((a)<(b)?(a):(b))
47
48 #undef MAX
49 #define MAX(a,b) ((a)>(b)?(a):(b))
50
51 typedef struct _XTag XTag;
52 typedef struct _XAttribute XAttribute;
53 typedef struct _XTagParser XTagParser;
54
55 /*
56  * struct _XTag is kind of a union ... it normally represents a whole
57  * tag (and its children), but it could alternatively represent some
58  * PCDATA. Basically, if tag->pcdata is non-NULL, interpret only it and
59  * ignore the name, attributes and inner_tags.
60  */
61 struct _XTag {
62   char * name;
63   char * pcdata;
64   XTag * parent;
65   XList * attributes;
66   XList * children;
67   XList * current_child;
68 };
69
70 struct _XAttribute {
71   char * name;
72   char * value;
73 };
74
75 struct _XTagParser {
76   int valid; /* boolean */
77   XTag * current_tag;
78   char * start;
79   char * end;
80 };
81
82 void   xtag_free (XTag * xtag);
83 XTag * xtag_new_parse (const char * s, int n);
84 char * xtag_get_name (XTag * xtag);
85 char * xtag_get_pcdata (XTag * xtag);
86 char * xtag_get_attribute (XTag * xtag, const char* attribute);
87 XTag * xtag_first_child (XTag * xtag, const char * name);
88 XTag * xtag_next_child (XTag * xtag, char * name);
89 int    xtag_snprint (char * buf, int n, XTag * xtag);
90
91 /* Character classes */
92 #define X_NONE           0
93 #define X_WHITESPACE  1<<0
94 #define X_OPENTAG     1<<1
95 #define X_CLOSETAG    1<<2
96 #define X_DQUOTE      1<<3
97 #define X_SQUOTE      1<<4
98 #define X_EQUAL       1<<5
99 #define X_SLASH       1<<6
100
101 static int
102 xtag_cin (char c, int char_class)
103 {
104   if (char_class & X_WHITESPACE)
105     if (isspace(c)) return true;
106
107   if (char_class & X_OPENTAG)
108     if (c == '<') return true;
109
110   if (char_class & X_CLOSETAG)
111     if (c == '>') return true;
112
113   if (char_class & X_DQUOTE)
114     if (c == '"') return true;
115
116   if (char_class & X_SQUOTE)
117     if (c == '\'') return true;
118
119   if (char_class & X_EQUAL)
120     if (c == '=') return true;
121
122   if (char_class & X_SLASH)
123     if (c == '/') return true;
124
125   return false;
126 }
127
128 static int
129 xtag_index (XTagParser * parser, int char_class)
130 {
131   char * s;
132   int i;
133
134   s = parser->start;
135
136   for (i = 0; s[i] && s != parser->end; i++) {
137     if (xtag_cin(s[i], char_class)) return i;
138   }
139
140   return -1;
141 }
142
143 static void
144 xtag_skip_over (XTagParser * parser, int char_class)
145 {
146   char * s;
147   int i;
148
149   if (!parser->valid) return;
150
151   s = (char *)parser->start;
152
153   for (i = 0; s[i] && s != parser->end; i++) {
154     if (!xtag_cin(s[i], char_class)) {
155       parser->start = &s[i];
156       return;
157     }
158   }
159
160   return;
161 }
162
163 static void
164 xtag_skip_whitespace (XTagParser * parser)
165 {
166   xtag_skip_over (parser, X_WHITESPACE);
167 }
168
169 #if 0
170 static void
171 xtag_skip_to (XTagParser * parser, int char_class)
172 {
173   char * s;
174   int i;
175
176   if (!parser->valid) return;
177
178   s = (char *)parser->start;
179
180   for (i = 0; s[i] && s != parser->end; i++) {
181     if (xtag_cin(s[i], char_class)) {
182       parser->start = &s[i];
183       return;
184     }
185   }
186
187   return;
188 }
189 #endif
190
191 static char *
192 xtag_slurp_to (XTagParser * parser, int good_end, int bad_end)
193 {
194   char * s, * ret;
195   int xi;
196
197   if (!parser->valid) return NULL;
198
199   s = parser->start;
200
201   xi = xtag_index (parser, good_end | bad_end);
202
203   if (xi > 0 && xtag_cin (s[xi], good_end)) {
204     ret = malloc (xi+1);
205     strncpy (ret, s, xi);
206     ret[xi] = '\0';
207     parser->start = &s[xi];
208     return ret;
209   }
210
211   return NULL;
212 }
213
214 static int
215 xtag_assert_and_pass (XTagParser * parser, int char_class)
216 {
217   char * s;
218
219   if (!parser->valid) return false;
220
221   s = parser->start;
222
223   if (!xtag_cin (s[0], char_class)) {
224     parser->valid = false;
225     return false;
226   }
227
228   parser->start = &s[1];
229
230   return true;
231 }
232
233 static char *
234 xtag_slurp_quoted (XTagParser * parser)
235 {
236   char * s, * ret;
237   int quote = X_DQUOTE; /* quote char to match on */
238   int xi;
239
240   if (!parser->valid) return NULL;
241
242   xtag_skip_whitespace (parser);
243
244   s = parser->start;
245
246   if (xtag_cin (s[0], X_SQUOTE)) quote = X_SQUOTE;
247
248   if (!xtag_assert_and_pass (parser, quote)) return NULL;
249
250   s = parser->start;
251
252   for (xi = 0; s[xi]; xi++) {
253     if (xtag_cin (s[xi], quote)) {
254       if (!(xi > 1 && s[xi-1] == '\\')) break;
255     }
256   }
257
258   ret = malloc (xi+1);
259   strncpy (ret, s, xi);
260   ret[xi] = '\0';
261   parser->start = &s[xi];
262
263   if (!xtag_assert_and_pass (parser, quote))
264   {
265      free( ret );
266      return NULL;
267   }
268
269   return ret;
270 }
271
272 static XAttribute *
273 xtag_parse_attribute (XTagParser * parser)
274 {
275   XAttribute * attr;
276   char * name, * value;
277   char * s;
278
279   if (!parser->valid) return NULL;
280
281   xtag_skip_whitespace (parser);
282  
283   name = xtag_slurp_to (parser, X_WHITESPACE | X_EQUAL, X_SLASH | X_CLOSETAG);
284
285   if (name == NULL) return NULL;
286
287   xtag_skip_whitespace (parser);
288   s = parser->start;
289
290   if (!xtag_assert_and_pass (parser, X_EQUAL)) {
291 #ifdef XTAG_DEBUG
292     printf ("xtag: attr failed EQUAL on <%s>\n", name);
293 #endif
294     goto err_free_name;
295   }
296
297   xtag_skip_whitespace (parser);
298
299   value = xtag_slurp_quoted (parser);
300
301   if (value == NULL) {
302 #ifdef XTAG_DEBUG
303     printf ("Got NULL quoted attribute value\n");
304 #endif
305     goto err_free_name;
306   }
307
308   attr = malloc (sizeof (*attr));
309   attr->name = name;
310   attr->value = value;
311
312   return attr;
313
314  err_free_name:
315   free (name);
316
317   parser->valid = false;
318
319   return NULL;
320 }
321
322 static XTag *
323 xtag_parse_tag (XTagParser * parser)
324 {
325   XTag * tag, * inner;
326   XAttribute * attr;
327   char * name;
328   char * pcdata;
329   char * s;
330
331   if (!parser->valid) return NULL;
332
333   if ((pcdata = xtag_slurp_to (parser, X_OPENTAG, X_NONE)) != NULL) {
334     tag = malloc (sizeof (*tag));
335     tag->name = NULL;
336     tag->pcdata = pcdata;
337     tag->parent = parser->current_tag;
338     tag->attributes = NULL;
339     tag->children = NULL;
340     tag->current_child = NULL;
341
342     return tag;
343   }
344
345   s = parser->start;
346
347   /* if this starts a close tag, return NULL and let the parent take it */
348   if (xtag_cin (s[0], X_OPENTAG) && xtag_cin (s[1], X_SLASH))
349     return NULL;
350
351   if (!xtag_assert_and_pass (parser, X_OPENTAG)) return NULL;
352
353   name = xtag_slurp_to (parser, X_WHITESPACE | X_SLASH | X_CLOSETAG, X_NONE);
354
355   if (name == NULL) return NULL;
356
357 #ifdef XTAG_DEBUG
358   printf ("<%s ...\n", name);
359 #endif
360
361   tag = malloc (sizeof (*tag));
362   tag->name = name;
363   tag->pcdata = NULL;
364   tag->parent = parser->current_tag;
365   tag->attributes = NULL;
366   tag->children = NULL;
367   tag->current_child = NULL;
368
369   s = parser->start;
370
371   if (xtag_cin (s[0], X_WHITESPACE)) {
372     while ((attr = xtag_parse_attribute (parser)) != NULL) {
373       tag->attributes = xlist_append (tag->attributes, attr);
374     }
375   }
376
377   xtag_skip_whitespace (parser);
378
379   s = parser->start;
380
381   if (xtag_cin (s[0], X_CLOSETAG)) {
382     parser->current_tag = tag;
383
384     xtag_assert_and_pass (parser, X_CLOSETAG);
385
386     while ((inner = xtag_parse_tag (parser)) != NULL) {
387       tag->children = xlist_append (tag->children, inner);
388     }
389
390     xtag_skip_whitespace (parser);
391
392     xtag_assert_and_pass (parser, X_OPENTAG);
393     xtag_assert_and_pass (parser, X_SLASH);
394     name = xtag_slurp_to (parser, X_WHITESPACE | X_CLOSETAG, X_NONE);
395     if (name) {
396       if (name && tag->name && strcmp (name, tag->name)) {
397 #ifdef XTAG_DEBUG
398         printf ("got %s expected %s\n", name, tag->name);
399 #endif
400         parser->valid = false;
401       }
402       free (name);
403     }
404
405     xtag_skip_whitespace (parser);
406     xtag_assert_and_pass (parser, X_CLOSETAG);
407
408   } else {
409     xtag_assert_and_pass (parser, X_SLASH);
410     xtag_assert_and_pass (parser, X_CLOSETAG);
411   }
412
413
414   return tag;
415 }
416
417 void xtag_free (XTag * xtag)
418 {
419   XList * l;
420   XAttribute * attr;
421   XTag * child;
422
423   if( !xtag )
424     return;
425
426   free( xtag->name );
427   free( xtag->pcdata );
428
429   for( l = xtag->attributes; l; l = l->next) {
430     if((attr = (XAttribute *)l->data) != NULL) {
431       free( attr->name );
432       free( attr->value );
433       free( attr );
434     }
435   }
436   xlist_free (xtag->attributes);
437
438   for (l = xtag->children; l; l = l->next) {
439     child = (XTag *)l->data;
440     xtag_free( child );
441   }
442   xlist_free (xtag->children);
443
444   free( xtag );
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* xtag_get_attribute (XTag * xtag, const char * attribute)
529 {
530     XList * l;
531     XAttribute * attr;
532
533     if( !xtag )
534         return NULL;
535
536     for( l = xtag->attributes; l; l = l->next )
537     {
538         if( ( attr = (XAttribute *)l->data ) != NULL )
539         {
540             if( attr->name && attribute && !strcmp( attr->name, attribute ) )
541                 return attr->value;
542         }
543     }
544     return NULL;
545 }
546
547 XTag* xtag_first_child (XTag * xtag, const char * name)
548 {
549     XList * l;
550     XTag * child;
551
552     if( !xtag )
553         return NULL;
554
555     if( ( l = xtag->children ) == NULL )
556         return NULL;
557
558     if( !name )
559     {
560         xtag->current_child = l;
561         return (XTag *)l->data;
562     }
563
564     for( ; l; l = l->next )
565     {
566         child = (XTag *)l->data;
567
568         if( child->name && name && !strcmp( child->name, name ) )
569         {
570             xtag->current_child = l;
571             return child;
572         }
573     }
574
575     xtag->current_child = NULL;
576     return NULL;
577 }
578
579 XTag *
580 xtag_next_child (XTag * xtag, char * name)
581 {
582   XList * l;
583   XTag * child;
584
585   if (xtag == NULL) return NULL;
586
587   if ((l = xtag->current_child) == NULL)
588     return xtag_first_child (xtag, name);
589
590   if ((l = l->next) == NULL)
591     return NULL;
592
593   if (name == NULL) {
594     xtag->current_child = l;
595     return (XTag *)l->data;
596   }
597
598   for (; l; l = l->next) {
599     child = (XTag *)l->data;
600
601     if (child->name && name && !strcmp(child->name, name)) {
602       xtag->current_child = l;
603       return child;
604     }
605   }
606
607   xtag->current_child = NULL;
608
609   return NULL;
610 }
611
612 /*
613  * This snprints function takes a variable list of char *, the last of
614  * which must be NULL, and prints each in turn to buf.
615  * Returns C99-style total length that would have been written, even if
616  * this is larger than n.
617  */
618 static int
619 xtag_snprints (char * buf, int n, ...)
620 {
621   va_list ap;
622   char * s;
623   int len, to_copy, total = 0;
624
625   va_start (ap, n);
626  
627   for (s = va_arg (ap, char *); s; s = va_arg (ap, char *)) {
628     len = strlen (s);
629
630     if ((to_copy = MIN (n, len)) > 0) {
631       memcpy (buf, s, to_copy);
632       buf += to_copy;
633       n -= to_copy;
634     }
635
636     total += len;
637   }
638
639   va_end (ap);
640
641   return total;
642 }
643
644 int
645 xtag_snprint (char * buf, int n, XTag * xtag)
646 {
647   int nn, written = 0;
648   XList * l;
649   XAttribute * attr;
650   XTag * child;
651
652 #define FORWARD(N) \
653   buf += MIN (n, N); \
654   n = MAX (n-N, 0);  \
655   written += N;
656
657   if (xtag == NULL) {
658     if (n > 0) buf[0] = '\0';
659     return 0;
660   }
661
662   if (xtag->pcdata) {
663     nn = xtag_snprints (buf, n, xtag->pcdata, NULL);
664     FORWARD(nn);
665
666     return written;
667   }
668
669   if (xtag->name) {
670     nn = xtag_snprints (buf, n, "<", xtag->name, NULL);
671     FORWARD(nn);
672
673     for (l = xtag->attributes; l; l = l->next) {
674       attr = (XAttribute *)l->data;
675  
676       nn = xtag_snprints (buf, n, " ", attr->name, "=\"", attr->value, "\"",
677                           NULL);
678       FORWARD(nn);
679     }
680  
681     if (xtag->children == NULL) {
682       nn = xtag_snprints (buf, n, "/>", NULL);
683       FORWARD(nn);
684
685       return written;
686     }
687  
688     nn = xtag_snprints (buf, n, ">", NULL);
689     FORWARD(nn);
690   }
691
692   for (l = xtag->children; l; l = l->next) {
693     child = (XTag *)l->data;
694
695     nn = xtag_snprint (buf, n, child);
696     FORWARD(nn);
697   }
698
699   if (xtag->name) {
700     nn = xtag_snprints (buf, n, "</", xtag->name, ">", NULL);
701     FORWARD(nn);
702   }
703
704   return written;
705 }
706