/* -*- Mode: C; c-basic-offset: 4 -*-
 * libglade - a library for building interfaces from XML files at runtime
 * Copyright (C) 1998-2002  James Henstridge <james@daa.com.au>
 *
 * glade-parser.c: functions for parsing glade-2.0 files
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <libxml/parser.h>
#include <time.h>

#include "glade-parser.h"
#include "glade.h"

#define GLADE_NOTE(a,b)

typedef enum {
    PARSER_START,
    PARSER_GLADE_INTERFACE,
    PARSER_REQUIRES,
    PARSER_WIDGET,
    PARSER_WIDGET_PROPERTY,
    PARSER_WIDGET_ATK,
    PARSER_WIDGET_ATK_PROPERTY,
    PARSER_WIDGET_ATK_ACTION,
    PARSER_WIDGET_ATK_RELATION,
    PARSER_WIDGET_AFTER_ATK,
    PARSER_WIDGET_SIGNAL,
    PARSER_WIDGET_AFTER_SIGNAL,
    PARSER_WIDGET_ACCEL,
    PARSER_WIDGET_AFTER_ACCEL,
    PARSER_WIDGET_CHILD,
    PARSER_WIDGET_CHILD_AFTER_WIDGET,
    PARSER_WIDGET_CHILD_PACKING,
    PARSER_WIDGET_CHILD_PACKING_PROPERTY,
    PARSER_WIDGET_CHILD_AFTER_PACKING,
    PARSER_WIDGET_CHILD_PLACEHOLDER,
    PARSER_WIDGET_CHILD_AFTER_PLACEHOLDER,
    PARSER_FINISH,
    PARSER_UNKNOWN
} ParserState;

#ifdef DEBUG
static const gchar *state_names[] = {
    "START",
    "GLADE_INTERFACE",
    "REQUIRES",
    "WIDGET",
    "WIDGET_PROPERTY",
    "WIDGET_ATK",
    "WIDGET_ATK_PROPERTY",
    "WIDGET_ATK_ACTION",
    "WIDGET_ATK_RELATION",
    "WIDGET_AFTER_ATK",
    "WIDGET_SIGNAL",
    "WIDGET_AFTER_SIGNAL",
    "WIDGET_ACCEL",
    "WIDGET_AFTER_ACCEL",
    "WIDGET_CHILD",
    "WIDGET_CHILD_AFTER_WIDGET",
    "WIDGET_CHILD_PACKING",
    "WIDGET_CHILD_PACKING_PROPERTY",
    "WIDGET_CHILD_AFTER_PACKING",
    "WIDGET_CHILD_PLACEHOLDER",
    "WIDGET_CHILD_AFTER_PLACEHOLDER",
    "FINISH",
    "UNKNOWN",
};
#endif

typedef struct _GladeParseState GladeParseState;
struct _GladeParseState {
    ParserState state;

    const gchar *domain;

    guint unknown_depth;    /* handle recursive unrecognised tags */
    ParserState prev_state; /* the last `known' state we were in */

    guint widget_depth;
    GString *content;

    GladeInterface *interface;
    GladeWidgetInfo *widget;

    enum {PROP_NONE, PROP_WIDGET, PROP_ATK, PROP_CHILD } prop_type;
    gchar *prop_name;
    gchar *comment;
    gboolean translate_prop;
    gboolean context_prop;
    GArray *props;

    GArray *signals;
    GArray *atk_actions;
    GArray *relations;
    GArray *accels;
};

static GladeWidgetInfo *
create_widget_info(GladeInterface *interface, const xmlChar **attrs)
{
    GladeWidgetInfo *info = g_new0(GladeWidgetInfo, 1);
    gint i;

    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
        if (!xmlStrcmp(attrs[i], BAD_CAST("class")))
            info->classname = glade_xml_alloc_string (interface, CAST_BAD(attrs[i+1]));
            else if (!xmlStrcmp(attrs[i], BAD_CAST("id")))
                info->name = glade_xml_alloc_string (interface, CAST_BAD(attrs[i+1]));
            else
                g_warning("unknown attribute `%s' for <widget>.", attrs[i]);
    }
    if (info->classname == NULL || info->name == NULL)
        g_warning("<widget> element missing required attributes!");
    g_hash_table_insert(interface->names, info->name, info);
    return info;
}

static inline void
flush_properties(GladeParseState *state)
{
    if (state->props == NULL)
	return;
    switch (state->prop_type) {
    case PROP_NONE:
	break;
    case PROP_WIDGET:
	if (state->widget->properties)
	    g_warning("we already read all the props for this key.  Leaking");
	state->widget->properties = (GladePropInfo *)state->props->data;
	state->widget->n_properties = state->props->len;
	g_array_free(state->props, FALSE);
	break;
    case PROP_ATK:
	if (state->widget->atk_props)
	    g_warning("we already read all the ATK props for this key.  Leaking");
	state->widget->atk_props = (GladePropInfo *)state->props->data;
	state->widget->n_atk_props = state->props->len;
	g_array_free(state->props, FALSE);
	break;
    case PROP_CHILD:
	if (state->widget->n_children == 0) {
	    g_warning("no children, but have child properties!");
	    g_array_free(state->props, TRUE);
	} else {
	    GladeChildInfo *info = &state->widget->children[
						state->widget->n_children-1];
	    if (info->properties)
		g_warning("we already read all the child props for this key.  Leaking");
	    info->properties = (GladePropInfo *)state->props->data;
	    info->n_properties = state->props->len;
	    g_array_free(state->props, FALSE);
	}
	break;
    }
    state->prop_type = PROP_NONE;
    state->prop_name = NULL;
    state->props = NULL;
}

static inline void
flush_signals(GladeParseState *state)
{
    if (state->signals) {
	state->widget->signals = (GladeSignalInfo *)state->signals->data;
	state->widget->n_signals = state->signals->len;
	g_array_free(state->signals, FALSE);
    }
    state->signals = NULL;
}

static inline void
flush_actions(GladeParseState *state)
{
    if (state->atk_actions) {
	state->widget->atk_actions = (GladeAtkActionInfo *)state->atk_actions->data;
	state->widget->n_atk_actions = state->atk_actions->len;
	g_array_free(state->atk_actions, FALSE);
    }
    state->atk_actions = NULL;
}

static inline void
flush_relations(GladeParseState *state)
{
    if (state->relations) {
	state->widget->relations = (GladeAtkRelationInfo *)state->relations->data;
	state->widget->n_relations = state->relations->len;
	g_array_free(state->relations, FALSE);
    }
    state->relations = NULL;
}

static inline void
flush_accels(GladeParseState *state)
{
    if (state->accels) {
	state->widget->accels = (GladeAccelInfo *)state->accels->data;
	state->widget->n_accels = state->accels->len;
	g_array_free(state->accels, FALSE);
    }
    state->accels = NULL;
}

