Class: CssParser::RuleSet

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/css_parser/rule_set.rb

Defined Under Namespace

Classes: Declarations

Constant Summary collapse

RE_ELEMENTS_AND_PSEUDO_ELEMENTS =

Patterns for specificity calculations

/((^|[\s+>]+)\w+|:(first-line|first-letter|before|after))/i.freeze
RE_NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES =
/(\.\w+)|(\[\w+)|(:(link|first-child|lang))/i.freeze
BACKGROUND_PROPERTIES =
['background-color', 'background-image', 'background-repeat', 'background-position', 'background-size', 'background-attachment'].freeze
LIST_STYLE_PROPERTIES =
['list-style-type', 'list-style-position', 'list-style-image'].freeze
FONT_STYLE_PROPERTIES =
['font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family'].freeze
FONT_WEIGHT_PROPERTIES =
['font-style', 'font-weight', 'font-variant'].freeze
BORDER_STYLE_PROPERTIES =
['border-width', 'border-style', 'border-color'].freeze
BORDER_PROPERTIES =
['border', 'border-left', 'border-right', 'border-top', 'border-bottom'].freeze
DIMENSION_DIRECTIONS =
[:top, :right, :bottom, :left].freeze
NUMBER_OF_DIMENSIONS =
4
DIMENSIONS =
[
  ['margin', %w[margin-top margin-right margin-bottom margin-left]],
  ['padding', %w[padding-top padding-right padding-bottom padding-left]],
  ['border-color', %w[border-top-color border-right-color border-bottom-color border-left-color]],
  ['border-style', %w[border-top-style border-right-style border-bottom-style border-left-style]],
  ['border-width', %w[border-top-width border-right-width border-bottom-width border-left-width]]
].freeze
WHITESPACE_REPLACEMENT =
'___SPACE___'
COLON =

Tokens for parse_declarations!

':'.freeze
SEMICOLON =
';'.freeze
LPAREN =
'('.freeze
RPAREN =
')'.freeze
IMPORTANT =
'!important'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args, selectors: nil, block: nil, offset: nil, filename: nil, specificity: nil) ⇒ RuleSet

rubocop:disable Metrics/ParameterLists



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/css_parser/rule_set.rb', line 257

def initialize(*args, selectors: nil, block: nil, offset: nil, filename: nil, specificity: nil) # rubocop:disable Metrics/ParameterLists
  if args.any?
    if selectors || block || offset || filename || specificity
      raise ArgumentError, "don't mix positional and keyword arguments"
    end

    warn '[DEPRECATION] positional arguments are deprecated use keyword instead.', uplevel: 1

    case args.length
    when 2
      selectors, block = args
    when 3
      selectors, block, specificity = args
    when 4
      filename, offset, selectors, block = args
    when 5
      filename, offset, selectors, block, specificity = args
    else
      raise ArgumentError
    end
  end

  @selectors = []
  @specificity = specificity

  unless offset.nil? == filename.nil?
    raise ArgumentError, 'require both offset and filename or no offset and no filename'
  end

  @offset = offset
  @filename = filename

  parse_selectors!(selectors) if selectors
  parse_declarations!(block)
end

Instance Attribute Details

#filenameObject

the local or remote location



241
242
243
# File 'lib/css_parser/rule_set.rb', line 241

def filename
  @filename
end

#offsetObject (readonly)

optional field for storing source reference File offset range



239
240
241
# File 'lib/css_parser/rule_set.rb', line 239

def offset
  @offset
end

#selectorsObject (readonly)

Array of selector strings.



244
245
246
# File 'lib/css_parser/rule_set.rb', line 244

def selectors
  @selectors
end

#specificityObject

Integer with the specificity to use for this RuleSet.



247
248
249
# File 'lib/css_parser/rule_set.rb', line 247

def specificity
  @specificity
end

Instance Method Details

#add_declaration!Object Also known as: []=



253
# File 'lib/css_parser/rule_set.rb', line 253

def_delegators :declarations, :add_declaration!, :delete

#create_background_shorthand!Object

Looks for long format CSS background properties (e.g. background-color) and converts them into a shorthand CSS background property.

Leaves properties declared !important alone.



544
545
546
547
548
549
550
551
552
553
554
555
# File 'lib/css_parser/rule_set.rb', line 544

