⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 parser.c

📁 一个很有名的浏览器
💻 C
字号:
/* CSS main parser *//* $Id: parser.c,v 1.139.6.2 2005/04/05 21:08:41 jonas Exp $ */#ifdef HAVE_CONFIG_H#include "config.h"#endif#include <stdlib.h>#include <string.h>#include "elinks.h"#include "document/css/parser.h"#include "document/css/property.h"#include "document/css/scanner.h"#include "document/css/stylesheet.h"#include "document/css/value.h"#include "document/html/parser.h"#include "util/color.h"#include "util/lists.h"#include "util/error.h"#include "util/memory.h"#include "util/string.h"/* #define DEBUG_CSS */voidcss_parse_properties(struct list_head *props, struct scanner *scanner){	assert(props && scanner);	while (scanner_has_tokens(scanner)) {		struct css_property_info *property_info = NULL;		struct css_property *prop;		struct scanner_token *token = get_scanner_token(scanner);		int i;		if (!token || token->type == '}') break;		/* Extract property name. */		if (token->type != CSS_TOKEN_IDENT		    || !check_next_scanner_token(scanner, ':')) {			/* Some use style="{ properties }" so we have to be			 * check what to skip to. */			if (token->type == '{') {				skip_scanner_token(scanner);			} else {				skip_css_tokens(scanner, ';');			}			continue;		}		for (i = 0; css_property_info[i].name; i++) {			struct css_property_info *info = &css_property_info[i];			if (scanner_token_strlcasecmp(token, info->name, -1)) {				property_info = info;				break;			}		}		/* Skip property name and separator and check for expression */		if (!skip_css_tokens(scanner, ':')) {			assert(!scanner_has_tokens(scanner));			break;		}		if (!property_info) { 			/* Unknown property, check the next one. */ 			goto ride_on; 		}		/* We might be on track of something, cook up the struct. */		prop = mem_calloc(1, sizeof(*prop));		if (!prop) {			goto ride_on;		}		prop->type = property_info->type;		prop->value_type = property_info->value_type;		if (!css_parse_value(property_info, &prop->value, scanner)) {			mem_free(prop);			goto ride_on;		}		add_to_list(*props, prop);		/* Maybe we have something else to go yet? */ride_on:		skip_css_tokens(scanner, ';');	}}/* TODO: We should handle support for skipping blocks better like "{ { } }" * will be handled correctly. --jonas */#define skip_css_block(scanner) \	if (skip_css_tokens(scanner, '{')) skip_css_tokens(scanner, '}');/* Atrules grammer: * * media_types: *	  <empty> *	| <ident> *	| media_types ',' <ident> * * atrule: * 	  '@charset' <string> ';' *	| '@import' <string> media_types ';' *	| '@import' <uri> media_types ';' *	| '@media' media_types '{' ruleset* '}' *	| '@page' <ident>? [':' <ident>]? '{' properties '}' *	| '@font-face' '{' properties '}' */static voidcss_parse_atrule(struct css_stylesheet *css, struct scanner *scanner,		 struct uri *base_uri){	struct scanner_token *token = get_scanner_token(scanner);	/* Skip skip skip that code */	switch (token->type) {		case CSS_TOKEN_AT_IMPORT:			token = get_next_scanner_token(scanner);			if (!token) break;			if (token->type == CSS_TOKEN_STRING			    || token->type == CSS_TOKEN_URL) {				assert(css->import);				css->import(css, base_uri, token->string, token->length);			}			skip_css_tokens(scanner, ';');			break;		case CSS_TOKEN_AT_CHARSET:			skip_css_tokens(scanner, ';');			break;		case CSS_TOKEN_AT_FONT_FACE:		case CSS_TOKEN_AT_MEDIA:		case CSS_TOKEN_AT_PAGE:			skip_css_block(scanner);			break;		case CSS_TOKEN_AT_KEYWORD:			/* TODO: Unkown @-rule so either skip til ';' or next block. */			while (scanner_has_tokens(scanner)) {				token = get_next_scanner_token(scanner);				if (!token) break;				if (token->type == ';') {					skip_scanner_token(scanner);					break;				} else if (token->type == '{') {					skip_css_block(scanner);					break;				}			}			break;		default:			INTERNAL("@-rule parser called without atrule.");	}}struct selector_pkg {	LIST_HEAD(struct selector_pkg);	struct css_selector *selector;};struct css_selector *reparent_selector(struct list_head *sels, struct css_selector *selector,                  struct css_selector **watch){	struct css_selector *twin = find_css_selector(sels, selector->type,	                                              selector->relation,	                                              selector->name, -1);	if (twin) {		merge_css_selectors(twin, selector);		/* Reparent leaves. */		while (selector->leaves.next != &selector->leaves) {			struct css_selector *leaf = selector->leaves.next;			reparent_selector(&twin->leaves, leaf, watch);		}		if (*watch == selector)			*watch = twin;		done_css_selector(selector);	} else {		if (selector->next) del_from_list(selector);		add_to_list(*sels, selector);	}	return twin ? twin : selector;}/* Our selector grammar: * * selector: *	  element_name? ('#' id)? ('.' class)? (':' pseudo_class)? \ *		  ((' ' | '>') selector)? * */static voidcss_parse_selector(struct css_stylesheet *css, struct scanner *scanner,		   struct list_head *selectors){	/* Shell for the last selector (the whole selector chain, that is). */	struct selector_pkg *pkg = NULL;	/* In 'p#x.y i.z', it's NULL for 'p', 'p' for '#x', '.y' and 'i', and	 * 'i' for '.z'. */	struct css_selector *prev_element_selector = NULL;	/* In 'p#x.y:q i', it's NULL for 'p' and '#x', '#x' for '.y', and '.y'	 * for ':q', and again NULL for 'i'. */	struct css_selector *prev_specific_selector = NULL;	/* In 'p#x.y div.z:a' it is NULL for 'p#x.y' and 'div', and 'p' for	 * '.z' and ':a'. So the difference from @prev_element_selector is that	 * it is changed after the current selector fragment is finished, not	 * right after the base selector is loaded. So it is set differently	 * for the '#x.y' and '.z:a' parts of selector. */	struct css_selector *last_chained_selector = NULL;	/* In 'p#x.y div.z:a, i.b {}', it's set for ':a' and '.b'. */	int last_fragment = 0;	/* In 'p#x .y', it's set for 'p' and '.y'. Note that it is always set in	 * the previous iteration so it's valid for the current token only	 * before "saving" the token. */	int selector_start = 1;	/* FIXME: element can be even '*' --pasky */	while (scanner_has_tokens(scanner)) {		struct scanner_token *token = get_scanner_token(scanner);		struct scanner_token last_token;		struct css_selector *selector;		enum css_selector_relation reltype = CSR_ROOT;		enum css_selector_type seltype = CST_ELEMENT;		assert(token);		assert(!last_fragment);		if (token->type == '{'		    || token->type == '}'		    || token->type == ';')			break;		/* Examine the selector fragment */		if (token->type != CSS_TOKEN_IDENT) {			switch (token->type) {			case CSS_TOKEN_HASH:			case CSS_TOKEN_HEX_COLOR:				seltype = CST_ID;				reltype = selector_start ? CSR_ANCESTOR : CSR_SPECIFITY;				break;			case '.':				seltype = CST_CLASS;				reltype = selector_start ? CSR_ANCESTOR : CSR_SPECIFITY;				break;			case ':':				seltype = CST_PSEUDO;				reltype = selector_start ? CSR_ANCESTOR : CSR_SPECIFITY;				break;			case '>':				seltype = CST_ELEMENT;				reltype = CSR_PARENT;				break;			default:				/* FIXME: Temporary fix for this weird CSS				 * precedence thing. ')' has higher than ','				 * and it can cause problems when skipping				 * here. The reason is for the function()				 * parsing. Hmm... --jonas */				if (!skip_css_tokens(scanner, ','))					skip_scanner_token(scanner);				seltype = CST_INVALID;				break;			}			if (seltype == CST_INVALID)				continue;			/* Hexcolor and hash already contains the ident			 * inside. */			if (token->type != CSS_TOKEN_HEX_COLOR			    && token->type != CSS_TOKEN_HASH) {				token = get_next_scanner_token(scanner);				if (!token) break;				if (token->type != CSS_TOKEN_IDENT) /* wtf */					continue;			} else {				/* Skip the leading '#'. */				token->string++, token->length--;			}		} else {			if (pkg) reltype = CSR_ANCESTOR;		}		/* Look ahead at what's coming next */		copy_struct(&last_token, token);		/* Detect whether upcoming tokens are separated by		 * whitespace or not (that's important for determining		 * whether it's a combinator or specificitier). */		if (last_token.string + last_token.length < scanner->end) {			selector_start = last_token.string[last_token.length];			selector_start = (selector_start != '#'			                  && selector_start != '.'			                  && selector_start != ':');		} /* else it doesn't matter as we are gonna bail out anyway. */		token = get_next_scanner_token(scanner);		if (!token) break;		last_fragment = (token->type == ',' || token->type == '{');		/* Register the selector */		if (!pkg) {			selector = get_css_base_selector(			                last_fragment ? css : NULL, seltype,					CSR_ROOT,					last_token.string, last_token.length);			if (!selector) continue;			pkg = mem_calloc(1, sizeof(*pkg));			if (!pkg) continue;			add_to_list(*selectors, pkg);			pkg->selector = selector;		} else if (reltype == CSR_SPECIFITY) {			/* We append under the last fragment. */			struct css_selector *base_sel = prev_specific_selector;			if (!base_sel) base_sel = prev_element_selector;			assert(base_sel);			selector = get_css_selector(&base_sel->leaves,			                            seltype, reltype,						    last_token.string,						    last_token.length);			if (!selector) continue;			if (last_chained_selector) {				/* The situation is like: 'div p#x', now it was				 * 'p -> div', but we need to redo that as				 * '(p ->) #x -> div'. */				del_from_list(last_chained_selector);				add_to_list(selector->leaves,				            last_chained_selector);			}			if (pkg->selector == base_sel) {				/* This is still just specificitying offspring				 * of the previous pkg->selector. */				pkg->selector = selector;			}			if (last_fragment) {				/* This is the last fragment of the selector				 * chain, that means the last base fragment				 * wasn't marked so and thus wasn't bound to				 * the stylesheet. Let's do that now. */				assert(prev_element_selector);				prev_element_selector->relation = CSR_ROOT;				prev_element_selector =					reparent_selector(&css->selectors,					                 prev_element_selector,							 &pkg->selector);			}		} else /* CSR_PARENT || CSR_ANCESTOR */ {			/* We - in the perlish speak - unshift in front			 * of the previous selector fragment and reparent			 * it to the upcoming one. */			selector = get_css_base_selector(			                last_fragment ? css : NULL, seltype,					CSR_ROOT,					last_token.string, last_token.length);			if (!selector) continue;			assert(prev_element_selector);			add_to_list(selector->leaves, prev_element_selector);			last_chained_selector = prev_element_selector;			prev_element_selector->relation = reltype;		}		/* Record the selector fragment for future generations */		if (reltype == CSR_SPECIFITY) {			prev_specific_selector = selector;		} else {			prev_element_selector = selector;			prev_specific_selector = NULL;		}		/* What to do next */		if (last_fragment) {			/* Next selector coming, clean up. */			pkg = NULL; last_fragment = 0; selector_start = 1;			prev_element_selector = NULL;			prev_specific_selector = NULL;			last_chained_selector = NULL;		}		if (token->type == ',') {			/* Another selector hooked to these properties. */			skip_scanner_token(scanner);		} else if (token->type == '{') {			/* End of selector list. */			break;		} /* else Another selector fragment probably coming up. */	}	/* Wipe the selector we were currently composing, if any. */	if (pkg) {		if (prev_element_selector)			done_css_selector(prev_element_selector);		del_from_list(pkg);		mem_free(pkg);	}}/* Ruleset grammar: * * ruleset: *	  selector [ ',' selector ]* '{' properties '}' */static voidcss_parse_ruleset(struct css_stylesheet *css, struct scanner *scanner){	INIT_LIST_HEAD(selectors);	INIT_LIST_HEAD(properties);	struct selector_pkg *pkg;	css_parse_selector(css, scanner, &selectors);	if (list_empty(selectors)	    || !skip_css_tokens(scanner, '{')) {		if (!list_empty(selectors)) free_list(selectors);		skip_css_tokens(scanner, '}');		return;	}	/* We don't handle the case where a property has already been added to	 * a selector. That doesn't matter though, because the best one will be	 * always the last one (FIXME: 'important!'), therefore the applier	 * will take it last and it will have the "final" effect.	 *	 * So it's only a little waste and no real harm. The thing is, what do	 * you do when you have 'background: #fff' and then 'background:	 * x-repeat'? It would require yet another logic to handle merging of	 * these etc and the induced overhead would in most cases mean more	 * waste that having the property multiple times in a selector, I	 * believe. --pasky */	pkg = selectors.next;	css_parse_properties(&properties, scanner);	skip_css_tokens(scanner, '}');	/* Mirror the properties to all the selectors. */	foreach (pkg, selectors) {#ifdef DEBUG_CSS		DBG("Binding properties (!!%d) to selector %s (type %d, relation %d, children %d)",			!list_empty(properties),			pkg->selector->name, pkg->selector->type,			pkg->selector->relation,			!list_empty(pkg->selector->leaves));#endif		add_selector_properties(pkg->selector, &properties);	}	free_list(selectors);	free_list(properties);}voidcss_parse_stylesheet(struct css_stylesheet *css, struct uri *base_uri,		     unsigned char *string, unsigned char *end){	struct scanner scanner;	init_scanner(&scanner, &css_scanner_info, string, end);	while (scanner_has_tokens(&scanner)) {		struct scanner_token *token = get_scanner_token(&scanner);		assert(token);		switch (token->type) {		case CSS_TOKEN_AT_KEYWORD:		case CSS_TOKEN_AT_CHARSET:		case CSS_TOKEN_AT_FONT_FACE:		case CSS_TOKEN_AT_IMPORT:		case CSS_TOKEN_AT_MEDIA:		case CSS_TOKEN_AT_PAGE:			css_parse_atrule(css, &scanner, base_uri);			break;		default:			/* And WHAT ELSE could it be?! */			css_parse_ruleset(css, &scanner);		}	}#ifdef DEBUG_CSS	dump_css_selector_tree(&css->selectors);	WDBG("That's it.");#endif}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -