Skip to content

Commit

Permalink
Memory and Performance Optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
Anmol Chopra committed Jan 1, 2018
1 parent 5764774 commit dab9a92
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 61 deletions.
10 changes: 6 additions & 4 deletions lib/css_parser.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
require 'addressable/uri'
require 'uri'
require 'net/https'
Expand Down Expand Up @@ -122,10 +123,10 @@ def self.merge(*rule_sets)
def self.calculate_specificity(selector)
a = 0
b = selector.scan(/\#/).length
c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX).length
d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX).length
c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC).length
d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC).length

(a.to_s + b.to_s + c.to_s + d.to_s).to_i
"#{a}#{b}#{c}#{d}".to_i
rescue
return 0
end
Expand Down Expand Up @@ -160,7 +161,8 @@ def self.convert_uris(css, base_uri)
end

def self.sanitize_media_query(raw)
mq = raw.to_s.gsub(/[\s]+/, ' ').strip
mq = raw.to_s.gsub(/[\s]+/, ' ')
mq.strip!
mq = 'all' if mq.empty?
mq.to_sym
end
Expand Down
65 changes: 34 additions & 31 deletions lib/css_parser/parser.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
module CssParser
# Exception class used for any errors encountered while downloading remote files.
class RemoteFileError < IOError; end
Expand Down Expand Up @@ -184,7 +185,8 @@ def add_rule_with_offsets!(selectors, declarations, filename, offset, media_type
def add_rule_set!(ruleset, media_types = :all)
raise ArgumentError unless ruleset.kind_of?(CssParser::RuleSet)

media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
media_types = [media_types] unless Array === media_types
media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt)}

@rules << {:media_types => media_types, :rules => ruleset}
end
Expand Down Expand Up @@ -253,7 +255,7 @@ def each_selector(all_media_types = :all, options = {}) # :yields: selectors, de

# Output all CSS rules as a single stylesheet.
def to_s(which_media = :all)
out = ''
out = String.new
styles_by_media_types = {}
each_selector(which_media) do |selectors, declarations, specificity, media_types|
media_types.each do |media_type|
Expand All @@ -264,17 +266,17 @@ def to_s(which_media = :all)

styles_by_media_types.each_pair do |media_type, media_styles|
media_block = (media_type != :all)
out += "@media #{media_type} {\n" if media_block
out << "@media #{media_type} {\n" if media_block

media_styles.each do |media_style|
if media_block
out += " #{media_style[0]} {\n #{media_style[1]}\n }\n"
out << " #{media_style[0]} {\n #{media_style[1]}\n }\n"
else
out += "#{media_style[0]} {\n#{media_style[1]}\n}\n"
out << "#{media_style[0]} {\n#{media_style[1]}\n}\n"
end
end

out += "}\n" if media_block
out << "}\n" if media_block
end

out
Expand Down Expand Up @@ -316,53 +318,52 @@ def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
in_at_media_rule = false
in_media_block = false

current_selectors = ''
current_media_query = ''
current_declarations = ''
current_selectors = String.new
current_media_query = String.new
current_declarations = String.new

# once we are in a rule, we will use this to store where we started if we are capturing offsets
rule_start = nil
offset = nil

block.scan(/(([\\]{2,})|([\\]?[{}\s"])|(.[^\s"{}\\]*))/) do |matches|
token = matches[0]

block.scan(/\s+|[\\]{2,}|[\\]?[{}\s"]|.[^\s"{}\\]*/) do |token|
# save the regex offset so that we know where in the file we are
offset = Regexp.last_match.offset(0) if options[:capture_offsets]

if token =~ /\A"/ # found un-escaped double quote
if token.start_with?('"') # found un-escaped double quote
in_string = !in_string
end

if in_declarations > 0
# too deep, malformed declaration block
if in_declarations > 1
in_declarations -= 1 if token =~ /\}/
in_declarations -= 1 if token.include?('}')
next
end

if token =~ /\{/ and not in_string
if !in_string && token.include?('{')
in_declarations += 1
next
end

current_declarations += token
current_declarations << token

if token =~ /\}/ and not in_string
if !in_string && token.include?('}')
current_declarations.gsub!(/\}[\s]*$/, '')

in_declarations -= 1
current_declarations.strip!

unless current_declarations.strip.empty?
unless current_declarations.empty?
if options[:capture_offsets]
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
else
add_rule!(current_selectors, current_declarations, current_media_queries)
end
end

current_selectors = ''
current_declarations = ''
current_selectors = String.new
current_declarations = String.new

# restart our search for selectors and declarations
rule_start = nil if options[:capture_offsets]
Expand All @@ -372,26 +373,28 @@ def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
in_at_media_rule = true
current_media_queries = []
elsif in_at_media_rule
if token =~ /\{/
if token.include?('{')
block_depth = block_depth + 1
in_at_media_rule = false
in_media_block = true
current_media_queries << CssParser.sanitize_media_query(current_media_query)
current_media_query = ''
elsif token =~ /[,]/
current_media_query = String.new
elsif token.include?(',')
# new media query begins
token.gsub!(/[,]/, ' ')
current_media_query += token.strip + ' '
token.tr!(',', ' ')
token.strip!
current_media_query << token << ' '
current_media_queries << CssParser.sanitize_media_query(current_media_query)
current_media_query = ''
current_media_query = String.new
else
current_media_query += token.strip + ' '
token.strip!
current_media_query << token << ' '
end
elsif in_charset or token =~ /@charset/i
# iterate until we are out of the charset declaration
in_charset = (token =~ /;/ ? false : true)
in_charset = !token.include?(';')
else
if token =~ /\}/ and not in_string
if !in_string && token.include?('}')
block_depth = block_depth - 1