def create_background_shorthand! # :nodoc:
  # When we have a background-size property we must separate it and distinguish it from
  # background-position by preceding it with a backslash. In this case we also need to
  # have a background-position property, so we set it if it's missing.
  # http://www.w3schools.com/cssref/css3_pr_background.asp
  if (declaration = declarations['background-size']) && !declaration.important
    declarations['background-position'] ||= '0% 0%'
    declaration.value = "/ #{declaration.value}"
  end

  create_shorthand_properties! BACKGROUND_PROPERTIES, 'background'
end

#create_border_shorthand!Object

Combine border-color, border-style and border-width into border Should be run after create_dimensions_shorthand!

TODO: this is extremely similar to create_background_shorthand! and should be combined



561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'lib/css_parser/rule_set.rb', line 561

def create_border_shorthand! # :nodoc:
  values = BORDER_STYLE_PROPERTIES.filter_map do |property|
    next unless (declaration = declarations[property])
    next if declaration.important
    # can't merge if any value contains a space (i.e. has multiple values)
    # we temporarily remove any spaces after commas for the check (inside rgba, etc...)
    next if /\s/.match?(declaration.value.gsub(/,\s/, ',').strip)

    declaration.value
  end

  return if values.size != BORDER_STYLE_PROPERTIES.size

  BORDER_STYLE_PROPERTIES.each do |property|
    declarations.delete(property)
  end

  declarations['border'] = values.join(' ')
end

#create_dimensions_shorthand!Object

Looks for long format CSS dimensional properties (margin, padding, border-color, border-style and border-width) and converts them into shorthand CSS properties.



583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/css_parser/rule_set.rb', line 583

def create_dimensions_shorthand! # :nodoc:
  return if declarations.size < NUMBER_OF_DIMENSIONS

  DIMENSIONS.each do |property, dimensions|
    values = DIMENSION_DIRECTIONS.each_with_index.with_object({}) do |(side, index), result|
      next unless (declaration = declarations[dimensions[index]])

      result[side] = declaration.value
    end

    # All four dimensions must be present
    next if values.size != dimensions.size

    new_value = values.values_at(*compute_dimensions_shorthand(values)).join(' ').strip
    declarations[property] = new_value unless new_value.empty?

    # Delete the longhand values
    dimensions.each { |d| declarations.delete(d) }
  end
end

#create_font_shorthand!Object

Looks for long format CSS font properties (e.g. font-weight) and tries to convert them into a shorthand CSS font property. All font properties must be present in order to create a shorthand declaration.



607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'lib/css_parser/rule_set.rb', line 607

def create_font_shorthand! # :nodoc:
  return unless FONT_STYLE_PROPERTIES.all? { |prop| declarations.key?(prop) }

  new_value = +''
  ['font-style', 'font-variant', 'font-weight'].each do |property|
    unless declarations[property].value == 'normal'
      new_value << declarations[property].value << ' '
    end
  end

  new_value << declarations['font-size'].value

  unless declarations['line-height'].value == 'normal'
    new_value << '/' << declarations['line-height'].value
  end

  new_value << ' ' << declarations['font-family'].value

  declarations['font'] = new_value.gsub(/\s+/, ' ')

  FONT_STYLE_PROPERTIES.each { |prop| declarations.delete(prop) }
end

#create_list_style_shorthand!Object

Looks for long format CSS list-style properties (e.g. list-style-type) and converts them into a shorthand CSS list-style property.

Leaves properties declared !important alone.



634
635
636
# File 'lib/css_parser/rule_set.rb', line 634

def create_list_style_shorthand! # :nodoc:
  create_shorthand_properties! LIST_STYLE_PROPERTIES, 'list-style'
end

#create_shorthand!Object

Create shorthand declarations (e.g. margin or font) whenever possible.



510
511
512
513
514
515
516
517
# File 'lib/css_parser/rule_set.rb', line 510

def create_shorthand!
  create_background_shorthand!
  create_dimensions_shorthand!
  # border must be shortened after dimensions
  create_border_shorthand!
  create_font_shorthand!
  create_list_style_shorthand!
end

#create_shorthand_properties!(properties, shorthand_property) ⇒ Object

Combine several properties into a shorthand one



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/css_parser/rule_set.rb', line 520

