module CssParser
def self.calculate_specificity(selector)
Thanks to Rafael Salazar and Nick Fitzsimons on the css-discuss list for their help.
--
=> 114
CssParser.calculate_specificity('#content div p:first-line a:link')
==== Example
Returns an integer.
per http://www.w3.org/TR/CSS21/cascade.html#specificity
Calculates the specificity of a CSS selector
def self.calculate_specificity(selector) a = 0 b = selector.scan('#').length c = selector.scan(NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC).length d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC).length "#{a}#{b}#{c}#{d}".to_i rescue 0 end
def self.convert_uris(css, base_uri)
"http://example.org/style/basic.css").inspect
CssParser.convert_uris("body { background: url('../style/yellow.png?abc=123') };",
==== Example
Returns a string.
per http://www.w3.org/TR/CSS21/syndata.html#uri
"For CSS style sheets, the base URI is that of the style sheet, not that of the source document."
Takes a block of CSS and returns it with all relative URIs converted to absolute URIs.
Make url() links absolute.
def self.convert_uris(css, base_uri) base_uri = Addressable::URI.parse(base_uri) unless base_uri.is_a?(Addressable::URI) css.gsub(URI_RX) do uri = Regexp.last_match(1).to_s.gsub(/["']+/, '') # Don't process URLs that are already absolute unless uri.match?(%r{^[a-z]+://}i) begin uri = base_uri.join(uri) rescue nil end end "url('#{uri}')" end end
def self.merge(*rule_sets)
TODO: declaration_hashes should be able to contain a RuleSet
--
=> "{ background: none black; }"
puts merged
merged = CssParser.merge(rs1, rs2)
rs2 = RuleSet.new(nil, 'background-image: none;')
rs1 = RuleSet.new(nil, 'background-color: black;')
==== Example #2
=> "{ margin: 0px; color: black; }"
puts merged
merged = CssParser.merge(rs1, rs2)
rs2 = RuleSet.new(nil, 'margin: 0px;')
rs1 = RuleSet.new(nil, 'color: black;')
==== Example #1
If multiple selectors are present then the greatest specificity is used.
If no selectors the specificity is treated as 0.
the specificity is calculated using that selector.
If no specificity is explicitly set and the RuleSet has *one* selector,
used in the cascade calculations.
If a RuleSet object has its +specificity+ defined, that specificity is
==== Cascading
Returns a RuleSet.
Takes one or more RuleSet objects.
(http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order).
Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules
def self.merge(*rule_sets) # in case called like CssParser.merge([rule_set, rule_set]) rule_sets.flatten! if rule_sets[0].is_a?(Array) unless rule_sets.all?(CssParser::RuleSet) raise ArgumentError, 'all parameters must be CssParser::RuleSets.' end return rule_sets[0] if rule_sets.length == 1 # Internal storage of CSS properties that we will keep properties = {} rule_sets.each do |rule_set| rule_set.expand_shorthand! specificity = rule_set.specificity specificity ||= rule_set.selectors.filter_map { |s| calculate_specificity(s) }.max || 0 rule_set.each_declaration do |property, value, is_important| # Add the property to the list to be folded per http://www.w3.org/TR/CSS21/cascade.html#cascading-order if !properties.key?(property) properties[property] = {value: value, specificity: specificity, is_important: is_important} elsif is_important if !properties[property][:is_important] || properties[property][:specificity] <= specificity properties[property] = {value: value, specificity: specificity, is_important: is_important} end elsif properties[property][:specificity] < specificity || properties[property][:specificity] == specificity unless properties[property][:is_important] properties[property] = {value: value, specificity: specificity, is_important: is_important} end end end end merged = properties.each_with_object(RuleSet.new(nil, nil)) do |(property, details), rule_set| value = details[:value].strip rule_set[property.strip] = details[:is_important] ? "#{value.gsub(/;\Z/, '')}!important" : value end merged.create_shorthand! merged end
def self.regex_possible_values(*values)
def self.regex_possible_values(*values) Regexp.new("([\s]*^)?(#{values.join('|')})([\s]*$)?", 'i') end
def self.sanitize_media_query(raw)
def self.sanitize_media_query(raw) mq = raw.to_s.gsub(/\s+/, ' ') mq.strip! mq = 'all' if mq.empty? mq.to_sym end