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

Fix test case && issue of exceeding selectors #74

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
111 changes: 51 additions & 60 deletions lib/css_splitter/splitter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,10 @@ def self.split_string(css_string, split = 1, max_selectors = MAX_SELECTORS_DEFAU
end

# splits string into array of rules (also strips comments)
ORIGINAL_REGX = "[^}]*}"
INNER_AT_RULES_REGX = "(?:[^{}]*{[^}]*})*"
def self.split_string_into_rules(css_string)
partial_rules = strip_comments(css_string).chomp.scan /[^}]*}/
whole_rules = []
bracket_balance = 0
in_media_query = false

partial_rules.each do |rule|
if rule =~ /^\s*@media/
in_media_query = true
elsif bracket_balance == 0
in_media_query = false
end

if bracket_balance == 0 || in_media_query
whole_rules << rule
else
whole_rules.last << rule
end

bracket_balance += get_rule_bracket_balance rule
end

whole_rules
return strip_comments(css_string).chomp.scan(/[^@{]*@[^{]*{#{INNER_AT_RULES_REGX}[^}]*}|#{ORIGINAL_REGX}/)
end

# extracts the specified part of an overlong CSS string
Expand All @@ -44,51 +25,62 @@ def self.extract_part(rules, part = 1, max_selectors = MAX_SELECTORS_DEFAULT)
return if rules.nil?

output = charset_statement || ""
current_part = 1
max = max_selectors
selectors_count = 0
selector_range = max_selectors * (part - 1) + 1 .. max_selectors * part # e.g (4096..8190)

current_media = nil
selectors_in_media = 0
first_hit = true
rules.each do |rule|
media_part = extract_media(rule)
if media_part
current_media = media_part
selectors_in_media = 0
check_part = proc{|rule|
from = selectors_count + 1
to = (selectors_count += count_selectors_of_rule(rule))
# min max
#----------AAAAAAAAAA---------- EX: (min: 1, max: 10)
#------------------@@@@-------- EX: (from: 9, to: 12)
# from to
#------------------BBBBBBBBBB-- EX: (min: 9, max: 18)
# new_min new_max
if to > max #out range
next :out_of_part if current_part >= part
current_part += 1
overlap = max - from + 1
max += max_selectors - overlap
end

rule_selectors_count = count_selectors_of_rule rule
selectors_count += rule_selectors_count

if rule =~ /\A\s*}\z$/
current_media = nil
# skip the line if the close bracket is the first rule for the new file
next if first_hit
# min max
#----------AAAAAAAAAA---------- EX: (min: 1, max: 10)
#----------@@@@---------------- EX: (from: 1, to: 4)
# from to
if to <= max #in range
next if current_part < part
next :in_part
end

if selector_range.cover? selectors_count # add rule to current output if within selector_range
if media_part
output << media_part
elsif first_hit && current_media
output << current_media
}
rules.each do |rule|
if (media_part = extract_media(rule))
hit = false
split_string_into_rules(rule).each_with_index do |rule, idx|
case check_part.call(rule)
when :out_of_part
need_break = true
break
when :in_part
output << media_part if not hit
output << rule
hit = true
end
end
output << '}' if hit
else
case check_part.call(rule)
when :out_of_part ; need_break = true
when :in_part ; output << rule
end
selectors_in_media += rule_selectors_count if current_media.present?
output << rule
first_hit = false
elsif selectors_count > selector_range.end # stop writing to output
break
end
break if need_break
end

if current_media.present? and selectors_in_media > 0
output << '}'
end

output
return output
end

# count selectors of one individual CSS rule
def self.count_selectors_of_rule(rule)
return split_string_into_rules(rule).map{|rule| count_selectors_of_rule(rule) }.inject(&:+) if extract_media(rule)
parts = strip_comments(rule).partition(/\{/)
parts.second.empty? ? 0 : parts.first.scan(/,/).count.to_i + 1
end
Expand All @@ -110,9 +102,8 @@ def self.count_selectors(css_file)
private

def self.extract_media(rule)
if rule.sub!(/^\s*(@media[^{]*{)([^{}]*{[^}]*})$/) { $2 }
$1
end
rule.sub!(/(@media[^{]*{)(#{INNER_AT_RULES_REGX})[^}]*}/, '\2')
return $1
end

# extracts potential charset declaration from the first rule
Expand Down
12 changes: 10 additions & 2 deletions test/unit/splitter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ class CssSplitterTest < ActiveSupport::TestCase

test '#split_string_into_rules containing media queries' do
has_media = "a{foo:bar;}@media print{b{baz:qux;}c{quux:corge;}}d{grault:garply;}"
assert_equal ["a{foo:bar;}", "@media print{b{baz:qux;}", "c{quux:corge;}", "}", "d{grault:garply;}"], CssSplitter::Splitter.split_string_into_rules(has_media)
assert_equal ["a{foo:bar;}", "@media print{b{baz:qux;}c{quux:corge;}}", "d{grault:garply;}"], CssSplitter::Splitter.split_string_into_rules(has_media)
end

test '#split_string_into_rules containing media queries with leading whitespace' do
has_media = "a{foo:bar;}\n @media print{b{baz:qux;}c{quux:corge;}}d{grault:garply;}"
assert_equal ["a{foo:bar;}", "\n @media print{b{baz:qux;}", "c{quux:corge;}", "}", "d{grault:garply;}"], CssSplitter::Splitter.split_string_into_rules(has_media)
assert_equal ["a{foo:bar;}", "\n @media print{b{baz:qux;}c{quux:corge;}}", "d{grault:garply;}"], CssSplitter::Splitter.split_string_into_rules(has_media)
end

test "#split_string_into_rules containing keyframes" do
Expand Down Expand Up @@ -190,6 +190,14 @@ class CssSplitterTest < ActiveSupport::TestCase
assert_equal last_contents, CssSplitter::Splitter.split_string(css_contents, 2, max_selectors)
end

test '#split_string where a split should not have more than MAX LIMIT selectors' do
max_selectors = 100
css_selectors1 = "#{Array.new( 10){|n| ".a#{n}" }.join(',')}{ color: red; }".gsub(/\s/, '')
css_selectors2 = "#{Array.new(100){|n| ".b#{n}" }.join(',')}{ color: red; }".gsub(/\s/, '')
css_selectors3 = "#{Array.new( 90){|n| ".c#{n}" }.join(',')}{ color: red; }".gsub(/\s/, '')
css_contents = "#{css_selectors1}#{css_selectors2}#{css_selectors3}"
assert_operator CssSplitter::Splitter.split_string(css_contents, 2, max_selectors).split(',').size, :<=, max_selectors
end
# --- strip_comments ---

test '#strip_comments: strip single line CSS coment' do
Expand Down