/*
  EIMIL.c
    EIMIL parser/dictionary/data manager.
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <EIMIL.h>
#include "EIMILint.h"

#define MAX_ELEMENT_DEPTH 10

static int EIMIL_ID_counter = 0;
#define EIMIL_ID_MAX ((1 << 15) - 1)

static EIMIL_value EIMIL_t_val;

static const unsigned char* EIMIL_xmlns_uri = "http://www.OpenI18N.org/EIMIL/NS/1.0";
EIMIL_dictionary *pdic_internal = NULL;
EIMIL_symbol *pEIMIL_nil_sym = NULL;
EIMIL_symbol *pEIMIL_t_sym = NULL;
EIMIL_symbol *pEIMIL_feedback_sym = NULL;
EIMIL_symbol *pEIMIL_candidates_sym = NULL;

static int EIMIL_inited = 0;

/********************************************************************************
			Char/Token manipulation functions.
 ********************************************************************************/

#define EIMIL_isspace(c) ((c) == ' ' || (c) == '\n' || (c) == '\t' || (c) == '\r')
#define EIMIL_istag_s(c) ((c) == '<')
#define EIMIL_istag_e(c) ((c) == '>')
#define EIMIL_isetag_mark(c) ((c) == '/')
#define EIMIL_isPImark(c) ((c) == '?')
#define EIMIL_isEXmark(c) ((c) == '!')
#define EIMIL_isnewline(c) ((c) == '\n')
#define EIMIL_isEq(c) ((c) == '=')
#define EIMIL_ispresep(c) ((c) == ':')
#define EIMIL_isquote(c) ((c) == '\x27' || (c) == '"')
#define EIMIL_isrefstart(c) ((c) == '&')
#define EIMIL_ischrefmark(c) ((c) == '#')
#define EIMIL_ischrefhexmark(c) ((c) == 'x')
#define EIMIL_ischrefdec(c) (((c) >= '0' && (c) <= '9'))
#define EIMIL_chrefdec(c) ((c) - '0')
#define EIMIL_ischrefhex(c) (((c) >= '0' && (c) <= '9')		\
                             || ((c) >= 'a' && (c) <= 'f')	\
                             || ((c) >= 'A' && (c) <= 'F'))
#define EIMIL_chrefhex(c) (((c) >= '0' && (c) <= '9') ? ((c) - '0') : \
                           ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a') : ((c) - 'A'))
#define EIMIL_isrefend(c) ((c) == ';')

#define EIMIL_UTF32_EBYTE_MAX_FACTOR sizeof(UTF32)

/* This function resolve character and entity references. */
static UTF8*
EIMIL_resolve_reference(
    Ebyte *s,
    Ebyte *e
)
{
    UTF32 ch;
    int size, n;
    UTF8 *ur, *uc;
    Ebyte *c1, *c2;

    ur = uc = (UTF8*) malloc((e - s + 1) * sizeof(UTF8));
    size = 0;
    for (c1 = s; (c1 < e); c1++) {
	if ((EIMIL_isrefstart(*c1))
	    && (EIMIL_ischrefmark(c1[1]))
	    && ((e - c1) > 2)) /* &#X; */ {
	    if (EIMIL_ischrefhexmark(c1[2])) {
		ch = 0;
		for (c2 = c1 + 3; (c2 < e); c2++) {
		    if (EIMIL_isrefend(*c2)) break;
		    if (!EIMIL_ischrefhex(*c2)) break;
		    ch = ch * 16 + EIMIL_chrefhex(*c2);
		    if (ch > 0x10FFFF) goto error_cleanup;
		}
		if (((c2 - c1) > 3) && EIMIL_isrefend(*c2)) {
		    n = EIMIL_convert_UTF32char_to_UTF8(ch, uc);
		    uc += n;
		    size += n;
		} else {
		    goto error_cleanup;
		}
	    } else {
		for (c2 = c1 + 2; (c2 < e); c2++) {
		    if (EIMIL_isrefend(*c2)) break;
		    if (!EIMIL_ischrefdec(*c2)) break;
		    ch = ch * 10 + EIMIL_chrefdec(*c2);
		    if (ch > 0x10FFFF) goto error_cleanup;
		}
		if (((c2 - c1) > 2) && EIMIL_isrefend(*c2)) {
		    n = EIMIL_convert_UTF32char_to_UTF8(ch, uc);
		    uc += n;
		    size += n;
		} else {
		    goto error_cleanup;
		}
	    }
	} else {
	    /* TODO:if we require decoding, do it here!
	       Now we assume Ebyte is UTF8 octet stream.  */
	    size++;
	    *uc++ = *c1;
	}
    }

    size++;
    *uc = 0;
    ur = (UTF8*) realloc(ur, size * sizeof(UTF8));
    return ur;

error_cleanup:
    free(ur);
    return NULL;
}

static void
EIMIL_compute_line(
    EIMIL_parser_state *pps,
    Ebyte *c1,
    int *pline
)
{
    int n = 0;
    Ebyte *c = pps->current;
    if (c < c1) {
	for (; (c < c1); c++) {
	    if (EIMIL_isnewline(*c)) {
		n++;
	    }
	}
    } else {
	while (c > c1) {
	    c--;
	    if (EIMIL_isnewline(*c)) {
		n--;
	    }
	}
    }
    *pline = pps->lineno + n;
}

static void
EIMIL_set_point(
    EIMIL_parser_state *pps,
    Ebyte *c
)
{
    int line;
    EIMIL_compute_line(pps, c, &line);
    pps->current = c;
    pps->lineno = line;
    return;
}

void
EIMIL_set_error(
    EIMIL_data *ped,
    const char* fmt,
    ...
)
{
    va_list va;
    char errbuf[EIMIL_MAXERRMSG];

    va_start(va, fmt);
    vsnprintf(errbuf, sizeof(errbuf), fmt, va);
    snprintf(ped->errstr, sizeof(ped->errstr),
	     "%s\n", errbuf);
    va_end(va);
}

void
EIMIL_set_error_pt(
    EIMIL_data *ped,
    Ebyte *c,
    const char* fmt,
    ...
)
{
    va_list va;
    int lineno;
    EIMIL_parser_state* pps = &ped->pcommon->ps;
    char errbuf[EIMIL_MAXERRMSG];

    if (c)
	EIMIL_compute_line(pps, c, &lineno);
    else
	lineno = pps->lineno;

    va_start(va, fmt);
    vsnprintf(errbuf, sizeof(errbuf), fmt, va);
    snprintf(ped->errstr, sizeof(ped->errstr),
	     "%s (%d)\n", errbuf, lineno);
    va_end(va);
}

void
EIMIL_set_out_of_memory(
    EIMIL_data *ped
)
{
    EIMIL_set_error(ped, "Out of memory.");
}

void
EIMIL_set_EOF_error(
    EIMIL_data *ped,
    Ebyte *e
)
{
    EIMIL_set_error_pt(ped, e, "End of file during parsing.");	\
}

Ebyte*
EIMIL_get_ebyte_token(
    Ebyte *s,
    Ebyte *e
)
{
    Ebyte *ret;

    ret = (Ebyte*) malloc((e - s + 1) * sizeof(Ebyte));
    if (!ret) return NULL;
    memcpy(ret, s, (e - s) * sizeof(Ebyte));
    ret[e - s] = 0;

    return ret;
}

UTF8*
EIMIL_get_UTF8_token(
    Ebyte *s,
    Ebyte *e
)
{
    UTF8 *pret, *p;

    pret = (UTF8*) malloc((e - s + 1) * sizeof(UTF8));
    if (!pret) return NULL;

    for (p = pret; s < e; p++, s++) {
	/* later we maybe need to convert code. */
	*p = *s;
    }
    *p = '\0';

    return pret;
}

#define EIMIL_check_EOF(c, e, ped)	\
do {					\
    if ((c) >= (e)) {			\
	EIMIL_set_EOF_error((ped), (e));\
	goto error_cleanup;		\
    }					\
}while (0)

#define EIMIL_skip_S(c, e, pps)				\
for (; (((c) < (e)) && EIMIL_isspace(*c)); (c)++)

static Ebyte*
EIMIL_match(
    Ebyte *c,
    Ebyte *e,
    char *str
)
{
    Ebyte *c1;
    char *p;
    for (p = str, c1 = c; (c1 < e); c1++, p++) {
	if (!(*p)) return c;
	if (*c1 != *p) break;
    }
    return NULL;
}

static Ebyte*
EIMIL_skip_to(
    Ebyte *c,
    Ebyte *e,
    char *str
)
{
    Ebyte *c1;
    char *p;
    for (; (c < e); c++) {
	for (p = str, c1 = c; (c1 < e); c1++, p++) {
	    if (!(*p)) return c;
	    if (*c1 != *p) break;
	}
    }
    return NULL;
}

/***************************************
            XML namespace
***************************************/
static unsigned char* no_default_namespace_uri = "";
#define EIMIL_XMLNS_INITIALI_SLOTS 16

static EIMIL_XMLNS*
EIMIL_expand_namespace_slot(
    EIMIL_parser_state *pps
)
{
    if (pps->xmlns_alloced <= pps->xmlns_entries) {
	int na;
	EIMIL_XMLNS *pns = pps->pxmlns;
	if (pps->xmlns_alloced == 0) {
	    na = EIMIL_XMLNS_INITIALI_SLOTS;
	    pns = (EIMIL_XMLNS*) malloc(na * sizeof(EIMIL_XMLNS));
	} else {
	    na = pps->xmlns_alloced * 2;
	    pns = (EIMIL_XMLNS*) realloc(pps->pxmlns,
					 na * sizeof(EIMIL_XMLNS));
	}
	if (!pns) return NULL;
	pps->xmlns_alloced = na;
	pps->pxmlns = pns;
    }

    return pps->pxmlns + pps->xmlns_entries;
}

static unsigned char*
EIMIL_get_prefix_namespace(
    EIMIL_parser_state *pps,
    unsigned char *prefix
)
{
    int i;
    int n = pps->xmlns_entries;
    EIMIL_XMLNS *pns = pps->pxmlns + n;

    for (i = 0; i < n; i++) {
	pns--;
	if (!pns->uri) continue;
	if ((prefix == pns->prefix)
	    || (prefix && (strcmp(prefix, pns->prefix) == 0))) {
	    if (pns->uri == no_default_namespace_uri) return NULL;
	    return pns->uri;
	}
    }
    return NULL;
}

static int
EIMIL_set_prefix_namespace(
    EIMIL_parser_state *pps,
    unsigned char *prefix,
    unsigned char *uri
)
{
    int i;
    int n = pps->xmlns_entries;
    EIMIL_XMLNS *pns = pps->pxmlns + n;

    for (i = 0; i < n; i++, pns--) {
	if (!pns->uri) {
	    i = n;
	    break;
	}
	if (prefix == pns->prefix) {
	    break;
	} else if (prefix && (strcmp(prefix, pns->prefix) == 0)) {
	    break;
	}
    }
    if (i == n) {
	pns = EIMIL_expand_namespace_slot(pps);
	if (!pns) return 0;
	pns->prefix = prefix;
	pps->xmlns_entries++;
    } else if ((pns->uri) && (pns->uri != no_default_namespace_uri)) {
	free(pns->uri);
    }

    if (*uri == '\0') {
	pns->uri = no_default_namespace_uri;
    } else {
	pns->uri = uri;
    }
    return 1;
}

static int
EIMIL_namespace_newbind(
    EIMIL_parser_state *pps
)
{
    EIMIL_XMLNS *pns;
    pns = EIMIL_expand_namespace_slot(pps);
    if (!pns) return 0;
    pps->xmlns_entries++;
    pns->prefix = NULL;
    pns->uri = NULL;

    return 1;
}

int
EIMIL_namespace_unbind(
    EIMIL_parser_state *pps
)
{
    int i;
    int n = pps->xmlns_entries;
    EIMIL_XMLNS *pns = pps->pxmlns + n;

    for (i = 1; i <= n; i++) {
	pns--;
	if (!pns->uri) break;
	if (pns->prefix) free(pns->prefix);
	if ((pns->uri) && (pns->uri != no_default_namespace_uri))
	    free(pns->uri);
    }
    pps->xmlns_entries -= i;
    return 1;
}

/***************************************
	      name
***************************************/

static Ebyte*
EIMIL_get_name(
    EIMIL_data *ped,
    Ebyte *c,
    Ebyte *e,
    UTF8 **pname,
    UTF8 **pprefix
)
{
#if FAMAO
    EIMIL_parser_state *pps = &ped->pcommon->ps;
#endif
    Ebyte *c1;

    *pprefix = *pname = NULL;
    c1 = c;
    while ((e > c)
	   && !EIMIL_isspace(*c)
	   && !EIMIL_ispresep(*c)
	   && !EIMIL_istag_e(*c)
	   && !EIMIL_isetag_mark(*c)) {
	c++;
    }
    if (e <= c) return NULL;
    if (EIMIL_ispresep(*c)) {
	*pprefix = EIMIL_get_UTF8_token(c1, c);
	c1 = c;
	while ((e > c)
	       && !EIMIL_isspace(*c)
	       && !EIMIL_istag_e(*c)
	       && !EIMIL_isetag_mark(*c)) {
	    c++;
	}
	if (e <= c) return NULL;
    }
    *pname = EIMIL_get_UTF8_token(c1, c);

    return c;
}

/***************************************
	      attribute
***************************************/

static void
EIMIL_free_attrs(
    EIMIL_attrs *patr
)
{
    EIMIL_attrs *pa;

    for (pa = patr;pa->name;pa++) {
	free(pa->name);
	free(pa->val);
    }
    free(patr);
}

