OSDN Git Service

79da274d23915d5f1882d2b71c3cf83901e69077
[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/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         g_queue_free(pd.stack);
381         g_queue_free(pd.tag_stack);
382         return pd.plist;
383 }
384
385 GValue*
386 ghb_plist_parse_file(const gchar *filename)
387 {
388         gchar *buffer;
389         size_t size;
390         GValue *gval;
391         FILE *fd;
392
393         fd = g_fopen(filename, "r");
394         if (fd == NULL)
395         {
396                 g_warning("Plist parse: failed to open %s", filename);
397                 return NULL;
398         }
399         fseek(fd, 0, SEEK_END);
400         size = ftell(fd);
401         fseek(fd, 0, SEEK_SET);
402         buffer = g_malloc(size+1);
403         size = fread(buffer, 1, size, fd);
404         buffer[size] = 0;
405         gval = ghb_plist_parse(buffer, (gssize)size);
406         g_free(buffer);
407         fclose(fd);
408         return gval;
409 }
410
411 static void
412 indent_fprintf(FILE *file, gint indent, const gchar *fmt, ...)
413 {
414         va_list ap;
415
416         for (; indent; indent--)
417                 putc('\t', file);
418         va_start(ap, fmt);
419         vfprintf(file, fmt, ap);
420         va_end(ap);
421 }
422
423 // Used for sorting dictionaries.
424 static gint
425 key_cmp(gconstpointer a, gconstpointer b)
426 {
427         gchar *stra = (gchar*)a;
428         gchar *strb = (gchar*)b;
429
430         return strcmp(stra, strb);
431 }
432
433 static void
434 gval_write(FILE *file, GValue *gval)
435 {
436         static gint indent = 0;
437         gint ii;
438         GType gtype;
439
440         if (gval == NULL) return;
441         gtype = G_VALUE_TYPE(gval);
442         if (gtype == ghb_array_get_type())
443         {
444                 GValue *val;
445                 gint count;
446
447                 indent_fprintf(file, indent, "<array>\n");
448                 indent++;
449                 count = ghb_array_len(gval);
450                 for (ii = 0; ii < count; ii++)
451                 {
452                         val = ghb_array_get_nth(gval, ii);
453                         gval_write(file, val);
454                 }
455                 indent--;
456                 indent_fprintf(file, indent, "</array>\n");
457         }
458         else if (gtype == ghb_dict_get_type())
459         {
460                 GValue *val;
461                 GHashTable *dict = g_value_get_boxed(gval);
462                 GList *link, *keys;
463                 keys = g_hash_table_get_keys(dict);
464                 // Sort the dictionary.  Not really necessray, but it makes
465                 // finding things easier
466                 keys = g_list_sort(keys, key_cmp);
467                 link = keys;
468                 indent_fprintf(file, indent, "<dict>\n");
469                 indent++;
470                 while (link)
471                 {
472                         gchar *key = (gchar*)link->data;
473                         val = g_hash_table_lookup(dict, key);
474                         indent_fprintf(file, indent, "<key>%s</key>\n", key);
475                         gval_write(file, val);
476                         link = link->next;
477                 }
478                 indent--;
479                 indent_fprintf(file, indent, "</dict>\n");
480                 g_list_free(keys);
481         }
482         else if (gtype == G_TYPE_BOOLEAN)
483         {
484                 gchar *tag;
485                 if (g_value_get_boolean(gval))
486                 {
487                         tag = "true";
488                 }
489                 else
490                 {
491                         tag = "false";
492                 }
493                 indent_fprintf(file, indent, "<%s />\n", tag);
494         }
495         else if (gtype == g_date_get_type())
496         {
497                 GDate *date;
498                 date = g_value_get_boxed(gval);
499                 indent_fprintf(file, indent, "<date>%d-%d-%d</date>\n", 
500                         g_date_get_year(date),
501                         g_date_get_month(date),
502                         g_date_get_day(date)
503                 );
504         }
505         else if (gtype == ghb_rawdata_get_type())
506         {
507                 ghb_rawdata_t *data;
508                 gchar *base64;
509                 data = g_value_get_boxed(gval);
510                 base64 = g_base64_encode(data->data, data->size);
511                 indent_fprintf(file, indent, "<data>\n");
512                 indent_fprintf(file, 0, "%s\n", base64);
513                 indent_fprintf(file, indent, "</data>\n");
514                 g_free(base64);
515         }
516         else if (gtype == G_TYPE_DOUBLE)
517         {
518                 gdouble val = g_value_get_double(gval);
519                 indent_fprintf(file, indent, "<real>%.17g</real>\n", val);
520         }
521         else if (gtype == G_TYPE_INT64)
522         {
523                 gint val = g_value_get_int64(gval);
524                 indent_fprintf(file, indent, "<integer>%d</integer>\n", val);
525         }
526         else if (gtype == G_TYPE_STRING)
527         {
528                 const gchar *str = g_value_get_string(gval);
529                 gchar *esc = g_markup_escape_text(str, -1);
530                 indent_fprintf(file, indent, "<string>%s</string>\n", esc);
531                 g_free(esc);
532         }
533         else
534         {
535                 // Try to make anything thats unrecognized into a string
536                 const gchar *str;
537                 GValue val = {0,};
538                 g_value_init(&val, G_TYPE_STRING);
539                 if (g_value_transform(gval, &val))
540                 {
541                         str = g_value_get_string(&val);
542                         gchar *esc = g_markup_escape_text(str, -1);
543                         indent_fprintf(file, indent, "<string>%s</string>\n", esc);
544                         g_free(esc);
545                 }
546                 else
547                 {
548                         g_message("failed to transform");
549                 }
550                 g_value_unset(&val);
551         }
552 }
553
554 void
555 ghb_plist_write(FILE *file, GValue *gval)
556 {
557         fprintf(file, "%s", preamble);
558         gval_write(file, gval);
559         fprintf(file, "%s", postfix);
560 }
561
562 void
563 ghb_plist_write_file(const gchar *filename, GValue *gval)
564 {
565         FILE *file;
566
567         file = fopen(filename, "w");
568         if (file == NULL)
569                 return;
570
571         fprintf(file, "%s", preamble);
572         gval_write(file, gval);
573         fprintf(file, "%s", postfix);
574 }
575
576
577 #if defined(PL_TEST)
578 gint
579 main(gint argc, gchar *argv[])
580 {
581         GValue *gval;
582
583         g_type_init();
584
585         file = g_fopen(argv[1], "r");
586         gval = ghb_plist_parse_file(file);
587         if (argc > 2)
588                 ghb_plist_write_file(argv[2], gval);
589         else
590                 ghb_plist_write(stdout, gval);
591         if (file) fclose (file);
592         return 0;
593 }
594 #endif