static inline void
handle_atk_action(GladeParseState *state, const xmlChar **attrs)
{
    gint i;
    GladeAtkActionInfo info = { 0 };

    flush_properties(state);

    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
	if (!xmlStrcmp(attrs[i], BAD_CAST("action_name")))
	    info.action_name = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else if (!xmlStrcmp(attrs[i], BAD_CAST("description")))
	    info.description = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else
	    g_warning("unknown attribute `%s' for <action>.", attrs[i]);
    }
    if (info.action_name == NULL) {
	g_warning("required <atkaction> attribute 'action_name' missing!!!");
	return;
    }
    if (!state->atk_actions)
	state->atk_actions = g_array_new(FALSE, FALSE,
				     sizeof(GladeAtkActionInfo));
    g_array_append_val(state->atk_actions, info);
}

static inline void
handle_atk_relation(GladeParseState *state, const xmlChar **attrs)
{
    gint i;
    GladeAtkRelationInfo info = { 0 };

    flush_properties(state);

    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
	if (!xmlStrcmp(attrs[i], BAD_CAST("target")))
	    info.target = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else if (!xmlStrcmp(attrs[i], BAD_CAST("type")))
	    info.type = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else
	    g_warning("unknown attribute `%s' for <signal>.", attrs[i]);
    }
    if (info.target == NULL || info.type == NULL) {
	g_warning("required <atkrelation> attributes ('target' and/or 'type') missing!!!");
	return;
    }
    if (!state->relations)
	state->relations = g_array_new(FALSE, FALSE,
				     sizeof(GladeAtkRelationInfo));
    g_array_append_val(state->relations, info);
}

static inline void
handle_signal(GladeParseState *state, const xmlChar **attrs)
{
    GladeSignalInfo info = { 0 };
    gint i;

    flush_properties(state);

    info.after = FALSE;
    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
	if (!xmlStrcmp(attrs[i], BAD_CAST("name")))
	    info.name = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else if (!xmlStrcmp(attrs[i], BAD_CAST("handler")))
	    info.handler = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else if (!xmlStrcmp(attrs[i], BAD_CAST("after")))
	    info.after = attrs[i+1][0] == 'y';
	else if (!xmlStrcmp(attrs[i], BAD_CAST("lookup")))
	    info.lookup = attrs[i+1][0] == 'y';
	else if (!xmlStrcmp(attrs[i], BAD_CAST("object")))
	    info.object = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else if (!xmlStrcmp(attrs[i], BAD_CAST("last_modification_time")))
	    /* Do nothing. */;
	else
	    g_warning("unknown attribute `%s' for <signal>.", attrs[i]);
    }
    if (info.name == NULL || info.handler == NULL) {
	g_warning("required <signal> attributes missing!!!");
	return;
    }
    if (!state->signals)
	state->signals = g_array_new(FALSE, FALSE,
				     sizeof(GladeSignalInfo));
    g_array_append_val(state->signals, info);
}

static inline void
handle_accel(GladeParseState *state, const xmlChar **attrs)
{
    GladeAccelInfo info = { 0 };
    gint i;

    flush_properties(state);
    flush_signals(state);
    flush_actions(state);
    flush_relations(state);

    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
	if (!xmlStrcmp(attrs[i], BAD_CAST("key")))
	    info.key = gdk_keyval_from_name(CAST_BAD(attrs[i+1]));
	else if (!xmlStrcmp(attrs[i], BAD_CAST("modifiers"))) {
	    const xmlChar *pos = attrs[i+1];

	    info.modifiers = 0;
	    while (pos[0])
		if (!xmlStrncmp(pos, BAD_CAST("GDK_"), 4)) {
		    pos += 4;
		    if (!xmlStrncmp(pos, BAD_CAST("SHIFT_MASK"), 10)) {
			info.modifiers |= GDK_SHIFT_MASK;
			pos += 10;
		    } else if (!xmlStrncmp(pos, BAD_CAST("LOCK_MASK"), 9)) {
			info.modifiers |= GDK_LOCK_MASK;
			pos += 9;
		    } else if (!xmlStrncmp(pos, BAD_CAST("CONTROL_MASK"), 12)) {
			info.modifiers |= GDK_CONTROL_MASK;
			pos += 12;
		    } else if (!xmlStrncmp(pos, BAD_CAST("MOD"), 3) &&
			       !xmlStrncmp(pos+4, BAD_CAST("_MASK"), 5)) {
			switch (pos[3]) {
			case '1':
			    info.modifiers |= GDK_MOD1_MASK; break;
			case '2':
			    info.modifiers |= GDK_MOD2_MASK; break;
			case '3':
			    info.modifiers |= GDK_MOD3_MASK; break;
			case '4':
			    info.modifiers |= GDK_MOD4_MASK; break;
			case '5':
			    info.modifiers |= GDK_MOD5_MASK; break;
			}
			pos += 9;
		    } else if (!xmlStrncmp(pos, BAD_CAST("BUTTON"), 6) &&
			       !xmlStrncmp(pos+7, BAD_CAST("_MASK"), 5)) {
			switch (pos[6]) {
			case '1':
			    info.modifiers |= GDK_BUTTON1_MASK; break;
			case '2':
			    info.modifiers |= GDK_BUTTON2_MASK; break;
			case '3':
			    info.modifiers |= GDK_BUTTON3_MASK; break;
			case '4':
			    info.modifiers |= GDK_BUTTON4_MASK; break;
			case '5':
			    info.modifiers |= GDK_BUTTON5_MASK; break;
			}
			pos += 12;
		    } else if (!xmlStrncmp(pos, BAD_CAST("RELEASE_MASK"), 12)) {
			info.modifiers |= GDK_RELEASE_MASK;
			pos += 12;
		    } else
			pos++;
               } else
                   pos++;
	} else if (!xmlStrcmp(attrs[i], BAD_CAST("signal")))
	    info.signal = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else
	    g_warning("unknown attribute `%s' for <accelerator>.", attrs[i]);
    }
    if (info.key == 0 || info.signal == NULL) {
	g_warning("required <accelerator> attributes missing!!!");
	return;
    }
    if (!state->accels)
	state->accels = g_array_new(FALSE, FALSE,
				    sizeof(GladeAccelInfo));

    g_array_append_val(state->accels, info);
}

static inline void
handle_child(GladeParseState *state, const xmlChar **attrs)
{
    GladeChildInfo *info;
    gint i;

    /* make sure all of these are flushed */
    flush_properties(state);
    flush_signals(state);
    flush_actions(state);
    flush_relations(state);
    flush_accels(state);

    state->widget->n_children++;
    state->widget->children = g_renew(GladeChildInfo, state->widget->children,
				      state->widget->n_children);
    info = &state->widget->children[state->widget->n_children-1];
    info->internal_child = NULL;
    info->properties = NULL;
    info->n_properties = 0;
    info->child = NULL;

    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
	if (!xmlStrcmp(attrs[i], BAD_CAST("internal-child")))
	    info->internal_child = glade_xml_alloc_string(state->interface, CAST_BAD(attrs[i+1]));
	else
	    g_warning("unknown attribute `%s' for <child>.", attrs[i]);
    }
}

static void
glade_parser_start_document(GladeParseState *state)
{
    state->state = PARSER_START;

    state->unknown_depth = 0;
    state->prev_state = PARSER_UNKNOWN;

    state->widget_depth = 0;
    state->content = g_string_sized_new(128);

    state->interface = glade_parser_interface_new ();
    state->widget = NULL;

    state->prop_type = PROP_NONE;
    state->prop_name = NULL;
    state->comment = NULL;
    state->translate_prop = FALSE;
    state->props = NULL;

    state->signals = NULL;
    state->accels = NULL;
}