static int
EIMIL_check_attrs(
    EIMIL_data *ped,
    EIMIL_attr_template *pat,
    EIMIL_attrs **ppatr
)
{
    int i, num;
    int eflag = 0;
#if FAMAO
    EIMIL_parser_state *pps = &ped->pcommon->ps;
#endif
    EIMIL_attrs *pa;

    if (*ppatr)
	for (num = 0, pa = *ppatr; pa->name; pa++) num++;
    else
	num = 0;
    if (!pat) return 1;
    for (; pat->name; pat++) {
	eflag = 0;
	for (pa = *ppatr, i = 0; i < num; pa++, i++) {
	    if (strcmp(pat->name, pa->name) == 0) {
		if (eflag) {
		    EIMIL_set_error_pt(ped, NULL,
				       "Duplicated attribute:%s",
				       pa->name);
		    return 0;
		}
		if ((pat->type == EIMIL_attr_FIXED)
		    && (strcmp(pat->default_value, pa->val) != 0)) {
		    EIMIL_set_error_pt(ped, NULL, "attribute:%s must be %s",
				       pat->name, pat->default_value);
		    return 0;
		}
		eflag = 1;
	    }
	}
	if (!eflag && pat->default_value) {
	    if (pat->type == EIMIL_attr_REQUIRED) {
		EIMIL_set_error_pt(ped, NULL, "attribute:%s is missing",
				   pat->name);
		return 0;
	    }
	    num++;
	    *ppatr = pa = (EIMIL_attrs*) realloc(*ppatr,
						 ((num + 1)
						  * sizeof(EIMIL_attrs)));
	    pa[num].name = NULL;
	    pa[num].val = NULL;
	    pa[num - 1].name = strdup(pat->name);
	    pa[num - 1].val = (Ebyte*) strdup(pat->default_value);
	}
    }
    return 1;
}

static EIMIL_attrs*
EIMIL_parse_attrs(
    EIMIL_data *ped
)
{
    EIMIL_parser_state *pps = &ped->pcommon->ps;
    Ebyte *c = pps->current;
    Ebyte *e = pps->end;
    Ebyte *c1;
    int atrnum = 0;
    EIMIL_attrs *patr_s = NULL;
    UTF8 *p, *prefix;
    UTF8 *name = NULL;
    Ebyte *val;

    while (e > c) {
	EIMIL_skip_S(c, e, pps);
	EIMIL_check_EOF(c, e, ped);
	if (EIMIL_istag_e(*c) || EIMIL_isetag_mark(*c)) break;
	/* ATTR S? = S? VAL */
	c1 = c;
	while ((e > c) && !EIMIL_isspace(*c) && !EIMIL_isEq(*c)) c++;
	EIMIL_check_EOF(c, e, ped);
	name = EIMIL_get_UTF8_token(c1, c);

	if (!name) goto error_cleanup;
	EIMIL_skip_S(c, e, pps);
	EIMIL_check_EOF(c, e, ped);
	if (!EIMIL_isEq(*c)) {
	    EIMIL_set_error_pt(ped, c, "Attribute format is invalid");
	    goto error_cleanup;
	}
	c++;
	EIMIL_check_EOF(c, e, ped);
	EIMIL_skip_S(c, e, pps);
	EIMIL_check_EOF(c, e, ped);
	{
	    /* cut attribute value out. */
	    c1 = c;
	    if (!EIMIL_isquote(*c1)) {
		EIMIL_set_error_pt(ped, c, "Attribute value must be enclosed with quote");
		goto error_cleanup;
	    }
	    c++;
	    while ((e > c) && (*c1 != *c)) c++;
	    EIMIL_check_EOF(c, e, ped);
	    val = EIMIL_get_ebyte_token(c1 + 1, c);
	    c++;
	}

	/* XML namespace check. */
	if ((p = strchr(name, ':')) != NULL) {
	    prefix = name;
	    *p = '\0';
	    p++;
	} else {
	    prefix = NULL;
	    p = name;
	}
	if (strcmp(p, "xmlns") == 0) {
	    EIMIL_set_prefix_namespace(pps, prefix, val);
	    continue;
	}
	if (prefix) {
	    /* EIMIL never use any attributes in the global paritition.  */
	    free(name);
	    free(val);
	    continue;
	}

	atrnum++;
	patr_s = (EIMIL_attrs*) realloc(patr_s, (atrnum + 1) * sizeof(EIMIL_attrs));
	patr_s[atrnum].name = NULL;
	patr_s[atrnum].val = NULL;
	patr_s[atrnum - 1].name = name;
	patr_s[atrnum - 1].val = val;
    }

    EIMIL_set_point(pps, c);

    return patr_s;

error_cleanup:
    if (patr_s) EIMIL_free_attrs(patr_s);
    return NULL;
}

void
EIMIL_remove_attr(
    EIMIL_attrs *patr
)
{
    EIMIL_attrs *p;

    ASSERT(patr->name);
    for (p = patr + 1; p->name; patr++, p++) *patr = *p;
    *patr = *p;
}

Ebyte*
EIMIL_get_attr_cdata(
    Ebyte *val,
    UTF8 **result
)
{
    Ebyte *e;

    for (e = val; *e; e++);

    if (result) {
	*result = EIMIL_resolve_reference(val, e);
	if (!(*result)) return NULL;
    }

    return e;
}

Ebyte*
EIMIL_get_attr_nmtoken(
    Ebyte *val,
    UTF8 **result
)
{
    Ebyte *s, *e, *p;

    for (s = val; (*s && EIMIL_isspace(*s)); s++);
    if (!(*s)) return NULL;
    for (e = s; (*e && !EIMIL_isspace(*e)); e++);
    for (p = e; *p; p++) {
	if (!EIMIL_isspace(*p)) return NULL;
    }

    if (result) {
	*result = EIMIL_resolve_reference(s, e);
	if (!(*result)) return NULL;
    }

    return e;
}

/*
  "  AAA   BBBB CCC  \0"  ==> "   BBBB CCC  \0", result == "AAA"
 */
Ebyte*
EIMIL_get_attr_nmtokens(
    Ebyte *val,
    UTF8 **result
)
{
    Ebyte *s, *e;

    for (s = val; (*s && EIMIL_isspace(*s)); s++);
    if (!(*s)) return NULL;
    for (e = s; (*e && !EIMIL_isspace(*e)); e++);

    if (result) {
	*result = EIMIL_resolve_reference(s, e);
	if (!(*result)) return NULL;
    }

    return e;
}

static enum EIMIL_TYPE
EIMIL_get_type_from_attrs(
    EIMIL_data *ped,
    EIMIL_attrs *patr
)
{
#if FAMAO
    EIMIL_parser_state *pps = &ped->pcommon->ps;
#endif
    UTF8 *name;
    enum EIMIL_TYPE type;

    for (; patr->name; patr++) {
	if (strcmp(patr->name, "type") == 0) {
	    if (!EIMIL_get_attr_nmtoken(patr->val, &name)) {
		type = EIMIL_TYPE_INVALID;
		break;
	    }
	    if (strcmp(name, "bool") == 0) type = EIMIL_TYPE_BOOL;
	    else if (strcmp(name, "number") == 0) type = EIMIL_TYPE_NUMBER;
	    else if (strcmp(name, "char") == 0) type = EIMIL_TYPE_CHAR;
	    else if (strcmp(name, "mtext") == 0) type = EIMIL_TYPE_MTEXT;
	    else type = EIMIL_TYPE_INVALID;
	    free(name);
	    free(patr->name);
	    free(patr->val);
	    EIMIL_remove_attr(patr);
	    break;
	}
    }
    if (type == EIMIL_TYPE_INVALID) {
	EIMIL_set_error_pt(ped, NULL,
			   "`type' attribute must be `bool', `number', `char', or `mtext'.");
    }
    return type;
}

static UTF8*
EIMIL_get_UTF8data_token(
    EIMIL_data *ped,
    int option
)
{
    EIMIL_parser_state *pps = &ped->pcommon->ps;
    Ebyte *c, *c1, *e;
    UTF8 *pu;
    c = pps->current;
    e = pps->end;
    
    if (option & EIMIL_PCDATA_TOKEN) {
	EIMIL_skip_S(c, e, pps);
	c1 = c;
	for (; c < e; c++) {
	    if (EIMIL_isspace(*c) || EIMIL_istag_s(*c)) break;
	}
    } else if (option & EIMIL_PCDATA_QUOTED_TOKEN) {
	Ebyte q;

	EIMIL_skip_S(c, e, pps);
	c1 = c;
	q = *c;
	if (EIMIL_isquote(q)) {
	    c++;
	    for (; c < e; c++) {
		if (*c == q) break;
	    }
	    c++;
	    if (c >= e) {
		EIMIL_set_error_pt(ped, c1,
				   "Corresponding quotation mark is missing.");
		return NULL;
	    }
	} else {
	    for (; c < e; c++) {
		if (EIMIL_isspace(*c) || EIMIL_istag_s(*c) || EIMIL_isquote(*c)) break;
	    }
	}
    } else {
	c1 = c;
	for (; c < e; c++) {
	    if (EIMIL_istag_s(*c)) break;
	}
    }
    EIMIL_set_point(pps, c);
    pu = EIMIL_resolve_reference(c1, c);
    return pu;
}

static int
EIMIL_match_name(
    EIMIL_parser_state *pps,
    char *name
)
{
    Ebyte *c, *e;
    c = pps->current;
    e = pps->end;

    while (c < e) {
	if (!*name) return 1;
	if (*name != *c) return 0;
	name++;
	c++;
    }

    return 0;
}

/*
  Find out the next token.
  Return the point of the tag if it is found.
  Never move the points of pps.
*/
static Ebyte*
EIMIL_next_token(
    EIMIL_data *ped
)
{
    EIMIL_parser_state *pps = &ped->pcommon->ps;
    Ebyte *c, *e;
    c = pps->current;
    e = pps->end;

    for (; c < e; c++) {
	if (!EIMIL_isspace(*c)) return c;
    }

    return NULL;
}

/*
  Find out the next tag.
  Return the point of the tag if it is found.
  Never move the points of pps.
*/
static Ebyte*
EIMIL_next_tag(
    EIMIL_data *ped
)
{
    EIMIL_parser_state *pps = &ped->pcommon->ps;
    Ebyte *c, *e;
    c = pps->current;
    e = pps->end;

    for (; c < e; c++) {
	if (EIMIL_istag_s(*c)) return c;
    }

    return NULL;
}

/*
  Parse the current tag where pps->current points.
  tag's end must be in [pps->current, pps->end).
  Move the pps->current to the end of tag.
*/
static enum EIMIL_TAG_TYPE
EIMIL_parse_tag(
    EIMIL_data *ped,
    UTF8 **puri,
    UTF8 **pname,
    EIMIL_attrs **ppattrs
)
{
    EIMIL_parser_state *pps = &ped->pcommon->ps;
    Ebyte *c, *c1, *e;
    UTF8 *prefix = NULL;
    EIMIL_attrs *pat = NULL;
    enum EIMIL_TAG_TYPE tt = EIMIL_INVALID_TAG;

    *puri = *pname = NULL;
    *ppattrs = NULL;
    c = pps->current;
    e = pps->end;
    /*
      <?NAME <anychar>?>
      or 
      <!DOCTYPE <anychar>>
      or
      <![CDATA[ <anychar>]]>
      or
      <!-- <anychar> -->
      or 
      <TAG-NAME ATTRS S>
      or
      <TAG-NAME ATTRS S/>
      or
      </TAG-NAME S>
    */
    if (!EIMIL_istag_s(*c)) return EIMIL_CHARDATA;
    c++;
    EIMIL_check_EOF(c, e, ped);
    if (EIMIL_isPImark(*c)) {
	/* <?NAME <anychar>?> */
	c = EIMIL_skip_to(c, e, "?>");
	EIMIL_set_point(pps, c + 2);
	return EIMIL_PI_TAG;
    } else if (EIMIL_isEXmark(*c)) {
	if ((c1 = EIMIL_match(c, e, "!--")) != NULL) {
	    c = EIMIL_skip_to(c1, e, "--");
	    c += 2;
	    if (!EIMIL_istag_e(*c)) {
		EIMIL_set_error_pt(ped, c, "`--' must not occur within comments.");
		return EIMIL_INVALID_TAG;
	    }
	    c++;
	    EIMIL_check_EOF(c, e, ped);
	    EIMIL_set_point(pps, c);
	    return EIMIL_COMMENT_TAG;
	}
	if ((c1 = EIMIL_match(c, e, "!DOCTYPE")) != NULL) {
	    c = EIMIL_skip_to(c1, e, ">");
	    EIMIL_set_point(pps, c + 1);
	    return EIMIL_DOCTYPE_TAG;
	}
	EIMIL_check_EOF(c1, e, ped);
	if ((c1 = EIMIL_match(c, e, "![CDATA[")) != NULL) {
	    c = EIMIL_skip_to(c1, e, "]]>");
	    EIMIL_set_point(pps, c + 3);
	    return EIMIL_CDATA_TAG;
	}
	EIMIL_check_EOF(c1, e, ped);
	EIMIL_set_error_pt(ped, c, "Unknown token.  Normal tags must not start with `!'.");
    } else if (EIMIL_isetag_mark(*c)) {
	/* </TAG-NAME> */
	tt = EIMIL_END_TAG;
	c++;
	if (!(c = EIMIL_get_name(ped, c, e, pname, &prefix))) {
	    EIMIL_check_EOF(c, e, ped);
	    goto error_cleanup;
	}
	EIMIL_skip_S(c, e, pps);
    } else {
	EIMIL_namespace_newbind(pps);
	if (!(c = EIMIL_get_name(ped, c, e, pname, &prefix))) {
	    EIMIL_check_EOF(c, e, ped);
	    goto error_cleanup;
	}
	if (EIMIL_isspace(*c)) {
	    EIMIL_set_point(pps, c);
	    pat = EIMIL_parse_attrs(ped);
	    c = pps->current;
	}
	EIMIL_skip_S(c, e, pps);
	EIMIL_check_EOF(c, e, ped);
	if (EIMIL_isetag_mark(*c)) {
	    tt = EIMIL_EMPTY_TAG;
	    /* <TAG-NAME ATTRS S/> */
	    c++;
	} else {
	    tt = EIMIL_START_TAG;
	}
    }
    if (!EIMIL_istag_e(*c)) goto error_cleanup;

    *puri = EIMIL_get_prefix_namespace(pps, prefix);
    if (prefix && !(*puri)) {
	EIMIL_set_error_pt(ped, c, "name is not belong to any namespace.");
	goto error_cleanup;
    }

    if (prefix) free(prefix);
    *ppattrs = pat;
    EIMIL_set_point(pps, c + 1);

    return tt;

error_cleanup:
    if (prefix) free(prefix);
    if (*pname) free(*pname);
    *pname = *puri = NULL;
    *ppattrs = NULL;
    if (pat) {
	EIMIL_free_attrs(pat);
    }

    return EIMIL_INVALID_TAG;
}

