OSDN Git Service

LinGui: yikes, this thing leaks worse than my roof, fixed
[handbrake-jp/handbrake-jp-git.git] / gtk / src / plist.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <fcntl.h>
4 #include <string.h>
5 #include <glib.h>
6 #include <glib-object.h>
7
8 #include "plist.h"
9 #include "values.h"
10
11 #define BUF_SZ  (128*1024)
12
13 static gchar *preamble = 
14         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
15         "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
16         "<plist version=\"1.0\">\n";
17 static gchar *postfix = 
18         "</plist>\n";
19
20 enum
21 {
22         P_NONE = 0,
23         P_PLIST,
24         P_KEY,
25         P_ARRAY,
26         P_DICT,
27         P_INTEGER,
28         P_REAL,
29         P_STRING,
30         P_DATE,
31         P_TRUE,
32         P_FALSE,
33         P_DATA,
34 };
35
36 typedef struct
37 {
38         gchar *tag;
39         gint id;
40 } tag_map_t;
41
42 static tag_map_t tag_map[] =
43 {
44         {"plist", P_PLIST},
45         {"key", P_KEY},
46         {"array", P_ARRAY},
47         {"dict", P_DICT},
48         {"integer", P_INTEGER},
49         {"real", P_REAL},
50         {"string", P_STRING},
51         {"date", P_DATE},
52         {"true", P_TRUE},
53         {"false", P_FALSE},
54         {"data", P_DATA},
55 };
56 #define TAG_MAP_SZ      (sizeof(tag_map)/sizeof(tag_map_t))
57
58 typedef struct
59 {
60         gchar *key;
61         gchar *value;
62         GValue *plist;
63         GQueue *stack;
64         GQueue *tag_stack;
65         gboolean closed_top;
66 } parse_data_t;
67
68 static void
69 start_element(
70         GMarkupParseContext *ctx, 
71         const gchar *name, 
72         const gchar **attr_names,
73         const gchar **attr_values,
74         gpointer ud,
75         GError **error)
76 {
77         parse_data_t *pd = (parse_data_t*)ud;
78         union 
79         {
80                 gint id;
81                 gpointer pid;
82         } id;
83         gint ii;
84
85         // Check to see if the first element found has been closed
86         // If so, ignore any junk following it.
87         if (pd->closed_top)
88                 return;
89
90         for (ii = 0; ii < TAG_MAP_SZ; ii++)
91         {
92                 if (strcmp(name, tag_map[ii].tag) == 0)
93                 {
94                         id.id = tag_map[ii].id;
95                         break;
96                 }
97         }
98         if (ii == TAG_MAP_SZ)
99         {
100                 g_warning("Unrecognized start tag (%s)", name);
101                 return;
102         }
103         g_queue_push_head(pd->tag_stack, id.pid);
104         GType gtype = 0;
105         GValue *gval = NULL;
106         GValue *current = g_queue_peek_head(pd->stack);
107         switch (id.id)
108         {
109                 case P_PLIST:
110                 { // Ignore
111                 } break;
112                 case P_KEY:
113                 {
114                         if (pd->key) g_free(pd->key);
115                         pd->key = NULL;
116                 } break;
117                 case P_DICT:
118                 {
119                         gval = ghb_dict_value_new();
120                         g_queue_push_head(pd->stack, gval);
121                 } break;
122                 case P_ARRAY:
123                 {
124                         gval = ghb_array_value_new(128);
125                         g_queue_push_head(pd->stack, gval);
126                 } break;
127                 case P_INTEGER:
128                 {
129                 } break;
130                 case P_REAL:
131                 {
132                 } break;
133                 case P_STRING:
134                 {
135                 } break;
136                 case P_DATE:
137                 {
138                 } break;
139                 case P_TRUE:
140                 {
141                 } break;
142                 case P_FALSE:
143                 {
144                 } break;
145                 case P_DATA:
146                 {
147                 } break;
148         }
149         // Add the element to the current container
150         if (gval)
151         { // There's an element to add
152                 if (current == NULL)
153                 {
154                         pd->plist = gval;
155                         return;
156                 }
157                 gtype = G_VALUE_TYPE(current);
158                 if (gtype == ghb_array_get_type())
159                 {
160                         ghb_array_append(current, gval);
161                 }
162                 else if (gtype == ghb_dict_get_type())
163                 {
164                         if (pd->key == NULL)
165                         {
166                                 g_warning("No key for dictionary item");
167                                 ghb_value_free(gval);
168                         }
169                         else
170                         {
171                                 ghb_dict_insert(current, g_strdup(pd->key), gval);
172                         }
173                 }
174                 else
175                 {
176                         g_error("Invalid container type. This shouldn't happen");
177                 }
178         }
179 }
180
181 static void
182 end_element(
183         GMarkupParseContext *ctx, 
184         const gchar *name, 
185         gpointer ud,
186         GError **error)
187 {
188         parse_data_t *pd = (parse_data_t*)ud;
189         gint id;
190         union 
191         {
192                 gint id;
193                 gpointer pid;
194         } start_id;
195         gint ii;
196
197         // Check to see if the first element found has been closed
198         // If so, ignore any junk following it.
199         if (pd->closed_top)
200                 return;
201
202         for (ii = 0; ii < TAG_MAP_SZ; ii++)
203         {
204                 if (strcmp(name, tag_map[ii].tag) == 0)
205                 {
206                         id = tag_map[ii].id;
207                         break;
208                 }
209         }
210         if (ii == TAG_MAP_SZ)
211         {
212                 g_warning("Unrecognized start tag (%s)", name);
213                 return;
214         }
215         start_id.pid = g_queue_pop_head(pd->tag_stack);
216         if (start_id.id != id)
217                 g_warning("start tag != end tag: (%s %d) %d", name, id, id);
218
219         GValue *gval = NULL;
220         GValue *current = g_queue_peek_head(pd->stack);
221         GType gtype = 0;
222         switch (id)
223         {
224                 case P_PLIST:
225                 { // Ignore
226                 } break;
227                 case P_KEY:
228                 {
229                         if (pd->key) g_free(pd->key);
230                         pd->key = g_strdup(pd->value);
231                         return;
232                 } break;
233                 case P_DICT:
234                 {
235                         g_queue_pop_head(pd->stack);
236                 } break;
237                 case P_ARRAY:
238                 {
239                         g_queue_pop_head(pd->stack);
240                 } break;
241                 case P_INTEGER:
242                 {
243                         gint64 val = g_strtod(pd->value, NULL);
244                         gval = ghb_int64_value_new(val);
245                 } break;
246                 case P_REAL:
247                 {
248                         gdouble val = g_strtod(pd->value, NULL);
249                         gval = ghb_double_value_new(val);
250                 } break;
251                 case P_STRING:
252                 {
253                         gval = ghb_string_value_new(pd->value);
254                 } break;
255                 case P_DATE:
256                 {
257                         GDate date;
258                         GTimeVal time;
259                         g_time_val_from_iso8601(pd->value, &time);
260                         g_date_set_time_val(&date, &time);
261                         gval = ghb_date_value_new(&date);
262                 } break;
263                 case P_TRUE:
264                 {
265                         gval = ghb_boolean_value_new(TRUE);
266                 } break;
267                 case P_FALSE:
268                 {
269                         gval = ghb_boolean_value_new(FALSE);
270                 } break;
271                 case P_DATA:
272                 {
273                         ghb_rawdata_t *data;
274                         data = g_malloc(sizeof(ghb_rawdata_t));
275                         data->data = g_base64_decode(pd->value, &(data->size));
276                         gval = ghb_rawdata_value_new(data);
277                 } break;
278         }
279         if (gval)
280         {
281                 // Get the top of the data structure stack and if it's an array
282                 // or dict, add the current element
283                 if (current == NULL)
284                 {
285                         pd->plist = gval;
286                         pd->closed_top = TRUE;
287                         return;
288                 }
289                 gtype = G_VALUE_TYPE(current);
290                 if (gtype == ghb_array_get_type())
291                 {
292                         ghb_array_append(current, gval);
293                 }
294                 else if (gtype == ghb_dict_get_type())
295                 {
296                         if (pd->key == NULL)
297                         {
298                                 g_warning("No key for dictionary item");
299                                 ghb_value_free(gval);
300                         }
301                         else
302                         {
303                                 ghb_dict_insert(current, g_strdup(pd->key), gval);
304                         }
305                 }
306                 else
307                 {
308                         g_error("Invalid container type. This shouldn't happen");
309                 }
310         }
311         if (g_queue_is_empty(pd->stack))
312                 pd->closed_top = TRUE;
313 }
314
315 static void
316 text_data(
317         GMarkupParseContext *ctx, 
318         const gchar *text, 
319         gsize len,
320         gpointer ud,
321         GError **error)
322 {
323         parse_data_t *pd = (parse_data_t*)ud;
324         if (pd->value) g_free(pd->value);
325         pd->value = g_strdup(text);
326 }
327
328 static void
329 passthrough(
330         GMarkupParseContext *ctx, 
331         const gchar *text, 
332         gsize len,
333         gpointer ud,
334         GError **error)
335 {
336         //parse_data_t *pd = (parse_data_t*)ud;
337
338         //g_debug("passthrough %s", text);
339 }
340
341 static void
342 parse_error(GMarkupParseContext *ctx, GError *error, gpointer ud)
343 {
344         g_warning("Plist parse error: %s", error->message);
345 }
346
347 // This is required or the parser crashes
348 static void 
349 destroy_notify(gpointer data)
350 { // Do nothing
351         g_debug("destroy parser");
352 }
353
354 GValue*
355 ghb_plist_parse(const gchar *buf, gssize len)
356 {
357         GMarkupParseContext *ctx;
358         GMarkupParser parser;
359         parse_data_t pd;
360         GError *err = NULL;
361
362         pd.stack = g_queue_new();
363         pd.tag_stack = g_queue_new();
364         pd.key = NULL;
365         pd.value = NULL;
366         pd.plist = NULL;
367         pd.closed_top = FALSE;
368
369         parser.start_element = start_element;
370         parser.end_element = end_element;
371         parser.text = text_data;
372         parser.passthrough = passthrough;
373         parser.error = parse_error;
374         ctx = g_markup_parse_context_new(&parser, 0, &pd, destroy_notify);
375
376         g_markup_parse_context_parse(ctx, buf, len, &err);
377         g_markup_parse_context_end_parse(ctx, &err);
378         g_markup_parse_context_free(ctx);
379         g_queue_free(pd.stack);
380         g_queue_free(pd.tag_stack);
381         return pd.plist;
382 }
383
384 GValue*
385 ghb_plist_parse_file(FILE *fd)
386 {
387         gchar *buffer;
388         size_t size;
389         GValue *gval;
390
391         if (fd == NULL)
392                 return NULL;
393         fseek(fd, 0, SEEK_END);
394         size = ftell(fd);
395         fseek(fd, 0, SEEK_SET);
396         buffer = g_malloc(size+1);
397         size = fread(buffer, 1, size, fd);
398         buffer[size] = 0;
399         gval = ghb_plist_parse(buffer, (gssize)size);
400         g_free(buffer);
401         return gval;
402 }
403
404 static void
405 indent_fprintf(FILE *file, gint indent, const gchar *fmt, ...)
406 {
407         va_list ap;
408
409         for (; indent; indent--)
410                 putc('\t', file);
411         va_start(ap, fmt);
412         vfprintf(file, fmt, ap);
413         va_end(ap);
414 }
415
416 // Used for sorting dictionaries.
417 static gint
418 key_cmp(gconstpointer a, gconstpointer b)
419 {
420         gchar *stra = (gchar*)a;
421         gchar *strb = (gchar*)b;
422
423         return strcmp(stra, strb);
424 }
425
426 static void
427 gval_write(FILE *file, GValue *gval)
428 {
429         static gint indent = 0;
430         gint ii;
431         GType gtype;
432
433         if (gval == NULL) return;
434         gtype = G_VALUE_TYPE(gval);
435         if (gtype == ghb_array_get_type())
436         {
437                 GValue *val;
438                 gint count;
439
440                 indent_fprintf(file, indent, "<array>\n");
441                 indent++;
442                 count = ghb_array_len(gval);
443                 for (ii = 0; ii < count; ii++)
444                 {
445                         val = ghb_array_get_nth(gval, ii);
446                         gval_write(file, val);
447                 }
448                 indent--;
449                 indent_fprintf(file, indent, "</array>\n");
450         }
451         else if (gtype == ghb_dict_get_type())
452         {
453                 GValue *val;
454                 GHashTable *dict = g_value_get_boxed(gval);
455                 GList *link, *keys;
456                 keys = g_hash_table_get_keys(dict);
457                 // Sort the dictionary.  Not really necessray, but it makes
458                 // finding things easier
459                 keys = g_list_sort(keys, key_cmp);
460                 link = keys;
461                 indent_fprintf(file, indent, "<dict>\n");
462                 indent++;
463                 while (link)
464                 {
465                         gchar *key = (gchar*)link->data;
466                         val = g_hash_table_lookup(dict, key);
467                         indent_fprintf(file, indent, "<key>%s</key>\n", key);
468                         gval_write(file, val);
469                         link = link->next;
470                 }
471                 indent--;
472                 indent_fprintf(file, indent, "</dict>\n");
473                 g_list_free(keys);
474         }
475         else if (gtype == G_TYPE_BOOLEAN)
476         {
477                 gchar *tag;
478                 if (g_value_get_boolean(gval))
479                 {
480                         tag = "true";
481                 }
482                 else
483                 {
484                         tag = "false";
485                 }
486                 indent_fprintf(file, indent, "<%s />\n", tag);
487         }
488         else if (gtype == g_date_get_type())
489         {
490                 GDate *date;
491                 date = g_value_get_boxed(gval);
492                 indent_fprintf(file, indent, "<date>%d-%d-%d</date>\n", 
493                         g_date_get_year(date),
494                         g_date_get_month(date),
495                         g_date_get_day(date)
496                 );
497         }
498         else if (gtype == ghb_rawdata_get_type())
499         {
500                 ghb_rawdata_t *data;
501                 gchar *base64;
502                 data = g_value_get_boxed(gval);
503                 base64 = g_base64_encode(data->data, data->size);
504                 indent_fprintf(file, indent, "<data>\n");
505                 indent_fprintf(file, 0, "%s\n", base64);
506                 indent_fprintf(file, indent, "</data>\n");
507                 g_free(base64);
508         }
509         else if (gtype == G_TYPE_DOUBLE)
510         {
511                 gdouble val = g_value_get_double(gval);
512                 indent_fprintf(file, indent, "<real>%.17g</real>\n", val);
513         }
514         else if (gtype == G_TYPE_INT64)
515         {
516                 gint val = g_value_get_int64(gval);
517                 indent_fprintf(file, indent, "<integer>%d</integer>\n", val);
518         }
519         else if (gtype == G_TYPE_STRING)
520         {
521                 const gchar *str = g_value_get_string(gval);
522                 gchar *esc = g_markup_escape_text(str, -1);
523                 indent_fprintf(file, indent, "<string>%s</string>\n", esc);
524                 g_free(esc);
525         }
526         else
527         {
528                 // Try to make anything thats unrecognized into a string
529                 const gchar *str;
530                 GValue val = {0,};
531                 g_value_init(&val, G_TYPE_STRING);
532                 if (g_value_transform(gval, &val))
533                 {
534                         str = g_value_get_string(&val);
535                         gchar *esc = g_markup_escape_text(str, -1);
536                         indent_fprintf(file, indent, "<string>%s</string>\n", esc);
537                         g_free(esc);
538                 }
539                 else
540                 {
541                         g_message("failed to transform");
542                 }
543                 g_value_unset(&val);
544         }
545 }
546
547 void
548 ghb_plist_write(FILE *file, GValue *gval)
549 {
550         fprintf(file, "%s", preamble);
551         gval_write(file, gval);
552         fprintf(file, "%s", postfix);
553 }
554
555 void
556 ghb_plist_write_file(const gchar *filename, GValue *gval)
557 {
558         FILE *file;
559
560         file = fopen(filename, "w");
561         if (file == NULL)
562                 return;
563
564         fprintf(file, "%s", preamble);
565         gval_write(file, gval);
566         fprintf(file, "%s", postfix);
567 }
568
569
570 #if defined(PL_TEST)
571 gint
572 main(gint argc, gchar *argv[])
573 {
574         GValue *gval;
575
576         g_type_init();
577
578         file = g_fopen(argv[1], "r");
579         gval = ghb_plist_parse_file(file);
580         if (argc > 2)
581                 ghb_plist_write_file(argv[2], gval);
582         else
583                 ghb_plist_write(stdout, gval);
584         if (file) fclose (file);
585         return 0;
586 }
587 #endif