pax_global_header 0000666 0000000 0000000 00000000064 14474270710 0014520 g ustar 00root root 0000000 0000000 52 comment=8167588ea302df8a5ff7917e875f9f59fcf2b76b
css_parser-1.16.0/ 0000775 0000000 0000000 00000000000 14474270710 0013751 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/.editorconfig 0000664 0000000 0000000 00000000246 14474270710 0016430 0 ustar 00root root 0000000 0000000 # editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
css_parser-1.16.0/.github/ 0000775 0000000 0000000 00000000000 14474270710 0015311 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000000142 14474270710 0021107 0 ustar 00root root 0000000 0000000 Why and what is being done.
## Pre-Merge Checklist
- [ ] CHANGELOG.md updated with short summary
css_parser-1.16.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14474270710 0017346 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000001353 14474270710 0020466 0 ustar 00root root 0000000 0000000 name: Run css_parser CI
on:
pull_request:
push:
branches:
- master
jobs:
test:
name: Test ruby version matrix
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.7', '3.0', '3.1', '3.2', 'jruby-head']
steps:
- uses: actions/checkout@v2
- run: rm Gemfile.lock
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
- run: bundle exec rake test
rubocop:
name: Run rubocop
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
bundler-cache: true
- run: bundle exec rake rubocop
css_parser-1.16.0/.gitignore 0000664 0000000 0000000 00000000032 14474270710 0015734 0 ustar 00root root 0000000 0000000 /pkg/
/bin/
.ruby-version
css_parser-1.16.0/.jrubyrc 0000664 0000000 0000000 00000000022 14474270710 0015424 0 ustar 00root root 0000000 0000000 cext.enabled=true
css_parser-1.16.0/.rubocop.yml 0000664 0000000 0000000 00000002077 14474270710 0016231 0 ustar 00root root 0000000 0000000 AllCops:
TargetRubyVersion: 2.7 # lowest supported version
NewCops: enable
Layout/ArgumentAlignment:
EnforcedStyle: with_fixed_indentation
Layout/AccessModifierIndentation:
EnforcedStyle: outdent
Layout/LineLength:
Enabled: false
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: no_space
Metrics/AbcSize:
Enabled: false
Metrics/BlockLength:
Enabled: false
Metrics/BlockNesting:
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/MethodLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
Style/AndOr:
Enabled: false
Style/Documentation:
Enabled: false
Style/IfUnlessModifier:
Enabled: false
Style/Not:
Enabled: false
Style/NumericPredicate:
Enabled: false
Style/RedundantFreeze:
Enabled: false
Style/RescueStandardError:
EnforcedStyle: implicit
Style/SafeNavigation:
Enabled: false
Style/StringLiterals:
Exclude:
- 'test/**/*'
Style/WordArray:
Enabled: false
Style/SymbolArray:
EnforcedStyle: brackets
css_parser-1.16.0/.vscode/ 0000775 0000000 0000000 00000000000 14474270710 0015312 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/.vscode/tasks.json 0000664 0000000 0000000 00000001601 14474270710 0017330 0 ustar 00root root 0000000 0000000 {
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "0.1.0",
"tasks": [
{
"taskName": "Bundle dependencies and stubs",
"command": "bundle",
"isShellCommand": true,
"isBackground": true,
"args": ["--binstubs"],
"showOutput": "silent"
},
{
"taskName": "Test",
"command": "${workspaceRoot}/bin/rake",
"isTestCommand": true,
"isShellCommand": false,
"args": [],
"showOutput": "always"
},
{
"taskName": "Benchmark",
"command": "${workspaceRoot}/bin/rake",
"isShellCommand": false,
"args": [
"benchmark"
],
"showOutput": "always"
}
]
} css_parser-1.16.0/CHANGELOG.md 0000664 0000000 0000000 00000013563 14474270710 0015572 0 ustar 00root root 0000000 0000000 ## Ruby CSS Parser CHANGELOG
### Unreleased
### Version v1.15.0
* Fix parsing space-less media query features like `@media(width:123px)` [#141](https://github.com/premailer/css_parser/pull/141)
### Version v1.15.0
* Fix parsing background shorthands in ruby 3.2 [#140](https://github.com/premailer/css_parser/pull/140)
### Version v1.14.0
* Fix parsing of multiline URL values for rule sets [#97](https://github.com/premailer/css_parser/pull/97)
### Version v1.13.0
* Drop suppor for EOL ruby versions
* fix regex deprecation
### Version v1.12.0
* Improve exception message for missing value [#131](https://github.com/premailer/css_parser/pull/131)
* `:rule_set_exceptions` option added [#132](https://github.com/premailer/css_parser/pull/132)
### Version 1.11.0
* Do not combine border styles width/color/style are not all present
### Version 1.10.0
* Allow CSS functions to be used in CssParser::RuleSet#expand_dimensions_shorthand! [#126](https://github.com/premailer/css_parser/pull/126)
### Version 1.9.0
* Misc cleanup [#122](https://github.com/premailer/css_parser/pull/122)
### Version 1.8.0
* Internal refactoring around ruleset [diff](https://github.com/premailer/css_parser/compare/v1.7.1...v1.8.0)
### Version 1.7.1
* Force UTF-8 encoding; do not strip out UTF-8 chars. [#106](https://github.com/premailer/css_parser/pull/106)
### Version 1.7.0
* No longer support ruby versions 1.9 2.0 2.1
* Memory allocation improvements
### Version 1.6.0
* Handles font-size/ line-height shorthand with spaces
### Version 1.5.0
* Extended color keywords support (https://www.w3.org/TR/css3-color/).
* `remove_rule_set!` method added.
* `:capture_offsets` feature added.
### Version 1.4.10
* Include uri in RemoteFileError message.
* Prevent to convert single declarations to their respective shorthand.
* Fix Ruby warnings.
### Version 1.4.9
* Support for vrem, vh, vw, vmin, vmax and vm box model units.
* Replace obsolete calls with actual ones.
* Fix some Ruby warnings.
### Version 1.4.8
* Allow to get CSS rules as Hash using `to_hash` method.
* Updates to support Ruby 1.9 and JRuby.
* utf-8 related update.
### Version 1.4.7
* background-position shorthand fix.
### Version 1.4.6
* Normalize whitespace in selectors and queries.
* Strip spaces from keys.
* More checks on ordering.
### Version 1.4.5
* Maintenance release.
### Version 1.4.4
* More robust redirection handling, refs #47.
### Version 1.4.3
* Look for redirects, MAX_REDIRECTS set to 3, refs #36.
* Fix border style expanding, refs #58.
* load_string! described, refs #70.
### Version 1.4.2
* Ship license with package, refs #69.
### Version 1.4.1
* Fix background shorthands, refs #66.
### Version 1.4.0
* Add support for background-size in the shorthand property @mitio
### 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.16.0/Gemfile 0000664 0000000 0000000 00000000246 14474270710 0015246 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
# Keep Gemfile.lock in repo. Reason: https://grosser.it/2015/08/14/check-in-your-gemfile-lock/
source 'https://rubygems.org'
gemspec
css_parser-1.16.0/Gemfile.lock 0000664 0000000 0000000 00000002154 14474270710 0016175 0 ustar 00root root 0000000 0000000 PATH
remote: .
specs:
css_parser (1.16.0)
addressable
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.1)
benchmark-ips (2.8.4)
bump (0.10.0)
maxitest (3.6.0)
minitest (>= 5.0.0, < 5.14.0)
memory_profiler (1.0.0)
minitest (5.12.0)
parallel (1.20.1)
parser (3.0.0.0)
ast (~> 2.4.1)
public_suffix (5.0.3)
rainbow (3.0.0)
rake (13.0.3)
regexp_parser (2.0.3)
rexml (3.2.4)
rubocop (1.8.0)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.2.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.4.0)
parser (>= 2.7.1.5)
rubocop-rake (0.5.1)
rubocop
ruby-progressbar (1.11.0)
unicode-display_width (2.0.0)
webrick (1.7.0)
PLATFORMS
java
ruby
DEPENDENCIES
benchmark-ips
bump
css_parser!
maxitest
memory_profiler
rake
rubocop (~> 1.8)
rubocop-rake
webrick
BUNDLED WITH
2.1.4
css_parser-1.16.0/MIT-LICENSE 0000664 0000000 0000000 00000002075 14474270710 0015411 0 ustar 00root root 0000000 0000000 === 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.16.0/README.md 0000664 0000000 0000000 00000004612 14474270710 0015233 0 ustar 00root root 0000000 0000000 # Ruby CSS Parser [](https://github.com/ojab/css_parser/actions?query=workflow%3A%22Run+css_parser+CI%22) [](https://badge.fury.io/rb/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)
# load a string
parser = CssParser::Parser.new
parser.load_string! 'a { color: hotpink; }'
# 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; }
# capturing byte offsets within a file
parser.load_uri!('../style.css', {base_uri: 'http://example.com/styles/inc/', capture_offsets: true)
content_rule = parser.find_rule_sets(['#content']).first
content_rule.filename
#=> 'http://example.com/styles/styles.css'
content_rule.offset
#=> 10703..10752
# capturing byte offsets within a string
parser.load_string!('a { color: hotpink; }', {filename: 'index.html', capture_offsets: true)
content_rule = parser.find_rule_sets(['a']).first
content_rule.filename
#=> 'index.html'
content_rule.offset
#=> 0..21
```
# 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/premailer/css_parser/contributors) for their updates.
Made on Vancouver Island.
css_parser-1.16.0/Rakefile 0000664 0000000 0000000 00000002372 14474270710 0015422 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'bundler/setup'
require 'bundler/gem_tasks'
require 'rake/testtask'
require 'rubocop/rake_task'
require 'bump/tasks'
task default: [:rubocop, :test]
Rake::TestTask.new do |test|
test.pattern = 'test/**/test*.rb'
test.verbose = true
end
RuboCop::RakeTask.new
desc 'Run a performance evaluation.'
task :benchmark do
require 'css_parser'
require 'benchmark/ips'
require 'memory_profiler'
fixtures_dir = Pathname.new(__dir__).join('test/fixtures')
import_css_path = fixtures_dir.join('import1.css').to_s.freeze
complex_css_path = fixtures_dir.join('complex.css').to_s.freeze
Benchmark.ips do |x|
x.report('import1.css loading') { CssParser::Parser.new.load_file!(import_css_path) }
x.report('complex.css loading') { CssParser::Parser.new.load_file!(complex_css_path) }
end
puts
report = MemoryProfiler.report { CssParser::Parser.new.load_file!(import_css_path) }
puts "Loading `import1.css` allocated #{report.total_allocated} objects, #{report.total_allocated_memsize / 1024} KiB"
report = MemoryProfiler.report { CssParser::Parser.new.load_file!(complex_css_path) }
puts "Loading `complex.css` allocated #{report.total_allocated} objects, #{report.total_allocated_memsize / 1024} KiB"
end
css_parser-1.16.0/css_parser.gemspec 0000664 0000000 0000000 00000002127 14474270710 0017464 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
name = 'css_parser'
require "./lib/#{name}/version"
Gem::Specification.new name, CssParser::VERSION 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.files = Dir.glob('lib/**/*') + ['MIT-LICENSE']
s.license = 'MIT'
s.required_ruby_version = '>= 2.7'
s.metadata['changelog_uri'] = 'https://github.com/premailer/css_parser/blob/master/CHANGELOG.md'
s.metadata['source_code_uri'] = 'https://github.com/premailer/css_parser'
s.metadata['bug_tracker_uri'] = 'https://github.com/premailer/css_parser/issues'
s.add_runtime_dependency 'addressable'
s.add_development_dependency 'benchmark-ips'
s.add_development_dependency 'bump'
s.add_development_dependency 'maxitest'
s.add_development_dependency 'memory_profiler'
s.add_development_dependency 'rake'
s.add_development_dependency 'rubocop', '~> 1.8'
s.add_development_dependency 'rubocop-rake'
s.add_development_dependency 'webrick'
end
css_parser-1.16.0/lib/ 0000775 0000000 0000000 00000000000 14474270710 0014517 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/lib/css_parser.rb 0000664 0000000 0000000 00000012054 14474270710 0017212 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
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].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.expand_shorthand!
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 not properties.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 = 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
# 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_NC).length
d = selector.scan(ELEMENTS_AND_PSEUDO_ELEMENTS_RX_NC).length
"#{a}#{b}#{c}#{d}".to_i
rescue
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.is_a?(Addressable::URI)
css.gsub(URI_RX) do
uri = Regexp.last_match(1).to_s.gsub(/["']+/, '')
# Don't process URLs that are already absolute
unless uri.match(%r{^[a-z]+://}i)
begin
uri = base_uri.join(uri)
rescue
nil
end
end
"url('#{uri}')"
end
end
def self.sanitize_media_query(raw)
mq = raw.to_s.gsub(/\s+/, ' ')
mq.strip!
mq = 'all' if mq.empty?
mq.to_sym
end
end
css_parser-1.16.0/lib/css_parser/ 0000775 0000000 0000000 00000000000 14474270710 0016663 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/lib/css_parser/parser.rb 0000664 0000000 0000000 00000053671 14474270710 0020520 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
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} (https://github.com/premailer/css_parser)"
STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
STRIP_HTML_COMMENTS_RX = //m.freeze
# Initial parsing
RE_AT_IMPORT_RULE = /@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s)]*)["']?\)?([\w\s,^\]()]*)\)?[;\n]?/.freeze
MAX_REDIRECTS = 3
# 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,
rule_set_exceptions: true,
capture_offsets: false}.merge(options)
# array of RuleSets
@rules = []
@redirect_count = nil
@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 [] 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|
selector = selector.gsub(/\s+/, ' ').strip
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, options)
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.empty? or !(media_types & options[:only_media_types]).empty?
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
import_options = {media_types: media_types}
import_options[:capture_offsets] = true if options[:capture_offsets]
if options[:base_uri]
import_uri = Addressable::URI.parse(options[:base_uri].to_s) + Addressable::URI.parse(import_path)
import_options[:base_uri] = options[:base_uri]
load_uri!(import_uri, import_options)
elsif options[:base_dir]
import_options[:base_dir] = options[:base_dir]
load_file!(import_path, import_options)
end
end
end
# Remove @import declarations
block = ignore_pattern(block, RE_AT_IMPORT_RULE, options)
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)
rescue ArgumentError => e
raise e if @options[:rule_set_exceptions]
end
# Add a CSS rule by setting the +selectors+, +declarations+, +filename+, +offset+ and +media_types+.
#
# +filename+ can be a string or uri pointing to the file or url location.
# +offset+ should be Range object representing the start and end byte locations where the rule was found in the file.
# +media_types+ can be a symbol or an array of symbols.
def add_rule_with_offsets!(selectors, declarations, filename, offset, media_types = :all)
rule_set = OffsetAwareRuleSet.new(filename, offset, 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.is_a?(CssParser::RuleSet)
media_types = [media_types] unless media_types.is_a?(Array)
media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt) }
@rules << {media_types: media_types, rules: ruleset}
end
# Remove a CssParser RuleSet object.
#
# +media_types+ can be a symbol or an array of symbols.
def remove_rule_set!(ruleset, media_types = :all)
raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
@rules.reject! do |rule|
rule[:media_types] == media_types && rule[:rules].to_s == ruleset.to_s
end
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
# Output all CSS rules as a Hash
def to_h(which_media = :all)
out = {}
styles_by_media_types = {}
each_selector(which_media) 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|
ms = {}
media_styles.each do |media_style|
ms = css_node_to_h(ms, media_style[0], media_style[1])
end
out[media_type.to_s] = ms
end
out
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(all_media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types
return to_enum(__method__, all_media_types, options) unless block_given?
each_rule_set(all_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(which_media = :all)
out = []
styles_by_media_types = {}
each_selector(which_media) 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} {" if media_block
media_styles.each do |media_style|
if media_block
out.push(" #{media_style[0]} {\n #{media_style[1]}\n }")
else
out.push("#{media_style[0]} {\n#{media_style[1]}\n}")
end
end
out << '}' if media_block
end
out << ''
out.join("\n")
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.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:
[]
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 = String.new
current_media_query = String.new
current_declarations = String.new
# once we are in a rule, we will use this to store where we started if we are capturing offsets
rule_start = nil
offset = nil
block.scan(/\s+|\\{2,}|\\?[{}\s"]|[()]|.[^\s"{}()\\]*/) do |token|
# save the regex offset so that we know where in the file we are
offset = Regexp.last_match.offset(0) if options[:capture_offsets]
if token.start_with?('"') # found un-escaped double quote
in_string = !in_string
end
if in_declarations > 0
# too deep, malformed declaration block
if in_declarations > 1
in_declarations -= 1 if token.include?('}')
next
end
if !in_string && token.include?('{')
in_declarations += 1
next
end
current_declarations << token
if !in_string && token.include?('}')
current_declarations.gsub!(/\}\s*$/, '')
in_declarations -= 1
current_declarations.strip!
unless current_declarations.empty?
if options[:capture_offsets]
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
else
add_rule!(current_selectors, current_declarations, current_media_queries)
end
end
current_selectors = String.new
current_declarations = String.new
# restart our search for selectors and declarations
rule_start = nil if options[:capture_offsets]
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.include?('{')
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 = String.new
elsif token.include?(',')
# new media query begins
token.tr!(',', ' ')
token.strip!
current_media_query << token << ' '
current_media_queries << CssParser.sanitize_media_query(current_media_query)
current_media_query = String.new
else
token.strip!
# special-case the ( and ) tokens to remove inner-whitespace
# (eg we'd prefer '(width: 500px)' to '( width: 500px )' )
case token
when '('
current_media_query << token
when ')'
current_media_query.sub!(/ ?$/, token)
else
current_media_query << token << ' '
end
end
elsif in_charset or token =~ /@charset/i
# iterate until we are out of the charset declaration
in_charset = !token.include?(';')
elsif !in_string && token.include?('}')
block_depth -= 1
# reset the current media query scope
if in_media_block
current_media_queries = [:all]
in_media_block = false
end
elsif !in_string && token.include?('{')
current_selectors.strip!
in_declarations += 1
else
# if we are in a selector, add the token to the current selectors
current_selectors << token
# mark this as the beginning of the selector unless we have already marked it
rule_start = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
end
end
# check for unclosed braces
return unless in_declarations > 0
unless options[:capture_offsets]
return add_rule!(current_selectors, current_declarations, current_media_queries)
end
add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
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
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?
# pass on the uri if we are capturing file offsets
opts[:filename] = uri.to_s if opts[:capture_offsets]
src, = read_remote_file(uri) # skip charset
add_block!(src, opts) if src
end
# Load a local CSS file.
def load_file!(file_name, options = {}, deprecated = nil)
opts = {base_dir: nil, media_types: :all}
if options.is_a? Hash
opts.merge!(options)
else
opts[:base_dir] = options if options.is_a? String
opts[:media_types] = deprecated if deprecated
end
file_name = File.expand_path(file_name, opts[:base_dir])
return unless File.readable?(file_name)
return unless circular_reference_check(file_name)
src = IO.read(file_name)
opts[:filename] = file_name if opts[:capture_offsets]
opts[:base_dir] = File.dirname(file_name)
add_block!(src, opts)
end
# Load a local CSS string.
def load_string!(src, options = {}, deprecated = nil)
opts = {base_dir: nil, media_types: :all}
if options.is_a? Hash
opts.merge!(options)
else
opts[:base_dir] = options if options.is_a? String
opts[:media_types] = deprecated if deprecated
end
add_block!(src, opts)
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]
false
else
@loaded_uris << path
true
end
end
# Remove a pattern from a given string
#
# Returns a string.
def ignore_pattern(css, regex, options)
# if we are capturing file offsets, replace the characters with spaces to retail the original positions
return css.gsub(regex) { |m| ' ' * m.length } if options[:capture_offsets]
# otherwise just strip it out
css.gsub(regex, '')
end
# Strip comments and clean up blank lines from a block of CSS.
#
# Returns a string.
def cleanup_block(block, options = {}) # :nodoc:
# Strip CSS comments
utf8_block = block.encode('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: ' ')
utf8_block = ignore_pattern(utf8_block, STRIP_CSS_COMMENTS_RX, options)
# Strip HTML comments - they shouldn't really be in here but
# some people are just crazy...
utf8_block = ignore_pattern(utf8_block, STRIP_HTML_COMMENTS_RX, options)
# Strip lines containing just whitespace
utf8_block.gsub!(/^\s+$/, '') unless options[:capture_offsets]
utf8_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:
if @redirect_count.nil?
@redirect_count = 0
else
@redirect_count += 1
end
unless circular_reference_check(uri.to_s)
@redirect_count = nil
return nil, nil
end
if @redirect_count > MAX_REDIRECTS
@redirect_count = nil
return nil, nil
end
src = '', charset = nil
begin
uri = Addressable::URI.parse(uri.to_s)
if uri.scheme == 'file'
# local file
path = uri.path
path.gsub!(%r{^/}, '') if Gem.win_platform?
src = File.read(path, mode: 'rb')
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 = res.respond_to?(:charset) ? res.encoding : 'utf-8'
if res.code.to_i >= 400
@redirect_count = nil
raise RemoteFileError, uri.to_s if @options[:io_exceptions]
return '', nil
elsif res.code.to_i >= 300 and res.code.to_i < 400
unless res['Location'].nil?
return read_remote_file Addressable::URI.parse(Addressable::URI.escape(res['Location']))
end
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
@redirect_count = nil
raise RemoteFileError, uri.to_s if @options[:io_exceptions]
return nil, nil
end
@redirect_count = nil
[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:
@folded_declaration_cache[block_hash] ||= nil
end
def reset! # :nodoc:
@folded_declaration_cache = {}
@css_source = ''
@css_rules = []
@css_warnings = []
end
# recurse through nested nodes and return them as Hashes nested in
# passed hash
def css_node_to_h(hash, key, val)
hash[key.strip] = '' and return hash if val.nil?
lines = val.split(';')
nodes = {}
lines.each do |line|
parts = line.split(':', 2)
if parts[1] =~ /:/
nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
else
nodes[parts[0].to_s.strip] = parts[1].to_s.strip
end
end
hash[key.strip] = nodes
hash
end
end
end
css_parser-1.16.0/lib/css_parser/regexps.rb 0000664 0000000 0000000 00000016255 14474270710 0020676 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
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 | Regexp::NOENCODING) # [^\0-\177]
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)
# General strings
RE_STRING1 = /("(.[^\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
# Initial parsing
RE_AT_IMPORT_RULE = /@import\s+(url\()?["']?(.[^'"\s]*)["']?\)?([\w\s,^\])]*)\)?;?/.freeze
#--
# 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.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
# Special units
BOX_MODEL_UNITS_RX = /(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)
# Functions like calc, var, clamp, etc.
RE_FUNCTIONS = /
(
[a-z0-9-]+ # function name
)
(?>
\( # opening parenthesis
(?:
([^()]+)
| # recursion via subexpression
\g<0>
)*
\) # closing parenthesis
)
/imx.freeze
# Patterns for specificity calculations
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX_NC = /
(?:\.\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
# Colours
NAMED_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)
# :startdoc:
end
css_parser-1.16.0/lib/css_parser/rule_set.rb 0000664 0000000 0000000 00000055326 14474270710 0021045 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'forwardable'
module CssParser
class RuleSet
# Patterns for specificity calculations
RE_ELEMENTS_AND_PSEUDO_ELEMENTS = /((^|[\s+>]+)\w+|:(first-line|first-letter|before|after))/i.freeze
RE_NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES = /(\.\w+)|(\[\w+)|(:(link|first-child|lang))/i.freeze
BACKGROUND_PROPERTIES = ['background-color', 'background-image', 'background-repeat', 'background-position', 'background-size', 'background-attachment'].freeze
LIST_STYLE_PROPERTIES = ['list-style-type', 'list-style-position', 'list-style-image'].freeze
FONT_STYLE_PROPERTIES = ['font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family'].freeze
BORDER_STYLE_PROPERTIES = ['border-width', 'border-style', 'border-color'].freeze
BORDER_PROPERTIES = ['border', 'border-left', 'border-right', 'border-top', 'border-bottom'].freeze
NUMBER_OF_DIMENSIONS = 4
DIMENSIONS = [
['margin', %w[margin-top margin-right margin-bottom margin-left]],
['padding', %w[padding-top padding-right padding-bottom padding-left]],
['border-color', %w[border-top-color border-right-color border-bottom-color border-left-color]],
['border-style', %w[border-top-style border-right-style border-bottom-style border-left-style]],
['border-width', %w[border-top-width border-right-width border-bottom-width border-left-width]]
].freeze
WHITESPACE_REPLACEMENT = '___SPACE___'
class Declarations
class Value
attr_reader :value
attr_accessor :important
def initialize(value, important: nil)
self.value = value
@important = important unless important.nil?
end
def value=(value)
value = value.to_s.sub(/\s*;\s*\Z/, '')
self.important = !value.slice!(CssParser::IMPORTANT_IN_PROPERTY_RX).nil?
value.strip!
raise ArgumentError, 'value is empty' if value.empty?
@value = value.freeze
end
def to_s
important ? "#{value} !important" : value
end
def ==(other)
return false unless other.is_a?(self.class)
value == other.value && important == other.important
end
end
extend Forwardable
def_delegators :declarations, :each
def initialize(declarations = {})
self.declarations = {}
declarations.each { |property, value| add_declaration!(property, value) }
end
# Add a CSS declaration
# @param [#to_s] property that should be added
# @param [Value, #to_s] value of the property
#
# @example
# declarations['color'] = 'blue'
#
# puts declarations['color']
# => #
#
# @example
# declarations['margin'] = '0px auto !important'
#
# puts declarations['margin']
# => #
#
# If the property already exists its value will be over-written.
# If the value is empty - property will be deleted
def []=(property, value)
property = normalize_property(property)
if value.is_a?(Value)
declarations[property] = value
elsif value.to_s.strip.empty?
delete property
else
declarations[property] = Value.new(value)
end
rescue ArgumentError => e
raise e.exception, "#{property} #{e.message}"
end
alias add_declaration! []=
def [](property)
declarations[normalize_property(property)]
end
alias get_value []
def key?(property)
declarations.key?(normalize_property(property))
end
def size
declarations.size
end
# Remove CSS declaration
# @param [#to_s] property property to be removed
#
# @example
# declarations.delete('color')
def delete(property)
declarations.delete(normalize_property(property))
end
alias remove_declaration! delete
# Replace CSS property with multiple declarations
# @param [#to_s] property property name to be replaces
# @param [Hash [String, Value]>] replacements hash with properties to replace with
#
# @example
# declarations = Declarations.new('line-height' => '0.25px', 'font' => 'small-caps', 'font-size' => '12em')
# declarations.replace_declaration!('font', {'line-height' => '1px', 'font-variant' => 'small-caps', 'font-size' => '24px'})
# declarations
# => ##,
# "font-variant"=>#,
# "font-size"=>#}>
def replace_declaration!(property, replacements, preserve_importance: false)
property = normalize_property(property)
raise ArgumentError, "property #{property} does not exist" unless key?(property)
replacement_declarations = self.class.new(replacements)
if preserve_importance
importance = get_value(property).important
replacement_declarations.each { |_key, value| value.important = importance }
end
replacement_keys = declarations.keys
replacement_values = declarations.values
property_index = replacement_keys.index(property)
# We should preserve subsequent declarations of the same properties
# and prior important ones if replacement one is not important
replacements = replacement_declarations.each.with_object({}) do |(key, replacement), result|
existing = declarations[key]
# No existing -> set
unless existing
result[key] = replacement
next
end
# Replacement more important than existing -> replace
if replacement.important && !existing.important
result[key] = replacement
replaced_index = replacement_keys.index(key)
replacement_keys.delete_at(replaced_index)
replacement_values.delete_at(replaced_index)
property_index -= 1 if replaced_index < property_index
next
end
# Existing is more important than replacement -> keep
next if !replacement.important && existing.important
# Existing and replacement importance are the same,
# value which is declared later wins
result[key] = replacement if property_index > replacement_keys.index(key)
end
return if replacements.empty?
replacement_keys.delete_at(property_index)
replacement_keys.insert(property_index, *replacements.keys)
replacement_values.delete_at(property_index)
replacement_values.insert(property_index, *replacements.values)
self.declarations = replacement_keys.zip(replacement_values).to_h
end
def to_s(options = {})
str = declarations.reduce(String.new) do |memo, (prop, value)|
importance = options[:force_important] || value.important ? ' !important' : ''
memo << "#{prop}: #{value.value}#{importance}; "
end
# TODO: Clean-up regexp doesn't seem to work
str.gsub!(/^[\s^({)]+|[\n\r\f\t]*|\s+$/mx, '')
str.strip!
str
end
def ==(other)
return false unless other.is_a?(self.class)
declarations == other.declarations && declarations.keys == other.declarations.keys
end
protected
attr_reader :declarations
private
attr_writer :declarations
def normalize_property(property)
property = property.to_s.downcase
property.strip!
property
end
end
extend Forwardable
# Array of selector strings.
attr_reader :selectors
# Integer with the specificity to use for this RuleSet.
attr_accessor :specificity
# @!method add_declaration!
# @see CssParser::RuleSet::Declarations#add_declaration!
# @!method delete
# @see CssParser::RuleSet::Declarations#delete
def_delegators :declarations, :add_declaration!, :delete
alias []= add_declaration!
alias remove_declaration! delete
def initialize(selectors, block, specificity = nil)
@selectors = []
@specificity = specificity
parse_selectors!(selectors) if selectors
parse_declarations!(block)
end
# Get the value of a property
def get_value(property)
return '' unless (value = declarations[property])
"#{value};"
end
alias [] get_value
# Iterate through selectors.
#
# Options
# - +force_important+ -- boolean
#
# ==== Example
# ruleset.each_selector do |sel, dec, spec|
# ...
# end
def each_selector(options = {}) # :yields: selector, declarations, specificity
decs = declarations.to_s(options)
if @specificity
@selectors.each { |sel| yield sel.strip, decs, @specificity }
else
@selectors.each { |sel| yield sel.strip, decs, CssParser.calculate_specificity(sel) }
end
end
# Iterate through declarations.
def each_declaration # :yields: property, value, is_important
declarations.each do |property_name, value|
yield property_name, value.value, value.important
end
end
# Return all declarations as a string.
def declarations_to_s(options = {})
declarations.to_s(options)
end
# Return the CSS rule set as a string.
def to_s
"#{@selectors.join(',')} { #{declarations} }"
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 (declaration = declarations['background'])
value = declaration.value.dup
replacement =
if value.match(CssParser::RE_INHERIT)
BACKGROUND_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
else
{
'background-image' => value.slice!(CssParser::RE_IMAGE),
'background-attachment' => value.slice!(CssParser::RE_SCROLL_FIXED),
'background-repeat' => value.slice!(CssParser::RE_REPEAT),
'background-color' => value.slice!(CssParser::RE_COLOUR),
'background-size' => extract_background_size_from(value),
'background-position' => value.slice!(CssParser::RE_BACKGROUND_POSITION)
}
end
declarations.replace_declaration!('background', replacement, preserve_importance: true)
end
def extract_background_size_from(value)
size = value.slice!(CssParser::RE_BACKGROUND_SIZE)
size.sub(%r{^\s*/\s*}, '') if size
end
# Split shorthand border declarations (e.g. border: 1px red;)
# Additional splitting happens in expand_dimensions_shorthand!
def expand_border_shorthand! # :nodoc:
BORDER_PROPERTIES.each do |k|
next unless (declaration = declarations[k])
value = declaration.value.dup
replacement = {
"#{k}-width" => value.slice!(CssParser::RE_BORDER_UNITS),
"#{k}-color" => value.slice!(CssParser::RE_COLOUR),
"#{k}-style" => value.slice!(CssParser::RE_BORDER_STYLE)
}
declarations.replace_declaration!(k, replacement, preserve_importance: true)
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:
DIMENSIONS.each do |property, (top, right, bottom, left)|
next unless (declaration = declarations[property])
value = declaration.value.dup
# 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*,\s*)/, ',') }
matches = split_value_preserving_function_whitespace(value)
case matches.length
when 1
values = matches.to_a * 4
when 2
values = matches.to_a * 2
when 3
values = matches.to_a
values << matches[1] # left = right
when 4
values = matches.to_a
else
raise ArgumentError, "Cannot parse #{value}"
end
replacement = [top, right, bottom, left].zip(values).to_h
declarations.replace_declaration!(property, replacement, preserve_importance: true)
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 (declaration = declarations['font'])
# reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
font_props = {
'font-style' => 'normal',
'font-variant' => 'normal',
'font-weight' => 'normal',
'font-size' => 'normal',
'line-height' => 'normal'
}
value = declaration.value.dup
value.gsub!(%r{/\s+}, '/') # handle spaces between font size and height shorthand (e.g. 14px/ 16px)
in_fonts = false
matches = value.scan(/"(?:.*[^"])"|'(?:.*[^'])'|(?:\w[^ ,]+)/)
matches.each do |m|
m.strip!
m.gsub!(/;$/, '')
if in_fonts
if font_props.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
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.include?('/')
font_props['font-size'], font_props['line-height'] = m.split('/', 2)
else
font_props['font-size'] = m
end
in_fonts = true
end
end
declarations.replace_declaration!('font', font_props, preserve_importance: true)
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 (declaration = declarations['list-style'])
value = declaration.value.dup
replacement =
if value =~ CssParser::RE_INHERIT
LIST_STYLE_PROPERTIES.map { |key| [key, 'inherit'] }.to_h
else
{
'list-style-type' => value.slice!(CssParser::RE_LIST_STYLE_TYPE),
'list-style-position' => value.slice!(CssParser::RE_INSIDE_OUTSIDE),
'list-style-image' => value.slice!(CssParser::URI_RX_OR_NONE)
}
end
declarations.replace_declaration!('list-style', replacement, preserve_importance: true)
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_to_delete = []
properties.each do |property|
next unless (declaration = declarations[property])
next if declaration.important
values << declaration.value
properties_to_delete << property
end
return if values.length <= 1
properties_to_delete.each do |property|
declarations.delete(property)
end
declarations[shorthand_property] = values.join(' ')
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:
# When we have a background-size property we must separate it and distinguish it from
# background-position by preceding it with a backslash. In this case we also need to
# have a background-position property, so we set it if it's missing.
# http://www.w3schools.com/cssref/css3_pr_background.asp
if (declaration = declarations['background-size']) && !declaration.important
declarations['background-position'] ||= '0% 0%'
declaration.value = "/ #{declaration.value}"
end
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_STYLE_PROPERTIES.map do |property|
next unless (declaration = declarations[property])
next if declaration.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...)
next if declaration.value.gsub(/,\s/, ',').strip =~ /\s/
declaration.value
end.compact
return if values.size != BORDER_STYLE_PROPERTIES.size
BORDER_STYLE_PROPERTIES.each do |property|
declarations.delete(property)
end
declarations['border'] = values.join(' ')
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:
return if declarations.size < NUMBER_OF_DIMENSIONS
DIMENSIONS.each do |property, dimensions|
values = [:top, :right, :bottom, :left].each_with_index.with_object({}) do |(side, index), result|
next unless (declaration = declarations[dimensions[index]])
result[side] = declaration.value
end
# All four dimensions must be present
next if values.size != dimensions.size
new_value = values.values_at(*compute_dimensions_shorthand(values)).join(' ').strip
declarations[property] = new_value unless new_value.empty?
# Delete the longhand values
dimensions.each { |d| declarations.delete(d) }
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:
return unless FONT_STYLE_PROPERTIES.all? { |prop| declarations.key?(prop) }
new_value = String.new
['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'] = new_value.gsub(/\s+/, ' ')
FONT_STYLE_PROPERTIES.each { |prop| declarations.delete(prop) }
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
attr_accessor :declarations
def compute_dimensions_shorthand(values)
# All four sides are equal, returning single value
return [:top] if values.values.uniq.count == 1
# `/* top | right | bottom | left */`
return [:top, :right, :bottom, :left] if values[:left] != values[:right]
# Vertical are the same & horizontal are the same, `/* vertical | horizontal */`
return [:top, :left] if values[:top] == values[:bottom]
[:top, :left, :bottom]
end
def parse_declarations!(block) # :nodoc:
self.declarations = Declarations.new
return unless block
continuation = nil
block.split(/[;$]+/m).each do |decs|
decs = (continuation ? continuation + decs : decs)
if decs =~ /\([^)]*\Z/ # if it has an unmatched parenthesis
continuation = "#{decs};"
elsif (matches = decs.match(/\s*(.[^:]*)\s*:\s*(?m:(.+))(?:;?\s*\Z)/i))
# skip end_of_declaration
property = matches[1]
value = matches[2]
add_declaration!(property, value)
continuation = nil
end
end
end
#--
# TODO: way too simplistic
#++
def parse_selectors!(selectors) # :nodoc:
@selectors = selectors.split(',').map do |s|
s.gsub!(/\s+/, ' ')
s.strip!
s
end
end
def split_value_preserving_function_whitespace(value)
split_value = value.gsub(RE_FUNCTIONS) do |c|
c.gsub!(/\s+/, WHITESPACE_REPLACEMENT)
c
end
matches = split_value.strip.split(/\s+/)
matches.each do |c|
c.gsub!(WHITESPACE_REPLACEMENT, ' ')
end
end
end
class OffsetAwareRuleSet < RuleSet
# File offset range
attr_reader :offset
# the local or remote location
attr_accessor :filename
def initialize(filename, offset, selectors, block, specificity = nil)
super(selectors, block, specificity)
@offset = offset
@filename = filename
end
end
end
css_parser-1.16.0/lib/css_parser/version.rb 0000664 0000000 0000000 00000000120 14474270710 0020666 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module CssParser
VERSION = '1.16.0'.freeze
end
css_parser-1.16.0/test/ 0000775 0000000 0000000 00000000000 14474270710 0014730 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/test/fixtures/ 0000775 0000000 0000000 00000000000 14474270710 0016601 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/test/fixtures/complex.css 0000664 0000000 0000000 00000025000 14474270710 0020757 0 ustar 00root root 0000000 0000000 /*
Fonts:
font-family:'Caslon 540 LT W01 Italic';
font-family:'Caslon 540 LT W01 Roman';
font-family:'Univers LT W01 53 Extended';
font-family:'Univers LT W01 57 Condensed';
font-family:'Univers LT W01 65 Bold';
*/
/*!
http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}
/* HTML5 display-role reset for older browsers */
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block;}
body{line-height:1;}
ol,ul{list-style:none;}
blockquote,q{quotes:none;}
blockquote:before,blockquote:after,
q:before,q:after{content:'';content:none;}
table{border-collapse:collapse;border-spacing:0;}
/*!
* Copyright (c) 2008, Yahoo! Inc. All rights reserved.
* Code licensed under the BSD License:
* http://developer.yahoo.net/yui/license.txt
* version: 2.6.0
*/
body{font:13px/1.231 helvetica,arial,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}
body {
font: normal 11px/20px sans-serif;
color: #3C3C46;
background: #FFF none;
}
.debug #page {background-image:url("grid.png?1");}
.debug img { opacity: .5;}
.debug .cyclenav, .debug .cycleprev, .debug .services li { background-color: rgba(100,100,100,0.5);}
/** Frame **/
body:after, #page:after, .features:after, section:after, #study_viewer:after, #study:after,#study .gallerywrap:after, footer:after, #name_case_converter:after, footer div:after, form .field:after{clear:both;display:block;visibility:hidden;overflow:hidden;height:0;content:"\0020";}
#container {
margin: 0 auto;
background: #FFF none;
}
#page {
width: 940px;
margin: 0 auto;
}
header{width:940px;margin:60px auto;background:#fff url("header_bg.png") 0 50% repeat-x;}
header li{display:block;float:left;width:180px;height:10px;padding:50px 0;line-height: 10px;}
#nav{width:590px;height:110px;margin:0 auto;font:normal 10px/10px "Univers LT W01 53 Extended",sans-serif;letter-spacing:4px;text-transform:uppercase;}
#logo,#logo a{display:block;width:110px;height:110px;margin:0 auto;padding:0;}
#logo{padding:0 55px;}
#logo a{background:transparent url("sprites.png") -380px 0;text-indent:-9999em;}
#logo a:hover,#logo a:focus{background-position:-380px -109px;}
#logo a:active{background-position:-380px -220px;}
#nav_work{text-align:right;}
header, footer, nav, section{clear:both;zoom:1;}
em, i { font-style: italic;}
b, strong {font-weight: bold;}
a { color: #000; text-decoration: none;}
img {display: block;}
pre {
margin: 20px;
line-height: 1.5;
}
h1 {
margin-top: 3px;
margin-bottom: 17px;
font: normal 70px/80px "Caslon 540 LT W01 Roman", Georgia, serif;
text-align: center;
text-shadow: -2px 2px 1px #FFFFFF, -3px 3px 1px #DCD7D2;
/*filter: dropshadow(color=#cccccc, offx=3, offy=2);*/
/*filter: Shadow(Color=#cccccc,Direction=135,Strenth=1);*/
}
h2 {
height: 20px;
margin: 50px 0 23px;
font: normal 9px/20px "Univers LT W01 53 Extended", sans-serif;
background: #FFF url("dashdots.png") 0 50% repeat-x;
text-align: center;
text-transform: uppercase;
letter-spacing: 5px;
}
h2 span { padding: 0 30px;background: #FFF none;}
h3 {
margin-bottom: 12px;
font: normal 16px/20px "Univers LT W01 57 Condensed", sans-serif;
}
ol, ul { margin-bottom: 20px; list-style-position: inside; font: italic 11px/20px Arial, sans-serif;}
blockquote {
margin: 20px;
font: italic 11px/20px Arial, sans-serif;
}
ul {
list-style-type: disc;
}
li {
margin: 0 0 20px;
}
p {
margin-bottom: 20px;
}
.lede {
width: 820px;
margin: 0 auto 100px;
font: normal 18px/30px "Caslon 540 LT W01 Roman", Georgia, serif;
text-align: center;
}
.lede em, .lede i, .lede a {
font-family: "Garamond W01 Italic", serif;
}
a {
outline: none;
font-style: italic;
}
a:hover, a:focus {
text-decoration: underline;
}
#nav a, .features a, footer a { font-style: normal;}
/* Feature boxes */
.features {
clear: both;
display: block;
width: 900px;
margin-bottom: 50px;
padding: 0 20px;
list-style: none;
font-style: normal;
background-color: transparent !important;
}
.features a:hover, .features a:focus {
text-decoration: none;
}
.features li {
float: left;
width: 180px;
margin: 0 60px 50px 0;
}
#contact .features h3 {font-size: 16px;}
#contact .features li {text-align: center;}
#studies .features, #contact .features {
width: 940px;
padding: 0 60px;
}
#studies .features li, #contact .features li {
width: 220px;
margin-right: 80px;
}
#studies .lede {margin: 0 auto 50px;
}
#studies nav {
width: 940px;
margin: 0;
}
#studies .fade_l {
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 100%;
z-index: 1000;
background: transparent url("fade_l.png") repeat-y;
}
#studies .fade_r {
position: absolute;
right: 0;
top: 0;
width: 40px;
height: 100%;
z-index: 1000;
background: transparent url("fade_r.png") repeat-y;
}
.features h3 { margin-bottom: 3px;}
.services h3 {
margin-left: -5px;
margin-bottom: 12px;
padding-left: 40px;
background: #FFF url("sprites.png") 0 -250px no-repeat;
}
.services .strategy h3 {
background-position: 0 -297px;
}
.services .id h3 {
padding-left: 43px;
background-position: 0 -347px;
}
.services .review h3 {
padding-left: 45px;
background-position: 0 -397px;
}
.services p {
padding: 0 7px;
}
.features p { margin-bottom:0;}
.features .img {
display: block;
width: 228px;
height: 168px;
margin: 0 -5px 15px;
border: 1px solid #e4e4e4;
}
.features .img:active {
background-color: #e4e4e4;
}
/*.features .img:hover img {opacity: 1;}*/
.features img { margin: 4px; }
.js .features img {opacity: 1;}
.features .first { clear: both;}
.features .last { margin-right: 0 !important;}
.features .meta {
margin-bottom: 0;
font: italic 12px/30px "Caslon 540 LT W01 Italic", Georgia, sans-serif;
}
#contact .email { font-style: italic;}
#content .tel a { font-style: normal;}
#contact a:hover, #contact a:focus { text-decoration: underline;}
footer {
width: 940px;
margin: 0 auto 0;
padding-bottom: 100px;
}
footer nav {
width: 100%;
height: 10px;
margin: 0 0 50px;
padding: 25px 0;
text-align: center;
font: normal 8px/10px "Univers LT W01 53 Extended", sans-serif;
letter-spacing: 3px;
text-transform: uppercase;
background: #fff url("header_bg.png") 0 50% repeat-x;
}
footer nav a {
display: inline;
padding: 0 32px;
text-align: center;
}
footer div {
margin: 0 auto;
font-size: 10px;
text-align: center;
color: #B3B3B3;
}
footer a { font-style: normal;}
footer img {
display: block;
margin: 20px auto;
}
#study h1, .code h1, .inside h1 {
font-size: 50px;
line-height: 60px;
text-shadow: none;
}
.focus h2 {
height: auto;
margin: 0 0 12px;
border: 0;
font: normal 15px/20px "Univers LT W01 57 Condensed", sans-serif;
text-transform: none;
text-align: left;
letter-spacing: 0;
background: none;
}
.focus ul { list-style: none;}
.focus li {
margin: 5px 0 10px;
font: normal 12px/15px "Univers LT W01 65 Bold", sans-serif;
}
#study { margin-bottom: 80px;}
#study .prose, #study .focus {
float: left;
}
#study .prose {
width: 460px;
margin: 0 40px 0 120px;
font: normal 15px/20px "Caslon 540 LT W01 Roman", Georgia, serif;
}
#study .prose em, #study .prose i {
font-family: "Garamond W01 Italic", serif;
}
#study .focus {
width: 200px;
}
#study .visit {
margin: 20px 0;
font: normal 15px/20px "Univers LT W01 57 Condensed", sans-serif;
text-transform: lowercase;
}
#study .visit a { text-decoration:none;font-style: normal;}
.prose a { text-decoration: underline;}
.gallerywrap .gallery,.cycleprev,.cyclenext{float:left;}
.cycleprev,.cyclenext{display:block;width:26px;height:26px;text-indent:-9999em;cursor:pointer;background:#FFF url("sprites.png") no-repeat;}
.cycleprev{margin-left:14px;margin-right:80px;background-position:-40px -60px;}
.cycleprev:hover,.cycleprev:focus{background-position:-70px -60px;}
.cyclenext{margin-right:14px;margin-left:80px;background-position:-100px -60px;}
.cyclenext:hover,.cyclenext:focus{background-position:-130px -60px;}
.cyclenav{width:700px;height:12px;margin:50px auto 50px;text-align:center;}
.cyclenav a{display:inline-block;width:12px;height:12px;margin:0 5px;text-indent:-9999em;background:#fff url("sprites.png") 0 -60px no-repeat;}
.cyclenav a:hover,.cyclenav a:focus,.cyclenav a.activeSlide{background-position:-15px -60px;}
#study .cyclenav { margin-top: 40px;}
#studies .studywrap .cycleprev { margin: 25px 40px 35px 34px;}
#studies .studywrap .cyclenext { margin: 25px 34px 35px 40px;}
#studies .studywrap .cyclenav { float: left; width: 740px;height: 26px;margin:30px auto 45px;}
#studies nav ul.features { background-color: #FFF;}
#studies nav { margin-bottom: 20px; background-color: #FFF;}
#study .cycleprev,#study .cyclenext{margin-top:235px;}
.gallerywrap {
width: 100%;
}
.gallery {
width: 700px;
margin: 0 auto 50px;
}
.js .gallery img {
display: none;
}
.js .gallery img:first-child {
display: block;
}
#study .gallery img {
width: 700px;
height: 500px;
margin: 0 auto;
}
/* Forms and code */
form {
width: 700px;
margin: 45px auto;
}
form .field {
clear: left;
margin: 0 0 50px;
}
label {
display: block;
float: left;
width: 280px;
margin: 0 20px 5px 0;
font: normal 20px/20px "Univers LT W01 57 Condensed", sans-serif;
}
label .pull {
display: inline-block;
width: 25px;
margin-left: -30px;
}
form .sublabel {
display: block;
margin: 10px 0 5px;
font: normal 12px/15px sans-serif;
}
form .hint {
margin: 5px 0;
font: italic 11px/15px sans-serif;
}
input.text, textarea {
width: 378px;
padding: 0 10px;
border: 1px solid #E4E4E4;
}
input.text {
height: 38px;
}
textarea { padding: 10px; line-height: 20px; height: 218px;}
form div.button {
padding-left: 300px;
}
button.submit {
display: block;
width: 85px;
height: 85px;
border: 0;
text-align: left;
text-indent: -9999em;
background: #FFF url("sprites.png") 0 -150px;
cursor: pointer;
}
button.submit:hover, button.submit:focus {
background-position: -100px -150px;
}
button.submit:active {
background-position: -200px -150px;
}
#name_case_converter textarea {
height: 400px;
}css_parser-1.16.0/test/fixtures/import-circular-reference.css 0000664 0000000 0000000 00000000147 14474270710 0024365 0 ustar 00root root 0000000 0000000 @import "import-circular-reference.css";
body { color: black; background: white; }
p { margin: 0px; }
css_parser-1.16.0/test/fixtures/import-malformed.css 0000664 0000000 0000000 00000000616 14474270710 0022574 0 ustar 00root root 0000000 0000000 .malformed.one:before {
content: "\\";
color: "red";
}
.wellformed.one {
color: "green";
}
.malformed.two:before {
content: "\"";
color: "red";
}
.wellformed.two {
color: "green";
}
.malformed.three:before {
content: "{";
color: "red";
}
.wellformed.three {
color: "green";
}
.malformed.four:before {
content: "}";
color: "red";
}
.wellformed.four {
color: "green";
} css_parser-1.16.0/test/fixtures/import-with-media-types.css 0000664 0000000 0000000 00000000076 14474270710 0024020 0 ustar 00root root 0000000 0000000 @import "simple.css" print, tv, screen;
div { color: lime; }
css_parser-1.16.0/test/fixtures/import1.css 0000664 0000000 0000000 00000000064 14474270710 0020706 0 ustar 00root root 0000000 0000000 @import 'subdir/import2.css';
div { color: lime; }
css_parser-1.16.0/test/fixtures/simple.css 0000664 0000000 0000000 00000000100 14474270710 0020573 0 ustar 00root root 0000000 0000000 body {
color: black;
background: white;
}
p { margin: 0px; }
css_parser-1.16.0/test/fixtures/subdir/ 0000775 0000000 0000000 00000000000 14474270710 0020071 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/test/fixtures/subdir/import2.css 0000664 0000000 0000000 00000000067 14474270710 0022202 0 ustar 00root root 0000000 0000000 @import "../simple.css";
a { text-decoration: none; }
css_parser-1.16.0/test/rule_set/ 0000775 0000000 0000000 00000000000 14474270710 0016552 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/test/rule_set/declarations/ 0000775 0000000 0000000 00000000000 14474270710 0021222 5 ustar 00root root 0000000 0000000 css_parser-1.16.0/test/rule_set/declarations/test_value.rb 0000664 0000000 0000000 00000013414 14474270710 0023725 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative '../../test_helper'
require 'minitest/spec'
require 'ostruct'
class RuleSetProperyTest < Minitest::Test
describe '.new' do
describe 'with invalid value' do
it 'raises an error when empty' do
exception = assert_raises(ArgumentError) { CssParser::RuleSet::Declarations::Value.new(' ') }
assert_equal 'value is empty', exception.message
end
it 'raises an error when nil' do
exception = assert_raises(ArgumentError) { CssParser::RuleSet::Declarations::Value.new(nil) }
assert_equal 'value is empty', exception.message
end
it 'raises an error when contains only important declaration' do
exception = assert_raises(ArgumentError) { CssParser::RuleSet::Declarations::Value.new(' !important; ') }
assert_equal 'value is empty', exception.message
end
end
describe 'with valid value' do
it 'remove semicolon at the end' do
assert_equal 'value', CssParser::RuleSet::Declarations::Value.new('value;').value
end
it 'removes important declarations' do
assert_equal 'value', CssParser::RuleSet::Declarations::Value.new('value !important').value
end
it 'strips value' do
assert_equal 'value', CssParser::RuleSet::Declarations::Value.new(' value ').value
end
it 'does everything above' do
assert_equal "value\t another one",
CssParser::RuleSet::Declarations::Value.new(" \tvalue\t another one \t!important \t ; ").value
end
it 'freezes the string' do
assert_equal true, CssParser::RuleSet::Declarations::Value.new('value').value.frozen?
end
end
describe 'important' do
describe 'when not set' do
it 'is not important if value is not important' do
assert_equal false, CssParser::RuleSet::Declarations::Value.new('value').important
end
it 'is important if value is not important' do
assert_equal true, CssParser::RuleSet::Declarations::Value.new('value !important;').important
end
end
describe 'when set' do
it 'overrides value importance' do
assert_equal false, CssParser::RuleSet::Declarations::Value.new('value !important;', important: false).important
assert_equal true, CssParser::RuleSet::Declarations::Value.new('value', important: true).important
end
end
end
end
describe 'important=' do
it 'sets importance' do
property = CssParser::RuleSet::Declarations::Value.new('value')
assert_equal false, property.important
property.important = true
assert_equal true, property.important
end
end
describe 'value=' do
it 'sets normalized value' do
property = CssParser::RuleSet::Declarations::Value.new('foo')
assert_equal 'foo', property.value
property.value = " \tvalue\t another one \t!important \t ; "
assert_equal "value\t another one", property.value
end
it 'sets importance' do
property = CssParser::RuleSet::Declarations::Value.new('foo')
assert_equal 'foo', property.value
assert_equal false, property.important
property.value = 'bar !important'
assert_equal 'bar', property.value
assert_equal true, property.important
end
it 'freezes the string' do
property = CssParser::RuleSet::Declarations::Value.new('foo')
property.value = 'bar'
assert_equal true, CssParser::RuleSet::Declarations::Value.new('value').value.frozen?
end
it 'raises an exception when the value is empty' do
assert_raises ArgumentError do
CssParser::RuleSet::Declarations::Value.new
end
end
end
describe '#to_s' do
it 'returns value if not important' do
assert_equal 'value', CssParser::RuleSet::Declarations::Value.new('value').to_s
end
it 'returns value with important annotation if important' do
assert_equal 'value !important', CssParser::RuleSet::Declarations::Value.new('value', important: true).to_s
end
end
describe '#==' do
it 'returns true if value & importance are the same' do
property = CssParser::RuleSet::Declarations::Value.new('value', important: true)
other = CssParser::RuleSet::Declarations::Value.new('value', important: true)
assert_equal property, other
end
it 'returns false if value is not a Declarations::Value' do
property = CssParser::RuleSet::Declarations::Value.new('value', important: true)
other = OpenStruct.new(value: 'value', important: true)
refute_equal other, property
end
it 'returns true if value is a Declarations::Value subclass and value are equal' do
property = CssParser::RuleSet::Declarations::Value.new('value', important: true)
other_class = Class.new(CssParser::RuleSet::Declarations::Value)
other = other_class.new('value', important: true)
assert_equal property, other
end
it 'returns false if value is a Declarations::Value subclass and value are not equal' do
property = CssParser::RuleSet::Declarations::Value.new('value', important: true)
other_class = Class.new(CssParser::RuleSet::Declarations::Value)
other = other_class.new('other value', important: true)
refute_equal other, property
end
it 'returns false if value is different' do
property = CssParser::RuleSet::Declarations::Value.new('value', important: true)
other = CssParser::RuleSet::Declarations::Value.new('other value', important: true)
refute_equal property, other
end
it 'returns false if importance is different' do
property = CssParser::RuleSet::Declarations::Value.new('value', important: true)
other = CssParser::RuleSet::Declarations::Value.new('value', important: false)
refute_equal property, other
end
end
end
css_parser-1.16.0/test/rule_set/test_declarations.rb 0000664 0000000 0000000 00000044242 14474270710 0022614 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative '../test_helper'
require 'minitest/spec'
class RuleSetDeclarationsTest < Minitest::Test
describe '.new' do
describe 'when initial declarations is not given' do
it 'initialized empty' do
assert_equal 0, CssParser::RuleSet::Declarations.new.size
end
end
describe 'when initial declarations is given' do
it 'initialized with given declarations' do
baz_property = CssParser::RuleSet::Declarations::Value.new('baz value', important: true)
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value', bar: 'bar value', baz: baz_property})
assert_equal 3, declarations.size
assert_equal CssParser::RuleSet::Declarations::Value.new('foo value'), declarations['foo']
assert_equal CssParser::RuleSet::Declarations::Value.new('bar value'), declarations[:bar]
assert_equal baz_property, declarations['baz']
end
end
end
describe '#[]=' do
it 'normalizes property name' do
declarations = CssParser::RuleSet::Declarations.new
declarations[:' fOo'] = 'foo value'
assert_equal true, declarations.key?('foo')
assert_equal 1, declarations.size
assert_equal 'foo value', declarations['foo'].value
end
it 'assigns proper value if Declarations::Value is given' do
declarations = CssParser::RuleSet::Declarations.new
property = CssParser::RuleSet::Declarations::Value.new('value', important: true)
declarations['foo'] = property
assert_equal property, declarations['foo']
end
it 'deletes property if given value is empty' do
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value', bar: 'bar value'})
assert_equal 2, declarations.size
declarations['foo'] = nil
assert_equal 1, declarations.size
assert_equal true, declarations.key?('bar')
end
it 'creates Declarations::Value with proper value if string is given' do
declarations = CssParser::RuleSet::Declarations.new
declarations['foo'] = 'foo value'
assert_instance_of CssParser::RuleSet::Declarations::Value, declarations['foo']
assert_equal 'foo value', declarations['foo'].value
end
it 'has alias #add_declaration!' do
declarations = CssParser::RuleSet::Declarations.new
assert_equal declarations.method(:[]=), declarations.method(:add_declaration!)
end
it 'raises an exception including the property when the value is empty' do
declarations = CssParser::RuleSet::Declarations.new
assert_raises ArgumentError, 'foo value is empty' do
declarations['foo'] = '!important'
end
end
end
describe '#[]' do
it 'returns property if exists' do
foo_value = CssParser::RuleSet::Declarations::Value.new('foo value', important: true)
declarations = CssParser::RuleSet::Declarations.new({foo: foo_value})
assert_equal foo_value, declarations['foo']
end
it 'returns nil if not exists' do
foo_value = CssParser::RuleSet::Declarations::Value.new('foo value', important: true)
declarations = CssParser::RuleSet::Declarations.new({foo: foo_value})
assert_nil declarations['bar']
end
it 'normalizes property name' do
foo_value = CssParser::RuleSet::Declarations::Value.new('foo value', important: true)
declarations = CssParser::RuleSet::Declarations.new({foo: foo_value})
assert_equal foo_value, declarations[:'Foo ']
end
it 'has alias #get_value' do
declarations = CssParser::RuleSet::Declarations.new
assert_equal declarations.method(:[]), declarations.method(:get_value)
end
end
describe '#key?' do
it 'return true if key exists' do
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value'})
assert_equal true, declarations.key?('foo')
end
it 'return false if key does not exists' do
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value'})
assert_equal false, declarations.key?('bar')
end
it 'normalizes property name' do
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value'})
assert_equal true, declarations.key?(:'foO ')
end
end
describe '#size' do
it 'returns declarations size' do
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value'})
assert_equal 1, declarations.size
declarations['bar'] = 'bar value'
assert_equal 2, declarations.size
declarations['foo'] = nil
assert_equal 1, declarations.size
end
end
describe '#delete' do
it 'removes declaration if exists' do
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value'})
assert_equal 1, declarations.size
declarations.remove_declaration!('foo')
assert_equal 0, declarations.size
end
it 'does nothing if declarations does not exist' do
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value'})
assert_equal 1, declarations.size
declarations.remove_declaration!('bar')
assert_equal 1, declarations.size
end
it 'normalizes property name' do
declarations = CssParser::RuleSet::Declarations.new({foo: 'foo value'})
assert_equal 1, declarations.size
declarations.remove_declaration!('fOo'.to_sym)
assert_equal 0, declarations.size
end
it 'has alias #remove_declaration!' do
declarations = CssParser::RuleSet::Declarations.new
assert_equal declarations.method(:delete), declarations.method(:remove_declaration!)
end
end
describe '#replace_declaration!' do
it 'raises an error when replaced property does not exist' do
declarations = CssParser::RuleSet::Declarations.new
exception = assert_raises(ArgumentError) { declarations.replace_declaration!('property_name', {}) }
assert_equal 'property property_name does not exist', exception.message
end
it 'replaces declaration with normalized property name in place' do
declarations = CssParser::RuleSet::Declarations.new('foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value')
declarations.replace_declaration!(" bAr\t\n", {'bar1' => 'bar1_value', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar1' => 'bar1_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value'
)
assert_equal expected, declarations
end
describe 'when `preserve_importance: false`' do
it 'does not set importance when replaced property is important' do
declarations = CssParser::RuleSet::Declarations.new('foo' => 'foo_value !important')
declarations.replace_declaration!('foo', {'bar' => 'bar_value', 'baz' => 'baz_value !important'})
expected = CssParser::RuleSet::Declarations.new({'bar' => 'bar_value', 'baz' => 'baz_value !important'})
assert_equal expected, declarations
end
it 'does not unset importance when replaced property is not important' do
declarations = CssParser::RuleSet::Declarations.new('foo' => 'foo_value')
declarations.replace_declaration!('foo', {'bar' => 'bar_value', 'baz' => 'baz_value !important'})
expected = CssParser::RuleSet::Declarations.new({'bar' => 'bar_value', 'baz' => 'baz_value !important'})
assert_equal expected, declarations
end
end
describe 'when `preserve_importance: true`' do
it 'sets importance when replaced property is important' do
declarations = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar' => 'bar_value !important', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value !important'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value', 'bar2' => 'bar2_value'}, preserve_importance: true)
expected = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar2' => 'bar2_value !important', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value !important'
)
assert_equal expected, declarations
end
it 'unsets importance when replaced property is not important' do
declarations = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value'
)
declarations.replace_declaration!(
'bar',
{'bar1' => 'bar1_value !important', 'bar2' => 'bar2_value !important'},
preserve_importance: true
)
expected = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value'
)
assert_equal expected, declarations
end
end
describe 'when subsequent declarations for the replacement declarations exist' do
it 'does not replace declarations when both are not important' do
declarations = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value'
)
assert_equal expected, declarations
end
it 'does not replace declarations when both are important' do
declarations = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value !important'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value !important', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value !important'
)
assert_equal expected, declarations
end
it 'does not replace declaration when only replaced is important' do
declarations = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value !important'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value !important'
)
assert_equal expected, declarations
end
it 'replaces declarations when only replacement is important' do
declarations = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value', 'bar1' => 'old_bar1_value'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value !important', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar1' => 'bar1_value !important', 'bar2' => 'bar2_value', 'baz' => 'baz_value'
)
assert_equal expected, declarations
end
end
describe 'when prior declarations for the replacement declarations exist' do
it 'replaces declarations when both are not important' do
declarations = CssParser::RuleSet::Declarations.new(
'bar1' => 'old_bar1_value', 'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'bar1' => 'bar1_value', 'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value'
)
assert_equal expected, declarations
end
it 'replaces declarations when both are important' do
declarations = CssParser::RuleSet::Declarations.new(
'bar1' => 'old_bar1_value !important', 'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value !important', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'bar1' => 'bar1_value !important', 'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value'
)
assert_equal expected, declarations
end
it 'does not replace declaration when only replaced is important' do
declarations = CssParser::RuleSet::Declarations.new(
'bar1' => 'old_bar1_value !important', 'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'bar1' => 'old_bar1_value !important', 'foo' => 'foo_value', 'bar2' => 'bar2_value', 'baz' => 'baz_value'
)
assert_equal expected, declarations
end
it 'replaces declarations when only replacement is important' do
declarations = CssParser::RuleSet::Declarations.new(
'bar1' => 'old_bar1_value', 'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value'
)
declarations.replace_declaration!('bar', {'bar1' => 'bar1_value !important', 'bar2' => 'bar2_value'})
expected = CssParser::RuleSet::Declarations.new(
'foo' => 'foo_value', 'bar1' => 'bar1_value !important', 'bar2' => 'bar2_value', 'baz' => 'baz_value'
)
assert_equal expected, declarations
end
end
end
describe '#each' do
describe 'when block is not given' do
it 'returns enumerator with properties in order' do
foo_value = CssParser::RuleSet::Declarations::Value.new('foo value')
bar_value = CssParser::RuleSet::Declarations::Value.new('bar value')
baz_value = CssParser::RuleSet::Declarations::Value.new('baz value')
declarations = CssParser::RuleSet::Declarations.new({foo: foo_value, bar: bar_value, baz: baz_value})
assert_instance_of Enumerator, declarations.each
assert_equal 3, declarations.each.size
assert_equal [['foo', foo_value], ['bar', bar_value], ['baz', baz_value]], declarations.each.to_a
end
end
describe 'when block is given' do
it 'yields properties in order' do
foo_value = CssParser::RuleSet::Declarations::Value.new('foo value')
bar_value = CssParser::RuleSet::Declarations::Value.new('bar value')
baz_value = CssParser::RuleSet::Declarations::Value.new('baz value')
declarations = CssParser::RuleSet::Declarations.new({foo: foo_value, bar: bar_value, baz: baz_value})
mock = Minitest::Mock.new
mock.expect :call, true, ['foo', foo_value]
mock.expect :call, true, ['bar', bar_value]
mock.expect :call, true, ['baz', baz_value]
declarations.each { |name, value| mock.call(name, value) }
assert_mock mock
end
end
end
describe '#to_s' do
context 'when `force_important` is not passed' do
it 'returns declarations with declared importance' do
foo_value = CssParser::RuleSet::Declarations::Value.new('foo value', important: true)
bar_value = CssParser::RuleSet::Declarations::Value.new('bar value', important: false)
baz_value = CssParser::RuleSet::Declarations::Value.new('baz value', important: true)
declarations = CssParser::RuleSet::Declarations.new({foo: foo_value, bar: bar_value, baz: baz_value})
assert_equal 'foo: foo value !important; bar: bar value; baz: baz value !important;', declarations.to_s
end
end
context 'when `force_important` is passed' do
it 'returns declarations with important annotations' do
foo_value = CssParser::RuleSet::Declarations::Value.new('foo value', important: false)
bar_value = CssParser::RuleSet::Declarations::Value.new('bar value', important: false)
baz_value = CssParser::RuleSet::Declarations::Value.new('baz value', important: false)
declarations = CssParser::RuleSet::Declarations.new({foo: foo_value, bar: bar_value, baz: baz_value})
assert_equal 'foo: foo value !important; bar: bar value !important; baz: baz value !important;',
declarations.to_s({force_important: true})
end
end
end
describe '#==' do
it 'returns true if declarations & their order are the same' do
declarations_hash = {'foo' => 'foo_value', 'bar' => 'bar_value'}
declarations = CssParser::RuleSet::Declarations.new(declarations_hash)
other = CssParser::RuleSet::Declarations.new(declarations_hash)
assert_equal declarations, other
end
it 'returns false if other is not a Declarations' do
declarations_hash = {'foo' => 'foo_value', 'bar' => 'bar_value'}
declarations = CssParser::RuleSet::Declarations.new(declarations_hash)
other = OpenStruct.new(declarations: declarations_hash)
refute_equal declarations, other
end
it 'returns true if value is a Declarations subclass and declarations are equal' do
declarations_hash = {'foo' => 'foo_value', 'bar' => 'bar_value'}
declarations = CssParser::RuleSet::Declarations.new(declarations_hash)
other_class = Class.new(CssParser::RuleSet::Declarations)
other = other_class.new(declarations_hash)
assert_equal declarations, other
end
it 'returns false if value is a Declarations subclass and value are not equal' do
declarations = CssParser::RuleSet::Declarations.new({'foo' => 'foo_value', 'bar' => 'bar_value'})
other_class = Class.new(CssParser::RuleSet::Declarations)
other = other_class.new({'bar' => 'bar_value', 'foo' => 'foo_value'})
refute_equal declarations, other
end
it 'returns false if declarations values are different' do
declarations = CssParser::RuleSet::Declarations.new({'foo' => 'foo_value', 'bar' => 'bar_value'})
other = CssParser::RuleSet::Declarations.new({'bar' => 'bar_value', 'foo' => 'other_foo_value'})
refute_equal declarations, other
end
it 'returns false if declarations are the same and their order is different' do
declarations = CssParser::RuleSet::Declarations.new({'foo' => 'foo_value', 'bar' => 'bar_value'})
other = CssParser::RuleSet::Declarations.new({'bar' => 'bar_value', 'foo' => 'foo_value'})
refute_equal declarations, other
end
end
end
css_parser-1.16.0/test/test_css_parser_basic.rb 0000664 0000000 0000000 00000005071 14474270710 0021624 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative "test_helper"
# Test cases for reading and generating CSS shorthand properties
class CssParserBasicTests < Minitest::Test
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_removing_a_rule_set
rs = CssParser::RuleSet.new('div', 'color: blue;')
@cp.add_rule_set!(rs)
rs2 = CssParser::RuleSet.new('div', 'color: blue;')
@cp.remove_rule_set!(rs2)
assert_equal '', @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
def test_converting_to_hash
rs = CssParser::RuleSet.new('div', 'color: blue;')
@cp.add_rule_set!(rs)
hash = @cp.to_h
assert_equal 'blue', hash['all']['div']['color']
end
end
css_parser-1.16.0/test/test_css_parser_loading.rb 0000664 0000000 0000000 00000015053 14474270710 0022161 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'test_helper'
# Test cases for the CssParser's loading functions.
class CssParserLoadingTests < Minitest::Test
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.expand_path('fixtures', __dir__)
@server_thread = Thread.new do
s = WEBrick::HTTPServer.new(Port: 12_000, DocumentRoot: @www_root, Logger: Log.new(nil, BasicLog::FATAL), AccessLog: [])
s.mount_proc('/redirect301') do |_request, response|
response['Location'] = '/simple.css'
raise WEBrick::HTTPStatus::MovedPermanently
end
s.mount_proc('/redirect302') do |_request, response|
response['Location'] = '/simple.css'
raise WEBrick::HTTPStatus::TemporaryRedirect
end
@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_301_redirect
@cp.load_uri!("#{@uri_base}/redirect301")
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
def test_loading_302_redirect
@cp.load_uri!("#{@uri_base}/redirect302")
assert_equal 'margin: 0px;', @cp.find_by_selector('p').join(' ')
end
def test_loading_a_local_file
file_name = File.expand_path('fixtures/simple.css', __dir__)
@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://#{__dir__}/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/premailer/css_parser/issues#issue/4
def test_loading_a_remote_file_over_ssl
@cp.load_uri!("https://dialect.ca/inc/screen.css")
assert_includes(@cp.find_by_selector('body').join(' '), "margin: 0;")
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.expand_path('fixtures', __dir__)
@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_remote_import_rules
css_block = '@import "http://example.com/css";'
assert_raises CssParser::RemoteFileError do
@cp.add_block!(css_block, base_uri: "#{@uri_base}/subdir/")
end
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_raises CssParser::RemoteFileError do
@cp.add_block!(css_block, base_uri: "#{@uri_base}/subdir/")
end
end
def test_loading_malformed_content_strings
file_name = File.expand_path('fixtures/import-malformed.css', __dir__)
@cp.load_file!(file_name)
@cp.each_selector do |_sel, dec, _spec|
assert_nil dec =~ /wellformed/
end
end
def test_loading_malformed_css_brackets
file_name = File.expand_path('fixtures/import-malformed.css', __dir__)
@cp.load_file!(file_name)
selector_count = 0
@cp.each_selector do |_sel, _dec, _spec|
selector_count += 1
end
assert_equal 8, selector_count
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_raises CircularReferenceError do
@cp.load_file!(File.expand_path('fixtures/import-circular-reference.css', __dir__))
end
end
def test_remote_circular_reference_exception
assert_raises 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)
cp_without_exceptions.load_uri!("#{@uri_base}/import-circular-reference.css")
end
def test_toggling_not_found_exceptions
cp_with_exceptions = Parser.new(io_exceptions: true)
err = assert_raises RemoteFileError do
cp_with_exceptions.load_uri!("#{@uri_base}/no-exist.xyz")
end
assert_includes err.message, "#{@uri_base}/no-exist.xyz"
cp_without_exceptions = Parser.new(io_exceptions: false)
cp_without_exceptions.load_uri!("#{@uri_base}/no-exist.xyz")
end
def test_rule_set_argument_exceptions
cp_with_exceptions = Parser.new(rule_set_exceptions: true)
assert_raises ArgumentError, 'background-color value is empty' do
cp_with_exceptions.add_rule!('body', 'background-color: !important')
end
cp_without_exceptions = Parser.new(rule_set_exceptions: false)
cp_without_exceptions.add_rule!('body', 'background-color: !important')
end
end
css_parser-1.16.0/test/test_css_parser_media_types.rb 0000664 0000000 0000000 00000011563 14474270710 0023051 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'test_helper'
# Test cases for the handling of media types
class CssParserMediaTypesTests < Minitest::Test
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(&: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_with_parenthesized_media_features
@cp.add_block!(<<-CSS)
body { color: black }
@media(prefers-color-scheme: dark) {
body { color: white }
}
@media(min-width: 500px) {
body { color: blue }
}
@media screen and (width > 500px) {
body { color: red }
}
CSS
assert_equal [:all, :'(prefers-color-scheme: dark)', :'(min-width: 500px)', :'screen and (width > 500px)'], @cp.rules_by_media_query.keys
assert_equal 'color: white;', @cp.find_by_selector('body', :'(prefers-color-scheme: dark)').join(' ')
assert_equal 'color: blue;', @cp.find_by_selector('body', :'(min-width: 500px)').join(' ')
assert_equal 'color: red;', @cp.find_by_selector('body', :'screen and (width > 500px)').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_includes @cp.to_s, 'color: black;'
end
def test_adding_block_and_limiting_media_types1
css = <<-CSS
@import "import1.css", print
CSS
base_dir = Pathname.new(__dir__).join('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 = Pathname.new(__dir__).join('fixtures')
@cp.add_block!(css, only_media_types: 'print and (color)', base_dir: base_dir)
assert_includes @cp.find_by_selector('div').join(' '), 'color: lime'
end
def test_adding_block_and_limiting_media_types
css = <<-CSS
@import "import1.css"
CSS
base_dir = Pathname.new(__dir__).join('fixtures')
@cp.add_block!(css, only_media_types: :print, base_dir: base_dir)
assert_equal '', @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.16.0/test/test_css_parser_misc.rb 0000664 0000000 0000000 00000016674 14474270710 0021511 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'test_helper'
# Test cases for the CssParser.
class CssParserTests < Minitest::Test
include CssParser
def setup
@cp = Parser.new
end
def test_utf8
css = <<-CSS
.chinese { font-family: "Microsoft YaHei","微软雅黑"; }
CSS
@cp.add_block!(css)
assert_equal 'font-family: "Microsoft YaHei","微软雅黑";', @cp.find_by_selector('.chinese').join(' ')
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_multiline_declarations
css = <<-CSS
@font-face {
font-family: 'some_font';
src: url(https://example.com/font.woff2) format('woff2'),
url(https://example.com/font.woff) format('woff');
font-style: normal;
}
CSS
@cp.add_block!(css)
@cp.each_selector do |selector, declarations, _spec|
assert_equal '@font-face', selector
assert_equal "font-family: 'some_font'; " \
"src: url(https://example.com/font.woff2) format('woff2')," \
"url(https://example.com/font.woff) format('woff'); " \
"font-style: normal;", declarations
end
end
def test_find_rule_sets
css = <<-CSS
h1, h2 { color: blue; }
h1 { font-size: 10px; }
h2 { font-size: 5px; }
article h3 { color: black; }
article
h3 { background-color: white; }
CSS
@cp.add_block!(css)
assert_equal 2, @cp.find_rule_sets(["h2"]).size
assert_equal 3, @cp.find_rule_sets(["h1", "h2"]).size
assert_equal 2, @cp.find_rule_sets(["article h3"]).size
assert_equal 2, @cp.find_rule_sets([" article \t \n h3 \n "]).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
# 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; }"
new_rule = RuleSet.new('div', "{ background-color: black !important; }")
assert_equal 'div { background-color: black !important; }', new_rule.to_s
end
def test_content_with_data
rule = RuleSet.new('div', '{content: url(data:image/png;base64,LOTSOFSTUFF)}')
assert_includes rule.to_s, "image/png;base64,LOTSOFSTUFF"
end
def test_enumerator_empty
assert_kind_of Enumerator, @cp.each_selector
end
def test_enumerator_nonempty
@cp.add_block! 'body {color: black;}'
assert_kind_of Enumerator, @cp.each_selector
@cp.each_selector.each do |sel, desc, _spec|
assert_equal 'body', sel
assert_equal 'color: black;', desc
end
end
end
css_parser-1.16.0/test/test_css_parser_offset_capture.rb 0000664 0000000 0000000 00000007252 14474270710 0023557 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'test_helper'
# Test cases for the CssParser's loading functions.
class CssParserOffsetCaptureTests < Minitest::Test
include CssParser
def setup
@cp = Parser.new
end
def test_capturing_offsets_for_local_file
file_name = File.expand_path('fixtures/simple.css', __dir__)
@cp.load_file!(file_name, capture_offsets: true)
rules = @cp.find_rule_sets(['body', 'p'])
# check that we found the body rule where we expected
assert_equal 0, rules[0].offset.first
assert_equal 43, rules[0].offset.last
assert_equal file_name, rules[0].filename
# and the p rule
assert_equal 45, rules[1].offset.first
assert_equal 63, rules[1].offset.last
assert_equal file_name, rules[1].filename
end
# http://github.com/premailer/css_parser/issues#issue/4
def test_capturing_offsets_from_remote_file
# TODO: test SSL locally
@cp.load_uri!("https://dialect.ca/inc/screen.css", capture_offsets: true)
# there are a lot of rules in this file, but check some rule offsets
rules = @cp.find_rule_sets(['#container', '#name_case_converter textarea'])
assert_equal 2, rules.count
assert_equal 2172, rules.first.offset.first
assert_equal 2227, rules.first.offset.last
assert_equal 'https://dialect.ca/inc/screen.css', rules.first.filename
assert_equal 10_703, rules.last.offset.first
assert_equal 10_752, rules.last.offset.last
assert_equal 'https://dialect.ca/inc/screen.css', rules.last.filename
end
def test_capturing_offsets_from_string
css = <<-CSS
body { margin: 0px; }
p { padding: 0px; }
#content { font: 12px/normal sans-serif; }
.content { color: red; }
CSS
@cp.load_string!(css, capture_offsets: true, filename: 'index.html')
rules = @cp.find_rule_sets(['body', 'p', '#content', '.content'])
assert_equal 4, rules.count
assert_equal 6, rules[0].offset.first
assert_equal 27, rules[0].offset.last
assert_equal 'index.html', rules[0].filename
assert_equal 34, rules[1].offset.first
assert_equal 53, rules[1].offset.last
assert_equal 'index.html', rules[1].filename
assert_equal 60, rules[2].offset.first
assert_equal 102, rules[2].offset.last
assert_equal 'index.html', rules[2].filename
assert_equal 109, rules[3].offset.first
assert_equal 133, rules[3].offset.last
assert_equal 'index.html', rules[3].filename
end
def test_capturing_offsets_with_imports
base_dir = Pathname.new(__dir__).join('fixtures')
@cp.load_file!('import1.css', base_dir: base_dir, capture_offsets: true)
rules = @cp.find_rule_sets(['div', 'a', 'body', 'p'])
# check that we found the div rule where we expected in the primary file
assert_equal 'div', rules[0].selectors.join
assert_equal 31, rules[0].offset.first
assert_equal 51, rules[0].offset.last
assert_equal base_dir.join('import1.css').to_s, rules[0].filename
# check that the a rule in the first import is where we expect
assert_equal 'a', rules[1].selectors.join
assert_equal 26, rules[1].offset.first
assert_equal 54, rules[1].offset.last
assert_equal base_dir.join('subdir/import2.css').to_s, rules[1].filename
# and the body rule in the second import
assert_equal 'body', rules[2].selectors.join
assert_equal 0, rules[2].offset.first
assert_equal 43, rules[2].offset.last
assert_equal base_dir.join('simple.css').to_s, rules[2].filename
# as well as the p rule in the second import
assert_equal 'p', rules[3].selectors.join
assert_equal 45, rules[3].offset.first
assert_equal 63, rules[3].offset.last
assert_equal base_dir.join('simple.css').to_s, rules[3].filename
end
end
css_parser-1.16.0/test/test_css_parser_regexps.rb 0000664 0000000 0000000 00000007050 14474270710 0022217 0 ustar 00root root 0000000 0000000 # coding: iso-8859-1
# frozen_string_literal: true
require_relative '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 < Minitest::Test
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_box_model_units
%w[auto inherit 80px 90pt 80pc 80rem 80vh 70vm 60vw 1vmin 2vmax 0 2em 3ex 1cm 100mm 2in 120%].each do |str|
assert_match(CssParser::BOX_MODEL_UNITS_RX, str)
end
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: hsla(-15, -77%, 19%, 5%);',
'color: black', 'color:Red;', 'color: AqUa;', 'color: blue ', 'color: transparent',
'color: darkslategray'
].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',
'color: alice_blue'
].each do |colour|
refute_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 ;")
refute_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
test_file
end
end
css_parser-1.16.0/test/test_helper.rb 0000664 0000000 0000000 00000000214 14474270710 0017570 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require 'bundler/setup'
require 'maxitest/autorun'
require 'net/http'
require 'webrick'
require 'css_parser'
css_parser-1.16.0/test/test_merging.rb 0000664 0000000 0000000 00000007641 14474270710 0017754 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'test_helper'
class MergingTests < Minitest::Test
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_raises 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.16.0/test/test_rule_set.rb 0000664 0000000 0000000 00000007072 14474270710 0020144 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'test_helper'
require "set"
# Test cases for parsing CSS blocks
class RuleSetTests < Minitest::Test
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[
{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
def test_not_raised_issue68
ok = true
begin
RuleSet.new('td', 'border-top: 5px solid; border-color: #fffff0;')
rescue
ok = false
end
assert_equal true, ok
end
end
css_parser-1.16.0/test/test_rule_set_creating_shorthand.rb 0000664 0000000 0000000 00000017112 14474270710 0024066 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'test_helper'
# Test cases for reading and generating CSS shorthand properties
class RuleSetCreatingShorthandTests < Minitest::Test
include CssParser
def setup
@cp = CssParser::Parser.new
end
def test_border_width
combined = create_shorthand('border-width': '1px')
assert_equal '', combined['border']
assert_equal '1px;', combined['border-width']
end
def test_border_width_with_border_color_with_spaces
combined = create_shorthand(
'border-width': '1px',
'border-color': 'rgb(0 0 0 / 1)',
'border-style': 'solid'
)
assert_equal '', combined['border']
assert_equal '1px;', combined['border-width']
assert_equal 'rgb(0 0 0 / 1);', combined['border-color']
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)',
'border-style' => 'solid'
}
combined = create_shorthand(properties)
assert_equal '22% solid rgba(255, 0, 0);', combined['border']
assert_equal '', combined['border-width']
assert_equal '', combined['border-color']
assert_equal '', combined['border-style']
properties = {
'border-top-style' => 'none',
'border-right-style' => 'none',
'border-bottom-style' => 'none',
'border-left-style' => 'none'
}
combined = create_shorthand(properties)
assert_equal '', combined['border']
assert_equal 'none;', combined['border-style']
properties = {
'border-top-color' => '#bada55',
'border-right-color' => '#000000',
'border-bottom-color' => '#ffffff',
'border-left-color' => '#ff0000'
}
combined = create_shorthand(properties)
assert_equal '#bada55 #000000 #ffffff #ff0000;', combined['border-color']
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
def test_combining_background_with_size_into_shorthand
properties = {
'background-image' => 'url(\'chess.png\')', 'background-color' => 'gray',
'background-position' => 'center -10.2%', 'background-attachment' => 'fixed',
'background-repeat' => 'no-repeat', 'background-size' => '50% 100%'
}
combined = create_shorthand(properties)
assert_equal('gray url(\'chess.png\') no-repeat center -10.2% / 50% 100% fixed;', combined['background'])
# after creating shorthand, all long-hand properties should be deleted
assert_properties_are_deleted(combined, properties)
end
def test_combining_background_with_size_and_no_position_into_shorthand
properties = {
'background-image' => 'url(\'chess.png\')', 'background-color' => 'gray',
'background-attachment' => 'fixed', 'background-repeat' => 'no-repeat',
'background-size' => '50% 100%'
}
combined = create_shorthand(properties)
assert_equal('gray url(\'chess.png\') no-repeat 0% 0% / 50% 100% 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
def test_a_single_property_is_not_shorted
properties = {'background-color' => 'gray'}
combined = create_shorthand(properties)
assert_equal('gray;', combined['background-color'])
assert_equal('', combined['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.16.0/test/test_rule_set_expanding_shorthand.rb 0000664 0000000 0000000 00000034511 14474270710 0024251 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require_relative 'test_helper'
class RuleSetExpandingShorthandTests < Minitest::Test
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-color: #000000 #bada55 #ffffff #ff0000')
assert_equal '#000000', declarations['border-top-color']
assert_equal '#bada55', declarations['border-right-color']
assert_equal '#ffffff', declarations['border-bottom-color']
assert_equal '#ff0000', declarations['border-left-color']
declarations = expand_declarations('border-color: #000000 #bada55 #ffffff')
assert_equal '#000000', declarations['border-top-color']
assert_equal '#bada55', declarations['border-right-color']
assert_equal '#ffffff', declarations['border-bottom-color']
assert_equal '#bada55', declarations['border-left-color']
declarations = expand_declarations('border-color: #000000 #bada55')
assert_equal '#000000', declarations['border-top-color']
assert_equal '#bada55', declarations['border-right-color']
assert_equal '#000000', declarations['border-bottom-color']
assert_equal '#bada55', 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'])
end
def test_getting_font_variant_from_shorthand_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
def test_getting_line_height_from_shorthand_with_spaces
['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
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_size_from_shorthand
['em', 'ex', 'in', 'px', 'pt', 'pc', '%'].each do |unit|
shorthand = "background: url('chess.png') gray 30% -0.20/-0.15#{unit} auto repeat fixed;"
declarations = expand_declarations(shorthand)
assert_equal("-0.15#{unit} auto", declarations['background-size'])
end
['cover', 'contain', 'auto', 'initial', 'inherit'].each do |size|
shorthand = "background: url('chess.png') #000fff 0% 50% / #{size} no-repeat fixed;"
declarations = expand_declarations(shorthand)
assert_equal(size, declarations['background-size'])
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
def test_expanding_shorthand_with_replaced_properties_after
shorthand = 'line-height: 0.25px !important; font-style: normal; font: small-caps italic 12px sans-serif; font-size: 12em;'
declarations = expand_declarations(shorthand)
expected_declarations = {
'line-height' => '0.25px',
'font-style' => 'italic',
'font-variant' => 'small-caps',
'font-weight' => 'normal',
'font-family' => 'sans-serif',
'font-size' => '12em'
}
assert_equal expected_declarations, declarations
end
def test_expanding_important_shorthand_with_replaced_properties
shorthand = 'line-height: 0.25px !important; font-style: normal; font: small-caps italic 12px sans-serif !important; font-size: 12em; font-family: emoji !important;'
declarations = expand_declarations(shorthand)
expected_declarations = {
'font-style' => 'italic',
'font-variant' => 'small-caps',
'font-weight' => 'normal',
'line-height' => 'normal',
'font-family' => 'emoji',
'font-size' => '12px'
}
assert_equal expected_declarations, declarations
end
def test_functions_with_many_spaces
shorthand = 'margin: calc(1em / 4 * var(--foo));'
declarations = expand_declarations(shorthand)
expected_declarations = {
'margin-top' => 'calc(1em / 4 * var(--foo))',
'margin-bottom' => 'calc(1em / 4 * var(--foo))',
'margin-left' => 'calc(1em / 4 * var(--foo))',
'margin-right' => 'calc(1em / 4 * var(--foo))'
}
assert_equal expected_declarations, declarations
end
def test_functions_with_no_spaces
shorthand = 'margin: calc(1em/4*4);'
declarations = expand_declarations(shorthand)
expected_declarations = {
'margin-top' => 'calc(1em/4*4)',
'margin-bottom' => 'calc(1em/4*4)',
'margin-left' => 'calc(1em/4*4)',
'margin-right' => 'calc(1em/4*4)'
}
assert_equal expected_declarations, declarations
end
def test_functions_with_one_space
shorthand = 'margin: calc(1em /4);'
declarations = expand_declarations(shorthand)
expected_declarations = {
'margin-top' => 'calc(1em /4)',
'margin-bottom' => 'calc(1em /4)',
'margin-left' => 'calc(1em /4)',
'margin-right' => 'calc(1em /4)'
}
assert_equal expected_declarations, declarations
end
def test_functions_with_commas
shorthand = 'margin: clamp(1rem, 2.5vw, 2rem)'
declarations = expand_declarations(shorthand)
expected_declarations = {
'margin-top' => 'clamp(1rem, 2.5vw, 2rem)',
'margin-bottom' => 'clamp(1rem, 2.5vw, 2rem)',
'margin-left' => 'clamp(1rem, 2.5vw, 2rem)',
'margin-right' => 'clamp(1rem, 2.5vw, 2rem)'
}
assert_equal expected_declarations, declarations
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