/*
  Find out the element in contents, and parse it.
  pps->current is always reset to the pps->start at the startup.
 */
static int
EIMIL_parse_element(
    EIMIL_data *ped,
    EIMIL_element_template *pet,
    EIMIL_element_template *pet_current,
    void* private,
    UTF8 *current_element_name,
    UTF8 *current_element_uri
)
{
    EIMIL_parser_state *pps = &ped->pcommon->ps;
    UTF8 *name, *uri;
    const UTF8 *saved_uri;
    enum EIMIL_TAG_TYPE tt;
    int i, n;
    int option;
    int *pelemnums;
    void *saved_private;
    Ebyte *s, *c, *e;
    Ebyte *tag_end;
    EIMIL_attrs *pat = NULL;
    EIMIL_element_template *pet2;

    s = pps->start;
    e = pps->end;

    EIMIL_set_point(pps, s);

    if (pet_current)
	option = pet_current->option;
    else
	option = 0;

    if (pps->element_depth > MAX_ELEMENT_DEPTH) {
	EIMIL_set_error_pt(ped, NULL, "The limit of element depth is exceeded.");
	return 0;
    }

    /* count the number of pet */
    if (pet) {
	for (n = 0, pet2 = pet; pet2->name; pet2++) n++;
    } else {
	n = 0;
    }

    pelemnums = (int*) alloca(sizeof(int) * n);
    memset(pelemnums, 0, sizeof(int) * n);
    pet2 = pet;

    name = NULL;
    i = 0;
    while ((c = EIMIL_next_token(ped)) != NULL) {

	EIMIL_set_point(pps, c);
	if (name) free(name);
	tt = EIMIL_parse_tag(ped, &uri, &name, &pat);
	tag_end = pps->current;
	EIMIL_set_point(pps, c);

	/* 
	   At this point,
	   <TAG> <-- `tag_end' points
	   ^-- `pps->current' points
	*/

	if (tt == EIMIL_END_TAG) {
	    if (current_element_name
		&& (strcmp(name, current_element_name) == 0)
		&& ((uri == current_element_uri)
		    || (uri && current_element_uri
			&& (strcmp(uri, current_element_uri) == 0)))) {
		EIMIL_namespace_unbind(pps);
		e = tag_end;
		break;
	    }
	    EIMIL_set_error_pt(ped, NULL, "Unbalanced end tag:%s, %s.",
			       name, current_element_name);
	    goto error_cleanup;
	} else if ((tt == EIMIL_PI_TAG)
		   || (tt == EIMIL_COMMENT_TAG)
		   || (tt == EIMIL_DOCTYPE_TAG)
		   || (tt == EIMIL_CDATA_TAG)) {
	    pps->start = tag_end;
	    EIMIL_set_point(pps, tag_end);
	    continue;
	} else if (tt == EIMIL_CHARDATA) {
	    if (pet_current && (!(pet_current->option & EIMIL_allow_PCDATA))) {
		EIMIL_set_error_pt(ped, NULL, "Invalid character token.");
		goto error_cleanup;
	    }
	    if (pet_current && pet_current->func) {
		UTF8 *pu = EIMIL_get_UTF8data_token(ped, pet_current->option);
		if (!pu) goto error_cleanup;
		if (!((*pet_current->func)(ped, NULL, tt, pu, &private)))
		    goto error_cleanup;
		free(pu);
	    } else {
		c = EIMIL_next_tag(ped);
		if (!c) {
		    EIMIL_set_error_pt(ped, NULL,
				       "Cannot find out any start tag while parsing chardata.");
		    goto error_cleanup;
		}
		EIMIL_set_point(pps, c);
	    }
	    continue;
	} else if (tt == EIMIL_INVALID_TAG) {
	    goto error_cleanup;
	}

	if ((uri) && (strcmp(uri, pps->current_uri) == 0)) {
	    if (pet2) {
		if ((i > 0)
		    && (pet2->option & EIMIL_element_lock_template)) {
		    /* When EIMIL_element_lock_template is set,
		       other we don't check any other template than pet2.  */
		    if (strcmp(pet2->name, name) != 0) {
			EIMIL_set_error_pt(ped, NULL, "%s must not be after %s.",
					   name, pet2->name);
			if (pat) EIMIL_free_attrs(pat);
			goto error_cleanup;
		    }
		} else {
		    if (!(option & EIMIL_subelement_ordered)) pet2 = pet;
		    for (; pet2->name; pet2++) {
			if (strcmp(pet2->name, name) == 0) break;
		    }
		}
	    }
	    if (!pet2 || !pet2->name) {
		EIMIL_set_error_pt(ped, NULL, "Unknown tag:%s.", name);
		if (pat) EIMIL_free_attrs(pat);
		goto error_cleanup;
	    }
	    if (pet2->attrtpls) {
		if (!EIMIL_check_attrs(ped, pet2->attrtpls, &pat)) {
		    goto error_cleanup;
		}
	    }
	    i++;
	    pelemnums[pet2 - pet]++;
	    EIMIL_SET_CURRENT_SUBELEMENT_TEMPLATE(pps, pet2->subelems);
	    saved_uri = pps->current_uri;
	    if (pet2->func) {
		pps->pcet = pet2;
		pps->element_idx = i;
		saved_private = private;
		if (!((*pet2->func)(ped, pat, tt, NULL, &private))) {
		    goto error_cleanup;
		}
	    }
	    if (tt == EIMIL_EMPTY_TAG) {
		EIMIL_namespace_unbind(pps);
		if (!(pet2->option & EIMIL_element_EMPTY)) {
		    EIMIL_set_error_pt(ped, NULL, "Tag %s must not be an empty element tag",
				       name);
		    goto error_cleanup;
		}
		c = tag_end;
	    } else {
		int status;

		if (pet2->option & EIMIL_element_EMPTY) {
		    EIMIL_set_error_pt(ped, NULL, "Tag %s must be an empty element tag",
				       name);
		    goto error_cleanup;
		}

		pps->start = tag_end;
		status = EIMIL_parse_element(ped, pps->psubet, pet2,
					     private, name, uri);
		/* Now `c' points the end of element.  */
		c = pps->end;
		if (status != -1) {
		    if (status)
			EIMIL_set_error_pt(ped, tag_end,
					   "End tag is missing:%s.",
					   name);
		    goto error_cleanup;
		}
		if (pet2->func) {
		    pps->start = tag_end;
		    pps->end = pps->current;
		    pps->element_idx = i;
		    pps->pcet = pet2;
		    if (!((*pet2->func)(ped, pat, EIMIL_END_TAG, NULL, &private))) {
			goto error_cleanup;
		    }
		    private = saved_private;
		}
	    }
	    EIMIL_SET_CURRENT_SUBELEMENT_TEMPLATE(pps, pet2->subelems);
	    pps->current_uri = saved_uri;
	} else {
	    int status;
	    /* Ignore the element */
	    if (pat) EIMIL_free_attrs(pat);
	    if (tt == EIMIL_EMPTY_TAG) {
		EIMIL_namespace_unbind(pps);
		c = tag_end;
	    } else {
		/* Skip to the end tag.  */
		pps->start = tag_end;
		status = EIMIL_parse_element(ped, pet, NULL,
					     private, name, uri);
		/* Now `c' points the end of element.  */
		c = pps->end;
		if (status != -1) {
		    if (status)
			EIMIL_set_error_pt(ped, tag_end,
					   "End tag is mising:%s.",
					   name);
		    goto error_cleanup;
		}
	    }
	}
	pps->start = c;
	EIMIL_set_point(pps, c);
	pps->end = e;
    }
    if (name) free(name);
    name = NULL;

    pps->start = s;
    pps->end = e;

    if (pet) {
	/* Check the number of elements.  */
	int opt;
	int *pn;
	for (pet2 = pet, pn = pelemnums; pet2->name; pet2++, pn++) {
	    opt = (pet2->option | EIMIL_element_option_mask);
	    if ((opt == EIMIL_element_single) && (*pn != 1)) {
		EIMIL_set_error_pt(ped, NULL, "element:%s must be only 1.",
				   pet2->name);
		return 0;
	    } else if ((opt == EIMIL_element_morethan1) && (*pn < 1)) {
		EIMIL_set_error_pt(ped, NULL, "element:%s must be more than 1.",
				   pet2->name);
		return 0;
	    } else if ((opt == EIMIL_element_0or1) && (*pn > 1)) {
		EIMIL_set_error_pt(ped, NULL, "element:%s must be 0 or 1.",
				   pet2->name);
		return 0;
	    }
	}
    }

    if (tt == EIMIL_END_TAG) return -1;

    return 1;

error_cleanup:
    if (name) free(name);
    return 0;
}

/********************************************************************************
			EIMIL value related services.
 ********************************************************************************/

EIMIL_value*
EIMIL_construct_number(
    int number
)
{
    EIMIL_value *pv;
    pv = (EIMIL_value*) malloc(sizeof(EIMIL_value));
    if (!pv) return NULL;
    pv->type = EIMIL_TYPE_NUMBER;
    pv->v.number = number;
    pv->refcount = 0;
    return pv;
}

EIMIL_value*
EIMIL_construct_bool(
    int bool_val
)
{
    if (!bool_val) return NULL;
    return &EIMIL_t_val;
}

EIMIL_value*
EIMIL_construct_char(
    UTF32 ch
)
{
    EIMIL_value *pv;
    pv = (EIMIL_value*) malloc(sizeof(EIMIL_value));
    if (!pv) return NULL;
    pv->type = EIMIL_TYPE_CHAR;
    pv->v.ch = ch;
    pv->refcount = 0;
    return pv;
}

EIMIL_value*
EIMIL_construct_event(
    UTF8 *type,
    EIMIL_value *pv_val,
    EIMIL_value *pv_mod,
    EIMIL_value *pv_char,
    EIMIL_value *pv_mtext
)
{
    EIMIL_value *pv;
    ASSERT(!pv_val || pv_val->type == EIMIL_TYPE_NUMBER);
    ASSERT(!pv_mod || pv_mod->type == EIMIL_TYPE_NUMBER);
    ASSERT(!pv_char || pv_char->type == EIMIL_TYPE_CHAR);
    ASSERT(!pv_mtext || pv_mtext->type == EIMIL_TYPE_MTEXT);
    pv = (EIMIL_value*) malloc(sizeof(EIMIL_value));
    if (!pv) return NULL;
    pv->type = EIMIL_TYPE_EVENT;

    pv->v.event.type = strdup(type);
    pv->v.event.pv_val = pv_val;
    if (pv_val) EIMIL_ADDREF(*pv_val);
    pv->v.event.pv_mod = pv_mod;
    if (pv_mod) EIMIL_ADDREF(*pv_mod);
    pv->v.event.pv_char = pv_char;
    if (pv_char) EIMIL_ADDREF(*pv_char);
    pv->v.event.pv_mtext = pv_mtext;
    if (pv_mtext) EIMIL_ADDREF(*pv_mtext);
    pv->refcount = 0;

    return pv;
}

int
EIMIL_construct_events_from_IMInputEvent(
    IMInputEvent *pimev,
    EIMIL_value ***pppevs
)
{
    EIMIL_value *pev;

    switch (pimev->type) {
      case IM_EventKeyList:
      {
	  IMKeyListEvent *pimkev = (IMKeyListEvent*) pimev;
	  IMKeyList keylist = pimkev->keylist;
	  int i, n = pimkev->n_key;
	  EIMIL_value **ppevs;
	  EIMIL_value *pev_code, *pev_char, *pev_mod;

	  ppevs = (EIMIL_value**) malloc(sizeof(EIMIL_value*) * n);
	  if (!ppevs) return 0;

	  for (i = 0; i < n; i++) {
	      pev_code = EIMIL_construct_number(keylist[i].keyCode);
	      if (!pev_code) return 0;
	      pev_char = EIMIL_construct_char(keylist[i].keyChar);
	      if (!pev_char) return 0;
	      pev_mod = EIMIL_construct_number(keylist[i].modifier);
	      if (!pev_mod) return 0;
	      pev = EIMIL_construct_event("keyevent",
					  pev_code, pev_char, pev_mod,
					  NULL);
	      if (!pev) return 0;
	      ppevs[i] = pev;
	  }
	  *pppevs = ppevs;
	  return n;
      }
    }

    return 0;
}

IMInputEvent*
EIMIL_convert_event_to_IMInputEvent(
    EIMIL_event *pev
)
{
    if (strcmp(pev->type, "keyevent") == 0) {
	IMKeyListEvent *pimkev;
	IMKeyList keylist;

	pimkev = (IMKeyListEvent*) malloc(sizeof(IMKeyListEvent));
	if (!pimkev) return NULL;
	memset(pimkev, 0, sizeof(IMKeyListEvent));
	keylist = (IMKeyList) malloc(sizeof(IMKeyEventStruct));
	if (!keylist) return NULL;
	memset(keylist, 0, sizeof(IMKeyEventStruct));

	pimkev->type = IM_EventKeyList;
	pimkev->n_key = 1;
	pimkev->keylist = keylist;
	if (pev->pv_val) {
	    keylist->keyCode = pev->pv_val->v.number;
	    keylist->keyChar = pev->pv_char->v.ch;
	    keylist->modifier = pev->pv_mod->v.number;
	}

	return (IMInputEvent*) pimkev;
    }

    return NULL;
}

