669 lines
22 KiB
C
669 lines
22 KiB
C
/*
|
|
* mooutils/moomarkup.c
|
|
*
|
|
* Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* See COPYING file that comes with this distribution.
|
|
*/
|
|
|
|
#include "mooutils/moomarkup.h"
|
|
#include <string.h>
|
|
#include <glib.h>
|
|
|
|
|
|
GType moo_markup_doc_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
if (type == 0)
|
|
type = g_boxed_type_register_static ("MooMarkupDoc",
|
|
(GBoxedCopyFunc)moo_markup_doc_ref,
|
|
(GBoxedFreeFunc)moo_markup_doc_unref);
|
|
return type;
|
|
}
|
|
|
|
|
|
typedef void (*markup_start_element_func) (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
gpointer user_data,
|
|
GError **error);
|
|
typedef void (*markup_end_element_func) (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
gpointer user_data,
|
|
GError **error);
|
|
typedef void (*markup_text_func) (GMarkupParseContext *context,
|
|
const gchar *text,
|
|
gsize text_len,
|
|
gpointer user_data,
|
|
GError **error);
|
|
typedef void (*markup_passthrough_func) (GMarkupParseContext *context,
|
|
const gchar *passthrough_text,
|
|
gsize text_len,
|
|
gpointer user_data,
|
|
GError **error);
|
|
|
|
|
|
typedef struct {
|
|
MooMarkupDoc *doc;
|
|
MooMarkupNode *current;
|
|
} ParserState;
|
|
|
|
|
|
#define ELEMENT_TEXT_SEPARATOR " "
|
|
#define BUFSIZE 1024
|
|
|
|
|
|
static MooMarkupDoc *moo_markup_doc_new_priv (const char *name);
|
|
static MooMarkupNode *moo_markup_element_new (MooMarkupDoc *doc,
|
|
MooMarkupNode *parent,
|
|
const char *name,
|
|
const char **attribute_names,
|
|
const char **attribute_values);
|
|
static MooMarkupNode *moo_markup_text_node_new (MooMarkupNodeType type,
|
|
MooMarkupDoc *doc,
|
|
MooMarkupNode *parent,
|
|
const char *text,
|
|
gsize text_len);
|
|
static void add_node (MooMarkupDoc *doc,
|
|
MooMarkupNode *parent,
|
|
MooMarkupNode *node);
|
|
|
|
|
|
static void moo_markup_doc_unref_private (MooMarkupDoc *doc);
|
|
static void moo_markup_element_free (MooMarkupElement *node);
|
|
static void moo_markup_text_node_free (MooMarkupNode *node);
|
|
static void moo_markup_node_free (MooMarkupNode *node);
|
|
|
|
static void collect_text_content (MooMarkupElement *node);
|
|
static void moo_markup_text_node_add_text (MooMarkupText *node,
|
|
const char *text,
|
|
gsize text_len);
|
|
|
|
static void moo_markup_element_print (MooMarkupElement *node,
|
|
GString *dest);
|
|
static void moo_markup_text_node_print (MooMarkupNode *node,
|
|
GString *dest);
|
|
|
|
|
|
static void start_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParserState *state,
|
|
GError **error);
|
|
static void end_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
ParserState *state,
|
|
GError **error);
|
|
static void text (GMarkupParseContext *context,
|
|
const gchar *text,
|
|
gsize text_len,
|
|
ParserState *state,
|
|
GError **error);
|
|
static void passthrough (GMarkupParseContext *context,
|
|
const gchar *passthrough_text,
|
|
gsize text_len,
|
|
ParserState *state,
|
|
GError **error);
|
|
|
|
|
|
MooMarkupDoc *moo_markup_parse_memory (const char *buffer,
|
|
int size,
|
|
GError **error)
|
|
{
|
|
GError *err = NULL;
|
|
|
|
GMarkupParser parser = {(markup_start_element_func)start_element,
|
|
(markup_end_element_func)end_element,
|
|
(markup_text_func)text,
|
|
(markup_passthrough_func)passthrough,
|
|
NULL};
|
|
|
|
MooMarkupDoc *doc;
|
|
ParserState state;
|
|
GMarkupParseContext *context;
|
|
|
|
if (size < 0) size = strlen (buffer);
|
|
|
|
doc = moo_markup_doc_new_priv (NULL);
|
|
state.doc = doc;
|
|
state.current = MOO_MARKUP_NODE (doc);
|
|
context = g_markup_parse_context_new (&parser, (GMarkupParseFlags)0, &state, NULL);
|
|
|
|
if (!g_markup_parse_context_parse (context, buffer, size, &err) ||
|
|
!g_markup_parse_context_end_parse (context, &err))
|
|
{
|
|
if (err)
|
|
{
|
|
if (error) *error = err;
|
|
else g_error_free (err);
|
|
}
|
|
g_markup_parse_context_free (context);
|
|
moo_markup_doc_unref (doc);
|
|
return NULL;
|
|
}
|
|
|
|
g_markup_parse_context_free (context);
|
|
return doc;
|
|
}
|
|
|
|
|
|
MooMarkupDoc *moo_markup_parse_file (const char *filename,
|
|
GError **error)
|
|
{
|
|
GMarkupParser parser = {(markup_start_element_func)start_element,
|
|
(markup_end_element_func)end_element,
|
|
(markup_text_func)text,
|
|
(markup_passthrough_func)passthrough,
|
|
NULL};
|
|
|
|
GError *err = NULL;
|
|
MooMarkupDoc *doc;
|
|
ParserState state;
|
|
GMarkupParseContext *context;
|
|
GIOChannel *file;
|
|
|
|
file = g_io_channel_new_file (filename, "r", &err);
|
|
if (!file)
|
|
{
|
|
if (err)
|
|
{
|
|
if (error) *error = err;
|
|
else g_error_free (err);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
doc = moo_markup_doc_new_priv (filename);
|
|
state.doc = doc;
|
|
state.current = MOO_MARKUP_NODE (doc);
|
|
context = g_markup_parse_context_new (&parser, (GMarkupParseFlags)0, &state, NULL);
|
|
|
|
while (TRUE)
|
|
{
|
|
char buf[BUFSIZE];
|
|
gsize len = 0;
|
|
GIOStatus status = g_io_channel_read_chars (file, buf, BUFSIZE, &len, &err);
|
|
if (status == G_IO_STATUS_ERROR)
|
|
{
|
|
if (err)
|
|
{
|
|
if (error) *error = err;
|
|
else g_error_free (err);
|
|
}
|
|
g_markup_parse_context_free (context);
|
|
moo_markup_doc_unref (doc);
|
|
return NULL;
|
|
}
|
|
|
|
if (len > 0)
|
|
{
|
|
if (!g_markup_parse_context_parse (context, buf, len, &err))
|
|
{
|
|
if (err)
|
|
{
|
|
if (error) *error = err;
|
|
else g_error_free (err);
|
|
}
|
|
g_markup_parse_context_free (context);
|
|
moo_markup_doc_unref (doc);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (status == G_IO_STATUS_EOF)
|
|
{
|
|
if (!g_markup_parse_context_end_parse (context, &err))
|
|
{
|
|
if (err)
|
|
{
|
|
if (error) *error = err;
|
|
else g_error_free (err);
|
|
}
|
|
g_markup_parse_context_free (context);
|
|
moo_markup_doc_unref (doc);
|
|
return NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_markup_parse_context_free (context);
|
|
return doc;
|
|
}
|
|
|
|
|
|
static void start_element (G_GNUC_UNUSED GMarkupParseContext *ctx,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
ParserState *state,
|
|
G_GNUC_UNUSED GError **error)
|
|
{
|
|
MooMarkupNode *elm = moo_markup_element_new (state->doc,
|
|
state->current,
|
|
element_name,
|
|
attribute_names,
|
|
attribute_values);
|
|
state->current = elm;
|
|
}
|
|
|
|
|
|
static void end_element (G_GNUC_UNUSED GMarkupParseContext *ctx,
|
|
G_GNUC_UNUSED const gchar *elm,
|
|
ParserState *state,
|
|
G_GNUC_UNUSED GError **error)
|
|
{
|
|
g_assert (state->current->type == MOO_MARKUP_ELEMENT_NODE);
|
|
collect_text_content (MOO_MARKUP_ELEMENT (state->current));
|
|
g_assert (state->current->parent != NULL);
|
|
state->current = state->current->parent;
|
|
}
|
|
|
|
|
|
static void text (G_GNUC_UNUSED GMarkupParseContext *ctx,
|
|
const gchar *text,
|
|
gsize text_len,
|
|
ParserState *state,
|
|
G_GNUC_UNUSED GError **error)
|
|
{
|
|
if (MOO_MARKUP_IS_TEXT (state->current->last))
|
|
moo_markup_text_node_add_text (MOO_MARKUP_TEXT (state->current->last),
|
|
text, text_len);
|
|
else
|
|
moo_markup_text_node_new (MOO_MARKUP_TEXT_NODE,
|
|
state->doc, state->current,
|
|
text, text_len);
|
|
}
|
|
|
|
|
|
static void passthrough (G_GNUC_UNUSED GMarkupParseContext *ctx,
|
|
const gchar *passthrough_text,
|
|
gsize text_len,
|
|
ParserState *state,
|
|
G_GNUC_UNUSED GError **error)
|
|
{
|
|
if (MOO_MARKUP_IS_COMMENT (state->current->last))
|
|
moo_markup_text_node_add_text (MOO_MARKUP_COMMENT (state->current->last),
|
|
passthrough_text, text_len);
|
|
else
|
|
moo_markup_text_node_new (MOO_MARKUP_COMMENT_NODE,
|
|
state->doc, state->current,
|
|
passthrough_text, text_len);
|
|
}
|
|
|
|
|
|
static MooMarkupDoc *moo_markup_doc_new_priv (const char *name)
|
|
{
|
|
MooMarkupDoc *doc = g_new0 (MooMarkupDoc, 1);
|
|
|
|
doc->type = MOO_MARKUP_DOC_NODE;
|
|
doc->name = g_strdup (name);
|
|
doc->doc = doc;
|
|
doc->ref_count = 1;
|
|
|
|
return doc;
|
|
}
|
|
|
|
|
|
static MooMarkupNode *moo_markup_element_new (MooMarkupDoc *doc,
|
|
MooMarkupNode *parent,
|
|
const char *name,
|
|
const char **attribute_names,
|
|
const char **attribute_values)
|
|
{
|
|
MooMarkupElement *elm = g_new0 (MooMarkupElement, 1);
|
|
add_node (doc, parent, MOO_MARKUP_NODE (elm));
|
|
|
|
elm->type = MOO_MARKUP_ELEMENT_NODE;
|
|
elm->name = g_strdup (name);
|
|
|
|
elm->attr_names = g_strdupv ((char**)attribute_names);
|
|
elm->attr_vals = g_strdupv ((char**)attribute_values);
|
|
for (elm->n_attrs = 0; attribute_names[elm->n_attrs]; ++elm->n_attrs) ;
|
|
|
|
return MOO_MARKUP_NODE (elm);
|
|
}
|
|
|
|
|
|
static MooMarkupNode *moo_markup_text_node_new(MooMarkupNodeType type,
|
|
MooMarkupDoc *doc,
|
|
MooMarkupNode *parent,
|
|
const char *text,
|
|
gsize text_len)
|
|
{
|
|
MooMarkupText *node;
|
|
|
|
g_assert (type == MOO_MARKUP_TEXT_NODE || type == MOO_MARKUP_COMMENT_NODE);
|
|
node = g_new0 (MooMarkupText, 1);
|
|
add_node (doc, parent, MOO_MARKUP_NODE (node));
|
|
|
|
node->type = type;
|
|
if (type == MOO_MARKUP_TEXT_NODE)
|
|
node->name = g_strdup ("TEXT");
|
|
else
|
|
node->name = g_strdup ("COMMENT");
|
|
|
|
node->text = g_strndup (text, text_len);
|
|
node->size = text_len;
|
|
|
|
return MOO_MARKUP_NODE (node);
|
|
}
|
|
|
|
|
|
static void add_node (MooMarkupDoc *doc,
|
|
MooMarkupNode *parent,
|
|
MooMarkupNode *node)
|
|
{
|
|
node->doc = doc;
|
|
node->parent = parent;
|
|
if (parent->last)
|
|
{
|
|
parent->last->next = node;
|
|
node->prev = parent->last;
|
|
parent->last = node;
|
|
}
|
|
else
|
|
{
|
|
parent->children = node;
|
|
parent->last = node;
|
|
}
|
|
}
|
|
|
|
|
|
static void moo_markup_text_node_add_text (MooMarkupText *node,
|
|
const char *text,
|
|
gsize text_len)
|
|
{
|
|
char *tmp = (char*)g_memdup (node->text, sizeof(char) * (node->size + text_len));
|
|
g_memmove (tmp + node->size, text, sizeof(char) * text_len);
|
|
tmp[node->size + text_len] = 0;
|
|
g_free (node->text);
|
|
node->text = tmp;
|
|
}
|
|
|
|
|
|
static void collect_text_content (MooMarkupElement *node)
|
|
{
|
|
MooMarkupNode *child;
|
|
char *text = NULL;
|
|
for (child = node->children; child != NULL; child = child->next)
|
|
{
|
|
if (MOO_MARKUP_IS_TEXT (child))
|
|
{
|
|
if (text)
|
|
text = g_strconcat (text, ELEMENT_TEXT_SEPARATOR,
|
|
MOO_MARKUP_TEXT(child)->text, NULL);
|
|
else
|
|
text = g_strndup (MOO_MARKUP_TEXT(child)->text,
|
|
MOO_MARKUP_TEXT(child)->size);
|
|
}
|
|
}
|
|
node->content = text;
|
|
}
|
|
|
|
|
|
static void moo_markup_node_free (MooMarkupNode *node)
|
|
{
|
|
MooMarkupNode *child;
|
|
|
|
g_return_if_fail (node != NULL);
|
|
|
|
g_free (node->name);
|
|
|
|
for (child = node->children; child != NULL; child = child->next)
|
|
moo_markup_node_free (child);
|
|
|
|
switch (node->type)
|
|
{
|
|
case MOO_MARKUP_DOC_NODE:
|
|
moo_markup_doc_unref_private (MOO_MARKUP_DOC (node));
|
|
break;
|
|
case MOO_MARKUP_ELEMENT_NODE:
|
|
moo_markup_element_free (MOO_MARKUP_ELEMENT (node));
|
|
break;
|
|
case MOO_MARKUP_TEXT_NODE:
|
|
case MOO_MARKUP_COMMENT_NODE:
|
|
moo_markup_text_node_free (node);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
|
|
static void moo_markup_element_free (MooMarkupElement *node)
|
|
{
|
|
g_free (node->content);
|
|
g_strfreev (node->attr_names);
|
|
g_strfreev (node->attr_vals);
|
|
}
|
|
|
|
|
|
static void moo_markup_text_node_free (MooMarkupNode *node)
|
|
{
|
|
g_assert (node->type == MOO_MARKUP_TEXT_NODE || node->type == MOO_MARKUP_COMMENT_NODE);
|
|
g_free (((MooMarkupText*)node)->text);
|
|
}
|
|
|
|
|
|
void moo_markup_doc_unref (MooMarkupDoc *doc)
|
|
{
|
|
g_return_if_fail (MOO_MARKUP_IS_DOC (doc));
|
|
if (--(doc->ref_count)) return;
|
|
moo_markup_node_free (MOO_MARKUP_NODE (doc));
|
|
}
|
|
|
|
|
|
MooMarkupDoc *moo_markup_doc_ref (MooMarkupDoc *doc)
|
|
{
|
|
g_return_val_if_fail (MOO_MARKUP_IS_DOC (doc), NULL);
|
|
++(doc->ref_count);
|
|
return doc;
|
|
}
|
|
|
|
|
|
static void moo_markup_doc_unref_private (G_GNUC_UNUSED MooMarkupDoc *doc)
|
|
{
|
|
}
|
|
|
|
|
|
char *moo_markup_doc_get_string (MooMarkupDoc *doc)
|
|
{
|
|
MooMarkupNode *child;
|
|
GString *str = g_string_new ("");
|
|
for (child = doc->children; child != NULL; child = child->next)
|
|
switch (child->type)
|
|
{
|
|
case MOO_MARKUP_ELEMENT_NODE:
|
|
moo_markup_element_print (MOO_MARKUP_ELEMENT (child), str);
|
|
break;
|
|
case MOO_MARKUP_TEXT_NODE:
|
|
case MOO_MARKUP_COMMENT_NODE:
|
|
moo_markup_text_node_print (child, str);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
|
|
static void moo_markup_element_print (MooMarkupElement *node,
|
|
GString *str)
|
|
{
|
|
guint i;
|
|
|
|
g_string_append_printf (str, "<%s", node->name);
|
|
for (i = 0; i < node->n_attrs; ++i)
|
|
g_string_append_printf (str, " %s=\"%s\"",
|
|
node->attr_names[i],
|
|
node->attr_vals[i]);
|
|
|
|
if (node->children)
|
|
{
|
|
MooMarkupNode *child;
|
|
|
|
g_string_append (str, ">");
|
|
for (child = node->children; child != NULL; child = child->next)
|
|
switch (child->type)
|
|
{
|
|
case MOO_MARKUP_ELEMENT_NODE:
|
|
moo_markup_element_print (MOO_MARKUP_ELEMENT (child), str);
|
|
break;
|
|
case MOO_MARKUP_TEXT_NODE:
|
|
case MOO_MARKUP_COMMENT_NODE:
|
|
moo_markup_text_node_print (child, str);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
g_string_append_printf (str, "</%s>", node->name);
|
|
}
|
|
else
|
|
g_string_append (str, "/>");
|
|
}
|
|
|
|
|
|
static void moo_markup_text_node_print (MooMarkupNode *node,
|
|
GString *str)
|
|
{
|
|
MooMarkupText *text = (MooMarkupText*) node;
|
|
g_string_append (str, text->text);
|
|
}
|
|
|
|
|
|
static MooMarkupElement *get_element (MooMarkupNode *node,
|
|
char **path)
|
|
{
|
|
MooMarkupNode *child;
|
|
|
|
if (!path || !path[0] || !path[0][0]) return MOO_MARKUP_ELEMENT (node);
|
|
|
|
/* TODO: fix this */
|
|
for (child = node->children; child; child = child->next)
|
|
{
|
|
if (MOO_MARKUP_IS_ELEMENT (child)) {
|
|
const char *name = moo_markup_get_prop (MOO_MARKUP_ELEMENT (child),
|
|
"name");
|
|
if (name && !strcmp (path[0], name))
|
|
return get_element (child, ++path);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
MooMarkupElement *moo_markup_get_element (MooMarkupNode *node,
|
|
const char *path)
|
|
{
|
|
char **p;
|
|
MooMarkupElement *elm;
|
|
|
|
g_return_val_if_fail (path != NULL, NULL);
|
|
|
|
p = g_strsplit (path, "/", 0);
|
|
elm = get_element (node, p);
|
|
g_strfreev (p);
|
|
return elm;
|
|
}
|
|
|
|
|
|
const char *moo_markup_get_prop (MooMarkupElement *node,
|
|
const char *prop_name)
|
|
{
|
|
guint i;
|
|
g_return_val_if_fail (node != NULL && prop_name != NULL, NULL);
|
|
for (i = 0 ; i < node->n_attrs; ++i)
|
|
if (!strcmp (prop_name, node->attr_names[i]))
|
|
return node->attr_vals[i];
|
|
return NULL;
|
|
}
|
|
|
|
|
|
MooMarkupElement*moo_markup_get_root_element(MooMarkupDoc *doc,
|
|
const char *name)
|
|
{
|
|
MooMarkupNode *child;
|
|
for (child = doc->children; child; child = child->next)
|
|
{
|
|
MooMarkupElement *elm;
|
|
if (!MOO_MARKUP_IS_ELEMENT (child)) continue;
|
|
elm = MOO_MARKUP_ELEMENT (child);
|
|
if (!name || !strcmp (elm->name, name))
|
|
return elm;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
#ifdef MOO_MARKUP_NODE
|
|
#undef MOO_MARKUP_NODE
|
|
#endif
|
|
#ifdef MOO_MARKUP_DOC
|
|
#undef MOO_MARKUP_DOC
|
|
#endif
|
|
#ifdef MOO_MARKUP_ELEMENT
|
|
#undef MOO_MARKUP_ELEMENT
|
|
#endif
|
|
#ifdef MOO_MARKUP_TEXT
|
|
#undef MOO_MARKUP_TEXT
|
|
#endif
|
|
#ifdef MOO_MARKUP_COMMENT
|
|
#undef MOO_MARKUP_COMMENT
|
|
#endif
|
|
|
|
|
|
MooMarkupNode *MOO_MARKUP_NODE (gpointer n)
|
|
{
|
|
MooMarkupNode *node = (MooMarkupNode*) n;
|
|
g_return_val_if_fail (n != NULL, NULL);
|
|
g_return_val_if_fail (node->type == MOO_MARKUP_DOC_NODE ||
|
|
node->type == MOO_MARKUP_ELEMENT_NODE ||
|
|
node->type == MOO_MARKUP_TEXT_NODE ||
|
|
node->type == MOO_MARKUP_COMMENT_NODE,
|
|
NULL);
|
|
return node;
|
|
}
|
|
|
|
MooMarkupDoc *MOO_MARKUP_DOC (gpointer n)
|
|
{
|
|
MooMarkupNode *node = (MooMarkupNode*) n;
|
|
g_return_val_if_fail (n != NULL, NULL);
|
|
g_return_val_if_fail (node->type == MOO_MARKUP_DOC_NODE, NULL);
|
|
return (MooMarkupDoc*) n;
|
|
}
|
|
|
|
MooMarkupElement *MOO_MARKUP_ELEMENT (gpointer n)
|
|
{
|
|
MooMarkupNode *node = (MooMarkupNode*) n;
|
|
g_return_val_if_fail (n != NULL, NULL);
|
|
g_return_val_if_fail (node->type == MOO_MARKUP_ELEMENT_NODE, NULL);
|
|
return (MooMarkupElement*) n;
|
|
}
|
|
|
|
MooMarkupText *MOO_MARKUP_TEXT (gpointer n)
|
|
{
|
|
MooMarkupNode *node = (MooMarkupNode*) n;
|
|
g_return_val_if_fail (n != NULL, NULL);
|
|
g_return_val_if_fail (node->type == MOO_MARKUP_TEXT_NODE, NULL);
|
|
return (MooMarkupText*) n;
|
|
}
|
|
|
|
MooMarkupComment *MOO_MARKUP_COMMENT (gpointer n)
|
|
{
|
|
MooMarkupNode *node = (MooMarkupNode*) n;
|
|
g_return_val_if_fail (n != NULL, NULL);
|
|
g_return_val_if_fail (node->type == MOO_MARKUP_COMMENT_NODE, NULL);
|
|
return (MooMarkupComment*) n;
|
|
}
|