diff --git a/src/document/css/css.c b/src/document/css/css.c index fc24d0ad..aaf801ae 100644 --- a/src/document/css/css.c +++ b/src/document/css/css.c @@ -49,10 +49,62 @@ struct option_info css_options_info[] = { "to ELinks' home directory.\n" "Leave as \"\" to use built-in document styling.")), + INIT_OPT_STRING("document.css", N_("Media types"), + "media", 0, "tty", + N_("CSS media types that ELinks claims to support, separated with\n" + "commas. The \"all\" type is implied. Currently, only ASCII\n" + "characters work reliably here. See CSS2 section 7:\n" + "http://www.w3.org/TR/1998/REC-CSS2-19980512/media.html")), + NULL_OPTION_INFO, }; +/** Check whether ELinks claims to support a specific CSS media type. + * + * @param optstr + * Null-terminated value of the document.css.media option. + * @param token + * A name parsed from a CSS file. Need not be null-terminated. + * @param token_length + * Length of @a token, in bytes. + * + * Both strings should be in the ASCII charset. + * + * @return nonzero if the media type is supported, 0 if not. */ +int +supports_css_media_type(const unsigned char *optstr, + const unsigned char *token, size_t token_length) +{ + /* Split @optstr into comma-delimited strings, strip leading + * and trailing spaces from each, and compare them to the + * token. */ + while (*optstr != '\0') { + const unsigned char *beg, *end; + + while (*optstr == ' ') + ++optstr; + + beg = optstr; + optstr += strcspn(optstr, ","); + end = optstr; + while (end > beg && end[-1] == ' ') + --end; + + if (!strlcasecmp(token, token_length, beg, end - beg)) + return 1; + + while (*optstr == ',') + ++optstr; + } + + /* An explicit "all" is probably rarer than e.g. "tty". */ + if (!strlcasecmp(token, token_length, "all", 3)) + return 1; + + return 0; +} + void import_css(struct css_stylesheet *css, struct uri *uri) { @@ -124,7 +176,8 @@ import_default_css(void) static int change_hook_css(struct session *ses, struct option *current, struct option *changed) { - if (!strcmp(changed->name, "stylesheet")) { + if (!strcmp(changed->name, "stylesheet") + || !strcmp(changed->name, "media")) { /** @todo TODO: We need to update all entries in * format cache. --jonas */ import_default_css(); diff --git a/src/document/css/css.h b/src/document/css/css.h index 458300ef..961827e3 100644 --- a/src/document/css/css.h +++ b/src/document/css/css.h @@ -23,4 +23,7 @@ extern struct module css_module; /** This function will try to import the given @a url from the cache. */ void import_css(struct css_stylesheet *css, struct uri *uri); +int supports_css_media_type(const unsigned char *optstr, + const unsigned char *token, size_t token_length); + #endif diff --git a/src/document/css/parser.c b/src/document/css/parser.c index ee61754a..442fcec4 100644 --- a/src/document/css/parser.c +++ b/src/document/css/parser.c @@ -10,6 +10,8 @@ #include "elinks.h" +#include "config/options.h" +#include "document/css/css.h" #include "document/css/parser.h" #include "document/css/property.h" #include "document/css/scanner.h" @@ -21,6 +23,9 @@ #include "util/memory.h" #include "util/string.h" +static void css_parse_ruleset(struct css_stylesheet *css, + struct scanner *scanner); + void css_parse_properties(LIST_OF(struct css_property) *props, @@ -109,16 +114,53 @@ skip_css_block(struct scanner *scanner) } } -/** Parse an atrule from @a scanner and update @a css accordingly. +/* Parse a list of media types. * - * Atrules grammar: + * Media types grammar: * * @verbatim * media_types: * * | * | media_types ',' + * @endverbatim * + * This does not entirely match appendix D of CSS2: ELinks allows any + * list of media types to be empty, whereas CSS2 allows that only in + * @@import and not in @@media. + * + * @return nonzero if the directive containing this list should take + * effect, zero if not. + */ +static int +css_parse_media_types(struct scanner *scanner) +{ + int matched = 0; + int empty = 1; + const unsigned char *const optstr = get_opt_str("document.css.media", NULL); + struct scanner_token *token = get_scanner_token(scanner); + + while (token && token->type == CSS_TOKEN_IDENT) { + empty = 0; + if (!matched) /* Skip string ops if already matched. */ + matched = supports_css_media_type( + optstr, token->string, token->length); + + token = get_next_scanner_token(scanner); + if (!token || token->type != ',') + break; + + token = get_next_scanner_token(scanner); + } + + return matched || empty; +} + +/** Parse an atrule from @a scanner and update @a css accordingly. + * + * Atrules grammar: + * + * @verbatim * atrule: * '@charset' ';' * | '@import' media_types ';' @@ -133,38 +175,81 @@ css_parse_atrule(struct css_stylesheet *css, struct scanner *scanner, struct uri *base_uri) { struct scanner_token *token = get_scanner_token(scanner); + struct string import_uri; /* 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) + goto skip_rest_of_atrule; - if (token->type == CSS_TOKEN_STRING - || token->type == CSS_TOKEN_URL) { - assert(css->import); - css->import(css, base_uri, token->string, token->length); + /* As of 2007-07, token->string points into the + * original CSS text, so the pointer will remain + * valid even if we parse more tokens. But this + * may have to change when backslash escapes are + * properly supported. So play it safe and make + * a copy of the string. */ + if (!init_string(&import_uri)) + goto skip_rest_of_atrule; + if (!add_bytes_to_string(&import_uri, + token->string, + token->length)) { + done_string(&import_uri); + goto skip_rest_of_atrule; } - skip_css_tokens(scanner, ';'); + + skip_scanner_token(scanner); + if (!css_parse_media_types(scanner)) { + done_string(&import_uri); + goto skip_rest_of_atrule; + } + + token = get_scanner_token(scanner); + if (!token || token->type != ';') { + done_string(&import_uri); + goto skip_rest_of_atrule; + } + skip_scanner_token(scanner); + + assert(css->import); + css->import(css, base_uri, + import_uri.source, import_uri.length); + done_string(&import_uri); break; case CSS_TOKEN_AT_CHARSET: skip_css_tokens(scanner, ';'); break; - case CSS_TOKEN_AT_FONT_FACE: case CSS_TOKEN_AT_MEDIA: + skip_scanner_token(scanner); + if (!css_parse_media_types(scanner)) + goto skip_rest_of_atrule; + token = get_scanner_token(scanner); + if (!token || token->type != '{') + goto skip_rest_of_atrule; + token = get_next_scanner_token(scanner); + while (token && token->type != '}') { + css_parse_ruleset(css, scanner); + token = get_scanner_token(scanner); + } + if (token && token->type == '}') + skip_scanner_token(scanner); + break; + + case CSS_TOKEN_AT_FONT_FACE: case CSS_TOKEN_AT_PAGE: skip_css_block(scanner); break; +skip_rest_of_atrule: 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; - + token = get_scanner_token(scanner); + while (token) { if (token->type == ';') { skip_scanner_token(scanner); break; @@ -173,6 +258,8 @@ css_parse_atrule(struct css_stylesheet *css, struct scanner *scanner, skip_css_block(scanner); break; } + + token = get_next_scanner_token(scanner); } break; default: diff --git a/src/document/css/stylesheet.c b/src/document/css/stylesheet.c index 815ad71e..a0ec6903 100644 --- a/src/document/css/stylesheet.c +++ b/src/document/css/stylesheet.c @@ -257,6 +257,7 @@ dump_css_selector_tree(struct css_selector_set *sels) #endif +#if 0 /* used only by clone_css_stylesheet */ struct css_stylesheet * init_css_stylesheet(css_stylesheet_importer_T importer, void *import_data) { @@ -270,6 +271,7 @@ init_css_stylesheet(css_stylesheet_importer_T importer, void *import_data) init_css_selector_set(&css->selectors); return css; } +#endif void mirror_css_stylesheet(struct css_stylesheet *css1, struct css_stylesheet *css2) diff --git a/src/document/css/stylesheet.h b/src/document/css/stylesheet.h index 48ff381d..79602307 100644 --- a/src/document/css/stylesheet.h +++ b/src/document/css/stylesheet.h @@ -103,7 +103,8 @@ typedef void (*css_stylesheet_importer_T)(struct css_stylesheet *, struct uri *, * stylesheet so it can contain stuff from both @