EIMIL_value*
EIMIL_construct_prop(
    EIMIL_symbol *psym
)
{
    EIMIL_value *pv;
    EIMIL_prop *pprop;

    ASSERT(psym->cat == EIMIL_CAT_PROPERTY);

    pv = (EIMIL_value*) malloc(sizeof(EIMIL_value));
    if (!pv) return NULL;
    pv->type = EIMIL_TYPE_PROP;
    pprop = &pv->v.prop;
    memset(pprop, 0, sizeof(EIMIL_prop));
    pprop->st = -1;
    pprop->end = -1;
    pprop->property_sym = psym;
    pprop->type = psym->obj.p.type;
    pv->refcount = 0;
    return pv;
}

EIMIL_value*
EIMIL_construct_prop2(
    enum EIMIL_TYPE type
)
{
    EIMIL_value *pv;
    EIMIL_prop *pprop;
    pv = (EIMIL_value*) malloc(sizeof(EIMIL_value));
    if (!pv) return NULL;
    pv->type = EIMIL_TYPE_PROP;
    pprop = &pv->v.prop;
    memset(pprop, 0, sizeof(EIMIL_prop));
    pprop->st = -1;
    pprop->end = -1;
    pprop->type = type;
    pv->refcount = 0;
    return pv;
}

int
EIMIL_add_prop(
    EIMIL_prop *pprop,
    EIMIL_value *pv
)
{
    EIMIL_value **ppv;

    ASSERT((!pv) || (pv->type == pprop->type));

    ppv = pprop->pvals;
    ppv = (EIMIL_value**) realloc(ppv, sizeof(EIMIL_value*) * (pprop->size + 1));
    if (!ppv) return 0;
    pprop->pvals = ppv;
    if (pv) EIMIL_ADDREF(*pv);
    ppv[pprop->size] = pv;
    pprop->size++;

    return 1;
}

int
EIMIL_delete_prop(
    EIMIL_prop *pprop,
    int idx
)
{
    EIMIL_value **ppv;

    if ((idx < 0) || (idx >= pprop->size)) return 0;
    ppv = pprop->pvals + idx;
    EIMIL_RMREF(**ppv);
    if ((pprop->size - idx - 1) > 0)
	memmove(ppv, ppv + 1, sizeof(EIMIL_value*) * (pprop->size - idx - 1));
    pprop->size--;

    return 1;
}

void
EIMIL_destruct_value(
    EIMIL_value *pv
)
{
    if (!pv) return;
    switch(pv->type) {
      case EIMIL_TYPE_PROP:
      {
	  int i;
	  EIMIL_value **ppv;

	  EIMIL_detach_prop_from_mtext(pv);
	  for (ppv = pv->v.prop.pvals, i = 0;
	       i < pv->v.prop.size;
	       ppv++, i++) {
	      EIMIL_RMREF(**ppv);
	  }
	  if (pv->v.prop.pvals) free(pv->v.prop.pvals);
	  break;
      }
      case EIMIL_TYPE_EVENT:
       if (pv->v.event.type) free(pv->v.event.type);
       if (pv->v.event.pv_val) EIMIL_RMREF(*pv->v.event.pv_val);
       if (pv->v.event.pv_mod) EIMIL_RMREF(*pv->v.event.pv_mod);
       if (pv->v.event.pv_char) EIMIL_RMREF(*pv->v.event.pv_char);
       if (pv->v.event.pv_mtext) EIMIL_RMREF(*pv->v.event.pv_mtext);
       break;
      case EIMIL_TYPE_MTEXT:
       EIMIL_destruct_mtext(&pv->v.mtext);
       break;
      default:
       break;
    }

    free(pv);
}

EIMIL_value*
EIMIL_copy_value(
    EIMIL_value *pv
)
{
    EIMIL_value *pv2;

    if (pv->type == EIMIL_TYPE_BOOL) {
	ASSERT(pv == &EIMIL_t_val);
	return pv;
    }

    pv2 = (EIMIL_value*) malloc(sizeof(EIMIL_value));
    memset(pv2, 0, sizeof(EIMIL_value));
    if (!pv2) return NULL;
    pv2->type = pv->type;
    pv2->refcount = 0;

    switch(pv->type) {
      case EIMIL_TYPE_NUMBER:
       pv2->v.number = pv->v.number;
       break;

      case EIMIL_TYPE_CHAR:
       pv2->v.ch = pv->v.ch;
       break;

      case EIMIL_TYPE_PROP:
      {
	  int i;
	  EIMIL_value *pvtmp;
	  pv2->v.prop = pv->v.prop;
	  pv2->v.prop.pvals = (EIMIL_value**) malloc(sizeof(EIMIL_value*)
						     * pv->v.prop.size);
	  if (!pv2->v.prop.pvals) return NULL;
	  for (i = 0; i < pv->v.prop.size; i++) {
	      pvtmp = pv->v.prop.pvals[i];
	      EIMIL_ADDREF(*pvtmp);
	      pv2->v.prop.pvals[i] = pvtmp;
	  }
	  break;
      }
      case EIMIL_TYPE_EVENT:
       pv2->v.event.type = strdup(pv->v.event.type);
       if (!pv2->v.event.type) {
	   free(pv2);
	   return NULL;
       }
       if (pv->v.event.pv_val) {
	   pv2->v.event.pv_val = EIMIL_copy_value(pv->v.event.pv_val);
	   if (!pv2->v.event.pv_val) {
	       free(pv2->v.event.type);
	       free(pv2);
	       return NULL;
	   }
       }
       if (pv->v.event.pv_mod) {
	   pv2->v.event.pv_mod = EIMIL_copy_value(pv->v.event.pv_mod);
	   if (!pv2->v.event.pv_mod) {
	       EIMIL_destruct_value(pv->v.event.pv_val);
	       free(pv2->v.event.type);
	       free(pv2);
	       return NULL;
	   }
       }
       if (pv->v.event.pv_char) {
	   pv2->v.event.pv_char = EIMIL_copy_value(pv->v.event.pv_char);
	   if (!pv2->v.event.pv_char) {
	       EIMIL_destruct_value(pv->v.event.pv_val);
	       EIMIL_destruct_value(pv->v.event.pv_mod);
	       free(pv2->v.event.type);
	       free(pv2);
	       return NULL;
	   }
       }
       if (!pv->v.event.pv_mtext) break;
      case EIMIL_TYPE_MTEXT:
      {
	  int i, j;
	  EIMIL_mtext *pmt2, *pmt;
	  EIMIL_mtext_props *pmp;
	  EIMIL_value **ppv;

	  if (pv->type == EIMIL_TYPE_MTEXT) {
	      pv2->v.mtext = pv->v.mtext;
	      pmt = &pv->v.mtext;
	      pmt2 = &pv2->v.mtext;
	  }else{
	      pmt = &pv->v.event.pv_mtext->v.mtext;
	      pmt2 = &pv2->v.event.pv_mtext->v.mtext;
	  }
	  pmt2->ustr = (UTF32*) malloc(sizeof(UTF32) * pmt->len);
	  if (!pmt2->ustr) {
	      free(pv2);
	      return NULL;
	  }
	  memcpy(pmt2->ustr, pmt->ustr, sizeof(UTF32) * pmt->len);

	  pmt2->pslots = (EIMIL_mtext_props*) malloc(sizeof(EIMIL_mtext_props)
						     * pmt->slotsnum);
	  if (!pmt2->pslots) {
	      free(pmt2->ustr);
	      free(pv2);
	      return NULL;
	  }
	  memcpy(pmt2->pslots, pmt->pslots,
		 sizeof(EIMIL_mtext_props) * pmt->slotsnum);

	  for (pmp = pmt2->pslots, i = 0;
	       i < pmt2->slotsnum;
	       i++, pmp++) {
	      ppv = (EIMIL_value**) malloc(sizeof(EIMIL_value*) * pmp->num);
	      if (!ppv) {
		  free(pmt2->pslots);
		  free(pmt2->ustr);
		  free(pv2);
		  return NULL;
	      }
	      memcpy(ppv, pmp->pprops, sizeof(EIMIL_value*) * pmp->num);
	      pmp->pprops = ppv;
	      for (j = 0; j < pmp->num; j++, ppv++) {
		  ASSERT((*ppv)->type == EIMIL_TYPE_PROP);
		  *ppv = EIMIL_copy_value(*ppv);
		  if (!*ppv) {
		      free(pmt2->pslots);
		      free(pmt2->ustr);
		      free(pv2);
		      return NULL;
					
		  }
		  (*ppv)->v.prop.target = pmt2;
		  EIMIL_ADDREF(**ppv);
	      }
	  }
	  break;
      }
      default:
       abort();
    }

    return pv2;
}

int
EIMIL_value_equal(
    EIMIL_value *pv1,
    EIMIL_value *pv2
)
{
	if (pv1 == pv2) return 1;

	/* TODO!!! */

	return 0;
}

int
EIMIL_generate_diff(
    EIMIL_symbol *psym,
    EIMIL_value *pv2,
    IMDifferential *pdiff
)
{
    EIMIL_value *pv1;

    ASSERT(psym->cat == EIMIL_CAT_VARIABLE);
    pv1 = psym->obj.v.pv;

    memset(pdiff, 0, sizeof(IMDifferential));

    ASSERT((!pv2) || (psym->obj.v.type == pv2->type));
    switch(psym->obj.v.type) {
      case EIMIL_TYPE_NUMBER:
       if ((pv1 != pv2)
	   || (pv1->v.number != pv2->v.number)) {
	   pdiff->number = pv1->v.number;
	   return 1;
       }
       break;

      case EIMIL_TYPE_BOOL:
       if ((pv1 != pv2)
	   || (pv1->v.bool_val != pv2->v.bool_val)) {
	   pdiff->bool_val = pv2->v.bool_val;
	   return 1;
       }
       break;

      case EIMIL_TYPE_CHAR:
       if ((pv1 != pv2)
	   || (pv1->v.ch != pv2->v.ch)) {
	   pdiff->ch = pv2->v.ch;
	   return 1;
       }
       break;

      case EIMIL_TYPE_MTEXT:
       return EIMIL_mtext_diff(&pv2->v.mtext, &pv1->v.mtext, pdiff);
       break;
      default:
       abort();
    }

    return 0;
}


/********************************************************************************
			EIMIL dictionary service.
 ********************************************************************************/

#define EIMIL_DICTIONARY_DEFAULT_SIZE 53 /* should be a primary number! */
#define EIMIL_DICTIONARY_SUBSLOT_UNIT 8

struct EIMIL_dictionary {
    int size;
    EIMIL_symbol ***namedic;
    EIMIL_symbol ***iddic;
};

EIMIL_dictionary*
EIMIL_new_dictionary(
    int size,
    int id_req_p
)
{
    EIMIL_dictionary *pdic;
    EIMIL_symbol ***pppsym;

    if (size == 0)
	size = EIMIL_DICTIONARY_DEFAULT_SIZE;

    pdic = malloc(sizeof(EIMIL_dictionary));
    if (!pdic) {
	return NULL;
    }
    pdic->size = size;
    pppsym = (EIMIL_symbol***) malloc(sizeof(EIMIL_dictionary**) * size);
    if (!pppsym) {
	free(pdic);
	return NULL;
    }
    memset(pppsym, 0, sizeof(EIMIL_dictionary**) * size);
    pdic->namedic = pppsym;
    if (id_req_p) {
	pppsym = (EIMIL_symbol***) malloc(sizeof(EIMIL_dictionary**) * size);
	if (!pppsym) {
	    free(pdic->namedic);
	    free(pdic);
	    return NULL;
	}
	memset(pppsym, 0, sizeof(EIMIL_dictionary**) * size);
	pdic->iddic = pppsym;
    } else {
	pdic->iddic = NULL;
    }
    return pdic;
}

void
EIMIL_free_dictionary(
    EIMIL_dictionary *pdic
)
{
    if (!pdic) return;

    /* TODO: free EIMIL_objects!!! */
    free(pdic->namedic);
    if (pdic->iddic) free(pdic->iddic);
    free(pdic);
    return;
}

void
EIMIL_free_dictionary_and_symbol(
    EIMIL_dictionary *pdic
)
{
    int i;
    EIMIL_symbol **pps;

    if (!pdic) return;

    for (i = 0; i < pdic->size; i++) {
	for (pps = pdic->namedic[i]; (pps && *pps); pps++) {
	    EIMIL_destruct_symbol(*pps);
	}
        if (pdic->namedic[i]) free(pdic->namedic[i]);
    }
    if (pdic->iddic) {
        for (i = 0; i < pdic->size; i++) {
            if (pdic->iddic[i]) free(pdic->iddic[i]);
        }
    }
    EIMIL_free_dictionary(pdic);
    return;
}

static int
hash_function_string(
    unsigned char *name,
    int size
)
{
    int i, v;

    for (v = 0; *name; i++, name++) {
	v = ((v << 8) + *name) % size;
    }

    return v;
}

#ifdef DEBUG
static void
list_symbols(
    EIMIL_dictionary *pdic
)
{
    int i;
    EIMIL_symbol **pps;

    for (i = 0; i < pdic->size; i++) {
	for (pps = pdic->namedic[i]; (pps && *pps); pps++) {
	    fprintf(stdout, "SYM(name):%s(%d)\n",
		    (*pps)->name, (*pps)->symbolid);
	    ASSERT(hash_function_string((*pps)->name, pdic->size)
		   == i);
	}
    }
    if (pdic->iddic) {
	for (i = 0; i < pdic->size; i++) {
	    for (pps = pdic->iddic[i];(pps && *pps);pps++) {
		fprintf(stdout, "SYM(id):%s(%d)\n",
			(*pps)->name, (*pps)->symbolid);
		ASSERT(((*pps)->symbolid %  pdic->size) == i);
	    }
	}
    }
    return;
}

