cataract 0.1.1 → 0.1.2
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/.github/workflows/ci.yml +0 -29
- data/.github/workflows/docs.yml +51 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +1 -0
- data/README.md +1 -1
- data/Rakefile +3 -2
- data/docs/files/EXAMPLE.md +35 -0
- data/examples/css_analyzer/analyzer.rb +12 -29
- data/examples/css_analyzer.rb +0 -7
- data/ext/cataract/cataract.c +19 -16
- data/ext/cataract/cataract.h +4 -0
- data/ext/cataract/extconf.rb +1 -1
- data/ext/cataract/merge.c +731 -59
- data/ext/cataract/shorthand_expander.c +152 -39
- data/ext/cataract_color/color_conversion.c +59 -28
- data/ext/cataract_color/color_conversion_named.c +10 -0
- data/lib/cataract/declarations.rb +1 -2
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +3 -3
- metadata +3 -1
data/ext/cataract/merge.c
CHANGED
|
@@ -333,6 +333,611 @@ void init_merge_constants(void) {
|
|
|
333
333
|
} \
|
|
334
334
|
} while(0)
|
|
335
335
|
|
|
336
|
+
// Helper macro: Recreate dimension shorthand (margin, padding, border-width)
|
|
337
|
+
// Takes a property prefix like "margin" and creates "margin" from margin-top/right/bottom/left
|
|
338
|
+
#define RECREATE_DIMENSION_SHORTHAND(hash, prefix, creator_func) \
|
|
339
|
+
do { \
|
|
340
|
+
char _top_name[64], _right_name[64], _bottom_name[64], _left_name[64]; \
|
|
341
|
+
snprintf(_top_name, sizeof(_top_name), "%s-top", prefix); \
|
|
342
|
+
snprintf(_right_name, sizeof(_right_name), "%s-right", prefix); \
|
|
343
|
+
snprintf(_bottom_name, sizeof(_bottom_name), "%s-bottom", prefix); \
|
|
344
|
+
snprintf(_left_name, sizeof(_left_name), "%s-left", prefix); \
|
|
345
|
+
\
|
|
346
|
+
VALUE _top_data = rb_hash_aref(hash, STR_NEW_CSTR(_top_name)); \
|
|
347
|
+
VALUE _right_data = rb_hash_aref(hash, STR_NEW_CSTR(_right_name)); \
|
|
348
|
+
VALUE _bottom_data = rb_hash_aref(hash, STR_NEW_CSTR(_bottom_name)); \
|
|
349
|
+
VALUE _left_data = rb_hash_aref(hash, STR_NEW_CSTR(_left_name)); \
|
|
350
|
+
\
|
|
351
|
+
if (!NIL_P(_top_data) && !NIL_P(_right_data) && !NIL_P(_bottom_data) && !NIL_P(_left_data)) { \
|
|
352
|
+
VALUE _top_imp = rb_hash_aref(_top_data, ID2SYM(id_important)); \
|
|
353
|
+
VALUE _right_imp = rb_hash_aref(_right_data, ID2SYM(id_important)); \
|
|
354
|
+
VALUE _bottom_imp = rb_hash_aref(_bottom_data, ID2SYM(id_important)); \
|
|
355
|
+
VALUE _left_imp = rb_hash_aref(_left_data, ID2SYM(id_important)); \
|
|
356
|
+
\
|
|
357
|
+
if (RTEST(_top_imp) == RTEST(_right_imp) && RTEST(_top_imp) == RTEST(_bottom_imp) && RTEST(_top_imp) == RTEST(_left_imp)) { \
|
|
358
|
+
VALUE _props = rb_hash_new(); \
|
|
359
|
+
rb_hash_aset(_props, STR_NEW_CSTR(_top_name), rb_hash_aref(_top_data, ID2SYM(id_value))); \
|
|
360
|
+
rb_hash_aset(_props, STR_NEW_CSTR(_right_name), rb_hash_aref(_right_data, ID2SYM(id_value))); \
|
|
361
|
+
rb_hash_aset(_props, STR_NEW_CSTR(_bottom_name), rb_hash_aref(_bottom_data, ID2SYM(id_value))); \
|
|
362
|
+
rb_hash_aset(_props, STR_NEW_CSTR(_left_name), rb_hash_aref(_left_data, ID2SYM(id_value))); \
|
|
363
|
+
\
|
|
364
|
+
VALUE _shorthand_value = creator_func(Qnil, _props); \
|
|
365
|
+
if (!NIL_P(_shorthand_value)) { \
|
|
366
|
+
VALUE _shorthand_data = rb_hash_new(); \
|
|
367
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_value), _shorthand_value); \
|
|
368
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_specificity), rb_hash_aref(_top_data, ID2SYM(id_specificity))); \
|
|
369
|
+
rb_hash_aset(_shorthand_data, ID2SYM(id_important), _top_imp); \
|
|
370
|
+
rb_hash_aset(hash, rb_usascii_str_new(prefix, strlen(prefix)), _shorthand_data); \
|
|
371
|
+
\
|
|
372
|
+
rb_hash_delete(hash, STR_NEW_CSTR(_top_name)); \
|
|
373
|
+
rb_hash_delete(hash, STR_NEW_CSTR(_right_name)); \
|
|
374
|
+
rb_hash_delete(hash, STR_NEW_CSTR(_bottom_name)); \
|
|
375
|
+
rb_hash_delete(hash, STR_NEW_CSTR(_left_name)); \
|
|
376
|
+
DEBUG_PRINTF(" -> Recreated %s shorthand\n", prefix); \
|
|
377
|
+
} \
|
|
378
|
+
} \
|
|
379
|
+
} \
|
|
380
|
+
} while(0)
|
|
381
|
+
|
|
382
|
+
/*
|
|
383
|
+
* Helper struct: For processing expanded properties during merge
|
|
384
|
+
*/
|
|
385
|
+
struct expand_property_data {
|
|
386
|
+
VALUE properties_hash; // Target hash to store properties
|
|
387
|
+
int specificity; // Specificity of the selector
|
|
388
|
+
int is_important; // Whether the original declaration was !important
|
|
389
|
+
long source_order; // Source order of the original declaration
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/*
|
|
393
|
+
* Callback: Process each expanded property and apply cascade rules
|
|
394
|
+
*/
|
|
395
|
+
static int process_expanded_property(VALUE prop_name, VALUE prop_value, VALUE arg) {
|
|
396
|
+
struct expand_property_data *data = (struct expand_property_data *)arg;
|
|
397
|
+
VALUE properties_hash = data->properties_hash;
|
|
398
|
+
int specificity = data->specificity;
|
|
399
|
+
int is_important = data->is_important;
|
|
400
|
+
long source_order = data->source_order;
|
|
401
|
+
|
|
402
|
+
DEBUG_PRINTF(" -> Processing expanded: %s: %s%s\n",
|
|
403
|
+
RSTRING_PTR(prop_name), RSTRING_PTR(prop_value),
|
|
404
|
+
is_important ? " !important" : "");
|
|
405
|
+
|
|
406
|
+
// Apply CSS cascade rules
|
|
407
|
+
VALUE existing = rb_hash_aref(properties_hash, prop_name);
|
|
408
|
+
if (NIL_P(existing)) {
|
|
409
|
+
DEBUG_PRINTF(" -> NEW property\n");
|
|
410
|
+
VALUE prop_data = rb_hash_new();
|
|
411
|
+
rb_hash_aset(prop_data, ID2SYM(id_value), prop_value);
|
|
412
|
+
rb_hash_aset(prop_data, ID2SYM(id_specificity), INT2NUM(specificity));
|
|
413
|
+
rb_hash_aset(prop_data, ID2SYM(id_important), is_important ? Qtrue : Qfalse);
|
|
414
|
+
rb_hash_aset(prop_data, ID2SYM(rb_intern("source_order")), LONG2NUM(source_order));
|
|
415
|
+
rb_hash_aset(properties_hash, prop_name, prop_data);
|
|
416
|
+
} else {
|
|
417
|
+
// Property exists - apply CSS cascade rules
|
|
418
|
+
VALUE existing_important = rb_hash_aref(existing, ID2SYM(id_important));
|
|
419
|
+
VALUE existing_source_order_val = rb_hash_aref(existing, ID2SYM(rb_intern("source_order")));
|
|
420
|
+
|
|
421
|
+
int existing_is_important = RTEST(existing_important);
|
|
422
|
+
long existing_source_order = NUM2LONG(existing_source_order_val);
|
|
423
|
+
|
|
424
|
+
DEBUG_PRINTF(" -> COLLISION: existing important=%d source_order=%ld, new important=%d source_order=%ld\n",
|
|
425
|
+
existing_is_important, existing_source_order, is_important, source_order);
|
|
426
|
+
|
|
427
|
+
int should_replace = 0;
|
|
428
|
+
|
|
429
|
+
// Apply CSS cascade rules:
|
|
430
|
+
// 1. !important always wins over non-!important
|
|
431
|
+
// 2. Higher specificity wins (same selector = same specificity, skip)
|
|
432
|
+
// 3. Later source order wins
|
|
433
|
+
if (is_important && !existing_is_important) {
|
|
434
|
+
// New declaration is !important, existing is not - replace
|
|
435
|
+
should_replace = 1;
|
|
436
|
+
DEBUG_PRINTF(" -> REPLACE (new is !important, existing is not)\n");
|
|
437
|
+
} else if (!is_important && existing_is_important) {
|
|
438
|
+
// Existing declaration is !important, new is not - keep existing
|
|
439
|
+
should_replace = 0;
|
|
440
|
+
DEBUG_PRINTF(" -> KEEP (existing is !important, new is not)\n");
|
|
441
|
+
} else {
|
|
442
|
+
// Same importance level - later source order wins
|
|
443
|
+
should_replace = source_order > existing_source_order;
|
|
444
|
+
DEBUG_PRINTF(" -> %s (same importance, %s source order)\n",
|
|
445
|
+
should_replace ? "REPLACE" : "KEEP",
|
|
446
|
+
should_replace ? "later" : "earlier");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (should_replace) {
|
|
450
|
+
rb_hash_aset(existing, ID2SYM(id_value), prop_value);
|
|
451
|
+
rb_hash_aset(existing, ID2SYM(id_important), is_important ? Qtrue : Qfalse);
|
|
452
|
+
rb_hash_aset(existing, ID2SYM(rb_intern("source_order")), LONG2NUM(source_order));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return ST_CONTINUE;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/*
|
|
460
|
+
* Helper function: Merge multiple rules with the same selector
|
|
461
|
+
*
|
|
462
|
+
* Takes an array of rule indices that all share the same selector,
|
|
463
|
+
* expands shorthands, applies cascade rules, and recreates shorthands.
|
|
464
|
+
*
|
|
465
|
+
* Returns: Array of merged Declaration structs
|
|
466
|
+
*/
|
|
467
|
+
static VALUE merge_rules_for_selector(VALUE rules_array, VALUE rule_indices, VALUE selector) {
|
|
468
|
+
long num_rules_in_group = RARRAY_LEN(rule_indices);
|
|
469
|
+
VALUE properties_hash = rb_hash_new();
|
|
470
|
+
|
|
471
|
+
DEBUG_PRINTF(" [merge_rules_for_selector] Merging %ld rules for selector '%s'\n",
|
|
472
|
+
num_rules_in_group, RSTRING_PTR(selector));
|
|
473
|
+
|
|
474
|
+
// Calculate specificity once for this selector
|
|
475
|
+
VALUE specificity_val = calculate_specificity(Qnil, selector);
|
|
476
|
+
int specificity = NUM2INT(specificity_val);
|
|
477
|
+
|
|
478
|
+
// Process each rule in this selector group
|
|
479
|
+
for (long g = 0; g < num_rules_in_group; g++) {
|
|
480
|
+
long rule_idx = FIX2LONG(rb_ary_entry(rule_indices, g));
|
|
481
|
+
VALUE rule = RARRAY_AREF(rules_array, rule_idx);
|
|
482
|
+
VALUE rule_id_val = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
483
|
+
long rule_id = NUM2LONG(rule_id_val);
|
|
484
|
+
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
485
|
+
long num_decls = RARRAY_LEN(declarations);
|
|
486
|
+
|
|
487
|
+
DEBUG_PRINTF(" [Rule %ld/%ld] rule_id=%ld, %ld declarations\n",
|
|
488
|
+
g + 1, num_rules_in_group, rule_id, num_decls);
|
|
489
|
+
|
|
490
|
+
// Process each declaration
|
|
491
|
+
for (long j = 0; j < num_decls; j++) {
|
|
492
|
+
VALUE decl = RARRAY_AREF(declarations, j);
|
|
493
|
+
VALUE property = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
494
|
+
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
495
|
+
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
496
|
+
int is_important = RTEST(important);
|
|
497
|
+
|
|
498
|
+
// Calculate source order
|
|
499
|
+
long source_order = rule_id * 1000 + j;
|
|
500
|
+
|
|
501
|
+
DEBUG_PRINTF(" [Decl %ld] %s: %s%s (source_order=%ld)\n",
|
|
502
|
+
j, RSTRING_PTR(property), RSTRING_PTR(value),
|
|
503
|
+
is_important ? " !important" : "", source_order);
|
|
504
|
+
|
|
505
|
+
// Expand shorthands (margin, padding, background, font, etc.)
|
|
506
|
+
// The expand functions return a hash of {property => value}
|
|
507
|
+
const char *prop_cstr = RSTRING_PTR(property);
|
|
508
|
+
VALUE expanded = Qnil;
|
|
509
|
+
|
|
510
|
+
if (strcmp(prop_cstr, "margin") == 0) {
|
|
511
|
+
expanded = cataract_expand_margin(Qnil, value);
|
|
512
|
+
DEBUG_PRINTF(" -> Expanding margin shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
513
|
+
} else if (strcmp(prop_cstr, "padding") == 0) {
|
|
514
|
+
expanded = cataract_expand_padding(Qnil, value);
|
|
515
|
+
DEBUG_PRINTF(" -> Expanding padding shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
516
|
+
} else if (strcmp(prop_cstr, "background") == 0) {
|
|
517
|
+
expanded = cataract_expand_background(Qnil, value);
|
|
518
|
+
DEBUG_PRINTF(" -> Expanding background shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
519
|
+
} else if (strcmp(prop_cstr, "font") == 0) {
|
|
520
|
+
expanded = cataract_expand_font(Qnil, value);
|
|
521
|
+
DEBUG_PRINTF(" -> Expanding font shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
522
|
+
} else if (strcmp(prop_cstr, "border") == 0) {
|
|
523
|
+
expanded = cataract_expand_border(Qnil, value);
|
|
524
|
+
DEBUG_PRINTF(" -> Expanding border shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
525
|
+
} else if (strcmp(prop_cstr, "border-color") == 0) {
|
|
526
|
+
expanded = cataract_expand_border_color(Qnil, value);
|
|
527
|
+
DEBUG_PRINTF(" -> Expanding border-color shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
528
|
+
} else if (strcmp(prop_cstr, "border-style") == 0) {
|
|
529
|
+
expanded = cataract_expand_border_style(Qnil, value);
|
|
530
|
+
DEBUG_PRINTF(" -> Expanding border-style shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
531
|
+
} else if (strcmp(prop_cstr, "border-width") == 0) {
|
|
532
|
+
expanded = cataract_expand_border_width(Qnil, value);
|
|
533
|
+
DEBUG_PRINTF(" -> Expanding border-width shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
534
|
+
} else if (strcmp(prop_cstr, "list-style") == 0) {
|
|
535
|
+
expanded = cataract_expand_list_style(Qnil, value);
|
|
536
|
+
DEBUG_PRINTF(" -> Expanding list-style shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
537
|
+
} else if (strcmp(prop_cstr, "border-top") == 0) {
|
|
538
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("top"), value);
|
|
539
|
+
DEBUG_PRINTF(" -> Expanding border-top shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
540
|
+
} else if (strcmp(prop_cstr, "border-right") == 0) {
|
|
541
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("right"), value);
|
|
542
|
+
DEBUG_PRINTF(" -> Expanding border-right shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
543
|
+
} else if (strcmp(prop_cstr, "border-bottom") == 0) {
|
|
544
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("bottom"), value);
|
|
545
|
+
DEBUG_PRINTF(" -> Expanding border-bottom shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
546
|
+
} else if (strcmp(prop_cstr, "border-left") == 0) {
|
|
547
|
+
expanded = cataract_expand_border_side(Qnil, STR_NEW_CSTR("left"), value);
|
|
548
|
+
DEBUG_PRINTF(" -> Expanding border-left shorthand (%ld longhands)\n", RHASH_SIZE(expanded));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Process expanded properties or the original property
|
|
552
|
+
if (!NIL_P(expanded) && RHASH_SIZE(expanded) > 0) {
|
|
553
|
+
// Use rb_hash_foreach to iterate over expanded properties
|
|
554
|
+
struct expand_property_data expand_data = {
|
|
555
|
+
.properties_hash = properties_hash,
|
|
556
|
+
.specificity = specificity,
|
|
557
|
+
.is_important = is_important,
|
|
558
|
+
.source_order = source_order
|
|
559
|
+
};
|
|
560
|
+
rb_hash_foreach(expanded, process_expanded_property, (VALUE)&expand_data);
|
|
561
|
+
} else {
|
|
562
|
+
// No expansion - process the original property directly
|
|
563
|
+
struct expand_property_data expand_data = {
|
|
564
|
+
.properties_hash = properties_hash,
|
|
565
|
+
.specificity = specificity,
|
|
566
|
+
.is_important = is_important,
|
|
567
|
+
.source_order = source_order
|
|
568
|
+
};
|
|
569
|
+
process_expanded_property(property, value, (VALUE)&expand_data);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Recreate shorthands where possible (reduces output size)
|
|
575
|
+
DEBUG_PRINTF(" [merge_rules_for_selector] Recreating shorthands...\n");
|
|
576
|
+
|
|
577
|
+
// Try to recreate margin shorthand (if all 4 sides present)
|
|
578
|
+
RECREATE_DIMENSION_SHORTHAND(properties_hash, "margin", cataract_create_margin_shorthand);
|
|
579
|
+
|
|
580
|
+
// Try to recreate padding shorthand (if all 4 sides present)
|
|
581
|
+
RECREATE_DIMENSION_SHORTHAND(properties_hash, "padding", cataract_create_padding_shorthand);
|
|
582
|
+
|
|
583
|
+
// Try to recreate border-width shorthand (if all 4 sides present)
|
|
584
|
+
{
|
|
585
|
+
VALUE top = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-top-width"));
|
|
586
|
+
VALUE right = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-right-width"));
|
|
587
|
+
VALUE bottom = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-bottom-width"));
|
|
588
|
+
VALUE left = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-left-width"));
|
|
589
|
+
|
|
590
|
+
if (!NIL_P(top) && !NIL_P(right) && !NIL_P(bottom) && !NIL_P(left)) {
|
|
591
|
+
VALUE top_imp = rb_hash_aref(top, ID2SYM(id_important));
|
|
592
|
+
VALUE right_imp = rb_hash_aref(right, ID2SYM(id_important));
|
|
593
|
+
VALUE bottom_imp = rb_hash_aref(bottom, ID2SYM(id_important));
|
|
594
|
+
VALUE left_imp = rb_hash_aref(left, ID2SYM(id_important));
|
|
595
|
+
|
|
596
|
+
if (RTEST(top_imp) == RTEST(right_imp) && RTEST(top_imp) == RTEST(bottom_imp) && RTEST(top_imp) == RTEST(left_imp)) {
|
|
597
|
+
VALUE props = rb_hash_new();
|
|
598
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-top-width"), rb_hash_aref(top, ID2SYM(id_value)));
|
|
599
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-right-width"), rb_hash_aref(right, ID2SYM(id_value)));
|
|
600
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-bottom-width"), rb_hash_aref(bottom, ID2SYM(id_value)));
|
|
601
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-left-width"), rb_hash_aref(left, ID2SYM(id_value)));
|
|
602
|
+
|
|
603
|
+
VALUE shorthand_value = cataract_create_border_width_shorthand(Qnil, props);
|
|
604
|
+
if (!NIL_P(shorthand_value)) {
|
|
605
|
+
VALUE shorthand_data = rb_hash_new();
|
|
606
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
607
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(top, ID2SYM(id_specificity)));
|
|
608
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_important), top_imp);
|
|
609
|
+
rb_hash_aset(properties_hash, USASCII_STR("border-width"), shorthand_data);
|
|
610
|
+
|
|
611
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-top-width"));
|
|
612
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-right-width"));
|
|
613
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-bottom-width"));
|
|
614
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-left-width"));
|
|
615
|
+
DEBUG_PRINTF(" -> Recreated border-width shorthand\n");
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Try to recreate border-style shorthand (if all 4 sides present)
|
|
622
|
+
{
|
|
623
|
+
VALUE top = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-top-style"));
|
|
624
|
+
VALUE right = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-right-style"));
|
|
625
|
+
VALUE bottom = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-bottom-style"));
|
|
626
|
+
VALUE left = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-left-style"));
|
|
627
|
+
|
|
628
|
+
if (!NIL_P(top) && !NIL_P(right) && !NIL_P(bottom) && !NIL_P(left)) {
|
|
629
|
+
VALUE top_imp = rb_hash_aref(top, ID2SYM(id_important));
|
|
630
|
+
VALUE right_imp = rb_hash_aref(right, ID2SYM(id_important));
|
|
631
|
+
VALUE bottom_imp = rb_hash_aref(bottom, ID2SYM(id_important));
|
|
632
|
+
VALUE left_imp = rb_hash_aref(left, ID2SYM(id_important));
|
|
633
|
+
|
|
634
|
+
if (RTEST(top_imp) == RTEST(right_imp) && RTEST(top_imp) == RTEST(bottom_imp) && RTEST(top_imp) == RTEST(left_imp)) {
|
|
635
|
+
VALUE props = rb_hash_new();
|
|
636
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-top-style"), rb_hash_aref(top, ID2SYM(id_value)));
|
|
637
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-right-style"), rb_hash_aref(right, ID2SYM(id_value)));
|
|
638
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-bottom-style"), rb_hash_aref(bottom, ID2SYM(id_value)));
|
|
639
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-left-style"), rb_hash_aref(left, ID2SYM(id_value)));
|
|
640
|
+
|
|
641
|
+
VALUE shorthand_value = cataract_create_border_style_shorthand(Qnil, props);
|
|
642
|
+
if (!NIL_P(shorthand_value)) {
|
|
643
|
+
VALUE shorthand_data = rb_hash_new();
|
|
644
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
645
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(top, ID2SYM(id_specificity)));
|
|
646
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_important), top_imp);
|
|
647
|
+
rb_hash_aset(properties_hash, USASCII_STR("border-style"), shorthand_data);
|
|
648
|
+
|
|
649
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-top-style"));
|
|
650
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-right-style"));
|
|
651
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-bottom-style"));
|
|
652
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-left-style"));
|
|
653
|
+
DEBUG_PRINTF(" -> Recreated border-style shorthand\n");
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Try to recreate border-color shorthand (if all 4 sides present)
|
|
660
|
+
{
|
|
661
|
+
VALUE top = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-top-color"));
|
|
662
|
+
VALUE right = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-right-color"));
|
|
663
|
+
VALUE bottom = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-bottom-color"));
|
|
664
|
+
VALUE left = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-left-color"));
|
|
665
|
+
|
|
666
|
+
if (!NIL_P(top) && !NIL_P(right) && !NIL_P(bottom) && !NIL_P(left)) {
|
|
667
|
+
VALUE top_imp = rb_hash_aref(top, ID2SYM(id_important));
|
|
668
|
+
VALUE right_imp = rb_hash_aref(right, ID2SYM(id_important));
|
|
669
|
+
VALUE bottom_imp = rb_hash_aref(bottom, ID2SYM(id_important));
|
|
670
|
+
VALUE left_imp = rb_hash_aref(left, ID2SYM(id_important));
|
|
671
|
+
|
|
672
|
+
if (RTEST(top_imp) == RTEST(right_imp) && RTEST(top_imp) == RTEST(bottom_imp) && RTEST(top_imp) == RTEST(left_imp)) {
|
|
673
|
+
VALUE props = rb_hash_new();
|
|
674
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-top-color"), rb_hash_aref(top, ID2SYM(id_value)));
|
|
675
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-right-color"), rb_hash_aref(right, ID2SYM(id_value)));
|
|
676
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-bottom-color"), rb_hash_aref(bottom, ID2SYM(id_value)));
|
|
677
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-left-color"), rb_hash_aref(left, ID2SYM(id_value)));
|
|
678
|
+
|
|
679
|
+
VALUE shorthand_value = cataract_create_border_color_shorthand(Qnil, props);
|
|
680
|
+
if (!NIL_P(shorthand_value)) {
|
|
681
|
+
VALUE shorthand_data = rb_hash_new();
|
|
682
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
683
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(top, ID2SYM(id_specificity)));
|
|
684
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_important), top_imp);
|
|
685
|
+
rb_hash_aset(properties_hash, USASCII_STR("border-color"), shorthand_data);
|
|
686
|
+
|
|
687
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-top-color"));
|
|
688
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-right-color"));
|
|
689
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-bottom-color"));
|
|
690
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-left-color"));
|
|
691
|
+
DEBUG_PRINTF(" -> Recreated border-color shorthand\n");
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Try to recreate border-style shorthand (if all 4 sides present)
|
|
698
|
+
{
|
|
699
|
+
VALUE top = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-top-style"));
|
|
700
|
+
VALUE right = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-right-style"));
|
|
701
|
+
VALUE bottom = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-bottom-style"));
|
|
702
|
+
VALUE left = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-left-style"));
|
|
703
|
+
|
|
704
|
+
if (!NIL_P(top) && !NIL_P(right) && !NIL_P(bottom) && !NIL_P(left)) {
|
|
705
|
+
VALUE top_imp = rb_hash_aref(top, ID2SYM(id_important));
|
|
706
|
+
VALUE right_imp = rb_hash_aref(right, ID2SYM(id_important));
|
|
707
|
+
VALUE bottom_imp = rb_hash_aref(bottom, ID2SYM(id_important));
|
|
708
|
+
VALUE left_imp = rb_hash_aref(left, ID2SYM(id_important));
|
|
709
|
+
|
|
710
|
+
if (RTEST(top_imp) == RTEST(right_imp) && RTEST(top_imp) == RTEST(bottom_imp) && RTEST(top_imp) == RTEST(left_imp)) {
|
|
711
|
+
VALUE props = rb_hash_new();
|
|
712
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-top-style"), rb_hash_aref(top, ID2SYM(id_value)));
|
|
713
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-right-style"), rb_hash_aref(right, ID2SYM(id_value)));
|
|
714
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-bottom-style"), rb_hash_aref(bottom, ID2SYM(id_value)));
|
|
715
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-left-style"), rb_hash_aref(left, ID2SYM(id_value)));
|
|
716
|
+
|
|
717
|
+
VALUE shorthand_value = cataract_create_border_style_shorthand(Qnil, props);
|
|
718
|
+
if (!NIL_P(shorthand_value)) {
|
|
719
|
+
VALUE shorthand_data = rb_hash_new();
|
|
720
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
721
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(top, ID2SYM(id_specificity)));
|
|
722
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_important), top_imp);
|
|
723
|
+
rb_hash_aset(properties_hash, USASCII_STR("border-style"), shorthand_data);
|
|
724
|
+
|
|
725
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-top-style"));
|
|
726
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-right-style"));
|
|
727
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-bottom-style"));
|
|
728
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-left-style"));
|
|
729
|
+
DEBUG_PRINTF(" -> Recreated border-style shorthand\n");
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Try to recreate full border shorthand (if border-width, border-style, border-color present)
|
|
736
|
+
{
|
|
737
|
+
VALUE width = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-width"));
|
|
738
|
+
VALUE style = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-style"));
|
|
739
|
+
VALUE color = rb_hash_aref(properties_hash, STR_NEW_CSTR("border-color"));
|
|
740
|
+
|
|
741
|
+
// Need at least style (border shorthand requires style)
|
|
742
|
+
if (!NIL_P(style)) {
|
|
743
|
+
// Check all have same !important flag
|
|
744
|
+
VALUE style_imp = rb_hash_aref(style, ID2SYM(id_important));
|
|
745
|
+
int same_importance = 1;
|
|
746
|
+
if (!NIL_P(width)) same_importance = same_importance && (RTEST(style_imp) == RTEST(rb_hash_aref(width, ID2SYM(id_important))));
|
|
747
|
+
if (!NIL_P(color)) same_importance = same_importance && (RTEST(style_imp) == RTEST(rb_hash_aref(color, ID2SYM(id_important))));
|
|
748
|
+
|
|
749
|
+
if (same_importance) {
|
|
750
|
+
VALUE props = rb_hash_new();
|
|
751
|
+
if (!NIL_P(width)) rb_hash_aset(props, STR_NEW_CSTR("border-width"), rb_hash_aref(width, ID2SYM(id_value)));
|
|
752
|
+
rb_hash_aset(props, STR_NEW_CSTR("border-style"), rb_hash_aref(style, ID2SYM(id_value)));
|
|
753
|
+
if (!NIL_P(color)) rb_hash_aset(props, STR_NEW_CSTR("border-color"), rb_hash_aref(color, ID2SYM(id_value)));
|
|
754
|
+
|
|
755
|
+
VALUE shorthand_value = cataract_create_border_shorthand(Qnil, props);
|
|
756
|
+
if (!NIL_P(shorthand_value)) {
|
|
757
|
+
VALUE shorthand_data = rb_hash_new();
|
|
758
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
759
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(style, ID2SYM(id_specificity)));
|
|
760
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_important), style_imp);
|
|
761
|
+
rb_hash_aset(properties_hash, USASCII_STR("border"), shorthand_data);
|
|
762
|
+
|
|
763
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-width"));
|
|
764
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-style"));
|
|
765
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("border-color"));
|
|
766
|
+
DEBUG_PRINTF(" -> Recreated border shorthand\n");
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Try to recreate list-style shorthand
|
|
773
|
+
{
|
|
774
|
+
VALUE type = rb_hash_aref(properties_hash, STR_NEW_CSTR("list-style-type"));
|
|
775
|
+
VALUE position = rb_hash_aref(properties_hash, STR_NEW_CSTR("list-style-position"));
|
|
776
|
+
VALUE image = rb_hash_aref(properties_hash, STR_NEW_CSTR("list-style-image"));
|
|
777
|
+
|
|
778
|
+
// Need at least 2 properties to create shorthand
|
|
779
|
+
// Single property should stay as longhand (semantic difference)
|
|
780
|
+
int list_count = 0;
|
|
781
|
+
if (!NIL_P(type)) list_count++;
|
|
782
|
+
if (!NIL_P(position)) list_count++;
|
|
783
|
+
if (!NIL_P(image)) list_count++;
|
|
784
|
+
|
|
785
|
+
if (list_count >= 2) {
|
|
786
|
+
// Check all have same !important flag
|
|
787
|
+
VALUE first_imp = Qnil;
|
|
788
|
+
if (!NIL_P(type)) first_imp = rb_hash_aref(type, ID2SYM(id_important));
|
|
789
|
+
else if (!NIL_P(position)) first_imp = rb_hash_aref(position, ID2SYM(id_important));
|
|
790
|
+
else if (!NIL_P(image)) first_imp = rb_hash_aref(image, ID2SYM(id_important));
|
|
791
|
+
|
|
792
|
+
int same_importance = 1;
|
|
793
|
+
if (!NIL_P(type)) same_importance = same_importance && (RTEST(first_imp) == RTEST(rb_hash_aref(type, ID2SYM(id_important))));
|
|
794
|
+
if (!NIL_P(position)) same_importance = same_importance && (RTEST(first_imp) == RTEST(rb_hash_aref(position, ID2SYM(id_important))));
|
|
795
|
+
if (!NIL_P(image)) same_importance = same_importance && (RTEST(first_imp) == RTEST(rb_hash_aref(image, ID2SYM(id_important))));
|
|
796
|
+
|
|
797
|
+
if (same_importance) {
|
|
798
|
+
VALUE props = rb_hash_new();
|
|
799
|
+
if (!NIL_P(type)) rb_hash_aset(props, STR_NEW_CSTR("list-style-type"), rb_hash_aref(type, ID2SYM(id_value)));
|
|
800
|
+
if (!NIL_P(position)) rb_hash_aset(props, STR_NEW_CSTR("list-style-position"), rb_hash_aref(position, ID2SYM(id_value)));
|
|
801
|
+
if (!NIL_P(image)) rb_hash_aset(props, STR_NEW_CSTR("list-style-image"), rb_hash_aref(image, ID2SYM(id_value)));
|
|
802
|
+
|
|
803
|
+
VALUE shorthand_value = cataract_create_list_style_shorthand(Qnil, props);
|
|
804
|
+
if (!NIL_P(shorthand_value)) {
|
|
805
|
+
VALUE shorthand_data = rb_hash_new();
|
|
806
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
807
|
+
VALUE first_prop = !NIL_P(type) ? type : (!NIL_P(position) ? position : image);
|
|
808
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(first_prop, ID2SYM(id_specificity)));
|
|
809
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_important), first_imp);
|
|
810
|
+
rb_hash_aset(properties_hash, USASCII_STR("list-style"), shorthand_data);
|
|
811
|
+
|
|
812
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("list-style-type"));
|
|
813
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("list-style-position"));
|
|
814
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("list-style-image"));
|
|
815
|
+
DEBUG_PRINTF(" -> Recreated list-style shorthand\n");
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Try to recreate font shorthand (requires at least font-size and font-family)
|
|
822
|
+
{
|
|
823
|
+
VALUE size = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-size"));
|
|
824
|
+
VALUE family = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-family"));
|
|
825
|
+
|
|
826
|
+
if (!NIL_P(size) && !NIL_P(family)) {
|
|
827
|
+
VALUE style = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-style"));
|
|
828
|
+
VALUE variant = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-variant"));
|
|
829
|
+
VALUE weight = rb_hash_aref(properties_hash, STR_NEW_CSTR("font-weight"));
|
|
830
|
+
VALUE line_height = rb_hash_aref(properties_hash, STR_NEW_CSTR("line-height"));
|
|
831
|
+
|
|
832
|
+
// Check all font properties have same !important flag
|
|
833
|
+
VALUE size_imp = rb_hash_aref(size, ID2SYM(id_important));
|
|
834
|
+
VALUE family_imp = rb_hash_aref(family, ID2SYM(id_important));
|
|
835
|
+
|
|
836
|
+
int same_importance = (RTEST(size_imp) == RTEST(family_imp));
|
|
837
|
+
if (!NIL_P(style)) same_importance = same_importance && (RTEST(size_imp) == RTEST(rb_hash_aref(style, ID2SYM(id_important))));
|
|
838
|
+
if (!NIL_P(variant)) same_importance = same_importance && (RTEST(size_imp) == RTEST(rb_hash_aref(variant, ID2SYM(id_important))));
|
|
839
|
+
if (!NIL_P(weight)) same_importance = same_importance && (RTEST(size_imp) == RTEST(rb_hash_aref(weight, ID2SYM(id_important))));
|
|
840
|
+
if (!NIL_P(line_height)) same_importance = same_importance && (RTEST(size_imp) == RTEST(rb_hash_aref(line_height, ID2SYM(id_important))));
|
|
841
|
+
|
|
842
|
+
if (same_importance) {
|
|
843
|
+
VALUE props = rb_hash_new();
|
|
844
|
+
rb_hash_aset(props, STR_NEW_CSTR("font-size"), rb_hash_aref(size, ID2SYM(id_value)));
|
|
845
|
+
rb_hash_aset(props, STR_NEW_CSTR("font-family"), rb_hash_aref(family, ID2SYM(id_value)));
|
|
846
|
+
if (!NIL_P(style)) rb_hash_aset(props, STR_NEW_CSTR("font-style"), rb_hash_aref(style, ID2SYM(id_value)));
|
|
847
|
+
if (!NIL_P(variant)) rb_hash_aset(props, STR_NEW_CSTR("font-variant"), rb_hash_aref(variant, ID2SYM(id_value)));
|
|
848
|
+
if (!NIL_P(weight)) rb_hash_aset(props, STR_NEW_CSTR("font-weight"), rb_hash_aref(weight, ID2SYM(id_value)));
|
|
849
|
+
if (!NIL_P(line_height)) rb_hash_aset(props, STR_NEW_CSTR("line-height"), rb_hash_aref(line_height, ID2SYM(id_value)));
|
|
850
|
+
|
|
851
|
+
VALUE shorthand_value = cataract_create_font_shorthand(Qnil, props);
|
|
852
|
+
if (!NIL_P(shorthand_value)) {
|
|
853
|
+
VALUE shorthand_data = rb_hash_new();
|
|
854
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
855
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(size, ID2SYM(id_specificity)));
|
|
856
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_important), size_imp);
|
|
857
|
+
rb_hash_aset(properties_hash, USASCII_STR("font"), shorthand_data);
|
|
858
|
+
|
|
859
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-size"));
|
|
860
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-family"));
|
|
861
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-style"));
|
|
862
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-variant"));
|
|
863
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("font-weight"));
|
|
864
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("line-height"));
|
|
865
|
+
DEBUG_PRINTF(" -> Recreated font shorthand\n");
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Try to recreate background shorthand (if 2+ properties present)
|
|
872
|
+
{
|
|
873
|
+
VALUE color = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-color"));
|
|
874
|
+
VALUE image = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-image"));
|
|
875
|
+
VALUE repeat = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-repeat"));
|
|
876
|
+
VALUE position = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-position"));
|
|
877
|
+
VALUE attachment = rb_hash_aref(properties_hash, STR_NEW_CSTR("background-attachment"));
|
|
878
|
+
|
|
879
|
+
int bg_count = 0;
|
|
880
|
+
if (!NIL_P(color)) bg_count++;
|
|
881
|
+
if (!NIL_P(image)) bg_count++;
|
|
882
|
+
if (!NIL_P(repeat)) bg_count++;
|
|
883
|
+
if (!NIL_P(position)) bg_count++;
|
|
884
|
+
if (!NIL_P(attachment)) bg_count++;
|
|
885
|
+
|
|
886
|
+
// Need at least 2 properties to create shorthand
|
|
887
|
+
if (bg_count >= 2) {
|
|
888
|
+
// Check all have same !important flag
|
|
889
|
+
VALUE first_imp = Qnil;
|
|
890
|
+
if (!NIL_P(color)) first_imp = rb_hash_aref(color, ID2SYM(id_important));
|
|
891
|
+
else if (!NIL_P(image)) first_imp = rb_hash_aref(image, ID2SYM(id_important));
|
|
892
|
+
else if (!NIL_P(repeat)) first_imp = rb_hash_aref(repeat, ID2SYM(id_important));
|
|
893
|
+
else if (!NIL_P(position)) first_imp = rb_hash_aref(position, ID2SYM(id_important));
|
|
894
|
+
else if (!NIL_P(attachment)) first_imp = rb_hash_aref(attachment, ID2SYM(id_important));
|
|
895
|
+
|
|
896
|
+
int same_importance = 1;
|
|
897
|
+
if (!NIL_P(color)) same_importance = same_importance && (RTEST(first_imp) == RTEST(rb_hash_aref(color, ID2SYM(id_important))));
|
|
898
|
+
if (!NIL_P(image)) same_importance = same_importance && (RTEST(first_imp) == RTEST(rb_hash_aref(image, ID2SYM(id_important))));
|
|
899
|
+
if (!NIL_P(repeat)) same_importance = same_importance && (RTEST(first_imp) == RTEST(rb_hash_aref(repeat, ID2SYM(id_important))));
|
|
900
|
+
if (!NIL_P(position)) same_importance = same_importance && (RTEST(first_imp) == RTEST(rb_hash_aref(position, ID2SYM(id_important))));
|
|
901
|
+
if (!NIL_P(attachment)) same_importance = same_importance && (RTEST(first_imp) == RTEST(rb_hash_aref(attachment, ID2SYM(id_important))));
|
|
902
|
+
|
|
903
|
+
if (same_importance) {
|
|
904
|
+
VALUE props = rb_hash_new();
|
|
905
|
+
if (!NIL_P(color)) rb_hash_aset(props, STR_NEW_CSTR("background-color"), rb_hash_aref(color, ID2SYM(id_value)));
|
|
906
|
+
if (!NIL_P(image)) rb_hash_aset(props, STR_NEW_CSTR("background-image"), rb_hash_aref(image, ID2SYM(id_value)));
|
|
907
|
+
if (!NIL_P(repeat)) rb_hash_aset(props, STR_NEW_CSTR("background-repeat"), rb_hash_aref(repeat, ID2SYM(id_value)));
|
|
908
|
+
if (!NIL_P(position)) rb_hash_aset(props, STR_NEW_CSTR("background-position"), rb_hash_aref(position, ID2SYM(id_value)));
|
|
909
|
+
if (!NIL_P(attachment)) rb_hash_aset(props, STR_NEW_CSTR("background-attachment"), rb_hash_aref(attachment, ID2SYM(id_value)));
|
|
910
|
+
|
|
911
|
+
VALUE shorthand_value = cataract_create_background_shorthand(Qnil, props);
|
|
912
|
+
if (!NIL_P(shorthand_value)) {
|
|
913
|
+
VALUE shorthand_data = rb_hash_new();
|
|
914
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_value), shorthand_value);
|
|
915
|
+
VALUE first_prop = !NIL_P(color) ? color : (!NIL_P(image) ? image : (!NIL_P(repeat) ? repeat : (!NIL_P(position) ? position : attachment)));
|
|
916
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_specificity), rb_hash_aref(first_prop, ID2SYM(id_specificity)));
|
|
917
|
+
rb_hash_aset(shorthand_data, ID2SYM(id_important), first_imp);
|
|
918
|
+
rb_hash_aset(properties_hash, USASCII_STR("background"), shorthand_data);
|
|
919
|
+
|
|
920
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-color"));
|
|
921
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-image"));
|
|
922
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-repeat"));
|
|
923
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-position"));
|
|
924
|
+
rb_hash_delete(properties_hash, STR_NEW_CSTR("background-attachment"));
|
|
925
|
+
DEBUG_PRINTF(" -> Recreated background shorthand\n");
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Build declarations array from properties_hash
|
|
932
|
+
VALUE merged_decls = rb_ary_new();
|
|
933
|
+
rb_hash_foreach(properties_hash, merge_build_result_callback, merged_decls);
|
|
934
|
+
|
|
935
|
+
DEBUG_PRINTF(" [merge_rules_for_selector] Result: %ld merged declarations\n",
|
|
936
|
+
RARRAY_LEN(merged_decls));
|
|
937
|
+
|
|
938
|
+
return merged_decls;
|
|
939
|
+
}
|
|
940
|
+
|
|
336
941
|
// Merge CSS rules according to cascade rules
|
|
337
942
|
// Input: Stylesheet object or CSS string
|
|
338
943
|
// Output: Stylesheet with merged declarations
|
|
@@ -378,6 +983,78 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
378
983
|
return empty_sheet;
|
|
379
984
|
}
|
|
380
985
|
|
|
986
|
+
/*
|
|
987
|
+
* ============================================================================
|
|
988
|
+
* MERGE ALGORITHM - Rules and Implementation Notes
|
|
989
|
+
* ============================================================================
|
|
990
|
+
*
|
|
991
|
+
* CORE PRINCIPLE: Group rules by selector, merge declarations within each group
|
|
992
|
+
*
|
|
993
|
+
* Different selectors (.test vs #test) target different elements and must stay separate.
|
|
994
|
+
* Same selectors should merge into one rule to reduce output size.
|
|
995
|
+
*
|
|
996
|
+
* ALGORITHM STEPS:
|
|
997
|
+
* 1. Group rules by selector (.test, #test, etc.)
|
|
998
|
+
* 2. For each selector group:
|
|
999
|
+
* a. Expand shorthand properties (margin, background, font, etc.)
|
|
1000
|
+
* b. Apply CSS cascade rules to resolve conflicts
|
|
1001
|
+
* c. Recreate shorthand properties where beneficial
|
|
1002
|
+
* 3. Output one rule per unique selector
|
|
1003
|
+
*
|
|
1004
|
+
* CSS CASCADE RULES (in order of precedence):
|
|
1005
|
+
* 1. !important declarations always win over non-!important
|
|
1006
|
+
* 2. Higher specificity wins (#id > .class > element)
|
|
1007
|
+
* 3. Later source order wins (for same importance + specificity)
|
|
1008
|
+
*
|
|
1009
|
+
* SOURCE ORDER CALCULATION:
|
|
1010
|
+
* source_order = rule_id * 1000 + declaration_index
|
|
1011
|
+
* This ensures declarations within the same rule maintain relative order.
|
|
1012
|
+
*
|
|
1013
|
+
* SHORTHAND EXPANSION:
|
|
1014
|
+
* When merging, all shorthands must be expanded to longhands first.
|
|
1015
|
+
* Example: "background: blue" expands to:
|
|
1016
|
+
* - background-color: blue
|
|
1017
|
+
* - background-image: none
|
|
1018
|
+
* - background-repeat: repeat
|
|
1019
|
+
* - background-position: 0% 0%
|
|
1020
|
+
* - background-attachment: scroll
|
|
1021
|
+
*
|
|
1022
|
+
* This is REQUIRED because partial overrides must work correctly:
|
|
1023
|
+
* .test { background: blue; }
|
|
1024
|
+
* .test { background-image: url(x.png); }
|
|
1025
|
+
* Should result in: blue background with image (not image reset to none)
|
|
1026
|
+
*
|
|
1027
|
+
* SHORTHAND RECREATION:
|
|
1028
|
+
* After cascade resolution, recreate shorthands for smaller output:
|
|
1029
|
+
* - margin-top: 10px, margin-right: 10px, ... → margin: 10px
|
|
1030
|
+
* - background-color: blue, background-image: none, ... → background: blue
|
|
1031
|
+
*
|
|
1032
|
+
* Optimization: Omit default values ONLY when all properties are present
|
|
1033
|
+
* (indicating they came from shorthand expansion, not explicit longhands)
|
|
1034
|
+
*
|
|
1035
|
+
* If only some properties present (explicit longhands), include all values:
|
|
1036
|
+
* background-color: black, background-image: none → "black none"
|
|
1037
|
+
* Not: "black" (user explicitly set image to none)
|
|
1038
|
+
*
|
|
1039
|
+
* If all properties present (from expansion), omit defaults:
|
|
1040
|
+
* background-color: blue, background-image: none, repeat: repeat, ... → "blue"
|
|
1041
|
+
* (The "none", "repeat", etc. are just defaults from expansion)
|
|
1042
|
+
*
|
|
1043
|
+
* EDGE CASES:
|
|
1044
|
+
* - Empty rules (no declarations): Skip during merge
|
|
1045
|
+
* - Nested CSS: Parent rules with children are containers only, skip their declarations
|
|
1046
|
+
* - Mixed !important: Properties with different importance cannot merge into shorthand
|
|
1047
|
+
* - Single property: Don't create shorthand (e.g., background-color alone stays as-is)
|
|
1048
|
+
* Reason: "background: blue" resets all other background properties to defaults,
|
|
1049
|
+
* which is semantically different from just setting background-color.
|
|
1050
|
+
*
|
|
1051
|
+
* PERFORMANCE NOTES:
|
|
1052
|
+
* - Use cached static strings (VALUE) for property names (no allocation)
|
|
1053
|
+
* - Group by selector in single pass (O(n) hash building)
|
|
1054
|
+
* - Merge within groups (O(n*m) where m is avg declarations per rule)
|
|
1055
|
+
* ============================================================================
|
|
1056
|
+
*/
|
|
1057
|
+
|
|
381
1058
|
// For nested CSS: identify parent rules (rules that have children)
|
|
382
1059
|
// These should be skipped during merge, even if they have declarations
|
|
383
1060
|
// Use Ruby hash as a set: parent_id => true
|
|
@@ -400,69 +1077,61 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
400
1077
|
}
|
|
401
1078
|
}
|
|
402
1079
|
|
|
403
|
-
//
|
|
404
|
-
//
|
|
1080
|
+
// ALWAYS build selector groups - this is the core of merge logic
|
|
1081
|
+
// Group rules by selector: different selectors stay separate
|
|
405
1082
|
// selector => [rule indices]
|
|
406
|
-
|
|
407
|
-
VALUE
|
|
408
|
-
|
|
409
|
-
if (has_nesting) {
|
|
410
|
-
DEBUG_PRINTF("\n=== Building selector groups ===\n");
|
|
411
|
-
selector_groups = rb_hash_new();
|
|
412
|
-
for (long i = 0; i < num_rules; i++) {
|
|
413
|
-
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
414
|
-
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
415
|
-
VALUE parent_rule_id = rb_struct_aref(rule, INT2FIX(RULE_PARENT_RULE_ID));
|
|
416
|
-
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
1083
|
+
DEBUG_PRINTF("\n=== Building selector groups (has_nesting=%d) ===\n", has_nesting);
|
|
1084
|
+
VALUE selector_groups = rb_hash_new();
|
|
417
1085
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
1086
|
+
for (long i = 0; i < num_rules; i++) {
|
|
1087
|
+
VALUE rule = RARRAY_AREF(rules_array, i);
|
|
1088
|
+
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
1089
|
+
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
421
1090
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
1091
|
+
// Skip empty rules (no declarations)
|
|
1092
|
+
// This handles both empty containers and rules with no properties
|
|
1093
|
+
if (RARRAY_LEN(declarations) == 0) {
|
|
1094
|
+
DEBUG_PRINTF(" [Rule %ld] SKIP: selector='%s' (empty declarations)\n",
|
|
1095
|
+
i, RSTRING_PTR(selector));
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
428
1098
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
1099
|
+
// Note: We do NOT skip parent rules that have children!
|
|
1100
|
+
// Per CSS spec, parent can have its own declarations AND nested rules.
|
|
1101
|
+
// Example: .parent { color: red; .child { color: blue; } }
|
|
1102
|
+
// Should output both .parent (color: red) and .parent .child (color: blue)
|
|
1103
|
+
// The nesting is already flattened during parsing, so they have different selectors.
|
|
432
1104
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
common_parent = parent_rule_id;
|
|
436
|
-
DEBUG_PRINTF(" Setting common_parent=%s\n",
|
|
437
|
-
NIL_P(common_parent) ? "nil" : RSTRING_PTR(rb_inspect(common_parent)));
|
|
438
|
-
}
|
|
1105
|
+
DEBUG_PRINTF(" [Rule %ld] ADD: selector='%s', %ld declarations\n",
|
|
1106
|
+
i, RSTRING_PTR(selector), RARRAY_LEN(declarations));
|
|
439
1107
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
rb_ary_push(group, LONG2FIX(i));
|
|
1108
|
+
VALUE group = rb_hash_aref(selector_groups, selector);
|
|
1109
|
+
if (NIL_P(group)) {
|
|
1110
|
+
group = rb_ary_new();
|
|
1111
|
+
rb_hash_aset(selector_groups, selector, group);
|
|
1112
|
+
DEBUG_PRINTF(" -> Created new selector group for '%s'\n", RSTRING_PTR(selector));
|
|
447
1113
|
}
|
|
448
|
-
|
|
1114
|
+
rb_ary_push(group, LONG2FIX(i));
|
|
449
1115
|
}
|
|
1116
|
+
DEBUG_PRINTF("=== Total selector groups: %ld ===\n\n", RHASH_SIZE(selector_groups));
|
|
450
1117
|
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
// Example: .
|
|
454
|
-
// Should return
|
|
455
|
-
DEBUG_PRINTF("
|
|
456
|
-
DEBUG_PRINTF("
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
DEBUG_PRINTF("
|
|
1118
|
+
// ALWAYS group by selector and keep them separate
|
|
1119
|
+
// Different selectors target different elements and must remain distinct
|
|
1120
|
+
// Example: .test { color: red; } #test { color: blue; }
|
|
1121
|
+
// Should return 2 rules (not merged into one)
|
|
1122
|
+
DEBUG_PRINTF("=== DECISION POINT ===\n");
|
|
1123
|
+
DEBUG_PRINTF(" selector_groups size: %ld\n", RHASH_SIZE(selector_groups));
|
|
1124
|
+
|
|
1125
|
+
if (RHASH_SIZE(selector_groups) == 0) {
|
|
1126
|
+
DEBUG_PRINTF(" -> No rules to merge (all were empty or skipped)\n");
|
|
1127
|
+
// Return empty stylesheet
|
|
1128
|
+
VALUE empty_sheet = rb_class_new_instance(0, NULL, cStylesheet);
|
|
1129
|
+
return empty_sheet;
|
|
460
1130
|
}
|
|
461
|
-
DEBUG_PRINTF(" Condition: has_nesting && !NIL_P(selector_groups) && RHASH_SIZE(selector_groups) > 1 = %d\n",
|
|
462
|
-
has_nesting && !NIL_P(selector_groups) && RHASH_SIZE(selector_groups) > 1);
|
|
463
1131
|
|
|
464
|
-
if (
|
|
465
|
-
DEBUG_PRINTF(" -> Taking
|
|
1132
|
+
if (RHASH_SIZE(selector_groups) > 0) {
|
|
1133
|
+
DEBUG_PRINTF(" -> Taking SELECTOR-GROUPED path (%ld unique selectors)\n",
|
|
1134
|
+
RHASH_SIZE(selector_groups));
|
|
466
1135
|
VALUE merged_sheet = rb_class_new_instance(0, NULL, cStylesheet);
|
|
467
1136
|
VALUE merged_rules = rb_ary_new();
|
|
468
1137
|
int rule_id_counter = 0;
|
|
@@ -470,22 +1139,23 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
470
1139
|
// Iterate through each selector group
|
|
471
1140
|
VALUE selectors = rb_funcall(selector_groups, rb_intern("keys"), 0);
|
|
472
1141
|
long num_selectors = RARRAY_LEN(selectors);
|
|
1142
|
+
DEBUG_PRINTF("\n=== Processing %ld selector groups ===\n", num_selectors);
|
|
473
1143
|
|
|
474
1144
|
for (long s = 0; s < num_selectors; s++) {
|
|
475
1145
|
VALUE selector = rb_ary_entry(selectors, s);
|
|
476
1146
|
VALUE group_indices = rb_hash_aref(selector_groups, selector);
|
|
477
1147
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
long first_idx = FIX2LONG(rb_ary_entry(group_indices, 0));
|
|
481
|
-
VALUE orig_rule = RARRAY_AREF(rules_array, first_idx);
|
|
482
|
-
VALUE orig_decls = rb_struct_aref(orig_rule, INT2FIX(RULE_DECLARATIONS));
|
|
1148
|
+
DEBUG_PRINTF("\n[Selector %ld/%ld] '%s' - %ld rules in group\n",
|
|
1149
|
+
s + 1, num_selectors, RSTRING_PTR(selector), RARRAY_LEN(group_indices));
|
|
483
1150
|
|
|
484
|
-
//
|
|
1151
|
+
// Merge all rules in this selector group
|
|
1152
|
+
VALUE merged_decls = merge_rules_for_selector(rules_array, group_indices, selector);
|
|
1153
|
+
|
|
1154
|
+
// Create new rule with this selector and merged declarations
|
|
485
1155
|
VALUE new_rule = rb_struct_new(cRule,
|
|
486
1156
|
INT2FIX(rule_id_counter++),
|
|
487
1157
|
selector,
|
|
488
|
-
|
|
1158
|
+
merged_decls,
|
|
489
1159
|
Qnil, // specificity
|
|
490
1160
|
Qnil, // parent_rule_id
|
|
491
1161
|
Qnil // nesting_style
|
|
@@ -493,6 +1163,8 @@ VALUE cataract_merge_new(VALUE self, VALUE input) {
|
|
|
493
1163
|
rb_ary_push(merged_rules, new_rule);
|
|
494
1164
|
}
|
|
495
1165
|
|
|
1166
|
+
DEBUG_PRINTF("\n=== Created %d output rules ===\n", rule_id_counter);
|
|
1167
|
+
|
|
496
1168
|
rb_ivar_set(merged_sheet, id_ivar_rules, merged_rules);
|
|
497
1169
|
|
|
498
1170
|
// Set @media_index with :all pointing to all rule IDs
|