/*
* Copyright (C) 2020-2026 Alexander Borisov
*
* Author: Alexander Borisov <borisov@lexbor.com>
*/
#include "lexbor/css/parser.h"
#include "lexbor/css/css.h"
#include "lexbor/css/selectors/selectors.h"
#include "lexbor/css/selectors/pseudo.h"
#include "lexbor/css/selectors/pseudo_const.h"
static const char lxb_css_selectors_module_name[] = "Selectors";
#define lxb_css_selectors_state_string_dup_m(selectors, name) \
do { \
(status) = lxb_css_syntax_token_string_dup( \
lxb_css_syntax_token_string(token), (name), \
(parser)->memory->mraw); \
if ((status) != LXB_STATUS_OK) { \
return (status); \
} \
} \
while (false)
#define lxb_css_selectors_state_append(parser, selectors, selector) \
do { \
(selector) = lxb_css_selector_create((selectors)->list_last); \
if ((selector) == NULL) { \
return lxb_css_parser_memory_fail(parser); \
} \
\
lxb_css_selectors_append_next((selectors), (selector)); \
\
(selector)->combinator = (selectors)->combinator; \
(selectors)->combinator = LXB_CSS_SELECTOR_COMBINATOR_CLOSE; \
} \
while (false)
#define lxb_css_selectors_state_list_append(parser, selectors, list) \
do { \
(list) = lxb_css_selector_list_create((parser)->memory); \
if ((list) == NULL) { \
return lxb_css_parser_memory_fail(parser); \
} \
\
lxb_css_selectors_list_append_next((selectors), (list)); \
\
(list)->parent = selectors->parent; \
} \
while (false)
static bool
lxb_css_selectors_state_complex_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_relative_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_relative_list_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_relative_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_relative_handler(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, bool list, bool root);
static bool
lxb_css_selectors_state_complex_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_complex_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_complex_handler(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, bool root);
static bool
lxb_css_selectors_state_compound_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_compound_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_compound_handler(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_compound_sub(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_compound_pseudo(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_simple_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_simple_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_simple_handler(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static bool
lxb_css_selectors_state_simple_back(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx);
static lxb_status_t
lxb_css_selectors_state_hash(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token);
static lxb_status_t
lxb_css_selectors_state_class(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token);
static lxb_status_t
lxb_css_selectors_state_element_ns(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token);
static lxb_status_t
lxb_css_selectors_state_element(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token);
static lxb_status_t
lxb_css_selectors_state_attribute(lxb_css_parser_t *parser);
static lxb_status_t
lxb_css_selectors_state_ns(lxb_css_parser_t *parser,
lxb_css_selector_t *selector);
static lxb_status_t
lxb_css_selectors_state_ns_ident(lxb_css_parser_t *parser,
lxb_css_selector_t *selector);
static lxb_status_t
lxb_css_selectors_state_pseudo_class(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token);
static lxb_status_t
lxb_css_selectors_state_pseudo_class_function(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
lxb_css_parser_state_f success);
static lxb_status_t
lxb_css_selectors_state_pseudo_element(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token);
static lxb_status_t
lxb_css_selectors_state_pseudo_element_function(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
lxb_css_parser_state_f success);
static lxb_status_t
lxb_css_selectors_state_forgiving_cb(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, lxb_css_parser_state_f state,
bool failed);
static void
lxb_css_selectors_state_restore_parent(lxb_css_selectors_t *selectors,
lxb_css_selector_list_t *last);
static bool
lxb_css_selectors_state_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
lxb_css_parser_state_f state);
static bool
lxb_css_selectors_state_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token, void *ctx);
static const lxb_css_syntax_token_t *
lxb_css_selectors_state_function_error(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token);
lxb_inline bool
lxb_css_selectors_done(lxb_css_parser_t *parser)
{
lxb_css_parser_states_pop(parser);
return lxb_css_parser_states_set_back(parser);
}
lxb_inline void
lxb_css_selectors_state_specificity_set_b(lxb_css_selectors_t *selectors)
{
lxb_css_selector_list_t *last = selectors->list_last;
if (selectors->parent == NULL) {
lxb_css_selector_sp_set_b(last->specificity,
lxb_css_selector_sp_b(last->specificity) + 1);
}
else if (last->specificity > LXB_CSS_SELECTOR_SP_B_MAX) {
if (selectors->combinator == LXB_CSS_SELECTOR_COMBINATOR_CLOSE) {
lxb_css_selector_sp_set_b(last->specificity, 1);
}
}
else {
if (selectors->combinator != LXB_CSS_SELECTOR_COMBINATOR_CLOSE) {
last->specificity = 0;
}
lxb_css_selector_sp_set_b(last->specificity, 1);
}
}
lxb_inline void
lxb_css_selectors_state_specificity_set_c(lxb_css_selectors_t *selectors)
{
lxb_css_selector_list_t *last = selectors->list_last;
if (selectors->parent == NULL) {
lxb_css_selector_sp_set_c(last->specificity,
lxb_css_selector_sp_c(last->specificity) + 1);
}
else if (last->specificity > LXB_CSS_SELECTOR_SP_C_MAX) {
if (selectors->combinator == LXB_CSS_SELECTOR_COMBINATOR_CLOSE) {
lxb_css_selector_sp_set_c(last->specificity, 1);
}
}
else {
if (selectors->combinator != LXB_CSS_SELECTOR_COMBINATOR_CLOSE) {
last->specificity = 0;
}
lxb_css_selector_sp_set_c(last->specificity, 1);
}
}
lxb_inline void
lxb_css_selectors_state_func_specificity(lxb_css_selectors_t *selectors)
{
lxb_css_selector_list_t *prev, *last;
last = selectors->list_last;
prev = last->prev;
if (prev->specificity > last->specificity) {
last->specificity = prev->specificity;
}
prev->specificity = 0;
}
/*
* <complex-selector-list>
*/
bool
lxb_css_selectors_state_complex_list(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_complex_wo_root,
lxb_css_selectors_state_complex_list_end,
ctx, true);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_complex_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
return lxb_css_selectors_state_list_end(parser, token,
lxb_css_selectors_state_complex_wo_root);
}
/*
* <relative-selector-list>
*/
bool
lxb_css_selectors_state_relative_list(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_relative_list_wo_root,
lxb_css_selectors_state_relative_list_end,
ctx, true);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_relative_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
return lxb_css_selectors_state_list_end(parser, token,
lxb_css_selectors_state_relative_list_wo_root);
}
/*
* <relative-selector>
*/
bool
lxb_css_selectors_state_relative(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_relative_wo_root,
lxb_css_selectors_state_end,
ctx, true);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_relative_list_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
return lxb_css_selectors_state_relative_handler(parser, token, ctx, true,
false);
}
static bool
lxb_css_selectors_state_relative_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
return lxb_css_selectors_state_relative_handler(parser, token, ctx, false,
false);
}
static bool
lxb_css_selectors_state_relative_handler(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, bool list, bool root)
{
lxb_css_parser_state_f back;
lxb_css_parser_state_t *states;
lxb_css_selectors_t *selectors = parser->selectors;
/* <combinator> */
switch (token->type) {
case LXB_CSS_SYNTAX_TOKEN_WHITESPACE:
lxb_css_syntax_parser_consume(parser);
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_DESCENDANT;
return true;
case LXB_CSS_SYNTAX_TOKEN_DELIM:
switch (lxb_css_syntax_token_delim_char(token)) {
case '>':
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_CHILD;
break;
case '+':
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_SIBLING;
break;
case '~':
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_FOLLOWING;
break;
case '|':
lxb_css_parser_token_next_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_DELIM
&& lxb_css_syntax_token_delim_char(token) == '|')
{
lxb_css_syntax_parser_consume(parser);
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_CELL;
break;
}
goto done;
default:
goto done;
}
break;
default:
goto done;
}
lxb_css_syntax_parser_consume(parser);
done:
back = (list) ? lxb_css_selectors_state_complex_end
: lxb_css_selectors_state_end;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_compound_wo_root,
back, ctx, root);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return true;
}
/*
* <complex-selector>
*/
bool
lxb_css_selectors_state_complex(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token, void *ctx)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_complex_wo_root,
lxb_css_selectors_state_end,
ctx, true);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_complex_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
return lxb_css_selectors_state_complex_handler(parser, token, ctx, false);
}
static bool
lxb_css_selectors_state_complex_handler(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, bool root)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_compound_wo_root,
lxb_css_selectors_state_complex_end,
ctx, root);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_complex_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_selectors_t *selectors = parser->selectors;
/* <combinator> */
again:
switch (token->type) {
case LXB_CSS_SYNTAX_TOKEN_WHITESPACE:
lxb_css_syntax_parser_consume(parser);
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_DESCENDANT;
lxb_css_parser_token_m(parser, token);
goto again;
case LXB_CSS_SYNTAX_TOKEN__END:
return lxb_css_selectors_done(parser);
case LXB_CSS_SYNTAX_TOKEN_DELIM:
switch (lxb_css_syntax_token_delim_char(token)) {
case '>':
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_CHILD;
break;
case '+':
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_SIBLING;
break;
case '~':
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_FOLLOWING;
break;
case '|':
lxb_css_parser_token_next_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_DELIM
&& lxb_css_syntax_token_delim_char(token) == '|')
{
lxb_css_syntax_parser_consume(parser);
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_CELL;
break;
}
goto done;
default:
if (selectors->combinator != LXB_CSS_SELECTOR_COMBINATOR_DESCENDANT) {
goto unexpected;
}
goto done;
}
break;
case LXB_CSS_SYNTAX_TOKEN_COMMA:
return lxb_css_selectors_done(parser);
default:
if (selectors->combinator != LXB_CSS_SELECTOR_COMBINATOR_DESCENDANT) {
goto unexpected;
}
goto done;
}
lxb_css_syntax_parser_consume(parser);
done:
lxb_css_parser_state_set(parser, lxb_css_selectors_state_compound_handler);
return true;
unexpected:
(void) lxb_css_selectors_done(parser);
return lxb_css_parser_unexpected(parser);
}
/*
* <compound-selector-list>
*/
bool
lxb_css_selectors_state_compound_list(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_compound_wo_root,
lxb_css_selectors_state_compound_list_end,
ctx, true);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_compound_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
return lxb_css_selectors_state_list_end(parser, token,
lxb_css_selectors_state_compound_wo_root);
}
/*
*
* <compound-selector>
*/
bool
lxb_css_selectors_state_compound(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_compound_wo_root,
lxb_css_selectors_state_end,
ctx, true);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_compound_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_selector_list_t *list;
lxb_css_selectors_state_list_append(parser, parser->selectors, list);
lxb_css_parser_state_set(parser, lxb_css_selectors_state_compound_handler);
return false;
}
static bool
lxb_css_selectors_state_compound_handler(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_status_t status;
lxb_css_selectors_t *selectors;
again:
lxb_css_parser_state_set(parser, lxb_css_selectors_state_compound_sub);
switch (token->type) {
case LXB_CSS_SYNTAX_TOKEN_HASH:
status = lxb_css_selectors_state_hash(parser, token);
break;
case LXB_CSS_SYNTAX_TOKEN_DELIM:
switch (lxb_css_syntax_token_delim_char(token)) {
case '.':
lxb_css_syntax_parser_consume(parser);
status = lxb_css_selectors_state_class(parser, token);
break;
case '|':
case '*':
status = lxb_css_selectors_state_element_ns(parser, token);
break;
default:
goto unexpected;
}
break;
case LXB_CSS_SYNTAX_TOKEN_IDENT:
status = lxb_css_selectors_state_element(parser, token);
break;
case LXB_CSS_SYNTAX_TOKEN_LS_BRACKET:
lxb_css_syntax_parser_consume(parser);
status = lxb_css_selectors_state_attribute(parser);
break;
case LXB_CSS_SYNTAX_TOKEN_COLON:
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
status = lxb_css_selectors_state_pseudo_class(parser, token);
break;
}
else if (token->type == LXB_CSS_SYNTAX_TOKEN_COLON) {
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
lxb_css_parser_state_set(parser,
lxb_css_selectors_state_compound_pseudo);
status = lxb_css_selectors_state_pseudo_element(parser, token);
break;
}
else if (token->type != LXB_CSS_SYNTAX_TOKEN_FUNCTION) {
return lxb_css_parser_unexpected(parser);
}
status = lxb_css_selectors_state_pseudo_element_function(parser, token,
lxb_css_selectors_state_compound_pseudo);
break;
}
else if (token->type != LXB_CSS_SYNTAX_TOKEN_FUNCTION) {
goto unexpected;
}
status = lxb_css_selectors_state_pseudo_class_function(parser, token,
lxb_css_selectors_state_compound_sub);
break;
case LXB_CSS_SYNTAX_TOKEN_WHITESPACE:
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
goto again;
case LXB_CSS_SYNTAX_TOKEN__END:
selectors = parser->selectors;
if (selectors->combinator > LXB_CSS_SELECTOR_COMBINATOR_CLOSE
|| selectors->list_last->first == NULL)
{
goto unexpected;
}
return lxb_css_selectors_done(parser);
default:
goto unexpected;
}
if (status == LXB_STATUS_OK) {
return true;
}
if (status == LXB_STATUS_ERROR_MEMORY_ALLOCATION) {
return lxb_css_parser_memory_fail(parser);
}
unexpected:
(void) lxb_css_parser_states_to_root(parser);
(void) lxb_css_parser_states_set_back(parser);
return lxb_css_parser_unexpected(parser);
}
static bool
lxb_css_selectors_state_compound_sub(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_status_t status;
/* <subclass-selector> */
switch (token->type) {
case LXB_CSS_SYNTAX_TOKEN_HASH:
status = lxb_css_selectors_state_hash(parser, token);
break;
case LXB_CSS_SYNTAX_TOKEN_DELIM:
switch (lxb_css_syntax_token_delim_char(token)) {
case '.':
lxb_css_syntax_parser_consume(parser);
status = lxb_css_selectors_state_class(parser, token);
break;
default:
return lxb_css_parser_states_set_back(parser);
}
break;
case LXB_CSS_SYNTAX_TOKEN_LS_BRACKET:
lxb_css_syntax_parser_consume(parser);
status = lxb_css_selectors_state_attribute(parser);
break;
case LXB_CSS_SYNTAX_TOKEN_COLON:
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
status = lxb_css_selectors_state_pseudo_class(parser, token);
break;
}
else if (token->type == LXB_CSS_SYNTAX_TOKEN_COLON) {
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
lxb_css_parser_state_set(parser,
lxb_css_selectors_state_compound_pseudo);
status = lxb_css_selectors_state_pseudo_element(parser,
token);
break;
}
else if (token->type != LXB_CSS_SYNTAX_TOKEN_FUNCTION) {
return lxb_css_parser_unexpected(parser);
}
status = lxb_css_selectors_state_pseudo_element_function(parser,
token, lxb_css_selectors_state_compound_pseudo);
break;
}
else if (token->type != LXB_CSS_SYNTAX_TOKEN_FUNCTION) {
return lxb_css_parser_unexpected(parser);
}
status = lxb_css_selectors_state_pseudo_class_function(parser, token,
lxb_css_selectors_state_compound_sub);
break;
default:
return lxb_css_parser_states_set_back(parser);
}
if (status == LXB_STATUS_OK) {
return true;
}
if (status == LXB_STATUS_ERROR_MEMORY_ALLOCATION) {
return lxb_css_parser_memory_fail(parser);
}
return lxb_css_parser_unexpected(parser);
}
static bool
lxb_css_selectors_state_compound_pseudo(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_status_t status;
if (token->type != LXB_CSS_SYNTAX_TOKEN_COLON) {
return lxb_css_parser_states_set_back(parser);
}
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
status = lxb_css_selectors_state_pseudo_class(parser, token);
}
else if (token->type == LXB_CSS_SYNTAX_TOKEN_COLON) {
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
status = lxb_css_selectors_state_pseudo_element(parser, token);
}
else if (token->type == LXB_CSS_SYNTAX_TOKEN_FUNCTION) {
status = lxb_css_selectors_state_pseudo_element_function(parser, token,
lxb_css_selectors_state_compound_pseudo);
}
else {
return lxb_css_parser_unexpected(parser);
}
}
else if (token->type != LXB_CSS_SYNTAX_TOKEN_FUNCTION) {
return lxb_css_parser_unexpected(parser);
}
else {
status = lxb_css_selectors_state_pseudo_class_function(parser, token,
lxb_css_selectors_state_compound_pseudo);
}
if (status == LXB_STATUS_OK) {
return true;
}
if (status == LXB_STATUS_ERROR_MEMORY_ALLOCATION) {
return lxb_css_parser_memory_fail(parser);
}
return lxb_css_parser_unexpected(parser);
}
/*
* <simple-selector-list>
*/
bool
lxb_css_selectors_state_simple_list(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser, lxb_css_selectors_state_simple_wo_root,
lxb_css_selectors_state_simple_list_end,
ctx, true);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_simple_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
return lxb_css_selectors_state_list_end(parser, token,
lxb_css_selectors_state_simple_wo_root);
}
/*
* <simple-selector>
*/
bool
lxb_css_selectors_state_simple(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token, void *ctx)
{
lxb_css_parser_state_t *states;
states = lxb_css_parser_states_next(parser,
lxb_css_selectors_state_simple_wo_root,
lxb_css_selectors_state_end,
ctx, true);
if (states == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return false;
}
static bool
lxb_css_selectors_state_simple_wo_root(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_css_selector_list_t *list;
lxb_css_selectors_state_list_append(parser, parser->selectors, list);
lxb_css_parser_state_set(parser, lxb_css_selectors_state_simple_handler);
return false;
}
static bool
lxb_css_selectors_state_simple_handler(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
lxb_status_t status;
again:
lxb_css_parser_state_set(parser, lxb_css_selectors_state_simple_back);
switch (token->type) {
case LXB_CSS_SYNTAX_TOKEN_HASH:
status = lxb_css_selectors_state_hash(parser, token);
break;
case LXB_CSS_SYNTAX_TOKEN_DELIM:
switch (lxb_css_syntax_token_delim_char(token)) {
case '.':
lxb_css_syntax_parser_consume(parser);
status = lxb_css_selectors_state_class(parser, token);
break;
case '|':
case '*':
status = lxb_css_selectors_state_element_ns(parser, token);
break;
default:
goto unexpected;
}
break;
case LXB_CSS_SYNTAX_TOKEN_IDENT:
status = lxb_css_selectors_state_element(parser, token);
break;
case LXB_CSS_SYNTAX_TOKEN_LS_BRACKET:
lxb_css_syntax_parser_consume(parser);
status = lxb_css_selectors_state_attribute(parser);
break;
case LXB_CSS_SYNTAX_TOKEN_COLON:
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
status = lxb_css_selectors_state_pseudo_class(parser, token);
break;
}
else if (token->type != LXB_CSS_SYNTAX_TOKEN_FUNCTION) {
goto unexpected;
}
status = lxb_css_selectors_state_pseudo_class_function(parser, token,
lxb_css_selectors_state_simple_back);
break;
case LXB_CSS_SYNTAX_TOKEN_WHITESPACE:
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_m(parser, token);
goto again;
case LXB_CSS_SYNTAX_TOKEN__END:
return lxb_css_parser_states_set_back(parser);
default:
goto unexpected;
}
if (status == LXB_STATUS_OK) {
return true;
}
if (status == LXB_STATUS_ERROR_MEMORY_ALLOCATION) {
return lxb_css_parser_memory_fail(parser);
}
unexpected:
(void) lxb_css_parser_states_set_back(parser);
return lxb_css_parser_unexpected(parser);
}
static bool
lxb_css_selectors_state_simple_back(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx)
{
return lxb_css_parser_states_set_back(parser);
}
static lxb_status_t
lxb_css_selectors_state_hash(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token)
{
lxb_status_t status;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
lxb_css_selector_list_t *last;
selectors = parser->selectors;
last = selectors->list_last;
if (selectors->parent == NULL) {
lxb_css_selector_sp_set_a(last->specificity,
lxb_css_selector_sp_a(last->specificity) + 1);
}
else if (lxb_css_selector_sp_a(last->specificity) == 0) {
if (selectors->combinator != LXB_CSS_SELECTOR_COMBINATOR_CLOSE) {
last->specificity = 0;
}
lxb_css_selector_sp_set_a(last->specificity, 1);
}
lxb_css_selectors_state_append(parser, selectors, selector);
selector->type = LXB_CSS_SELECTOR_TYPE_ID;
status = lxb_css_syntax_token_string_dup(lxb_css_syntax_token_string(token),
&selector->name, parser->memory->mraw);
lxb_css_syntax_parser_consume(parser);
return status;
}
static lxb_status_t
lxb_css_selectors_state_class(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token)
{
lxb_status_t status;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
lxb_css_parser_token_status_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
selectors = parser->selectors;
lxb_css_selectors_state_specificity_set_b(selectors);
lxb_css_selectors_state_append(parser, selectors, selector);
selector->type = LXB_CSS_SELECTOR_TYPE_CLASS;
status = lxb_css_syntax_token_string_dup(lxb_css_syntax_token_string(token),
&selector->name, parser->memory->mraw);
lxb_css_syntax_parser_consume(parser);
return status;
}
return lxb_css_parser_unexpected_status(parser);
}
static lxb_status_t
lxb_css_selectors_state_element_ns(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token)
{
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
selectors = parser->selectors;
lxb_css_selectors_state_append(parser, selectors, selector);
selector->type = LXB_CSS_SELECTOR_TYPE_ANY;
selector->name.data = lexbor_mraw_alloc(parser->memory->mraw, 2);
if (selector->name.data == NULL) {
return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
}
selector->name.data[0] = '*';
selector->name.data[1] = '\0';
selector->name.length = 1;
if (lxb_css_syntax_token_delim_char(token) == '*') {
lxb_css_syntax_parser_consume(parser);
return lxb_css_selectors_state_ns(parser, selector);
}
lxb_css_syntax_parser_consume(parser);
return lxb_css_selectors_state_ns_ident(parser, selector);
}
static lxb_status_t
lxb_css_selectors_state_element(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token)
{
lxb_status_t status;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
selectors = parser->selectors;
lxb_css_selectors_state_specificity_set_c(selectors);
lxb_css_selectors_state_append(parser, selectors, selector);
selector->type = LXB_CSS_SELECTOR_TYPE_ELEMENT;
lxb_css_selectors_state_string_dup_m(selectors, &selector->name);
lxb_css_syntax_parser_consume(parser);
return lxb_css_selectors_state_ns(parser, selector);
}
static lxb_status_t
lxb_css_selectors_state_attribute(lxb_css_parser_t *parser)
{
lxb_char_t modifier;
lxb_status_t status;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
const lxb_css_syntax_token_t *token;
lxb_css_selector_attribute_t *attribute;
selectors = parser->selectors;
lxb_css_selectors_state_append(parser, selectors, selector);
lxb_css_parser_token_status_wo_ws_m(parser, token);
switch (token->type) {
case LXB_CSS_SYNTAX_TOKEN_DELIM:
if (lxb_css_syntax_token_delim_char(token) != '|') {
goto failed;
}
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_m(parser, token);
if (token->type != LXB_CSS_SYNTAX_TOKEN_IDENT) {
goto failed;
}
selector->type = LXB_CSS_SELECTOR_TYPE_ATTRIBUTE;
selector->ns.data = lexbor_mraw_alloc(parser->memory->mraw, 2);
if (selector->ns.data == NULL) {
return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
}
selector->ns.data[0] = '*';
selector->ns.data[1] = '\0';
selector->ns.length = 1;
lxb_css_selectors_state_string_dup_m(parser->selectors,
&selector->name);
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_wo_ws_m(parser, token);
break;
case LXB_CSS_SYNTAX_TOKEN_IDENT:
selector->type = LXB_CSS_SELECTOR_TYPE_ATTRIBUTE;
lxb_css_selectors_state_string_dup_m(selectors, &selector->name);
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_m(parser, token);
if (token->type != LXB_CSS_SYNTAX_TOKEN_DELIM
|| lxb_css_syntax_token_delim_char(token) != '|')
{
if (token->type == LXB_CSS_SYNTAX_TOKEN_WHITESPACE) {
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_m(parser, token);
}
break;
}
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_m(parser, token);
if (token->type != LXB_CSS_SYNTAX_TOKEN_IDENT) {
attribute = &selector->u.attribute;
attribute->match = LXB_CSS_SELECTOR_MATCH_DASH;
goto assignment;
}
selector->ns = selector->name;
lexbor_str_clean_all(&selector->name);
lxb_css_selectors_state_string_dup_m(selectors, &selector->name);
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_wo_ws_m(parser, token);
break;
default:
goto failed;
}
attribute = &selector->u.attribute;
switch (token->type) {
case LXB_CSS_SYNTAX_TOKEN_RS_BRACKET:
goto done;
case LXB_CSS_SYNTAX_TOKEN_DELIM:
switch (lxb_css_syntax_token_delim_char(token)) {
case '~':
attribute->match = LXB_CSS_SELECTOR_MATCH_INCLUDE;
break;
case '|':
attribute->match = LXB_CSS_SELECTOR_MATCH_DASH;
break;
case '^':
attribute->match = LXB_CSS_SELECTOR_MATCH_PREFIX;
break;
case '$':
attribute->match = LXB_CSS_SELECTOR_MATCH_SUFFIX;
break;
case '*':
attribute->match = LXB_CSS_SELECTOR_MATCH_SUBSTRING;
break;
case '=':
attribute->match = LXB_CSS_SELECTOR_MATCH_EQUAL;
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_wo_ws_m(parser, token);
goto string_or_ident;
default:
goto failed;
}
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_m(parser, token);
break;
default:
goto failed;
}
assignment:
if (token->type != LXB_CSS_SYNTAX_TOKEN_DELIM
|| lxb_css_syntax_token_delim_char(token) != '=')
{
goto failed;
}
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_wo_ws_m(parser, token);
string_or_ident:
if (token->type != LXB_CSS_SYNTAX_TOKEN_STRING
&& token->type != LXB_CSS_SYNTAX_TOKEN_IDENT)
{
goto failed;
}
lxb_css_selectors_state_string_dup_m(selectors, &attribute->value);
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_wo_ws_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_RS_BRACKET) {
goto done;
}
if (token->type != LXB_CSS_SYNTAX_TOKEN_IDENT) {
goto failed;
}
modifier = *lxb_css_syntax_token_string(token)->data;
switch (modifier) {
case 'i':
attribute->modifier = LXB_CSS_SELECTOR_MODIFIER_I;
break;
case 's':
attribute->modifier = LXB_CSS_SELECTOR_MODIFIER_S;
break;
default:
goto failed;
}
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_wo_ws_m(parser, token);
if (token->type != LXB_CSS_SYNTAX_TOKEN_RS_BRACKET) {
goto failed;
}
done:
lxb_css_selectors_state_specificity_set_b(selectors);
lxb_css_syntax_parser_consume(parser);
return LXB_STATUS_OK;
failed:
return lxb_css_parser_unexpected_status(parser);
}
static lxb_status_t
lxb_css_selectors_state_ns(lxb_css_parser_t *parser,
lxb_css_selector_t *selector)
{
const lxb_css_syntax_token_t *token;
lxb_css_parser_token_status_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_DELIM
&& lxb_css_syntax_token_delim_char(token) == '|')
{
lxb_css_syntax_parser_consume(parser);
return lxb_css_selectors_state_ns_ident(parser, selector);
}
return LXB_STATUS_OK;
}
static lxb_status_t
lxb_css_selectors_state_ns_ident(lxb_css_parser_t *parser,
lxb_css_selector_t *selector)
{
lxb_status_t status;
const lxb_css_syntax_token_t *token;
lxb_css_selectors_t *selectors;
lxb_css_parser_token_status_m(parser, token);
if (token->type == LXB_CSS_SYNTAX_TOKEN_IDENT) {
selectors = parser->selectors;
lxb_css_selectors_state_specificity_set_c(selectors);
selector->type = LXB_CSS_SELECTOR_TYPE_ELEMENT;
selector->ns = selector->name;
lexbor_str_clean_all(&selector->name);
status = lxb_css_syntax_token_string_dup(lxb_css_syntax_token_string(token),
&selector->name, parser->memory->mraw);
lxb_css_syntax_parser_consume(parser);
return status;
}
else if (token->type == LXB_CSS_SYNTAX_TOKEN_DELIM
&& lxb_css_syntax_token_delim_char(token) == '*')
{
lxb_css_syntax_parser_consume(parser);
selector->type = LXB_CSS_SELECTOR_TYPE_ANY;
selector->ns = selector->name;
selector->name.data = lexbor_mraw_alloc(parser->memory->mraw, 2);
if (selector->name.data == NULL) {
return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
}
selector->name.data[0] = '*';
selector->name.data[1] = '\0';
selector->name.length = 1;
return LXB_STATUS_OK;
}
return lxb_css_parser_unexpected_status(parser);
}
static lxb_status_t
lxb_css_selectors_state_pseudo_class(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token)
{
lxb_status_t status;
lxb_css_log_message_t *msg;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
const lxb_css_selectors_pseudo_data_t *pseudo;
selectors = parser->selectors;
lxb_css_selectors_state_append(parser, selectors, selector);
selector->type = LXB_CSS_SELECTOR_TYPE_PSEUDO_CLASS;
lxb_css_selectors_state_string_dup_m(selectors, &selector->name);
pseudo = lxb_css_selector_pseudo_class_by_name(selector->name.data,
selector->name.length);
if (pseudo == NULL) {
return lxb_css_parser_unexpected_status(parser);
}
switch (pseudo->id) {
case LXB_CSS_SELECTOR_PSEUDO_CLASS_CURRENT:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_DEFAULT:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FOCUS_VISIBLE:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FOCUS_WITHIN:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FULLSCREEN:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUTURE:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_IN_RANGE:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_INDETERMINATE:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_INVALID:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_LOCAL_LINK:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_OUT_OF_RANGE:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_PAST:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_SCOPE:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_TARGET:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_TARGET_WITHIN:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_USER_INVALID:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_VALID:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_VISITED:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_WARNING:
msg = lxb_css_log_not_supported(parser->log,
lxb_css_selectors_module_name,
(const char *) selector->name.data);
if (msg == NULL) {
return lxb_css_parser_memory_fail(parser);
}
return lxb_css_parser_unexpected_status(parser);
default:
break;
}
selector->u.pseudo.type = pseudo->id;
selector->u.pseudo.data = NULL;
lxb_css_syntax_parser_consume(parser);
return LXB_STATUS_OK;
}
static lxb_status_t
lxb_css_selectors_state_pseudo_class_function(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
lxb_css_parser_state_f success)
{
lxb_status_t status;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
lxb_css_log_message_t *msg;
lxb_css_syntax_rule_t *rule;
const lxb_css_selectors_pseudo_data_func_t *func;
selectors = parser->selectors;
lxb_css_selectors_state_append(parser, selectors, selector);
selector->type = LXB_CSS_SELECTOR_TYPE_PSEUDO_CLASS_FUNCTION;
lxb_css_selectors_state_string_dup_m(selectors, &selector->name);
func = lxb_css_selector_pseudo_class_function_by_name(selector->name.data,
selector->name.length);
if (func == NULL) {
return lxb_css_parser_unexpected_status(parser);
}
switch (func->id) {
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_DIR:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_LANG:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_COL:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_LAST_COL:
msg = lxb_css_log_not_supported(parser->log,
lxb_css_selectors_module_name,
(const char *) selector->name.data);
if (msg == NULL) {
goto failed;
}
return lxb_css_parser_unexpected_status(parser);
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_CHILD:
case LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_LAST_CHILD:
lxb_css_selectors_state_specificity_set_b(selectors);
break;
default:
break;
}
selector->u.pseudo.type = func->id;
selector->u.pseudo.data = NULL;
selectors->combinator = func->combinator;
selectors->comb_default = func->combinator;
selectors->parent = selector;
rule = lxb_css_syntax_consume_function(parser, token, &func->cb,
success, selectors->list_last);
if (rule == NULL) {
goto failed;
}
lxb_css_syntax_parser_consume(parser);
return LXB_STATUS_OK;
failed:
(void) lxb_css_parser_memory_fail(parser);
return parser->status;
}
static lxb_status_t
lxb_css_selectors_state_pseudo_element(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token)
{
lxb_status_t status;
lxb_css_log_message_t *msg;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
const lxb_css_selectors_pseudo_data_t *pseudo;
selectors = parser->selectors;
lxb_css_selectors_state_append(parser, selectors, selector);
selector->type = LXB_CSS_SELECTOR_TYPE_PSEUDO_ELEMENT;
lxb_css_selectors_state_string_dup_m(selectors, &selector->name);
pseudo = lxb_css_selector_pseudo_element_by_name(selector->name.data,
selector->name.length);
if (pseudo == NULL) {
return lxb_css_parser_unexpected_status(parser);
}
switch (pseudo->id) {
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_AFTER:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_BACKDROP:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_BEFORE:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_FIRST_LETTER:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_FIRST_LINE:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_GRAMMAR_ERROR:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_INACTIVE_SELECTION:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_MARKER:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_PLACEHOLDER:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_SELECTION:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_SPELLING_ERROR:
case LXB_CSS_SELECTOR_PSEUDO_ELEMENT_TARGET_TEXT:
msg = lxb_css_log_not_supported(parser->log,
lxb_css_selectors_module_name,
(const char *) selector->name.data);
if (msg == NULL) {
(void) lxb_css_parser_memory_fail(parser);
return parser->status;
}
return lxb_css_parser_unexpected_status(parser);
default:
break;
}
selector->u.pseudo.type = pseudo->id;
selector->u.pseudo.data = NULL;
lxb_css_syntax_parser_consume(parser);
return LXB_STATUS_OK;
}
static lxb_status_t
lxb_css_selectors_state_pseudo_element_function(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
lxb_css_parser_state_f success)
{
lxb_status_t status;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors;
lxb_css_syntax_rule_t *rule;
const lxb_css_selectors_pseudo_data_func_t *func;
selectors = parser->selectors;
lxb_css_selectors_state_append(parser, selectors, selector);
selector->type = LXB_CSS_SELECTOR_TYPE_PSEUDO_ELEMENT_FUNCTION;
lxb_css_selectors_state_string_dup_m(selectors, &selector->name);
func = lxb_css_selector_pseudo_element_function_by_name(selector->name.data,
selector->name.length);
if (func == NULL) {
return lxb_css_parser_unexpected_status(parser);
}
selector->u.pseudo.type = func->id;
selector->u.pseudo.data = NULL;
selectors->combinator = func->combinator;
selectors->comb_default = func->combinator;
selectors->parent = selector;
rule = lxb_css_syntax_consume_function(parser, token, &func->cb,
success, selectors->list_last);
if (rule == NULL) {
(void) lxb_css_parser_memory_fail(parser);
return parser->status;
}
lxb_css_syntax_parser_consume(parser);
return LXB_STATUS_OK;
}
lxb_inline void
lxb_css_selectors_state_restore_combinator(lxb_css_selectors_t *selectors)
{
lxb_css_selector_t *parent;
lxb_css_selector_combinator_t comb_default;
const lxb_css_selectors_pseudo_data_func_t *data_func;
comb_default = LXB_CSS_SELECTOR_COMBINATOR_DESCENDANT;
if (selectors->parent != NULL) {
parent = selectors->parent;
if (parent->type == LXB_CSS_SELECTOR_TYPE_PSEUDO_CLASS_FUNCTION) {
data_func = lxb_css_selector_pseudo_class_function_by_id(parent->u.pseudo.type);
}
else {
data_func = lxb_css_selector_pseudo_element_function_by_id(parent->u.pseudo.type);
}
comb_default = data_func->combinator;
}
selectors->combinator = LXB_CSS_SELECTOR_COMBINATOR_CLOSE;
selectors->comb_default = comb_default;
}
lxb_status_t
lxb_css_selectors_state_function_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, bool failed)
{
bool cy;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors = parser->selectors;
if (token->type == LXB_CSS_SYNTAX_TOKEN__EOF) {
(void) lxb_css_log_format(parser->log, LXB_CSS_LOG_SYNTAX_ERROR,
"%s. End Of File in pseudo function",
lxb_css_selectors_module_name);
}
if (selectors->list_last == NULL) {
lxb_css_selectors_state_restore_parent(selectors, ctx);
goto empty;
}
lxb_css_selectors_state_restore_parent(selectors, ctx);
return LXB_STATUS_OK;
empty:
selector = selectors->list_last->last;
cy = selector->type == LXB_CSS_SELECTOR_TYPE_PSEUDO_CLASS_FUNCTION;
cy = lxb_css_selector_pseudo_function_can_empty(selector->u.pseudo.type,
cy);
if (cy) {
lxb_css_parser_set_ok(parser);
return LXB_STATUS_OK;
}
(void) lxb_css_log_format(parser->log, LXB_CSS_LOG_SYNTAX_ERROR,
"%s. Pseudo function can't be empty: %S()",
lxb_css_selectors_module_name, &selector->name);
lxb_css_selector_remove(selector);
lxb_css_selector_destroy(selector);
lxb_css_parser_failed_set_by_id(parser, -1, true);
selectors->err_in_function = true;
return LXB_STATUS_OK;
}
lxb_status_t
lxb_css_selectors_state_function_forgiving(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, bool failed)
{
return lxb_css_selectors_state_forgiving_cb(parser, token, ctx,
lxb_css_selectors_state_complex_list,
failed);
}
lxb_status_t
lxb_css_selectors_state_function_forgiving_relative(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, bool failed)
{
return lxb_css_selectors_state_forgiving_cb(parser, token, ctx,
lxb_css_selectors_state_relative_list,
failed);
}
static lxb_status_t
lxb_css_selectors_state_forgiving_cb(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
void *ctx, lxb_css_parser_state_f state,
bool failed)
{
bool cy;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors = parser->selectors;
lxb_css_parser_set_ok(parser);
if (token->type == LXB_CSS_SYNTAX_TOKEN__EOF) {
(void) lxb_css_log_format(parser->log, LXB_CSS_LOG_SYNTAX_ERROR,
"%s. End Of File in pseudo function",
lxb_css_selectors_module_name);
}
if (selectors->list_last == NULL) {
lxb_css_selectors_state_restore_parent(selectors, ctx);
goto empty;
}
if (selectors->parent->u.pseudo.type
== LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_WHERE)
{
selectors->list_last->specificity = 0;
}
lxb_css_selectors_state_restore_parent(selectors, ctx);
return LXB_STATUS_OK;
empty:
selector = selectors->list_last->last;
cy = selector->type == LXB_CSS_SELECTOR_TYPE_PSEUDO_CLASS_FUNCTION;
cy = lxb_css_selector_pseudo_function_can_empty(selector->u.pseudo.type,
cy);
if (cy) {
return LXB_STATUS_OK;
}
(void) lxb_css_log_format(parser->log, LXB_CSS_LOG_SYNTAX_ERROR,
"%s. Pseudo function can't be empty: %S()",
lxb_css_selectors_module_name, &selector->name);
lxb_css_selector_remove(selector);
lxb_css_selector_destroy(selector);
lxb_css_parser_failed_set_by_id(parser, -1, true);
selectors->err_in_function = true;
return LXB_STATUS_OK;
}
static void
lxb_css_selectors_state_restore_parent(lxb_css_selectors_t *selectors,
lxb_css_selector_list_t *last)
{
uint32_t src, dst;
if (selectors->list_last != NULL && selectors->list_last != last) {
dst = last->specificity;
src = selectors->list_last->specificity;
selectors->list_last = 0;
if (last->parent == NULL) {
lxb_css_selector_sp_add_a(dst, lxb_css_selector_sp_a(src));
lxb_css_selector_sp_add_b(dst, lxb_css_selector_sp_b(src));
lxb_css_selector_sp_add_c(dst, lxb_css_selector_sp_c(src));
}
else if (selectors->combinator == LXB_CSS_SELECTOR_COMBINATOR_CLOSE) {
dst |= src;
}
else if (src > dst) {
dst = src;
}
last->specificity = dst;
}
if (selectors->list != NULL) {
last->last->u.pseudo.data = selectors->list;
}
selectors->list_last = last;
/* Get first Selector in chain. */
while (last->prev != NULL) {
last = last->prev;
}
selectors->list = last;
selectors->parent = last->parent;
lxb_css_selectors_state_restore_combinator(selectors);
}
static bool
lxb_css_selectors_state_list_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token,
lxb_css_parser_state_f state)
{
lxb_css_parser_state_t *states;
lxb_css_selectors_t *selectors = parser->selectors;
if (lxb_css_parser_is_failed(parser)) {
token = lxb_css_selectors_state_function_error(parser, token);
if (token == NULL) {
return lxb_css_parser_fail(parser,
LXB_STATUS_ERROR_MEMORY_ALLOCATION);
}
}
else if (token->type == LXB_CSS_SYNTAX_TOKEN_WHITESPACE) {
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_m(parser, token);
}
if (selectors->parent != NULL && selectors->list_last &&
selectors->list_last->prev != NULL)
{
lxb_css_selectors_state_func_specificity(selectors);
}
if (token->type != LXB_CSS_SYNTAX_TOKEN_COMMA) {
states = lxb_css_parser_states_current(parser);
if (states->root) {
if (token->type != LXB_CSS_SYNTAX_TOKEN__END) {
token = lxb_css_selectors_state_function_error(parser, token);
if (token == NULL) {
return lxb_css_parser_fail(parser,
LXB_STATUS_ERROR_MEMORY_ALLOCATION);
}
}
(void) lxb_css_parser_states_pop(parser);
return lxb_css_parser_success(parser);
}
return lxb_css_selectors_done(parser);
}
selectors->combinator = selectors->comb_default;
lxb_css_syntax_token_consume(parser->tkz);
lxb_css_parser_state_set(parser, state);
lxb_css_parser_set_ok(parser);
return true;
}
static bool
lxb_css_selectors_state_end(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token, void *ctx)
{
lxb_css_parser_state_t *states;
if (token->type == LXB_CSS_SYNTAX_TOKEN_WHITESPACE) {
lxb_css_syntax_parser_consume(parser);
lxb_css_parser_token_status_m(parser, token);
}
if (lxb_css_parser_is_failed(parser)) {
token = lxb_css_selectors_state_function_error(parser, token);
if (token == NULL) {
return lxb_css_parser_fail(parser,
LXB_STATUS_ERROR_MEMORY_ALLOCATION);
}
}
states = lxb_css_parser_states_current(parser);
if (states->root) {
if (token->type != LXB_CSS_SYNTAX_TOKEN__END) {
token = lxb_css_selectors_state_function_error(parser, token);
if (token == NULL) {
return lxb_css_parser_fail(parser,
LXB_STATUS_ERROR_MEMORY_ALLOCATION);
}
}
(void) lxb_css_parser_states_pop(parser);
return lxb_css_parser_success(parser);
}
return lxb_css_selectors_done(parser);
}
static const lxb_css_syntax_token_t *
lxb_css_selectors_state_function_error(lxb_css_parser_t *parser,
const lxb_css_syntax_token_t *token)
{
bool cy, comma;
lxb_css_selector_list_t *list;
lxb_css_selector_t *selector;
lxb_css_selectors_t *selectors = parser->selectors;
const lxb_css_syntax_token_t *origin;
const lxb_css_selectors_pseudo_data_func_t *func;
cy = false;
comma = true;
list = selectors->list_last;
selector = selectors->parent;
if (selector != NULL) {
cy = selector->type == LXB_CSS_SELECTOR_TYPE_PSEUDO_CLASS_FUNCTION;
func = lxb_css_selector_pseudo_function_by_id(selector->u.pseudo.type,
cy);
if (func == NULL) {
return NULL;
}
cy = func->forgiving;
comma = func->comma;
}
if (!selectors->err_in_function) {
origin = lxb_css_syntax_token(parser->tkz);
if (origin == NULL) {
return NULL;
}
if (token->type != LXB_CSS_SYNTAX_TOKEN__END) {
origin = token;
}
else if (origin->type != LXB_CSS_SYNTAX_TOKEN__EOF) {
origin = NULL;
}
if (origin != NULL) {
if (lxb_css_syntax_token_error(parser, origin,
"Selectors") == NULL)
{
return NULL;
}
}
}
selectors->err_in_function = false;
if (cy) {
lxb_css_selector_list_selectors_remove(selectors, list);
lxb_css_selector_list_destroy(list);
while (token != NULL
&& token->type != LXB_CSS_SYNTAX_TOKEN__END)
{
if (comma == true
&& token->type == LXB_CSS_SYNTAX_TOKEN_COMMA
&& lxb_css_parser_rule_deep(parser) == 0)
{
break;
}
lxb_css_syntax_parser_consume(parser);
token = lxb_css_syntax_parser_token(parser);
}
return token;
}
lxb_css_selector_list_destroy_chain(selectors->list);
selectors->list = NULL;
selectors->list_last = NULL;
while (token != NULL
&& token->type != LXB_CSS_SYNTAX_TOKEN__END)
{
lxb_css_syntax_parser_consume(parser);
token = lxb_css_syntax_parser_token(parser);
}
return token;
}