#endif

static CARD32BIT
EIMIL_generate_symbolid(
    EIMIL_data *ped,
    enum EIMIL_CATEGORY cat,
    enum EIMIL_TYPE type
)
{
    int c;
    CARD32BIT r = 0;

    switch (cat) {
      case EIMIL_CAT_VARIABLE:
       switch (type) {
	 case EIMIL_TYPE_NIL:
	  return EIMIL_SYMBOL_ID_NIL;
	 case EIMIL_TYPE_T:
	  return EIMIL_SYMBOL_ID_T;
	 case EIMIL_TYPE_FEEDBACK:
	  return EIMIL_SYMBOL_ID_FEEDBACK;
	 case EIMIL_TYPE_CANDIDATES:
	  return EIMIL_SYMBOL_ID_CANDIDATES;
	 case EIMIL_TYPE_ANY:
	  r = EIMIL_SYMBOL_ID_PRIVATE;
	  break;
	 case EIMIL_TYPE_BOOL:
	  r = EIMIL_SYMBOL_ID_VARIABLE_BOOL;
	  break;
	 case EIMIL_TYPE_NUMBER:
	  r = EIMIL_SYMBOL_ID_VARIABLE_NUMBER;
	  break;
	 case EIMIL_TYPE_CHAR:
	  r = EIMIL_SYMBOL_ID_VARIABLE_CHAR;
	  break;
	 case EIMIL_TYPE_MTEXT:
	  r = EIMIL_SYMBOL_ID_VARIABLE_MTEXT;
	  break;
	 case EIMIL_TYPE_EVENT:
	  r = EIMIL_SYMBOL_ID_VARIABLE_EVENT;
	  break;
	 case EIMIL_TYPE_PROP:
	  r = EIMIL_SYMBOL_ID_VARIABLE_PROP;
	  break;
	 default:
	  abort();
       }
       break;
      case EIMIL_CAT_PROPERTY:
       switch (type) {
	 case EIMIL_TYPE_BOOL:
	  r = EIMIL_SYMBOL_ID_PROPERTY_BOOL;
	  break;
	 case EIMIL_TYPE_NUMBER:
	  r = EIMIL_SYMBOL_ID_PROPERTY_NUMBER;
	  break;
	 case EIMIL_TYPE_CHAR:
	  r = EIMIL_SYMBOL_ID_PROPERTY_CHAR;
	  break;
	 case EIMIL_TYPE_MTEXT:
	  r = EIMIL_SYMBOL_ID_PROPERTY_MTEXT;
	  break;
	 default:
	  abort();
       }
       break;
      case EIMIL_CAT_OPERATION:
       r = EIMIL_SYMBOL_ID_OPERATION;
       break;
      case EIMIL_CAT_FUNCTION:
       r = EIMIL_SYMBOL_ID_FUNCTION;
       break;
      case EIMIL_CAT_EXCEPTION:
       r = EIMIL_SYMBOL_ID_EXCEPTION;
       break;
      default:
       abort();
    }

    LOCK_SYNC_OBJECT(ped->pcommon->sync_object);
    c = ++ped->pcommon->id_counter;
    UNLOCK_SYNC_OBJECT(ped->pcommon->sync_object);
    if (c > EIMIL_ID_MAX) {
	/* TODO:  we should return error code.  */
	abort();
    }

    return ((r << 16) | c);
}

static EIMIL_symbol*
EIMIL_make_symbol(
    char *name,
    int len,
    enum EIMIL_CATEGORY cat
)
{
    EIMIL_symbol *p;

    p = (EIMIL_symbol*) malloc(sizeof(EIMIL_symbol));
    if (!p) return NULL;
    memset(p, 0, sizeof(EIMIL_symbol));
    p->name = (char*) malloc(sizeof(char) * (len + 1));
    if (!p->name) {
	free(p);
	return NULL;
    }
    p->namelen = len;
    memcpy(p->name, name, len);
    p->name[len] = '\0';
    p->cat = cat;

    return p;
}

static EIMIL_symbol*
lookup_symbol(
    EIMIL_dictionary *pdic,
    unsigned char *name
)
{
    EIMIL_symbol **p, *psym;
    int len = strlen(name);
    int hash = hash_function_string(name, pdic->size);

    p = pdic->namedic[hash];

    if (!p) return NULL;
    for (; *p; p++) {
	psym = *p;
	if ((psym->namelen == len)
	    && (memcmp(name, psym->name, len) == 0))
	    return psym;
    }

    return NULL;
}

static EIMIL_symbol*
lookup_predefined_symbol(
    unsigned char *name
)
{
    return lookup_symbol(pdic_internal, name);
}

static int
register_symbol_id(
    EIMIL_dictionary *pdic,
    EIMIL_symbol *psym
)
{
    int i;
    int id = psym->symbolid;
    int idx = id % pdic->size;
    EIMIL_symbol **p;

    if (!pdic->iddic) return 1;

    p = pdic->iddic[idx];
    if (!p) {
        /* create iddic[idx] */
        p = (EIMIL_symbol**) calloc(sizeof(EIMIL_symbol *), EIMIL_DICTIONARY_SUBSLOT_UNIT);
	if (!p) return 0;
	pdic->iddic[idx] = p;
        p[0] = psym;
	return 1;
    }

    for (i = 1;; i++, p++) {
	if (!*p) {
	    if ((i % EIMIL_DICTIONARY_SUBSLOT_UNIT) == 0) {
                /* ID dictionary is fulled with EIMIL_symbol. grow this dictionary. */
		p = (EIMIL_symbol**) realloc(pdic->iddic[idx],
					     (i + EIMIL_DICTIONARY_SUBSLOT_UNIT)
					     * sizeof(EIMIL_symbol*));
		if (!p) return 0;
		pdic->iddic[idx] = p;
		p = p + i - 1;
                memset(p, 0, EIMIL_DICTIONARY_SUBSLOT_UNIT * sizeof(EIMIL_symbol *));
	    }
	    p[0] = psym;
	    p[1] = NULL;
	    return 1;
	}
    }
    /* notreached */
    abort();
    return 0;
}

static EIMIL_symbol*
register_symbol(
    EIMIL_dictionary *pdic,
    unsigned char *name,
    enum EIMIL_CATEGORY cat,
    int id
)
{
    int i;
    EIMIL_symbol **p;
    EIMIL_symbol *psym;
    int len = strlen(name);
    int hash = hash_function_string(name, pdic->size);
    int id_req_p = pdic->iddic ? 1 : 0;

    if (lookup_predefined_symbol(name)) return NULL;

    p = pdic->namedic[hash];
    if (!p) {
        p = (EIMIL_symbol**) calloc(EIMIL_DICTIONARY_SUBSLOT_UNIT, sizeof(EIMIL_symbol*));
	if (!p) return NULL;
	pdic->namedic[hash] = p;
	psym = EIMIL_make_symbol(name, len, cat);
	psym->symbolid = id;
	if (!psym) return NULL;
        /* register psym to id dictionary */
        /* FIXME: may cause memory leaks if failed */
	if (!register_symbol_id(pdic, psym)) return NULL;
        p[0] = psym;
	return psym;
    }
    for (i = 1; ; i++, p++) {
	if (!*p) {
	    psym = EIMIL_make_symbol(name, len, cat);
	    psym->symbolid = id;
	    if (!psym) return NULL;
            /* register psym to id dictionary */
            /* FIXME: may cause memory leaks if failed */
	    if (!register_symbol_id(pdic, psym)) return NULL;
	    if ((i % EIMIL_DICTIONARY_SUBSLOT_UNIT) == 0) {
                /* Named dictionary is fulled with psym, grow it */
		p = (EIMIL_symbol**) realloc(pdic->namedic[hash],
					     (i + EIMIL_DICTIONARY_SUBSLOT_UNIT)
					     * sizeof(EIMIL_symbol*));
		pdic->namedic[hash] = p;
		p = p + i - 1;
                memset(p, 0, sizeof(EIMIL_DICTIONARY_SUBSLOT_UNIT) * sizeof(EIMIL_symbol*));
	    }
	    p[0] = psym;
	    p[1] = NULL;
	    return psym;
	}
	psym = *p;
        /* check whether name is already registerd */
	if ((psym->namelen == len)
	    && (memcmp(name, psym->name, len) == 0))
	    return NULL;
    }

    return NULL;

}

static int
re_register_symbol_internal(
    EIMIL_dictionary *pdic,
    EIMIL_symbol *psym
)
{
    int i;
    int id = psym->symbolid;
    int idx = id % pdic->size;
    EIMIL_symbol **p;

    if (!pdic->iddic) return 1;

    p = pdic->iddic[idx];
    if (!p) return 0;

    for (i = 1; *p; i++, p++) {
	if ((*p)->symbolid == psym->symbolid) {
	    *p = psym;
	    return 1;
	}
    }
    /* notreached */
    abort();
    return 0;
}

EIMIL_symbol*
EIMIL_register_symbol(
    EIMIL_data *ped,
    EIMIL_dictionary *pdic,
    unsigned char *name,
    enum EIMIL_CATEGORY cat,
    enum EIMIL_TYPE type
)
{
    int id;
    int id_req_p = pdic->iddic ? 1 : 0;

    if (id_req_p) id = EIMIL_generate_symbolid(ped, cat, type);
    else id = 0;

    return register_symbol(pdic, name, cat, id);
}

EIMIL_symbol*
EIMIL_intern_soft(
    EIMIL_dictionary *pdic,
    unsigned char *name
)
{
    EIMIL_symbol *psym;
    if ((psym = lookup_predefined_symbol(name)) != NULL) return psym;

    return lookup_symbol(pdic, name);
}

EIMIL_symbol*
EIMIL_lookup_symbol_internal(
    EIMIL_dictionary *pdic,
    CARD32BIT id
)
{
    int idx;
    EIMIL_symbol **p, *psym;

    switch (id) {
      case EIMIL_SYMBOL_ID_NIL:
       return pEIMIL_nil_sym;
      case EIMIL_SYMBOL_ID_T:
       return pEIMIL_t_sym;
      case EIMIL_SYMBOL_ID_FEEDBACK:
       return pEIMIL_feedback_sym;
      case EIMIL_SYMBOL_ID_CANDIDATES:
       return pEIMIL_candidates_sym;
      default:
       if (!pdic->iddic) return NULL;
       idx = id % pdic->size;
       p = pdic->iddic[idx];
       if (!p) return NULL;
	
       for (; *p; p++) {
	   psym = *p;
	   if (psym->symbolid == id) return psym;
       }
    }

    return NULL;
}

void
EIMIL_destruct_symbol(
    EIMIL_symbol *psym
)
{

    if (!psym) return;

    if (psym->name) free(psym->name);
    switch(psym->cat) {
      case EIMIL_CAT_VARIABLE:
       if (psym->obj.v.pv) {
         if (!psym->obj.v.constp)
             EIMIL_RMREF(*psym->obj.v.pv);
       }
       break;
      case EIMIL_CAT_EXCEPTION:
      {
	  if (psym->obj.e.msgs) {
	      EIMIL_message *msgs;
	      for (msgs = psym->obj.e.msgs;msgs->lang;msgs++) {
		  free(msgs->lang);
		  free(msgs->msg);
	      }
	      free(psym->obj.e.msgs);
	  }
      }
      case EIMIL_CAT_OPERATION:
      {
	  int i, n;
	  EIMIL_dependency *pdeps;
	  EIMIL_symbol **pps;

	  n = psym->obj.o.numdepends;
	  pdeps = psym->obj.o.pdeps;
	  for (i = 0;i < n;i++, pdeps++) {
	      pps = pdeps->depends;
	      if (pps) free(pps);
	      pps = pdeps->affects;
	      if (pps) free(pps);
	  }
	  free(pdeps);
	  break;
      }
      case EIMIL_CAT_FUNCTION:
       /* TODO */
       break;
      default:
       break;
    }

    free(psym);
}

static EIMIL_symbol*
copy_symbol(
    EIMIL_symbol *psym
)
{
    EIMIL_symbol *p;

    p = (EIMIL_symbol*) malloc(sizeof(EIMIL_symbol));
    if (!p) return NULL;
    *p = *psym;

    p->name = (char*) malloc(sizeof(char) * (psym->namelen + 1));
    if (!p->name) {
	free(p);
	return NULL;
    }
    p->namelen = psym->namelen;
    memcpy(p->name, psym->name, p->namelen + 1);

    return p;
}

static EIMIL_symbol*
duplicate_variable_symbol(
    EIMIL_symbol *psym
)
{
    EIMIL_value *pvs, *pvd;
    EIMIL_symbol *p;

    ASSERT(psym->cat == EIMIL_CAT_VARIABLE);

    p = copy_symbol(psym);
    if (!p) return NULL;

    p->obj.v.pv = NULL;
    pvs = psym->obj.v.pv;
    if (!pvs) return p;
    pvd = EIMIL_copy_value(pvs);
    if (!pvd) {
	EIMIL_destruct_symbol(p);
	return NULL;
    }
    EIMIL_ADDREF(*pvd);
    p->obj.v.pv = pvd;

    return p;
}