static void
glade_parser_end_document(GladeParseState *state)
{
    g_string_free(state->content, TRUE);

    if (state->unknown_depth != 0)
	g_warning("unknown_depth != 0 (%d)", state->unknown_depth);
    if (state->widget_depth != 0)
	g_warning("widget_depth != 0 (%d)", state->widget_depth);
}

static void
glade_parser_comment (GladeParseState *state, const xmlChar *comment)
{
	if (state->state == PARSER_START)
		state->interface->comment = g_strdup (CAST_BAD (comment));
}

static void
glade_parser_start_element(GladeParseState *state,
			   const xmlChar *name, const xmlChar **attrs)
{
    gint i;

    GLADE_NOTE(PARSER, g_message("<%s> in state %s",
				 name, state_names[state->state]));

    switch (state->state) {
    case PARSER_START:
	if (!xmlStrcmp(name, BAD_CAST("glade-interface"))) {
	    state->state = PARSER_GLADE_INTERFACE;
#if 0
	    /* check for correct XML namespace */
	    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
		if (!strcmp(attrs[i], "xmlns") &&
		    !strcmp(attrs[i+1], "...")) {
		    g_warning("bad XML namespace `%s'.", attrs[i+1]);
		} else
		    g_warning("unknown attribute `%s' for <glade-interface>",
			      attrs[i]);
	    }
#endif
	} else {
	    g_warning("Expected <glade-interface>.  Got <%s>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_GLADE_INTERFACE:
	if (!xmlStrcmp(name, BAD_CAST("requires"))) {
	    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
		if (!xmlStrcmp(attrs[i], BAD_CAST("lib"))) {
		    GladeInterface *iface = state->interface;

		    /* add to the list of requirements for this module */
		    iface->n_requires++;
		    iface->requires = g_renew(gchar *, iface->requires,
					      iface->n_requires);
		    iface->requires[iface->n_requires-1] =
			glade_xml_alloc_string(iface, CAST_BAD(attrs[i+1]));
		} else
		    g_warning("unknown attribute `%s' for <requires>.",
			      attrs[i]);
	    }
	    state->state = PARSER_REQUIRES;
	} else if (!xmlStrcmp(name, BAD_CAST("widget"))) {
	    GladeInterface *iface = state->interface;

	    iface->n_toplevels++;
	    iface->toplevels = g_renew(GladeWidgetInfo *, iface->toplevels,
				       iface->n_toplevels);
	    state->widget = create_widget_info(iface, attrs);
	    iface->toplevels[iface->n_toplevels-1] = state->widget;

	    state->widget_depth++;
	    state->prop_type = PROP_NONE;
	    state->prop_name = NULL;
	    state->comment = NULL;
	    state->props = NULL;
	    state->signals = NULL;
	    state->accels = NULL;

	    state->state = PARSER_WIDGET;
	} else {
	    g_warning("Unexpected element <%s> inside <glade-interface>.",
		      name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_REQUIRES:
	g_warning("<requires> element should be empty.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET:
	if (!xmlStrcmp(name, BAD_CAST("property"))) {
	    gboolean bad_agent = FALSE;

	    if (state->prop_type != PROP_NONE &&
		state->prop_type != PROP_WIDGET)
		g_warning("non widget properties defined here (oh no!)");
	    state->translate_prop = FALSE;
	    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
		if (!xmlStrcmp(attrs[i], BAD_CAST("name")))
		    state->prop_name = glade_xml_alloc_propname(state->interface,
						      CAST_BAD(attrs[i+1]));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("translatable")))
		    state->translate_prop = !xmlStrcmp(attrs[i+1], BAD_CAST("yes"));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("context")))
		    state->context_prop = !xmlStrcmp(attrs[i+1], BAD_CAST("yes"));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("comments")))
		    state->comment = glade_xml_alloc_string(state->interface, 
							    CAST_BAD(attrs[i+1]));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("agent")))
		    bad_agent = xmlStrcmp(attrs[i], BAD_CAST("libglade")) != 0;
		else
		    g_warning("unknown attribute `%s' for <property>.",
			      attrs[i]);
	    }
	    if (bad_agent) {
		/* ignore the property ... */
		state->prev_state = state->state;
		state->state = PARSER_UNKNOWN;
		state->unknown_depth++;
	    } else {
		state->prop_type = PROP_WIDGET;
		state->state = PARSER_WIDGET_PROPERTY;
	    }
	} else if (!xmlStrcmp(name, BAD_CAST("accessibility"))) {
	    flush_properties(state);

	    if (attrs != NULL && attrs[0] != NULL)
		g_warning("<accessibility> element should have no attributes");
	    state->state = PARSER_WIDGET_ATK;
	} else if (!xmlStrcmp(name, BAD_CAST("signal"))) {
	    handle_signal(state, attrs);
	    state->state = PARSER_WIDGET_SIGNAL;
	} else if (!xmlStrcmp(name, BAD_CAST("accelerator"))) {
	    handle_accel(state, attrs);
	    state->state = PARSER_WIDGET_ACCEL;
	} else if (!xmlStrcmp(name, BAD_CAST("child"))) {
	    handle_child(state, attrs);
	    state->state = PARSER_WIDGET_CHILD;
	} else {
	    g_warning("Unexpected element <%s> inside <widget>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_PROPERTY:
	g_warning("<property> element should be empty.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET_ATK:
	if (!xmlStrcmp(name, BAD_CAST("atkproperty"))) {
	    if (state->prop_type != PROP_NONE &&
		state->prop_type != PROP_ATK)
		g_warning("non atk properties defined here (oh no!)");
	    state->prop_type = PROP_ATK;
	    state->translate_prop = FALSE;
	    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
		if (!xmlStrcmp(attrs[i], BAD_CAST("name")))
		    state->prop_name = glade_xml_alloc_propname(state->interface,
						      CAST_BAD(attrs[i+1]));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("translatable")))
		    state->translate_prop = !xmlStrcmp(attrs[i+1], BAD_CAST("yes"));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("context")))
		    state->context_prop = !xmlStrcmp(attrs[i+1], BAD_CAST("yes"));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("comments")))
		    state->comment = glade_xml_alloc_string(state->interface, 
							    CAST_BAD(attrs[i+1]));
		else
		    g_warning("unknown attribute `%s' for <atkproperty>.",
			      attrs[i]);
	    }
	    state->state = PARSER_WIDGET_ATK_PROPERTY;
	} else if (!xmlStrcmp(name, BAD_CAST("atkaction"))) {
	    handle_atk_action(state, attrs);
	    state->state = PARSER_WIDGET_ATK_ACTION;
	} else if (!xmlStrcmp(name, BAD_CAST("atkrelation"))) {
	    handle_atk_relation(state, attrs);
	    state->state = PARSER_WIDGET_ATK_RELATION;
	} else {
	    g_warning("Unexpected element <%s> inside <accessibility>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_ATK_PROPERTY:
	if (!xmlStrcmp(name, BAD_CAST("accessibility"))) {
	    state->state = PARSER_WIDGET_ATK;
	} else {
	    g_warning("Unexpected element <%s> inside <atkproperty>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_ATK_ACTION:
	g_warning("<atkaction> element should be empty.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET_ATK_RELATION:
	g_warning("<atkrelation> element should be empty.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET_AFTER_ATK:
	if (!xmlStrcmp(name, BAD_CAST("signal"))) {
	    handle_signal(state, attrs);
	    state->state = PARSER_WIDGET_SIGNAL;
	} else if (!xmlStrcmp(name, BAD_CAST("accelerator"))) {
	    handle_accel(state, attrs);
	    state->state = PARSER_WIDGET_ACCEL;
	} else if (!xmlStrcmp(name, BAD_CAST("child"))) {
	    handle_child(state, attrs);
	    state->state = PARSER_WIDGET_CHILD;
	} else {
	    g_warning("Unexpected element <%s> inside <widget>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_SIGNAL:
	g_warning("<signal> element should be empty.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET_AFTER_SIGNAL:
	if (!xmlStrcmp(name, BAD_CAST("accelerator"))) {
	    handle_accel(state, attrs);
	    state->state = PARSER_WIDGET_ACCEL;
	} else if (!xmlStrcmp(name, BAD_CAST("child"))) {
	    handle_child(state, attrs);
	    state->state = PARSER_WIDGET_CHILD;
	} else {
	    g_warning("Unexpected element <%s> inside <widget>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_ACCEL:
	g_warning("<accelerator> element should be empty.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET_AFTER_ACCEL:
	if (!xmlStrcmp(name, BAD_CAST("child"))) {
	    handle_child(state, attrs);
	    state->state = PARSER_WIDGET_CHILD;
	} else {
	    g_warning("Unexpected element <%s> inside <widget>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_CHILD:
	if (!xmlStrcmp(name, BAD_CAST("widget"))) {
	    GladeWidgetInfo *parent = state->widget;
	    GladeChildInfo *info = &parent->children[parent->n_children-1];

	    if (info->child)
		g_warning("widget pointer already set!! not good");

	    state->widget = create_widget_info(state->interface, attrs);
	    info->child = state->widget;
	    info->child->parent = parent;

	    state->widget_depth++;
	    state->prop_type = PROP_NONE;
	    state->prop_name = NULL;
	    state->comment = NULL;
	    state->props = NULL;
	    state->signals = NULL;
	    state->accels = NULL;

	    state->state = PARSER_WIDGET;
	} else if (!xmlStrcmp(name, BAD_CAST("placeholder"))) {
	    /* this isn't a real child, so load a NULL ChildInfo to 
	     * symbolize a placeholder
	     */
	    state->state = PARSER_WIDGET_CHILD_PLACEHOLDER;
	} else {
	    g_warning("Unexpected element <%s> inside <child>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_CHILD_AFTER_WIDGET:
	if (!xmlStrcmp(name, BAD_CAST("packing"))) {
	    state->state = PARSER_WIDGET_CHILD_PACKING;
	} else {
	    g_warning("Unexpected element <%s> inside <child>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_CHILD_PACKING:
	if (!xmlStrcmp(name, BAD_CAST("property"))) {
	    gboolean bad_agent = FALSE;

	    if (state->prop_type != PROP_NONE &&
		state->prop_type != PROP_CHILD)
		g_warning("non child properties defined here (oh no!)");
	    state->translate_prop = FALSE;
	    for (i = 0; attrs && attrs[i] != NULL; i += 2) {
		if (!xmlStrcmp(attrs[i], BAD_CAST("name")))
		    state->prop_name = glade_xml_alloc_propname(state->interface,
						      CAST_BAD(attrs[i+1]));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("translatable")))
		    state->translate_prop = !xmlStrcmp(attrs[i+1], BAD_CAST("yes"));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("context")))
		    state->context_prop = !xmlStrcmp(attrs[i+1], BAD_CAST("yes"));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("comments")))
		    state->comment = glade_xml_alloc_string(state->interface, 
							    CAST_BAD(attrs[i+1]));
		else if (!xmlStrcmp(attrs[i], BAD_CAST("agent")))
		    bad_agent = xmlStrcmp(attrs[i], BAD_CAST("libglade")) != 0;
		else
		    g_warning("unknown attribute `%s' for <property>.",
			      attrs[i]);
	    }
	    if (bad_agent) {
		/* ignore the property ... */
		state->prev_state = state->state;
		state->state = PARSER_UNKNOWN;
		state->unknown_depth++;
	    } else {
		state->prop_type = PROP_CHILD;
		state->state = PARSER_WIDGET_CHILD_PACKING_PROPERTY;
	    }
	} else {
	    g_warning("Unexpected element <%s> inside <child>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_WIDGET_CHILD_PACKING_PROPERTY:
	g_warning("<property> element should be empty.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET_CHILD_AFTER_PACKING:
	g_warning("<child> should have no elements after <packing>.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET_CHILD_PLACEHOLDER:
	g_warning("<placeholder> should be empty.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_WIDGET_CHILD_AFTER_PLACEHOLDER:
	/* Get packing info on placeholders, for special cases */
	if (!xmlStrcmp(name, BAD_CAST("packing"))) {
	    state->state = PARSER_WIDGET_CHILD_PACKING;
	} else {
	    g_warning("Unexpected element <%s> inside <child>.", name);
	    state->prev_state = state->state;
	    state->state = PARSER_UNKNOWN;
	    state->unknown_depth++;
	}
	break;
    case PARSER_FINISH:
	g_warning("There should be no elements here.  Found <%s>.", name);
	state->prev_state = state->state;
	state->state = PARSER_UNKNOWN;
	state->unknown_depth++;
	break;
    case PARSER_UNKNOWN:
	state->unknown_depth++;
	break;
    }
    /* truncate the content string ... */
    g_string_truncate(state->content, 0);
}

static void
glade_parser_end_element(GladeParseState *state, const xmlChar *name)
{
    GladePropInfo prop;

    GLADE_NOTE(PARSER, g_message("</%s> in state %s",
				 name, state_names[state->state]));

    switch (state->state) {
    case PARSER_START:
	g_warning("should not be closing any elements in this state");
	break;
    case PARSER_GLADE_INTERFACE:
	if (xmlStrcmp(name, BAD_CAST("glade-interface")) != 0)
	    g_warning("should find </glade-interface> here.  Found </%s>",
		      name);
	state->state = PARSER_FINISH;
	break;
    case PARSER_REQUIRES:
	if (xmlStrcmp(name, BAD_CAST("requires")) != 0)
	    g_warning("should find </requires> here.  Found </%s>", name);
	state->state = PARSER_GLADE_INTERFACE;
	break;
    case PARSER_WIDGET:
    case PARSER_WIDGET_AFTER_ATK:
    case PARSER_WIDGET_AFTER_SIGNAL:
    case PARSER_WIDGET_AFTER_ACCEL:
	if (xmlStrcmp(name, BAD_CAST("widget")) != 0)
	    g_warning("should find </widget> here.  Found </%s>", name);
	flush_properties(state);
	flush_signals(state);
	flush_actions(state);
	flush_relations(state);
	flush_accels(state);
	state->widget = state->widget->parent;
	state->widget_depth--;

	if (state->widget_depth == 0)
	    state->state = PARSER_GLADE_INTERFACE;
	else
	    state->state = PARSER_WIDGET_CHILD_AFTER_WIDGET;
	break;
    case PARSER_WIDGET_PROPERTY:
	if (xmlStrcmp(name, BAD_CAST("property")) != 0)
	    g_warning("should find </property> here.  Found </%s>", name);
	if (!state->props)
	    state->props = g_array_new(FALSE, FALSE, sizeof(GladePropInfo));

	prop.name         = state->prop_name;
	prop.has_context  = state->context_prop;
	prop.translatable = state->translate_prop;
	prop.comment      = state->comment;
	prop.value        = glade_xml_alloc_string(state->interface, state->content->str);

	g_array_append_val(state->props, prop);
	state->prop_name = NULL;
	state->comment   = NULL;
	state->state = PARSER_WIDGET;
	break;
    case PARSER_WIDGET_ATK:
	if (xmlStrcmp(name, BAD_CAST("accessibility")) != 0)
	    g_warning("should find </accessibility> here.  Found </%s>", name);
	flush_properties(state); /* flush the ATK properties */
	state->state = PARSER_WIDGET_AFTER_ATK;
	break;
    case PARSER_WIDGET_ATK_PROPERTY:
	if (xmlStrcmp(name, BAD_CAST("atkproperty")) != 0)
	    g_warning("should find </atkproperty> here.  Found </%s>", name);
	if (!state->props)
	    state->props = g_array_new(FALSE, FALSE, sizeof(GladePropInfo));
	prop.name         = state->prop_name;
	prop.has_context  = state->context_prop;
	prop.translatable = state->translate_prop;
	prop.comment      = state->comment;
	prop.value        = glade_xml_alloc_string(state->interface, state->content->str);

	g_array_append_val(state->props, prop);
	state->prop_name = NULL;
	state->comment   = NULL;

	state->state = PARSER_WIDGET_ATK;
	break;
    case PARSER_WIDGET_ATK_ACTION:
	if (xmlStrcmp(name, BAD_CAST("atkaction")) != 0)
	    g_warning("should find </atkaction> here.  Found </%s>", name);
        state->prop_name = NULL;
        state->state = PARSER_WIDGET_ATK;
        break;
    case PARSER_WIDGET_ATK_RELATION:
	if (xmlStrcmp(name, BAD_CAST("atkrelation")) != 0)
	    g_warning("should find </atkrelation> here.  Found </%s>", name);
        state->prop_name = NULL;
        state->state = PARSER_WIDGET_ATK;
        break;
    case PARSER_WIDGET_SIGNAL:
	if (xmlStrcmp(name, BAD_CAST("signal")) != 0)
	    g_warning("should find </signal> here.  Found </%s>", name);
	state->state = PARSER_WIDGET_AFTER_ATK;
	break;
    case PARSER_WIDGET_ACCEL:
	if (xmlStrcmp(name, BAD_CAST("accelerator")) != 0)
	    g_warning("should find </accelerator> here.  Found </%s>", name);
	state->state = PARSER_WIDGET_AFTER_SIGNAL;
	break;
    case PARSER_WIDGET_CHILD:
	if (xmlStrcmp(name, BAD_CAST("child")) != 0)
	    g_warning("should find </child> here.  Found </%s>", name);
	/* if we are ending the element in this state, then there
	 * hasn't been a <widget> element inside this <child>
	 * element. (If there was, then we would be in
	 * PARSER_WIDGET_CHILD_AFTER_WIDGET state. */
	g_warning("no <widget> element found inside <child>.  Discarding");
	g_free(state->widget->children[
			state->widget->n_children-1].properties);
	state->widget->n_children--;
	state->state = PARSER_WIDGET_AFTER_ACCEL;
	break;
    case PARSER_WIDGET_CHILD_AFTER_WIDGET:
	if (xmlStrcmp(name, BAD_CAST("child")) != 0)
	    g_warning("should find </child> here.  Found </%s>", name);
	state->state = PARSER_WIDGET_AFTER_ACCEL;
	break;
    case PARSER_WIDGET_CHILD_PACKING:
	if (xmlStrcmp(name, BAD_CAST("packing")) != 0)
	    g_warning("should find </packing> here.  Found </%s>", name);
	state->state = PARSER_WIDGET_CHILD_AFTER_PACKING;
	flush_properties(state); /* flush the properties. */
	break;
    case PARSER_WIDGET_CHILD_PACKING_PROPERTY:
	if (xmlStrcmp(name, BAD_CAST("property")) != 0)
	    g_warning("should find </property> here.  Found </%s>", name);
	if (!state->props)
	    state->props = g_array_new(FALSE, FALSE, sizeof(GladePropInfo));

	prop.name         = state->prop_name;
	prop.has_context  = state->context_prop;
	prop.translatable = state->translate_prop;
	prop.comment      = state->comment;
	prop.value        = glade_xml_alloc_string(state->interface, state->content->str);

	g_array_append_val(state->props, prop);
	state->prop_name = NULL;
	state->comment   = NULL;

	state->state = PARSER_WIDGET_CHILD_PACKING;
	break;
    case PARSER_WIDGET_CHILD_AFTER_PACKING:
	if (xmlStrcmp(name, BAD_CAST("child")) != 0)
	    g_warning("should find </child> here.  Found </%s>", name);
	state->state = PARSER_WIDGET_AFTER_ACCEL;
	break;
    case PARSER_WIDGET_CHILD_PLACEHOLDER:
	if (xmlStrcmp(name, BAD_CAST("placeholder")) != 0)
	    g_warning("should find </placeholder> here.  Found </%s>", name);
	state->state = PARSER_WIDGET_CHILD_AFTER_PLACEHOLDER;
	break;
    case PARSER_WIDGET_CHILD_AFTER_PLACEHOLDER:
	if (xmlStrcmp(name, BAD_CAST("child")) != 0)
	    g_warning("should find </child> here.  Found </%s>", name);
	state->state = PARSER_WIDGET_AFTER_ACCEL;
	break;
    case PARSER_FINISH:
	g_warning("should not be closing any elements in this state");
	break;
    case PARSER_UNKNOWN:
	state->unknown_depth--;
	if (state->unknown_depth == 0)
	    state->state = state->prev_state;
	break;
    }
}

static void
glade_parser_characters(GladeParseState *state, const xmlChar *chars, gint len)
{
    switch (state->state) {
    case PARSER_WIDGET_PROPERTY:
    case PARSER_WIDGET_ATK_PROPERTY:
    case PARSER_WIDGET_CHILD_PACKING_PROPERTY:
	g_string_append_len(state->content, CAST_BAD(chars), len);
	break;
    default:
	/* don't care about content in any other states */
	break;
    }
}

static xmlEntityPtr
glade_parser_get_entity(GladeParseState *state, const xmlChar *name)
{
    return xmlGetPredefinedEntity(name);
}

static void
glade_parser_warning(GladeParseState *state, const gchar *msg, ...)
{
    va_list args;

    va_start(args, msg);
    g_logv("XML", G_LOG_LEVEL_WARNING, msg, args);
    va_end(args);
}

static void
glade_parser_error(GladeParseState *state, const gchar *msg, ...)
{
    va_list args;

    va_start(args, msg);
    g_logv("XML", G_LOG_LEVEL_CRITICAL, msg, args);
    va_end(args);
}

static void
glade_parser_fatal_error(GladeParseState *state, const gchar *msg, ...)
{
    va_list args;

    va_start(args, msg);
    g_logv("XML", G_LOG_LEVEL_ERROR, msg, args);
    va_end(args);
}

static xmlSAXHandler glade_parser = {
    0, /* internalSubset */
    0, /* isStandalone */
    0, /* hasInternalSubset */
    0, /* hasExternalSubset */
    0, /* resolveEntity */
    (getEntitySAXFunc)glade_parser_get_entity, /* getEntity */
    0, /* entityDecl */
    0, /* notationDecl */
    0, /* attributeDecl */
    0, /* elementDecl */
    0, /* unparsedEntityDecl */
    0, /* setDocumentLocator */
    (startDocumentSAXFunc)glade_parser_start_document, /* startDocument */
    (endDocumentSAXFunc)glade_parser_end_document, /* endDocument */
    (startElementSAXFunc)glade_parser_start_element, /* startElement */
    (endElementSAXFunc)glade_parser_end_element, /* endElement */
    0, /* reference */
    (charactersSAXFunc)glade_parser_characters, /* characters */
    0, /* ignorableWhitespace */
    0, /* processingInstruction */
    (commentSAXFunc)glade_parser_comment, /* comment */
    (warningSAXFunc)glade_parser_warning, /* warning */
    (errorSAXFunc)glade_parser_error, /* error */
    (fatalErrorSAXFunc)glade_parser_fatal_error, /* fatalError */
};

static void
widget_info_free(GladeWidgetInfo *info)
{
    gint i;

    if (!info) return;

    g_free(info->properties);
    g_free(info->atk_props);
    g_free(info->signals);
    g_free(info->accels);

    for (i = 0; i < info->n_children; i++) {
	g_free(info->children[i].properties);
	widget_info_free(info->children[i].child);
    }
    g_free(info->children);
    g_free(info);
}

/**
 * glade_parser_interface_new
 *
 * Returns a newly allocated GladeInterface.
 */
GladeInterface *
glade_parser_interface_new ()
{
	GladeInterface *interface;
	interface = g_new0 (GladeInterface, 1);
	interface->names = g_hash_table_new (g_str_hash, g_str_equal);
	interface->strings = g_hash_table_new_full (g_str_hash,
						    g_str_equal,
						    (GDestroyNotify)g_free,
						    NULL);
	return interface;
}

/**
 * glade_parser_interface_destroy
 * @interface: the GladeInterface structure.
 *
 * Frees a GladeInterface structure.
 */
void
glade_parser_interface_destroy (GladeInterface *interface)
{
    gint i;

    g_return_if_fail(interface != NULL);

    /* free requirements */
    g_free(interface->requires);

    for (i = 0; i < interface->n_toplevels; i++)
	widget_info_free(interface->toplevels[i]);
    g_free(interface->toplevels);

    g_hash_table_destroy(interface->names);

    /* free the strings hash table.  The destroy notify will take care
     * of the strings. */
    g_hash_table_destroy(interface->strings);

    g_free (interface->comment);
    
    g_free(interface);
}

/**
 * glade_parser_interface_new_from_file
 * @file: the filename of the glade XML file.
 * @domain: the translation domain for the XML file.
 *
 * This function parses a Glade XML interface file to a GladeInterface
 * object (which is libglade's internal representation of the
 * interface data).
 *
 * Generally, user code won't need to call this function.  Instead, it
 * should go through the GladeXML interfaces.
 *
 * Returns: the GladeInterface structure for the XML file.
 */
GladeInterface *
glade_parser_interface_new_from_file (const gchar *file, const gchar *domain)
{
    GladeParseState state = { 0 };
    int prevSubstituteEntities;
    int rc;

    if (!g_file_test(file, G_FILE_TEST_IS_REGULAR)) {
	glade_util_ui_message (glade_app_get_window (), 
			       GLADE_UI_ERROR,
			       _("Could not find glade file %s"), file);
	return NULL;
    }

    state.interface = NULL;
    if (domain)
	state.domain = domain;
    else
	state.domain = textdomain(NULL);

    prevSubstituteEntities = xmlSubstituteEntitiesDefault(1);

    rc = xmlSAXUserParseFile(&glade_parser, &state, file);

    xmlSubstituteEntitiesDefault(prevSubstituteEntities);

    if (rc < 0) {
	glade_util_ui_message (glade_app_get_window (), 
			       GLADE_UI_ERROR,
			       _("Errors parsing glade file %s"), file);
	if (state.interface)
	    glade_parser_interface_destroy (state.interface);
	return NULL;
    }
    if (state.state != PARSER_FINISH) {
	glade_util_ui_message (glade_app_get_window (), 
			       GLADE_UI_ERROR,
			       _("Errors parsing glade file %s"), file);
	if (state.interface)
	    glade_parser_interface_destroy(state.interface);
	return NULL;
    }
    return state.interface;
}

/**
 * glade_parser_interface_new_from_buffer
 * @buffer: a buffer in memory containing XML data.
 * @len: the length of @buffer.
 * @domain: the translation domain for the XML file.
 *
 * This function is similar to glade_parser_parse_file, except that it
 * parses XML data from a buffer in memory.  This could be used to
 * embed an interface into the executable, for instance.
 *
 * Generally, user code won't need to call this function.  Instead, it
 * should go through the GladeXML interfaces.
 *
 * Returns: the GladeInterface structure for the XML buffer.
 */
GladeInterface *
glade_parser_interface_new_from_buffer (const gchar *buffer,
					gint len,
					const gchar *domain)
{
    GladeParseState state = { 0 };
    int prevSubstituteEntities;
    int rc;

    state.interface = NULL;
    if (domain)
	state.domain = domain;
    else
	state.domain = textdomain(NULL);

    prevSubstituteEntities = xmlSubstituteEntitiesDefault(1);

    rc = xmlSAXUserParseMemory(&glade_parser, &state, buffer, len);

    xmlSubstituteEntitiesDefault(prevSubstituteEntities);

    if (rc < 0) {
	g_warning("document not well formed!");
	if (state.interface)
	    glade_parser_interface_destroy (state.interface);
	return NULL;
    }
    if (state.state != PARSER_FINISH) {
	g_warning("did not finish in PARSER_FINISH state!");
	if (state.interface)
	    glade_parser_interface_destroy(state.interface);
	return NULL;
    }
    return state.interface;
}

static gchar *
modifier_string_from_bits (GdkModifierType modifiers)
{
    GString *string = g_string_new ("");

    if (modifiers & GDK_SHIFT_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_SHIFT_MASK");
    }

    if (modifiers & GDK_LOCK_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_LOCK_MASK");
    }

    if (modifiers & GDK_CONTROL_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_CONTROL_MASK");
    }

    if (modifiers & GDK_MOD1_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_MOD1_MASK");
    }

    if (modifiers & GDK_MOD2_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_MOD2_MASK");
    }

    if (modifiers & GDK_MOD3_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_MOD3_MASK");
    }

    if (modifiers & GDK_MOD4_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_MOD4_MASK");
    }

    if (modifiers & GDK_MOD5_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_MOD5_MASK");
    }

    if (modifiers & GDK_BUTTON1_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_BUTTON1_MASK");
    }

    if (modifiers & GDK_BUTTON2_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_BUTTON2_MASK");
    }

    if (modifiers & GDK_BUTTON3_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_BUTTON3_MASK");
    }

    if (modifiers & GDK_BUTTON4_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_BUTTON4_MASK");
    }

    if (modifiers & GDK_BUTTON5_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_BUTTON5_MASK");
    }

    if (modifiers & GDK_RELEASE_MASK) {
	if (string->len > 0)
	    g_string_append (string, " | ");
	g_string_append (string, "GDK_RELEASE_MASK");
    }

    if (string->len > 0)
	return g_string_free (string, FALSE);

    g_string_free (string, TRUE);
    return NULL;
}

static void
dump_widget(xmlNode *parent, GladeWidgetInfo *info, gint indent)
{
    xmlNode *widget;
    gint i, j;

    if (info == NULL) {
	widget = xmlNewNode(NULL, BAD_CAST("placeholder"));
	xmlAddChild(parent, widget);
	return;
    }

    widget = xmlNewNode(NULL, BAD_CAST("widget"));

    xmlSetProp(widget, BAD_CAST("class"), BAD_CAST(info->classname));
    xmlSetProp(widget, BAD_CAST("id"), BAD_CAST(info->name));
    xmlAddChild(parent, widget);
    xmlNodeAddContent(widget, BAD_CAST("\n"));

    for (i = 0; i < info->n_properties; i++) { 
	xmlNode *node;

	for (j = 0; j < indent + 1; j++)
	    xmlNodeAddContent(widget, BAD_CAST("  "));
	node = xmlNewNode(NULL, BAD_CAST("property"));
	xmlSetProp(node, BAD_CAST("name"), BAD_CAST(info->properties[i].name));
	if (info->properties[i].translatable)
	    xmlSetProp(node, BAD_CAST("translatable"), BAD_CAST("yes"));
	if (info->properties[i].has_context)
	    xmlSetProp(node, BAD_CAST("context"), BAD_CAST("yes"));
	if (info->properties[i].comment)
	    xmlSetProp(node, BAD_CAST("comments"), 
		       BAD_CAST(info->properties[i].comment));
	xmlNodeSetContent(node, BAD_CAST(info->properties[i].value));
	xmlAddChild(widget, node);
	xmlNodeAddContent(widget, BAD_CAST("\n"));
    }

    if (info->n_atk_props   != 0 ||
	info->n_atk_actions != 0 ||
	info->n_relations   != 0) {
	xmlNode *atk;

	for (j = 0; j < indent + 1; j++)
	    xmlNodeAddContent(widget, BAD_CAST("  "));
	atk = xmlNewNode(NULL, BAD_CAST("accessibility"));
	xmlAddChild(widget, atk);
	xmlNodeAddContent(widget, BAD_CAST("\n"));
	xmlNodeAddContent(atk, BAD_CAST("\n"));

	for (i = 0; i < info->n_atk_props; i++) {
	    xmlNode *node;

	    for (j = 0; j < indent + 2; j++)
		xmlNodeAddContent(atk, BAD_CAST("  "));
	    node = xmlNewNode(NULL, BAD_CAST("atkproperty"));
	    xmlSetProp(node, BAD_CAST("name"), BAD_CAST(info->atk_props[i].name));
	    if (info->atk_props[i].translatable)
		xmlSetProp(node, BAD_CAST("translatable"), BAD_CAST("yes"));
	    if (info->atk_props[i].has_context)
		xmlSetProp(node, BAD_CAST("context"), BAD_CAST("yes"));
	    if (info->atk_props[i].comment)
		xmlSetProp(node, BAD_CAST("comments"), 
			   BAD_CAST(info->atk_props[i].comment));
	    xmlNodeSetContent(node, BAD_CAST(info->atk_props[i].value));
	    xmlAddChild(atk, node);
	    xmlNodeAddContent(atk, BAD_CAST("\n"));
	}

	for (i = 0; i < info->n_atk_actions; i++) {
	    xmlNode *node;

	    for (j = 0; j < indent + 2; j++)
		xmlNodeAddContent(atk, BAD_CAST("  "));
	    node = xmlNewNode(NULL, BAD_CAST("atkaction"));
	    xmlSetProp(node, BAD_CAST("action_name"), 
		       BAD_CAST(info->atk_actions[i].action_name));
	    xmlSetProp(node, BAD_CAST("description"), 
		       BAD_CAST(info->atk_actions[i].description));
	    xmlAddChild(atk, node);
	    xmlNodeAddContent(atk, BAD_CAST("\n"));
	}

	for (i = 0; i < info->n_relations; i++) {
	    xmlNode *node;

	    for (j = 0; j < indent + 2; j++)
		xmlNodeAddContent(atk, BAD_CAST("  "));
	    node = xmlNewNode(NULL, BAD_CAST("atkrelation"));
	    xmlSetProp(node, BAD_CAST("target"), 
		       BAD_CAST(info->relations[i].target));
	    xmlSetProp(node, BAD_CAST("type"), 
		       BAD_CAST(info->relations[i].type));
	    xmlAddChild(atk, node);
	    xmlNodeAddContent(atk, BAD_CAST("\n"));
	}

	for (j = 0; j < indent + 1; j++)
	    xmlNodeAddContent(atk, BAD_CAST("  "));
    }

    for (i = 0; i < info->n_signals; i++) {
	xmlNode *node;

	for (j = 0; j < indent + 1; j++)
	    xmlNodeAddContent(widget, BAD_CAST("  "));

	node = xmlNewNode(NULL, BAD_CAST("signal"));
	xmlSetProp(node, BAD_CAST("name"), BAD_CAST(info->signals[i].name));
	xmlSetProp(node, BAD_CAST("handler"), BAD_CAST(info->signals[i].handler));
	if (info->signals[i].after)
	    xmlSetProp(node, BAD_CAST("after"), BAD_CAST("yes"));
	if (info->signals[i].lookup)
	    xmlSetProp(node, BAD_CAST("lookup"), BAD_CAST("yes"));
	if (info->signals[i].object)
	    xmlSetProp(node, BAD_CAST("object"), BAD_CAST(info->signals[i].object));
	xmlAddChild(widget, node);
	xmlNodeAddContent(widget, BAD_CAST("\n"));
    }

    for (i = 0; i < info->n_accels; i++) {
	xmlNode *node;
	gchar   *modifiers;

	modifiers = modifier_string_from_bits (info->accels[i].modifiers);

	for (j = 0; j < indent + 1; j++)
	    xmlNodeAddContent(widget, BAD_CAST("  "));

	node = xmlNewNode(NULL, BAD_CAST("accelerator"));
	xmlSetProp(node, BAD_CAST("key"), BAD_CAST(gdk_keyval_name(info->accels[i].key)));
	xmlSetProp(node, BAD_CAST("modifiers"), BAD_CAST(modifiers));
	xmlSetProp(node, BAD_CAST("signal"), BAD_CAST(info->accels[i].signal));
	xmlAddChild(widget, node);
	xmlNodeAddContent(widget, BAD_CAST("\n"));

	if (modifiers)
	    g_free (modifiers);
    }

    for (i = 0; i < info->n_children; i++) {
	xmlNode *child, *packing;
	GladeChildInfo *childinfo = &info->children[i];
	gint k;

	for (j = 0; j < indent + 1; j++)
	    xmlNodeAddContent(widget, BAD_CAST("  "));

	child = xmlNewNode(NULL, BAD_CAST("child"));
	if (childinfo->internal_child)
	    xmlSetProp(child, BAD_CAST("internal-child"), BAD_CAST(childinfo->internal_child));
	xmlAddChild(widget, child);
	xmlNodeAddContent(widget, BAD_CAST("\n"));
	xmlNodeAddContent(child, BAD_CAST("\n"));

	for (j = 0; j < indent + 2; j++)
	    xmlNodeAddContent(child, BAD_CAST("  "));
	dump_widget(child, childinfo->child, indent + 2);
	xmlNodeAddContent(child, BAD_CAST("\n"));

	if (childinfo->n_properties) {
	    for (j = 0; j < indent + 2; j++)
		xmlNodeAddContent(child, BAD_CAST("  "));
	    packing = xmlNewNode(NULL, BAD_CAST("packing"));
	    xmlAddChild(child, packing);
	    xmlNodeAddContent(packing, BAD_CAST("\n"));

	    for (k = 0; k < childinfo->n_properties; k++) { 
		xmlNode *node;

		for (j = 0; j < indent + 3; j++)
		    xmlNodeAddContent(packing, BAD_CAST("  "));
		node = xmlNewNode(NULL, BAD_CAST("property"));
		xmlSetProp(node, BAD_CAST("name"), BAD_CAST(childinfo->properties[k].name));
		if (childinfo->properties[k].translatable)
		    xmlSetProp(node, BAD_CAST("translatable"), BAD_CAST("yes"));
		if (childinfo->properties[k].has_context)
		    xmlSetProp(node, BAD_CAST("context"), BAD_CAST("yes"));
		if (childinfo->properties[k].comment)
		    xmlSetProp(node, BAD_CAST("comments"), 
			       BAD_CAST(childinfo->properties[k].comment));
		xmlNodeSetContent(node, BAD_CAST(childinfo->properties[k].value));
		xmlAddChild(packing, node);
		xmlNodeAddContent(packing, BAD_CAST("\n"));
	    }
	}

	if (childinfo->n_properties) {
	    for (j = 0; j < indent + 2; j++)
		xmlNodeAddContent(packing, BAD_CAST("  "));
	    xmlNodeAddContent(child, BAD_CAST("\n"));
	}

	for (j = 0; j < indent + 1; j++)
	    xmlNodeAddContent(child, BAD_CAST("  "));
    }

    for (j = 0; j < indent; j++)
	xmlNodeAddContent(widget, BAD_CAST("  "));
}

static xmlDoc *
glade_interface_make_doc (GladeInterface *interface)
{
    xmlDoc *doc;
    xmlNode *root, *comment;
    gint i;

    doc = xmlNewDoc(BAD_CAST("1.0"));
    doc->standalone = FALSE;
    xmlCreateIntSubset(doc, BAD_CAST("glade-interface"),
		       NULL, BAD_CAST("glade-2.0.dtd"));

    if (interface->comment)
    {
	comment = xmlNewComment(BAD_CAST (interface->comment));
	xmlDocSetRootElement(doc, comment);
    }
	
    root = xmlNewNode(NULL, BAD_CAST("glade-interface"));
    xmlDocSetRootElement(doc, root);

    xmlNodeAddContent(root, BAD_CAST("\n"));

    for (i = 0; i < interface->n_requires; i++) {
	xmlNode *node = xmlNewNode(NULL, BAD_CAST("requires"));

	xmlSetProp(node, BAD_CAST("lib"), BAD_CAST(interface->requires[i]));

	xmlNodeAddContent(root, BAD_CAST("  "));
	xmlAddChild(root, node);
	xmlNodeAddContent(root, BAD_CAST("\n"));
    }

    for (i = 0; i < interface->n_toplevels; i++) {
	xmlNodeAddContent(root, BAD_CAST("  "));
	dump_widget(root, interface->toplevels[i], 1);
	xmlNodeAddContent(root, BAD_CAST("\n"));
    }
    return doc;
}

static void
glade_interface_buffer (GladeInterface  *interface,
			gpointer        *buffer,
			gint            *size)
{
    xmlDoc *doc;
    g_return_if_fail (interface != NULL);
    g_return_if_fail (buffer    != NULL);
    g_return_if_fail (size      != NULL);

    doc = glade_interface_make_doc (interface);
    xmlDocDumpFormatMemoryEnc(doc, (xmlChar **)buffer,
			      size, "UTF-8",  TRUE);
    xmlFreeDoc(doc);
}

/**
 * glade_parser_interface_dump
 * @interface: the GladeInterface
 * @filename: the filename to write the interface data to.
 * @error: a #GError for error handleing.
 *
 * This function dumps the contents of a GladeInterface into a file as
 * XML.  It is used by glade to write glade files.
 *
 * Returns whether the write was successfull or not.
 */
gboolean
glade_parser_interface_dump (GladeInterface *interface,
			     const gchar *filename,
			     GError **error)
{
	GIOChannel *fd;
	gpointer buffer;
	gint     size, retval = G_IO_STATUS_ERROR;

	glade_interface_buffer (interface, &buffer, &size);
	
	if (buffer == NULL)
	{
		g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM,
			     _("Could not allocate memory for interface"));
		return FALSE;
	}
	
	if ((fd = g_io_channel_new_file (filename, "w", error)))
	{
		retval = g_io_channel_write_chars (fd, buffer, size, NULL, error);
		g_io_channel_shutdown(fd, TRUE, NULL);
		g_io_channel_unref (fd);
	}
	
	xmlFree (buffer);
	
	return (retval == G_IO_STATUS_NORMAL) ? TRUE : FALSE;
}

G_CONST_RETURN gchar *
glade_parser_pvalue_from_winfo (GladeWidgetInfo *winfo,
				const gchar     *pname)
{
	gchar *dup_name = g_strdup (pname);
	gint i;

	/* Make '_' & '-' synonymous here for convenience.
	 */
	glade_util_replace (dup_name, '-', '_');
	for (i = 0; i < winfo->n_properties; i++)
	{
		if (!strcmp (pname, winfo->properties[i].name) ||
		    !strcmp (dup_name, winfo->properties[i].name))
			return winfo->properties[i].value;
	}
	return NULL;
}


#if 0
int
main(int argc, char **argv) {
    gtk_init(&argc, &argv);
    if (argc > 1) {
	GladeInterface *interface = glade_parser_parse_file(argv[1]);
	g_message("output: %p", interface);
	if (interface) {
	    glade_interface_dump(interface, "/dev/stdout");
	    glade_interface_destroy(interface);
	}
    } else
	g_message("need filename");
    return 0;
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1