#include #include #include #include #include #include #include #include "plist.h" #include "values.h" #define BUF_SZ (128*1024) static gchar *preamble = "\n" "\n" "\n"; static gchar *postfix = "\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(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); if (pd.key) g_free(pd.key); if (pd.value) g_free(pd.value); g_queue_free(pd.stack); g_queue_free(pd.tag_stack); return pd.plist; } GValue* ghb_plist_parse_file(const gchar *filename) { gchar *buffer; size_t size; GValue *gval; FILE *fd; fd = g_fopen(filename, "r"); if (fd == NULL) { g_warning("Plist parse: failed to open %s", filename); return NULL; } fseek(fd, 0, SEEK_END); size = ftell(fd); fseek(fd, 0, SEEK_SET); buffer = g_malloc(size+1); size = fread(buffer, 1, size, fd); buffer[size] = 0; gval = ghb_plist_parse(buffer, (gssize)size); g_free(buffer); fclose(fd); 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, "\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, "\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, "\n"); indent++; while (link) { gchar *key = (gchar*)link->data; val = g_hash_table_lookup(dict, key); indent_fprintf(file, indent, "%s\n", key); gval_write(file, val); link = link->next; } indent--; indent_fprintf(file, indent, "\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, "%d-%d-%d\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, "\n"); indent_fprintf(file, 0, "%s\n", base64); indent_fprintf(file, indent, "\n"); g_free(base64); } else if (gtype == G_TYPE_DOUBLE) { gdouble val = g_value_get_double(gval); indent_fprintf(file, indent, "%.17g\n", val); } else if (gtype == G_TYPE_INT64) { gint val = g_value_get_int64(gval); indent_fprintf(file, indent, "%d\n", val); } else if (gtype == G_TYPE_INT) { gint val = g_value_get_int(gval); indent_fprintf(file, indent, "%d\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, "%s\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, "%s\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(); file = g_fopen(argv[1], "r"); gval = ghb_plist_parse_file(file); if (argc > 2) ghb_plist_write_file(argv[2], gval); else ghb_plist_write(stdout, gval); if (file) fclose (file); return 0; } #endif