OSDN Git Service

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