Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory and Performance Optimization #91

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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