cataract 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +9 -0
- data/CHANGELOG.md +8 -0
- data/ext/cataract/cataract.c +124 -11
- data/ext/cataract/cataract.h +13 -2
- data/ext/cataract/css_parser.c +298 -36
- data/ext/cataract/flatten.c +6 -2
- data/ext/cataract/specificity.c +13 -2
- data/lib/cataract/at_rule.rb +4 -2
- data/lib/cataract/conditional_group.rb +72 -0
- data/lib/cataract/native.rb +1 -0
- data/lib/cataract/pure/flatten.rb +7 -1
- data/lib/cataract/pure/parser.rb +266 -24
- data/lib/cataract/pure/serializer.rb +95 -8
- data/lib/cataract/pure/specificity.rb +10 -2
- data/lib/cataract/pure.rb +1 -0
- data/lib/cataract/rule.rb +8 -4
- data/lib/cataract/stylesheet.rb +67 -20
- data/lib/cataract/version.rb +1 -1
- metadata +3 -1
data/ext/cataract/css_parser.c
CHANGED
|
@@ -26,9 +26,11 @@ typedef struct {
|
|
|
26
26
|
VALUE imports_array; // Array of ImportStatement structs
|
|
27
27
|
VALUE media_queries; // Array of MediaQuery structs
|
|
28
28
|
VALUE media_query_lists; // Hash: list_id => Array of MediaQuery IDs
|
|
29
|
+
VALUE conditional_groups; // Array of ConditionalGroup structs (@supports/@layer/@container/@scope)
|
|
29
30
|
int rule_id_counter; // Next rule ID (0-indexed)
|
|
30
31
|
int next_selector_list_id; // Next selector list ID (0-indexed)
|
|
31
32
|
int media_query_id_counter; // Next MediaQuery ID (0-indexed)
|
|
33
|
+
int conditional_group_id_counter; // Next ConditionalGroup ID (0-indexed)
|
|
32
34
|
int next_media_query_list_id; // Next media query list ID (0-indexed)
|
|
33
35
|
int media_query_count; // Safety limit for media queries
|
|
34
36
|
BOOLEAN has_nesting; // Set to 1 if any nested rules are created
|
|
@@ -62,6 +64,7 @@ static inline ParserContext init_child_context(ParserContext *parent) {
|
|
|
62
64
|
child.imports_array = rb_ary_new();
|
|
63
65
|
child.media_queries = rb_ary_new();
|
|
64
66
|
child.media_query_lists = rb_hash_new();
|
|
67
|
+
child.conditional_groups = rb_ary_new();
|
|
65
68
|
|
|
66
69
|
child.selector_lists_enabled = parent->selector_lists_enabled;
|
|
67
70
|
|
|
@@ -885,7 +888,8 @@ static VALUE parse_declarations(const char *start, const char *end, ParserContex
|
|
|
885
888
|
|
|
886
889
|
// Forward declarations
|
|
887
890
|
static void parse_css_recursive(ParserContext *ctx, const char *css, const char *pe,
|
|
888
|
-
VALUE parent_media_sym, VALUE parent_selector, VALUE parent_rule_id, int parent_media_query_id
|
|
891
|
+
VALUE parent_media_sym, VALUE parent_selector, VALUE parent_rule_id, int parent_media_query_id,
|
|
892
|
+
int parent_conditional_group_id);
|
|
889
893
|
static VALUE combine_media_queries(VALUE parent, VALUE child);
|
|
890
894
|
|
|
891
895
|
/*
|
|
@@ -1032,7 +1036,8 @@ static void parse_single_media_query(const char *query_start, const char *query_
|
|
|
1032
1036
|
* Returns: Array of declarations (only the declarations, not nested rules)
|
|
1033
1037
|
*/
|
|
1034
1038
|
static VALUE parse_mixed_block(ParserContext *ctx, const char *start, const char *end,
|
|
1035
|
-
VALUE parent_selector, VALUE parent_rule_id, VALUE parent_media_sym, int parent_media_query_id
|
|
1039
|
+
VALUE parent_selector, VALUE parent_rule_id, VALUE parent_media_sym, int parent_media_query_id,
|
|
1040
|
+
int parent_conditional_group_id) {
|
|
1036
1041
|
// Check recursion depth to prevent stack overflow
|
|
1037
1042
|
if (ctx->depth > MAX_PARSE_DEPTH) {
|
|
1038
1043
|
rb_raise(eDepthError,
|
|
@@ -1156,11 +1161,13 @@ static VALUE parse_mixed_block(ParserContext *ctx, const char *start, const char
|
|
|
1156
1161
|
// Parse mixed block (may contain declarations and/or nested @media)
|
|
1157
1162
|
ctx->depth++;
|
|
1158
1163
|
VALUE media_declarations = parse_mixed_block(ctx, media_block_start, media_block_end,
|
|
1159
|
-
parent_selector, INT2FIX(media_rule_id), Qnil, combined_media_query_id
|
|
1164
|
+
parent_selector, INT2FIX(media_rule_id), Qnil, combined_media_query_id,
|
|
1165
|
+
parent_conditional_group_id);
|
|
1160
1166
|
ctx->depth--;
|
|
1161
1167
|
|
|
1162
1168
|
// Create rule with the parent selector and declarations, associated with combined media query
|
|
1163
1169
|
VALUE media_query_id_val = INT2FIX(combined_media_query_id);
|
|
1170
|
+
VALUE conditional_group_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
1164
1171
|
VALUE rule = rb_struct_new(cRule,
|
|
1165
1172
|
INT2FIX(media_rule_id),
|
|
1166
1173
|
parent_selector,
|
|
@@ -1169,7 +1176,8 @@ static VALUE parse_mixed_block(ParserContext *ctx, const char *start, const char
|
|
|
1169
1176
|
parent_rule_id, // Link to parent for nested @media serialization
|
|
1170
1177
|
Qnil, // nesting_style (nil for @media nesting)
|
|
1171
1178
|
Qnil, // selector_list_id
|
|
1172
|
-
media_query_id_val // media_query_id from parent context
|
|
1179
|
+
media_query_id_val, // media_query_id from parent context
|
|
1180
|
+
conditional_group_id_val // conditional_group_id from parent context
|
|
1173
1181
|
);
|
|
1174
1182
|
|
|
1175
1183
|
// Mark that we have nesting (only set once)
|
|
@@ -1251,11 +1259,13 @@ static VALUE parse_mixed_block(ParserContext *ctx, const char *start, const char
|
|
|
1251
1259
|
// Recursively parse nested block
|
|
1252
1260
|
ctx->depth++;
|
|
1253
1261
|
VALUE nested_declarations = parse_mixed_block(ctx, nested_block_start, nested_block_end,
|
|
1254
|
-
resolved_selector, INT2FIX(rule_id), parent_media_sym, parent_media_query_id
|
|
1262
|
+
resolved_selector, INT2FIX(rule_id), parent_media_sym, parent_media_query_id,
|
|
1263
|
+
parent_conditional_group_id);
|
|
1255
1264
|
ctx->depth--;
|
|
1256
1265
|
|
|
1257
1266
|
// Create rule for nested selector
|
|
1258
1267
|
VALUE media_query_id_val = (parent_media_query_id >= 0) ? INT2FIX(parent_media_query_id) : Qnil;
|
|
1268
|
+
VALUE conditional_group_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
1259
1269
|
VALUE rule = rb_struct_new(cRule,
|
|
1260
1270
|
INT2FIX(rule_id),
|
|
1261
1271
|
resolved_selector,
|
|
@@ -1264,7 +1274,8 @@ static VALUE parse_mixed_block(ParserContext *ctx, const char *start, const char
|
|
|
1264
1274
|
parent_rule_id,
|
|
1265
1275
|
nesting_style,
|
|
1266
1276
|
Qnil, // selector_list_id
|
|
1267
|
-
media_query_id_val // media_query_id from parent context
|
|
1277
|
+
media_query_id_val, // media_query_id from parent context
|
|
1278
|
+
conditional_group_id_val // conditional_group_id from parent context
|
|
1268
1279
|
);
|
|
1269
1280
|
|
|
1270
1281
|
// Mark that we have nesting (only set once)
|
|
@@ -1508,7 +1519,8 @@ static void parse_import_statement(ParserContext *ctx, const char **p_ptr, const
|
|
|
1508
1519
|
* "@media ... { ... }" construct (including its closing '}', if found).
|
|
1509
1520
|
*/
|
|
1510
1521
|
static void handle_media_at_rule(ParserContext *ctx, const char **p_ptr, const char *pe,
|
|
1511
|
-
VALUE parent_media_sym, int parent_media_query_id
|
|
1522
|
+
VALUE parent_media_sym, int parent_media_query_id,
|
|
1523
|
+
int parent_conditional_group_id) {
|
|
1512
1524
|
const char *p = *p_ptr;
|
|
1513
1525
|
p += 6; // Skip "@media"
|
|
1514
1526
|
|
|
@@ -1541,7 +1553,8 @@ static void handle_media_at_rule(ParserContext *ctx, const char **p_ptr, const c
|
|
|
1541
1553
|
|
|
1542
1554
|
// Parse block contents with NO media query context
|
|
1543
1555
|
ctx->depth++;
|
|
1544
|
-
parse_css_recursive(ctx, block_start, block_end, parent_media_sym, NO_PARENT_SELECTOR, NO_PARENT_RULE_ID, parent_media_query_id
|
|
1556
|
+
parse_css_recursive(ctx, block_start, block_end, parent_media_sym, NO_PARENT_SELECTOR, NO_PARENT_RULE_ID, parent_media_query_id,
|
|
1557
|
+
parent_conditional_group_id);
|
|
1545
1558
|
ctx->depth--;
|
|
1546
1559
|
|
|
1547
1560
|
if (p < pe && *p == '}') p++;
|
|
@@ -1645,13 +1658,60 @@ static void handle_media_at_rule(ParserContext *ctx, const char **p_ptr, const c
|
|
|
1645
1658
|
|
|
1646
1659
|
// Recursively parse @media block with new media query context
|
|
1647
1660
|
ctx->depth++;
|
|
1648
|
-
parse_css_recursive(ctx, block_start, block_end, combined_media_sym, NO_PARENT_SELECTOR, NO_PARENT_RULE_ID, current_media_query_id
|
|
1661
|
+
parse_css_recursive(ctx, block_start, block_end, combined_media_sym, NO_PARENT_SELECTOR, NO_PARENT_RULE_ID, current_media_query_id,
|
|
1662
|
+
parent_conditional_group_id);
|
|
1649
1663
|
ctx->depth--;
|
|
1650
1664
|
|
|
1651
1665
|
if (p < pe && *p == '}') p++;
|
|
1652
1666
|
*p_ptr = p;
|
|
1653
1667
|
}
|
|
1654
1668
|
|
|
1669
|
+
// Case-insensitive ASCII check for "not" - used to tell an anonymous
|
|
1670
|
+
// @container condition starting with "not" apart from a genuine
|
|
1671
|
+
// container-query name (CSS reserves "not"/"and"/"or" as condition
|
|
1672
|
+
// keywords, so they're never valid name idents).
|
|
1673
|
+
static BOOLEAN is_not_keyword(const char *start, long len) {
|
|
1674
|
+
if (len != 3) return 0;
|
|
1675
|
+
|
|
1676
|
+
char c0 = start[0], c1 = start[1], c2 = start[2];
|
|
1677
|
+
if (c0 >= 'A' && c0 <= 'Z') c0 += 32;
|
|
1678
|
+
if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
|
|
1679
|
+
if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
|
|
1680
|
+
return c0 == 'n' && c1 == 'o' && c2 == 't';
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/*
|
|
1684
|
+
* Split a raw @container prelude into name/condition VALUEs (Qnil if
|
|
1685
|
+
* absent):
|
|
1686
|
+
* "sidebar (min-width: 400px)" -> name="sidebar", condition="(min-width: 400px)"
|
|
1687
|
+
* "(min-width: 400px)" -> name=nil, condition="(min-width: 400px)"
|
|
1688
|
+
* "sidebar" -> name="sidebar", condition=nil
|
|
1689
|
+
* "not (min-width: 400px)" -> name=nil, condition="not (min-width: 400px)" (anonymous)
|
|
1690
|
+
*/
|
|
1691
|
+
static void split_container_prelude(const char *start, const char *end, VALUE *name_out, VALUE *condition_out) {
|
|
1692
|
+
if (start >= end || *start == '(') {
|
|
1693
|
+
*name_out = Qnil;
|
|
1694
|
+
*condition_out = (start < end) ? rb_utf8_str_new(start, end - start) : Qnil;
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
const char *space = start;
|
|
1699
|
+
while (space < end && !IS_WHITESPACE(*space)) space++;
|
|
1700
|
+
|
|
1701
|
+
if (is_not_keyword(start, space - start)) {
|
|
1702
|
+
*name_out = Qnil;
|
|
1703
|
+
*condition_out = rb_utf8_str_new(start, end - start);
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
*name_out = rb_utf8_str_new(start, space - start);
|
|
1708
|
+
|
|
1709
|
+
const char *rest_start = space;
|
|
1710
|
+
while (rest_start < end && IS_WHITESPACE(*rest_start)) rest_start++;
|
|
1711
|
+
|
|
1712
|
+
*condition_out = (rest_start < end) ? rb_utf8_str_new(rest_start, end - rest_start) : Qnil;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1655
1715
|
/*
|
|
1656
1716
|
* Handle a conditional-group at-rule (@supports/@layer/@container/@scope)
|
|
1657
1717
|
* found at brace_depth 0. Behaves like @media but doesn't affect media
|
|
@@ -1660,29 +1720,77 @@ static void handle_media_at_rule(ParserContext *ctx, const char **p_ptr, const c
|
|
|
1660
1720
|
* '@'; at_start/at_name_end/at_name_len describe the already-scanned
|
|
1661
1721
|
* at-rule name. Advances *p_ptr past the entire construct (including its
|
|
1662
1722
|
* closing '}', if found).
|
|
1723
|
+
*
|
|
1724
|
+
* @supports and @container build a real ConditionalGroup and tag their
|
|
1725
|
+
* child rules with a conditional_group_id - @layer/@scope still just
|
|
1726
|
+
* recurse with parent_conditional_group_id passed through unchanged (their
|
|
1727
|
+
* condition/name text is extracted below only for the missing-condition
|
|
1728
|
+
* validation, same as before). Each gets its own faithful representation
|
|
1729
|
+
* in its own follow-up bead.
|
|
1663
1730
|
*/
|
|
1664
1731
|
static void handle_conditional_group_at_rule(ParserContext *ctx, const char **p_ptr, const char *pe,
|
|
1665
1732
|
const char *at_start, const char *at_name_end, long at_name_len,
|
|
1666
1733
|
VALUE parent_media_sym, VALUE parent_selector, VALUE parent_rule_id,
|
|
1667
|
-
int parent_media_query_id) {
|
|
1734
|
+
int parent_media_query_id, int parent_conditional_group_id) {
|
|
1735
|
+
BOOLEAN is_supports = (at_name_len == 8 && strncmp(at_start, "supports", 8) == 0);
|
|
1736
|
+
BOOLEAN is_container = (at_name_len == 9 && strncmp(at_start, "container", 9) == 0);
|
|
1737
|
+
BOOLEAN is_layer = (at_name_len == 5 && strncmp(at_start, "layer", 5) == 0);
|
|
1738
|
+
|
|
1668
1739
|
// Check if this rule requires a condition
|
|
1669
|
-
BOOLEAN requires_condition =
|
|
1670
|
-
(at_name_len == 8 && strncmp(at_start, "supports", 8) == 0) ||
|
|
1671
|
-
(at_name_len == 9 && strncmp(at_start, "container", 9) == 0);
|
|
1740
|
+
BOOLEAN requires_condition = is_supports || is_container;
|
|
1672
1741
|
|
|
1673
1742
|
// Extract condition (between at-rule name and opening brace)
|
|
1674
1743
|
const char *cond_start = at_name_end;
|
|
1675
1744
|
while (cond_start < pe && IS_WHITESPACE(*cond_start)) cond_start++;
|
|
1676
1745
|
|
|
1677
|
-
// Skip to
|
|
1746
|
+
// Skip to whichever terminator comes first: '{' (block form) or ';'
|
|
1747
|
+
// (statement form - only valid for @layer, but we still stop there for
|
|
1748
|
+
// the others rather than scanning straight through it into whatever
|
|
1749
|
+
// rule happens to follow)
|
|
1678
1750
|
const char *p = at_name_end;
|
|
1679
|
-
while (p < pe && *p != '{') p++;
|
|
1751
|
+
while (p < pe && *p != '{' && *p != ';') p++;
|
|
1680
1752
|
|
|
1681
|
-
if (p >= pe
|
|
1753
|
+
if (p >= pe) {
|
|
1682
1754
|
*p_ptr = p;
|
|
1683
1755
|
return; // Malformed
|
|
1684
1756
|
}
|
|
1685
1757
|
|
|
1758
|
+
if (*p == ';') {
|
|
1759
|
+
// Trim trailing whitespace off the name list
|
|
1760
|
+
const char *stmt_end = p;
|
|
1761
|
+
while (stmt_end > cond_start && IS_WHITESPACE(*(stmt_end - 1))) stmt_end--;
|
|
1762
|
+
|
|
1763
|
+
if (is_layer && stmt_end > cond_start) {
|
|
1764
|
+
VALUE selector = rb_utf8_str_new(at_start - 1, stmt_end - (at_start - 1));
|
|
1765
|
+
int rule_id = ctx->rule_id_counter++;
|
|
1766
|
+
VALUE media_query_id_val = (parent_media_query_id >= 0) ? INT2FIX(parent_media_query_id) : Qnil;
|
|
1767
|
+
VALUE conditional_group_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
1768
|
+
VALUE at_rule = rb_struct_new(cAtRule,
|
|
1769
|
+
INT2FIX(rule_id),
|
|
1770
|
+
selector,
|
|
1771
|
+
Qnil, // content - statement form has no block
|
|
1772
|
+
Qnil, // specificity
|
|
1773
|
+
media_query_id_val,
|
|
1774
|
+
conditional_group_id_val
|
|
1775
|
+
);
|
|
1776
|
+
rb_ary_push(ctx->rules_array, at_rule);
|
|
1777
|
+
RB_GC_GUARD(selector);
|
|
1778
|
+
|
|
1779
|
+
if (!NIL_P(parent_media_sym)) {
|
|
1780
|
+
VALUE rule_ids = rb_hash_aref(ctx->media_index, parent_media_sym);
|
|
1781
|
+
if (NIL_P(rule_ids)) {
|
|
1782
|
+
rule_ids = rb_ary_new();
|
|
1783
|
+
rb_hash_aset(ctx->media_index, parent_media_sym, rule_ids);
|
|
1784
|
+
}
|
|
1785
|
+
rb_ary_push(rule_ids, INT2FIX(rule_id));
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
p++; // Skip ';'
|
|
1790
|
+
*p_ptr = p;
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1686
1794
|
// Trim condition
|
|
1687
1795
|
const char *cond_end = p;
|
|
1688
1796
|
while (cond_end > cond_start && IS_WHITESPACE(*(cond_end - 1))) cond_end--;
|
|
@@ -1701,15 +1809,134 @@ static void handle_conditional_group_at_rule(ParserContext *ctx, const char **p_
|
|
|
1701
1809
|
const char *block_end = find_matching_brace_strict(p, pe, ctx->check_unclosed_blocks);
|
|
1702
1810
|
p = block_end;
|
|
1703
1811
|
|
|
1812
|
+
// For @supports/@container/@layer, build a ConditionalGroup (nested
|
|
1813
|
+
// inside the enclosing one, if any) and tag every rule parsed inside
|
|
1814
|
+
// this block with it. @scope passes parent_conditional_group_id through
|
|
1815
|
+
// unchanged until its own bead gives it the same treatment.
|
|
1816
|
+
int child_conditional_group_id = parent_conditional_group_id;
|
|
1817
|
+
if ((is_supports || is_container || is_layer) && (cond_end > cond_start || is_layer)) {
|
|
1818
|
+
VALUE name, condition;
|
|
1819
|
+
ID type_id;
|
|
1820
|
+
|
|
1821
|
+
if (is_supports) {
|
|
1822
|
+
name = Qnil;
|
|
1823
|
+
condition = rb_utf8_str_new(cond_start, cond_end - cond_start);
|
|
1824
|
+
type_id = rb_intern("supports");
|
|
1825
|
+
} else if (is_container) {
|
|
1826
|
+
split_container_prelude(cond_start, cond_end, &name, &condition);
|
|
1827
|
+
type_id = rb_intern("container");
|
|
1828
|
+
} else {
|
|
1829
|
+
name = (cond_end > cond_start) ? rb_utf8_str_new(cond_start, cond_end - cond_start) : Qnil;
|
|
1830
|
+
condition = Qnil;
|
|
1831
|
+
type_id = rb_intern("layer");
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
VALUE parent_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
1835
|
+
VALUE group = rb_struct_new(cConditionalGroup,
|
|
1836
|
+
INT2FIX(ctx->conditional_group_id_counter),
|
|
1837
|
+
ID2SYM(type_id),
|
|
1838
|
+
name,
|
|
1839
|
+
condition,
|
|
1840
|
+
parent_id_val
|
|
1841
|
+
);
|
|
1842
|
+
rb_ary_push(ctx->conditional_groups, group);
|
|
1843
|
+
child_conditional_group_id = ctx->conditional_group_id_counter;
|
|
1844
|
+
ctx->conditional_group_id_counter++;
|
|
1845
|
+
RB_GC_GUARD(condition);
|
|
1846
|
+
RB_GC_GUARD(name);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1704
1849
|
// Recursively parse block content (preserve parent media context)
|
|
1705
1850
|
ctx->depth++;
|
|
1706
|
-
parse_css_recursive(ctx, block_start, block_end, parent_media_sym, parent_selector, parent_rule_id, parent_media_query_id
|
|
1851
|
+
parse_css_recursive(ctx, block_start, block_end, parent_media_sym, parent_selector, parent_rule_id, parent_media_query_id,
|
|
1852
|
+
child_conditional_group_id);
|
|
1707
1853
|
ctx->depth--;
|
|
1708
1854
|
|
|
1709
1855
|
if (p < pe && *p == '}') p++;
|
|
1710
1856
|
*p_ptr = p;
|
|
1711
1857
|
}
|
|
1712
1858
|
|
|
1859
|
+
/*
|
|
1860
|
+
* Handle @namespace [prefix] (url(...) | "...") ; found at brace_depth 0.
|
|
1861
|
+
* Per css-namespaces-3 this is purely a statement - it always ends in ';'
|
|
1862
|
+
* and never has a block - so it's modeled exactly like @layer's statement
|
|
1863
|
+
* form above: captured verbatim as an AtRule with content nil, since
|
|
1864
|
+
* multiple @namespace declarations are legal (one default + any number of
|
|
1865
|
+
* prefixed ones) and there's no resolution/merge semantics to compute.
|
|
1866
|
+
*
|
|
1867
|
+
* Called with *p_ptr pointing at the '@'. Advances *p_ptr past the
|
|
1868
|
+
* statement (including the terminating ';').
|
|
1869
|
+
*/
|
|
1870
|
+
static void handle_namespace_at_rule(ParserContext *ctx, const char **p_ptr, const char *pe,
|
|
1871
|
+
VALUE parent_media_sym, int parent_media_query_id,
|
|
1872
|
+
int parent_conditional_group_id) {
|
|
1873
|
+
const char *selector_start = *p_ptr; // Points to '@'
|
|
1874
|
+
const char *p = selector_start + 10; // Skip "@namespace"
|
|
1875
|
+
|
|
1876
|
+
// Scan to the terminating ';', honoring quoted strings and paren depth
|
|
1877
|
+
// so a URI given as url(...) can't have a stray ';' inside it (e.g. a
|
|
1878
|
+
// data: URI) end the statement early.
|
|
1879
|
+
char in_quote = 0;
|
|
1880
|
+
int paren_depth = 0;
|
|
1881
|
+
while (p < pe) {
|
|
1882
|
+
char c = *p;
|
|
1883
|
+
if (in_quote) {
|
|
1884
|
+
if (c == in_quote) {
|
|
1885
|
+
in_quote = 0;
|
|
1886
|
+
} else if (c == '\\' && p + 1 < pe) {
|
|
1887
|
+
p++;
|
|
1888
|
+
}
|
|
1889
|
+
} else if (c == ';' && paren_depth == 0) {
|
|
1890
|
+
break;
|
|
1891
|
+
} else if (c == '"' || c == '\'') {
|
|
1892
|
+
in_quote = c;
|
|
1893
|
+
} else if (c == '(') {
|
|
1894
|
+
paren_depth++;
|
|
1895
|
+
} else if (c == ')') {
|
|
1896
|
+
paren_depth--;
|
|
1897
|
+
}
|
|
1898
|
+
p++;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
if (p >= pe) {
|
|
1902
|
+
*p_ptr = p;
|
|
1903
|
+
return; // Malformed - no terminating ';'
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
// Trim trailing whitespace off the captured text
|
|
1907
|
+
const char *stmt_end = p;
|
|
1908
|
+
while (stmt_end > selector_start + 10 && IS_WHITESPACE(*(stmt_end - 1))) stmt_end--;
|
|
1909
|
+
|
|
1910
|
+
if (stmt_end > selector_start + 10) {
|
|
1911
|
+
VALUE selector = rb_utf8_str_new(selector_start, stmt_end - selector_start);
|
|
1912
|
+
int rule_id = ctx->rule_id_counter++;
|
|
1913
|
+
VALUE media_query_id_val = (parent_media_query_id >= 0) ? INT2FIX(parent_media_query_id) : Qnil;
|
|
1914
|
+
VALUE conditional_group_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
1915
|
+
VALUE at_rule = rb_struct_new(cAtRule,
|
|
1916
|
+
INT2FIX(rule_id),
|
|
1917
|
+
selector,
|
|
1918
|
+
Qnil, // content - statement form has no block
|
|
1919
|
+
Qnil, // specificity
|
|
1920
|
+
media_query_id_val,
|
|
1921
|
+
conditional_group_id_val
|
|
1922
|
+
);
|
|
1923
|
+
rb_ary_push(ctx->rules_array, at_rule);
|
|
1924
|
+
RB_GC_GUARD(selector);
|
|
1925
|
+
|
|
1926
|
+
if (!NIL_P(parent_media_sym)) {
|
|
1927
|
+
VALUE rule_ids = rb_hash_aref(ctx->media_index, parent_media_sym);
|
|
1928
|
+
if (NIL_P(rule_ids)) {
|
|
1929
|
+
rule_ids = rb_ary_new();
|
|
1930
|
+
rb_hash_aset(ctx->media_index, parent_media_sym, rule_ids);
|
|
1931
|
+
}
|
|
1932
|
+
rb_ary_push(rule_ids, INT2FIX(rule_id));
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
p++; // Skip ';'
|
|
1937
|
+
*p_ptr = p;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1713
1940
|
/*
|
|
1714
1941
|
* Handle an @keyframes (or -webkit-/-moz- prefixed) at-rule found at
|
|
1715
1942
|
* brace_depth 0. Called with *p_ptr pointing at the '@'; at_name_end marks
|
|
@@ -1717,7 +1944,8 @@ static void handle_conditional_group_at_rule(ParserContext *ctx, const char **p_
|
|
|
1717
1944
|
* entire construct (including its closing '}', if found).
|
|
1718
1945
|
*/
|
|
1719
1946
|
static void handle_keyframes_at_rule(ParserContext *ctx, const char **p_ptr, const char *pe,
|
|
1720
|
-
const char *at_name_end, VALUE parent_media_sym, int parent_media_query_id
|
|
1947
|
+
const char *at_name_end, VALUE parent_media_sym, int parent_media_query_id,
|
|
1948
|
+
int parent_conditional_group_id) {
|
|
1721
1949
|
// Build full selector string: "@keyframes fade"
|
|
1722
1950
|
const char *selector_start = *p_ptr; // Points to '@'
|
|
1723
1951
|
const char *p = at_name_end;
|
|
@@ -1746,19 +1974,22 @@ static void handle_keyframes_at_rule(ParserContext *ctx, const char **p_ptr, con
|
|
|
1746
1974
|
// NOT land in ctx->rules_array — they need their own array to become this
|
|
1747
1975
|
// AtRule's `content` below, so parse into an isolated child context instead.
|
|
1748
1976
|
ParserContext nested_ctx = init_child_context(ctx);
|
|
1749
|
-
parse_css_recursive(&nested_ctx, block_start, block_end, NO_PARENT_MEDIA, NO_PARENT_SELECTOR, NO_PARENT_RULE_ID, NO_MEDIA_QUERY_ID
|
|
1977
|
+
parse_css_recursive(&nested_ctx, block_start, block_end, NO_PARENT_MEDIA, NO_PARENT_SELECTOR, NO_PARENT_RULE_ID, NO_MEDIA_QUERY_ID,
|
|
1978
|
+
NO_CONDITIONAL_GROUP_ID);
|
|
1750
1979
|
|
|
1751
1980
|
// Get rule ID and increment
|
|
1752
1981
|
int rule_id = ctx->rule_id_counter++;
|
|
1753
1982
|
|
|
1754
1983
|
// Create AtRule with nested rules
|
|
1755
1984
|
VALUE media_query_id_val = (parent_media_query_id >= 0) ? INT2FIX(parent_media_query_id) : Qnil;
|
|
1985
|
+
VALUE conditional_group_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
1756
1986
|
VALUE at_rule = rb_struct_new(cAtRule,
|
|
1757
1987
|
INT2FIX(rule_id),
|
|
1758
1988
|
selector,
|
|
1759
1989
|
nested_ctx.rules_array, // Array of Rule (keyframe blocks)
|
|
1760
1990
|
Qnil, // specificity
|
|
1761
|
-
media_query_id_val // media_query_id from parent context
|
|
1991
|
+
media_query_id_val, // media_query_id from parent context
|
|
1992
|
+
conditional_group_id_val // conditional_group_id from parent context
|
|
1762
1993
|
);
|
|
1763
1994
|
|
|
1764
1995
|
// Add to rules array
|
|
@@ -1785,7 +2016,8 @@ static void handle_keyframes_at_rule(ParserContext *ctx, const char **p_ptr, con
|
|
|
1785
2016
|
* closing '}', if found).
|
|
1786
2017
|
*/
|
|
1787
2018
|
static void handle_font_face_at_rule(ParserContext *ctx, const char **p_ptr, const char *pe,
|
|
1788
|
-
const char *at_name_end, VALUE parent_media_sym, int parent_media_query_id
|
|
2019
|
+
const char *at_name_end, VALUE parent_media_sym, int parent_media_query_id,
|
|
2020
|
+
int parent_conditional_group_id) {
|
|
1789
2021
|
// Build selector string: "@font-face"
|
|
1790
2022
|
const char *selector_start = *p_ptr; // Points to '@'
|
|
1791
2023
|
const char *p = at_name_end;
|
|
@@ -1817,12 +2049,14 @@ static void handle_font_face_at_rule(ParserContext *ctx, const char **p_ptr, con
|
|
|
1817
2049
|
|
|
1818
2050
|
// Create AtRule with declarations
|
|
1819
2051
|
VALUE media_query_id_val = (parent_media_query_id >= 0) ? INT2FIX(parent_media_query_id) : Qnil;
|
|
2052
|
+
VALUE conditional_group_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
1820
2053
|
VALUE at_rule = rb_struct_new(cAtRule,
|
|
1821
2054
|
INT2FIX(rule_id),
|
|
1822
2055
|
selector,
|
|
1823
2056
|
declarations, // Array of Declaration
|
|
1824
2057
|
Qnil, // specificity
|
|
1825
|
-
media_query_id_val // media_query_id from parent context
|
|
2058
|
+
media_query_id_val, // media_query_id from parent context
|
|
2059
|
+
conditional_group_id_val // conditional_group_id from parent context
|
|
1826
2060
|
);
|
|
1827
2061
|
|
|
1828
2062
|
// Add to rules array
|
|
@@ -1852,7 +2086,8 @@ static void handle_font_face_at_rule(ParserContext *ctx, const char **p_ptr, con
|
|
|
1852
2086
|
* selector_start/decl_start - the caller does that once this returns.
|
|
1853
2087
|
*/
|
|
1854
2088
|
static void finish_rule_block(ParserContext *ctx, const char *selector_start, const char *decl_start, const char *p,
|
|
1855
|
-
VALUE parent_selector, VALUE parent_rule_id, VALUE parent_media_sym, int parent_media_query_id
|
|
2089
|
+
VALUE parent_selector, VALUE parent_rule_id, VALUE parent_media_sym, int parent_media_query_id,
|
|
2090
|
+
int parent_conditional_group_id) {
|
|
1856
2091
|
// We've found a complete CSS rule block - now determine if it has nesting
|
|
1857
2092
|
// Example: .parent { color: red; & .child { font-size: 14px; } }
|
|
1858
2093
|
// ^selector_start ^decl_start ^p (at })
|
|
@@ -2006,6 +2241,7 @@ static void finish_rule_block(ParserContext *ctx, const char *selector_start, co
|
|
|
2006
2241
|
|
|
2007
2242
|
// Create Rule
|
|
2008
2243
|
VALUE media_query_id_val = (parent_media_query_id >= 0) ? INT2FIX(parent_media_query_id) : Qnil;
|
|
2244
|
+
VALUE conditional_group_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
2009
2245
|
VALUE rule = rb_struct_new(cRule,
|
|
2010
2246
|
INT2FIX(rule_id),
|
|
2011
2247
|
resolved_selector,
|
|
@@ -2014,7 +2250,8 @@ static void finish_rule_block(ParserContext *ctx, const char *selector_start, co
|
|
|
2014
2250
|
parent_id_val,
|
|
2015
2251
|
nesting_style_val,
|
|
2016
2252
|
selector_list_id_val,
|
|
2017
|
-
media_query_id_val // media_query_id from parent context
|
|
2253
|
+
media_query_id_val, // media_query_id from parent context
|
|
2254
|
+
conditional_group_id_val // conditional_group_id from parent context
|
|
2018
2255
|
);
|
|
2019
2256
|
|
|
2020
2257
|
// Track rule in selector list if applicable
|
|
@@ -2119,7 +2356,8 @@ static void finish_rule_block(ParserContext *ctx, const char *selector_start, co
|
|
|
2119
2356
|
// Nested rules will be added AFTER the placeholder
|
|
2120
2357
|
ctx->depth++;
|
|
2121
2358
|
VALUE parent_declarations = parse_mixed_block(ctx, decl_start, p,
|
|
2122
|
-
resolved_current, INT2FIX(current_rule_id), parent_media_sym, parent_media_query_id
|
|
2359
|
+
resolved_current, INT2FIX(current_rule_id), parent_media_sym, parent_media_query_id,
|
|
2360
|
+
parent_conditional_group_id);
|
|
2123
2361
|
ctx->depth--;
|
|
2124
2362
|
|
|
2125
2363
|
// Determine selector_list_id value
|
|
@@ -2128,6 +2366,7 @@ static void finish_rule_block(ParserContext *ctx, const char *selector_start, co
|
|
|
2128
2366
|
// Create parent rule and replace placeholder
|
|
2129
2367
|
// Always create the rule (even if empty) to avoid edge cases
|
|
2130
2368
|
VALUE media_query_id_val = (parent_media_query_id >= 0) ? INT2FIX(parent_media_query_id) : Qnil;
|
|
2369
|
+
VALUE conditional_group_id_val = (parent_conditional_group_id >= 0) ? INT2FIX(parent_conditional_group_id) : Qnil;
|
|
2131
2370
|
VALUE rule = rb_struct_new(cRule,
|
|
2132
2371
|
INT2FIX(current_rule_id),
|
|
2133
2372
|
resolved_current,
|
|
@@ -2136,7 +2375,8 @@ static void finish_rule_block(ParserContext *ctx, const char *selector_start, co
|
|
|
2136
2375
|
current_parent_id,
|
|
2137
2376
|
current_nesting_style,
|
|
2138
2377
|
selector_list_id_val,
|
|
2139
|
-
media_query_id_val // media_query_id from parent context
|
|
2378
|
+
media_query_id_val, // media_query_id from parent context
|
|
2379
|
+
conditional_group_id_val // conditional_group_id from parent context
|
|
2140
2380
|
);
|
|
2141
2381
|
|
|
2142
2382
|
// Track rule in selector list if applicable
|
|
@@ -2169,7 +2409,8 @@ static void finish_rule_block(ParserContext *ctx, const char *selector_start, co
|
|
|
2169
2409
|
* parent_rule_id: Parent rule ID (Fixnum) for nested rules (or Qnil for top-level)
|
|
2170
2410
|
*/
|
|
2171
2411
|
static void parse_css_recursive(ParserContext *ctx, const char *css, const char *pe,
|
|
2172
|
-
VALUE parent_media_sym, VALUE parent_selector, VALUE parent_rule_id, int parent_media_query_id
|
|
2412
|
+
VALUE parent_media_sym, VALUE parent_selector, VALUE parent_rule_id, int parent_media_query_id,
|
|
2413
|
+
int parent_conditional_group_id) {
|
|
2173
2414
|
// Check recursion depth to prevent stack overflow
|
|
2174
2415
|
if (ctx->depth > MAX_PARSE_DEPTH) {
|
|
2175
2416
|
rb_raise(eDepthError,
|
|
@@ -2215,10 +2456,22 @@ static void parse_css_recursive(ParserContext *ctx, const char *css, const char
|
|
|
2215
2456
|
continue;
|
|
2216
2457
|
}
|
|
2217
2458
|
|
|
2459
|
+
// Check for @namespace at-rule (statement-only - always ends in
|
|
2460
|
+
// ';', never has a block; css-namespaces-3). A quote isn't a valid
|
|
2461
|
+
// ident character, so a prefix or value can immediately follow
|
|
2462
|
+
// with no whitespace (e.g. minified `@namespace"...";` or
|
|
2463
|
+
// `@namespace svg"...";`) - accept that alongside whitespace/';'.
|
|
2464
|
+
if (RB_UNLIKELY(brace_depth == 0 && p + 10 < pe && *p == '@' &&
|
|
2465
|
+
strncmp(p + 1, "namespace", 9) == 0 &&
|
|
2466
|
+
(IS_WHITESPACE(p[10]) || p[10] == ';' || p[10] == '"' || p[10] == '\''))) {
|
|
2467
|
+
handle_namespace_at_rule(ctx, &p, pe, parent_media_sym, parent_media_query_id, parent_conditional_group_id);
|
|
2468
|
+
continue;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2218
2471
|
// Check for @media at-rule (only at depth 0)
|
|
2219
2472
|
if (RB_UNLIKELY(brace_depth == 0 && p + 6 < pe && *p == '@' &&
|
|
2220
|
-
strncmp(p + 1, "media", 5) == 0 && IS_WHITESPACE(p[6]))) {
|
|
2221
|
-
handle_media_at_rule(ctx, &p, pe, parent_media_sym, parent_media_query_id);
|
|
2473
|
+
strncmp(p + 1, "media", 5) == 0 && (IS_WHITESPACE(p[6]) || p[6] == '('))) {
|
|
2474
|
+
handle_media_at_rule(ctx, &p, pe, parent_media_sym, parent_media_query_id, parent_conditional_group_id);
|
|
2222
2475
|
continue;
|
|
2223
2476
|
}
|
|
2224
2477
|
|
|
@@ -2229,8 +2482,11 @@ static void parse_css_recursive(ParserContext *ctx, const char *css, const char
|
|
|
2229
2482
|
const char *at_start = p + 1;
|
|
2230
2483
|
const char *at_name_end = at_start;
|
|
2231
2484
|
|
|
2232
|
-
// Find end of at-rule name (stop at whitespace or
|
|
2233
|
-
|
|
2485
|
+
// Find end of at-rule name (stop at whitespace, '{', '(', or ';' -
|
|
2486
|
+
// an ident naturally terminates at any of these even with no
|
|
2487
|
+
// space, e.g. minified "@supports(display:grid){")
|
|
2488
|
+
while (at_name_end < pe && !IS_WHITESPACE(*at_name_end) && *at_name_end != '{' &&
|
|
2489
|
+
*at_name_end != '(' && *at_name_end != ';') {
|
|
2234
2490
|
at_name_end++;
|
|
2235
2491
|
}
|
|
2236
2492
|
|
|
@@ -2245,7 +2501,8 @@ static void parse_css_recursive(ParserContext *ctx, const char *css, const char
|
|
|
2245
2501
|
|
|
2246
2502
|
if (is_conditional_group) {
|
|
2247
2503
|
handle_conditional_group_at_rule(ctx, &p, pe, at_start, at_name_end, at_name_len,
|
|
2248
|
-
parent_media_sym, parent_selector, parent_rule_id, parent_media_query_id
|
|
2504
|
+
parent_media_sym, parent_selector, parent_rule_id, parent_media_query_id,
|
|
2505
|
+
parent_conditional_group_id);
|
|
2249
2506
|
continue;
|
|
2250
2507
|
}
|
|
2251
2508
|
|
|
@@ -2257,7 +2514,7 @@ static void parse_css_recursive(ParserContext *ctx, const char *css, const char
|
|
|
2257
2514
|
(at_name_len == 13 && strncmp(at_start, "-moz-keyframes", 13) == 0);
|
|
2258
2515
|
|
|
2259
2516
|
if (is_keyframes) {
|
|
2260
|
-
handle_keyframes_at_rule(ctx, &p, pe, at_name_end, parent_media_sym, parent_media_query_id);
|
|
2517
|
+
handle_keyframes_at_rule(ctx, &p, pe, at_name_end, parent_media_sym, parent_media_query_id, parent_conditional_group_id);
|
|
2261
2518
|
continue;
|
|
2262
2519
|
}
|
|
2263
2520
|
|
|
@@ -2265,7 +2522,7 @@ static void parse_css_recursive(ParserContext *ctx, const char *css, const char
|
|
|
2265
2522
|
BOOLEAN is_font_face = (at_name_len == 9 && strncmp(at_start, "font-face", 9) == 0);
|
|
2266
2523
|
|
|
2267
2524
|
if (is_font_face) {
|
|
2268
|
-
handle_font_face_at_rule(ctx, &p, pe, at_name_end, parent_media_sym, parent_media_query_id);
|
|
2525
|
+
handle_font_face_at_rule(ctx, &p, pe, at_name_end, parent_media_sym, parent_media_query_id, parent_conditional_group_id);
|
|
2269
2526
|
continue;
|
|
2270
2527
|
}
|
|
2271
2528
|
}
|
|
@@ -2289,7 +2546,8 @@ static void parse_css_recursive(ParserContext *ctx, const char *css, const char
|
|
|
2289
2546
|
brace_depth--;
|
|
2290
2547
|
if (brace_depth == 0 && selector_start != NULL && decl_start != NULL) {
|
|
2291
2548
|
finish_rule_block(ctx, selector_start, decl_start, p,
|
|
2292
|
-
parent_selector, parent_rule_id, parent_media_sym, parent_media_query_id
|
|
2549
|
+
parent_selector, parent_rule_id, parent_media_sym, parent_media_query_id,
|
|
2550
|
+
parent_conditional_group_id);
|
|
2293
2551
|
selector_start = NULL;
|
|
2294
2552
|
decl_start = NULL;
|
|
2295
2553
|
}
|
|
@@ -2406,9 +2664,11 @@ VALUE parse_css_new_impl(VALUE css_string, VALUE parser_options, int rule_id_off
|
|
|
2406
2664
|
ctx.imports_array = rb_ary_new();
|
|
2407
2665
|
ctx.media_queries = rb_ary_new();
|
|
2408
2666
|
ctx.media_query_lists = rb_hash_new();
|
|
2667
|
+
ctx.conditional_groups = rb_ary_new();
|
|
2409
2668
|
ctx.rule_id_counter = rule_id_offset; // Start from offset
|
|
2410
2669
|
ctx.next_selector_list_id = 0; // Start from 0
|
|
2411
2670
|
ctx.media_query_id_counter = 0; // Start from 0
|
|
2671
|
+
ctx.conditional_group_id_counter = 0; // Start from 0
|
|
2412
2672
|
ctx.next_media_query_list_id = 0; // Start from 0
|
|
2413
2673
|
ctx.media_query_count = 0;
|
|
2414
2674
|
ctx.has_nesting = 0; // Will be set to 1 if any nested rules are created
|
|
@@ -2429,7 +2689,7 @@ VALUE parse_css_new_impl(VALUE css_string, VALUE parser_options, int rule_id_off
|
|
|
2429
2689
|
|
|
2430
2690
|
// Parse CSS (top-level, no parent context)
|
|
2431
2691
|
DEBUG_PRINTF("[PARSE] Starting parse_css_recursive from: %.80s\n", p);
|
|
2432
|
-
parse_css_recursive(&ctx, p, pe, NO_PARENT_MEDIA, NO_PARENT_SELECTOR, NO_PARENT_RULE_ID, NO_MEDIA_QUERY_ID);
|
|
2692
|
+
parse_css_recursive(&ctx, p, pe, NO_PARENT_MEDIA, NO_PARENT_SELECTOR, NO_PARENT_RULE_ID, NO_MEDIA_QUERY_ID, NO_CONDITIONAL_GROUP_ID);
|
|
2433
2693
|
|
|
2434
2694
|
// Build result hash
|
|
2435
2695
|
VALUE result = rb_hash_new();
|
|
@@ -2438,6 +2698,7 @@ VALUE parse_css_new_impl(VALUE css_string, VALUE parser_options, int rule_id_off
|
|
|
2438
2698
|
rb_hash_aset(result, ID2SYM(rb_intern("media_queries")), ctx.media_queries);
|
|
2439
2699
|
rb_hash_aset(result, ID2SYM(rb_intern("_selector_lists")), ctx.selector_lists);
|
|
2440
2700
|
rb_hash_aset(result, ID2SYM(rb_intern("_media_query_lists")), ctx.media_query_lists);
|
|
2701
|
+
rb_hash_aset(result, ID2SYM(rb_intern("conditional_groups")), ctx.conditional_groups);
|
|
2441
2702
|
rb_hash_aset(result, ID2SYM(rb_intern("imports")), ctx.imports_array);
|
|
2442
2703
|
rb_hash_aset(result, ID2SYM(rb_intern("charset")), charset);
|
|
2443
2704
|
rb_hash_aset(result, ID2SYM(rb_intern("last_rule_id")), INT2FIX(ctx.rule_id_counter));
|
|
@@ -2449,6 +2710,7 @@ VALUE parse_css_new_impl(VALUE css_string, VALUE parser_options, int rule_id_off
|
|
|
2449
2710
|
RB_GC_GUARD(ctx.media_queries);
|
|
2450
2711
|
RB_GC_GUARD(ctx.selector_lists);
|
|
2451
2712
|
RB_GC_GUARD(ctx.media_query_lists);
|
|
2713
|
+
RB_GC_GUARD(ctx.conditional_groups);
|
|
2452
2714
|
RB_GC_GUARD(ctx.imports_array);
|
|
2453
2715
|
RB_GC_GUARD(ctx.base_uri);
|
|
2454
2716
|
RB_GC_GUARD(ctx.uri_resolver);
|
data/ext/cataract/flatten.c
CHANGED
|
@@ -644,12 +644,15 @@ static int flatten_selector_group_callback(VALUE group_key, VALUE group_indices,
|
|
|
644
644
|
|
|
645
645
|
int new_rule_id = *ctx->rule_id_counter;
|
|
646
646
|
|
|
647
|
-
// Extract media_query_id from first rule in group
|
|
647
|
+
// Extract media_query_id/conditional_group_id from first rule in group
|
|
648
|
+
// (all should share both, same as selector_list_id above)
|
|
648
649
|
VALUE media_query_id = Qnil;
|
|
650
|
+
VALUE conditional_group_id = Qnil;
|
|
649
651
|
if (RARRAY_LEN(group_indices) > 0) {
|
|
650
652
|
long first_rule_idx = FIX2LONG(RARRAY_AREF(group_indices, 0));
|
|
651
653
|
VALUE first_rule = RARRAY_AREF(ctx->rules_array, first_rule_idx);
|
|
652
654
|
media_query_id = rb_struct_aref(first_rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
655
|
+
conditional_group_id = rb_struct_aref(first_rule, INT2FIX(RULE_CONDITIONAL_GROUP_ID));
|
|
653
656
|
}
|
|
654
657
|
|
|
655
658
|
// Track old rule IDs to new rule ID mapping (only for rules in media queries)
|
|
@@ -672,7 +675,8 @@ static int flatten_selector_group_callback(VALUE group_key, VALUE group_indices,
|
|
|
672
675
|
Qnil, // parent_rule_id
|
|
673
676
|
Qnil, // nesting_style
|
|
674
677
|
selector_list_id, // Preserve selector_list_id if all rules in group share same ID
|
|
675
|
-
media_query_id // Preserve media_query_id from source rules
|
|
678
|
+
media_query_id, // Preserve media_query_id from source rules
|
|
679
|
+
conditional_group_id // Preserve conditional_group_id from source rules
|
|
676
680
|
);
|
|
677
681
|
rb_ary_push(ctx->merged_rules, new_rule);
|
|
678
682
|
|