mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 21:17:44 +00:00
LibWeb: Add basic support for :lang() CSS selector
This doesn't have parsing support for multiple languages in the same selector. Support for language subcodes is not great either. But it does do the basics.
This commit is contained in:
parent
5d672717aa
commit
bdd42c9b0e
4 changed files with 52 additions and 1 deletions
|
@ -603,6 +603,14 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_simple_sel
|
||||||
return ParsingResult::SyntaxError;
|
return ParsingResult::SyntaxError;
|
||||||
}
|
}
|
||||||
simple_selector.pseudo_class.argument_selector_list = not_selector.release_value();
|
simple_selector.pseudo_class.argument_selector_list = not_selector.release_value();
|
||||||
|
} else if (pseudo_function.name().equals_ignoring_case("lang"sv)) {
|
||||||
|
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Lang;
|
||||||
|
if (pseudo_function.values().is_empty()) {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "Empty :lang() selector");
|
||||||
|
return ParsingResult::SyntaxError;
|
||||||
|
}
|
||||||
|
// FIXME: Support multiple, comma-separated, language ranges.
|
||||||
|
simple_selector.pseudo_class.languages.append(pseudo_function.values().first().token().to_string());
|
||||||
} else if (pseudo_function.name().equals_ignoring_case("nth-child"sv)) {
|
} else if (pseudo_function.name().equals_ignoring_case("nth-child"sv)) {
|
||||||
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthChild;
|
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthChild;
|
||||||
if (!parse_nth_child_pattern(simple_selector, pseudo_function, true))
|
if (!parse_nth_child_pattern(simple_selector, pseudo_function, true))
|
||||||
|
|
|
@ -79,6 +79,7 @@ public:
|
||||||
Not,
|
Not,
|
||||||
Where,
|
Where,
|
||||||
Active,
|
Active,
|
||||||
|
Lang,
|
||||||
};
|
};
|
||||||
Type type { Type::None };
|
Type type { Type::None };
|
||||||
|
|
||||||
|
@ -87,6 +88,9 @@ public:
|
||||||
ANPlusBPattern nth_child_pattern;
|
ANPlusBPattern nth_child_pattern;
|
||||||
|
|
||||||
SelectorList argument_selector_list {};
|
SelectorList argument_selector_list {};
|
||||||
|
|
||||||
|
// Used for :lang(en-gb,dk)
|
||||||
|
Vector<FlyString> languages;
|
||||||
};
|
};
|
||||||
PseudoClass pseudo_class {};
|
PseudoClass pseudo_class {};
|
||||||
PseudoElement pseudo_element { PseudoElement::None };
|
PseudoElement pseudo_element { PseudoElement::None };
|
||||||
|
@ -219,6 +223,8 @@ constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Ty
|
||||||
return "not"sv;
|
return "not"sv;
|
||||||
case Selector::SimpleSelector::PseudoClass::Type::Where:
|
case Selector::SimpleSelector::PseudoClass::Type::Where:
|
||||||
return "where"sv;
|
return "where"sv;
|
||||||
|
case Selector::SimpleSelector::PseudoClass::Type::Lang:
|
||||||
|
return "lang"sv;
|
||||||
case Selector::SimpleSelector::PseudoClass::Type::None:
|
case Selector::SimpleSelector::PseudoClass::Type::None:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,34 @@
|
||||||
|
|
||||||
namespace Web::SelectorEngine {
|
namespace Web::SelectorEngine {
|
||||||
|
|
||||||
|
// https://drafts.csswg.org/selectors-4/#the-lang-pseudo
|
||||||
|
static inline bool matches_lang_pseudo_class(DOM::Element const& element, Vector<FlyString> const& languages)
|
||||||
|
{
|
||||||
|
FlyString element_language;
|
||||||
|
for (auto const* e = &element; e; e = e->parent_element()) {
|
||||||
|
auto lang = e->attribute(HTML::AttributeNames::lang);
|
||||||
|
if (!lang.is_null()) {
|
||||||
|
element_language = lang;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (element_language.is_null())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// FIXME: This is ad-hoc. Implement a proper language range matching algorithm as recommended by BCP47.
|
||||||
|
for (auto const& language : languages) {
|
||||||
|
if (language.is_empty())
|
||||||
|
return false;
|
||||||
|
if (language == "*"sv)
|
||||||
|
return true;
|
||||||
|
if (!element_language.view().contains('-'))
|
||||||
|
return element_language.equals_ignoring_case(language);
|
||||||
|
auto parts = element_language.view().split_view('-');
|
||||||
|
return parts[0].equals_ignoring_case(language);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool matches_hover_pseudo_class(DOM::Element const& element)
|
static inline bool matches_hover_pseudo_class(DOM::Element const& element)
|
||||||
{
|
{
|
||||||
auto* hovered_node = element.document().hovered_node();
|
auto* hovered_node = element.document().hovered_node();
|
||||||
|
@ -140,6 +168,8 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
|
||||||
return !next_sibling_with_same_tag_name(element);
|
return !next_sibling_with_same_tag_name(element);
|
||||||
case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyOfType:
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyOfType:
|
||||||
return !previous_sibling_with_same_tag_name(element) && !next_sibling_with_same_tag_name(element);
|
return !previous_sibling_with_same_tag_name(element) && !next_sibling_with_same_tag_name(element);
|
||||||
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang:
|
||||||
|
return matches_lang_pseudo_class(element, pseudo_class.languages);
|
||||||
case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled:
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled:
|
||||||
if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input))
|
if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input))
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -438,10 +438,17 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
|
||||||
case CSS::Selector::SimpleSelector::PseudoClass::Type::Where:
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Where:
|
||||||
pseudo_class_description = "Where";
|
pseudo_class_description = "Where";
|
||||||
break;
|
break;
|
||||||
|
case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang:
|
||||||
|
pseudo_class_description = "Lang";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.appendff(" pseudo_class={}", pseudo_class_description);
|
builder.appendff(" pseudo_class={}", pseudo_class_description);
|
||||||
if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Not
|
if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Lang) {
|
||||||
|
builder.append('(');
|
||||||
|
builder.join(',', pseudo_class.languages);
|
||||||
|
builder.append(')');
|
||||||
|
} else if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Not
|
||||||
|| pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Is
|
|| pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Is
|
||||||
|| pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Where) {
|
|| pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Where) {
|
||||||
builder.append("([");
|
builder.append("([");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue