525 lines
12 KiB
C
525 lines
12 KiB
C
|
/* $Id$
|
||
|
|
||
|
Part of SWI-Prolog
|
||
|
|
||
|
Author: Jan Wielemaker
|
||
|
E-mail: jan@swi.psy.uva.nl
|
||
|
WWW: http://www.swi-prolog.org
|
||
|
Copyright (C): 1985-2002, University of Amsterdam
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Lesser General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2.1 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
|
||
|
Lesser General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser 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
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <assert.h>
|
||
|
#include "dtd.h"
|
||
|
#include "model.h"
|
||
|
|
||
|
#define MAX_VISITED 256
|
||
|
#define MAX_ALLOWED 64
|
||
|
|
||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||
|
This module implements a finite state engine for validating the content
|
||
|
model of elements. A state machine is the only feasible approach for
|
||
|
realising an event-driven SGML parser.
|
||
|
|
||
|
The public functions are:
|
||
|
|
||
|
dtd_state *new_dtd_state(void)
|
||
|
Create an anonymous new state. Normally an element creates two of
|
||
|
these for it ->initial_state and ->final_state attributes.
|
||
|
|
||
|
dtd_state *make_state_engine(dtd_element *e)
|
||
|
Associate a state engine to this element and return the initial
|
||
|
state of the engine. If the element has an engine, simply return
|
||
|
the initial state.
|
||
|
|
||
|
dtd_state *make_dtd_transition(dtd_state *here, dtd_element *e)
|
||
|
Given the current state, see whether we can accept e and return
|
||
|
the resulting state. If no transition is possible return NULL.
|
||
|
|
||
|
int same_state(dtd_state *final, dtd_state *here)
|
||
|
See whether two states are the same, or the final state can be
|
||
|
reached only traversing equivalence links.
|
||
|
|
||
|
The A&B&... model
|
||
|
|
||
|
Models of the type a&b&c are hard to translate, as the resulting state
|
||
|
machine is of size order N! In practice only a little of this will be
|
||
|
used however and we `fix' this problem using a `lazy state-engine', that
|
||
|
expands to the next level only after reaching some level. See the
|
||
|
function state_transitions(). The design takes more lazy generation into
|
||
|
consideration.
|
||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
typedef struct _state_transition
|
||
|
{ dtd_element *element; /* element on transition */
|
||
|
dtd_state *state; /* state to go to */
|
||
|
struct _state_transition *next; /* next possible transition */
|
||
|
} transition;
|
||
|
|
||
|
typedef struct _dtd_model_list /* list (set) of models */
|
||
|
{ dtd_model *model;
|
||
|
struct _dtd_model_list *next;
|
||
|
} dtd_model_list;
|
||
|
|
||
|
typedef enum
|
||
|
{ EX_AND /* expand (a&b&...) */
|
||
|
} expand_type;
|
||
|
|
||
|
typedef struct _state_expander
|
||
|
{ dtd_state *target; /* Target state to expand to */
|
||
|
expand_type type; /* EX_* */
|
||
|
union
|
||
|
{ struct
|
||
|
{ dtd_model_list *set; /* Models we should still see */
|
||
|
} and; /* Expand (a&b&...) */
|
||
|
} kind;
|
||
|
} expander;
|
||
|
|
||
|
typedef struct _visited
|
||
|
{ int size; /* set-size */
|
||
|
dtd_state *states[MAX_VISITED]; /* The set */
|
||
|
} visited;
|
||
|
|
||
|
|
||
|
static void translate_model(dtd_model *m, dtd_state *from, dtd_state *to);
|
||
|
static transition *state_transitions(dtd_state *state);
|
||
|
|
||
|
static int
|
||
|
visit(dtd_state *state, visited *visited)
|
||
|
{ int i;
|
||
|
|
||
|
for(i=0; i<visited->size; i++)
|
||
|
{ if ( visited->states[i] == state )
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if ( visited->size >= MAX_VISITED )
|
||
|
{ fprintf(stderr, "Reached MAX_VISITED!\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
visited->states[visited->size++] = state;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
static dtd_state *
|
||
|
do_make_dtd_transition(dtd_state *here, dtd_element *e, visited *visited)
|
||
|
{ transition *tset = state_transitions(here);
|
||
|
transition *t;
|
||
|
|
||
|
for(t=tset; t; t=t->next)
|
||
|
{ if ( t->element == e )
|
||
|
return t->state;
|
||
|
}
|
||
|
|
||
|
for(t=tset; t; t=t->next)
|
||
|
{ if ( t->element == NULL && visit(t->state, visited) )
|
||
|
{ dtd_state *new;
|
||
|
|
||
|
if ( (new=do_make_dtd_transition(t->state, e, visited)) )
|
||
|
return new;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
dtd_state *
|
||
|
make_dtd_transition(dtd_state *here, dtd_element *e)
|
||
|
{ visited visited;
|
||
|
visited.size = 0;
|
||
|
|
||
|
if ( !here ) /* from nowhere to nowhere */
|
||
|
return NULL;
|
||
|
|
||
|
return do_make_dtd_transition(here, e, &visited);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int
|
||
|
find_same_state(dtd_state *final, dtd_state *here, visited *visited)
|
||
|
{ transition *t;
|
||
|
|
||
|
if ( final == here )
|
||
|
return TRUE;
|
||
|
|
||
|
for(t=state_transitions(here); t; t=t->next)
|
||
|
{ if ( t->element == NULL && visit(t->state, visited) )
|
||
|
{ if ( find_same_state(final, t->state, visited) )
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
same_state(dtd_state *final, dtd_state *here)
|
||
|
{ visited visited;
|
||
|
visited.size = 0;
|
||
|
|
||
|
return find_same_state(final, here, &visited);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||
|
state_allows_for(dtd_state *state, dtd_element **allow, int *n)
|
||
|
See what elements are allowed if we are in this state. This is
|
||
|
currently not used, but might prove handly for error messages or
|
||
|
syntax-directed editors.
|
||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
static void
|
||
|
do_state_allows_for(dtd_state *here, dtd_element **allow, int *n,
|
||
|
visited *visited)
|
||
|
{ transition *t;
|
||
|
|
||
|
for(t=state_transitions(here); t; t=t->next)
|
||
|
{ int i;
|
||
|
|
||
|
if ( t->element == NULL )
|
||
|
{ if ( visit(t->state, visited) )
|
||
|
do_state_allows_for(t->state, allow, n, visited);
|
||
|
} else
|
||
|
{ for(i=0; i<*n; i++)
|
||
|
{ if ( allow[i] == t->element )
|
||
|
goto next;
|
||
|
}
|
||
|
allow[(*n)++] = t->element;
|
||
|
}
|
||
|
next:
|
||
|
;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
state_allows_for(dtd_state *state, dtd_element **allow, int *n)
|
||
|
{ visited visited;
|
||
|
visited.size = 0;
|
||
|
|
||
|
*n = 0;
|
||
|
if ( state )
|
||
|
do_state_allows_for(state, allow, n, &visited);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int
|
||
|
do_find_omitted_path(dtd_state *state, dtd_element *e,
|
||
|
dtd_element **path, int *pl,
|
||
|
visited *visited)
|
||
|
{ transition *tset = state_transitions(state);
|
||
|
transition *t;
|
||
|
int pathlen = *pl;
|
||
|
|
||
|
for(t=tset; t; t=t->next)
|
||
|
{ if ( t->element == e )
|
||
|
return TRUE;
|
||
|
|
||
|
if ( t->element &&
|
||
|
t->element != CDATA_ELEMENT &&
|
||
|
t->element->structure &&
|
||
|
t->element->structure->omit_open &&
|
||
|
visit(t->state, visited) )
|
||
|
{ dtd_state *initial = make_state_engine(t->element);
|
||
|
|
||
|
path[pathlen] = t->element;
|
||
|
*pl = pathlen+1;
|
||
|
if ( do_find_omitted_path(initial, e, path, pl, visited) )
|
||
|
return TRUE;
|
||
|
*pl = pathlen;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(t=tset; t; t=t->next)
|
||
|
{ if ( !t->element &&
|
||
|
visit(t->state, visited) )
|
||
|
{ if ( do_find_omitted_path(t->state, e, path, pl, visited) )
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
find_omitted_path(dtd_state *state, dtd_element *e, dtd_element **path)
|
||
|
{ int pl = 0;
|
||
|
visited visited;
|
||
|
visited.size = 0;
|
||
|
|
||
|
if ( state && do_find_omitted_path(state, e, path, &pl, &visited) )
|
||
|
return pl;
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
dtd_state *
|
||
|
new_dtd_state()
|
||
|
{ dtd_state *s = sgml_calloc(1, sizeof(*s));
|
||
|
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
link(dtd_state *from, dtd_state *to, dtd_element *e)
|
||
|
{ transition *t = sgml_calloc(1, sizeof(*t));
|
||
|
|
||
|
t->state = to;
|
||
|
t->element = e;
|
||
|
t->next = from->transitions;
|
||
|
from->transitions = t;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* EXPANSION *
|
||
|
*******************************/
|
||
|
|
||
|
static void
|
||
|
add_model_list(dtd_model_list **list, dtd_model *m)
|
||
|
{ dtd_model_list *l = sgml_calloc(1, sizeof(*l));
|
||
|
|
||
|
l->model = m;
|
||
|
|
||
|
for( ; *list; list = &(*list)->next)
|
||
|
;
|
||
|
*list = l;
|
||
|
}
|
||
|
|
||
|
|
||
|
static transition *
|
||
|
state_transitions(dtd_state *state)
|
||
|
{ if ( !state->transitions && state->expander )
|
||
|
{ expander *ex = state->expander;
|
||
|
|
||
|
switch(ex->type)
|
||
|
{ case EX_AND:
|
||
|
{ dtd_model_list *left = ex->kind.and.set;
|
||
|
|
||
|
if ( !left ) /* empty AND (should not happen) */
|
||
|
{ link(state, ex->target, NULL);
|
||
|
} else if ( !left->next ) /* only one left */
|
||
|
{ translate_model(left->model, state, ex->target);
|
||
|
} else
|
||
|
{ for( ; left; left = left->next )
|
||
|
{ dtd_state *tmp = new_dtd_state();
|
||
|
expander *nex = sgml_calloc(1, sizeof(*nex));
|
||
|
dtd_model_list *l;
|
||
|
|
||
|
translate_model(left->model, state, tmp);
|
||
|
tmp->expander = nex;
|
||
|
nex->target = ex->target;
|
||
|
nex->type = EX_AND;
|
||
|
for(l=ex->kind.and.set; l; l=l->next)
|
||
|
{ if ( l != left )
|
||
|
add_model_list(&nex->kind.and.set, l->model);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return state->transitions;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* TRANSLATION *
|
||
|
*******************************/
|
||
|
|
||
|
|
||
|
static void
|
||
|
translate_one(dtd_model *m, dtd_state *from, dtd_state *to)
|
||
|
{ switch(m->type)
|
||
|
{ case MT_ELEMENT:
|
||
|
{ dtd_element *e = m->content.element;
|
||
|
|
||
|
link(from, to, e);
|
||
|
return;
|
||
|
}
|
||
|
case MT_SEQ: /* a,b,... */
|
||
|
{ dtd_model *sub;
|
||
|
|
||
|
for( sub = m->content.group; sub->next; sub = sub->next )
|
||
|
{ dtd_state *tmp = new_dtd_state();
|
||
|
translate_model(sub, from, tmp);
|
||
|
from = tmp;
|
||
|
}
|
||
|
translate_model(sub, from, to);
|
||
|
return;
|
||
|
}
|
||
|
case MT_AND: /* a&b&... */
|
||
|
{ expander *ex = sgml_calloc(1, sizeof(*ex));
|
||
|
dtd_model *sub;
|
||
|
|
||
|
ex->target = to;
|
||
|
ex->type = EX_AND;
|
||
|
|
||
|
for( sub = m->content.group; sub; sub = sub->next )
|
||
|
add_model_list(&ex->kind.and.set, sub);
|
||
|
|
||
|
from->expander = ex;
|
||
|
return;
|
||
|
}
|
||
|
case MT_OR: /* a|b|... */
|
||
|
{ dtd_model *sub;
|
||
|
|
||
|
for( sub = m->content.group; sub; sub = sub->next )
|
||
|
translate_model(sub, from, to);
|
||
|
return;
|
||
|
}
|
||
|
case MT_PCDATA:
|
||
|
case MT_UNDEF:
|
||
|
assert(0);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
translate_model(dtd_model *m, dtd_state *from, dtd_state *to)
|
||
|
{ if ( m->type == MT_PCDATA )
|
||
|
{ link(from, from, CDATA_ELEMENT);
|
||
|
link(from, to, NULL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch(m->cardinality)
|
||
|
{ case MC_OPT: /* ? */
|
||
|
link(from, to, NULL);
|
||
|
/*FALLTHROUGH*/
|
||
|
case MC_ONE:
|
||
|
translate_one(m, from, to);
|
||
|
return;
|
||
|
case MC_REP: /* * */
|
||
|
translate_one(m, from, from);
|
||
|
link(from, to, NULL);
|
||
|
return;
|
||
|
case MC_PLUS: /* + */
|
||
|
translate_one(m, from, to);
|
||
|
translate_one(m, to, to);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
dtd_state *
|
||
|
make_state_engine(dtd_element *e)
|
||
|
{ if ( e->structure )
|
||
|
{ dtd_edef *def = e->structure;
|
||
|
|
||
|
if ( !def->initial_state )
|
||
|
{ if ( def->content )
|
||
|
{ def->initial_state = new_dtd_state();
|
||
|
def->final_state = new_dtd_state();
|
||
|
|
||
|
translate_model(def->content, def->initial_state, def->final_state);
|
||
|
} else if ( def->type == C_CDATA || def->type == C_RCDATA )
|
||
|
{ def->initial_state = new_dtd_state();
|
||
|
def->final_state = new_dtd_state();
|
||
|
|
||
|
link(def->initial_state, def->initial_state, CDATA_ELEMENT);
|
||
|
link(def->initial_state, def->final_state, NULL);
|
||
|
} else
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return def->initial_state;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* FREE *
|
||
|
*******************************/
|
||
|
|
||
|
static void do_free_state_engine(dtd_state *state, visited *visited);
|
||
|
|
||
|
static void
|
||
|
free_model_list(dtd_model_list *l)
|
||
|
{ dtd_model_list *next;
|
||
|
|
||
|
for( ; l; l=next)
|
||
|
{ next = l->next;
|
||
|
|
||
|
sgml_free(l);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
free_expander(expander *e, visited *visited)
|
||
|
{ if ( visit(e->target, visited) )
|
||
|
do_free_state_engine(e->target, visited);
|
||
|
|
||
|
switch(e->type)
|
||
|
{ case EX_AND:
|
||
|
free_model_list(e->kind.and.set);
|
||
|
default:
|
||
|
;
|
||
|
}
|
||
|
|
||
|
sgml_free(e);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
do_free_state_engine(dtd_state *state, visited *visited)
|
||
|
{ transition *t, *next;
|
||
|
|
||
|
for(t=state->transitions; t; t=next)
|
||
|
{ next = t->next;
|
||
|
|
||
|
if ( visit(t->state, visited) )
|
||
|
do_free_state_engine(t->state, visited);
|
||
|
|
||
|
sgml_free(t);
|
||
|
}
|
||
|
|
||
|
if ( state->expander )
|
||
|
free_expander(state->expander, visited);
|
||
|
|
||
|
sgml_free(state);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
free_state_engine(dtd_state *state)
|
||
|
{ if ( state )
|
||
|
{ visited visited;
|
||
|
visited.size = 0;
|
||
|
|
||
|
visit(state, &visited);
|
||
|
do_free_state_engine(state, &visited);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|