def create_shorthand_properties!(properties, shorthand_property) # :nodoc:
  values = []
  properties_to_delete = []
  properties.each do |property|
    next unless (declaration = declarations[property])
    next if declaration.important

    values << declaration.value
    properties_to_delete << property
  end

  return if values.length <= 1

  properties_to_delete.each do |property|
    declarations.delete(property)
  end

  declarations[shorthand_property] = values.join(' ')
end

#declarations_to_s(options = {}) ⇒ Object

Return all declarations as a string.



327
328
329
# File 'lib/css_parser/rule_set.rb', line 327

def declarations_to_s(options = {})
  declarations.to_s(options)
end

#deleteObject Also known as: remove_declaration!



253
# File 'lib/css_parser/rule_set.rb', line 253

def_delegators :declarations, :add_declaration!, :delete

#each_declarationObject

Iterate through declarations.



320
321
322
323
324
# File 'lib/css_parser/rule_set.rb', line 320

def each_declaration # :yields: property, value, is_important
  declarations.each do |property_name, value|
    yield property_name, value.value, value.important
  end
end

#each_selector(options = {}) ⇒ Object

Iterate through selectors.

Options

  • force_important – boolean

Example

ruleset.each_selector do |sel, dec, spec|
  ...
end


310
311
312
313
314
315
316
317
# File 'lib/css_parser/rule_set.rb', line 310

def each_selector(options = {}) # :yields: selector, declarations, specificity
  decs = declarations.to_s(options)
  if @specificity
    @selectors.each { |sel| yield sel.strip, decs, @specificity }
  else
    @selectors.each { |sel| yield sel.strip, decs, CssParser.calculate_specificity(sel) }
  end
end

#expand_background_shorthand!Object

Convert shorthand background declarations (e.g. background: url("chess.png") gray 50% repeat fixed;) into their constituent parts.

See www.w3.org/TR/CSS21/colors.html#propdef-background



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/css_parser/rule_set.rb', line 350

def expand_background_shorthand! # :nodoc:
  return unless (declaration = declarations['background'])

  value = declaration.value.dup

  replacement =
    if value.match(CssParser::RE_INHERIT)
      BACKGROUND_PROPERTIES.to_h { |key| [key, 'inherit'] }
    else
      {
        'background-image' => value.slice!(CssParser::RE_IMAGE),
        'background-attachment' => value.slice!(CssParser::RE_SCROLL_FIXED),
        'background-repeat' => value.slice!(CssParser::RE_REPEAT),
        'background-color' => value.slice!(CssParser::RE_COLOUR),
        'background-size' => extract_background_size_from(value),
        'background-position' => value.slice!(CssParser::RE_BACKGROUND_POSITION)
      }
    end

  declarations.replace_declaration!('background', replacement, preserve_importance: true)
end

#expand_border_shorthand!Object

Split shorthand border declarations (e.g. border: 1px red;) Additional splitting happens in expand_dimensions_shorthand!



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/css_parser/rule_set.rb', line 380

def expand_border_shorthand! # :nodoc:
  BORDER_PROPERTIES.each do |k|
    next unless (declaration = declarations[k])

    value = declaration.value.dup

    replacement = {
      "#{k}-width" => value.slice!(CssParser::RE_BORDER_UNITS),
      "#{k}-color" => value.slice!(CssParser::RE_COLOUR),
      "#{k}-style" => value.slice!(CssParser::RE_BORDER_STYLE)
    }

    declarations.replace_declaration!(k, replacement, preserve_importance: true)
  end
end

#expand_dimensions_shorthand!Object

Split shorthand dimensional declarations (e.g. margin: 0px auto;) into their constituent parts. Handles margin, padding, border-color, border-style and border-width.



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/css_parser/rule_set.rb', line 398

def expand_dimensions_shorthand! # :nodoc:
  DIMENSIONS.each do |property, (top, right, bottom, left)|
    next unless (declaration = declarations[property])

    value = declaration.value.dup

    # RGB and HSL values in borders are the only units that can have spaces (within params).
    # We cheat a bit here by stripping spaces after commas in RGB and HSL values so that we
    # can split easily on spaces.
    #
    # TODO: rgba, hsl, hsla
    value.gsub!(RE_COLOUR) { |c| c.gsub(/(\s*,\s*)/, ',') }

    matches = split_value_preserving_function_whitespace(value)

    case matches.length
    when 1
      values = matches.to_a * 4
    when 2
      values = matches.to_a * 2
    when 3
      values = matches.to_a
      values << matches[1] # left = right
    when 4
      values = matches.to_a
    else
      raise ArgumentError, "Cannot parse #{value}"
    end

    replacement = [top, right, bottom, left].zip(values).to_h

    declarations.replace_declaration!(property, replacement, preserve_importance: true)
  end
