[project @ 2003-09-27 23:36:34 by bursa]

Implement CSS attribute selectors and parent / preceding combinators.

svn path=/import/netsurf/; revision=324
This commit is contained in:
James Bursa 2003-09-27 23:36:34 +00:00
parent 3e023a74a7
commit 1975b5cd1a
7 changed files with 178 additions and 84 deletions

224
css/css.c
View File

@ -33,6 +33,7 @@ struct decl {
static void css_atimport_callback(content_msg msg, struct content *css,
void *p1, void *p2, const char *error);
static bool css_match_rule(struct node *rule, xmlNode *element);
const struct css_style css_base_style = {
0xffffff,
@ -126,6 +127,8 @@ int css_convert(struct content *c, unsigned int width, unsigned int height)
css_parser_Free(c->data.css.css->parser, free);
css_lex_destroy(c->data.css.css->lexer);
/*css_dump_stylesheet(c->data.css.css);*/
/* complete fetch of any imported stylesheets */
while (c->active != 0) {
LOG(("importing %i from '%s'", c->active, c->url));
@ -198,6 +201,7 @@ struct node * css_new_node(node_type type, char *data,
struct node *node = xcalloc(1, sizeof(*node));
node->type = type;
node->data = data;
node->data2 = 0;
node->left = left;
node->right = right;
node->next = 0;
@ -357,90 +361,150 @@ void css_atimport_callback(content_msg msg, struct content *css,
}
/**
* Find the style which applies to an element.
*/
void css_get_style(struct content *c, struct css_selector * selector,
unsigned int selectors, struct css_style * style)
void css_get_style(struct content *css, xmlNode *element,
struct css_style *style)
{
struct css_stylesheet *stylesheet = c->data.css.css;
struct node *r, *n, *m;
unsigned int hash, i, done_empty = 0;
struct css_stylesheet *stylesheet = css->data.css.css;
struct node *rule;
unsigned int hash, i;
/*LOG(("stylesheet '%s'", c->url));*/
/* match rules which end with the same element */
hash = css_hash((char *) element->name);
for (rule = stylesheet->rule[hash]; rule; rule = rule->next)
if (css_match_rule(rule, element))
css_merge(style, rule->style);
hash = css_hash(selector[selectors - 1].element);
for (r = stylesheet->rule[hash]; ; r = r->next) {
if (r == 0 && !done_empty) {
r = stylesheet->rule[0];
done_empty = 1;
}
if (r == 0)
break;
i = selectors - 1;
n = r;
/* compare element */
if (n->data != 0)
if (strcasecmp(selector[i].element, n->data) != 0)
goto not_matched;
/*LOG(("top element '%s' matched", selector[i].element));*/
while (1) {
/* class and id */
for (m = n->left; m != 0; m = m->next) {
if (m->type == NODE_ID) {
/* TODO: check if case sensitive */
if (selector[i].id == 0 ||
strcmp(selector[i].id, m->data + 1) != 0)
goto not_matched;
} else if (m->type == NODE_CLASS) {
/* TODO: check if case sensitive */
if (selector[i].class == 0 ||
strcmp(selector[i].class, m->data) != 0)
goto not_matched;
} else {
goto not_matched;
}
}
/*LOG(("class and id matched"));*/
/* ancestors etc. */
if (n->comb == COMB_NONE)
goto matched; /* match successful */
else if (n->comb == COMB_ANCESTOR) {
/* search for ancestor */
assert(n->right != 0);
n = n->right;
if (n->data == 0)
goto not_matched; /* TODO: handle this case */
/*LOG(("searching for ancestor '%s'", n->data));*/
while (i != 0 && strcasecmp(selector[i - 1].element, n->data) != 0)
i--;
if (i == 0)
goto not_matched;
i--;
/*LOG(("found"));*/
} else {
/* TODO: COMB_PRECEDED, COMB_PARENT */
goto not_matched;
}
}
matched:
/* TODO: sort by specificity */
/*LOG(("matched rule %p", r));*/
css_merge(style, r->style);
not_matched:
}
/* match rules which apply to all elements */
for (rule = stylesheet->rule[0]; rule; rule = rule->next)
if (css_match_rule(rule, element))
css_merge(style, rule->style);
/* imported stylesheets */
for (i = 0; i != c->data.css.import_count; i++)
if (c->data.css.import_content[i] != 0)
css_get_style(c->data.css.import_content[i], selector,
selectors, style);
for (i = 0; i != css->data.css.import_count; i++)
if (css->data.css.import_content[i] != 0)
css_get_style(css->data.css.import_content[i],
element, style);
}
/**
* Determine if a rule applies to an element.
*/
bool css_match_rule(struct node *rule, xmlNode *element)
{
bool match;
char *s, *word, *space;
unsigned int i;
struct node *detail;
xmlNode *anc, *prev;
if (rule->data && strcasecmp(rule->data, (char *) element->name) != 0)
return false;
for (detail = rule->left; detail; detail = detail->next) {
match = false;
switch (detail->type) {
case NODE_ID:
s = (char *) xmlGetProp(element, (const xmlChar *) "id");
if (s && strcasecmp(detail->data, s) == 0)
match = true;
break;
case NODE_CLASS:
s = (char *) xmlGetProp(element, (const xmlChar *) "class");
if (s && strcasecmp(detail->data, s) == 0)
match = true;
break;
case NODE_ATTRIB:
/* matches if an attribute is present */
s = (char *) xmlGetProp(element, (const xmlChar *) detail->data);
if (s)
match = true;
break;
case NODE_ATTRIB_EQ:
/* matches if an attribute has a certain value */
s = (char *) xmlGetProp(element, (const xmlChar *) detail->data);
if (s && strcasecmp(detail->data2, s) == 0)
match = true;
break;
case NODE_ATTRIB_INC:
/* matches if one of the space separated words
* in the attribute is equal */
s = (char *) xmlGetProp(element, (const xmlChar *) detail->data);
if (s) {
word = s;
do {
space = strchr(word, ' ');
if (space)
*space = 0;
if (strcasecmp(word, detail->data2) == 0) {
match = true;
break;
}
word = space;
} while (word);
}
break;
case NODE_ATTRIB_DM:
/* matches if a prefix up to a hyphen matches */
s = (char *) xmlGetProp(element, (const xmlChar *) detail->data);
if (s) {
i = strlen(detail->data2);
if (strncasecmp(detail->data2, s, i) == 0 &&
(s[i] == '-' || s[i] == 0))
match = true;
}
break;
default:
assert(0);
}
if (s)
xmlFree(s);
if (!match)
return false;
}
if (!rule->right)
return true;
switch (rule->comb) {
case COMB_ANCESTOR:
for (anc = element->parent; anc; anc = anc->parent)
if (css_match_rule(rule->right, anc))
return true;
break;
case COMB_PRECEDED:
for (prev = element->prev;
prev && prev->type != XML_ELEMENT_NODE;
prev = prev->prev)
;
if (!prev)
return false;
return css_match_rule(rule->right, prev);
break;
case COMB_PARENT:
if (!element->parent)
return false;
return css_match_rule(rule->right, element->parent);
break;
default:
assert(0);
}
return false;
}
@ -546,6 +610,10 @@ void css_dump_stylesheet(const struct css_stylesheet * stylesheet)
switch (m->type) {
case NODE_ID: fprintf(stderr, "%s", m->data); break;
case NODE_CLASS: fprintf(stderr, ".%s", m->data); break;
case NODE_ATTRIB: fprintf(stderr, "[%s]", m->data); break;
case NODE_ATTRIB_EQ: fprintf(stderr, "[%s=%s]", m->data, m->data2); break;
case NODE_ATTRIB_INC: fprintf(stderr, "[%s~=%s]", m->data, m->data2); break;
case NODE_ATTRIB_DM: fprintf(stderr, "[%s|=%s]", m->data, m->data2); break;
default: fprintf(stderr, "unexpected node");
}
}

View File

@ -8,6 +8,7 @@
#ifndef _NETSURF_CSS_CSS_H_
#define _NETSURF_CSS_CSS_H_
#include "libxml/HTMLparser.h"
#include "css_enum.h"
/**
@ -125,6 +126,10 @@ typedef enum {
NODE_SELECTOR,
NODE_ID,
NODE_CLASS,
NODE_ATTRIB,
NODE_ATTRIB_EQ,
NODE_ATTRIB_INC,
NODE_ATTRIB_DM,
} node_type;
typedef enum {
@ -137,6 +142,7 @@ typedef enum {
struct node {
node_type type;
char *data;
char *data2;
struct node *left;
struct node *right;
struct node *next;
@ -195,8 +201,7 @@ void css_parser_(void *yyp, int yymajor, char* yyminor,
#endif
void css_get_style(struct content *c, struct css_selector * selector,
unsigned int selectors, struct css_style * style);
void css_get_style(struct content *c, xmlNode *n, struct css_style * style);
void css_cascade(struct css_style * const style, const struct css_style * const apply);
void css_merge(struct css_style * const style, const struct css_style * const apply);
void css_parse_property_list(struct css_style * style, char * str);

View File

@ -93,7 +93,21 @@ detail_list(A) ::= HASH(B) detail_list(C).
{ A = css_new_node(NODE_ID, B, 0, 0); A->next = C; }
detail_list(A) ::= DOT IDENT(B) detail_list(C).
{ A = css_new_node(NODE_CLASS, B, 0, 0); A->next = C; }
/* TODO: attrib, pseudo */
detail_list(A) ::= LBRAC IDENT(B) RBRAC detail_list(C).
{ A = css_new_node(NODE_ATTRIB, B, 0, 0); A->next = C; }
detail_list(A) ::= LBRAC IDENT(B) EQUALS IDENT(C) RBRAC detail_list(D).
{ A = css_new_node(NODE_ATTRIB_EQ, B, 0, 0); A->data2 = C; A->next = D; }
detail_list(A) ::= LBRAC IDENT(B) EQUALS STRING(C) RBRAC detail_list(D).
{ A = css_new_node(NODE_ATTRIB_EQ, B, 0, 0); A->data2 = C; A->next = D; }
detail_list(A) ::= LBRAC IDENT(B) INCLUDES IDENT(C) RBRAC detail_list(D).
{ A = css_new_node(NODE_ATTRIB_INC, B, 0, 0); A->data2 = C; A->next = D; }
detail_list(A) ::= LBRAC IDENT(B) INCLUDES STRING(C) RBRAC detail_list(D).
{ A = css_new_node(NODE_ATTRIB_INC, B, 0, 0); A->data2 = C; A->next = D; }
detail_list(A) ::= LBRAC IDENT(B) DASHMATCH IDENT(C) RBRAC detail_list(D).
{ A = css_new_node(NODE_ATTRIB_DM, B, 0, 0); A->data2 = C; A->next = D; }
detail_list(A) ::= LBRAC IDENT(B) DASHMATCH STRING(C) RBRAC detail_list(D).
{ A = css_new_node(NODE_ATTRIB_DM, B, 0, 0); A->data2 = C; A->next = D; }
/* TODO: pseudo */
declaration_list(A) ::= .
{ A = 0; }
@ -191,6 +205,7 @@ any(A) ::= LBRAC any_list(B) RBRAC.
%destructor selector_list { css_free_node($$); }
%destructor selector { css_free_node($$); }
%destructor simple_selector { css_free_node($$); }
%destructor detail_list { css_free_node($$); }
%destructor declaration_list { css_free_node($$); }
%destructor declaration { css_free_node($$); }
%destructor value { css_free_node($$); }

View File

@ -206,7 +206,10 @@ int compare_selectors(struct node *n0, struct node *n1)
int found = 0;
for (m1 = n1->left; m1 != 0; m1 = m1->next) {
/* TODO: should this be case sensitive for IDs? */
if (m0->type == m1->type && strcasecmp(m0->data, m1->data) == 0) {
if (m0->type == m1->type &&
strcasecmp(m0->data, m1->data) == 0 &&
((m0->data2 == 0 && m1->data2 == 0) ||
strcasecmp(m0->data2, m1->data2) == 0)) {
found = 1;
break;
}

View File

@ -63,6 +63,7 @@ U\+[0-9A-F?]{1,6}(-[0-9A-F]{1,6})? {
[ \t\r\n\f]+ /* ignore whitespace */
\/\*[^*]*\*+([^/][^*]*\*+)*\/ /* ignore comments */
{ident}\( { return FUNCTION; }
= { return EQUALS; }
~= { return INCLUDES; }
"|=" { return DASHMATCH; }
: { return COLON; }

View File

@ -16,8 +16,10 @@
const char *fetch_filetype(const char *unix_path)
{
int l;
LOG(("unix path %s", unix_path));
if (strcasecmp(unix_path, "home/james/Projects/netsurf/CSS") == 0)
l = strlen(unix_path);
if (2 < l && strcasecmp(unix_path + l - 3, "css") == 0)
return "text/css";
return "text/html";
}

View File

@ -479,7 +479,7 @@ struct css_style * box_get_style(struct content ** stylesheet,
for (i = 0; i != stylesheet_count; i++) {
if (stylesheet[i] != 0) {
assert(stylesheet[i]->type == CONTENT_CSS);
css_get_style(stylesheet[i], selector, depth, style_new);
css_get_style(stylesheet[i], n, style_new);
}
}
css_cascade(style, style_new);