# reset the current media query scope
Expand All @@ -400,12 +403,12 @@ def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
in_media_block = false
end
else
if token =~ /\{/ and not in_string
if !in_string && token.include?('{')
current_selectors.strip!
in_declarations += 1
else
# if we are in a selector, add the token to the current selectors
current_selectors += token
current_selectors << token

# mark this as the beginning of the selector unless we have already marked it
rule_start = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
Expand Down
14 changes: 7 additions & 7 deletions lib/css_parser/regexps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ def self.regex_possible_values *values


# Patterns for specificity calculations
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX= /
(\.[\w]+) # classes
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC= /
(?:\.[\w]+) # classes
|
\[(\w+) # attributes
\[(?:\w+) # attributes
|
(\:( # pseudo classes
(?:\:(?: # pseudo classes
link|visited|active
|hover|focus
|lang
Expand All @@ -72,10 +72,10 @@ def self.regex_possible_values *values
|empty|contains
))
/ix
ELEMENTS_AND_PSEUDO_ELEMENTS_RX = /
((^|[\s\+\>\~]+)[\w]+ # elements
ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC = /
(?:(?:^|[\s\+\>\~]+)[\w]+ # elements
|
\:{1,2}( # pseudo-elements
\:{1,2}(?: # pseudo-elements
after|before
|first-letter|first-line
|selection
Expand Down
45 changes: 26 additions & 19 deletions lib/css_parser/rule_set.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
module CssParser
class RuleSet
# Patterns for specificity calculations
Expand Down Expand Up @@ -27,7 +28,7 @@ def get_value(property)
return '' unless property and not property.empty?

property = property.downcase.strip
properties = @declarations.inject('') do |val, (key, data)|
properties = @declarations.inject(String.new) do |val, (key, data)|
#puts "COMPARING #{key} #{key.inspect} against #{property} #{property.inspect}"
importance = data[:is_important] ? ' !important' : ''
val << "#{data[:value]}#{importance}; " if key.downcase.strip == property
Expand Down Expand Up @@ -58,7 +59,8 @@ def add_declaration!(property, value)

value.gsub!(/;\Z/, '')
is_important = !value.gsub!(CssParser::IMPORTANT_IN_PROPERTY_RX, '').nil?
property = property.downcase.strip
property = property.downcase
property.strip!
#puts "SAVING #{property} #{value} #{is_important.inspect}"
@declarations[property] = {
:value => value, :is_important => is_important, :order => @order += 1
Expand Down Expand Up @@ -106,12 +108,14 @@ def each_declaration # :yields: property, value, is_important
#++
def declarations_to_s(options = {})
options = {:force_important => false}.merge(options)
str = ''
str = String.new
each_declaration do |prop, val, is_important|
importance = (options[:force_important] || is_important) ? ' !important' : ''
str += "#{prop}: #{val}#{importance}; "
str << "#{prop}: #{val}#{importance}; "
end
str.gsub(/^[\s^(\{)]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
str.gsub!(/^[\s^(\{)]+|[\n\r\f\t]*|[\s]+$/mx, '')
str.strip!
str
end

# Return the CSS rule set as a string.
Expand Down Expand Up @@ -434,20 +438,20 @@ def create_font_shorthand! # :nodoc:
return unless @declarations.has_key?(prop)
end

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

new_value += @declarations['font-size'][:value]
new_value << @declarations['font-size'][:value]

unless @declarations['line-height'][:value] == 'normal'
new_value += '/' + @declarations['line-height'][:value]
new_value << '/' << @declarations['line-height'][:value]
end

new_value += ' ' + @declarations['font-family'][:value]
new_value << ' ' << @declarations['font-family'][:value]

@declarations['font'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}

Expand Down Expand Up @@ -490,19 +494,18 @@ def parse_declarations!(block) # :nodoc:

return unless block

block.gsub!(/(^[\s]*)|([\s]*$)/, '')

continuation = ''
continuation = nil
block.split(/[\;$]+/m).each do |decs|
decs = continuation + decs
decs = continuation ? continuation + decs : decs
if decs =~ /\([^)]*\Z/ # if it has an unmatched parenthesis
continuation = decs + ';'

elsif matches = decs.match(/(.[^:]*)\s*:\s*(.+)(;?\s*\Z)/i)
property, value, = matches.captures # skip end_of_declaration

elsif matches = decs.match(/\s*(.[^:]*)\s*:\s*(.+?)(;?\s*\Z)/i)
# skip end_of_declaration
property = matches[1]
value = matches[2]
add_declaration!(property, value)
continuation = ''
continuation = nil
end
end
end
Expand All @@ -511,7 +514,11 @@ def parse_declarations!(block) # :nodoc:
# TODO: way too simplistic
#++
def parse_selectors!(selectors) # :nodoc:
@selectors = selectors.split(',').map { |s| s.gsub(/\s+/, ' ').strip }
@selectors = selectors.split(',').map do |s|
s.gsub!(/\s+/, ' ')
s.strip!
s
end
end
end

Expand Down

0 comments on commit dab9a92

Please sign in to comment.