/* * xml_tok.c -- pull based xml tokenizer * * Copyright (C) 2002 Øyvind Kolås * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, or (at your option) any later * version. * * This program 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include "xml_tok.h" enum { s_null = 0, s_start, s_tag, s_tagnamestart, s_tagname, s_tagnamedone, s_intag, s_attstart, s_attname, s_attdone, s_att, s_atteq, s_eqquot, s_eqvalstart, s_eqapos, s_eqaposval, s_eqaposvaldone, s_eqval, s_eqvaldone, s_eqquotval, s_eqquotvaldone, s_tagend, s_empty, s_emptyend, s_whitespace, s_whitespacedone, s_entitystart, s_entity, s_entitydone, s_word, s_worddone, s_tagclose, s_tagclosenamestart, s_tagclosename, s_tagclosedone, s_tagexcl, s_commentdash1, s_commentdash2, s_incomment, s_commentenddash1, s_commentenddash2, s_commentdone, s_dtd, s_prolog, s_prologq, s_prologdone, s_eof, s_error }; char *c_ws = " \n\r\t"; enum { c_nil = 0, c_eat = 1, /* request that another char be used for the next state */ c_store = 2 /* store the current char in the output buffer */ }; typedef struct { int state; char *chars; unsigned char r_start; unsigned char r_end; int next_state; int resetbuf; int charhandling; int return_type; /* if set return current buf, with type set to the type */ } state_entry; #define max_entries 20 static state_entry state_table[s_error][max_entries]; static void a (int state, char *chars, unsigned char r_start, unsigned char r_end, int charhandling, int next_state) { int no = 0; while (state_table[state][no].state != s_null) no++; state_table[state][no].state = state; state_table[state][no].r_start = r_start; if (chars) state_table[state][no].chars = strdup (chars); state_table[state][no].r_end = r_end; state_table[state][no].charhandling = charhandling; state_table[state][no].next_state = next_state; } static void r (int state, int return_type, int next_state) { state_table[state][0].state = state; state_table[state][0].return_type = return_type; state_table[state][0].next_state = next_state; } /* *INDENT-OFF* */ void init_statetable(void){ static int inited=0; if(inited)return; inited=1; memset(state_table,0,sizeof(state_table)); a(s_start, "<", 0,0, c_eat, s_tag); a(s_start, c_ws, 0,0, c_eat+c_store, s_whitespace); a(s_start, "&", 0,0, c_eat, s_entitystart); a(s_start, NULL, 0,255, c_eat+c_store, s_word); a(s_tag, c_ws, 0,0, c_eat, s_tag); a(s_tag, "/", 0,0, c_eat, s_tagclose); a(s_tag, "!", 0,0, c_eat, s_tagexcl); a(s_tag, "?", 0,0, c_eat, s_prolog); a(s_tag, NULL, 0,255, c_eat+c_store, s_tagnamestart); a(s_tagclose, NULL, 0,255, c_eat+c_store, s_tagclosenamestart); a(s_tagclosenamestart, ">", 0,0, c_eat, s_tagclosedone); a(s_tagclosenamestart, NULL, 0,255, c_eat+c_store, s_tagclosename); a(s_tagclosename, ">", 0,0, c_eat, s_tagclosedone); a(s_tagclosename, NULL, 0,255, c_eat+c_store, s_tagclosename); r(s_tagclosedone, t_closetag, s_start); a(s_whitespace, c_ws, 0,0, c_eat+c_store, s_whitespace); a(s_whitespace, NULL, 0,255, c_nil, s_whitespacedone); r(s_whitespacedone, t_whitespace, s_start); a(s_entitystart,";", 0,0, c_eat, s_entitydone); a(s_entitystart,NULL, 0,255, c_eat+c_store, s_entity); a(s_entity, ";", 0,0, c_eat, s_entitydone); a(s_entity,NULL, 0,255, c_eat+c_store, s_entity); r(s_entitydone, t_entity, s_start); a(s_word, c_ws, 0,0, c_nil, s_worddone); a(s_word, "<&", 0,0, c_nil, s_worddone); a(s_word, NULL, 0,255, c_eat+c_store, s_word); r(s_worddone, t_word, s_start); a(s_tagnamestart,c_ws, 0,0, c_nil, s_tagnamedone); a(s_tagnamestart, "/>", 0,0, c_nil, s_tagnamedone); a(s_tagnamestart,NULL, 0,255, c_eat+c_store, s_tagname); a(s_tagname, c_ws, 0,0, c_nil, s_tagnamedone); a(s_tagname, "/>", 0,0, c_nil, s_tagnamedone); a(s_tagname, NULL, 0,255, c_eat+c_store, s_tagname); r(s_tagnamedone, t_tag, s_intag); a(s_intag, c_ws, 0,0, c_eat, s_intag); a(s_intag, ">", 0,0, c_eat, s_tagend); a(s_intag, "/", 0,0, c_eat, s_empty); a(s_intag, NULL, 0,255, c_eat+c_store, s_attstart); a(s_attstart, c_ws, 0,0, c_eat, s_attdone); a(s_attstart, "=/>", 0,0, c_nil, s_attdone); a(s_attstart, NULL, 0,255, c_eat+c_store, s_attname); a(s_attname, "=/>", 0,0, c_nil, s_attdone); a(s_attname, c_ws, 0,0, c_eat, s_attdone); a(s_attname, NULL, 0,255, c_eat+c_store, s_attname); r(s_attdone, t_att, s_att); a(s_att, c_ws, 0,0, c_eat, s_att); a(s_att, "=", 0,0, c_eat, s_atteq); a(s_att, NULL, 0,255, c_eat, s_intag); a(s_atteq, "'", 0,0, c_eat, s_eqapos); a(s_atteq, "\"", 0,0, c_eat, s_eqquot); a(s_atteq, c_ws, 0,0, c_eat, s_atteq); a(s_atteq, NULL, 0,255, c_nil, s_eqval); a(s_eqapos, "'", 0,0, c_eat, s_eqaposvaldone); a(s_eqapos, NULL, 0,255, c_eat+c_store, s_eqaposval); a(s_eqaposval, "'", 0,0, c_eat, s_eqaposvaldone); a(s_eqaposval, NULL, 0,255, c_eat+c_store, s_eqaposval); r(s_eqaposvaldone, t_val, s_intag); a(s_eqquot, "\"", 0,0, c_eat, s_eqquotvaldone); a(s_eqquot, NULL, 0,255, c_eat+c_store, s_eqquotval); a(s_eqquotval, "\"", 0,0, c_eat, s_eqquotvaldone); a(s_eqquotval, NULL, 0,255, c_eat+c_store, s_eqquotval); r(s_eqquotvaldone, t_val, s_intag); a(s_eqval, c_ws, 0,0, c_nil, s_eqvaldone); a(s_eqval, "/>", 0,0, c_nil, s_eqvaldone); a(s_eqval, NULL, 0,255, c_eat+c_store, s_eqval); r(s_eqvaldone, t_val, s_intag); r(s_tagend, t_endtag, s_start); a(s_empty, ">",0,0, c_eat, s_emptyend); a(s_empty, NULL,0,255, c_eat, s_empty); r(s_emptyend, t_closeemptytag, s_start); a(s_prolog, "?",0,0, c_eat, s_prologq); a(s_prolog, NULL,0,255, c_eat+c_store, s_prolog); a(s_prologq, ">",0,0, c_eat, s_prologdone); a(s_prologq, NULL,0,255, c_eat+c_store, s_prolog); r(s_prologdone, t_prolog, s_start); a(s_tagexcl, "-",0,0, c_eat, s_commentdash1); a(s_tagexcl, "D",0,0, c_nil, s_dtd); a(s_tagexcl, NULL,0,255, c_eat, s_start); a(s_commentdash1, "-",0,0, c_eat, s_commentdash2); a(s_commentdash1, NULL,0,255, c_eat, s_error); a(s_commentdash2, "-",0,0, c_eat, s_commentenddash1); a(s_commentdash2, NULL,0,255, c_eat+c_store, s_incomment); a(s_incomment , "-",0,0, c_eat, s_commentenddash1); a(s_incomment , NULL,0,255, c_eat+c_store, s_incomment); a(s_commentenddash1, "-",0,0, c_eat, s_commentenddash2); a(s_commentenddash1, NULL,0,255, c_eat+c_store, s_incomment); a(s_commentenddash2, ">",0,0, c_eat, s_commentdone); a(s_commentenddash2, NULL,0,255, c_eat+c_store, s_incomment); r(s_commentdone, t_comment, s_start); } /* *INDENT-ON* */ static int is_oneof (char c, char *chars) { while (*chars) { if (c == *chars) return 1; chars++; } return 0; } static int nextchar (xml_tok_state * t) { int ret; if(! (t->inbufposinbuflen) ){ t->inbuflen= fread(t->inbuf,1,inbufsize,t->file_in); t->inbufpos=0; if(!t->inbuflen) return -1; } ret=(int) t->inbuf[t->inbufpos++]; if(ret=='\n') t->line_no++; return ret; } int xml_tok_get (xml_tok_state * t, char **data) { int rbuflen = 0; state_entry *s; init_statetable (); t->rbuf[rbuflen] = 0; while (2 + 2 == 4) { if (!t->c_held) { t->c = nextchar (t); if (t->c == -1) return t_eof; t->c_held = 1; } if (t->state == s_dtd) { /* FIXME: should make better code for skipping DTD */ /* int angle = 0;*/ int squote = 0; int dquote = 0; int abracket = 1; /* int sbracket = 0;*/ t->rbuf[rbuflen++] = t->c; t->rbuf[rbuflen] = 0; while (abracket) { switch (t->c = nextchar (t)) { case -1: return t_eof; case '<': if ((!squote) && (!dquote)) abracket++; break; case '>': if ((!squote) && (!dquote)) abracket--; break; case '"': case '\'': case '[': case ']': default: break; } t->rbuf[rbuflen++] = t->c; t->rbuf[rbuflen] = 0; } t->c_held = 0; t->state = s_start; t->rbuf[--rbuflen] = 0; return t_dtd; } s = &state_table[t->state][0]; while (s->state) { if (s->return_type != t_none) { *data = t->rbuf; t->state = s->next_state; if (s->return_type == t_tag) strcpy (t->curtag, t->rbuf); if (s->return_type == t_endtag) *data = t->curtag; if (s->return_type == t_closeemptytag) *data = t->curtag; return s->return_type; } if ((s->chars && is_oneof (t->c, s->chars)) || ((s->r_start + s->r_end) && (t->c >= s->r_start && t->c <= s->r_end))) { if (s->charhandling & c_store) { t->rbuf[rbuflen++] = t->c; t->rbuf[rbuflen] = 0; } if (s->charhandling & c_eat) { t->c_held = 0; } t->state = s->next_state; break; } s++; } } return t_eof; } xml_tok_state *xml_tok_init (FILE * file_in) { xml_tok_state *ret; ret = calloc (1, sizeof (xml_tok_state)); ret->file_in = file_in; ret->state = s_start; return ret; } void xml_tok_cleanup (xml_tok_state * t) { free (t); } char *empty_tags[] = { "img", "IMG", "br", "BR", "hr", "HR", "META", "meta", "link", "LINK", NULL }; char *endomission_tags[] = { "li", "LI", "p", "P", "td", "TD", "tr", "TR", NULL }; int string_is_oneof (char *s, char **ss) { while (*ss) { if (!strcmp (s, *ss)) return 1; ss++; } return 0; } int html_tok_get (xml_tok_state * s, char **data) { static int got_a_stored_tag = 0; static char stored_tag[4096]; static int stored_type = t_eof; static char opentags[64][64]; static int level = 0; char *rdata; int type; if (got_a_stored_tag) { got_a_stored_tag = 0; *data = (char *) &stored_tag; return stored_type; } type = xml_tok_get (s, &rdata); switch (type) { case t_tag: if (level >= 0) { if (!strcmp (opentags[level - 1], rdata)) { got_a_stored_tag = 1; strcpy (stored_tag, rdata); stored_type = type; return t_closetag; } } strcpy (opentags[level], rdata); level++; break; case t_endtag: *data = rdata; if (string_is_oneof (rdata, empty_tags)) { level--; return t_closeemptytag; } break; case t_closeemptytag: case t_closetag: /* FIXME: do more than one level */ level--; if (strcmp (opentags[level], rdata)) { fprintf (stderr, "%s/%s\n", opentags[level], rdata); got_a_stored_tag = 1; stored_type = t_closetag; strcpy (stored_tag, rdata); level--; return t_closetag; } break; default: break; } *data = rdata; return type; }