mirror of
https://github.com/rkd77/elinks.git
synced 2025-02-02 15:09:23 -05:00
410 lines
11 KiB
C
410 lines
11 KiB
C
/* SGML token scanner utilities */
|
|
/* $Id: scanner.c,v 1.11 2004/11/08 19:27:22 jonas Exp $ */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "elinks.h"
|
|
|
|
#include "document/sgml/scanner.h"
|
|
#include "util/error.h"
|
|
#include "util/scanner.h"
|
|
#include "util/string.h"
|
|
|
|
|
|
/* Bitmap entries for the SGML character groups used in the scanner table */
|
|
|
|
/* The SGML tokenizer maintains a state that can be either text or element
|
|
* state. The state has only meaning while doing the actual scanning and is not
|
|
* accessible at the parsing time. */
|
|
enum sgml_scanner_state {
|
|
SGML_STATE_TEXT,
|
|
SGML_STATE_ELEMENT,
|
|
};
|
|
|
|
enum sgml_char_group {
|
|
SGML_CHAR_ENTITY = (1 << 1),
|
|
SGML_CHAR_IDENT = (1 << 2),
|
|
SGML_CHAR_NEWLINE = (1 << 3),
|
|
SGML_CHAR_WHITESPACE = (1 << 4),
|
|
SGML_CHAR_NOT_TEXT = (1 << 5),
|
|
SGML_CHAR_NOT_ATTRIBUTE = (1 << 6),
|
|
};
|
|
|
|
static struct scan_table_info sgml_scan_table_info[] = {
|
|
SCAN_TABLE_RANGE("0", '9', SGML_CHAR_IDENT | SGML_CHAR_ENTITY),
|
|
SCAN_TABLE_RANGE("A", 'Z', SGML_CHAR_IDENT | SGML_CHAR_ENTITY),
|
|
SCAN_TABLE_RANGE("a", 'z', SGML_CHAR_IDENT | SGML_CHAR_ENTITY),
|
|
/* For the octal number impared (me including) \241 is 161 --jonas */
|
|
SCAN_TABLE_RANGE("\241", 255, SGML_CHAR_IDENT | SGML_CHAR_ENTITY),
|
|
|
|
SCAN_TABLE_STRING("-_:.", SGML_CHAR_IDENT | SGML_CHAR_ENTITY),
|
|
SCAN_TABLE_STRING("#", SGML_CHAR_ENTITY),
|
|
SCAN_TABLE_STRING(" \f\n\r\t\v", SGML_CHAR_WHITESPACE),
|
|
SCAN_TABLE_STRING("\f\n\r", SGML_CHAR_NEWLINE),
|
|
SCAN_TABLE_STRING("<&", SGML_CHAR_NOT_TEXT),
|
|
SCAN_TABLE_STRING("<=>", SGML_CHAR_NOT_ATTRIBUTE),
|
|
|
|
SCAN_TABLE_END,
|
|
};
|
|
|
|
static struct scanner_string_mapping sgml_string_mappings[] = {
|
|
{ "--", SGML_TOKEN_NOTATION_COMMENT, SGML_TOKEN_NOTATION },
|
|
{ "ATTLIST", SGML_TOKEN_NOTATION_ATTLIST, SGML_TOKEN_NOTATION },
|
|
{ "DOCTYPE", SGML_TOKEN_NOTATION_DOCTYPE, SGML_TOKEN_NOTATION },
|
|
{ "ELEMENT", SGML_TOKEN_NOTATION_ELEMENT, SGML_TOKEN_NOTATION },
|
|
{ "ENTITY", SGML_TOKEN_NOTATION_ENTITY, SGML_TOKEN_NOTATION },
|
|
|
|
{ "xml", SGML_TOKEN_PROCESS_XML, SGML_TOKEN_PROCESS },
|
|
|
|
{ NULL, SGML_TOKEN_NONE, SGML_TOKEN_NONE },
|
|
};
|
|
|
|
static struct scanner_token *scan_sgml_tokens(struct scanner *scanner);
|
|
|
|
struct scanner_info sgml_scanner_info = {
|
|
sgml_string_mappings,
|
|
sgml_scan_table_info,
|
|
scan_sgml_tokens,
|
|
};
|
|
|
|
#define check_sgml_table(c, bit) (sgml_scanner_info.scan_table[(c)] & (bit))
|
|
|
|
#define scan_sgml(scanner, s, bit) \
|
|
while ((s) < (scanner)->end && check_sgml_table(*(s), bit)) (s)++;
|
|
|
|
#define is_sgml_ident(c) check_sgml_table(c, SGML_CHAR_IDENT)
|
|
#define is_sgml_entity(c) check_sgml_table(c, SGML_CHAR_ENTITY)
|
|
#define is_sgml_space(c) check_sgml_table(c, SGML_CHAR_WHITESPACE)
|
|
#define is_sgml_text(c) !check_sgml_table(c, SGML_CHAR_NOT_TEXT)
|
|
#define is_sgml_token_start(c) check_sgml_table(c, SGML_CHAR_TOKEN_START)
|
|
#define is_sgml_attribute(c) !check_sgml_table(c, SGML_CHAR_NOT_ATTRIBUTE | SGML_CHAR_WHITESPACE)
|
|
|
|
|
|
/* Text token scanning */
|
|
|
|
/* I think it is faster to not check the table here --jonas */
|
|
#define foreach_sgml_cdata(scanner, str) \
|
|
for (; ((str) < (scanner)->end && *(str) != '<' && *(str) != '&'); (str)++)
|
|
|
|
static inline void
|
|
scan_sgml_text_token(struct scanner *scanner, struct scanner_token *token)
|
|
{
|
|
unsigned char *string = scanner->position;
|
|
unsigned char first_char = *string;
|
|
enum sgml_token_type type = SGML_TOKEN_GARBAGE;
|
|
int real_length = -1;
|
|
|
|
/* In scan_sgml_tokens() we check that first_char != '<' */
|
|
assert(first_char != '<' && scanner->state == SGML_STATE_TEXT);
|
|
|
|
token->string = string++;
|
|
|
|
if (first_char == '&') {
|
|
if (is_sgml_entity(*string)) {
|
|
scan_sgml(scanner, string, SGML_CHAR_ENTITY);
|
|
type = SGML_TOKEN_ENTITY;
|
|
token->string++;
|
|
real_length = string - token->string;
|
|
}
|
|
|
|
foreach_sgml_cdata (scanner, string) {
|
|
if (*string == ';') {
|
|
string++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if (is_sgml_space(first_char)) {
|
|
scan_sgml(scanner, string, SGML_CHAR_WHITESPACE);
|
|
type = string < scanner->end && is_sgml_text(*string)
|
|
? SGML_TOKEN_TEXT : SGML_TOKEN_SPACE;
|
|
} else {
|
|
type = SGML_TOKEN_TEXT;
|
|
}
|
|
|
|
foreach_sgml_cdata (scanner, string) {
|
|
/* m33p */;
|
|
}
|
|
}
|
|
|
|
token->type = type;
|
|
token->length = real_length >= 0 ? real_length : string - token->string;
|
|
token->precedence = get_sgml_precedence(type);
|
|
scanner->position = string;
|
|
}
|
|
|
|
|
|
/* Element scanning */
|
|
|
|
/* Check whether it is safe to skip the @token when looking for @skipto. */
|
|
static inline int
|
|
check_sgml_precedence(int type, int skipto)
|
|
{
|
|
return get_sgml_precedence(type) <= get_sgml_precedence(skipto);
|
|
}
|
|
|
|
/* XXX: Only element or ``in tag'' precedence is handled correctly however
|
|
* using this function for CDATA or text would be overkill. */
|
|
static inline unsigned char *
|
|
skip_sgml(struct scanner *scanner, unsigned char **string, unsigned char skipto,
|
|
int check_quoting)
|
|
{
|
|
unsigned char *pos = *string;
|
|
|
|
for (; pos < scanner->end; pos++) {
|
|
if (*pos == skipto) {
|
|
*string = pos + 1;
|
|
return pos;
|
|
}
|
|
|
|
if (!check_sgml_precedence(*pos, skipto))
|
|
break;
|
|
|
|
if (check_quoting && isquote(*pos)) {
|
|
int length = scanner->end - pos;
|
|
unsigned char *end = memchr(pos + 1, *pos, length);
|
|
|
|
if (end) pos = end;
|
|
}
|
|
}
|
|
|
|
*string = pos;
|
|
return NULL;
|
|
}
|
|
|
|
static inline int
|
|
skip_comment(struct scanner *scanner, unsigned char **string)
|
|
{
|
|
unsigned char *pos = *string;
|
|
int length = 0;
|
|
|
|
for (; pos < scanner->end - 3; pos++)
|
|
if (pos[0] == '-' && pos[1] == '-' && pos[2] == '>') {
|
|
length = pos - *string;
|
|
pos += 3;
|
|
break;
|
|
}
|
|
|
|
*string = pos;
|
|
return length;
|
|
}
|
|
|
|
#define scan_sgml_attribute(scanner, str) \
|
|
while ((str) < (scanner)->end && is_sgml_attribute(*(str))) \
|
|
(str)++;
|
|
|
|
static inline void
|
|
scan_sgml_element_token(struct scanner *scanner, struct scanner_token *token)
|
|
{
|
|
unsigned char *string = scanner->position;
|
|
unsigned char first_char = *string;
|
|
enum sgml_token_type type = SGML_TOKEN_GARBAGE;
|
|
int real_length = -1;
|
|
|
|
token->string = string++;
|
|
|
|
if (first_char == '<') {
|
|
scan_sgml(scanner, string, SGML_CHAR_WHITESPACE);
|
|
|
|
if (scanner->state == SGML_STATE_ELEMENT) {
|
|
/* Already inside an element so insert a tag end token
|
|
* and continue scanning in next iteration. */
|
|
string--;
|
|
real_length = 0;
|
|
type = SGML_TOKEN_TAG_END;
|
|
scanner->state = SGML_STATE_TEXT;
|
|
|
|
} else if (is_sgml_ident(*string)) {
|
|
token->string = string;
|
|
scan_sgml(scanner, string, SGML_CHAR_IDENT);
|
|
|
|
real_length = string - token->string;
|
|
|
|
scan_sgml(scanner, string, SGML_CHAR_WHITESPACE);
|
|
if (*string == '>') {
|
|
type = SGML_TOKEN_ELEMENT;
|
|
string++;
|
|
} else {
|
|
scanner->state = SGML_STATE_ELEMENT;
|
|
type = SGML_TOKEN_ELEMENT_BEGIN;
|
|
}
|
|
|
|
} else if (*string == '!') {
|
|
unsigned char *ident;
|
|
enum sgml_token_type base = SGML_TOKEN_NOTATION;
|
|
|
|
string++;
|
|
scan_sgml(scanner, string, SGML_CHAR_WHITESPACE);
|
|
token->string = ident = string;
|
|
|
|
if (string + 1 < scanner->end
|
|
&& string[0] == '-' && string[1] == '-') {
|
|
string += 2;
|
|
type = SGML_TOKEN_NOTATION_COMMENT;
|
|
token->string = string;
|
|
real_length = skip_comment(scanner, &string);
|
|
assert(real_length >= 0);
|
|
} else {
|
|
scan_sgml(scanner, string, SGML_CHAR_IDENT);
|
|
type = map_scanner_string(scanner, ident, string, base);
|
|
skip_sgml(scanner, &string, '>', 0);
|
|
}
|
|
|
|
} else if (*string == '?') {
|
|
unsigned char *pos;
|
|
enum sgml_token_type base = SGML_TOKEN_PROCESS;
|
|
|
|
string++;
|
|
scan_sgml(scanner, string, SGML_CHAR_WHITESPACE);
|
|
token->string = pos = string;
|
|
scan_sgml(scanner, string, SGML_CHAR_IDENT);
|
|
|
|
type = map_scanner_string(scanner, pos, string, base);
|
|
|
|
/* Figure out where the processing instruction ends */
|
|
for (pos = string; skip_sgml(scanner, &pos, '>', 0); ) {
|
|
if (pos[-2] != '?') continue;
|
|
|
|
/* Set length until '?' char and move position
|
|
* beyond '>'. */
|
|
real_length = pos - token->string - 2;
|
|
break;
|
|
}
|
|
|
|
switch (type) {
|
|
case SGML_TOKEN_PROCESS_XML:
|
|
/* We want to parse the attributes */
|
|
assert(scanner->state != SGML_STATE_ELEMENT);
|
|
scanner->state = SGML_STATE_ELEMENT;
|
|
break;
|
|
|
|
default:
|
|
/* Just skip the whole thing */
|
|
string = pos;
|
|
}
|
|
|
|
} else if (*string == '/') {
|
|
string++;
|
|
scan_sgml(scanner, string, SGML_CHAR_WHITESPACE);
|
|
|
|
if (is_sgml_ident(*string)) {
|
|
token->string = string;
|
|
scan_sgml(scanner, string, SGML_CHAR_IDENT);
|
|
real_length = string - token->string;
|
|
|
|
type = SGML_TOKEN_ELEMENT_END;
|
|
skip_sgml(scanner, &string, '>', 1);
|
|
|
|
} else if (*string == '>') {
|
|
string++;
|
|
real_length = 0;
|
|
type = SGML_TOKEN_ELEMENT_END;
|
|
}
|
|
|
|
if (type != SGML_TOKEN_GARBAGE)
|
|
scanner->state = SGML_STATE_TEXT;
|
|
|
|
} else {
|
|
/* Alien < > stuff so ignore it */
|
|
skip_sgml(scanner, &string, '>', 0);
|
|
}
|
|
|
|
} else if (first_char == '=') {
|
|
type = '=';
|
|
|
|
} else if (first_char == '?' || first_char == '>') {
|
|
if (first_char == '?') {
|
|
skip_sgml(scanner, &string, '>', 0);
|
|
}
|
|
|
|
type = SGML_TOKEN_TAG_END;
|
|
assert(scanner->state == SGML_STATE_ELEMENT);
|
|
scanner->state = SGML_STATE_TEXT;
|
|
|
|
} else if (first_char == '/') {
|
|
if (*string == '>') {
|
|
string++;
|
|
real_length = 0;
|
|
type = SGML_TOKEN_ELEMENT_EMPTY_END;
|
|
assert(scanner->state == SGML_STATE_ELEMENT);
|
|
scanner->state = SGML_STATE_TEXT;
|
|
} else if (is_sgml_attribute(*string)) {
|
|
scan_sgml_attribute(scanner, string);
|
|
type = SGML_TOKEN_ATTRIBUTE;
|
|
}
|
|
|
|
} else if (isquote(first_char)) {
|
|
int size = scanner->end - string;
|
|
unsigned char *string_end = memchr(string, first_char, size);
|
|
|
|
if (string_end) {
|
|
/* We don't want the delimiters in the token */
|
|
token->string++;
|
|
real_length = string_end - token->string;
|
|
string = string_end + 1;
|
|
type = SGML_TOKEN_STRING;
|
|
} else if (is_sgml_attribute(*string)) {
|
|
token->string++;
|
|
scan_sgml_attribute(scanner, string);
|
|
type = SGML_TOKEN_ATTRIBUTE;
|
|
}
|
|
|
|
} else if (is_sgml_attribute(first_char)) {
|
|
if (is_sgml_ident(first_char)) {
|
|
scan_sgml(scanner, string, SGML_CHAR_IDENT);
|
|
type = SGML_TOKEN_IDENT;
|
|
}
|
|
|
|
if (is_sgml_attribute(*string)) {
|
|
scan_sgml_attribute(scanner, string);
|
|
type = SGML_TOKEN_ATTRIBUTE;
|
|
}
|
|
}
|
|
|
|
token->type = type;
|
|
token->length = real_length >= 0 ? real_length : string - token->string;
|
|
token->precedence = get_sgml_precedence(type);
|
|
scanner->position = string;
|
|
}
|
|
|
|
|
|
/* Scanner multiplexor */
|
|
|
|
static struct scanner_token *
|
|
scan_sgml_tokens(struct scanner *scanner)
|
|
{
|
|
struct scanner_token *table_end = scanner->table + SCANNER_TOKENS;
|
|
struct scanner_token *current;
|
|
|
|
if (!begin_token_scanning(scanner))
|
|
return get_scanner_token(scanner);
|
|
|
|
/* Scan tokens until we fill the table */
|
|
for (current = scanner->table + scanner->tokens;
|
|
current < table_end && scanner->position < scanner->end;
|
|
current++) {
|
|
if (scanner->state == SGML_STATE_ELEMENT
|
|
|| *scanner->position == '<') {
|
|
scan_sgml(scanner, scanner->position, SGML_CHAR_WHITESPACE);
|
|
if (scanner->position >= scanner->end) break;
|
|
|
|
scan_sgml_element_token(scanner, current);
|
|
|
|
/* Shall we scratch this token? */
|
|
if (current->type == SGML_TOKEN_SKIP) {
|
|
current--;
|
|
}
|
|
} else {
|
|
scan_sgml_text_token(scanner, current);
|
|
}
|
|
}
|
|
|
|
return end_token_scanning(scanner, current);
|
|
}
|