33#include " webscene_css_matching.h"
44#include " webscene_css_transitions.h"
55
6+ #include < iterator>
7+
68namespace webscene_native ::css {
79// Host supplies rule/keyframe storage, media capability inventory and diagnostics.
8- // This adapter preserves the existing runtime at-rule policy; it is not a full
9- // implementation of supports, layers, containers or CSS nesting.
10+ // This adapter preserves the existing runtime at-rule policy. CSS nesting is
11+ // expanded before selector compilation; supports, layers, and containers remain
12+ // the bounded implementations documented below.
1013template <typename Host>
1114class stylesheet_sink final : public css_syntax_sink {
1215public:
@@ -48,7 +51,16 @@ class stylesheet_sink final : public css_syntax_sink {
4851
4952 if (kind == css_syntax_style_rule) {
5053 current.prelude = std::string (prelude);
51- current.children_active = false ;
54+ if (const auto * parent = nearest_style_frame (); parent != nullptr
55+ && !parent->keyframes ) {
56+ current.prelude = combine_nested_selectors (
57+ parent->prelude , current.prelude );
58+ owner_.record_feature (
59+ " css" , " css-nesting" , " supported" ,
60+ " qualified nested rules with implicit descendant, combinator, and ampersand expansion" ,
61+ " stylesheet-parser" );
62+ }
63+ current.children_active = current.active ;
5264 // Two declarations is the common generated-rule shape and
5365 // avoids the former IR's exact-size allocation without
5466 // over-reserving every small rule.
@@ -156,11 +168,33 @@ class stylesheet_sink final : public css_syntax_sink {
156168 inherited_media.push_back (ancestor.media_query );
157169 }
158170 }
159- owner_.append_parsed_css_style_rule (
160- std::move (current.prelude ),
161- std::move (current.declarations ),
162- inherited_media,
163- stylesheet_address_);
171+ std::vector<completed_style_rule> completed;
172+ completed.reserve (1U + current.nested_rules .size ());
173+ if (!current.declarations .empty ()) {
174+ completed.push_back ({
175+ std::move (current.prelude ),
176+ std::move (current.declarations ),
177+ std::move (inherited_media)});
178+ }
179+ completed.insert (
180+ completed.end (),
181+ std::make_move_iterator (current.nested_rules .begin ()),
182+ std::make_move_iterator (current.nested_rules .end ()));
183+ if (auto * parent = nearest_style_frame (); parent != nullptr
184+ && !parent->keyframes ) {
185+ parent->nested_rules .insert (
186+ parent->nested_rules .end (),
187+ std::make_move_iterator (completed.begin ()),
188+ std::make_move_iterator (completed.end ()));
189+ return true ;
190+ }
191+ for (auto & rule : completed) {
192+ owner_.append_parsed_css_style_rule (
193+ std::move (rule.selector ),
194+ std::move (rule.declarations ),
195+ rule.media_queries ,
196+ stylesheet_address_);
197+ }
164198 } else if (current.keyframes ) {
165199 completed_keyframes_.emplace_back (
166200 std::move (current.prelude ),
@@ -177,19 +211,128 @@ class stylesheet_sink final : public css_syntax_sink {
177211 }
178212
179213private:
214+ struct completed_style_rule final {
215+ std::string selector;
216+ std::vector<css_declaration> declarations;
217+ std::vector<std::string> media_queries;
218+ };
219+
180220 struct frame final {
181221 size_t rule_index{css_syntax_no_parent};
182222 uint32_t kind{css_syntax_style_rule};
183223 std::string prelude;
184224 std::string media_query;
185225 std::vector<css_declaration> declarations;
226+ std::vector<completed_style_rule> nested_rules;
186227 css_opacity_keyframes keyframe_definition;
187228 size_t observed_declarations{0U };
188229 bool active{false };
189230 bool children_active{false };
190231 bool keyframes{false };
191232 };
192233
234+ frame* nearest_style_frame () noexcept
235+ {
236+ for (auto iterator = stack_.rbegin (); iterator != stack_.rend (); ++iterator) {
237+ if (iterator->keyframes ) return nullptr ;
238+ if (iterator->kind == css_syntax_style_rule) return &*iterator;
239+ }
240+ return nullptr ;
241+ }
242+
243+ const frame* nearest_style_frame () const noexcept
244+ {
245+ for (auto iterator = stack_.rbegin (); iterator != stack_.rend (); ++iterator) {
246+ if (iterator->keyframes ) return nullptr ;
247+ if (iterator->kind == css_syntax_style_rule) return &*iterator;
248+ }
249+ return nullptr ;
250+ }
251+
252+ static std::vector<std::string> split_selector_list (std::string_view value)
253+ {
254+ std::vector<std::string> result;
255+ size_t start = 0U ;
256+ size_t parentheses = 0U ;
257+ size_t brackets = 0U ;
258+ char quote = ' \0 ' ;
259+ auto escaped = false ;
260+ for (size_t index = 0U ; index <= value.size (); ++index) {
261+ const auto character = index < value.size () ? value[index] : ' ,' ;
262+ if (escaped) {
263+ escaped = false ;
264+ continue ;
265+ }
266+ if (character == ' \\ ' ) {
267+ escaped = true ;
268+ continue ;
269+ }
270+ if (quote != ' \0 ' ) {
271+ if (character == quote) quote = ' \0 ' ;
272+ continue ;
273+ }
274+ if (character == ' \' ' || character == ' "' ) {
275+ quote = character;
276+ continue ;
277+ }
278+ if (character == ' (' ) ++parentheses;
279+ else if (character == ' )' && parentheses > 0U ) --parentheses;
280+ else if (character == ' [' ) ++brackets;
281+ else if (character == ' ]' && brackets > 0U ) --brackets;
282+ else if (character == ' ,' && parentheses == 0U && brackets == 0U ) {
283+ auto selector = trim_value (value.substr (start, index - start));
284+ if (!selector.empty ()) result.push_back (std::move (selector));
285+ start = index + 1U ;
286+ }
287+ }
288+ return result;
289+ }
290+
291+ static std::string replace_nesting_selector (
292+ std::string child,
293+ const std::string& parent)
294+ {
295+ size_t offset = 0U ;
296+ while ((offset = child.find (' &' , offset)) != std::string::npos) {
297+ child.replace (offset, 1U , parent);
298+ offset += parent.size ();
299+ }
300+ return child;
301+ }
302+
303+ static std::string combine_nested_selectors (
304+ std::string_view parent,
305+ std::string_view child)
306+ {
307+ const auto parents = split_selector_list (parent);
308+ const auto children = split_selector_list (child);
309+ if (parents.empty () || children.empty ()) return {};
310+ std::string parent_reference;
311+ if (parents.size () == 1U ) {
312+ parent_reference = parents.front ();
313+ } else {
314+ parent_reference = " :is(" ;
315+ for (const auto & selector : parents) {
316+ if (parent_reference.size () > 4U ) parent_reference += " , " ;
317+ parent_reference += selector;
318+ }
319+ parent_reference += ' )' ;
320+ }
321+ std::string result;
322+ for (const auto & child_selector : children) {
323+ if (!result.empty ()) result += " , " ;
324+ if (child_selector.find (' &' ) != std::string::npos) {
325+ result += replace_nesting_selector (
326+ child_selector, parent_reference);
327+ } else {
328+ result += parent_reference;
329+ result += ' ' ;
330+ result += child_selector;
331+ }
332+ }
333+ return result;
334+ }
335+
193336 Host& owner_;
194337 const std::string& stylesheet_address_;
195338 std::vector<frame> stack_;
0 commit comments