css-parser-1.3.6/ 0000755 0001750 0001750 00000000000 12542615072 014021 5 ustar terceiro terceiro css-parser-1.3.6/CHANGELOG.md 0000644 0001750 0001750 00000006202 12542615072 015632 0 ustar terceiro terceiro ## Ruby CSS Parser CHANGELOG
### Version 1.3.6
* Fix bug not setting general rules after media query @jievans.
* We doesn't support Ruby 1.8 anymore.
* Run tests on Ruby 2.0 and Ruby 2.1.
* Respect the :import option.
### Version 1.3.5
* Use URI#request_uri instead of URI#path @duckinator.
* Media_query_support @mzsanford
* Don't require open-uri @aripollak
* Symbols not sortable on 1.8.7 @morten
* Improve create_dimensions_shorthand performance @aaronjensen
* Fixes hash ordering in tests @morten
### Version 1.3.4
* Enable code highlighting for tests @grosser
* Fix error in media query parsing @smgt
* Add test to missing cleaning of media type in parsing @smgt
### Version 1.3.3
* Require version before requiring classes that depend on it @morten
### Version 1.3.2
* Fix them crazy requires and only define version once @grosser
* Apply ocd @grosser
### Version 1.3.1
* More tests (and fixes) for background gradients @fortnightlabs
* Support declarations with `;` in them @flavorpill
* Stricter detection of !important @flavorpill
### Version 1.3.0
* Updates of gem by @grosser
* Multiple selectors should properly calculate specificity @alexdunae
* Specificity: The selector with the highest specificity may be in a compound selector statement? @morten
* Selectors should not be registered with surrounding whitespace. @morten
* Fix RE_GRADIENT reference @alexdunae
* Add load_string! method tests @alexdunae
* Gradient regexp tests @alexdunae
* Edited rule set @mccuskk
### Version 1.2.6
* JRuby and Ruby 1.9.3-preview1 compat
### Version 1.2.5
* Fix merging of multiple !important rules to match the spec
### Version 1.2.3
* First pass of media query support
### Version 1.2.2
* Fix merging of multiple !important rules to match the spec
### Version 1.2.1
* Better border shorthand handling
* List shorthand handling
* Malformed URI handling improvements
* Use Bundler
### Version 1.2.0
* Specificity improvements
* RGBA, HSL and HSLA support
* Bug fixes
### Version 1.1.9
* Add remove_declaration! to RuleSet
### Version 1.1.8
* Fix syntax error
### Version 1.1.7
* Automatically close missing braces at the end of a block
### Version 1.1.6
* Fix media type handling in add_block! and load_uri!
### Version 1.1.5
* Fix merging of !important declarations
### Version 1.1.4
* Ruby 1.9.2 compat
### Version 1.1.3
* allow limiting by media type in add_block!
### Version 1.1.2
* improve parsing of malformed declarations
* improve support for local files
* added support for loading over SSL
* added support for deflate
### Version 1.1.1
* Ruby 1.9 compatibility
* @import regexp updates
* various bug fixes
### Version 1.1.0
* Added support for local @import
* Better remote @import handling
### Version 1.0.1
* Fallback for declarations without sort order
### Version 1.0.0
* Various test fixes and udpate for Ruby 1.9 (thanks to Tyler Cunnion)
* Allow setting CSS declarations to nil
### Version 0.9
* Initial version forked from Premailer project
### TODO: Future
* re-implement caching on CssParser.merge
* correctly parse http://www.webstandards.org/files/acid2/test.html
css-parser-1.3.6/.travis.yml 0000644 0001750 0001750 00000000127 12542615072 016132 0 ustar terceiro terceiro notifications:
disabled: true
rvm:
- 1.9.2
- 1.9.3
- 2.0.0
- 2.1.0
- jruby
css-parser-1.3.6/lib/ 0000755 0001750 0001750 00000000000 12542615072 014567 5 ustar terceiro terceiro css-parser-1.3.6/lib/css_parser/ 0000755 0001750 0001750 00000000000 12542615072 016733 5 ustar terceiro terceiro css-parser-1.3.6/lib/css_parser/regexps.rb 0000644 0001750 0001750 00000011142 12542615072 020734 0 ustar terceiro terceiro module CssParser
def self.regex_possible_values *values
Regexp.new("([\s]*^)?(#{values.join('|')})([\s]*$)?", 'i')
end
# :stopdoc:
# Base types
RE_NL = Regexp.new('(\n|\r\n|\r|\f)')
RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE, 'n') #[^\0-\177]
RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE, 'n')
RE_ESCAPE = Regexp.union(RE_UNICODE, '|(\\\\[^\n\r\f0-9a-f])')
RE_IDENT = Regexp.new("[\-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9\-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE, 'n')
# General strings
RE_STRING1 = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
RE_STRING2 = Regexp.new('(\'(.[^\n\r\f\\\']*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\')')
RE_STRING = Regexp.union(RE_STRING1, RE_STRING2)
RE_INHERIT = regex_possible_values 'inherit'
RE_URI = Regexp.new('(url\([\s]*([\s]*' + RE_STRING.to_s + '[\s]*)[\s]*\))|(url\([\s]*([!#$%&*\-~]|' + RE_NON_ASCII.to_s + '|' + RE_ESCAPE.to_s + ')*[\s]*)\)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE, 'n')
URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im
RE_GRADIENT = /[-a-z]*gradient\([-a-z0-9 .,#%()]*\)/im
# Initial parsing
RE_AT_IMPORT_RULE = /\@import[\s]+(url\()?["''"]?(.[^'"\s"']*)["''"]?\)?([\w\s\,^\])]*)\)?;?/
#--
#RE_AT_MEDIA_RULE = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
#RE_AT_IMPORT_RULE = Regexp.new('@import[\s]*(' + RE_STRING.to_s + ')([\w\s\,]*)[;]?', Regexp::IGNORECASE) -- should handle url() even though it is not allowed
#++
IMPORTANT_IN_PROPERTY_RX = /[\s]*!important\b[\s]*/i
RE_INSIDE_OUTSIDE = regex_possible_values 'inside', 'outside'
RE_SCROLL_FIXED = regex_possible_values 'scroll', 'fixed'
RE_REPEAT = regex_possible_values 'repeat(\-x|\-y)*|no\-repeat'
RE_LIST_STYLE_TYPE = regex_possible_values 'disc', 'circle', 'square', 'decimal-leading-zero', 'decimal', 'lower-roman',
'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', 'upper-alpha',
'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', 'hiragana',
'hira-gana-iroha', 'katakana-iroha', 'katakana', 'none'
STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
# Special units
BOX_MODEL_UNITS_RX = /(auto|inherit|0|([\-]*([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%)))([\s;]|\Z)/imx
RE_LENGTH_OR_PERCENTAGE = Regexp.new('([\-]*(([0-9]*\.[0-9]+)|[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%))', Regexp::IGNORECASE)
RE_BACKGROUND_POSITION = Regexp.new("((((#{RE_LENGTH_OR_PERCENTAGE})|left|center|right|top|bottom)[\s]*){1,2})", Regexp::IGNORECASE | Regexp::EXTENDED)
FONT_UNITS_RX = /(([x]+\-)*small|medium|large[r]*|auto|inherit|([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%)*)/i
RE_BORDER_STYLE = /([\s]*^)?(none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset)([\s]*$)?/imx
RE_BORDER_UNITS = Regexp.union(BOX_MODEL_UNITS_RX, /(thin|medium|thick)/i)
# Patterns for specificity calculations
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX= /
(\.[\w]+) # classes
|
\[(\w+) # attributes
|
(\:( # pseudo classes
link|visited|active
|hover|focus
|lang
|target
|enabled|disabled|checked|indeterminate
|root
|nth-child|nth-last-child|nth-of-type|nth-last-of-type
|first-child|last-child|first-of-type|last-of-type
|only-child|only-of-type
|empty|contains
))
/ix
ELEMENTS_AND_PSEUDO_ELEMENTS_RX = /
((^|[\s\+\>\~]+)[\w]+ # elements
|
\:{1,2}( # pseudo-elements
after|before
|first-letter|first-line
|selection
)
)/ix
# Colours
RE_COLOUR_NUMERIC = Regexp.new('((hsl|rgb)[\s]*\([\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*\))', Regexp::IGNORECASE)
RE_COLOUR_NUMERIC_ALPHA = Regexp.new('((hsla|rgba)[\s]*\([\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*\))', Regexp::IGNORECASE)
RE_COLOUR_HEX = /(#([0-9a-f]{6}|[0-9a-f]{3})([\s;]|$))/i
RE_COLOUR_NAMED = /([\s]*^)?(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|transparent)([\s]*$)?/i
RE_COLOUR = Regexp.union(RE_COLOUR_NUMERIC, RE_COLOUR_NUMERIC_ALPHA, RE_COLOUR_HEX, RE_COLOUR_NAMED)
# :startdoc:
end
css-parser-1.3.6/lib/css_parser/rule_set.rb 0000644 0001750 0001750 00000042716 12542615072 021114 0 ustar terceiro terceiro module CssParser
class RuleSet
# Patterns for specificity calculations
RE_ELEMENTS_AND_PSEUDO_ELEMENTS = /((^|[\s\+\>]+)[\w]+|\:(first\-line|first\-letter|before|after))/i
RE_NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES = /(\.[\w]+)|(\[[\w]+)|(\:(link|first\-child|lang))/i
BACKGROUND_PROPERTIES = ['background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment']
LIST_STYLE_PROPERTIES = ['list-style-type', 'list-style-position', 'list-style-image']
# Array of selector strings.
attr_reader :selectors
# Integer with the specificity to use for this RuleSet.
attr_accessor :specificity
def initialize(selectors, block, specificity = nil)
@selectors = []
@specificity = specificity
@declarations = {}
@order = 0
parse_selectors!(selectors) if selectors
parse_declarations!(block)
end
# Get the value of a property
def get_value(property)
return '' unless property and not property.empty?
property = property.downcase.strip
properties = @declarations.inject('') 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
val
end
return properties ? properties.strip : ''
end
alias_method :[], :get_value
# Add a CSS declaration to the current RuleSet.
#
# rule_set.add_declaration!('color', 'blue')
#
# puts rule_set['color']
# => 'blue;'
#
# rule_set.add_declaration!('margin', '0px auto !important')
#
# puts rule_set['margin']
# => '0px auto !important;'
#
# If the property already exists its value will be over-written.
def add_declaration!(property, value)
if value.nil? or value.empty?
@declarations.delete(property)
return
end
value.gsub!(/;\Z/, '')
is_important = !value.gsub!(CssParser::IMPORTANT_IN_PROPERTY_RX, '').nil?
property = property.downcase.strip
#puts "SAVING #{property} #{value} #{is_important.inspect}"
@declarations[property] = {
:value => value, :is_important => is_important, :order => @order += 1
}
end
alias_method :[]=, :add_declaration!
# Remove CSS declaration from the current RuleSet.
#
# rule_set.remove_declaration!('color')
def remove_declaration!(property)
@declarations.delete(property)
end
# Iterate through selectors.
#
# Options
# - +force_important+ -- boolean
#
# ==== Example
# ruleset.each_selector do |sel, dec, spec|
# ...
# end
def each_selector(options = {}) # :yields: selector, declarations, specificity
declarations = declarations_to_s(options)
if @specificity
@selectors.each { |sel| yield sel.strip, declarations, @specificity }
else
@selectors.each { |sel| yield sel.strip, declarations, CssParser.calculate_specificity(sel) }
end
end
# Iterate through declarations.
def each_declaration # :yields: property, value, is_important
decs = @declarations.sort { |a,b| a[1][:order].nil? || b[1][:order].nil? ? 0 : a[1][:order] <=> b[1][:order] }
decs.each do |property, data|
value = data[:value]
yield property.downcase.strip, value.strip, data[:is_important]
end
end
# Return all declarations as a string.
#--
# TODO: Clean-up regexp doesn't seem to work
#++
def declarations_to_s(options = {})
options = {:force_important => false}.merge(options)
str = ''
each_declaration do |prop, val, is_important|
importance = (options[:force_important] || is_important) ? ' !important' : ''
str += "#{prop}: #{val}#{importance}; "
end
str.gsub(/^[\s^(\{)]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
end
# Return the CSS rule set as a string.
def to_s
decs = declarations_to_s
"#{@selectors.join(',')} { #{decs} }"
end
# Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts.
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
# Convert shorthand background declarations (e.g. background: url("chess.png") gray 50% repeat fixed;)
# into their constituent parts.
#
# See http://www.w3.org/TR/CSS21/colors.html#propdef-background
def expand_background_shorthand! # :nodoc:
return unless @declarations.has_key?('background')
value = @declarations['background'][:value]
if value =~ CssParser::RE_INHERIT
BACKGROUND_PROPERTIES.each do |prop|
split_declaration('background', prop, 'inherit')
end
end
split_declaration('background', 'background-image', value.slice!(Regexp.union(CssParser::URI_RX, CssParser::RE_GRADIENT, /none/i)))
split_declaration('background', 'background-attachment', value.slice!(CssParser::RE_SCROLL_FIXED))
split_declaration('background', 'background-repeat', value.slice!(CssParser::RE_REPEAT))
split_declaration('background', 'background-color', value.slice!(CssParser::RE_COLOUR))
split_declaration('background', 'background-position', value.slice(CssParser::RE_BACKGROUND_POSITION))
@declarations.delete('background')
end
# Split shorthand border declarations (e.g. border: 1px red;)
# Additional splitting happens in expand_dimensions_shorthand!
def expand_border_shorthand! # :nodoc:
['border', 'border-left', 'border-right', 'border-top', 'border-bottom'].each do |k|
next unless @declarations.has_key?(k)
value = @declarations[k][:value]
split_declaration(k, "#{k}-width", value.slice!(CssParser::RE_BORDER_UNITS))
split_declaration(k, "#{k}-color", value.slice!(CssParser::RE_COLOUR))
split_declaration(k, "#{k}-style", value.slice!(CssParser::RE_BORDER_STYLE))
@declarations.delete(k)
end
end
# Split shorthand dimensional declarations (e.g. margin: 0px auto;)
# into their constituent parts. Handles margin, padding, border-color, border-style and border-width.
def expand_dimensions_shorthand! # :nodoc:
{'margin' => 'margin-%s',
'padding' => 'padding-%s',
'border-color' => 'border-%s-color',
'border-style' => 'border-%s-style',
'border-width' => 'border-%s-width'}.each do |property, expanded|
next unless @declarations.has_key?(property)
value = @declarations[property][:value]
# 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]+/, '') }
matches = value.strip.split(/[\s]+/)
t, r, b, l = nil
case matches.length
when 1
t, r, b, l = matches[0], matches[0], matches[0], matches[0]
when 2
t, b = matches[0], matches[0]
r, l = matches[1], matches[1]
when 3
t = matches[0]
r, l = matches[1], matches[1]
b = matches[2]
when 4
t = matches[0]
r = matches[1]
b = matches[2]
l = matches[3]
end
split_declaration(property, expanded % 'top', t)
split_declaration(property, expanded % 'right', r)
split_declaration(property, expanded % 'bottom', b)
split_declaration(property, expanded % 'left', l)
@declarations.delete(property)
end
end
# Convert shorthand font declarations (e.g. font: 300 italic 11px/14px verdana, helvetica, sans-serif;)
# into their constituent parts.
def expand_font_shorthand! # :nodoc:
return unless @declarations.has_key?('font')
font_props = {}
# reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
['font-style', 'font-variant', 'font-weight', 'font-size',
'line-height'].each do |prop|
font_props[prop] = 'normal'
end
value = @declarations['font'][:value]
is_important = @declarations['font'][:is_important]
order = @declarations['font'][:order]
in_fonts = false
matches = value.scan(/("(.*[^"])"|'(.*[^'])'|(\w[^ ,]+))/)
matches.each do |match|
m = match[0].to_s.strip
m.gsub!(/[;]$/, '')
if in_fonts
if font_props.has_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 unless font_props.has_key?(font_prop)
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 =~ /\//
font_props['font-size'], font_props['line-height'] = m.split('/')
else
font_props['font-size'] = m
end
in_fonts = true
end
end
font_props.each { |font_prop, font_val| @declarations[font_prop] = {:value => font_val, :is_important => is_important, :order => order} }
@declarations.delete('font')
end
# Convert shorthand list-style declarations (e.g. list-style: lower-alpha outside;)
# into their constituent parts.
#
# See http://www.w3.org/TR/CSS21/generate.html#lists
def expand_list_style_shorthand! # :nodoc:
return unless @declarations.has_key?('list-style')
value = @declarations['list-style'][:value]
if value =~ CssParser::RE_INHERIT
LIST_STYLE_PROPERTIES.each do |prop|
split_declaration('list-style', prop, 'inherit')
end
end
split_declaration('list-style', 'list-style-type', value.slice!(CssParser::RE_LIST_STYLE_TYPE))
split_declaration('list-style', 'list-style-position', value.slice!(CssParser::RE_INSIDE_OUTSIDE))
split_declaration('list-style', 'list-style-image', value.slice!(Regexp.union(CssParser::URI_RX, /none/i)))
@declarations.delete('list-style')
end
# Create shorthand declarations (e.g. +margin+ or +font+) whenever possible.
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
# Combine several properties into a shorthand one
def create_shorthand_properties! properties, shorthand_property # :nodoc:
values = []
properties.each do |property|
if @declarations.has_key?(property) and not @declarations[property][:is_important]
values << @declarations[property][:value]
@declarations.delete(property)
end
end
unless values.empty?
@declarations[shorthand_property] = {:value => values.join(' ')}
end
end
# 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.
def create_background_shorthand! # :nodoc:
create_shorthand_properties! BACKGROUND_PROPERTIES, 'background'
end
# 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
def create_border_shorthand! # :nodoc:
values = []
['border-width', 'border-style', 'border-color'].each do |property|
if @declarations.has_key?(property) and not @declarations[property][:is_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...)
return if @declarations[property][:value].gsub(/\,[\s]/, ',').strip =~ /[\s]/
values << @declarations[property][:value]
end
end
@declarations.delete('border-width')
@declarations.delete('border-style')
@declarations.delete('border-color')
unless values.empty?
@declarations['border'] = {:value => values.join(' ')}
end
end
# Looks for long format CSS dimensional properties (margin, padding, border-color, border-style and border-width)
# and converts them into shorthand CSS properties.
def create_dimensions_shorthand! # :nodoc:
directions = ['top', 'right', 'bottom', 'left']
{'margin' => 'margin-%s',
'padding' => 'padding-%s',
'border-color' => 'border-%s-color',
'border-style' => 'border-%s-style',
'border-width' => 'border-%s-width'}.each do |property, expanded|
top, right, bottom, left = ['top', 'right', 'bottom', 'left'].map { |side| expanded % side }
foldable = @declarations.select do |dim, val|
dim == top or dim == right or dim == bottom or dim == left
end
# All four dimensions must be present
if foldable.length == 4
values = {}
directions.each { |d| values[d.to_sym] = @declarations[expanded % d][:value].downcase.strip }
if values[:left] == values[:right]
if values[:top] == values[:bottom]
if values[:top] == values[:left] # All four sides are equal
new_value = values[:top]
else # Top and bottom are equal, left and right are equal
new_value = values[:top] + ' ' + values[:left]
end
else # Only left and right are equal
new_value = values[:top] + ' ' + values[:left] + ' ' + values[:bottom]
end
else # No sides are equal
new_value = values[:top] + ' ' + values[:right] + ' ' + values[:bottom] + ' ' + values[:left]
end
new_value.strip!
@declarations[property] = {:value => new_value.strip} unless new_value.empty?
# Delete the longhand values
directions.each { |d| @declarations.delete(expanded % d) }
end
end
end
# 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.
def create_font_shorthand! # :nodoc:
['font-style', 'font-variant', 'font-weight', 'font-size',
'line-height', 'font-family'].each do |prop|
return unless @declarations.has_key?(prop)
end
new_value = ''
['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'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
['font-style', 'font-variant', 'font-weight', 'font-size',
'line-height', 'font-family'].each do |prop|
@declarations.delete(prop)
end
end
# 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.
def create_list_style_shorthand! # :nodoc:
create_shorthand_properties! LIST_STYLE_PROPERTIES, 'list-style'
end
private
# utility method for re-assign shorthand elements to longhand versions
def split_declaration(src, dest, v) # :nodoc:
return unless v and not v.empty?
if @declarations.has_key?(dest)
#puts "dest #{dest} already exists"
if @declarations[dest][:order] > @declarations[src][:order]
#puts "skipping #{dest}:#{v} due to order "
return
else
@declarations[dest] = {}
end
end
@declarations[dest] = @declarations[src].merge({:value => v.to_s.strip})
end
def parse_declarations!(block) # :nodoc:
@declarations = {}
return unless block
block.gsub!(/(^[\s]*)|([\s]*$)/, '')
continuation = ''
block.split(/[\;$]+/m).each do |decs|
decs = continuation + decs
if decs =~ /\([^)]*\Z/ # if it has an unmatched parenthesis
continuation = decs + ';'
elsif matches = decs.match(/(.[^:]*)\s*:\s*(.+)(;?\s*\Z)/i)
property, value, end_of_declaration = matches.captures
add_declaration!(property, value)
continuation = ''
end
end
end
#--
# TODO: way too simplistic
#++
def parse_selectors!(selectors) # :nodoc:
@selectors = selectors.split(',').map { |s| s.strip }
end
end
end
css-parser-1.3.6/lib/css_parser/parser.rb 0000644 0001750 0001750 00000040056 12542615072 020561 0 ustar terceiro terceiro module CssParser
# Exception class used for any errors encountered while downloading remote files.
class RemoteFileError < IOError; end
# Exception class used if a request is made to load a CSS file more than once.
class CircularReferenceError < StandardError; end
# == Parser class
#
# All CSS is converted to UTF-8.
#
# When calling Parser#new there are some configuaration options:
# [absolute_paths] Convert relative paths to absolute paths (href, src and url(''). Boolean, default is false.
# [import] Follow @import rules. Boolean, default is true.
# [io_exceptions] Throw an exception if a link can not be found. Boolean, default is true.
class Parser
USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (http://github.com/alexdunae/css_parser)"
STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
# Initial parsing
RE_AT_IMPORT_RULE = /\@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s\)]*)["']?\)?([\w\s\,^\]\(\))]*)\)?[;\n]?/
# Array of CSS files that have been loaded.
attr_reader :loaded_uris
#--
# Class variable? see http://www.oreillynet.com/ruby/blog/2007/01/nubygems_dont_use_class_variab_1.html
#++
@folded_declaration_cache = {}
class << self; attr_reader :folded_declaration_cache; end
def initialize(options = {})
@options = {:absolute_paths => false,
:import => true,
:io_exceptions => true}.merge(options)
# array of RuleSets
@rules = []
@loaded_uris = []
# unprocessed blocks of CSS
@blocks = []
reset!
end
# Get declarations by selector.
#
# +media_types+ are optional, and can be a symbol or an array of symbols.
# The default value is :all.
#
# ==== Examples
# find_by_selector('#content')
# => 'font-size: 13px; line-height: 1.2;'
#
# find_by_selector('#content', [:screen, :handheld])
# => 'font-size: 13px; line-height: 1.2;'
#
# find_by_selector('#content', :print)
# => 'font-size: 11pt; line-height: 1.2;'
#
# Returns an array of declarations.
def find_by_selector(selector, media_types = :all)
out = []
each_selector(media_types) do |sel, dec, spec|
out << dec if sel.strip == selector.strip
end
out
end
alias_method :[], :find_by_selector
# Finds the rule sets that match the given selectors
def find_rule_sets(selectors, media_types = :all)
rule_sets = []
selectors.each do |selector|
each_rule_set(media_types) do |rule_set, media_type|
if !rule_sets.member?(rule_set) && rule_set.selectors.member?(selector)
rule_sets << rule_set
end
end
end
rule_sets
end
# Add a raw block of CSS.
#
# In order to follow +@import+ rules you must supply either a
# +:base_dir+ or +:base_uri+ option.
#
# Use the +:media_types+ option to set the media type(s) for this block. Takes an array of symbols.
#
# Use the +:only_media_types+ option to selectively follow +@import+ rules. Takes an array of symbols.
#
# ==== Example
# css = <<-EOT
# body { font-size: 10pt }
# p { margin: 0px; }
# @media screen, print {
# body { line-height: 1.2 }
# }
# EOT
#
# parser = CssParser::Parser.new
# parser.add_block!(css)
def add_block!(block, options = {})
options = {:base_uri => nil, :base_dir => nil, :charset => nil, :media_types => :all, :only_media_types => :all}.merge(options)
options[:media_types] = [options[:media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
options[:only_media_types] = [options[:only_media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
block = cleanup_block(block)
if options[:base_uri] and @options[:absolute_paths]
block = CssParser.convert_uris(block, options[:base_uri])
end
# Load @imported CSS
if @options[:import]
block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
media_types = []
if media_string = import_rule[-1]
media_string.split(/[,]/).each do |t|
media_types << CssParser.sanitize_media_query(t) unless t.empty?
end
else
media_types = [:all]
end
next unless options[:only_media_types].include?(:all) or media_types.length < 1 or (media_types & options[:only_media_types]).length > 0
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
if options[:base_uri]
import_uri = Addressable::URI.parse(options[:base_uri].to_s) + Addressable::URI.parse(import_path)
load_uri!(import_uri, options[:base_uri], media_types)
elsif options[:base_dir]
load_file!(import_path, options[:base_dir], media_types)
end
end
end
# Remove @import declarations
block.gsub!(RE_AT_IMPORT_RULE, '')
parse_block_into_rule_sets!(block, options)
end
# Add a CSS rule by setting the +selectors+, +declarations+ and +media_types+.
#
# +media_types+ can be a symbol or an array of symbols.
def add_rule!(selectors, declarations, media_types = :all)
rule_set = RuleSet.new(selectors, declarations)
add_rule_set!(rule_set, media_types)
end
# Add a CssParser RuleSet object.
#
# +media_types+ can be a symbol or an array of symbols.
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)}
@rules << {:media_types => media_types, :rules => ruleset}
end
# Iterate through RuleSet objects.
#
# +media_types+ can be a symbol or an array of symbols.
def each_rule_set(media_types = :all) # :yields: rule_set, media_types
media_types = [:all] if media_types.nil?
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
@rules.each do |block|
if media_types.include?(:all) or block[:media_types].any? { |mt| media_types.include?(mt) }
yield(block[:rules], block[:media_types])
end
end
end
# Iterate through CSS selectors.
#
# +media_types+ can be a symbol or an array of symbols.
# See RuleSet#each_selector for +options+.
def each_selector(media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types
each_rule_set(media_types) do |rule_set, media_types|
rule_set.each_selector(options) do |selectors, declarations, specificity|
yield selectors, declarations, specificity, media_types
end
end
end
# Output all CSS rules as a single stylesheet.
def to_s(media_types = :all)
out = ''
styles_by_media_types = {}
each_selector(media_types) do |selectors, declarations, specificity, media_types|
media_types.each do |media_type|
styles_by_media_types[media_type] ||= []
styles_by_media_types[media_type] << [selectors, declarations]
end
end
styles_by_media_types.each_pair do |media_type, media_styles|
media_block = (media_type != :all)
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"
else
out += "#{media_style[0]} {\n#{media_style[1]}\n}\n"
end
end
out += "}\n" if media_block
end
out
end
# A hash of { :media_query => rule_sets }
def rules_by_media_query
rules_by_media = {}
@rules.each do |block|
block[:media_types].each do |mt|
unless rules_by_media.has_key?(mt)
rules_by_media[mt] = []
end
rules_by_media[mt] << block[:rules]
end
end
rules_by_media
end
# Merge declarations with the same selector.
def compact! # :nodoc:
compacted = []
compacted
end
def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
current_media_queries = [:all]
if options[:media_types]
current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
end
in_declarations = 0
block_depth = 0
in_charset = false # @charset is ignored for now
in_string = false
in_at_media_rule = false
in_media_block = false
current_selectors = ''
current_media_query = ''
current_declarations = ''
block.scan(/([\\]?[{}\s"]|(.[^\s"{}\\]*))/).each do |matches|
token = matches[0]
if token =~ /\A"/ # 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 =~ /\}/
next
end
if token =~ /\{/
in_declarations += 1
next
end
current_declarations += token
if token =~ /\}/ and not in_string
current_declarations.gsub!(/\}[\s]*$/, '')
in_declarations -= 1
unless current_declarations.strip.empty?
add_rule!(current_selectors, current_declarations, current_media_queries)
end
current_selectors = ''
current_declarations = ''
end
elsif token =~ /@media/i
# found '@media', reset current media_types
in_at_media_rule = true
current_media_queries = []
elsif in_at_media_rule
if token =~ /\{/
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 =~ /[,]/
# new media query begins
token.gsub!(/[,]/, ' ')
current_media_query += token.strip + ' '
current_media_queries << CssParser.sanitize_media_query(current_media_query)
current_media_query = ''
else
current_media_query += token.strip + ' '
end
elsif in_charset or token =~ /@charset/i
# iterate until we are out of the charset declaration
in_charset = (token =~ /;/ ? false : true)
else
if token =~ /\}/ and not in_string
block_depth = block_depth - 1
# reset the current media query scope
if in_media_block
current_media_queries = [:all]
in_media_block = false
end
else
if token =~ /\{/ and not in_string
current_selectors.gsub!(/^[\s]*/, '')
current_selectors.gsub!(/[\s]*$/, '')
in_declarations += 1
else
current_selectors += token
end
end
end
end
# check for unclosed braces
if in_declarations > 0
add_rule!(current_selectors, current_declarations, current_media_queries)
end
end
# Load a remote CSS file.
#
# You can also pass in file://test.css
#
# See add_block! for options.
#
# Deprecated: originally accepted three params: `uri`, `base_uri` and `media_types`
def load_uri!(uri, options = {}, deprecated = nil)
uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme
#base_uri = nil, media_types = :all, options = {}
opts = {:base_uri => nil, :media_types => :all}
if options.is_a? Hash
opts.merge!(options)
else
opts[:base_uri] = options if options.is_a? String
opts[:media_types] = deprecated if deprecated
end
if uri.scheme == 'file' or uri.scheme.nil?
uri.path = File.expand_path(uri.path)
uri.scheme = 'file'
end
opts[:base_uri] = uri if opts[:base_uri].nil?
src, charset = read_remote_file(uri)
if src
add_block!(src, opts)
end
end
# Load a local CSS file.
def load_file!(file_name, base_dir = nil, media_types = :all)
file_name = File.expand_path(file_name, base_dir)
return unless File.readable?(file_name)
return unless circular_reference_check(file_name)
src = IO.read(file_name)
base_dir = File.dirname(file_name)
add_block!(src, {:media_types => media_types, :base_dir => base_dir})
end
# Load a local CSS string.
def load_string!(src, base_dir = nil, media_types = :all)
add_block!(src, {:media_types => media_types, :base_dir => base_dir})
end
protected
# Check that a path hasn't been loaded already
#
# Raises a CircularReferenceError exception if io_exceptions are on,
# otherwise returns true/false.
def circular_reference_check(path)
path = path.to_s
if @loaded_uris.include?(path)
raise CircularReferenceError, "can't load #{path} more than once" if @options[:io_exceptions]
return false
else
@loaded_uris << path
return true
end
end
# Strip comments and clean up blank lines from a block of CSS.
#
# Returns a string.
def cleanup_block(block) # :nodoc:
# Strip CSS comments
block.gsub!(STRIP_CSS_COMMENTS_RX, '')
# Strip HTML comments - they shouldn't really be in here but
# some people are just crazy...
block.gsub!(STRIP_HTML_COMMENTS_RX, '')
# Strip lines containing just whitespace
block.gsub!(/^\s+$/, "")
block
end
# Download a file into a string.
#
# Returns the file's data and character set in an array.
#--
# TODO: add option to fail silently or throw and exception on a 404
#++
def read_remote_file(uri) # :nodoc:
return nil, nil unless circular_reference_check(uri.to_s)
src = '', charset = nil
begin
uri = Addressable::URI.parse(uri.to_s)
if uri.scheme == 'file'
# local file
fh = open(uri.path, 'rb')
src = fh.read
fh.close
else
# remote file
if uri.scheme == 'https'
uri.port = 443 unless uri.port
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
else
http = Net::HTTP.new(uri.host, uri.port)
end
res = http.get(uri.request_uri, {'User-Agent' => USER_AGENT, 'Accept-Encoding' => 'gzip'})
src = res.body
charset = fh.respond_to?(:charset) ? fh.charset : 'utf-8'
if res.code.to_i >= 400
raise RemoteFileError if @options[:io_exceptions]
return '', nil
end
case res['content-encoding']
when 'gzip'
io = Zlib::GzipReader.new(StringIO.new(res.body))
src = io.read
when 'deflate'
io = Zlib::Inflate.new
src = io.inflate(res.body)
end
end
if charset
if String.method_defined?(:encode)
src.encode!('UTF-8', charset)
else
ic = Iconv.new('UTF-8//IGNORE', charset)
src = ic.iconv(src)
end
end
rescue
raise RemoteFileError if @options[:io_exceptions]
return nil, nil
end
return src, charset
end
private
# Save a folded declaration block to the internal cache.
def save_folded_declaration(block_hash, folded_declaration) # :nodoc:
@folded_declaration_cache[block_hash] = folded_declaration
end
# Retrieve a folded declaration block from the internal cache.
def get_folded_declaration(block_hash) # :nodoc:
return @folded_declaration_cache[block_hash] ||= nil
end
def reset! # :nodoc:
@folded_declaration_cache = {}
@css_source = ''
@css_rules = []
@css_warnings = []
end
end
end
css-parser-1.3.6/lib/css_parser/version.rb 0000644 0001750 0001750 00000000060 12542615072 020741 0 ustar terceiro terceiro module CssParser
VERSION = "1.3.6".freeze
end
css-parser-1.3.6/lib/css_parser.rb 0000644 0001750 0001750 00000012307 12542615072 017263 0 ustar terceiro terceiro require 'addressable/uri'
require 'uri'
require 'net/https'
require 'digest/md5'
require 'zlib'
require 'stringio'
require 'iconv' unless String.method_defined?(:encode)
require 'css_parser/version'
require 'css_parser/rule_set'
require 'css_parser/regexps'
require 'css_parser/parser'
module CssParser
# Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules
# (http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order).
#
# Takes one or more RuleSet objects.
#
# Returns a RuleSet.
#
# ==== Cascading
# If a RuleSet object has its +specificity+ defined, that specificity is
# used in the cascade calculations.
#
# If no specificity is explicitly set and the RuleSet has *one* selector,
# the specificity is calculated using that selector.
#
# If no selectors the specificity is treated as 0.
#
# If multiple selectors are present then the greatest specificity is used.
#
# ==== Example #1
# rs1 = RuleSet.new(nil, 'color: black;')
# rs2 = RuleSet.new(nil, 'margin: 0px;')
#
# merged = CssParser.merge(rs1, rs2)
#
# puts merged
# => "{ margin: 0px; color: black; }"
#
# ==== Example #2
# rs1 = RuleSet.new(nil, 'background-color: black;')
# rs2 = RuleSet.new(nil, 'background-image: none;')
#
# merged = CssParser.merge(rs1, rs2)
#
# puts merged
# => "{ background: none black; }"
#--
# TODO: declaration_hashes should be able to contain a RuleSet
# this should be a Class method
def self.merge(*rule_sets)
@folded_declaration_cache = {}
# in case called like CssParser.merge([rule_set, rule_set])
rule_sets.flatten! if rule_sets[0].kind_of?(Array)
unless rule_sets.all? {|rs| rs.kind_of?(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
unless specificity
if rule_set.selectors.length == 0
specificity = 0
else
specificity = rule_set.selectors.map { |s| calculate_specificity(s) }.compact.max || 0
end
end
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 not properties.has_key?(property)
properties[property] = {:value => value, :specificity => specificity, :is_important => is_important}
elsif is_important
if not properties[property][:is_important] or properties[property][:specificity] <= specificity
properties[property] = {:value => value, :specificity => specificity, :is_important => is_important}
end
elsif properties[property][:specificity] < specificity or properties[property][:specificity] == specificity
unless properties[property][:is_important]
properties[property] = {:value => value, :specificity => specificity, :is_important => is_important}
end
end
end
end
merged = RuleSet.new(nil, nil)
properties.each do |property, details|
if details[:is_important]
merged[property.strip] = details[:value].strip.gsub(/\;\Z/, '') + '!important'
else
merged[property.strip] = details[:value].strip
end
end
merged.create_shorthand!
merged
end
# Calculates the specificity of a CSS selector
# per http://www.w3.org/TR/CSS21/cascade.html#specificity
#
# Returns an integer.
#
# ==== Example
# CssParser.calculate_specificity('#content div p:first-line a:link')
# => 114
#--
# Thanks to Rafael Salazar and Nick Fitzsimons on the css-discuss list for their help.
#++
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
(a.to_s + b.to_s + c.to_s + d.to_s).to_i
rescue
return 0
end
# Make url() links absolute.
#
# Takes a block of CSS and returns it with all relative URIs converted to absolute URIs.
#
# "For CSS style sheets, the base URI is that of the style sheet, not that of the source document."
# per http://www.w3.org/TR/CSS21/syndata.html#uri
#
# Returns a string.
#
# ==== Example
# CssParser.convert_uris("body { background: url('../style/yellow.png?abc=123') };",
# "http://example.org/style/basic.css").inspect
# => "body { background: url('http://example.org/style/yellow.png?abc=123') };"
def self.convert_uris(css, base_uri)
base_uri = Addressable::URI.parse(base_uri) unless base_uri.kind_of?(Addressable::URI)
css.gsub(URI_RX) do
uri = $1.to_s
uri.gsub!(/["']+/, '')
# Don't process URLs that are already absolute
unless uri =~ /^[a-z]+\:\/\//i
begin
uri = base_uri + uri
rescue; end
end
"url('#{uri.to_s}')"
end
end
def self.sanitize_media_query(raw)
mq = raw.to_s.gsub(/[\s]+/, ' ').strip
mq = 'all' if mq.empty?
mq.to_sym
end
end
css-parser-1.3.6/Gemfile.lock 0000644 0001750 0001750 00000000627 12542615072 016250 0 ustar terceiro terceiro PATH
remote: .
specs:
css_parser (1.3.6)
addressable
GEM
remote: https://rubygems.org/
specs:
addressable (2.3.6)
bouncy-castle-java (1.5.0146.1)
bump (0.3.12)
jruby-openssl (0.7.6.1)
bouncy-castle-java (>= 1.5.0146.1)
rake (0.9.2.2)
test-unit (2.5.4)
PLATFORMS
java
ruby
DEPENDENCIES
bump
css_parser!
jruby-openssl
rake
test-unit (>= 2.5.3)
css-parser-1.3.6/MIT-LICENSE 0000644 0001750 0001750 00000002075 12542615072 015461 0 ustar terceiro terceiro === Ruby CSS Parser License
Copyright (c) 2007-11 Alex Dunae
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. css-parser-1.3.6/Readme.md 0000644 0001750 0001750 00000003140 12542615072 015536 0 ustar terceiro terceiro # Ruby CSS Parser
Load, parse and cascade CSS rule sets in Ruby.
# Setup
```Bash
gem install css_parser
```
# Usage
```Ruby
require 'css_parser'
include CssParser
parser = CssParser::Parser.new
parser.load_uri!('http://example.com/styles/style.css')
parser = CssParser::Parser.new
parser.load_uri!('file://home/user/styles/style.css')
# load a remote file, setting the base_uri and media_types
parser.load_uri!('../style.css', {:base_uri => 'http://example.com/styles/inc/', :media_types => [:screen, :handheld])
# load a local file, setting the base_dir and media_types
parser.load_file!('print.css', '~/styles/', :print)
# lookup a rule by a selector
parser.find_by_selector('#content')
#=> 'font-size: 13px; line-height: 1.2;'
# lookup a rule by a selector and media type
parser.find_by_selector('#content', [:screen, :handheld])
# iterate through selectors by media type
parser.each_selector(:screen) do |selector, declarations, specificity|
...
end
# add a block of CSS
css = <<-EOT
body { margin: 0 1em; }
EOT
parser.add_block!(css)
# output all CSS rules in a single stylesheet
parser.to_s
=> #content { font-size: 13px; line-height: 1.2; }
body { margin: 0 1em; }
```
# Testing
```Bash
bundle
bundle exec rake
```
Runs on Ruby/JRuby 1.9.2 or above.
# Credits
By Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2007-11.
License: MIT
Thanks to [all the wonderful contributors](http://github.com/alexdunae/css_parser/contributors) for their updates.
Made on Vancouver Island.
[](https://travis-ci.org/alexdunae/css_parser)
css-parser-1.3.6/test/ 0000755 0001750 0001750 00000000000 12542615072 015000 5 ustar terceiro terceiro css-parser-1.3.6/test/test_css_parser_loading.rb 0000644 0001750 0001750 00000011630 12542615072 022226 0 ustar terceiro terceiro require File.expand_path(File.dirname(__FILE__) + '/test_helper')
# Test cases for the CssParser's loading functions.
class CssParserLoadingTests < Test::Unit::TestCase
include CssParser
include WEBrick
def setup
# from http://nullref.se/blog/2006/5/17/testing-with-webrick
@cp = Parser.new
@uri_base = 'http://localhost:12000'
@www_root = File.dirname(__FILE__) + '/fixtures/'
@server_thread = Thread.new do
s = WEBrick::HTTPServer.new(:Port => 12000, :DocumentRoot => @www_root, :Logger => Log.new(nil, BasicLog::FATAL), :AccessLog => [])
@port = s.config[:Port]
begin
s.start
ensure
s.shutdown
end
end
sleep 1 # ensure the server has time to load
end
def teardown
@server_thread.kill
@server_thread.join(5)
@server_thread = nil
end
def test_loading_a_local_file
file_name = File.dirname(__FILE__) + '/fixtures/simple.css'
@cp.load_file!(file_name)
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
def test_loading_a_local_file_with_scheme
file_name = 'file://' + File.expand_path(File.dirname(__FILE__)) + '/fixtures/simple.css'
@cp.load_uri!(file_name)
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
def test_loading_a_remote_file
@cp.load_uri!("#{@uri_base}/simple.css")
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
# http://github.com/alexdunae/css_parser/issues#issue/4
def test_loading_a_remote_file_over_ssl
# TODO: test SSL locally
@cp.load_uri!("https://dialect.ca/inc/screen.css")
assert_match /margin\: 0\;/, @cp.find_by_selector('body').join(' ')
end
def test_loading_a_string
@cp.load_string!("p{margin:0px}")
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
def test_following_at_import_rules_local
base_dir = File.dirname(__FILE__) + '/fixtures'
@cp.load_file!('import1.css', base_dir)
# from '/import1.css'
assert_equal 'color: lime;', @cp.find_by_selector('div').join(' ')
# from '/subdir/import2.css'
assert_equal 'text-decoration: none;', @cp.find_by_selector('a').join(' ')
# from '/subdir/../simple.css'
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
def test_following_at_import_rules_remote
@cp.load_uri!("#{@uri_base}/import1.css")
# from '/import1.css'
assert_equal 'color: lime;', @cp.find_by_selector('div').join(' ')
# from '/subdir/import2.css'
assert_equal 'text-decoration: none;', @cp.find_by_selector('a').join(' ')
# from '/subdir/../simple.css'
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
def test_imports_disabled
cp = Parser.new(:import => false)
cp.load_uri!("#{@uri_base}/import1.css")
# from '/import1.css'
assert_equal 'color: lime;', cp.find_by_selector('div').join(' ')
# from '/subdir/import2.css'
assert_equal '', cp.find_by_selector('a').join(' ')
# from '/subdir/../simple.css'
assert_equal '', cp.find_by_selector('p').join(' ')
end
def test_following_badly_escaped_import_rules
css_block = '@import "http://example.com/css?family=Droid+Sans:regular,bold|Droid+Serif:regular,italic,bold,bolditalic&subset=latin";'
assert_nothing_raised do
@cp.add_block!(css_block, :base_uri => "#{@uri_base}/subdir/")
end
end
def test_following_at_import_rules_from_add_block
css_block = '@import "../simple.css";'
@cp.add_block!(css_block, :base_uri => "#{@uri_base}/subdir/")
# from 'simple.css'
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
def test_importing_with_media_types
@cp.load_uri!("#{@uri_base}/import-with-media-types.css")
# from simple.css with :screen media type
assert_equal 'margin: 0px;', @cp.find_by_selector('p', :screen).join(' ')
assert_equal '', @cp.find_by_selector('p', :tty).join(' ')
end
def test_local_circular_reference_exception
assert_raise CircularReferenceError do
@cp.load_file!(File.dirname(__FILE__) + '/fixtures/import-circular-reference.css')
end
end
def test_remote_circular_reference_exception
assert_raise CircularReferenceError do
@cp.load_uri!("#{@uri_base}/import-circular-reference.css")
end
end
def test_suppressing_circular_reference_exceptions
cp_without_exceptions = Parser.new(:io_exceptions => false)
assert_nothing_raised CircularReferenceError do
cp_without_exceptions.load_uri!("#{@uri_base}/import-circular-reference.css")
end
end
def test_toggling_not_found_exceptions
cp_with_exceptions = Parser.new(:io_exceptions => true)
assert_raise RemoteFileError do
cp_with_exceptions.load_uri!("#{@uri_base}/no-exist.xyz")
end
cp_without_exceptions = Parser.new(:io_exceptions => false)
assert_nothing_raised RemoteFileError do
cp_without_exceptions.load_uri!("#{@uri_base}/no-exist.xyz")
end
end
end
css-parser-1.3.6/test/test_helper.rb 0000644 0001750 0001750 00000000232 12542615072 017640 0 ustar terceiro terceiro require 'rubygems'
require 'bundler/setup'
require 'test/unit'
require 'net/http'
require 'webrick'
require File.dirname(__FILE__) + '/../lib/css_parser'
css-parser-1.3.6/test/test_css_parser_regexps.rb 0000644 0001750 0001750 00000006411 12542615072 022267 0 ustar terceiro terceiro # coding: iso-8859-1
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
# Test cases for CSS regular expressions
#
# see http://www.w3.org/TR/CSS21/syndata.html and
# http://www.w3.org/TR/CSS21/grammar.html
class CssParserRegexpTests < Test::Unit::TestCase
def test_strings
# complete matches
[
'"abcd"', '" A sd sédrcv \'dsf\' asd rfg asd"', '"A\ d??ef 123!"',
"\"this is\\\n a test\"", '"back\67round"', '"r\000065 ed"',
"'abcd'", "' A sd sedrcv \"dsf\" asd rf—&23$%#%$g asd'", "'A\\\n def 123!'",
"'this is\\\n a test'", "'back\\67round'", "'r\\000065 ed'"
].each do |str|
assert_equal str, str.match(CssParser::RE_STRING).to_s
end
test_string = "p { background: red url(\"url\\.'p'ng\"); }"
assert_equal "\"url\\.'p'ng\"", test_string.match(CssParser::RE_STRING).to_s
end
def test_unicode
['back\67round', 'r\000065 ed', '\00006C'].each do |str|
assert_match(Regexp.new(CssParser::RE_UNICODE), str)
end
end
def test_colour
[
'color: #fff', 'color:#f0a09c;', 'color: #04A', 'color: #04a9CE',
'color: rgb(100, -10%, 300);', 'color: rgb(10,10,10)', 'color:rgb(12.7253%, -12%,0)',
'color: black', 'color:Red;', 'color: AqUa;', 'color: blue ', 'color: transparent'
].each do |colour|
assert_match(CssParser::RE_COLOUR, colour)
end
[
'color: #fa', 'color:#f009c;', 'color: #04G', 'color: #04a9Cq',
'color: rgb 100, -10%, 300;', 'color: rgb 10,10,10', 'color:rgb(12px, -12%,0)',
'color:fuscia;', 'color: thick'
].each do |colour|
assert_no_match(CssParser::RE_COLOUR, colour)
end
end
def test_gradients
[
'linear-gradient(bottom, rgb(197,112,191) 7%, rgb(237,146,230) 54%, rgb(255,176,255) 77%)',
'linear-gradient(top, hsla(0, 0%, 0%, 0.00) 0%, hsla(0, 0%, 0%, 0.20) 100%)',
'-o-linear-gradient(bottom, rgb(197,112,191) 7%, rgb(237,146,230) 54%, rgb(255,176,255) 77%)',
'-moz-linear-gradient(bottom, rgb(197,112,191) 7%, rgb(237,146,230) 54%, rgb(255,176,255) 77%)',
'-webkit-linear-gradient(bottom, rgb(197,112,191) 7%, rgb(237,146,230) 54%, rgb(255,176,255) 77%)',
'-webkit-gradient(linear, left top, left bottom, color-stop(0, hsla(0, 0%, 0%, 0.00)), color-stop(1, hsla(0, 0%, 0%, 0.20)))',
'-ms-linear-gradient(bottom, rgb(197,112,191) 7%, rgb(237,146,230) 54%, rgb(255,176,255) 77%)'
].each do |grad|
assert_match(CssParser::RE_GRADIENT, grad)
end
end
def test_uris
crazy_uri = 'http://www.example.com:80/~/redb%20all.png?test=test&test;test+test#test!'
assert_equal "url('#{crazy_uri}')",
"li { list-style: url('#{crazy_uri}') disc }".match(CssParser::RE_URI).to_s
assert_equal "url(#{crazy_uri})",
"li { list-style: url(#{crazy_uri}) disc }".match(CssParser::RE_URI).to_s
assert_equal "url(\"#{crazy_uri}\")",
"li { list-style: url(\"#{crazy_uri}\") disc }".match(CssParser::RE_URI).to_s
end
def test_important
assert_match(CssParser::IMPORTANT_IN_PROPERTY_RX, "color: #f00 !important ;")
assert_no_match(CssParser::IMPORTANT_IN_PROPERTY_RX, "color: #f00 !importantish;")
end
protected
def load_test_file(filename)
fh = File.new("fixtures/#{filename}", 'r')
test_file = fh.read
fh.close
return test_file
end
end
css-parser-1.3.6/test/fixtures/ 0000755 0001750 0001750 00000000000 12542615072 016651 5 ustar terceiro terceiro css-parser-1.3.6/test/fixtures/import-circular-reference.css 0000644 0001750 0001750 00000000147 12542615072 024435 0 ustar terceiro terceiro @import "import-circular-reference.css";
body { color: black; background: white; }
p { margin: 0px; }
css-parser-1.3.6/test/fixtures/subdir/ 0000755 0001750 0001750 00000000000 12542615072 020141 5 ustar terceiro terceiro css-parser-1.3.6/test/fixtures/subdir/import2.css 0000644 0001750 0001750 00000000067 12542615072 022252 0 ustar terceiro terceiro @import "../simple.css";
a { text-decoration: none; }
css-parser-1.3.6/test/fixtures/import1.css 0000644 0001750 0001750 00000000064 12542615072 020756 0 ustar terceiro terceiro @import 'subdir/import2.css';
div { color: lime; }
css-parser-1.3.6/test/fixtures/simple.css 0000644 0001750 0001750 00000000100 12542615072 020643 0 ustar terceiro terceiro body {
color: black;
background: white;
}
p { margin: 0px; }
css-parser-1.3.6/test/fixtures/import-with-media-types.css 0000644 0001750 0001750 00000000076 12542615072 024070 0 ustar terceiro terceiro @import "simple.css" print, tv, screen;
div { color: lime; }
css-parser-1.3.6/test/test_css_parser_media_types.rb 0000644 0001750 0001750 00000010175 12542615072 023117 0 ustar terceiro terceiro require File.expand_path(File.dirname(__FILE__) + '/test_helper')
# Test cases for the handling of media types
class CssParserMediaTypesTests < Test::Unit::TestCase
include CssParser
def setup
@cp = Parser.new
end
def test_that_media_types_dont_include_all
@cp.add_block!(<<-CSS)
@media handheld {
body { color: blue; }
p { color: grey; }
}
@media screen {
body { color: red; }
}
CSS
rules = @cp.rules_by_media_query
assert_equal [ "handheld", "screen" ], rules.keys.map { |k| k.to_s }.sort
end
def test_finding_by_media_type
# from http://www.w3.org/TR/CSS21/media.html#at-media-rule
@cp.add_block!(<<-CSS)
@media print {
body { font-size: 10pt }
}
@media screen {
body { font-size: 13px }
}
@media screen, print {
body { line-height: 1.2 }
}
@media screen, 3d-glasses, print and resolution > 90dpi {
body { color: blue; }
}
CSS
assert_equal 'font-size: 10pt; line-height: 1.2;', @cp.find_by_selector('body', :print).join(' ')
assert_equal 'font-size: 13px; line-height: 1.2; color: blue;', @cp.find_by_selector('body', :screen).join(' ')
assert_equal 'color: blue;', @cp.find_by_selector('body', 'print and resolution > 90dpi'.to_sym).join(' ')
end
def test_finding_by_multiple_media_types
@cp.add_block!(<<-CSS)
@media print {
body { font-size: 10pt }
}
@media handheld {
body { font-size: 13px }
}
@media screen, print {
body { line-height: 1.2 }
}
CSS
assert_equal 'font-size: 13px; line-height: 1.2;', @cp.find_by_selector('body', [:screen,:handheld]).join(' ')
end
def test_adding_block_with_media_types
@cp.add_block!(<<-CSS, :media_types => [:screen])
body { font-size: 10pt }
CSS
assert_equal 'font-size: 10pt;', @cp.find_by_selector('body', :screen).join(' ')
assert @cp.find_by_selector('body', :handheld).empty?
end
def test_adding_block_with_media_types_followed_by_general_rule
@cp.add_block!(<<-CSS)
@media print {
body { font-size: 10pt }
}
body { color: black; }
CSS
assert_match 'color: black;', @cp.to_s
end
def test_adding_block_and_limiting_media_types1
css = <<-CSS
@import "import1.css", print
CSS
base_dir = File.dirname(__FILE__) + '/fixtures/'
@cp.add_block!(css, :only_media_types => :screen, :base_dir => base_dir)
assert @cp.find_by_selector('div').empty?
end
def test_adding_block_and_limiting_media_types2
css = <<-CSS
@import "import1.css", print and (color)
CSS
base_dir = File.dirname(__FILE__) + '/fixtures/'
@cp.add_block!(css, :only_media_types => 'print and (color)', :base_dir => base_dir)
assert_match 'color: lime', @cp.find_by_selector('div').join(' ')
end
def test_adding_block_and_limiting_media_types
css = <<-CSS
@import "import1.css"
CSS
base_dir = File.dirname(__FILE__) + '/fixtures/'
@cp.add_block!(css, :only_media_types => :print, :base_dir => base_dir)
assert_match '', @cp.find_by_selector('div').join(' ')
end
def test_adding_rule_set_with_media_type
@cp.add_rule!('body', 'color: black;', [:handheld,:tty])
@cp.add_rule!('body', 'color: blue;', :screen)
assert_equal 'color: black;', @cp.find_by_selector('body', :handheld).join(' ')
end
def test_adding_rule_set_with_media_query
@cp.add_rule!('body', 'color: black;', 'aural and (device-aspect-ratio: 16/9)')
assert_equal 'color: black;', @cp.find_by_selector('body', 'aural and (device-aspect-ratio: 16/9)').join(' ')
assert_equal 'color: black;', @cp.find_by_selector('body', :all).join(' ')
end
def test_selecting_with_all_media_types
@cp.add_rule!('body', 'color: black;', [:handheld,:tty])
assert_equal 'color: black;', @cp.find_by_selector('body', :all).join(' ')
end
def test_to_s_includes_media_queries
@cp.add_rule!('body', 'color: black;', 'aural and (device-aspect-ratio: 16/9)')
assert_equal "@media aural and (device-aspect-ratio: 16/9) {\n body {\n color: black;\n }\n}\n", @cp.to_s
end
end
css-parser-1.3.6/test/test_css_parser_basic.rb 0000644 0001750 0001750 00000004210 12542615072 021666 0 ustar terceiro terceiro require File.expand_path(File.dirname(__FILE__) + '/test_helper')
# Test cases for reading and generating CSS shorthand properties
class CssParserBasicTests < Test::Unit::TestCase
include CssParser
def setup
@cp = CssParser::Parser.new
@css = <<-CSS
html, body, p { margin: 0px; }
p { padding: 0px; }
#content { font: 12px/normal sans-serif; }
.content { color: red; }
CSS
end
def test_finding_by_selector
@cp.add_block!(@css)
assert_equal 'margin: 0px;', @cp.find_by_selector('body').join(' ')
assert_equal 'margin: 0px; padding: 0px;', @cp.find_by_selector('p').join(' ')
assert_equal 'font: 12px/normal sans-serif;', @cp.find_by_selector('#content').join(' ')
assert_equal 'color: red;', @cp.find_by_selector('.content').join(' ')
end
def test_adding_block
@cp.add_block!(@css)
assert_equal 'margin: 0px;', @cp.find_by_selector('body').join
end
def test_adding_block_without_closing_brace
@cp.add_block!('p { color: red;')
assert_equal 'color: red;', @cp.find_by_selector('p').join
end
def test_adding_a_rule
@cp.add_rule!('div', 'color: blue;')
assert_equal 'color: blue;', @cp.find_by_selector('div').join(' ')
end
def test_adding_a_rule_set
rs = CssParser::RuleSet.new('div', 'color: blue;')
@cp.add_rule_set!(rs)
assert_equal 'color: blue;', @cp.find_by_selector('div').join(' ')
end
def test_toggling_uri_conversion
# with conversion
cp_with_conversion = Parser.new(:absolute_paths => true)
cp_with_conversion.add_block!("body { background: url('../style/yellow.png?abc=123') };",
:base_uri => 'http://example.org/style/basic.css')
assert_equal "background: url('http://example.org/style/yellow.png?abc=123');",
cp_with_conversion['body'].join(' ')
# without conversion
cp_without_conversion = Parser.new(:absolute_paths => false)
cp_without_conversion.add_block!("body { background: url('../style/yellow.png?abc=123') };",
:base_uri => 'http://example.org/style/basic.css')
assert_equal "background: url('../style/yellow.png?abc=123');",
cp_without_conversion['body'].join(' ')
end
end
css-parser-1.3.6/test/test_rule_set_expanding_shorthand.rb 0000644 0001750 0001750 00000023235 12542615072 024322 0 ustar terceiro terceiro require File.expand_path(File.dirname(__FILE__) + '/test_helper')
class RuleSetExpandingShorthandTests < Test::Unit::TestCase
include CssParser
def setup
@cp = CssParser::Parser.new
end
# Dimensions shorthand
def test_expanding_border_shorthand
declarations = expand_declarations('border: none')
assert_equal 'none', declarations['border-right-style']
declarations = expand_declarations('border: 1px solid red')
assert_equal '1px', declarations['border-top-width']
assert_equal 'solid', declarations['border-bottom-style']
declarations = expand_declarations('border-color: red hsla(255, 0, 0, 5) rgb(2% ,2%,2%)')
assert_equal 'red', declarations['border-top-color']
assert_equal 'rgb(2%,2%,2%)', declarations['border-bottom-color']
assert_equal 'hsla(255,0,0,5)', declarations['border-left-color']
declarations = expand_declarations('border: thin dot-dot-dash')
assert_equal 'dot-dot-dash', declarations['border-left-style']
assert_equal 'thin', declarations['border-left-width']
assert_nil declarations['border-left-color']
end
# Dimensions shorthand
def test_getting_dimensions_from_shorthand
# test various shorthand forms
['margin: 0px auto', 'margin: 0px auto 0px', 'margin: 0px auto 0px'].each do |shorthand|
declarations = expand_declarations(shorthand)
assert_equal({"margin-right" => "auto", "margin-bottom" => "0px", "margin-left" => "auto", "margin-top" => "0px"}, declarations)
end
# test various units
['em', 'ex', 'in', 'px', 'pt', 'pc', '%'].each do |unit|
shorthand = "margin: 0% -0.123#{unit} 9px -.9pc"
declarations = expand_declarations(shorthand)
assert_equal({"margin-right" => "-0.123#{unit}", "margin-bottom" => "9px", "margin-left" => "-.9pc", "margin-top" => "0%"}, declarations)
end
end
# Font shorthand
def test_getting_font_size_from_shorthand
['em', 'ex', 'in', 'px', 'pt', 'pc', '%'].each do |unit|
shorthand = "font: 300 italic 11.25#{unit}/14px verdana, helvetica, sans-serif;"
declarations = expand_declarations(shorthand)
assert_equal("11.25#{unit}", declarations['font-size'])
end
['smaller', 'small', 'medium', 'large', 'x-large', 'auto'].each do |unit|
shorthand = "font: 300 italic #{unit}/14px verdana, helvetica, sans-serif;"
declarations = expand_declarations(shorthand)
assert_equal(unit, declarations['font-size'])
end
end
def test_getting_font_families_from_shorthand
shorthand = "font: 300 italic 12px/14px \"Helvetica-Neue-Light 45\", 'verdana', helvetica, sans-serif;"
declarations = expand_declarations(shorthand)
assert_equal("\"Helvetica-Neue-Light 45\", 'verdana', helvetica, sans-serif", declarations['font-family'])
end
def test_getting_font_weight_from_shorthand
['300', 'bold', 'bolder', 'lighter', 'normal'].each do |unit|
shorthand = "font: #{unit} italic 12px sans-serif;"
declarations = expand_declarations(shorthand)
assert_equal(unit, declarations['font-weight'])
end
# ensure normal is the default state
['font: normal italic 12px sans-serif;', 'font: italic 12px sans-serif;',
'font: small-caps normal 12px sans-serif;', 'font: 12px/16px sans-serif;'].each do |shorthand|
declarations = expand_declarations(shorthand)
assert_equal('normal', declarations['font-weight'], shorthand)
end
end
def test_getting_font_variant_from_shorthand
shorthand = "font: small-caps italic 12px sans-serif;"
declarations = expand_declarations(shorthand)
assert_equal('small-caps', declarations['font-variant'])
# ensure normal is the default state
['font: normal italic 12px sans-serif;', 'font: italic 12px sans-serif;',
'font: normal 12px sans-serif;', 'font: 12px/16px sans-serif;'].each do |shorthand|
declarations = expand_declarations(shorthand)
assert_equal('normal', declarations['font-variant'], shorthand)
end
end
def test_getting_font_style_from_shorthand
['italic', 'oblique'].each do |unit|
shorthand = "font: normal #{unit} bold 12px sans-serif;"
declarations = expand_declarations(shorthand)
assert_equal(unit, declarations['font-style'])
end
# ensure normal is the default state
['font: normal bold 12px sans-serif;', 'font: small-caps 12px sans-serif;',
'font: normal 12px sans-serif;', 'font: 12px/16px sans-serif;'].each do |shorthand|
declarations = expand_declarations(shorthand)
assert_equal('normal', declarations['font-style'], shorthand)
end
end
def test_getting_line_height_from_shorthand
['em', 'ex', 'in', 'px', 'pt', 'pc', '%'].each do |unit|
shorthand = "font: 300 italic 12px/0.25#{unit} verdana, helvetica, sans-serif;"
declarations = expand_declarations(shorthand)
assert_equal("0.25#{unit}", declarations['line-height'])
end
# ensure normal is the default state
['font: normal bold 12px sans-serif;', 'font: small-caps 12px sans-serif;',
'font: normal 12px sans-serif;', 'font: 12px sans-serif;'].each do |shorthand|
declarations = expand_declarations(shorthand)
assert_equal('normal', declarations['line-height'], shorthand)
end
end
# Background shorthand
def test_getting_background_properties_from_shorthand
expected = {"background-image" => "url('chess.png')", "background-color" => "gray", "background-repeat" => "repeat",
"background-attachment" => "fixed", "background-position" => "50%"}
shorthand = "background: url('chess.png') gray 50% repeat fixed;"
declarations = expand_declarations(shorthand)
assert_equal expected, declarations
end
def test_getting_background_position_from_shorthand
['em', 'ex', 'in', 'px', 'pt', 'pc', '%'].each do |unit|
shorthand = "background: url('chess.png') gray 30% -0.15#{unit} repeat fixed;"
declarations = expand_declarations(shorthand)
assert_equal("30% -0.15#{unit}", declarations['background-position'])
end
['left', 'center', 'right', 'top', 'bottom', 'inherit'].each do |position|
shorthand = "background: url('chess.png') #000fff #{position} no-repeat fixed;"
declarations = expand_declarations(shorthand)
assert_equal(position, declarations['background-position'])
end
end
def test_getting_background_colour_from_shorthand
['blue', 'lime', 'rgb(10,10,10)', 'rgb ( -10%, 99, 300)', '#ffa0a0', '#03c', 'trAnsparEnt', 'inherit'].each do |colour|
shorthand = "background:#{colour} url('chess.png') center repeat fixed ;"
declarations = expand_declarations(shorthand)
assert_equal(colour, declarations['background-color'])
end
end
def test_getting_background_attachment_from_shorthand
['scroll', 'fixed', 'inherit'].each do |attachment|
shorthand = "background:#0f0f0f url('chess.png') center repeat #{attachment};"
declarations = expand_declarations(shorthand)
assert_equal(attachment, declarations['background-attachment'])
end
end
def test_getting_background_repeat_from_shorthand
['repeat-x', 'repeat-y', 'no-repeat', 'inherit'].each do |repeat|
shorthand = "background:#0f0f0f none #{repeat};"
declarations = expand_declarations(shorthand)
assert_equal(repeat, declarations['background-repeat'])
end
end
def test_getting_background_image_from_shorthand
['url("chess.png")', 'url("https://example.org:80/~files/chess.png?123=abc&test#5")',
'url(https://example.org:80/~files/chess.png?123=abc&test#5)',
"url('https://example.org:80/~files/chess.png?123=abc&test#5')", 'none', 'inherit'].each do |image|
shorthand = "background: #0f0f0f #{image} ;"
declarations = expand_declarations(shorthand)
assert_equal(image, declarations['background-image'])
end
end
def test_getting_background_gradient_from_shorthand
['linear-gradient(top, hsla(0, 0%, 0%, 0.00) 0%, hsla(0, 0%, 0%, 0.20) 100%)',
'-webkit-gradient(linear, left top, left bottom, color-stop(0, hsla(0, 0%, 0%, 0.00)), color-stop(1, hsla(0, 0%, 0%, 0.20)))',
'-moz-linear-gradient(bottom, blue, red)'
].each do |image|
shorthand = "background: #0f0f0f #{image} repeat ;"
declarations = expand_declarations(shorthand)
assert_equal(image, declarations['background-image'])
end
end
# List-style shorthand
def test_getting_list_style_properties_from_shorthand
expected = {'list-style-image' => 'url(\'chess.png\')', 'list-style-type' => 'katakana',
'list-style-position' => 'inside'}
shorthand = "list-style: katakana inside url(\'chess.png\');"
declarations = expand_declarations(shorthand)
assert_equal expected, declarations
end
def test_getting_list_style_position_from_shorthand
['inside', 'outside'].each do |position|
shorthand = "list-style: katakana #{position} url('chess.png');"
declarations = expand_declarations(shorthand)
assert_equal(position, declarations['list-style-position'])
end
end
def test_getting_list_style_type_from_shorthand
['disc', 'circle', 'square', 'decimal', 'decimal-leading-zero', 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', 'hiragana', 'katakana', 'hira-gana-iroha', 'katakana-iroha', 'none'].each do |type|
shorthand = "list-style: #{type} inside url('chess.png');"
declarations = expand_declarations(shorthand)
assert_equal(type, declarations['list-style-type'])
end
end
protected
def expand_declarations(declarations)
ruleset = RuleSet.new(nil, declarations)
ruleset.expand_shorthand!
collected = {}
ruleset.each_declaration do |prop, val, imp|
collected[prop.to_s] = val.to_s
end
collected
end
end
css-parser-1.3.6/test/test_rule_set_creating_shorthand.rb 0000644 0001750 0001750 00000012102 12542615072 024130 0 ustar terceiro terceiro require File.expand_path(File.dirname(__FILE__) + '/test_helper')
# Test cases for reading and generating CSS shorthand properties
class RuleSetCreatingShorthandTests < Test::Unit::TestCase
include CssParser
def setup
@cp = CssParser::Parser.new
end
# Border shorthand
def test_combining_borders_into_shorthand
properties = {'border-top-width' => 'auto', 'border-right-width' => 'thin', 'border-bottom-width' => 'auto', 'border-left-width' => '0px'}
combined = create_shorthand(properties)
assert_equal('', combined['border'])
assert_equal('auto thin auto 0px;', combined['border-width'])
# after creating shorthand, all long-hand properties should be deleted
assert_properties_are_deleted(combined, properties)
# should not combine if any properties are missing
properties.delete('border-top-width')
combined = create_shorthand(properties)
assert_equal '', combined['border-width']
properties = {'border-width' => '22%', 'border-color' => 'rgba(255, 0, 0)'}
combined = create_shorthand(properties)
assert_equal '22% rgba(255, 0, 0);', combined['border']
assert_equal '', combined['border-width']
properties = {'border-top-style' => 'none', 'border-right-style' => 'none', 'border-bottom-style' => 'none', 'border-left-style' => 'none'}
combined = create_shorthand(properties)
assert_equal 'none;', combined['border']
end
# Dimensions shorthand
def test_combining_dimensions_into_shorthand
properties = {'margin-right' => 'auto', 'margin-bottom' => '0px', 'margin-left' => 'auto', 'margin-top' => '0px',
'padding-right' => '1.25em', 'padding-bottom' => '11%', 'padding-left' => '3pc', 'padding-top' => '11.25ex'}
combined = create_shorthand(properties)
assert_equal('0px auto;', combined['margin'])
assert_equal('11.25ex 1.25em 11% 3pc;', combined['padding'])
# after creating shorthand, all long-hand properties should be deleted
assert_properties_are_deleted(combined, properties)
# should not combine if any properties are missing
properties.delete('margin-right')
properties.delete('padding-right')
combined = create_shorthand(properties)
assert_equal '', combined['margin']
assert_equal '', combined['padding']
end
# Dimensions shorthand, auto property
def test_combining_dimensions_into_shorthand_with_auto
rs = RuleSet.new('#page', "margin: 0; margin-left: auto; margin-right: auto;")
rs.expand_shorthand!
assert_equal('auto;', rs['margin-left'])
rs.create_shorthand!
assert_equal('0 auto;', rs['margin'])
end
# Font shorthand
def test_combining_font_into_shorthand
# should combine if all font properties are present
properties = {"font-weight" => "300", "font-size" => "12pt",
"font-family" => "sans-serif", "line-height" => "18px",
"font-style" => "oblique", "font-variant" => "small-caps"}
combined = create_shorthand(properties)
assert_equal('oblique small-caps 300 12pt/18px sans-serif;', combined['font'])
# after creating shorthand, all long-hand properties should be deleted
assert_properties_are_deleted(combined, properties)
# should not combine if any properties are missing
properties.delete('font-weight')
combined = create_shorthand(properties)
assert_equal '', combined['font']
end
# Background shorthand
def test_combining_background_into_shorthand
properties = {'background-image' => 'url(\'chess.png\')', 'background-color' => 'gray',
'background-position' => 'center -10.2%', 'background-attachment' => 'fixed',
'background-repeat' => 'no-repeat'}
combined = create_shorthand(properties)
assert_equal('gray url(\'chess.png\') no-repeat center -10.2% fixed;', combined['background'])
# after creating shorthand, all long-hand properties should be deleted
assert_properties_are_deleted(combined, properties)
end
# List-style shorthand
def test_combining_list_style_into_shorthand
properties = {'list-style-image' => 'url(\'chess.png\')', 'list-style-type' => 'katakana',
'list-style-position' => 'inside'}
combined = create_shorthand(properties)
assert_equal('katakana inside url(\'chess.png\');', combined['list-style'])
# after creating shorthand, all long-hand properties should be deleted
assert_properties_are_deleted(combined, properties)
end
def test_property_values_in_url
rs = RuleSet.new('#header', "background:url(http://example.com/1528/www/top-logo.jpg) no-repeat top right; padding: 79px 0 10px 0; text-align:left;")
rs.expand_shorthand!
assert_equal('top right;', rs['background-position'])
rs.create_shorthand!
assert_equal('url(http://example.com/1528/www/top-logo.jpg) no-repeat top right;', rs['background'])
end
protected
def assert_properties_are_deleted(ruleset, properties)
properties.each do |property, value|
assert_equal '', ruleset[property]
end
end
def create_shorthand(properties)
ruleset = RuleSet.new(nil, nil)
properties.each do |property, value|
ruleset[property] = value
end
ruleset.create_shorthand!
ruleset
end
end
css-parser-1.3.6/test/test_css_parser_misc.rb 0000644 0001750 0001750 00000013467 12542615072 021556 0 ustar terceiro terceiro require File.expand_path(File.dirname(__FILE__) + '/test_helper')
# Test cases for the CssParser.
class CssParserTests < Test::Unit::TestCase
include CssParser
def setup
@cp = Parser.new
end
def test_at_page_rule
# from http://www.w3.org/TR/CSS21/page.html#page-selectors
css = <<-CSS
@page { margin: 2cm }
@page :first {
margin-top: 10cm
}
CSS
@cp.add_block!(css)
assert_equal 'margin: 2cm;', @cp.find_by_selector('@page').join(' ')
assert_equal 'margin-top: 10cm;', @cp.find_by_selector('@page :first').join(' ')
end
def test_should_ignore_comments
# see http://www.w3.org/Style/CSS/Test/CSS2.1/current/html4/t040109-c17-comments-00-b.htm
css =<<-CSS
/* This is a CSS comment. */
.one {color: green;} /* Another comment */
/* The following should not be used:
.one {color: red;} */
.two {color: green; /* color: yellow; */}
/**
.three {color: red;} */
.three {color: green;}
/**/
.four {color: green;}
/*********/
.five {color: green;}
/* a comment **/
.six {color: green;}
CSS
@cp.add_block!(css)
@cp.each_selector do |sel, decs, spec|
assert_equal 'color: green;', decs
end
end
def test_parsing_blocks
# dervived from http://www.w3.org/TR/CSS21/syndata.html#rule-sets
css = <<-CSS
div[name='test'] {
color:
red;
}div:hover{coloR:red;
}div:first-letter{color:red;/*color:blue;}"commented out"*/}
p[example="public class foo\
{\
private string x;\
\
foo(int x) {\
this.x = 'test';\
this.x = \"test\";\
}\
\
}"] { color: red }
p { color:red}
CSS
@cp.add_block!(css)
@cp.each_selector do |sel, decs, spec|
assert_equal 'color: red;', decs
end
end
def test_ignoring_malformed_declarations
# dervived from http://www.w3.org/TR/CSS21/syndata.html#parsing-errors
css = <<-CSS
p { color:green }
p { color:green; color } /* malformed declaration missing ':', value */
p { color:red; color; color:green } /* same with expected recovery */
p { color:green; color: } /* malformed declaration missing value */
p { color:red; color:; color:green } /* same with expected recovery */
p { color:green; color{;color:maroon} } /* unexpected tokens { } */
p { color:red; color{;color:maroon}; color:green } /* same with recovery */
CSS
@cp.add_block!(css)
@cp.each_selector do |sel, decs, spec|
assert_equal 'color: green;', decs
end
end
def test_find_rule_sets
css = <<-CSS
h1, h2 { color: blue; }
h1 { font-size: 10px; }
h2 { font-size: 5px; }
CSS
@cp.add_block!(css)
assert_equal 2, @cp.find_rule_sets(["h2"]).size
assert_equal 3, @cp.find_rule_sets(["h1", "h2"]).size
end
def test_calculating_specificity
# from http://www.w3.org/TR/CSS21/cascade.html#specificity
assert_equal 0, CssParser.calculate_specificity('*')
assert_equal 1, CssParser.calculate_specificity('li')
assert_equal 2, CssParser.calculate_specificity('li:first-line')
assert_equal 2, CssParser.calculate_specificity('ul li')
assert_equal 3, CssParser.calculate_specificity('ul ol+li')
assert_equal 11, CssParser.calculate_specificity('h1 + *[rel=up]')
assert_equal 13, CssParser.calculate_specificity('ul ol li.red')
assert_equal 21, CssParser.calculate_specificity('li.red.level')
assert_equal 100, CssParser.calculate_specificity('#x34y')
# from http://www.hixie.ch/tests/adhoc/css/cascade/specificity/003.html
assert_equal CssParser.calculate_specificity('div *'), CssParser.calculate_specificity('p')
assert CssParser.calculate_specificity('body div *') > CssParser.calculate_specificity('div *')
# other tests
assert_equal 11, CssParser.calculate_specificity('h1[id|=123]')
end
def test_converting_uris
base_uri = 'http://www.example.org/style/basic.css'
["body { background: url(yellow) };", "body { background: url('yellow') };",
"body { background: url('/style/yellow') };",
"body { background: url(\"../style/yellow\") };",
"body { background: url(\"lib/../../style/yellow\") };"].each do |css|
converted_css = CssParser.convert_uris(css, base_uri)
assert_equal "body { background: url('http://www.example.org/style/yellow') };", converted_css
end
converted_css = CssParser.convert_uris("body { background: url(../style/yellow-dot_symbol$.png?abc=123&def=456&ghi=789#1011) };", base_uri)
assert_equal "body { background: url('http://www.example.org/style/yellow-dot_symbol$.png?abc=123&def=456&ghi=789#1011') };", converted_css
# taken from error log: 2007-10-23 04:37:41#2399
converted_css = CssParser.convert_uris('.specs {font-family:Helvetica;font-weight:bold;font-style:italic;color:#008CA8;font-size:1.4em;list-style-image:url("images/bullet.gif");}', 'http://www.example.org/directory/file.html')
assert_equal ".specs {font-family:Helvetica;font-weight:bold;font-style:italic;color:#008CA8;font-size:1.4em;list-style-image:url('http://www.example.org/directory/images/bullet.gif');}", converted_css
end
def test_ruleset_with_braces
=begin
parser = Parser.new
parser.add_block!("div { background-color: black !important; }")
parser.add_block!("div { background-color: red; }")
rulesets = []
parser['div'].each do |declaration|
rulesets << RuleSet.new('div', declaration)
end
merged = CssParser.merge(rulesets)
result: # merged.to_s => "{ background-color: black !important; }"
=end
new_rule = RuleSet.new('div', "{ background-color: black !important; }")
assert_equal 'div { background-color: black !important; }', new_rule.to_s
end
end
css-parser-1.3.6/test/test_merging.rb 0000644 0001750 0001750 00000007654 12542615072 020030 0 ustar terceiro terceiro require File.expand_path(File.dirname(__FILE__) + '/test_helper')
class MergingTests < Test::Unit::TestCase
include CssParser
def setup
@cp = CssParser::Parser.new
end
def test_simple_merge
rs1 = RuleSet.new(nil, 'color: black;')
rs2 = RuleSet.new(nil, 'margin: 0px;')
merged = CssParser.merge(rs1, rs2)
assert_equal '0px;', merged['margin']
assert_equal 'black;', merged['color']
end
def test_merging_array
rs1 = RuleSet.new(nil, 'color: black;')
rs2 = RuleSet.new(nil, 'margin: 0px;')
merged = CssParser.merge([rs1, rs2])
assert_equal '0px;', merged['margin']
assert_equal 'black;', merged['color']
end
def test_merging_with_compound_selectors
@cp.add_block! "body { margin: 0; }"
@cp.add_block! "h2 { margin: 5px; }"
rules = @cp.find_rule_sets([ "body", "h2" ])
assert_equal "margin: 5px;", CssParser.merge(rules).declarations_to_s
@cp = CssParser::Parser.new
@cp.add_block! "body { margin: 0; }"
@cp.add_block! "h2,h1 { margin: 5px; }"
rules = @cp.find_rule_sets([ "body", "h2" ])
assert_equal "margin: 5px;", CssParser.merge(rules).declarations_to_s
end
def test_merging_multiple
rs1 = RuleSet.new(nil, 'color: black;')
rs2 = RuleSet.new(nil, 'margin: 0px;')
rs3 = RuleSet.new(nil, 'margin: 5px;')
merged = CssParser.merge(rs1, rs2, rs3)
assert_equal '5px;', merged['margin']
end
def test_multiple_selectors_should_have_proper_specificity
rs1 = RuleSet.new('p, a[rel="external"]', 'color: black;')
rs2 = RuleSet.new('a', 'color: blue;')
merged = CssParser.merge(rs1, rs2)
assert_equal 'black;', merged['color']
end
def test_setting_specificity
rs1 = RuleSet.new(nil, 'color: red;', 20)
rs2 = RuleSet.new(nil, 'color: blue;', 10)
merged = CssParser.merge(rs1, rs2)
assert_equal 'red;', merged['color']
end
def test_properties_should_be_case_insensitive
rs1 = RuleSet.new(nil, ' CoLor : red ;', 20)
rs2 = RuleSet.new(nil, 'color: blue;', 10)
merged = CssParser.merge(rs1, rs2)
assert_equal 'red;', merged['color']
end
def test_merging_backgrounds
rs1 = RuleSet.new(nil, 'background-color: black;')
rs2 = RuleSet.new(nil, 'background-image: none;')
merged = CssParser.merge(rs1, rs2)
assert_equal 'black none;', merged['background']
end
def test_merging_dimensions
rs1 = RuleSet.new(nil, 'margin: 3em;')
rs2 = RuleSet.new(nil, 'margin-left: 1em;')
merged = CssParser.merge(rs1, rs2)
assert_equal '3em 3em 3em 1em;', merged['margin']
end
def test_merging_fonts
rs1 = RuleSet.new(nil, 'font: 11px Arial;')
rs2 = RuleSet.new(nil, 'font-weight: bold;')
merged = CssParser.merge(rs1, rs2)
assert_equal 'bold 11px Arial;', merged['font']
end
def test_raising_error_on_bad_type
assert_raise ArgumentError do
CssParser.merge([1,2,3])
end
end
def test_returning_early_with_only_one_params
rs = RuleSet.new(nil, 'font-weight: bold;')
merged = CssParser.merge(rs)
assert_equal rs.object_id, merged.object_id
end
def test_merging_important
rs1 = RuleSet.new(nil, 'color: black !important;')
rs2 = RuleSet.new(nil, 'color: red;')
merged = CssParser.merge(rs1, rs2)
assert_equal 'black !important;', merged['color']
end
def test_merging_multiple_important
rs1 = RuleSet.new(nil, 'color: black !important;', 1000)
rs2 = RuleSet.new(nil, 'color: red !important;', 1)
merged = CssParser.merge(rs1, rs2)
assert_equal 'black !important;', merged['color']
rs3 = RuleSet.new(nil, 'color: blue !important;', 1000)
merged = CssParser.merge(rs1, rs2, rs3)
assert_equal 'blue !important;', merged['color']
end
def test_merging_shorthand_important
rs1 = RuleSet.new(nil, 'background: black none !important;')
rs2 = RuleSet.new(nil, 'background-color: red;')
merged = CssParser.merge(rs1, rs2)
assert_equal 'black !important;', merged['background-color']
end
end
css-parser-1.3.6/test/test_rule_set.rb 0000644 0001750 0001750 00000006725 12542615072 020220 0 ustar terceiro terceiro require File.expand_path(File.dirname(__FILE__) + '/test_helper')
require "set"
# Test cases for parsing CSS blocks
class RuleSetTests < Test::Unit::TestCase
include CssParser
def setup
@cp = Parser.new
end
def test_setting_property_values
rs = RuleSet.new(nil, nil)
rs['background-color'] = 'red'
assert_equal('red;', rs['background-color'])
rs['background-color'] = 'blue !important;'
assert_equal('blue !important;', rs['background-color'])
end
def test_getting_property_values
rs = RuleSet.new('#content p, a', 'color: #fff;')
assert_equal('#fff;', rs['color'])
end
def test_getting_property_value_ignoring_case
rs = RuleSet.new('#content p, a', 'color: #fff;')
assert_equal('#fff;', rs[' ColoR '])
end
def test_each_selector
expected = [
{:selector => "#content p", :declarations => "color: #fff;", :specificity => 101},
{:selector => "a", :declarations => "color: #fff;", :specificity => 1}
]
actual = []
rs = RuleSet.new('#content p, a', 'color: #fff;')
rs.each_selector do |sel, decs, spec|
actual << {:selector => sel, :declarations => decs, :specificity => spec}
end
assert_equal(expected, actual)
end
def test_each_declaration
expected = Set.new([
{:property => 'margin', :value => '1px -0.25em', :is_important => false},
{:property => 'background', :value => 'white none no-repeat', :is_important => true},
{:property => 'color', :value => '#fff', :is_important => false}
])
actual = Set.new
rs = RuleSet.new(nil, 'color: #fff; Background: white none no-repeat !important; margin: 1px -0.25em;')
rs.each_declaration do |prop, val, imp|
actual << {:property => prop, :value => val, :is_important => imp}
end
assert_equal(expected, actual)
end
def test_each_declaration_respects_order
css_fragment = "margin: 0; padding: 20px; margin-bottom: 28px;"
rs = RuleSet.new(nil, css_fragment)
expected = %w(margin padding margin-bottom)
actual = []
rs.each_declaration { |prop, val, imp| actual << prop }
assert_equal(expected, actual)
end
def test_each_declaration_containing_semicolons
rs = RuleSet.new(nil, "background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAiCAMAAAB7);" +
"background-repeat: no-repeat")
assert_equal('url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAiCAMAAAB7);', rs['background-image'])
assert_equal('no-repeat;', rs['background-repeat'])
end
def test_selector_sanitization
selectors = "h1, h2,\nh3 "
rs = RuleSet.new(selectors, "color: #fff;")
assert rs.selectors.member?("h3")
end
def test_multiple_selectors_to_s
selectors = "#content p, a"
rs = RuleSet.new(selectors, "color: #fff;")
assert_match(/^\s*#content p,\s*a\s*\{/, rs.to_s)
end
def test_declarations_to_s
declarations = 'color: #fff; font-weight: bold;'
rs = RuleSet.new('#content p, a', declarations)
assert_equal(declarations.split(' ').sort, rs.declarations_to_s.split(' ').sort)
end
def test_important_declarations_to_s
declarations = 'color: #fff; font-weight: bold !important;'
rs = RuleSet.new('#content p, a', declarations)
assert_equal(declarations.split(' ').sort, rs.declarations_to_s.split(' ').sort)
end
def test_overriding_specificity
rs = RuleSet.new('#content p, a', 'color: white', 1000)
rs.each_selector do |sel, decs, spec|
assert_equal 1000, spec
end
end
end
css-parser-1.3.6/Gemfile 0000644 0001750 0001750 00000000212 12542615072 015307 0 ustar terceiro terceiro source "https://rubygems.org"
gemspec
gem 'rake'
gem 'bump'
gem 'test-unit', '>= 2.5.3'
platforms :jruby do
gem 'jruby-openssl'
end
css-parser-1.3.6/css_parser.gemspec 0000644 0001750 0001750 00000000725 12542615072 017536 0 ustar terceiro terceiro $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
name = "css_parser"
require "#{name}/version"
Gem::Specification.new name, CssParser::VERSION.dup do |s|
s.summary = "Ruby CSS parser."
s.description = "A set of classes for parsing CSS in Ruby."
s.email = "code@dunae.ca"
s.homepage = "https://github.com/premailer/#{name}"
s.author = "Alex Dunae"
s.add_runtime_dependency 'addressable'
s.files = Dir.glob("lib/**/*")
s.license = "MIT"
end
css-parser-1.3.6/Rakefile 0000644 0001750 0001750 00000000276 12542615072 015473 0 ustar terceiro terceiro require 'bundler/setup'
require 'bundler/gem_tasks'
require 'rake/testtask'
require 'bump/tasks'
desc 'Run the unit tests.'
Rake::TestTask.new(:default) do |test|
test.verbose = true
end