evil-qemacs/libqhtml/cssparse.c
Charlie Gordon ae163f897b fixed crash on charset_completion
renamed css_strtolower -> qe_strtolower
use offsetof for SAVED_DATA_SIZE macro (this stuff must go)
cosmetics in css_get_enum
remarks
fixed text-mode colorization bug: because of saved_data stuff,
  must reset colorization function after mode restore.  This saved data
  stuff is bogus and should be rewritten
smaller version of roman numeral converter
fixed alpha converter
smaller implementation of css_counter_str
fixed list_mode name: no longer generates extra mode completion entry
2008-01-07 16:25:22 +00:00

1191 lines
34 KiB
C

/*
* CSS2 parser for qemacs.
* Copyright (c) 2000, 2001, 2002 Fabrice Bellard.
*
* 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 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 "qe.h"
#include "qfribidi.h"
#include "css.h"
//#define DEBUG
#ifdef DEBUG
#define dprintf(fmt, args...) printf(fmt, ##args)
#else
#define dprintf(fmt, args...)
#define NDEBUG
#endif
#define ddprintf(fmt, args...)
#include <assert.h>
static void css_error1(CSSParseState *b, const char *fmt, ...)
__attr_printf(2,3);
static void css_error1(CSSParseState *b, const char *fmt, ...)
{
char buf[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
css_error(b->filename, b->line_num, buf);
va_end(ap);
}
/***********************************************************/
/* CSS property parser */
/* return a length and its unit. some normalization is done to limit
the number of units. Return non zero if error. */
static int css_get_length(int *length_ptr, int *unit_ptr, const char *p)
{
int num, len, unit;
const char *p1;
char buf[32];
float f;
p1 = p;
if (*p == '+' || *p == '-')
p++;
while (qe_isdigit(*p))
p++;
if (*p == '.') {
p++;
while (qe_isdigit(*p))
p++;
}
len = p - p1;
if (len == 0)
return -1;
/* CG: use pstrncpy */
if (len > (int)sizeof(buf) - 1)
len = (int)sizeof(buf) - 1;
memcpy(buf, p1, len);
buf[len] = '\0';
f = strtod(buf, NULL);
/* CG: should use define or global for consistency with
* CSS_UNIT_xxx values */
unit = css_get_enum(p, "px,%,ex,em,mm,in,cm,pt,pc");
if (unit < 0) {
/* only 0 is valid without unit */
if (f != 0.0 || *p != '\0')
return -1;
num = 0;
unit = CSS_UNIT_NONE;
} else {
unit++;
if (f < 0 && unit != CSS_UNIT_PERCENT)
return -1;
switch (unit) {
case CSS_UNIT_PIXEL:
num = (int)(f);
break;
case CSS_UNIT_PERCENT:
if (f < 0) {
/* CG: ain't wrong ? */
if (f >= 100)
return -1;
f = 100 - f;
}
num = (int)(f * (0.01 * CSS_LENGTH_FRAC_BASE));
break;
case CSS_UNIT_EX:
case CSS_UNIT_EM:
num = (int)(f * CSS_LENGTH_FRAC_BASE);
break;
case CSS_UNIT_IN:
phys_unit:
unit = CSS_UNIT_IN;
num = (int)(f * CSS_LENGTH_FRAC_BASE);
break;
case CSS_UNIT_MM:
f = f / 25.4;
goto phys_unit;
case CSS_UNIT_CM:
f = f / 2.54;
goto phys_unit;
case CSS_UNIT_PT:
f = f / 72.0;
goto phys_unit;
case CSS_UNIT_PC:
f = f * 12.0 / 72.0;
goto phys_unit;
default:
return -1;
}
}
*length_ptr = num;
*unit_ptr = unit;
return 0;
}
/* return the font size in CSS_UNIT_IN corresponding to size 0 <= i <= 6 */
/* XXX: change name */
int get_font_size(int i)
{
int val;
val = (14 * CSS_LENGTH_FRAC_BASE) / 72;
i -= 2;
/* compute 1.2^(i-2) */
while (i > 0) {
val = (val * 12) / 10;
i--;
}
while (i < 0) {
val = (val * 10) / 12;
i++;
}
return val;
}
/* XXX: expand string dynamically */
/* XXX: end of line ? */
static char *css_parse_string(const char **pp)
{
char buf[4096], *q;
const char *p;
int sep, c;
p = *pp;
q = buf;
sep = *p++;
for (;;) {
c = *p++;
if (c == sep || c == '\0')
break;
if (c == '\\') {
c = *p++;
if (c == 'A') {
c = CSS_CONTENT_EOL;
}
/* XXX: hex digits */
}
if ((q - buf) < (int)sizeof(buf) - 1)
*q++ = c;
}
*q = '\0';
// printf("string='%s'\n", buf);
*pp = p;
return qe_strdup(buf);
}
/* add a given number of values */
void css_add_prop_values(CSSProperty ***last_prop,
int property_index,
int nb_values, CSSPropertyValue *val_ptr)
{
CSSProperty *prop;
prop = qe_malloc_hack(CSSProperty,
(nb_values - 1) * sizeof(CSSPropertyValue));
if (!prop)
return;
prop->property = property_index;
prop->next = NULL;
prop->nb_values = nb_values;
memcpy(&prop->value, val_ptr, nb_values * sizeof(CSSPropertyValue));
**last_prop = prop;
*last_prop = &prop->next;
}
void css_add_prop(CSSProperty ***last_prop,
int property_index, CSSPropertyValue *val_ptr)
{
css_add_prop_values(last_prop, property_index, 1, val_ptr);
}
void css_add_prop_unit(CSSProperty ***last_prop,
int property_index, int type, int val)
{
CSSPropertyValue value;
value.type = type;
value.u.val = val;
css_add_prop(last_prop, property_index, &value);
}
void css_add_prop_int(CSSProperty ***last_prop,
int property_index, int val)
{
css_add_prop_unit(last_prop, property_index, CSS_UNIT_NONE, val);
}
static const char border_style_enum[] =
"none,hidden,dotted,dashed,solid,double,groove,ridge,inset,outset";
static const char list_style_enum[] =
"disc,circle,square,decimal,lower-alpha,upper-alpha,lower-roman,upper-roman,none";
#define MAX_ARGS 32
/* parse the properties and return a list of properties. NOTE: 'b' is
only used for error reporting */
CSSProperty *css_parse_properties(CSSParseState *b, const char *props_str)
{
const char *p;
char property[64];
char buf[1024], buf2[64];
int property_index, type, val, nb_args, i, unit;
int property_index1;
CSSPropertyValue args[MAX_ARGS];
CSSProperty **last_prop, *first_prop;
const CSSPropertyDef *def;
val = 0;
first_prop = NULL;
last_prop = &first_prop;
p = props_str;
for (;;) {
get_str(&p, property, sizeof(property), ":");
if (*p == '\0')
break;
if (*p == ':')
p++;
skip_spaces(&p);
/* find the property */
def = css_properties;
for (;;) {
if (def >= css_properties + NB_PROPERTIES) {
css_error1(b, "unsupported property '%s'", property);
/* property not found skip it: find next ';' */
while (*p && *p != ';')
p++;
goto next;
}
if (!strcmp(def->name, property))
break;
def++;
}
property_index = def - css_properties;
type = def->type;
nb_args = 0;
for (;;) {
/* get argument */
skip_spaces(&p);
if (*p == ';' || *p == '\0')
break;
/* more than 1 argument only if wanted */
if (nb_args >= 1 &&
!(type & (CSS_TYPE_FOUR|CSS_TYPE_TWO|CSS_TYPE_ARGS)))
break;
if (nb_args >= 2 &&
!(type & (CSS_TYPE_FOUR|CSS_TYPE_ARGS)))
break;
if (nb_args >= 4 &&
(!type & CSS_TYPE_ARGS))
break;
if (nb_args >= MAX_ARGS)
break;
if (*p == '\"' || *p == '\'') {
/* string parsing */
/* if no string expected, continue parsing */
if (!(type & CSS_TYPE_STRING))
goto next;
args[nb_args].u.str = css_parse_string(&p);
unit = CSS_VALUE_STRING;
goto got_val;
}
if (type & CSS_TYPE_ATTR) {
/* attr(x) support */
if (strstart(p, "attr(", &p)) {
get_str(&p, buf, sizeof(buf), ");");
if (buf[0] != '\0') {
if (*p != ')')
goto next;
p++;
if (b->ignore_case)
qe_strtolower(buf, sizeof(buf), buf);
args[nb_args].u.attr_id = css_new_ident(buf);
unit = CSS_VALUE_ATTR;
goto got_val;
}
}
}
if (type & CSS_TYPE_COUNTER) {
/* counter(x[,type]) support */
if (strstart(p, "counter(", &p)) {
get_str(&p, buf, sizeof(buf), ",);");
args[nb_args].u.counter.type = CSS_LIST_STYLE_TYPE_DECIMAL;
if (*p == ',') {
p++;
get_str(&p, buf2, sizeof(buf2), ");");
val = css_get_enum(buf2, list_style_enum);
if (val >= 0)
args[nb_args].u.counter.type = val;
}
if (*p != ')')
goto next;
p++;
args[nb_args].u.counter.counter_id = css_new_ident(buf);
unit = CSS_VALUE_COUNTER;
goto got_val;
}
}
get_str(&p, buf, sizeof(buf), ";");
unit = CSS_UNIT_NONE;
if (type & CSS_TYPE_AUTO) {
if (!strcmp(buf, "auto")) {
val = CSS_AUTO;
goto got_val;
}
}
if (!(type & CSS_TYPE_NOINHERIT)) {
if (!strcmp(buf, "inherit")) {
val = CSS_INHERIT;
goto got_val;
}
}
if (type & CSS_TYPE_INTEGER) {
const char *p1;
val = strtol(buf, (char **)&p1, 0);
if (*p1 == '\0') {
unit = CSS_VALUE_INTEGER;
goto got_val;
}
}
if (type & CSS_TYPE_LENGTH) {
if (!css_get_length(&val, &unit, buf))
goto got_val;
}
if (type & CSS_TYPE_BORDER_STYLE) {
val = css_get_enum(buf, border_style_enum);
if (val >= 0)
goto got_val;
}
if (type & CSS_TYPE_LIST_STYLE) {
val = css_get_enum(buf, list_style_enum);
if (val >= 0)
goto got_val;
}
if (type & CSS_TYPE_ENUM) {
val = css_get_enum(buf, def->name + strlen(def->name) + 1);
if (val >= 0)
goto got_val;
}
if (type & CSS_TYPE_IDENT) {
val = css_new_ident(buf);
unit = CSS_VALUE_IDENT;
goto got_val;
}
if (type & CSS_TYPE_FONT_FAMILY) {
val = css_get_font_family(buf);
if (val == 0)
val = CSS_INHERIT;
goto got_val;
}
if (type & CSS_TYPE_COLOR) {
QEColor color;
/* XXX: color parsing is not always discriminant */
if (!css_get_color(&color, buf)) {
val = color;
unit = CSS_VALUE_COLOR;
goto got_val;
}
}
css_error1(b, "unrecognized value '%s' for property '%s'",
buf, def->name);
goto next;
got_val:
/* specific handling may be necessary. We do them here */
switch (property_index) {
case CSS_font_size:
if (unit == CSS_UNIT_NONE) {
if (val == 7) {
/* smaller */
unit = CSS_UNIT_PERCENT;
val = (CSS_LENGTH_FRAC_BASE * 10) / 12;
} else if (val == 8) {
/* larger */
unit = CSS_UNIT_PERCENT;
val = (CSS_LENGTH_FRAC_BASE * 12) / 10;
} else if (val >= 0) {
unit = CSS_UNIT_IN;
val = get_font_size(val);
} else {
goto next;
}
}
break;
case CSS_border:
case CSS_border_left:
case CSS_border_top:
case CSS_border_right:
case CSS_border_bottom:
if (unit == CSS_VALUE_COLOR) {
property_index1 = property_index +
CSS_border_color - CSS_border;
} else if (unit == CSS_UNIT_NONE) {
property_index1 = property_index +
CSS_border_style - CSS_border;
} else {
property_index1 = property_index +
CSS_border_width - CSS_border;
}
args[0].type = unit;
args[0].u.val = val;
if (property_index == CSS_border) {
for (i = 0; i < 4; i++)
css_add_prop(&last_prop, property_index1 + 1 + i,
&args[0]);
} else {
css_add_prop(&last_prop, property_index1, &args[0]);
}
/* parse next args without storing them */
continue;
}
args[nb_args].type = unit;
if (unit != CSS_VALUE_STRING &&
unit != CSS_VALUE_ATTR &&
unit != CSS_VALUE_COUNTER) {
args[nb_args].u.val = val;
}
nb_args++;
}
if (type & CSS_TYPE_SPECIAL)
goto next;
if (type & CSS_TYPE_FOUR) {
CSSPropertyValue v1, v2, v3, v4;
/* handle specifically the four args case */
v1 = args[0];
switch (nb_args) {
case 1:
args[1] = args[2] = args[3] = v1;
break;
case 2:
v2 = args[1];
args[1] = args[3] = v1;
args[0] = args[2] = v2;
break;
case 3:
v2 = args[1];
v3 = args[2];
args[1] = v1;
args[0] = args[2] = v2;
args[3] = v3;
break;
case 4:
default:
v2 = args[1];
v3 = args[2];
v4 = args[3];
args[1] = v1;
args[2] = v2;
args[3] = v3;
args[0] = v4;
break;
}
for (i = 0; i < 4; i++)
css_add_prop(&last_prop, property_index + 1 + i, &args[i]);
} else if (type & CSS_TYPE_TWO) {
if (nb_args == 1)
args[1] = args[0];
for (i = 0; i < 2; i++)
css_add_prop(&last_prop, property_index + 1 + i, &args[i]);
} else if (type & CSS_TYPE_ARGS) {
/* unbounded number of args */
css_add_prop_values(&last_prop, property_index, nb_args, args);
} else {
css_add_prop(&last_prop, property_index, &args[0]);
}
next:
skip_spaces(&p);
if (*p != ';')
break;
p++;
}
return first_prop;
}
#ifdef DEBUG
void css_dump_properties(CSSProperty *prop)
{
const CSSPropertyDef *def;
int val, j;
CSSPropertyValue *value;
while (prop != NULL) {
def = css_properties + prop->property;
val = prop->value.u.val;
printf("%s: ", def->name);
if (val == CSS_AUTO) {
printf("auto");
} else if (val == CSS_INHERIT) {
printf("inherit");
} else {
value = &prop->value;
for (j = 0; j < prop-> nb_values;j++) {
val = value->u.val;
switch (value->type) {
case CSS_UNIT_EX:
printf("%0.1fex", (double)val / CSS_LENGTH_FRAC_BASE);
break;
case CSS_UNIT_EM:
printf("%0.1fem", (double)val / CSS_LENGTH_FRAC_BASE);
break;
case CSS_UNIT_PERCENT:
printf("%0.0f%%", (double)val / CSS_LENGTH_FRAC_BASE);
break;
case CSS_VALUE_COLOR:
printf("#%06X", val);
break;
case CSS_UNIT_NONE:
{
const char *p, *p1;
int i, len;
char buf[100];
if (def->type & CSS_TYPE_BORDER_STYLE)
p = border_style_enum;
else
p = def->name + strlen(def->name) + 1;
i = 0;
for (;;) {
p1 = strchr(p, ',');
if (i == val) {
if (!p1)
len = strlen(p);
else
len = p1 - p;
memcpy(buf, p, len);
buf[len] = '\0';
printf("%s", buf);
break;
}
if (!p1) {
printf("[%d]", val);
break;
}
i++;
p = p1 + 1;
}
}
break;
case CSS_VALUE_STRING:
printf("\"%s\"", value->u.str);
break;
case CSS_VALUE_ATTR:
printf("attr(%s)", css_ident_str(value->u.attr_id));
break;
case CSS_VALUE_COUNTER:
printf("counter(%s,%d)",
css_ident_str(value->u.counter.counter_id),
value->u.counter.type);
break;
case CSS_VALUE_INTEGER:
printf("%d", val);
break;
case CSS_VALUE_IDENT:
printf("%s", css_ident_str(value->u.attr_id));
break;
default:
printf("[%d]", val);
break;
}
if (j != (prop->nb_values - 1))
printf(" ");
value++;
}
}
printf("; ");
prop = prop->next;
}
}
#endif
CSSStyleSheet *css_new_style_sheet(void)
{
CSSStyleSheet *s;
s = qe_mallocz(CSSStyleSheet);
if (!s)
return NULL;
s->plast_entry = &s->first_entry;
return s;
}
static void free_selector(CSSSimpleSelector *ss)
{
CSSStyleSheetAttributeEntry *attr, *attr1;
for (attr = ss->attrs; attr != NULL; attr = attr1) {
attr1 = attr->next;
qe_free(&attr);
}
}
/* XXX: free idents too */
void css_free_style_sheet(CSSStyleSheet *s)
{
CSSStyleSheetEntry *e, *e1;
CSSProperty *p, *p1;
CSSSimpleSelector *ss, *ss1;
for (e = s->first_entry; e != NULL; e = e1) {
e1 = e->next;
for (ss = e->sel.next; ss != NULL; ss = ss1) {
ss1 = ss->next;
free_selector(ss);
qe_free(&ss);
}
free_selector(&e->sel);
for (p = e->props; p != NULL; p = p1) {
p1 = p->next;
qe_free(&p);
}
qe_free(&e);
}
qe_free(&s);
}
static int bgetc1(CSSParseState *b)
{
int ch;
ch = (unsigned char)*b->ptr;
if (ch) {
b->ptr++;
return ch;
} else {
return EOF;
}
}
static int bgetc(CSSParseState *b)
{
int ch, last;
for (;;) {
ch = bgetc1(b);
if (ch != '/')
return ch;
ch = bgetc1(b);
if (ch != '*') {
if (ch != EOF)
b->ptr--;
return '/';
}
last = 0;
for (;;) {
ch = bgetc1(b);
if (ch == EOF)
return ch;
if (ch == '/' && last == '*')
break;
last = ch;
}
}
}
/* XXX: read_string and read_ident not exactly CSS2 conformant */
static void read_string(CSSParseState *b, int *ch_ptr, char *ident, int ident_size)
{
int ch, quote;
char *q;
quote = *ch_ptr;
q = ident;
for (;;) {
ch = bgetc(b);
if (ch == quote)
break;
if ((q - ident) < ident_size - 1)
*q++ = ch;
}
*q = '\0';
ch = bgetc(b);
*ch_ptr = ch;
}
static void read_ident(CSSParseState *b, int *ch_ptr, char *ident, int ident_size)
{
char *q;
int c;
c = *ch_ptr;
q = ident;
for (;;) {
if (!(qe_isalnum_(c) || c == '*' || c == '-'))
break;
if ((q - ident) < ident_size - 1)
*q++ = c;
c = bgetc(b);
}
*q = '\0';
*ch_ptr = c;
}
static void bskip_spaces(CSSParseState *b, int *ch_ptr)
{
int c;
c = *ch_ptr;
while (qe_isspace(c))
c = bgetc(b);
*ch_ptr = c;
}
void add_attribute(CSSStyleSheetAttributeEntry ***last_attr,
CSSIdent attr, int op, const char *value)
{
CSSStyleSheetAttributeEntry *ae;
ae = qe_malloc_hack(CSSStyleSheetAttributeEntry, strlen(value));
ae->attr = attr;
ae->op = op;
strcpy(ae->value, value);
ae->next = NULL;
**last_attr = ae;
*last_attr = &ae->next;
}
/* add the tag in the hash table */
/* XXX: use ident hash table ? */
CSSStyleSheetEntry *add_style_entry(CSSStyleSheet *s,
CSSSimpleSelector *ss,
int media)
{
CSSStyleSheetEntry *e, **pp;
/* add the style sheet entry */
e = qe_mallocz(CSSStyleSheetEntry);
if (!e)
return NULL;
e->sel = *ss;
e->media = media;
/* add in entry list */
*s->plast_entry = e;
s->plast_entry = &e->next;
e->next = NULL;
/* add in tag hash table */
pp = &s->tag_hash[css_hash_ident(e->sel.tag, CSS_TAG_HASH_SIZE)];
while (*pp != NULL)
pp = &(*pp)->hash_next;
*pp = e;
e->hash_next = NULL;
return e;
}
/* copy a selector and its associated attributes */
static void dup_selector(CSSSimpleSelector *dest, CSSSimpleSelector *src)
{
CSSStyleSheetAttributeEntry *attr, *first_attr, **plast_attr;
first_attr = NULL;
plast_attr = &first_attr;
for (attr = src->attrs; attr != NULL; attr = attr->next)
add_attribute(&plast_attr, attr->attr,
attr->op, attr->value);
memcpy(dest, src, sizeof(CSSSimpleSelector));
dest->attrs = first_attr;
}
/* duplicate css properties */
static CSSProperty *dup_properties(CSSProperty *props)
{
CSSProperty *p, *first_p, **plast_p;
first_p = NULL;
plast_p = &first_p;
for (p = props; p != NULL; p = p->next) {
css_add_prop_values(&plast_p, p->property, p->nb_values, &p->value);
}
return first_p;
}
/* merge style sheet 'a' in 's' */
void css_merge_style_sheet(CSSStyleSheet *s, CSSStyleSheet *a)
{
CSSStyleSheetEntry *e, *e1;
CSSSimpleSelector *ss, *ss1, ss2, **pss;
for (e = a->first_entry; e != NULL; e = e->next) {
/* add selector */
dup_selector(&ss2, &e->sel);
e1 = add_style_entry(s, &ss2, e->media);
/* add selector operations */
pss = &e1->sel.next;
for (ss = e->sel.next; ss != NULL; ss = ss->next) {
ss1 = qe_malloc(CSSSimpleSelector);
dup_selector(ss1, ss);
*pss = ss1;
pss = &ss1->next;
}
/* add css properties */
e1->props = dup_properties(e->props);
}
}
static void beat(CSSParseState *b, int *ch_ptr, const char *str)
{
int ch;
ch = *ch_ptr;
while (*str) {
if (ch == EOF || ch != *str)
break;
ch = bgetc(b);
str++;
}
*ch_ptr = ch;
}
static void parse_simple_selector(CSSSimpleSelector *ss, CSSParseState *b,
int *ch_ptr)
{
char value[1024];
char tag[64];
char tag_id[64];
char attribute[64];
int ch, pclass, val;
CSSStyleSheetAttributeEntry *first_attr, **last_attr;
ch = *ch_ptr;
/* read the tag */
first_attr = NULL;
last_attr = &first_attr;
tag[0] = '\0';
tag_id[0] = '\0';
pclass = 0;
read_ident(b, &ch, tag, sizeof(tag));
if (b->ignore_case)
qe_strtolower(tag, sizeof(tag), tag);
/* read '.class', '[xxx]', ':pseudo-class' */
for (;;) {
bskip_spaces(b, &ch);
if (ch == '.') {
/* read the class and add it as an attribute */
ch = bgetc(b);
read_ident(b, &ch, value, sizeof(value));
add_attribute(&last_attr, CSS_ID_class, CSS_ATTR_OP_EQUAL, value);
} else if (ch == '#') {
/* read the id */
ch = bgetc(b);
read_ident(b, &ch, tag_id, sizeof(tag_id));
} else if (ch == '[') {
/* read the attribute */
int op;
ch = bgetc(b);
read_ident(b, &ch, attribute, sizeof(attribute));
if (b->ignore_case)
qe_strtolower(attribute, sizeof(attribute), attribute);
switch (ch) {
case '~':
op = CSS_ATTR_OP_IN_LIST;
ch = bgetc(b);
goto get_value;
case '=':
op = CSS_ATTR_OP_EQUAL;
goto get_value;
case '|':
op = CSS_ATTR_OP_IN_HLIST;
ch = bgetc(b);
get_value:
ch = bgetc(b);
if (ch == '\"' || ch == '\'') {
read_string(b, &ch, value, sizeof(value));
} else {
read_ident(b, &ch, value, sizeof(value));
}
break;
case ']':
op = CSS_ATTR_OP_SET;
value[0] = '\0';
break;
default:
dprintf("op: incorrect char '%c'\n", ch);
return; /* cannot do more */
}
if (ch == ']')
ch = bgetc(b);
add_attribute(&last_attr, css_new_ident(attribute), op, value);
} else if (ch == ':') {
ch = bgetc(b);
read_ident(b, &ch, value, sizeof(value));
val = css_get_enum(value, "first-child,link,visited,active,hover,focus,first-line,first-letter,before,after");
if (val >= 0)
pclass |= 1 << val;
} else {
break;
}
}
memset(ss, 0, sizeof(CSSSimpleSelector));
if (tag[0] == '\0') {
ss->tag = CSS_ID_ALL;
} else {
ss->tag = css_new_ident(tag);
}
if (tag_id[0] != '\0') {
/* XXX: not fully correct, but good enough for now */
add_attribute(&last_attr, CSS_ID_id, CSS_ATTR_OP_EQUAL, value);
/* we also add the id, just in case we use it in the futur */
ss->tag_id = css_new_ident(tag_id);
}
ss->attrs = first_attr;
ss->pclasses = pclass;
*ch_ptr = ch;
}
/* flags: only XML_IGNORE_CASE is handled */
void css_parse_style_sheet(CSSStyleSheet *s, CSSParseState *b)
{
char value[1024];
char tag[64];
char tag_id[64];
char *q;
int ch, media, val, last_tree_op, i;
CSSStyleSheetEntry *e, **first_eprops;
CSSSimpleSelector ss2, *ss = &ss2, *last_ss, *ss1;
CSSProperty *props;
ch = bgetc(b);
media = CSS_MEDIA_ALL;
for (;;) {
redo:
first_eprops = s->plast_entry;
bskip_spaces(b, &ch);
if (ch == EOF)
break;
/* eat inserted HTML comments for compatible STYLE tag parsing */
if (ch == '<') {
beat(b, &ch, "<!--");
goto redo;
} else if (ch == '-') {
beat(b, &ch, "-->");
goto redo;
}
/* handle '@media { ... }' */
if (ch == '@') {
ch = bgetc(b);
read_ident(b, &ch, tag, sizeof(tag));
switch (css_get_enum(tag, "media,page")) {
case 0:
/* @media */
media = 0;
for (;;) {
bskip_spaces(b, &ch);
read_ident(b, &ch, tag, sizeof(tag));
val = css_get_enum(tag, "tty,screen,print,tv,speech,all");
if (val < 0 || val == 5)
media = CSS_MEDIA_ALL;
else
media |= (1 << val);
bskip_spaces(b, &ch);
if (ch == ',') {
ch = bgetc(b);
} else if (ch == '{' || ch == EOF) {
ch = bgetc(b);
break;
}
}
goto redo;
case 1:
/* @page */
bskip_spaces(b, &ch);
if (ch != '{') {
read_ident(b, &ch, tag_id, sizeof(tag_id));
bskip_spaces(b, &ch);
}
memset(ss, 0, sizeof(CSSSimpleSelector));
ss->tag = css_new_ident("@page");
if (tag_id[0] != '\0')
ss->tag_id = css_new_ident(tag_id);
add_style_entry(s, ss, media);
goto parse_props;
default:
css_error1(b, "unrecognized css directive '@%s'", tag);
break;
}
} else if (ch == '}') {
/* XXX: end of media, should unstack */
ch = bgetc(b);
goto redo;
}
/* parse a selector list */
for (;;) {
/* parse simple selectors with operations */
last_ss = NULL;
last_tree_op = CSS_TREE_OP_NONE;
for (;;) {
int tree_op;
bskip_spaces(b, &ch);
parse_simple_selector(ss, b, &ch);
bskip_spaces(b, &ch);
ss->tree_op = last_tree_op;
ss->next = last_ss;
if (ch == '+') {
tree_op = CSS_TREE_OP_PRECEEDED;
ch = bgetc(b);
goto add_tree;
} else
if (ch == '>') {
tree_op = CSS_TREE_OP_CHILD;
ch = bgetc(b);
goto add_tree;
} else
if (qe_isalpha(ch)) {
tree_op = CSS_TREE_OP_DESCENDANT;
add_tree:
ss1 = qe_malloc_dup(ss, sizeof(CSSSimpleSelector));
if (ss1) {
last_ss = ss1;
}
last_tree_op = tree_op;
} else {
/* other char: exit */
break;
}
}
add_style_entry(s, ss, media);
/* get next selector, if present */
if (ch != ',')
break;
ch = bgetc(b);
}
parse_props:
/* expect start of properties */
if (ch != '{')
break;
ch = bgetc(b);
q = value;
while (ch != '}' && ch != EOF) {
if ((q - value) < (int)sizeof(value) - 1)
*q++ = ch;
ch = bgetc(b);
}
*q = '\0';
if (ch == '}')
ch = bgetc(b);
/* the properties are extracted, now add them to each tag */
/* XXX: should locate entries first, then add, to avoid adding
duplicate entries */
/* XXX: should put font properties first to avoid em/ex units
problems, but it would still not be sufficient. */
props = css_parse_properties(b, value);
i = 0;
for (e = *first_eprops; e != NULL; e = e->next) {
if (i == 0)
e->props = props;
else
e->props = dup_properties(props);
i++;
}
}
#ifdef DEBUG
css_dump_style_sheet(s);
#endif
}
void css_parse_style_sheet_str(CSSStyleSheet *s, const char *buffer, int flags)
{
CSSParseState b1, *b = &b1;
b->ptr = buffer;
b->filename = "builtin";
b->line_num = 1;
b->ignore_case = flags & XML_IGNORE_CASE;
css_parse_style_sheet(s, b);
}
#ifdef DEBUG
static void dump_selector(CSSSimpleSelector *ss)
{
CSSStyleSheetAttributeEntry *ae;
switch (ss->tree_op) {
case CSS_TREE_OP_DESCENDANT:
dump_selector(ss->next);
printf(" ");
break;
case CSS_TREE_OP_CHILD:
dump_selector(ss->next);
printf(" > ");
break;
case CSS_TREE_OP_PRECEEDED:
dump_selector(ss->next);
printf(" + ");
break;
default:
break;
}
printf("%s", css_ident_str(ss->tag));
if (ss->pclasses & CSS_PCLASS_FIRST_CHILD)
printf(":first-child");
if (ss->tag_id)
printf("#%s", css_ident_str(ss->tag_id));
ae = ss->attrs;
while (ae != NULL) {
printf("[%s", css_ident_str(ae->attr));
switch (ae->op) {
case CSS_ATTR_OP_EQUAL:
printf("=%s", ae->value);
break;
case CSS_ATTR_OP_IN_LIST:
printf("~=%s", ae->value);
break;
case CSS_ATTR_OP_IN_HLIST:
printf("|=%s", ae->value);
break;
}
printf("]");
ae = ae->next;
}
}
void css_dump_style_sheet(CSSStyleSheet *s)
{
CSSStyleSheetEntry *e;
printf("<STYLE type=\"text/css\">\n");
e = s->first_entry;
while (e != NULL) {
dump_selector(&e->sel);
printf(" { ");
css_dump_properties(e->props);
printf("}\n");
e = e->next;
}
printf("</STYLE>\n");
}
#endif