#include "html.h"
#include "stylesheet.h"
#include "css_parser.h"
namespace litehtml
{
// https://www.w3.org/TR/css-syntax-3/#parse-a-css-stylesheet
template // Input == string or css_token_vector
void css::parse_css_stylesheet(const Input& input, string baseurl, document::ptr doc, media_query_list_list::ptr media, bool top_level)
{
if (doc && media)
doc->add_media_list(media);
// To parse a CSS stylesheet, first parse a stylesheet.
auto rules = css_parser::parse_stylesheet(input, top_level);
bool import_allowed = top_level;
// Interpret all of the resulting top-level qualified rules as style rules, defined below.
// If any style rule is invalid, or any at-rule is not recognized or is invalid according
// to its grammar or context, it's a parse error. Discard that rule.
for (auto rule : rules)
{
if (rule->type == raw_rule::qualified)
{
if (parse_style_rule(rule, baseurl, doc, media))
import_allowed = false;
continue;
}
// Otherwise: at-rule
switch (_id(lowcase(rule->name)))
{
case _charset_: // ignored https://www.w3.org/TR/css-syntax-3/#charset-rule
break;
case _import_:
if (import_allowed)
parse_import_rule(rule, baseurl, doc, media);
else
css_parse_error("incorrect placement of @import rule");
break;
// https://www.w3.org/TR/css-conditional-3/#at-media
// @media { }
case _media_:
{
if (rule->block.type != CURLY_BLOCK) break;
auto new_media = media;
auto mq_list = parse_media_query_list(rule->prelude, doc);
// An empty media query list evaluates to true. https://drafts.csswg.org/mediaqueries-5/#example-6f06ee45
if (!mq_list.empty())
{
new_media = make_shared(media ? *media : media_query_list_list());
new_media->add(mq_list);
}
parse_css_stylesheet(rule->block.value, baseurl, doc, new_media, false);
import_allowed = false;
break;
}
default:
css_parse_error("unrecognized rule @" + rule->name);
}
}
}
// https://drafts.csswg.org/css-cascade-5/#at-import
// `layer` and `supports` are not supported
// @import [ | ] ?
void css::parse_import_rule(raw_rule::ptr rule, string baseurl, document::ptr doc, media_query_list_list::ptr media)
{
auto tokens = rule->prelude;
int index = 0;
skip_whitespace(tokens, index);
auto tok = at(tokens, index);
string url;
auto parse_string = [](const css_token& tok, string& str)
{
if (tok.type != STRING) return false;
str = tok.str;
return true;
};
bool ok = parse_url(tok, url) || parse_string(tok, url);
if (!ok) {
css_parse_error("invalid @import rule");
return;
}
document_container* container = doc->container();
string css_text;
string css_baseurl = baseurl;
container->import_css(css_text, url, css_baseurl);
auto new_media = media;
tokens = slice(tokens, index + 1);
auto mq_list = parse_media_query_list(tokens, doc);
if (!mq_list.empty())
{
new_media = make_shared(media ? *media : media_query_list_list());
new_media->add(mq_list);
}
parse_css_stylesheet(css_text, css_baseurl, doc, new_media, true);
}
// https://www.w3.org/TR/css-syntax-3/#style-rules
bool css::parse_style_rule(raw_rule::ptr rule, string baseurl, document::ptr doc, media_query_list_list::ptr media)
{
// The prelude of the qualified rule is parsed as a . If this returns failure, the entire style rule is invalid.
auto list = parse_selector_list(rule->prelude, strict_mode, doc->mode());
if (list.empty())
{
css_parse_error("invalid selector");
return false;
}
style::ptr style = make_shared(); // style block
// The content of the qualified rule's block is parsed as a style block's contents.
style->add(rule->block.value, baseurl, doc->container());
for (auto sel : list)
{
sel->m_style = style;
sel->m_media_query = media;
sel->calc_specificity();
add_selector(sel);
}
return true;
}
void css::sort_selectors()
{
std::sort(m_selectors.begin(), m_selectors.end(),
[](const css_selector::ptr& v1, const css_selector::ptr& v2)
{
return (*v1) < (*v2);
}
);
}
} // namespace litehtml