static EIMIL_symbol*
rebuild_operation_symbol(
    EIMIL_dictionary *pdic,
    EIMIL_symbol *psym
)
{
    int i, j, n;
    EIMIL_operation *pop, *popd;
    EIMIL_dependency *pdeps_s, *pdeps_d;
    EIMIL_symbol *p, *psymt;
    EIMIL_symbol **pps_s, **pps_d;
    ASSERT(psym->cat == EIMIL_CAT_OPERATION);

    p = copy_symbol(psym);
    if (!p) return NULL;
    pop = &psym->obj.o;
    popd = &p->obj.o;

    popd->pdeps = NULL;
    if (pop->numdepends > 0) {
	pdeps_d = (EIMIL_dependency*) malloc(sizeof(EIMIL_dependency)
					     * pop->numdepends);
	if (!pdeps_d) {
	    EIMIL_destruct_symbol(p);
	    return NULL;
	}
	memset(pdeps_d, 0, sizeof(EIMIL_dependency) * pop->numdepends);
    } else {
	pdeps_d = NULL;
    }
    popd->pdeps = pdeps_d;
    popd->numdepends = pop->numdepends;
    pdeps_s = pop->pdeps;
    for (i = 0; i < pop->numdepends;
	 i++, pdeps_s++, pdeps_d++) {
	if (pdeps_s->numdepends > 0) {
	    n = pdeps_s->numdepends;
	    pps_s = pdeps_s->depends;
	    pps_d = (EIMIL_symbol**) malloc(sizeof(EIMIL_symbol*) * n);
	    if (!pps_d) {
		EIMIL_destruct_symbol(p);
		return NULL;
	    }
	    pdeps_d->numdepends = n;
	    pdeps_d->depends = pps_d;
	    for (j = 0; j < n; j++, pps_s++, pps_d++) {
		psymt = EIMIL_lookup_symbol_internal(pdic, (*pps_s)->symbolid);
		ASSERT(psymt);
		ASSERT(psymt->cat == EIMIL_CAT_VARIABLE);
		*pps_d = psymt;
	    }
	}
	if (pdeps_s->numaffects > 0) {
	    n = pdeps_s->numaffects;
	    pps_s = pdeps_s->affects;
	    pps_d = (EIMIL_symbol**) malloc(sizeof(EIMIL_symbol*) * n);
	    if (!pps_d) {
		EIMIL_destruct_symbol(p);
		return NULL;
	    }
	    pdeps_d->numaffects = n;
	    pdeps_d->affects = pps_d;
	    for (j = 0; j < n; j++, pps_s++, pps_d++) {
		psymt = EIMIL_lookup_symbol_internal(pdic, (*pps_s)->symbolid);
		ASSERT(psymt);
		ASSERT(psymt->cat == EIMIL_CAT_VARIABLE);
		*pps_d = psymt;
	    }
	}
    }

    return p;
}

EIMIL_dictionary*
EIMIL_duplicate_dictionary(
    EIMIL_dictionary *psdic
)
{
    int i, j, n, size;
    EIMIL_symbol **ppss, **ppsd, **ppsdh;
    EIMIL_symbol *psym;
    EIMIL_dictionary *pddic;

    pddic = EIMIL_new_dictionary(psdic->size, (psdic->iddic != NULL));
    if (!pddic) return NULL;

    /* STEP1: shallow copy */
    for (i = 0; i < psdic->size; i++) {
	ppss = psdic->namedic[i];
	if (!ppss) continue;
	for (n = 0; *ppss; ppss++, n++);
	ppss = psdic->namedic[i];
	size = (((n / EIMIL_DICTIONARY_SUBSLOT_UNIT) + 1)
		* EIMIL_DICTIONARY_SUBSLOT_UNIT);
	ppsdh = (EIMIL_symbol**) malloc(sizeof(EIMIL_symbol*) * size);
	if (!ppsdh) return NULL;
	ppsd = ppsdh;
	ppsd[n] = NULL;
	for (j = 0; j < n; j++, ppss++, ppsd++) {
	    *ppsd = *ppss;
	    register_symbol_id(pddic, *ppsd);
	}
	pddic->namedic[i] = ppsd;
    }
    /* STEP2: variable duplication. */
    for (i = 0; i < psdic->size; i++) {
	ppss = psdic->namedic[i];
	if (!ppss) continue;
	ppsd = pddic->namedic[i];
	for (; *ppss; ppss++, ppsd++) {
	    if (ppss[0]->cat == EIMIL_CAT_VARIABLE) {
		psym = duplicate_variable_symbol(*ppss);
		if (!psym) return NULL;
		*ppsd = psym;
		re_register_symbol_internal(pddic, psym);
	    }
	}
    }
    /* STEP3: rebuild operation symbol.  */
    for (i = 0; i < psdic->size; i++) {
	ppss = psdic->namedic[i];
	if (!ppss) continue;
	ppsd = pddic->namedic[i];
	for (; *ppss; ppss++, ppsd++) {
	    if (ppss[0]->cat == EIMIL_CAT_OPERATION) {
		psym = rebuild_operation_symbol(pddic, *ppss);
		if (!psym) return NULL;
		*ppsd = psym;
		re_register_symbol_internal(pddic, psym);
	    }
	}
    }

    return pddic;
}

/********************************************************************************
			EIMIL document definition.
*********************************************************************************/

/***************************************
	      IDP parser
***************************************/

/*
  function declaration.
*/
static int
EIMIL_message_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_dependency_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_decldata_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_declprop_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_declop_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_commitnotify_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_declexception_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_UIdata_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_inherit_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_interface_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);
static int
EIMIL_engine_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
);

/*
  IDP document template.
 */

