/*=======================================*\ | ____ ____ | | / \ /\ | | | \____ / \ |____ | | \ /____\ | | | \____/prite / \nimation |____ditor | | | | Copyleft Vasily_Makarov 2011 | | | \*=======================================*/ #include "xml.h" gchar** xml_attr_new(const gchar *name, const gchar *value) { gchar **attr = g_new0(gchar*, 2); attr[0] = g_strdup(name); attr[1] = g_strdup(value); return attr; } gchar* xml_node_get_attr_value(const XMLNode *node, const gchar *attr_name) { gchar **attr = node->attributes; int i; for (i = 0; i < g_strv_length(attr); i += 2) if (g_str_equal(attr[i], attr_name)) return attr[i + 1]; return NULL; } gint xml_node_compare_with_name_func(gconstpointer a, gconstpointer b) { return g_strcmp0((gchar *)b, ((XMLNode *)a)->name); } gint xml_node_compare_with_action_node_by_imageset_name_func(gconstpointer a, gconstpointer b) { return g_strcmp0("action", ((XMLNode *)a)->name) || g_strcmp0((gchar *)b, xml_node_get_attr_value((XMLNode *)a, "imageset")); } gint xml_node_compare_with_attr_func(const XMLNode *node, const gchar **attr) { return g_strcmp0(attr[1], xml_node_get_attr_value(node, attr[0])); } typedef enum { STATE_START, STATE_AFTER_OPEN_ANGLE, STATE_AFTER_CLOSE_ANGLE, STATE_AFTER_ELISION_SLASH, /* the slash that obviates need for end element */ STATE_INSIDE_OPEN_TAG_NAME, STATE_INSIDE_ATTRIBUTE_NAME, STATE_AFTER_ATTRIBUTE_NAME, STATE_BETWEEN_ATTRIBUTES, STATE_AFTER_ATTRIBUTE_EQUALS_SIGN, STATE_INSIDE_ATTRIBUTE_VALUE_SQ, STATE_INSIDE_ATTRIBUTE_VALUE_DQ, STATE_INSIDE_TEXT, STATE_AFTER_CLOSE_TAG_SLASH, STATE_INSIDE_CLOSE_TAG_NAME, STATE_AFTER_CLOSE_TAG_NAME, STATE_INSIDE_PASSTHROUGH, STATE_ERROR } GMarkupParseState; typedef struct { const GMarkupParser *parser; GMarkupParseFlags flags; gint line_number; gint char_number; GMarkupParseState state; gpointer user_data; GDestroyNotify dnotify; /* A piece of character data or an element that * hasn't "ended" yet so we haven't yet called * the callback for it. */ GString *partial_chunk; GSList *spare_chunks; GSList *tag_stack; GSList *tag_stack_gstr; GSList *spare_list_nodes; GString **attr_names; GString **attr_values; gint cur_attr; gint alloc_attrs; const gchar *current_text; gssize current_text_len; const gchar *current_text_end; /* used to save the start of the last interesting thingy */ const gchar *start; const gchar *iter; guint document_empty : 1; guint parsing : 1; guint awaiting_pop : 1; gint balance; /* subparser support */ GSList *subparser_stack; /* (GMarkupRecursionTracker *) */ const char *subparser_element; gpointer held_user_data; } _GMarkupParseContext; static GMarkupParser parser; void xml_free (XMLNode *node) { g_free (node->name); g_free (node->text); g_strfreev (node->attributes); g_list_foreach (node->sub_nodes, (GFunc) xml_free, NULL); g_list_free (node->sub_nodes); g_slice_free (XMLNode, node); } static void _start_root_element_cb ( GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { XMLNode **node = (XMLNode **) user_data; g_assert (node != NULL); XMLNode *p = g_slice_new0 (XMLNode); p->name = g_strdup (element_name); GArray *attributes = g_array_new (TRUE, TRUE, sizeof (gchar *)); while (*attribute_names != NULL && *attribute_values != NULL) { gchar *p; p = g_strdup (*attribute_names++); g_array_append_val (attributes, p); p = g_strdup (*attribute_values++); g_array_append_val (attributes, p); } p->attributes = (gchar **) g_array_free (attributes, FALSE); g_markup_parse_context_push (context, &parser, p); *node = p; } static void _start_element_cb ( GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { XMLNode *node = (XMLNode *) user_data; if (node->text) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, " "); return; } XMLNode *p = g_slice_new0 (XMLNode); node->sub_nodes = g_list_append (node->sub_nodes, p); g_markup_parse_context_push (context, &parser, p); p->name = g_strdup (element_name); GArray *attributes = g_array_new (TRUE, TRUE, sizeof (gchar *)); while (*attribute_names != NULL && *attribute_values != NULL) { gchar *p; p = g_strdup (*attribute_names++); g_array_append_val (attributes, p); p = g_strdup (*attribute_values++); g_array_append_val (attributes, p); } p->attributes = (gchar **)g_array_free (attributes, FALSE); p->line_number = ((_GMarkupParseContext*)context)->line_number - (((_GMarkupParseContext*)context)->char_number <= 1); } static void _end_element_cb ( GMarkupParseContext *context, const gchar *element_name, gpointer user_data, GError **error) { XMLNode *p = (XMLNode *) g_markup_parse_context_pop (context); if (p->text && p->sub_nodes) { g_warning ("Error"); } if (p->text == NULL && p->sub_nodes == NULL) { p->text = g_strdup (""); } } static gboolean _is_space ( const gchar *text, gsize text_len) { gsize i = 0; for (i = 0; text[i] != '\0' && i < text_len; i++) { switch (text[i]) { case '\t': case ' ': case '\n': case '\r': continue; default: return FALSE; } } return TRUE; } static void _text_cb ( GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, GError **error) { XMLNode *p = (XMLNode *)user_data; if (_is_space (text, text_len)) { return; } if (p->sub_nodes || p->text) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, " "); return; } p->text = g_strndup (text, text_len); } static GMarkupParser parser = { _start_element_cb, _end_element_cb, _text_cb, 0, 0, }; XMLNode *xml_parse_file (const gchar *filename) { gboolean retval = FALSE; GError *error = NULL; FILE *pf = fopen (filename, "r"); if (pf == NULL) { return NULL; } GMarkupParseContext *context; XMLNode *node; const static GMarkupParser root_parser = { _start_root_element_cb, _end_element_cb, _text_cb, 0, 0, }; do { context = g_markup_parse_context_new (&root_parser, 0, &node, 0); while (!feof (pf)) { gchar buf[1024]; gssize len = 0; len = fread (buf, 1, sizeof (buf), pf); retval = g_markup_parse_context_parse (context, buf, len, &error); if (!retval) break; } fclose (pf); if (!retval) break; retval = g_markup_parse_context_end_parse (context, &error); if (!retval) break; g_markup_parse_context_free (context); return node; } while (0); g_warning ("Parse %s failed: %s", filename, error->message); g_error_free (error); g_markup_parse_context_free (context); return NULL; } XMLNode *xml_parse_buffer (const gchar *buffer, GError **error) { gboolean retval; GMarkupParseContext *context; XMLNode *node; const static GMarkupParser root_parser = { _start_root_element_cb, _end_element_cb, _text_cb, 0, 0, }; context = g_markup_parse_context_new (&root_parser, 0, &node, 0); do { retval = g_markup_parse_context_parse (context, buffer, strlen (buffer), error); if (!retval) break; retval = g_markup_parse_context_end_parse (context, error); if (!retval) break; g_markup_parse_context_free (context); return node; } while (0); //g_warning ("Parse buffer failed: %s", (*error)->message); g_markup_parse_context_free (context); return NULL; } static void output_indent (int level, GString *output) { gint i; for (i = 0; i < level; i++) { g_string_append (output, " "); } } static void xml_output_indent (const XMLNode *node, int level, GString *output) { gchar **attrs; output_indent (level, output); g_string_append_printf (output, "<%s", node->name); attrs = node->attributes; while (attrs != NULL && *attrs != NULL) { g_string_append_printf (output, " %s", *(attrs++)); g_string_append_printf (output, "=\"%s\"", *(attrs++)); } if (node->sub_nodes != NULL) { g_string_append (output, ">\n"); GList *sub_node; for (sub_node = node->sub_nodes; sub_node != NULL; sub_node = sub_node->next) { xml_output_indent (sub_node->data, level + 1, output); } output_indent (level, output); g_string_append_printf (output, "\n",node->name); } else if (node->text != NULL) { gchar *text = g_markup_escape_text (node->text, -1); g_string_append_printf (output, ">%s\n", text, node->name); g_free (text); } else { g_string_append (output, "/>\n"); } } void xml_output (const XMLNode *node, GString *output) { xml_output_indent (node, 0, output); }