end

#expand_font_shorthand!Object

Convert shorthand font declarations (e.g. font: 300 italic 11px/14px verdana, helvetica, sans-serif;) into their constituent parts.



435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/css_parser/rule_set.rb', line 435

def expand_font_shorthand! # :nodoc:
  return unless (declaration = declarations['font'])

  # reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
  font_props = {
    'font-style' => 'normal',
    'font-variant' => 'normal',
    'font-weight' => 'normal',
    'font-size' => 'normal',
    'line-height' => 'normal'
  }

  value = declaration.value.dup
  value.gsub!(%r{/\s+}, '/') # handle spaces between font size and height shorthand (e.g. 14px/ 16px)

  in_fonts = false

  matches = value.scan(/"(?:.*[^"])"|'(?:.*[^'])'|(?:\w[^ ,]+)/)
  matches.each do |m|
    m.strip!
    m.gsub!(/;$/, '')

    if in_fonts
      if font_props.key?('font-family')
        font_props['font-family'] += ", #{m}"
      else
        font_props['font-family'] = m
      end
    elsif /normal|inherit/i.match?(m)
      FONT_WEIGHT_PROPERTIES.each do |font_prop|
        font_props[font_prop] ||= m
      end
    elsif /italic|oblique/i.match?(m)
      font_props['font-style'] = m
    elsif /small-caps/i.match?(m)
      font_props['font-variant'] = m
    elsif /[1-9]00$|bold|bolder|lighter/i.match?(m)
      font_props['font-weight'] = m
    elsif CssParser::FONT_UNITS_RX.match?(m)
      if m.include?('/')
        font_props['font-size'], font_props['line-height'] = m.split('/', 2)
      else
        font_props['font-size'] = m
      end
      in_fonts = true
    end
  end

  declarations.replace_declaration!('font', font_props, preserve_importance: true)
end

#expand_list_style_shorthand!Object

Convert shorthand list-style declarations (e.g. list-style: lower-alpha outside;) into their constituent parts.

See www.w3.org/TR/CSS21/generate.html#lists



490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/css_parser/rule_set.rb', line 490

def expand_list_style_shorthand! # :nodoc:
  return unless (declaration = declarations['list-style'])

  value = declaration.value.dup

  replacement =
    if CssParser::RE_INHERIT.match?(value)
      LIST_STYLE_PROPERTIES.to_h { |key| [key, 'inherit'] }
    else
      {
        'list-style-type' => value.slice!(CssParser::RE_LIST_STYLE_TYPE),
        'list-style-position' => value.slice!(CssParser::RE_INSIDE_OUTSIDE),
        'list-style-image' => value.slice!(CssParser::URI_RX_OR_NONE)
      }
    end

  declarations.replace_declaration!('list-style', replacement, preserve_importance: true)
end

#expand_shorthand!Object

Split shorthand declarations (e.g. margin or font) into their constituent parts.



337
338
339
340
341
342
343
344
# File 'lib/css_parser/rule_set.rb', line 337

def expand_shorthand!
  # border must be expanded before dimensions
  expand_border_shorthand!
  expand_dimensions_shorthand!
  expand_font_shorthand!
  expand_background_shorthand!
  expand_list_style_shorthand!
end

#extract_background_size_from(value) ⇒ Object



372
373
374
375
376
# File 'lib/css_parser/rule_set.rb', line 372

def extract_background_size_from(value)
  size = value.slice!(CssParser::RE_BACKGROUND_SIZE)

  size.sub(%r{^\s*/\s*}, '') if size
end

#get_value(property) ⇒ Object Also known as: []

Get the value of a property



294
295
296
297
298
# File 'lib/css_parser/rule_set.rb', line 294

def get_value(property)
  return '' unless (value = declarations[property])

  "#{value};"
end

#to_sObject

Return the CSS rule set as a string.



332
333
334
# File 'lib/css_parser/rule_set.rb', line 332

def to_s
  "#{@selectors.join(',')} { #{declarations} }"
end