diff options
Diffstat (limited to 'gtk/src/plist.c')
-rw-r--r-- | gtk/src/plist.c | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/gtk/src/plist.c b/gtk/src/plist.c new file mode 100644 index 000000000..da7bc55e7 --- /dev/null +++ b/gtk/src/plist.c @@ -0,0 +1,587 @@ +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> +#include <glib.h> +#include <glib-object.h> + +#include "plist.h" +#include "values.h" + +#define BUF_SZ (128*1024) + +static gchar *preamble = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">\n"; +static gchar *postfix = + "</plist>\n"; + +enum +{ + P_NONE = 0, + P_PLIST, + P_KEY, + P_ARRAY, + P_DICT, + P_INTEGER, + P_REAL, + P_STRING, + P_DATE, + P_TRUE, + P_FALSE, + P_DATA, +}; + +typedef struct +{ + gchar *tag; + gint id; +} tag_map_t; + +static tag_map_t tag_map[] = +{ + {"plist", P_PLIST}, + {"key", P_KEY}, + {"array", P_ARRAY}, + {"dict", P_DICT}, + {"integer", P_INTEGER}, + {"real", P_REAL}, + {"string", P_STRING}, + {"date", P_DATE}, + {"true", P_TRUE}, + {"false", P_FALSE}, + {"data", P_DATA}, +}; +#define TAG_MAP_SZ (sizeof(tag_map)/sizeof(tag_map_t)) + +typedef struct +{ + gchar *key; + gchar *value; + GValue *plist; + GQueue *stack; + GQueue *tag_stack; + gboolean closed_top; +} parse_data_t; + +static void +start_element( + GMarkupParseContext *ctx, + const gchar *name, + const gchar **attr_names, + const gchar **attr_values, + gpointer ud, + GError **error) +{ + parse_data_t *pd = (parse_data_t*)ud; + union + { + gint id; + gpointer pid; + } id; + gint ii; + + // Check to see if the first element found has been closed + // If so, ignore any junk following it. + if (pd->closed_top) + return; + + for (ii = 0; ii < TAG_MAP_SZ; ii++) + { + if (strcmp(name, tag_map[ii].tag) == 0) + { + id.id = tag_map[ii].id; + break; + } + } + if (ii == TAG_MAP_SZ) + { + g_warning("Unrecognized start tag (%s)", name); + return; + } + g_queue_push_head(pd->tag_stack, id.pid); + GType gtype = 0; + GValue *gval = NULL; + GValue *current = g_queue_peek_head(pd->stack); + switch (id.id) + { + case P_PLIST: + { // Ignore + } break; + case P_KEY: + { + if (pd->key) g_free(pd->key); + pd->key = NULL; + } break; + case P_DICT: + { + gval = ghb_dict_value_new(); + g_queue_push_head(pd->stack, gval); + } break; + case P_ARRAY: + { + gval = ghb_array_value_new(128); + g_queue_push_head(pd->stack, gval); + } break; + case P_INTEGER: + { + } break; + case P_REAL: + { + } break; + case P_STRING: + { + } break; + case P_DATE: + { + } break; + case P_TRUE: + { + } break; + case P_FALSE: + { + } break; + case P_DATA: + { + } break; + } + // Add the element to the current container + if (gval) + { // There's an element to add + if (current == NULL) + { + pd->plist = gval; + return; + } + gtype = G_VALUE_TYPE(current); + if (gtype == ghb_array_get_type()) + { + ghb_array_append(current, gval); + } + else if (gtype == ghb_dict_get_type()) + { + if (pd->key == NULL) + { + g_warning("No key for dictionary item"); + ghb_value_free(gval); + } + else + { + ghb_dict_insert(current, g_strdup(pd->key), gval); + } + } + else + { + g_error("Invalid container type. This shouldn't happen"); + } + } +} + +static void +end_element( + GMarkupParseContext *ctx, + const gchar *name, + gpointer ud, + GError **error) +{ + parse_data_t *pd = (parse_data_t*)ud; + gint id; + union + { + gint id; + gpointer pid; + } start_id; + gint ii; + + // Check to see if the first element found has been closed + // If so, ignore any junk following it. + if (pd->closed_top) + return; + + for (ii = 0; ii < TAG_MAP_SZ; ii++) + { + if (strcmp(name, tag_map[ii].tag) == 0) + { + id = tag_map[ii].id; + break; + } + } + if (ii == TAG_MAP_SZ) + { + g_warning("Unrecognized start tag (%s)", name); + return; + } + start_id.pid = g_queue_pop_head(pd->tag_stack); + if (start_id.id != id) + g_warning("start tag != end tag: (%s %d) %d", name, id, id); + + GValue *gval = NULL; + GValue *current = g_queue_peek_head(pd->stack); + GType gtype = 0; + switch (id) + { + case P_PLIST: + { // Ignore + } break; + case P_KEY: + { + if (pd->key) g_free(pd->key); + pd->key = g_strdup(pd->value); + return; + } break; + case P_DICT: + { + g_queue_pop_head(pd->stack); + } break; + case P_ARRAY: + { + g_queue_pop_head(pd->stack); + } break; + case P_INTEGER: + { + gint64 val = g_strtod(pd->value, NULL); + gval = ghb_int64_value_new(val); + } break; + case P_REAL: + { + gdouble val = g_strtod(pd->value, NULL); + gval = ghb_double_value_new(val); + } break; + case P_STRING: + { + gval = ghb_string_value_new(g_strdup(pd->value)); + } break; + case P_DATE: + { + GDate date; + GTimeVal time; + g_time_val_from_iso8601(pd->value, &time); + g_date_set_time_val(&date, &time); + gval = ghb_date_value_new(&date); + } break; + case P_TRUE: + { + gval = ghb_boolean_value_new(TRUE); + } break; + case P_FALSE: + { + gval = ghb_boolean_value_new(FALSE); + } break; + case P_DATA: + { + ghb_rawdata_t *data; + data = g_malloc(sizeof(ghb_rawdata_t)); + data->data = g_base64_decode(pd->value, &(data->size)); + gval = ghb_rawdata_value_new(data); + } break; + } + if (gval) + { + // Get the top of the data structure stack and if it's an array + // or dict, add the current element + if (current == NULL) + { + pd->plist = gval; + pd->closed_top = TRUE; + return; + } + gtype = G_VALUE_TYPE(current); + if (gtype == ghb_array_get_type()) + { + ghb_array_append(current, gval); + } + else if (gtype == ghb_dict_get_type()) + { + if (pd->key == NULL) + { + g_warning("No key for dictionary item"); + ghb_value_free(gval); + } + else + { + ghb_dict_insert(current, g_strdup(pd->key), gval); + } + } + else + { + g_error("Invalid container type. This shouldn't happen"); + } + } + if (g_queue_is_empty(pd->stack)) + pd->closed_top = TRUE; +} + +static void +text_data( + GMarkupParseContext *ctx, + const gchar *text, + gsize len, + gpointer ud, + GError **error) +{ + parse_data_t *pd = (parse_data_t*)ud; + if (pd->value) g_free(pd->value); + pd->value = g_strdup(text); +} + +static void +passthrough( + GMarkupParseContext *ctx, + const gchar *text, + gsize len, + gpointer ud, + GError **error) +{ + //parse_data_t *pd = (parse_data_t*)ud; + + //g_debug("passthrough %s", text); +} + +static void +parse_error(GMarkupParseContext *ctx, GError *error, gpointer ud) +{ + g_warning("Plist parse error: %s", error->message); +} + +// This is required or the parser crashes +static void +destroy_notify(gpointer data) +{ // Do nothing + g_debug("destroy parser"); +} + +GValue* +ghb_plist_parse(const gchar *buf, gssize len) +{ + GMarkupParseContext *ctx; + GMarkupParser parser; + parse_data_t pd; + GError *err = NULL; + + pd.stack = g_queue_new(); + pd.tag_stack = g_queue_new(); + pd.key = NULL; + pd.value = NULL; + pd.plist = NULL; + pd.closed_top = FALSE; + + parser.start_element = start_element; + parser.end_element = end_element; + parser.text = text_data; + parser.passthrough = passthrough; + parser.error = parse_error; + ctx = g_markup_parse_context_new(&parser, 0, &pd, destroy_notify); + + g_markup_parse_context_parse(ctx, buf, len, &err); + g_markup_parse_context_end_parse(ctx, &err); + g_markup_parse_context_free(ctx); + g_queue_free(pd.stack); + g_queue_free(pd.tag_stack); + return pd.plist; +} + +GValue* +ghb_plist_parse_file(const gchar *filename) +{ + FILE *fd; + gchar *buffer; + size_t size; + GValue *gval; + + fd = fopen(filename, "r"); + if (fd == NULL) + return NULL; + fseek(fd, 0, SEEK_END); + size = ftell(fd); + fseek(fd, 0, SEEK_SET); + buffer = g_malloc(size); + size = fread(buffer, 1, size, fd); + buffer[size] = 0; + gval = ghb_plist_parse(buffer, (gssize)size); + g_free(buffer); + return gval; +} + +static void +indent_fprintf(FILE *file, gint indent, const gchar *fmt, ...) +{ + va_list ap; + + for (; indent; indent--) + putc('\t', file); + va_start(ap, fmt); + vfprintf(file, fmt, ap); + va_end(ap); +} + +// Used for sorting dictionaries. +static gint +key_cmp(gconstpointer a, gconstpointer b) +{ + gchar *stra = (gchar*)a; + gchar *strb = (gchar*)b; + + return strcmp(stra, strb); +} + +static void +gval_write(FILE *file, GValue *gval) +{ + static gint indent = 0; + gint ii; + GType gtype; + + if (gval == NULL) return; + gtype = G_VALUE_TYPE(gval); + if (gtype == ghb_array_get_type()) + { + GValue *val; + gint count; + + indent_fprintf(file, indent, "<array>\n"); + indent++; + count = ghb_array_len(gval); + for (ii = 0; ii < count; ii++) + { + val = ghb_array_get_nth(gval, ii); + gval_write(file, val); + } + indent--; + indent_fprintf(file, indent, "</array>\n"); + } + else if (gtype == ghb_dict_get_type()) + { + GValue *val; + GHashTable *dict = g_value_get_boxed(gval); + GList *link, *keys; + keys = g_hash_table_get_keys(dict); + // Sort the dictionary. Not really necessray, but it makes + // finding things easier + keys = g_list_sort(keys, key_cmp); + link = keys; + indent_fprintf(file, indent, "<dict>\n"); + indent++; + while (link) + { + gchar *key = (gchar*)link->data; + val = g_hash_table_lookup(dict, key); + indent_fprintf(file, indent, "<key>%s</key>\n", key); + gval_write(file, val); + link = link->next; + } + indent--; + indent_fprintf(file, indent, "</dict>\n"); + g_list_free(keys); + } + else if (gtype == G_TYPE_BOOLEAN) + { + gchar *tag; + if (g_value_get_boolean(gval)) + { + tag = "true"; + } + else + { + tag = "false"; + } + indent_fprintf(file, indent, "<%s />\n", tag); + } + else if (gtype == g_date_get_type()) + { + GDate *date; + date = g_value_get_boxed(gval); + indent_fprintf(file, indent, "<date>%d-%d-%d</date>\n", + g_date_get_year(date), + g_date_get_month(date), + g_date_get_day(date) + ); + } + else if (gtype == ghb_rawdata_get_type()) + { + ghb_rawdata_t *data; + gchar *base64; + data = g_value_get_boxed(gval); + base64 = g_base64_encode(data->data, data->size); + indent_fprintf(file, indent, "<data>\n"); + indent_fprintf(file, 0, "%s\n", base64); + indent_fprintf(file, indent, "</data>\n"); + g_free(base64); + } + else if (gtype == G_TYPE_DOUBLE) + { + gdouble val = g_value_get_double(gval); + indent_fprintf(file, indent, "<real>%.17g</real>\n", val); + } + else if (gtype == G_TYPE_INT64) + { + gint val = g_value_get_int64(gval); + indent_fprintf(file, indent, "<integer>%d</integer>\n", val); + } + else if (gtype == G_TYPE_STRING) + { + const gchar *str = g_value_get_string(gval); + gchar *esc = g_markup_escape_text(str, -1); + indent_fprintf(file, indent, "<string>%s</string>\n", esc); + g_free(esc); + } + else + { + // Try to make anything thats unrecognized into a string + const gchar *str; + GValue val = {0,}; + g_value_init(&val, G_TYPE_STRING); + if (g_value_transform(gval, &val)) + { + str = g_value_get_string(&val); + gchar *esc = g_markup_escape_text(str, -1); + indent_fprintf(file, indent, "<string>%s</string>\n", esc); + g_free(esc); + } + else + { + g_message("failed to transform"); + } + g_value_unset(&val); + } +} + +void +ghb_plist_write(FILE *file, GValue *gval) +{ + fprintf(file, "%s", preamble); + gval_write(file, gval); + fprintf(file, "%s", postfix); +} + +void +ghb_plist_write_file(const gchar *filename, GValue *gval) +{ + FILE *file; + + file = fopen(filename, "w"); + if (file == NULL) + return; + + fprintf(file, "%s", preamble); + gval_write(file, gval); + fprintf(file, "%s", postfix); +} + + +#if defined(PL_TEST) +gint +main(gint argc, gchar *argv[]) +{ + GValue *gval; + + g_type_init(); + + gval = ghb_plist_parse_file(argv[1]); + if (argc > 2) + ghb_plist_write_file(argv[2], gval); + else + ghb_plist_write(stdout, gval); + return 0; +} +#endif |