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
BORDER_STYLE_PROPERTIES =
['border-width', 'border-style', 'border-color'].freeze
BORDER_PROPERTIES =
['border', 'border-left', 'border-right', 'border-top', 'border-bottom'].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



252
253
254
255
256
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
# File 'lib/css_parser/rule_set.rb', line 252

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



236
237
238
# File 'lib/css_parser/rule_set.rb', line 236

def filename
  @filename
end

#offsetObject (readonly)

optional field for storing source reference File offset range



234
235
236
# File 'lib/css_parser/rule_set.rb', line 234

def offset
  @offset
end

#selectorsObject (readonly)

Array of selector strings.



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

def selectors
  @selectors
end

#specificityObject

Integer with the specificity to use for this RuleSet.



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

def specificity
  @specificity
end

Instance Method Details

#add_declaration!Object Also known as: []=



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

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.



539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/css_parser/rule_set.rb', line 539

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



556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# File 'lib/css_parser/rule_set.rb', line 556

def create_border_shorthand! # :nodoc:
  values = BORDER_STYLE_PROPERTIES.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 declaration.value.gsub(/,\s/, ',').strip =~ /\s/

    declaration.value
  end.compact

  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.



578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'lib/css_parser/rule_set.rb', line 578

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

  DIMENSIONS.each do |property, dimensions|
    values = [:top, :right, :bottom, :left].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.



602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
# File 'lib/css_parser/rule_set.rb', line 602

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

  new_value = String.new
  ['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.



629
630
631
# File 'lib/css_parser/rule_set.rb', line 629

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.



505
506
507
508
509
510
511
512
# File 'lib/css_parser/rule_set.rb', line 505

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



515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/css_parser/rule_set.rb', line 515

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.



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

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

#deleteObject Also known as: remove_declaration!



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

def_delegators :declarations, :add_declaration!, :delete

#each_declarationObject

Iterate through declarations.



315
316
317
318
319
# File 'lib/css_parser/rule_set.rb', line 315

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


305
306
307
308
309
310
311
312
# File 'lib/css_parser/rule_set.rb', line 305

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



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/css_parser/rule_set.rb', line 345

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!



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/css_parser/rule_set.rb', line 375

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.



393
394
395
396
397
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
# File 'lib/css_parser/rule_set.rb', line 393

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.



430
431
432
433
434
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
# File 'lib/css_parser/rule_set.rb', line 430

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 m =~ /normal|inherit/i
      ['font-style', 'font-weight', 'font-variant'].each do |font_prop|
        font_props[font_prop] ||= m
      end
    elsif m =~ /italic|oblique/i
      font_props['font-style'] = m
    elsif m =~ /small-caps/i
      font_props['font-variant'] = m
    elsif m =~ /[1-9]00$|bold|bolder|lighter/i
      font_props['font-weight'] = m
    elsif m =~ CssParser::FONT_UNITS_RX
      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



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/css_parser/rule_set.rb', line 485

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

  value = declaration.value.dup

  replacement =
    if value =~ CssParser::RE_INHERIT
      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.



332
333
334
335
336
337
338
339
# File 'lib/css_parser/rule_set.rb', line 332

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



367
368
369
370
371
# File 'lib/css_parser/rule_set.rb', line 367

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



289
290
291
292
293
# File 'lib/css_parser/rule_set.rb', line 289

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

  "#{value};"
end

#to_sObject

Return the CSS rule set as a string.



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

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