EIMIL_attr_template
EIMIL_attr_langinfo[] = {{"xml:lang", EIMIL_attr_REQUIRED, NULL},
			 {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_decldata[] = {{"name", EIMIL_attr_REQUIRED, NULL},
			 {"type", EIMIL_attr_REQUIRED, NULL},
			 {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_declprop[] = {{"name", EIMIL_attr_REQUIRED, NULL},
			 {"type", EIMIL_attr_REQUIRED, NULL},
			 {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_declop[] = {{"name", EIMIL_attr_REQUIRED, NULL},
		       {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_declexception[] = {{"name", EIMIL_attr_REQUIRED, NULL},
			      {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_commitnotify[] = {{"name", EIMIL_attr_REQUIRED, NULL},
			     {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_UIdata[] = {{"depend", EIMIL_attr_REQUIRED, NULL},
		       {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_message[] = {{"xml:lang", EIMIL_attr_IMPLIED, NULL},
		       {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_dependency[] = {{"depend", EIMIL_attr_REQUIRED, NULL},
			   {"affect", EIMIL_attr_REQUIRED, NULL},
			   {NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_EIMIL[] = {{"name", EIMIL_attr_REQUIRED, NULL},
		      {"revision", EIMIL_attr_REQUIRED, NULL},
		      {"type", EIMIL_attr_NORMAL, "concreate"},
		      {NULL, 0, NULL}};

EIMIL_element_template
EIMIL_declexception_template[] = {{"message", EIMIL_message_element_parser,
				   EIMIL_attr_message,
				   EIMIL_element_single, NULL, NULL},
				  {NULL, NULL, NULL, 0, NULL, NULL}};

EIMIL_element_template
EIMIL_declop_template[] = {{"dependency", EIMIL_dependency_element_parser,
			    EIMIL_attr_dependency,
			    EIMIL_element_EMPTY | EIMIL_element_single,
			    NULL, NULL},
			   {NULL, NULL, NULL, 0, NULL, NULL}};

EIMIL_element_template
EIMIL_interface_template[] = {{"langinfo", NULL,
			       EIMIL_attr_langinfo,
			       EIMIL_element_single, NULL, NULL},
			      {"decldata", EIMIL_decldata_element_parser,
			       EIMIL_attr_decldata,
			       EIMIL_element_EMPTY | EIMIL_element_multiple,
			       NULL, NULL},
			      {"declprop", EIMIL_declprop_element_parser,
			       EIMIL_attr_declprop,
			       EIMIL_element_EMPTY | EIMIL_element_multiple,
			       NULL, NULL},
			      {"declop", EIMIL_declop_element_parser,
			       EIMIL_attr_declop,
			       EIMIL_element_multiple,
			       EIMIL_declop_template, NULL},
			      {"commitnotify", EIMIL_commitnotify_element_parser,
			       EIMIL_attr_commitnotify,
			       EIMIL_element_EMPTY | EIMIL_element_0or1,
			       NULL, NULL},
			      {"declexception", EIMIL_declexception_element_parser,
			       EIMIL_attr_declexception,
			       EIMIL_element_multiple,
			       EIMIL_declexception_template, NULL},
			      {"UIdata", EIMIL_UIdata_element_parser,
			       EIMIL_attr_UIdata,
			       EIMIL_element_EMPTY | EIMIL_element_0or1,
			       NULL, NULL},
			      {NULL, NULL, NULL, 0, NULL, NULL}};


EIMIL_attr_template
EIMIL_attr_inherit[] = {{"src", EIMIL_attr_REQUIRED, NULL},
			{NULL, 0, NULL}};

EIMIL_attr_template
EIMIL_attr_engine[] = {{"name", EIMIL_attr_REQUIRED, NULL},
		       {"class", EIMIL_attr_REQUIRED, NULL},
		       {NULL, 0, NULL}};

EIMIL_element_template
EIMIL_doctemp[] = {{"inherit", EIMIL_inherit_element_parser,
		    EIMIL_attr_inherit,
		    EIMIL_element_EMPTY | EIMIL_element_multiple,
		    NULL, NULL},
		   {"interface", EIMIL_interface_element_parser,
		    NULL,
		    (EIMIL_subelement_ordered | EIMIL_element_single),
		    EIMIL_interface_template, NULL},
		   {"engine", EIMIL_engine_element_parser,
		    EIMIL_attr_engine,
		    EIMIL_element_multiple, NULL, NULL},
		   {NULL, NULL, NULL, 0, NULL, NULL}};

EIMIL_element_template
EIMIL_docroot[] = {{"EIMIL", NULL,
		    EIMIL_attr_EIMIL,
		    (EIMIL_subelement_ordered | EIMIL_element_single),
		    EIMIL_doctemp, NULL},
		   {NULL, NULL, NULL, 0, NULL, NULL}};

static int
EIMIL_message_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    int n;
    EIMIL_parser_state* pps = &ped->pcommon->ps;
    EIMIL_message **pmsgs = (EIMIL_message**) *pprivate;
    EIMIL_message *msgs = *pmsgs;
    UTF8 *lang, *msg;
    UTF32 *pu32;

    if (type != EIMIL_END_TAG) return 1;

    if (msgs) {
	for (n = 0;msgs->lang;msgs++, n++);
	msgs = *pmsgs;
    }else{
	n = 0;
    }

    for (;patr->name;patr++) {
	if (strcmp(patr->name, "xml:lang") == 0) {
	    if (!EIMIL_get_attr_cdata(patr->val, &lang)) {
		EIMIL_set_error_pt(ped, NULL, "Invalid cdata in xml:lang");
		return 0;
	    }
	} else {
	    return 0;
	}
    }
    {
	Ebyte *s, *e;

	for (s = pps->start;(s < e && EIMIL_isspace(*s));s++);
	for (e = pps->end;(e > s && EIMIL_isspace(*e));e--);
	if ((s >= e)
	    || (!(msg = EIMIL_resolve_reference(s, e)))) {
	    EIMIL_set_error_pt(ped, s, "Invalid contents in message element");
	    free(lang);
	    return 0;
	}
    }
    n++;
    msgs = (EIMIL_message*) realloc(msgs, sizeof(EIMIL_message) * (n + 1));
    pu32 = EIMIL_convert_UTF8_to_UTF32(lang);
    free(lang);
    if (!pu32) return 0;
    msgs[n - 1].lang = pu32;
    pu32 = EIMIL_convert_UTF8_to_UTF32(msg);
    free(msg);
    if (!pu32) return 0;
    msgs[n - 1].msg = pu32;
    msgs[n].lang = NULL;
    msgs[n].msg = NULL;
    *pmsgs = msgs;

    return 1;
}

static int
EIMIL_add_symbol_to_slots(
    EIMIL_data *ped,
    int num, EIMIL_symbol ***pslots,
    unsigned char *name,
    enum EIMIL_CATEGORY cat
)
{
    EIMIL_symbol *psym, **slots;

    slots = *pslots;

    psym = EIMIL_intern_soft(ped->pdic, name);
    if (!psym) {
	EIMIL_set_error_pt(ped, NULL,
			   "%s is not declared by declop.", name);
	return 0;
    }
    if (!((psym->publicp)
	  && (psym->cat == cat))) {
	EIMIL_set_error_pt(ped, NULL,
			   "%s is registered, but it's not defined properly.", name);
	return 0;
    }

    slots = (EIMIL_symbol**) realloc(slots, sizeof(EIMIL_symbol*) * (num + 1));
    if (!slots) {
	EIMIL_set_out_of_memory(ped);
	return 0;
    }
    slots[num] = psym;
    *pslots = slots;

    return 1;
}

static int
EIMIL_dependency_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    UTF8 *name;
    EIMIL_dependency *pdep;
    EIMIL_operation *pop = (EIMIL_operation*) *pprivate;

    if (type != EIMIL_EMPTY_TAG) return 1;

    pop->pdeps = (EIMIL_dependency*) realloc(pop->pdeps,
					     sizeof(EIMIL_dependency)
					     * (pop->numdepends + 1));
    if (!pop->pdeps) return 0;
    pdep = pop->pdeps + pop->numdepends;
    pop->numdepends++;
    memset(pdep, 0, sizeof(EIMIL_dependency));

    for (; patr->name; patr++) {
	if (strcmp(patr->name, "depend") == 0) {
	    Ebyte *c;

	    c = patr->val;
	    while ((c = EIMIL_get_attr_nmtokens(c, &name)) != NULL) {
		if (!EIMIL_add_symbol_to_slots(ped,
					       pdep->numdepends,
					       &pdep->depends,
					       name, EIMIL_CAT_VARIABLE)) {
		    free(name);
		    return 0;
		}
		free(name);
		pdep->numdepends++;
	    }
	    if (pdep->numdepends == 0) {
		EIMIL_set_error_pt(ped, NULL, "Invalid nmtokens in `depend'");
		return 0;
	    }
	} else if (strcmp(patr->name, "affect") == 0) {
	    Ebyte *c;

	    c = patr->val;
	    while ((c = EIMIL_get_attr_nmtokens(c, &name)) != NULL) {
		if (!EIMIL_add_symbol_to_slots(ped,
					       pdep->numaffects,
					       &pdep->affects,
					       name, EIMIL_CAT_VARIABLE)) {
		    free(name);
		    return 0;
		}
		free(name);
		pdep->numaffects++;
	    }
	    if (pdep->numaffects == 0) {
		EIMIL_set_error_pt(ped, NULL, "Invalid nmtokens in `depend'");
		return 0;
	    }
	}
    }
    if (type == EIMIL_TYPE_INVALID) {
	EIMIL_set_error_pt(ped, NULL,
			   "`type' attribute must be `bool', `number', `char', or `mtext'.");
    }
    return type;
}

static int
EIMIL_decldata_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    enum EIMIL_TYPE etype;
    UTF8 *name;
    EIMIL_symbol *psym = NULL;

    if (type != EIMIL_EMPTY_TAG) return 1;

    etype = EIMIL_get_type_from_attrs(ped, patr);
    if (etype == EIMIL_TYPE_INVALID) {
	return 0;
    }
    for (;patr->name;patr++) {
	if (strcmp(patr->name, "name") == 0) {
	    if (!EIMIL_get_attr_nmtoken(patr->val, &name)) {
		EIMIL_set_error_pt(ped, NULL, "Invalid nmtoken in `name'");
		return 0;
	    }
	    psym = EIMIL_register_symbol(ped, ped->pdic, name,
					 EIMIL_CAT_VARIABLE,
					 etype);
	    if (!psym) {
		EIMIL_set_error_pt(ped, NULL,
				   "%s is already registered.", name);
		free(name);
		return 0;
	    }
	    free(name);
	    psym->publicp = 1;
	    psym->obj.v.type = etype;
	    psym->obj.v.pv = NULL;
	} else {
	    return 0;
	}
    }
    ASSERT(psym);
    return 1;
}

static int
EIMIL_declprop_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    UTF8 *name;
    EIMIL_symbol *psym = NULL;

    if (type != EIMIL_EMPTY_TAG) return 1;

    type = EIMIL_get_type_from_attrs(ped, patr);
    if (type == EIMIL_TYPE_INVALID) {
	return 0;
    }
    for (;patr->name;patr++) {
	if (strcmp(patr->name, "name") == 0) {
	    if (!EIMIL_get_attr_nmtoken(patr->val, &name)) {
		EIMIL_set_error_pt(ped, NULL, "Invalid nmtoken in `name'");
		return 0;
	    }
	    psym = EIMIL_register_symbol(ped, ped->pdic, name,
					 EIMIL_CAT_PROPERTY,
					 type);
	    if (!psym) {
		EIMIL_set_error_pt(ped, NULL,
				   "%s is already registered.",
				   name);
		free(name);
		return 0;
	    }
	    free(name);
	    psym->publicp = 1;
	    psym->obj.p.type = type;
	} else {
	    return 0;
	}
    }
    ASSERT(psym);
    return 1;
}

static int
EIMIL_declop_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    UTF8 *name;
    EIMIL_symbol *psym = NULL;

    if (type != EIMIL_START_TAG) return 1;

    for (; patr->name; patr++) {
	if (strcmp(patr->name, "name") == 0) {
	    if (!EIMIL_get_attr_nmtoken(patr->val, &name)) {
		EIMIL_set_error_pt(ped, NULL, "Invalid nmtoken in `name'");
		return 0;
	    }
	    psym = EIMIL_register_symbol(ped, ped->pdic, name,
					 EIMIL_CAT_OPERATION,
					 EIMIL_TYPE_INVALID);
	    if (!psym) {
		EIMIL_set_error_pt(ped, NULL,
				   "%s is already registered.");
		return 0;
	    }
	    free(name);
	    psym->publicp = 1;
	    psym->obj.o.commitnotifyp = 0;
	    psym->obj.o.numdepends = 0;
	    psym->obj.o.pdeps = NULL;
	} else {
	    return 0;
	}
    }
    ASSERT(psym);

    *pprivate = &psym->obj.o;

    return 1;
}

static int
EIMIL_add_commitnotify(
    EIMIL_data *ped,
    EIMIL_symbol *psym
)
{
    int num;
    EIMIL_symbol **psyms;

    ASSERT(psym->cat == EIMIL_CAT_OPERATION);

    if (psym->obj.o.commitnotifyp) return 1;

    psyms = ped->commitnotify_ops;
    num = ped->commitnotify_numops;
    psyms = (EIMIL_symbol**) realloc(psyms, sizeof(EIMIL_symbol*) * (num + 1));
    if (!psyms) return 0;
    psyms[num] = psym;
    ped->commitnotify_ops = psyms;
    ped->commitnotify_numops++;
    return 1;
}

static int
EIMIL_del_commitnotify(
    EIMIL_data *ped,
    EIMIL_symbol *psym
)
{
    int i, num;
    EIMIL_symbol **psyms;

    ASSERT(psym->cat == EIMIL_CAT_OPERATION);

    if (!psym->obj.o.commitnotifyp) return 1;

    psyms = ped->commitnotify_ops;
    num = ped->commitnotify_numops;
    for (i = 0; i < num; i++) {
	if (psyms[i] == psym) {
	    if ((num - i - 1) > 0) {
		memmove(psyms + i, psyms + i + 1,
			sizeof(EIMIL_symbol*)
			* (num - i - 1));
	    }
	    ped->commitnotify_numops--;
	    return 1;
	}
    }
    /* not reached */
    abort();
    return 0;
}

static int
EIMIL_commitnotify_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    EIMIL_attrs *patr2;
    UTF8 *name;
    int flag = -1;
    EIMIL_symbol *psym = NULL;

    if (type != EIMIL_EMPTY_TAG) return 1;

    for (patr2 = patr; patr2->name; patr2++) {
	if (strcmp(patr->name, "flag") == 0) {
	    if (!EIMIL_get_attr_nmtoken(patr2->val, &name)) {
		flag = -1;
		break;
	    }
	    if (strcmp(name, "on") == 0) flag = 1;
	    else if (strcmp(name, "off") == 0) flag = 0;
	    else flag = -1;
	    free(name);
	    break;
	}
    }
    if (flag < 0) {
	EIMIL_set_error_pt(ped, NULL, "`flag' attribute must be `on' or `off'.");
	return 0;
    }
    for (; patr->name; patr++) {
	if (strcmp(patr->name, "op") == 0) {
	    Ebyte *c;
	    int i = 0;

	    for (c = patr->val;c;c = EIMIL_get_attr_nmtokens(patr->val, &name)) {
		psym = EIMIL_intern_soft(ped->pdic, name);
		if (!psym) {
		    EIMIL_set_error_pt(ped, NULL,
				       "%s is not declared by declop.");
		    return 0;
		}
		if (!((psym->publicp)
		      && (psym->cat == EIMIL_CAT_OPERATION))) {
		    EIMIL_set_error_pt(ped, NULL,
				       "%s is registered, but it's not valid operation.");
		    return 0;
		}
		if (flag) {
		    EIMIL_add_commitnotify(ped, psym);
		    psym->obj.o.commitnotifyp = 1;
		} else {
		    EIMIL_del_commitnotify(ped, psym);
		    psym->obj.o.commitnotifyp = 0;
		}
		free(name);
		i++;
	    }
	    if (i == 0) {
		EIMIL_set_error_pt(ped, NULL, "Invalid nmtokens in `op'");
		return 0;
	    }
	} else {
	    return 0;
	}
    }
    return 1;
}

static int
EIMIL_declexception_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    UTF8 *name;
    EIMIL_symbol *psym = NULL;

    if (type != EIMIL_START_TAG) return 1;

    for (; patr->name; patr++) {
	if (strcmp(patr->name, "name") == 0) {
	    if (!EIMIL_get_attr_nmtoken(patr->val, &name)) {
		EIMIL_set_error_pt(ped, NULL, "Invalid nmtoken in `name'");
		return 0;
	    }
	    psym = EIMIL_register_symbol(ped, ped->pdic, name,
					 EIMIL_CAT_EXCEPTION,
					 EIMIL_TYPE_INVALID);
	    if (!psym) {
		EIMIL_set_error_pt(ped, NULL,
				   "%s is already registered.");
		return 0;
	    }
	    free(name);
	    psym->publicp = 1;
	    psym->obj.e.msgs = NULL;
	} else {
	    return 0;
	}
    }
    ASSERT(psym);

    return 1;
}

static int
EIMIL_UIdata_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    UTF8 *name;
    EIMIL_symbol *psym;
    EIMIL_value *pv;

    if (type != EIMIL_EMPTY_TAG) return 1;

    for (; patr->name; patr++) {
	if (strcmp(patr->name, "depend") == 0) {
	    if (!EIMIL_get_attr_nmtoken(patr->val, &name)) {
		EIMIL_set_error_pt(ped, NULL, "Invalid nmtoken in `depend'");
		return 0;
	    }
	    psym = EIMIL_intern_soft(ped->pdic, name);
	    if (!psym) {
		EIMIL_set_error_pt(ped, NULL,
				   "%s is not declared by decldata.");
		return 0;
	    }
	    if (!((psym->publicp)
		  && (psym->cat == EIMIL_CAT_VARIABLE)
		  && (psym->obj.v.type == EIMIL_TYPE_MTEXT))) {
		EIMIL_set_error_pt(ped, NULL,
				   "%s is registered, but it's not valid mtext data.");
		return 0;
	    }
	    if ((ped->psym_uidata)
		&& (ped->psym_uidata->obj.v.pv)) {
		pv = ped->psym_uidata->obj.v.pv;
		ASSERT(pv->type == EIMIL_TYPE_MTEXT);
		pv->v.mtext.UIdatap = 0;
	    }
	    pv = psym->obj.v.pv;
	    if (pv) {
		ASSERT(pv->type == EIMIL_TYPE_MTEXT);
		pv->v.mtext.UIdatap = 1;
	    }
	    ped->psym_uidata = psym;
	    free(name);
	} else {
	    return 0;
	}
    }
    return 1;
}

static int
EIMIL_inherit_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    UTF8 *uaddr;

    if (type != EIMIL_EMPTY_TAG) return 1;

    for (;patr->name;patr++) {
	if (strcmp(patr->name, "src") == 0) {
	    if (!EIMIL_get_attr_cdata(patr->val, &uaddr)) {
		EIMIL_set_error_pt(ped, NULL, "Invalid cdata in `src'");
		return 0;
	    }
	    /* TODO: Load other EIMIL file. */
	    fprintf(stderr, "Inherit %s\n", uaddr);
	    free(uaddr);
	} else {
	    return 0;
	}
    }
    return 1;
}

static int
EIMIL_interface_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    /* Do nothing specially. */
    return 1;
}

/***************************************
      Engine specific interface
***************************************/

static int num_engines;
static EIMIL_engine_table *pengines;

static void
EIMIL_free_engine()
{
    int i;
    EIMIL_engine_table *p = pengines;
    for (i = 0; ; ++i) {
        if (i == num_engines) break;
        free (p->classname);
        free (p->uri);
        ++p;
    }

    free (pengines);

    /* reset */
    pengines = NULL;
    num_engines = 0;
}

static EIMIL_engine_table*
EIMIL_get_engine(
    const UTF8 *classname
)
{
    int i;
    EIMIL_engine_table *p = pengines;

    for (i = 0;; i++) {
	if (i == num_engines) return NULL;
	if (strcmp(classname, p->classname) == 0) break;
	p++;
    }

    return p;
}

int
EIMIL_register_engine(
    const UTF8 *classname,
    EIMIL_element_template *pet,
    EIMIL_engine_handler handler,
    EIMIL_engine_execute_handler execute_handler,
    const UTF8 *uri
)
{
    EIMIL_engine_table *p;
    p = EIMIL_get_engine(classname);
    if (!p) {
	p = (EIMIL_engine_table*) realloc(pengines,
					  sizeof(EIMIL_engine_table)
					  * (num_engines + 1));
	if (!p) return 0;
	pengines = p;
	p += num_engines;
	p->classname = strdup(classname);
	if (!p->classname) return 0;
	p->uri = strdup(uri);
	if (!p->uri) {
	    free(p->classname);
	    return 0;
	}
	num_engines++;
    } else {
	if (p->uri) free(p->uri);
	p->uri = strdup(uri);
	if (!p->uri) {
	    free(p->classname);
	    return 0;
	}
    }

    p->execute_handler = execute_handler;
    p->handler = handler;
    p->pet = pet;

    return 1;
}

static int
EIMIL_engine_element_parser(
    EIMIL_data *ped,
    EIMIL_attrs *patr,
    enum EIMIL_TAG_TYPE type,
    UTF8 *pchars,
    void **pprivate
)
{
    if (type == EIMIL_START_TAG) {
	UTF8 *ustr;
	void **pengine_context;
	EIMIL_engine_table *pt;
	EIMIL_engine *pe;
	EIMIL_parser_state* pps = &ped->pcommon->ps;
	EIMIL_cdata *pc = ped->pcommon;

	for (;patr->name;patr++) {
	    if (strcmp(patr->name, "class") == 0) {
		if (!EIMIL_get_attr_cdata(patr->val, &ustr)) {
		    EIMIL_set_error_pt(ped, NULL, "Invalid class name in `class'");
		    return 0;
		}
		pt = EIMIL_get_engine(ustr);
		if (!pt) {
		    EIMIL_set_error_pt(ped, NULL,
				       "Class:%s does not exist.",
				       ustr);
		    free(ustr);
		    return 0;
		}
		free(ustr);
	    } else if (strcmp(patr->name, "name") == 0) {
		if (!EIMIL_get_attr_cdata(patr->val, &ustr)) {
		    EIMIL_set_error_pt(ped, NULL, "Invalid engine name in `name'");
		    return 0;
		}
		pe = (EIMIL_engine*) realloc(pc->pengine,
					     sizeof(EIMIL_engine)
					     * (pc->num_engines + 1));
		if (!pe) {
		    EIMIL_set_out_of_memory(ped);
		    return 0;
		}
		pc->pengine = pe;
		pe += pc->num_engines;
		pengine_context = (void**) realloc(ped->pengine_context,
						   sizeof(void*)
						   * (pc->num_engines + 1));
		if (!pengine_context) {
		    EIMIL_set_out_of_memory(ped);
		    return 0;
		}
		ped->pengine_context = pengine_context;
		pengine_context +=  pc->num_engines;
		pe->name = ustr;
		pe->private = NULL;
	    } else {
		return 0;
	    }
	}

	/* Set up the engine. */
	pe->ptable = pt;
	pe->private = (*pt->handler)(EIMIL_ENGINE_INSTANCIATE, ped, NULL, NULL);

	if (!pe->private) {
	    EIMIL_set_out_of_memory(ped);
	    return 0;
	}
	*pengine_context = (*pt->handler)(EIMIL_ENGINE_DUPLICATE, ped,
					  pe->private, NULL);
	if (!(*pengine_context)) {
	    EIMIL_set_out_of_memory(ped);
	    return 0;
	}
	
	pc->num_engines++;
	pe->idx = pc->num_engines;
	EIMIL_SET_CURRENT_SUBELEMENT_TEMPLATE(pps, pt->pet);
	pps->current_uri = pt->uri;
	*pprivate = *pengine_context;
    }
    return 1;
}

/***************************************
	  EIMIL handle
***************************************/

EIMIL_data*
EIMIL_make_handle_data(
    EIMIL_cdata *pbase
)
{
    EIMIL_data *ped;
    EIMIL_dictionary *pdic;

    ped = (EIMIL_data*) malloc(sizeof(EIMIL_data));
    if (!ped) return NULL;
    memset(ped, 0, sizeof(EIMIL_data));

    if (!pbase) {
	pbase = (EIMIL_cdata*) malloc(sizeof(EIMIL_cdata));
	if (!pbase) {
	    free(ped);
	    return NULL;
	}
	memset(pbase, 0, sizeof(EIMIL_cdata));
	INIT_SYNC_OBJECT(pbase->sync_object);

	/* 
	   EIMIL_duplicate_handle() make its own dictionary,
	   so it must not create a new dictionary here.
	 */
	pdic = EIMIL_new_dictionary(0, 1);
	if (!pdic) {
	    free(ped);
	    return NULL;
	}
	ped->pdic = pdic;
    } else {
	ped->duplicated = 1;
    }
    ped->pcommon = pbase;

    ped->pcur_ev = ped->pqueue_ev = ped->queueslots;

    return ped;
}

/***************************************
	  parser entry.
***************************************/

int
EIMIL_parse_start(
    EIMIL_data *ped
)
{
    ped->pcommon->ps.current_uri = EIMIL_xmlns_uri;
    return EIMIL_parse_element(ped, EIMIL_docroot, NULL, NULL, NULL, NULL);
}

/********************************************************************************
				API
 ********************************************************************************/

EIMIL_symbol*
EIMIL_lookup_symbol(
    EIMIL_handle eh,
    CARD32BIT id
)
{
    EIMIL_data *ped = (EIMIL_data*) eh;
    return EIMIL_lookup_symbol_internal(ped->pdic, id);
}

int
EIMIL_duplicate_handle(
    EIMIL_handle *peh,
    EIMIL_handle eh
)
{
    EIMIL_data *peds, *pedd;
    EIMIL_symbol *psym;
    EIMIL_dictionary *pdic;
    int i, n;

    peds = (EIMIL_data*) eh;
    pedd = EIMIL_make_handle_data(peds->pcommon);
    if (!pedd) return 0;

    /* copy handle data */
    *pedd = *peds;

    /* dupliacte dictionary */
    pdic = EIMIL_duplicate_dictionary(peds->pdic);
    if (!pdic) return 0;
    pedd->pdic = pdic;

    /* engine context */
    {
	void **pecs, **pecd;
	EIMIL_engine *pe;
	EIMIL_engine_table *pt;

	n = peds->pcommon->num_engines;
	pecs = pedd->pengine_context;
	pecd = (void**) malloc(sizeof(void*) * n);
	if (!pecd) return 0;
	for (i = 0; i < n; i++, pecs++, pecd++) {
	    if (*pecs) {
		pe = peds->pcommon->pengine + i;
		pt = pe->ptable;
		*pecd = (*pt->handler)(EIMIL_ENGINE_DUPLICATE, peds,
				       pe->private, *pecs);
		if (*pecd) return 0;
	    } else {
		*pecd = NULL;
	    }
	}
    }

    /* set psym_uidata. */
    if (peds->psym_uidata) {
	psym = EIMIL_lookup_symbol_internal(pdic, peds->psym_uidata->symbolid);
	ASSERT(psym);
	pedd->psym_uidata = psym;
    }

    /* set commitnotify_ops. */
    if (peds->commitnotify_numops > 0) {
	EIMIL_symbol **psyms_s, **psyms_d;
	psyms_d = (EIMIL_symbol**) malloc(sizeof(EIMIL_symbol*)
					  * peds->commitnotify_numops);
	if (!psyms_d) return 0;
	pedd->commitnotify_ops = psyms_d;
	psyms_s = peds->commitnotify_ops;
	for (i = 0;
	     peds->commitnotify_numops;
	     i++, psyms_s++, psyms_d++) {
	    *psyms_d = EIMIL_lookup_symbol_internal(pdic, psyms_s[0]->symbolid);
	    ASSERT(*psyms_d);
	}
    }

    /* Reset journal state */
    pedd->current_journal_id = 0;
    pedd->pjst = NULL;

    /* Success */

    *peh = pedd;
		      
    return 1;
}

int
EIMIL_free_handle(
    EIMIL_handle eh
)
{
    EIMIL_data *ped;

    if (!eh) return 0;
    ped = (EIMIL_data*) eh;
    EIMIL_journal_free(eh);

    if (!ped->duplicated) {
	EIMIL_cdata *pc = ped->pcommon;
	if (pc->ps.buf) free(pc->ps.buf);
        if (pc->ps.pxmlns) free(pc->ps.pxmlns);
	DESTROY_SYNC_OBJECT(pc->sync_object);
	free(pc);
    }

    EIMIL_free_dictionary(ped->pdic);

    free(ped);

    return 1;
}

int
EIMIL_get_errormsg(
    EIMIL_handle eh,
    char **ppmsg
)
{
    EIMIL_data *ped;

    if (!eh) return 0;
    ped = (EIMIL_data*) eh;

    *ppmsg = ped->errstr;

    return 1;
}

static int
EIMIL_init_predefined_symbol()
{
    EIMIL_symbol *psym;

    pdic_internal = EIMIL_new_dictionary(11, 1);
    if (!pdic_internal) return 0;

    psym = register_symbol(pdic_internal, "nil",
			   EIMIL_CAT_VARIABLE,
			   EIMIL_SYMBOL_ID_NIL);
    if (!psym) return 0;
    psym->obj.v.type = EIMIL_TYPE_NIL;
    psym->obj.v.constp = 1;
    psym->obj.v.pv = NULL;
    pEIMIL_nil_sym = psym;

    psym = register_symbol(pdic_internal, "t",
			   EIMIL_CAT_VARIABLE,
			   EIMIL_SYMBOL_ID_T);
    if (!psym) return 0;
    psym->obj.v.type = EIMIL_TYPE_BOOL;
    psym->obj.v.constp = 1;
    psym->obj.v.pv = EIMIL_construct_bool(1);
    pEIMIL_t_sym = psym;

    psym = register_symbol(pdic_internal, "feedback",
			   EIMIL_CAT_PROPERTY,
			   EIMIL_SYMBOL_ID_FEEDBACK);
    if (!psym) return 0;
    psym->obj.p.type = EIMIL_TYPE_NUMBER;
    pEIMIL_feedback_sym = psym;

    psym = register_symbol(pdic_internal, "candidates",
			   EIMIL_CAT_PROPERTY,
			   EIMIL_SYMBOL_ID_CANDIDATES);
    psym->obj.p.type = EIMIL_TYPE_MTEXT;
    pEIMIL_candidates_sym = psym;

    return 1;
}

int
EIMIL_initialize()
{

    if (EIMIL_inited) return 1;

    /* initialize internal object */
    EIMIL_t_val.refcount = 1;
    EIMIL_t_val.type = EIMIL_TYPE_BOOL;
    EIMIL_t_val.v.bool_val = 1;

    if (!EIMIL_init_predefined_symbol()) return 0;
    if (!EIMILFile_init()) return 0;
    if (!PCE_init()) return 0;

    EIMIL_inited = 1;
    return 1;
}

int
EIMIL_finalize()
{
    if (!EIMIL_inited) return 1;

    EIMIL_free_dictionary_and_symbol(pdic_internal);

    EIMIL_free_engine(pengines);

    pEIMIL_nil_sym = NULL;
    pEIMIL_t_sym = NULL;
    pEIMIL_feedback_sym = NULL;
    pEIMIL_candidates_sym = NULL;
    pdic_internal = NULL;
    EIMIL_inited = 0;

    return 1;
}

void
EIMIL_set_private(
    EIMIL_handle eh,
    void* private
)
{
    EIMIL_data *ped = (EIMIL_data*) eh;
    ped->private = private;
}

void*
EIMIL_get_private(
    EIMIL_handle eh,
    void* private
)
{
    EIMIL_data *ped = (EIMIL_data*) eh;
    return ped->private;
}

/****************************************
           EIMIL service IF.
 ****************************************/

int
EIMIL_toggle_preedit(
    EIMIL_data *ped,
    int flag
)
{
    int r;

    if (!ped->pcommon->uiproc) return 0;

    if (flag)
	r = (*ped->pcommon->uiproc)(ped, ped->psym_uidata->obj.v.pv,
				    EIMIL_ENABLE_PREEDIT);
    else
	r = (*ped->pcommon->uiproc)(ped, ped->psym_uidata->obj.v.pv,
				    EIMIL_DISABLE_PREEDIT);

    return r;
}

int
EIMIL_update_preedit(
    EIMIL_data *ped
)
{
    return (*ped->pcommon->uiproc)(ped, ped->psym_uidata->obj.v.pv,
				   EIMIL_UPDATE_PREEDIT);
}

int
EIMIL_toggle_lookup_choice(
    EIMIL_data *ped,
    int flag
)
{
    int r;

    if (!ped->pcommon->uiproc) return 0;

    if (flag)
	r = (*ped->pcommon->uiproc)(ped, ped->psym_uidata->obj.v.pv,
				    EIMIL_ENABLE_LOOKUP_CHOICE);
    else
	r = (*ped->pcommon->uiproc)(ped, ped->psym_uidata->obj.v.pv,
				    EIMIL_DISABLE_LOOKUP_CHOICE);

    return r;
}

int
EIMIL_update_lookup_choice(
    EIMIL_data *ped
)
{
    if (!ped->pcommon->uiproc) return 0;

    return (*ped->pcommon->uiproc)(ped, ped->psym_uidata->obj.v.pv,
				   EIMIL_UPDATE_LOOKUP_CHOICE);
}

int
EIMIL_reply_event(
    EIMIL_data *ped,
    EIMIL_value *pv_event
)
{
    if (!ped->pcommon->evproc) return 0;

    ASSERT(pv_event->type == EIMIL_TYPE_EVENT);

    return (*ped->pcommon->evproc)(ped, &pv_event->v.event);
}

int
EIMIL_queue_event(
    EIMIL_data *ped,
    EIMIL_value *pv_event
)
{
    EIMIL_value **pqn;


    if (ped->pqueue_ev == (ped->queueslots + EIMIL_EVENT_QUEUESIZE - 1))
	pqn = ped->queueslots;
    else
	pqn = ped->pqueue_ev + 1;

    if (pqn == ped->pcur_ev) return 0;

    *ped->pqueue_ev = pv_event;
    ped->pqueue_ev = pqn;
    EIMIL_ADDREF(*pv_event);

    return 1;
}

EIMIL_value*
EIMIL_next_event(
    EIMIL_data *ped
)
{
    EIMIL_value **pqn, *pev;

    if (ped->pcur_ev == ped->pqueue_ev) return NULL;

    if (ped->pcur_ev == (ped->queueslots + EIMIL_EVENT_QUEUESIZE - 1))
	pqn = ped->queueslots;
    else
	pqn = ped->pcur_ev + 1;

    pev = *ped->pcur_ev;
    ped->pcur_ev = pqn;

    if (pev) EIMIL_RMREF_WITHOUT_DESTRUCTION(*pev);

    return pev;
}

/****************************************
           EIMIL application IF.
 ****************************************/

int
EIMIL_register_handler(
    EIMIL_handle eh,
    EIMIL_EVENT_PROC evproc,
    EIMIL_UICHANGE_PROC uiproc,
    EIMIL_OPISSUE_PROC opproc
)
{
    EIMIL_data *ped = (EIMIL_data*) eh;
    EIMIL_cdata *pcommon = ped->pcommon;

    pcommon->evproc = evproc;
    pcommon->uiproc = uiproc;
    pcommon->opproc = opproc;

    return 1;
}

int
EIMIL_send_event(
    EIMIL_handle eh,
    EIMIL_value *pv_event
)
{
    int i, n;
    EIMIL_data *ped = (EIMIL_data*) eh;
    EIMIL_cdata *pcommon = ped->pcommon;
    EIMIL_engine *pe = pcommon->pengine;
    void **pengine_ctx;

    n = pcommon->num_engines;
    pengine_ctx = ped->pengine_context;

    /* if event, store event */
    if (pv_event) {
	ASSERT(pv_event->type == EIMIL_TYPE_EVENT);
	EIMIL_queue_event(ped, pv_event);
    }

    for (i = 0; i < n; i++) {
	if ((*pe->ptable->execute_handler)(*pengine_ctx)
	    != EIMIL_ENGINE_STATUS_SKIPPED)
	    break;
	pe++;
	pengine_ctx++;
    }

    if (pv_event) EIMIL_destruct_value(pv_event);
    return 1;
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
