Module: CssParser
- Defined in:
- lib/css_parser.rb,
lib/css_parser/parser.rb,
lib/css_parser/regexps.rb,
lib/css_parser/version.rb,
lib/css_parser/rule_set.rb
Defined Under Namespace
Classes: CircularReferenceError, Parser, RemoteFileError, RuleSet
Constant Summary collapse
- RE_NL =
:stopdoc: Base types
Regexp.new('(\n|\r\n|\r|\f)')
- RE_NON_ASCII =
- ^0-177
Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE | Regexp::NOENCODING)
- RE_UNICODE =
Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE | Regexp::NOENCODING)
- 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 | Regexp::NOENCODING)
- RE_STRING1 =
General strings
/("(.[^\n\r\f"]*|\\#{RE_NL}|#{RE_ESCAPE})*")/.freeze
- RE_STRING2 =
/('(.[^\n\r\f']*|\\#{RE_NL}|#{RE_ESCAPE})*')/.freeze
- RE_STRING =
Regexp.union(RE_STRING1, RE_STRING2)
- RE_INHERIT =
regex_possible_values 'inherit'
- RE_URI =
/(url\(\s*(\s*#{RE_STRING}\s*)\s*\))|(url\(\s*([!#$%&*\-~]|#{RE_NON_ASCII}|#{RE_ESCAPE})*\s*)\)/ixm.freeze
- URI_RX =
/url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im.freeze
- URI_RX_OR_NONE =
Regexp.union(URI_RX, /none/i)
- RE_GRADIENT =
/[-a-z]*gradient\([-a-z0-9 .,#%()]*\)/im.freeze
- RE_AT_IMPORT_RULE =
Initial parsing
/@import\s+(url\()?["']?(.[^'"\s]*)["']?\)?([\w\s,^\])]*)\)?;?/.freeze
- IMPORTANT_IN_PROPERTY_RX =
RE_AT_IMPORT_RULE = Regexp.new(‘@import*(’ + RE_STRING.to_s + ‘)([ws,]*)?’, Regexp::IGNORECASE) – should handle url() even though it is not allowed ++
/\s*!important\b\s*/i.freeze
- 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' )
- RE_IMAGE =
Regexp.union(CssParser::URI_RX, CssParser::RE_GRADIENT, /none/i)
- STRIP_CSS_COMMENTS_RX =
%r{/\*.*?\*/}m.freeze
- STRIP_HTML_COMMENTS_RX =
/<!--|-->/m.freeze
- BOX_MODEL_UNITS_RX =
Special units
/(auto|inherit|0|(-*([0-9]+|[0-9]*\.[0-9]+)(rem|vw|vh|vm|vmin|vmax|e[mx]+|px|[cm]+m|p[tc+]|in|%)))([\s;]|\Z)/imx.freeze
- RE_LENGTH_OR_PERCENTAGE =
Regexp.new('([\-]*(([0-9]*\.[0-9]+)|[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%))', Regexp::IGNORECASE)
- RE_SINGLE_BACKGROUND_POSITION =
/#{RE_LENGTH_OR_PERCENTAGE}|left|center|right|top|bottom/i.freeze
- RE_SINGLE_BACKGROUND_SIZE =
/#{RE_LENGTH_OR_PERCENTAGE}|auto|cover|contain|initial|inherit/i.freeze
- RE_BACKGROUND_POSITION =
/#{RE_SINGLE_BACKGROUND_POSITION}\s+#{RE_SINGLE_BACKGROUND_POSITION}|#{RE_SINGLE_BACKGROUND_POSITION}/.freeze
- RE_BACKGROUND_SIZE =
%r{\s*/\s*(#{RE_SINGLE_BACKGROUND_SIZE}\s+#{RE_SINGLE_BACKGROUND_SIZE}|#{RE_SINGLE_BACKGROUND_SIZE})}.freeze
- FONT_UNITS_RX =
/((x+-)*small|medium|larger*|auto|inherit|([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|%)*)/i.freeze
- RE_BORDER_STYLE =
/(\s*^)?(none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset)(\s*$)?/imx.freeze
- RE_BORDER_UNITS =
Regexp.union(BOX_MODEL_UNITS_RX, /(thin|medium|thick)/i)
- RE_FUNCTIONS =
Functions like calc, var, clamp, etc.
/ ( [a-z0-9-]+ # function name ) (?> \( # opening parenthesis (?: ([^()]+) | # recursion via subexpression \g<0> )* \) # closing parenthesis ) /imx.freeze
- NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC =
Patterns for specificity calculations
/ (?:\.\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.freeze
- ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC =
/ (?:(?:^|[\s+>~]+)\w+ # elements | :{1,2}(?: # pseudo-elements after|before |first-letter|first-line |selection ) )/ix.freeze
- NAMED_COLOURS =
Colours
%w[ aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen transparent inherit currentColor ].freeze
- RE_COLOUR_NUMERIC =
/\b(hsl|rgb)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i.freeze
- RE_COLOUR_NUMERIC_ALPHA =
/\b(hsla|rgba)\s*\(-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?,-?\s*-?\d+(\.\d+)?%?\s*%?\)/i.freeze
- RE_COLOUR_HEX =
/\s*#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/.freeze
- RE_COLOUR_NAMED =
/\s*\b(#{NAMED_COLOURS.join('|')})\b/i.freeze
- RE_COLOUR =
Regexp.union(RE_COLOUR_NUMERIC, RE_COLOUR_NUMERIC_ALPHA, RE_COLOUR_HEX, RE_COLOUR_NAMED)
- VERSION =
'1.19.1'.freeze
Class Method Summary collapse
-
.calculate_specificity(selector) ⇒ Object
Calculates the specificity of a CSS selector per www.w3.org/TR/CSS21/cascade.html#specificity.
-
.convert_uris(css, base_uri) ⇒ Object
Make
url()
links absolute. -
.merge(*rule_sets) ⇒ Object
Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules (www.w3.org/TR/REC-CSS2/cascade.html#cascading-order).
- .regex_possible_values(*values) ⇒ Object
- .sanitize_media_query(raw) ⇒ Object
Class Method Details
.calculate_specificity(selector) ⇒ Object
Calculates the specificity of a CSS selector per 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. ++
112 113 114 115 116 117 118 119 120 121 |
# File 'lib/css_parser.rb', line 112 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 |
.convert_uris(css, base_uri) ⇒ Object
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 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') };"
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/css_parser.rb', line 136 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 |
.merge(*rule_sets) ⇒ Object
Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules (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
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/css_parser.rb', line 55 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].is_a?(Array) unless rule_sets.all? { |rs| rs.is_a?(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. specificity = rule_set.specificity specificity ||= rule_set.selectors.map { |s| calculate_specificity(s) }.compact.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 |
.regex_possible_values(*values) ⇒ Object
4 5 6 |
# File 'lib/css_parser/regexps.rb', line 4 def self.regex_possible_values(*values) Regexp.new("([\s]*^)?(#{values.join('|')})([\s]*$)?", 'i') end |
.sanitize_media_query(raw) ⇒ Object
153 154 155 156 157 158 |
# File 'lib/css_parser.rb', line 153 def self.sanitize_media_query(raw) mq = raw.to_s.gsub(/\s+/, ' ') mq.strip! mq = 'all' if mq.empty? mq.to_sym end |