crass 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +25 -0
- data/HISTORY.md +45 -77
- data/LICENSE +1 -1
- data/README.md +32 -93
- data/crass.gemspec +17 -9
- data/lib/crass/parser.rb +125 -57
- data/lib/crass/scanner.rb +30 -11
- data/lib/crass/tokenizer.rb +85 -39
- data/lib/crass/version.rb +1 -1
- data/lib/crass.rb +14 -2
- metadata +14 -12
- data/.travis.yml +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a90afad390dcbfa666bb15dcc55ff6bc7711a4fabd9e5a920c53783bc22d2c8b
|
|
4
|
+
data.tar.gz: ba5fe09c8233f06503da5bf0f89a9c716cb19aa6b867eb6e1bff3f3d7153446d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efc02970ebefe0e0baf624b97c4aa43f9ce95c39acb7c3a8ccd8e5e74d56545bf9ee4efb0b5d67ba4c5e80f1c13563e8fbc1332feea7cd0ac6c207312c57cab4
|
|
7
|
+
data.tar.gz: 999316893f90a5f3a7fc9cd48dd20f2f1afd8c3087e772bec065bde19bfe08ad59d7430e786b232bd03f78a3f8054bc79cb4800130cab66ddaf93aff1dcc4a47
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
on: [ push, pull_request ]
|
|
3
|
+
jobs:
|
|
4
|
+
test:
|
|
5
|
+
strategy:
|
|
6
|
+
fail-fast: false
|
|
7
|
+
matrix:
|
|
8
|
+
os:
|
|
9
|
+
- ubuntu-latest
|
|
10
|
+
- macos-latest
|
|
11
|
+
ruby:
|
|
12
|
+
- '3.3'
|
|
13
|
+
- '3.4'
|
|
14
|
+
- jruby
|
|
15
|
+
- ruby
|
|
16
|
+
- ruby-head
|
|
17
|
+
continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
|
|
18
|
+
runs-on: ${{ matrix.os }}
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v7
|
|
21
|
+
- uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314.0
|
|
22
|
+
with:
|
|
23
|
+
bundler-cache: true
|
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
|
25
|
+
- run: bundle exec rake
|
data/HISTORY.md
CHANGED
|
@@ -1,121 +1,89 @@
|
|
|
1
|
-
Crass
|
|
2
|
-
====================
|
|
1
|
+
# Crass Changelog
|
|
3
2
|
|
|
4
|
-
1.0.
|
|
5
|
-
------------------
|
|
3
|
+
## 1.0.7 (2026-06-25)
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
### Security
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
- High: Fixed a denial of service vulnerability in which a large numeric exponent could consume disproportionate CPU and memory before the value was clamped. Exponents are now bounded before `10**exponent` is computed. (GHSA-6wmf-3r64-vcwv)
|
|
10
8
|
|
|
9
|
+
- Moderate: Fixed a scenario in which deeply nested simple blocks or functions could exhaust the Ruby stack and raise `SystemStackError`, or could result in excessive memory usage. Parser nesting is now limited to a configurable maximum depth via a new option (`:maximum_depth`, with a conservative default of 25). Constructs nested more deeply are discarded as an `:error` node with the value "maximum-depth-exceeded". (GHSA-6jxj-px6v-747w)
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
------------------
|
|
11
|
+
- Moderate: Fixed a scenario in which a long run of adjacent comments could exhaust the Ruby stack and raise `SystemStackError`. Discarded comments are now skipped iteratively rather than recursively. (GHSA-wwpr-jff3-395c)
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
- Moderate: Fixed a denial of service vulnerability in which inputs containing many non-ASCII characters could cause excessive CPU usage due to inefficient handling of multi-byte characters during tokenization. (GHSA-8vfg-2r28-hvhj)
|
|
16
14
|
|
|
15
|
+
## 1.0.6 (2020-01-12)
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
------------------
|
|
17
|
+
- Number values are now limited to a maximum of `Float::MAX` and a minimum of negative `Float::MAX`. (#11)
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
- Added project metadata to the gemspec. (#9 - @orien)
|
|
22
20
|
|
|
21
|
+
## 1.0.5 (2019-10-15)
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
------------------
|
|
23
|
+
- Removed test files from the gem. (#8 - @t-richards)
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
block (and subsequent tokens until a semicolon) incorrectly appended to its
|
|
29
|
-
prelude. This was super dumb and made me very sad.
|
|
25
|
+
## 1.0.4 (2018-04-08)
|
|
30
26
|
|
|
27
|
+
- Fixed whitespace warnings. (#7 - @yahonda)
|
|
31
28
|
|
|
32
|
-
1.0.
|
|
33
|
-
------------------
|
|
29
|
+
## 1.0.3 (2017-11-13)
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
weren't reflected when that node was stringified. This was a regression
|
|
37
|
-
introduced in 1.0.0.
|
|
31
|
+
- Added support for frozen string literals. (#3 - @flavorjones)
|
|
38
32
|
|
|
33
|
+
## 1.0.2 (2015-04-17)
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
------------------
|
|
35
|
+
- Fixed: An at-rule immediately followed by a `{}` simple block would have the block (and subsequent tokens until a semicolon) incorrectly appended to its prelude. This was super dumb and made me very sad.
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
[14 November 2014 editor's draft][css-syntax-draft] of the CSS syntax spec.
|
|
45
|
-
The most significant outwardly visible change is that quoted URLs like
|
|
46
|
-
`url("foo")` are now returned as `:function` tokens and not `:url` tokens due
|
|
47
|
-
to a change in the tokenization spec.
|
|
37
|
+
## 1.0.1 (2014-11-16)
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
won't notice.
|
|
39
|
+
- Fixed: Modifications made to the block of an `:at_rule` node in a parse tree weren't reflected when that node was stringified. This was a regression introduced in 1.0.0.
|
|
51
40
|
|
|
52
|
-
|
|
53
|
-
serialization.
|
|
41
|
+
## 1.0.0 (2014-11-16)
|
|
54
42
|
|
|
55
|
-
|
|
56
|
-
encoding error because `StringScanner#peek` is a jerkface.
|
|
43
|
+
- Many parsing and tokenization tweaks to bring us into full compliance with the [14 November 2014 editor's draft](http://dev.w3.org/csswg/css-syntax-3/) of the CSS syntax spec. The most significant outwardly visible change is that quoted URLs like `url("foo")` are now returned as `:function` tokens and not `:url` tokens due to a change in the tokenization spec.
|
|
57
44
|
|
|
58
|
-
|
|
45
|
+
- Teensy tiny speed and memory usage improvements that you almost certainly won't notice.
|
|
59
46
|
|
|
47
|
+
- Fixed: A semicolon following a `@charset` rule would be omitted during serialization.
|
|
60
48
|
|
|
61
|
-
|
|
62
|
-
------------------
|
|
49
|
+
- Fixed: A multibyte char at the beginning of an id token could trigger an encoding error because `StringScanner#peek` is a jerkface.
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
semicolon. [#2][]
|
|
51
|
+
## 0.2.1 (2014-07-22)
|
|
66
52
|
|
|
67
|
-
[#2]
|
|
53
|
+
- Fixed: Error when the last property of a rule has no value and no terminating semicolon. [#2](https://github.com/rgrove/crass/issues/2)
|
|
68
54
|
|
|
55
|
+
## 0.2.0 (2013-10-10)
|
|
69
56
|
|
|
70
|
-
|
|
71
|
-
------------------
|
|
57
|
+
- Added a `:children` field to `:property` nodes. It's an array containing all the nodes that make up the property's value.
|
|
72
58
|
|
|
73
|
-
|
|
74
|
-
the nodes that make up the property's value.
|
|
59
|
+
- Fixed: Incorrect value was given for `:property` nodes whose values contained functions.
|
|
75
60
|
|
|
76
|
-
|
|
77
|
-
functions.
|
|
61
|
+
- Fixed: When parsing the value of an at-rule's block as a list of rules, a selector containing a function (such as `#foo:not(.bar)`) would cause that property and the rest of the token stream to be discarded.
|
|
78
62
|
|
|
79
|
-
|
|
80
|
-
selector containing a function (such as `#foo:not(.bar)`) would cause that
|
|
81
|
-
property and the rest of the token stream to be discarded.
|
|
63
|
+
## 0.1.0 (2013-10-04)
|
|
82
64
|
|
|
65
|
+
- Tokenization is a little over 50% faster.
|
|
83
66
|
|
|
84
|
-
|
|
85
|
-
------------------
|
|
67
|
+
- Added tons of unit tests.
|
|
86
68
|
|
|
87
|
-
|
|
69
|
+
- Added `Crass.parse_properties` and `Crass::Parser.parse_properties`, which can be used to parse the contents of an HTML element's `style` attribute.
|
|
88
70
|
|
|
89
|
-
|
|
71
|
+
- Added `Crass::Parser.parse_rules`, which can be used to parse the contents of an `:at_rule` block like `@media` that may contain style rules.
|
|
90
72
|
|
|
91
|
-
|
|
92
|
-
be used to parse the contents of an HTML element's `style` attribute.
|
|
73
|
+
- Fixed: `Crass::Parser#consume_at_rule` and `#consume_qualified_rule` didn't properly handle already-parsed `:simple_block` nodes in the input, which occurs when parsing rules in the value of an `:at_rule` block.
|
|
93
74
|
|
|
94
|
-
|
|
95
|
-
an `:at_rule` block like `@media` that may contain style rules.
|
|
75
|
+
- Fixed: On `:property` nodes, `:important` is now set to `true` when the property is followed by an "!important" declaration.
|
|
96
76
|
|
|
97
|
-
|
|
98
|
-
properly handle already-parsed `:simple_block` nodes in the input, which
|
|
99
|
-
occurs when parsing rules in the value of an `:at_rule` block.
|
|
77
|
+
- Fixed: "!important" is no longer included in the value of a `:property` node.
|
|
100
78
|
|
|
101
|
-
|
|
102
|
-
property is followed by an "!important" declaration.
|
|
79
|
+
- Fixed: A variety of tokenization bugs uncovered by tests.
|
|
103
80
|
|
|
104
|
-
|
|
81
|
+
- Fixed: Added a workaround for a possible spec bug when an `:at_keyword` is encountered while consuming declarations.
|
|
105
82
|
|
|
106
|
-
|
|
83
|
+
## 0.0.2 (2013-09-30)
|
|
107
84
|
|
|
108
|
-
|
|
109
|
-
encountered while consuming declarations.
|
|
85
|
+
- Fixed: `:at_rule` nodes now have a `:name` key.
|
|
110
86
|
|
|
87
|
+
## 0.0.1 (2013-09-27)
|
|
111
88
|
|
|
112
|
-
|
|
113
|
-
------------------
|
|
114
|
-
|
|
115
|
-
* Fixed: `:at_rule` nodes now have a `:name` key.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
0.0.1 (2013-09-27)
|
|
119
|
-
------------------
|
|
120
|
-
|
|
121
|
-
* Initial release.
|
|
89
|
+
- Initial release.
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -1,64 +1,47 @@
|
|
|
1
|
-
Crass
|
|
2
|
-
=====
|
|
1
|
+
# Crass
|
|
3
2
|
|
|
4
|
-
Crass is a Ruby CSS parser that's fully compliant with the
|
|
5
|
-
[CSS Syntax Level 3][css] specification.
|
|
3
|
+
Crass is a Ruby CSS parser that's fully compliant with the [CSS Syntax Level 3](http://dev.w3.org/csswg/css-syntax/) specification.
|
|
6
4
|
|
|
7
|
-
* [Home](https://github.com/rgrove/crass/)
|
|
8
|
-
* [API Docs](http://rubydoc.info/github/rgrove/crass/master)
|
|
9
|
-
|
|
10
|
-
[](https://travis-ci.org/rgrove/crass)
|
|
11
5
|
[](http://badge.fury.io/rb/crass)
|
|
6
|
+
[](https://github.com/rgrove/crass/actions/workflows/tests.yml)
|
|
7
|
+
|
|
8
|
+
## Links
|
|
9
|
+
|
|
10
|
+
- [Home](https://github.com/rgrove/crass/)
|
|
11
|
+
- [API Docs](https://rubydoc.info/github/rgrove/crass/Crass)
|
|
12
12
|
|
|
13
|
-
Features
|
|
14
|
-
--------
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
## Features
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
2014 editor's draft of the [CSS Syntax Level 3][css] specification.
|
|
16
|
+
- Pure Ruby, with no runtime dependencies other than Ruby 1.9.x or higher.
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
should be able to handle it too.
|
|
18
|
+
- Tokenizes and parses CSS according to the rules defined in the 14 November 2014 editor's draft of the [CSS Syntax Level 3](http://dev.w3.org/csswg/css-syntax/) specification.
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
- Extremely tolerant of broken or invalid CSS. If a browser can handle it, Crass should be able to handle it too.
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
otherwise be discarded according to CSS3 tokenizing rules.
|
|
22
|
+
- Optionally includes comments in the token stream.
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
original whitespace, comments, and indentation.
|
|
24
|
+
- Optionally preserves certain CSS hacks, such as the IE "*" hack, which would otherwise be discarded according to CSS3 tokenizing rules.
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
- Capable of serializing the parse tree back to CSS while maintaining all original whitespace, comments, and indentation.
|
|
33
27
|
|
|
34
|
-
Problems
|
|
35
|
-
--------
|
|
28
|
+
## Problems
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
standards. But compared to the CSS parser in your average browser? Yeah, it's
|
|
39
|
-
slow.
|
|
30
|
+
- Crass isn't terribly fast. I mean, it's Ruby, and it's not really slow by Ruby standards. But compared to the CSS parser in your average browser? Yeah, it's slow.
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
doesn't coalesce selectors, etc. You can do this yourself by consuming the
|
|
43
|
-
parse tree, though.
|
|
32
|
+
- Crass only parses the CSS syntax; it doesn't understand what any of it means, doesn't coalesce selectors, etc. You can do this yourself by consuming the parse tree, though.
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
serialized back to CSS with perfect fidelity, changes made to those nodes
|
|
47
|
-
(except for wholesale removal of nodes) are not reflected in the serialized
|
|
48
|
-
output.
|
|
34
|
+
- While any node in the parse tree (or the parse tree as a whole) can be serialized back to CSS with perfect fidelity, changes made to those nodes (except for wholesale removal of nodes) are not reflected in the serialized output.
|
|
49
35
|
|
|
50
|
-
|
|
51
|
-
any other encoding will be converted to UTF-8.
|
|
36
|
+
- Crass only supports UTF-8 input and doesn't respect `@charset` rules. Input in any other encoding will be converted to UTF-8.
|
|
52
37
|
|
|
53
|
-
Installing
|
|
54
|
-
----------
|
|
38
|
+
## Installing
|
|
55
39
|
|
|
56
40
|
```
|
|
57
41
|
gem install crass
|
|
58
42
|
```
|
|
59
43
|
|
|
60
|
-
Examples
|
|
61
|
-
--------
|
|
44
|
+
## Examples
|
|
62
45
|
|
|
63
46
|
Say you have a string containing some CSS:
|
|
64
47
|
|
|
@@ -76,7 +59,7 @@ Parsing it is simple:
|
|
|
76
59
|
tree = Crass.parse(css, :preserve_comments => true)
|
|
77
60
|
```
|
|
78
61
|
|
|
79
|
-
This returns a big
|
|
62
|
+
This returns a big beautiful parse tree, which looks like this:
|
|
80
63
|
|
|
81
64
|
```ruby
|
|
82
65
|
[{:node=>:comment, :pos=>0, :raw=>"/* Comment! */", :value=>" Comment! "},
|
|
@@ -151,64 +134,20 @@ a:hover {
|
|
|
151
134
|
|
|
152
135
|
Wasn't that exciting?
|
|
153
136
|
|
|
154
|
-
|
|
155
|
-
--------------------
|
|
156
|
-
|
|
157
|
-
As of version 1.0.0, Crass adheres strictly to [SemVer 2.0][semver].
|
|
158
|
-
|
|
159
|
-
[semver]:http://semver.org/spec/v2.0.0.html
|
|
160
|
-
|
|
161
|
-
Contributing
|
|
162
|
-
------------
|
|
163
|
-
|
|
164
|
-
The best way to contribute is to use Crass and [create issues][issue] when you
|
|
165
|
-
run into problems.
|
|
166
|
-
|
|
167
|
-
Pull requests that fix bugs are more than welcome as long as they include tests.
|
|
168
|
-
Please adhere to the style and format of the surrounding code, or I might ask
|
|
169
|
-
you to change things.
|
|
170
|
-
|
|
171
|
-
If you want to add a feature or refactor something, please get in touch first to
|
|
172
|
-
make sure I'm on board with your idea and approach; I'm pretty picky, and I'd
|
|
173
|
-
hate to have to turn down a pull request you spent a lot of time on.
|
|
174
|
-
|
|
175
|
-
[issue]: https://github.com/rgrove/crass/issues/new
|
|
176
|
-
|
|
177
|
-
Acknowledgments
|
|
178
|
-
---------------
|
|
137
|
+
## Versioning
|
|
179
138
|
|
|
180
|
-
|
|
181
|
-
comprehensive [CSS parsing tests][css-tests], which I adapted to create many of
|
|
182
|
-
Crass's tests. They've been invaluable in helping me fix bugs and handle weird
|
|
183
|
-
edge cases, and Crass would be much crappier without them.
|
|
139
|
+
As of version 1.0.0, Crass adheres strictly to [SemVer 2.0](http://semver.org/spec/v2.0.0.html).
|
|
184
140
|
|
|
185
|
-
|
|
186
|
-
work on the [CSS Syntax Level 3][spec] specification, which defines the
|
|
187
|
-
tokenizing and parsing rules that Crass implements.
|
|
141
|
+
## Contributing
|
|
188
142
|
|
|
189
|
-
[
|
|
190
|
-
[simon]:http://exyr.org/about/
|
|
191
|
-
[spec]:http://www.w3.org/TR/css-syntax-3/
|
|
192
|
-
[tab]:http://www.xanthir.com/contact/
|
|
143
|
+
The best way to contribute is to use Crass and [create issues](https://github.com/rgrove/crass/issues/new) when you run into problems.
|
|
193
144
|
|
|
194
|
-
|
|
195
|
-
-------
|
|
145
|
+
Pull requests that fix bugs are more than welcome as long as they include tests. Please adhere to the style and format of the surrounding code, or I might ask you to change things.
|
|
196
146
|
|
|
197
|
-
|
|
147
|
+
If you want to add a feature or refactor something, please get in touch first to make sure I'm on board with your idea and approach; I'm pretty picky, and I'd hate to have to turn down a pull request you spent a lot of time on.
|
|
198
148
|
|
|
199
|
-
|
|
200
|
-
this software and associated documentation files (the ‘Software’), to deal in
|
|
201
|
-
the Software without restriction, including without limitation the rights to
|
|
202
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
203
|
-
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
204
|
-
subject to the following conditions:
|
|
149
|
+
## Acknowledgments
|
|
205
150
|
|
|
206
|
-
|
|
207
|
-
copies or substantial portions of the Software.
|
|
151
|
+
I'm deeply grateful to [Simon Sapin](http://exyr.org/about/) for his wonderfully comprehensive [CSS parsing tests](https://github.com/SimonSapin/css-parsing-tests/), which I adapted to create many of Crass's tests. They've been invaluable in helping me fix bugs and handle weird edge cases, and Crass would be much crappier without them.
|
|
208
152
|
|
|
209
|
-
|
|
210
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
211
|
-
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
212
|
-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
213
|
-
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
214
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
153
|
+
I'm also grateful to [Tab Atkins-Bittner](https://www.xanthir.com/contact/) and Simon Sapin (again!) for their work on the [CSS Syntax Level 3](http://www.w3.org/TR/css-syntax-3/) specification, which defines the tokenizing and parsing rules that Crass implements.
|
data/crass.gemspec
CHANGED
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
require './lib/crass/version'
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
|
-
s.name
|
|
6
|
-
s.summary
|
|
5
|
+
s.name = 'crass'
|
|
6
|
+
s.summary = 'CSS parser based on the CSS Syntax Level 3 spec.'
|
|
7
7
|
s.description = 'Crass is a pure Ruby CSS parser based on the CSS Syntax Level 3 spec.'
|
|
8
|
-
s.version
|
|
9
|
-
s.authors
|
|
10
|
-
s.email
|
|
11
|
-
s.homepage
|
|
12
|
-
s.license
|
|
8
|
+
s.version = Crass::VERSION
|
|
9
|
+
s.authors = ['Ryan Grove']
|
|
10
|
+
s.email = ['ryan@wonko.com']
|
|
11
|
+
s.homepage = 'https://github.com/rgrove/crass/'
|
|
12
|
+
s.license = 'MIT'
|
|
13
|
+
|
|
14
|
+
s.metadata = {
|
|
15
|
+
'bug_tracker_uri' => 'https://github.com/rgrove/crass/issues',
|
|
16
|
+
'changelog_uri' => "https://github.com/rgrove/crass/blob/v#{s.version}/HISTORY.md",
|
|
17
|
+
'documentation_uri' => "https://www.rubydoc.info/gems/crass/#{s.version}",
|
|
18
|
+
'rubygems_mfa_required' => 'true',
|
|
19
|
+
'source_code_uri' => "https://github.com/rgrove/crass/tree/v#{s.version}",
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
s.platform = Gem::Platform::RUBY
|
|
15
23
|
s.required_ruby_version = Gem::Requirement.new('>= 1.9.2')
|
|
@@ -19,6 +27,6 @@ Gem::Specification.new do |s|
|
|
|
19
27
|
s.files = `git ls-files -z`.split("\x0").grep_v(%r{^test/})
|
|
20
28
|
|
|
21
29
|
# Development dependencies.
|
|
22
|
-
s.add_development_dependency 'minitest', '~>
|
|
23
|
-
s.add_development_dependency 'rake',
|
|
30
|
+
s.add_development_dependency 'minitest', '~> 6.0.6'
|
|
31
|
+
s.add_development_dependency 'rake', '~> 13.4.2'
|
|
24
32
|
end
|
data/lib/crass/parser.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Crass
|
|
|
6
6
|
|
|
7
7
|
# Parses a CSS string or list of tokens.
|
|
8
8
|
#
|
|
9
|
-
# 5.
|
|
9
|
+
# 5. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parsing
|
|
10
10
|
class Parser
|
|
11
11
|
BLOCK_END_TOKENS = {
|
|
12
12
|
:'{' => :'}',
|
|
@@ -14,14 +14,28 @@ module Crass
|
|
|
14
14
|
:'(' => :')'
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
# Default maximum nesting depth for simple blocks and functions. This is far
|
|
18
|
+
# higher than any legitimate CSS needs, but far below the depth at which
|
|
19
|
+
# Ruby would raise `SystemStackError` while recursively parsing nested
|
|
20
|
+
# constructs.
|
|
21
|
+
#
|
|
22
|
+
# Keeping this low also bounds memory usage: each nested simple block and
|
|
23
|
+
# function retains a `:tokens` array spanning its descendants for
|
|
24
|
+
# serialization, so the total serialization metadata grows with nesting
|
|
25
|
+
# depth. A modest limit prevents deeply nested (but otherwise valid) input
|
|
26
|
+
# from amplifying memory disproportionately.
|
|
27
|
+
#
|
|
28
|
+
# It can be overridden with the `:maximum_depth` option.
|
|
29
|
+
DEFAULT_MAXIMUM_DEPTH = 25
|
|
30
|
+
|
|
17
31
|
# -- Class Methods ---------------------------------------------------------
|
|
18
32
|
|
|
19
33
|
# Parses CSS properties (such as the contents of an HTML element's `style`
|
|
20
34
|
# attribute) and returns a parse tree.
|
|
21
35
|
#
|
|
22
|
-
# See {
|
|
36
|
+
# See {Crass.parse} for _options_.
|
|
23
37
|
#
|
|
24
|
-
# 5.3.6.
|
|
38
|
+
# 5.3.6. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-list-of-declarations
|
|
25
39
|
def self.parse_properties(input, options = {})
|
|
26
40
|
Parser.new(input, options).parse_properties
|
|
27
41
|
end
|
|
@@ -30,9 +44,9 @@ module Crass
|
|
|
30
44
|
# parse tree. The only difference from {parse_stylesheet} is that CDO/CDC
|
|
31
45
|
# nodes (`<!--` and `-->`) aren't ignored.
|
|
32
46
|
#
|
|
33
|
-
# See {
|
|
47
|
+
# See {Crass.parse} for _options_.
|
|
34
48
|
#
|
|
35
|
-
# 5.3.3.
|
|
49
|
+
# 5.3.3. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-list-of-rules
|
|
36
50
|
def self.parse_rules(input, options = {})
|
|
37
51
|
parser = Parser.new(input, options)
|
|
38
52
|
rules = parser.consume_rules
|
|
@@ -48,9 +62,9 @@ module Crass
|
|
|
48
62
|
|
|
49
63
|
# Parses a CSS stylesheet and returns a parse tree.
|
|
50
64
|
#
|
|
51
|
-
# See {
|
|
65
|
+
# See {Crass.parse} for _options_.
|
|
52
66
|
#
|
|
53
|
-
# 5.3.2.
|
|
67
|
+
# 5.3.2. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-stylesheet
|
|
54
68
|
def self.parse_stylesheet(input, options = {})
|
|
55
69
|
parser = Parser.new(input, options)
|
|
56
70
|
rules = parser.consume_rules(:top_level => true)
|
|
@@ -122,18 +136,20 @@ module Crass
|
|
|
122
136
|
# Initializes a parser based on the given _input_, which may be a CSS string
|
|
123
137
|
# or an array of tokens.
|
|
124
138
|
#
|
|
125
|
-
# See {
|
|
139
|
+
# See {Crass.parse} for _options_.
|
|
126
140
|
def initialize(input, options = {})
|
|
127
141
|
unless input.kind_of?(Enumerable)
|
|
128
142
|
input = Tokenizer.tokenize(input, options)
|
|
129
143
|
end
|
|
130
144
|
|
|
145
|
+
@depth = 0
|
|
146
|
+
@maximum_depth = options[:maximum_depth] || DEFAULT_MAXIMUM_DEPTH
|
|
131
147
|
@tokens = TokenScanner.new(input)
|
|
132
148
|
end
|
|
133
149
|
|
|
134
150
|
# Consumes an at-rule and returns it.
|
|
135
151
|
#
|
|
136
|
-
# 5.4.2.
|
|
152
|
+
# 5.4.2. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-at-rule
|
|
137
153
|
def consume_at_rule(input = @tokens)
|
|
138
154
|
rule = {}
|
|
139
155
|
|
|
@@ -180,7 +196,7 @@ module Crass
|
|
|
180
196
|
# Consumes a component value and returns it, or `nil` if there are no more
|
|
181
197
|
# tokens.
|
|
182
198
|
#
|
|
183
|
-
# 5.4.6.
|
|
199
|
+
# 5.4.6. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-component-value
|
|
184
200
|
def consume_component_value(input = @tokens)
|
|
185
201
|
return nil unless token = input.consume
|
|
186
202
|
|
|
@@ -205,7 +221,7 @@ module Crass
|
|
|
205
221
|
|
|
206
222
|
# Consumes a declaration and returns it, or `nil` on parse error.
|
|
207
223
|
#
|
|
208
|
-
# 5.4.5.
|
|
224
|
+
# 5.4.5. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-declaration
|
|
209
225
|
def consume_declaration(input = @tokens)
|
|
210
226
|
declaration = {}
|
|
211
227
|
value = []
|
|
@@ -272,7 +288,7 @@ module Crass
|
|
|
272
288
|
# * **:strict** - Set to `true` to exclude non-standard `:comment`,
|
|
273
289
|
# `:semicolon`, and `:whitespace` nodes.
|
|
274
290
|
#
|
|
275
|
-
# 5.4.4.
|
|
291
|
+
# 5.4.4. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-list-of-declarations
|
|
276
292
|
def consume_declarations(input = @tokens, options = {})
|
|
277
293
|
declarations = []
|
|
278
294
|
|
|
@@ -322,38 +338,48 @@ module Crass
|
|
|
322
338
|
|
|
323
339
|
# Consumes a function and returns it.
|
|
324
340
|
#
|
|
325
|
-
# 5.4.8.
|
|
341
|
+
# 5.4.8. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-function
|
|
326
342
|
def consume_function(input = @tokens)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
343
|
+
@depth += 1
|
|
344
|
+
|
|
345
|
+
begin
|
|
346
|
+
# Discard functions nested more deeply than the maximum allowed depth to
|
|
347
|
+
# avoid exhausting the Ruby stack on maliciously nested input.
|
|
348
|
+
return discard_block(input) if @depth > @maximum_depth
|
|
349
|
+
|
|
350
|
+
function = {
|
|
351
|
+
:name => input.current[:value],
|
|
352
|
+
:value => [],
|
|
353
|
+
:tokens => [input.current] # Non-standard, used for serialization.
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function[:tokens].concat(input.collect {
|
|
357
|
+
while token = input.consume
|
|
358
|
+
case token[:node]
|
|
359
|
+
when :')'
|
|
360
|
+
break
|
|
361
|
+
|
|
362
|
+
# Non-standard.
|
|
363
|
+
when :comment
|
|
364
|
+
next
|
|
365
|
+
|
|
366
|
+
else
|
|
367
|
+
input.reconsume
|
|
368
|
+
function[:value] << consume_component_value(input)
|
|
369
|
+
end
|
|
346
370
|
end
|
|
347
|
-
|
|
348
|
-
})
|
|
371
|
+
})
|
|
349
372
|
|
|
350
|
-
|
|
373
|
+
create_node(:function, function)
|
|
374
|
+
ensure
|
|
375
|
+
@depth -= 1
|
|
376
|
+
end
|
|
351
377
|
end
|
|
352
378
|
|
|
353
379
|
# Consumes a qualified rule and returns it, or `nil` if a parse error
|
|
354
380
|
# occurs.
|
|
355
381
|
#
|
|
356
|
-
# 5.4.3.
|
|
382
|
+
# 5.4.3. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-qualified-rule
|
|
357
383
|
def consume_qualified_rule(input = @tokens)
|
|
358
384
|
rule = {:prelude => []}
|
|
359
385
|
|
|
@@ -394,7 +420,7 @@ module Crass
|
|
|
394
420
|
|
|
395
421
|
# Consumes a list of rules and returns them.
|
|
396
422
|
#
|
|
397
|
-
# 5.4.1.
|
|
423
|
+
# 5.4.1. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-list-of-rules
|
|
398
424
|
def consume_rules(flags = {})
|
|
399
425
|
rules = []
|
|
400
426
|
|
|
@@ -430,28 +456,70 @@ module Crass
|
|
|
430
456
|
# Consumes and returns a simple block associated with the current input
|
|
431
457
|
# token.
|
|
432
458
|
#
|
|
433
|
-
# 5.4.7.
|
|
459
|
+
# 5.4.7. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-simple-block
|
|
434
460
|
def consume_simple_block(input = @tokens)
|
|
435
|
-
|
|
436
|
-
end_token = BLOCK_END_TOKENS[start_token]
|
|
461
|
+
@depth += 1
|
|
437
462
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
:tokens => [input.current] # Non-standard. Used for serialization.
|
|
443
|
-
}
|
|
463
|
+
begin
|
|
464
|
+
# Discard blocks nested more deeply than the maximum allowed depth to
|
|
465
|
+
# avoid exhausting the Ruby stack on maliciously nested input.
|
|
466
|
+
return discard_block(input) if @depth > @maximum_depth
|
|
444
467
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
break if token[:node] == end_token
|
|
468
|
+
start_token = input.current[:node]
|
|
469
|
+
end_token = BLOCK_END_TOKENS[start_token]
|
|
448
470
|
|
|
449
|
-
|
|
450
|
-
|
|
471
|
+
block = {
|
|
472
|
+
:start => start_token.to_s,
|
|
473
|
+
:end => end_token.to_s,
|
|
474
|
+
:value => [],
|
|
475
|
+
:tokens => [input.current] # Non-standard. Used for serialization.
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
block[:tokens].concat(input.collect do
|
|
479
|
+
while token = input.consume
|
|
480
|
+
break if token[:node] == end_token
|
|
481
|
+
|
|
482
|
+
input.reconsume
|
|
483
|
+
block[:value] << consume_component_value(input)
|
|
484
|
+
end
|
|
485
|
+
end)
|
|
486
|
+
|
|
487
|
+
create_node(:simple_block, block)
|
|
488
|
+
ensure
|
|
489
|
+
@depth -= 1
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# Discards an over-nested simple block or function without recursing, then
|
|
494
|
+
# returns an `:error` node. Assumes `input.current` is the opening token (a
|
|
495
|
+
# `{`, `[`, `(`, or function token).
|
|
496
|
+
#
|
|
497
|
+
# This is reached only when the configured maximum nesting depth is
|
|
498
|
+
# exceeded. It iteratively consumes tokens up to the matching closing token
|
|
499
|
+
# (tracking nested blocks and functions with an explicit stack) so that a
|
|
500
|
+
# deeply nested construct can't exhaust the Ruby stack.
|
|
501
|
+
def discard_block(input)
|
|
502
|
+
opening = input.current
|
|
503
|
+
|
|
504
|
+
stack = [opening[:node] == :function ? :')' : BLOCK_END_TOKENS[opening[:node]]]
|
|
505
|
+
tokens = [opening] # Non-standard. Used for serialization.
|
|
506
|
+
|
|
507
|
+
tokens.concat(input.collect do
|
|
508
|
+
until stack.empty?
|
|
509
|
+
break unless token = input.consume
|
|
510
|
+
|
|
511
|
+
case token[:node]
|
|
512
|
+
when :'{', :'[', :'('
|
|
513
|
+
stack.push(BLOCK_END_TOKENS[token[:node]])
|
|
514
|
+
when :function
|
|
515
|
+
stack.push(:')')
|
|
516
|
+
when stack.last
|
|
517
|
+
stack.pop
|
|
518
|
+
end
|
|
451
519
|
end
|
|
452
520
|
end)
|
|
453
521
|
|
|
454
|
-
create_node(:
|
|
522
|
+
create_node(:error, :value => 'maximum-depth-exceeded', :tokens => tokens)
|
|
455
523
|
end
|
|
456
524
|
|
|
457
525
|
# Creates and returns a new parse node with the given _properties_.
|
|
@@ -479,7 +547,7 @@ module Crass
|
|
|
479
547
|
|
|
480
548
|
# Parses a single component value and returns it.
|
|
481
549
|
#
|
|
482
|
-
# 5.3.7.
|
|
550
|
+
# 5.3.7. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-component-value
|
|
483
551
|
def parse_component_value(input = @tokens)
|
|
484
552
|
input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
|
|
485
553
|
|
|
@@ -506,7 +574,7 @@ module Crass
|
|
|
506
574
|
|
|
507
575
|
# Parses a list of component values and returns an array of parsed tokens.
|
|
508
576
|
#
|
|
509
|
-
# 5.3.8.
|
|
577
|
+
# 5.3.8. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-list-of-component-values
|
|
510
578
|
def parse_component_values(input = @tokens)
|
|
511
579
|
input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
|
|
512
580
|
tokens = []
|
|
@@ -520,7 +588,7 @@ module Crass
|
|
|
520
588
|
|
|
521
589
|
# Parses a single declaration and returns it.
|
|
522
590
|
#
|
|
523
|
-
# 5.3.5.
|
|
591
|
+
# 5.3.5. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-declaration
|
|
524
592
|
def parse_declaration(input = @tokens)
|
|
525
593
|
input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
|
|
526
594
|
|
|
@@ -548,7 +616,7 @@ module Crass
|
|
|
548
616
|
#
|
|
549
617
|
# See {#consume_declarations} for _options_.
|
|
550
618
|
#
|
|
551
|
-
# 5.3.6.
|
|
619
|
+
# 5.3.6. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-list-of-declarations
|
|
552
620
|
def parse_declarations(input = @tokens, options = {})
|
|
553
621
|
input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
|
|
554
622
|
consume_declarations(input, options)
|
|
@@ -582,7 +650,7 @@ module Crass
|
|
|
582
650
|
|
|
583
651
|
# Parses a single rule and returns it.
|
|
584
652
|
#
|
|
585
|
-
# 5.3.4.
|
|
653
|
+
# 5.3.4. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-rule
|
|
586
654
|
def parse_rule(input = @tokens)
|
|
587
655
|
input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
|
|
588
656
|
|
data/lib/crass/scanner.rb
CHANGED
|
@@ -16,6 +16,10 @@ module Crass
|
|
|
16
16
|
|
|
17
17
|
# Position of the next character that will be consumed. This is a character
|
|
18
18
|
# position, not a byte position, so it accounts for multi-byte characters.
|
|
19
|
+
#
|
|
20
|
+
# Byte offsets (used internally for fast substring extraction) are tracked
|
|
21
|
+
# separately by the underlying StringScanner, whose `pos` always reflects
|
|
22
|
+
# the byte offset corresponding to this character position.
|
|
19
23
|
attr_accessor :pos
|
|
20
24
|
|
|
21
25
|
# String being scanned.
|
|
@@ -46,6 +50,11 @@ module Crass
|
|
|
46
50
|
def consume_rest
|
|
47
51
|
result = @scanner.rest
|
|
48
52
|
|
|
53
|
+
# `StringScanner#rest` does not advance the scan pointer, so move it to
|
|
54
|
+
# the end of the input to keep the byte offset in sync with {#pos}. This
|
|
55
|
+
# ensures a subsequent {#marked} extracts the correct substring.
|
|
56
|
+
@scanner.terminate
|
|
57
|
+
|
|
49
58
|
@current = result[-1]
|
|
50
59
|
@pos = @len
|
|
51
60
|
|
|
@@ -61,24 +70,31 @@ module Crass
|
|
|
61
70
|
# Sets the marker to the position of the next character that will be
|
|
62
71
|
# consumed.
|
|
63
72
|
def mark
|
|
64
|
-
@
|
|
73
|
+
@byte_marker = @scanner.pos
|
|
74
|
+
@marker = @pos
|
|
65
75
|
end
|
|
66
76
|
|
|
67
77
|
# Returns the substring between {#marker} and {#pos}, without altering the
|
|
68
78
|
# pointer.
|
|
69
79
|
def marked
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
# Extract the marked text using byte offsets rather than character
|
|
81
|
+
# offsets. Slicing the original string by character offset is O(n) on
|
|
82
|
+
# multi-byte input (Ruby must translate the character index into a byte
|
|
83
|
+
# index), which makes tokenizing non-ASCII input superlinear. Byte slicing
|
|
84
|
+
# is O(length) regardless of how far into the string we are.
|
|
85
|
+
@string.byteslice(@byte_marker, @scanner.pos - @byte_marker) || ''
|
|
75
86
|
end
|
|
76
87
|
|
|
77
88
|
# Returns up to _length_ characters starting at the current position, but
|
|
78
89
|
# doesn't consume them. The number of characters returned may be less than
|
|
79
90
|
# _length_ if the end of the string is reached.
|
|
80
91
|
def peek(length = 1)
|
|
81
|
-
|
|
92
|
+
# Grab the bytes for up to _length_ characters and then take the first
|
|
93
|
+
# _length_ characters. A UTF-8 character is at most four bytes, so `length
|
|
94
|
+
# * 4` bytes always contains at least _length_ whole characters when that
|
|
95
|
+
# many remain. This avoids the O(n) character-offset slice that
|
|
96
|
+
# `@string[pos, length]` would otherwise perform on multi-byte input.
|
|
97
|
+
@string.byteslice(@scanner.pos, length * 4).slice(0, length) || ''
|
|
82
98
|
end
|
|
83
99
|
|
|
84
100
|
# Moves the pointer back one character without changing the value of
|
|
@@ -91,10 +107,13 @@ module Crass
|
|
|
91
107
|
|
|
92
108
|
# Resets the pointer to the beginning of the string.
|
|
93
109
|
def reset
|
|
94
|
-
@
|
|
95
|
-
|
|
96
|
-
@
|
|
97
|
-
@
|
|
110
|
+
@scanner.reset
|
|
111
|
+
|
|
112
|
+
@byte_marker = 0
|
|
113
|
+
@current = nil
|
|
114
|
+
@len = @string.size
|
|
115
|
+
@marker = 0
|
|
116
|
+
@pos = 0
|
|
98
117
|
end
|
|
99
118
|
|
|
100
119
|
# Tries to match _pattern_ at the current position. If it matches, the
|
data/lib/crass/tokenizer.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Crass
|
|
|
5
5
|
|
|
6
6
|
# Tokenizes a CSS string.
|
|
7
7
|
#
|
|
8
|
-
# 4.
|
|
8
|
+
# 4. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#tokenization
|
|
9
9
|
class Tokenizer
|
|
10
10
|
RE_COMMENT_CLOSE = /\*\//
|
|
11
11
|
RE_DIGIT = /[0-9]+/
|
|
@@ -66,19 +66,20 @@ module Crass
|
|
|
66
66
|
|
|
67
67
|
# Consumes a token and returns the token that was consumed.
|
|
68
68
|
#
|
|
69
|
-
# 4.3.1.
|
|
69
|
+
# 4.3.1. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-token
|
|
70
70
|
def consume
|
|
71
|
-
|
|
71
|
+
# Skip over any comments. When comments aren't being preserved this is
|
|
72
|
+
# done iteratively rather than recursively so that a long run of adjacent
|
|
73
|
+
# comments can't exhaust the Ruby stack and raise a `SystemStackError`.
|
|
74
|
+
loop do
|
|
75
|
+
return nil if @s.eos?
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
@s.mark
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
else
|
|
80
|
-
return consume
|
|
81
|
-
end
|
|
79
|
+
comment_token = consume_comments
|
|
80
|
+
break unless comment_token
|
|
81
|
+
|
|
82
|
+
return comment_token if @options[:preserve_comments]
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
# Consume whitespace.
|
|
@@ -271,7 +272,7 @@ module Crass
|
|
|
271
272
|
|
|
272
273
|
# Consumes the remnants of a bad URL and returns the consumed text.
|
|
273
274
|
#
|
|
274
|
-
# 4.3.15.
|
|
275
|
+
# 4.3.15. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-the-remnants-of-a-bad-url
|
|
275
276
|
def consume_bad_url
|
|
276
277
|
text = String.new
|
|
277
278
|
|
|
@@ -297,7 +298,7 @@ module Crass
|
|
|
297
298
|
|
|
298
299
|
# Consumes comments and returns them, or `nil` if no comments were consumed.
|
|
299
300
|
#
|
|
300
|
-
# 4.3.2.
|
|
301
|
+
# 4.3.2. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-comments
|
|
301
302
|
def consume_comments
|
|
302
303
|
if @s.peek(2) == '/*'
|
|
303
304
|
@s.consume
|
|
@@ -322,7 +323,7 @@ module Crass
|
|
|
322
323
|
# next character in the input has already been verified not to be a newline
|
|
323
324
|
# or EOF.
|
|
324
325
|
#
|
|
325
|
-
# 4.3.8.
|
|
326
|
+
# 4.3.8. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-an-escaped-code-point
|
|
326
327
|
def consume_escaped
|
|
327
328
|
return "\ufffd" if @s.eos?
|
|
328
329
|
|
|
@@ -346,7 +347,7 @@ module Crass
|
|
|
346
347
|
|
|
347
348
|
# Consumes an ident-like token and returns it.
|
|
348
349
|
#
|
|
349
|
-
# 4.3.4.
|
|
350
|
+
# 4.3.4. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-an-ident-like-token
|
|
350
351
|
def consume_ident
|
|
351
352
|
value = consume_name
|
|
352
353
|
|
|
@@ -371,7 +372,7 @@ module Crass
|
|
|
371
372
|
|
|
372
373
|
# Consumes a name and returns it.
|
|
373
374
|
#
|
|
374
|
-
# 4.3.12.
|
|
375
|
+
# 4.3.12. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-name
|
|
375
376
|
def consume_name
|
|
376
377
|
result = String.new
|
|
377
378
|
|
|
@@ -403,7 +404,7 @@ module Crass
|
|
|
403
404
|
# original representation, its numeric value, and its type (either
|
|
404
405
|
# `:integer` or `:number`).
|
|
405
406
|
#
|
|
406
|
-
# 4.3.13.
|
|
407
|
+
# 4.3.13. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-number
|
|
407
408
|
def consume_number
|
|
408
409
|
repr = String.new
|
|
409
410
|
type = :integer
|
|
@@ -426,37 +427,46 @@ module Crass
|
|
|
426
427
|
|
|
427
428
|
# Consumes a numeric token and returns it.
|
|
428
429
|
#
|
|
429
|
-
# 4.3.3.
|
|
430
|
+
# 4.3.3. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-numeric-token
|
|
430
431
|
def consume_numeric
|
|
431
432
|
number = consume_number
|
|
433
|
+
repr = number[0]
|
|
434
|
+
value = number[1]
|
|
435
|
+
type = number[2]
|
|
436
|
+
|
|
437
|
+
if type == :integer
|
|
438
|
+
value = value.to_i
|
|
439
|
+
else
|
|
440
|
+
value = value.to_f
|
|
441
|
+
end
|
|
432
442
|
|
|
433
443
|
if start_identifier?(@s.peek(3))
|
|
434
444
|
create_token(:dimension,
|
|
435
|
-
:repr
|
|
436
|
-
:type
|
|
437
|
-
:unit
|
|
438
|
-
:value =>
|
|
445
|
+
:repr => repr,
|
|
446
|
+
:type => type,
|
|
447
|
+
:unit => consume_name,
|
|
448
|
+
:value => value)
|
|
439
449
|
|
|
440
450
|
elsif @s.peek == '%'
|
|
441
451
|
@s.consume
|
|
442
452
|
|
|
443
453
|
create_token(:percentage,
|
|
444
|
-
:repr
|
|
445
|
-
:type
|
|
446
|
-
:value =>
|
|
454
|
+
:repr => repr,
|
|
455
|
+
:type => type,
|
|
456
|
+
:value => value)
|
|
447
457
|
|
|
448
458
|
else
|
|
449
459
|
create_token(:number,
|
|
450
|
-
:repr
|
|
451
|
-
:type
|
|
452
|
-
:value =>
|
|
460
|
+
:repr => repr,
|
|
461
|
+
:type => type,
|
|
462
|
+
:value => value)
|
|
453
463
|
end
|
|
454
464
|
end
|
|
455
465
|
|
|
456
466
|
# Consumes a string token that ends at the given character, and returns the
|
|
457
467
|
# token.
|
|
458
468
|
#
|
|
459
|
-
# 4.3.5.
|
|
469
|
+
# 4.3.5. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-string-token
|
|
460
470
|
def consume_string(ending = nil)
|
|
461
471
|
ending = @s.current if ending.nil?
|
|
462
472
|
value = String.new
|
|
@@ -497,7 +507,7 @@ module Crass
|
|
|
497
507
|
# Consumes a Unicode range token and returns it. Assumes the initial "u+" or
|
|
498
508
|
# "U+" has already been consumed.
|
|
499
509
|
#
|
|
500
|
-
# 4.3.7.
|
|
510
|
+
# 4.3.7. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-unicode-range-token
|
|
501
511
|
def consume_unicode_range
|
|
502
512
|
value = @s.scan(RE_HEX) || String.new
|
|
503
513
|
|
|
@@ -529,7 +539,7 @@ module Crass
|
|
|
529
539
|
# Consumes a URL token and returns it. Assumes the original "url(" has
|
|
530
540
|
# already been consumed.
|
|
531
541
|
#
|
|
532
|
-
# 4.3.6.
|
|
542
|
+
# 4.3.6. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-url-token
|
|
533
543
|
def consume_url
|
|
534
544
|
value = String.new
|
|
535
545
|
|
|
@@ -577,7 +587,7 @@ module Crass
|
|
|
577
587
|
|
|
578
588
|
# Converts a valid CSS number string into a number and returns the number.
|
|
579
589
|
#
|
|
580
|
-
# 4.3.14.
|
|
590
|
+
# 4.3.14. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#convert-a-string-to-a-number
|
|
581
591
|
def convert_string_to_number(str)
|
|
582
592
|
matches = RE_NUMBER_STR.match(str)
|
|
583
593
|
|
|
@@ -588,9 +598,45 @@ module Crass
|
|
|
588
598
|
t = matches[:exponent_sign] == '-' ? -1 : 1
|
|
589
599
|
e = matches[:exponent].to_i
|
|
590
600
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
601
|
+
exponent = t * e
|
|
602
|
+
|
|
603
|
+
# Guard against denial of service via attacker-controlled exponentiation.
|
|
604
|
+
# A tiny input like "1e5000000" would otherwise force Ruby to compute
|
|
605
|
+
# `10**exponent` (an enormous integer) before the value is clamped below,
|
|
606
|
+
# consuming disproportionate CPU and memory. When the exponent's magnitude
|
|
607
|
+
# is far outside the range a finite double can represent, we saturate or
|
|
608
|
+
# underflow without performing the exponentiation.
|
|
609
|
+
if i == 0 && f == 0
|
|
610
|
+
# The mantissa is zero, so the value is zero regardless of the exponent.
|
|
611
|
+
value = s * 0.0
|
|
612
|
+
|
|
613
|
+
elsif exponent - d > Float::MAX_10_EXP + 1
|
|
614
|
+
# The value is larger than `Float::MAX` and would be clamped anyway.
|
|
615
|
+
value = s * Float::MAX
|
|
616
|
+
|
|
617
|
+
elsif exponent + matches[:integer].length < -325
|
|
618
|
+
# The value is smaller than the smallest representable double and would
|
|
619
|
+
# round to zero. (-325 is one less than the smallest subnormal base-10
|
|
620
|
+
# exponent of roughly -324; `matches[:integer].length` is an upper bound
|
|
621
|
+
# on the mantissa's magnitude, ensuring we never zero a representable
|
|
622
|
+
# value.)
|
|
623
|
+
value = s * 0.0
|
|
624
|
+
|
|
625
|
+
else
|
|
626
|
+
# I know this formula looks nutty, but it's exactly what's defined in
|
|
627
|
+
# the spec, and it works.
|
|
628
|
+
value = s * (i + f * 10**-d) * 10**exponent
|
|
629
|
+
|
|
630
|
+
# Maximum and minimum values aren't defined in the spec, but are
|
|
631
|
+
# enforced here for sanity.
|
|
632
|
+
if value > Float::MAX
|
|
633
|
+
value = Float::MAX
|
|
634
|
+
elsif value < -Float::MAX
|
|
635
|
+
value = -Float::MAX
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
value
|
|
594
640
|
end
|
|
595
641
|
|
|
596
642
|
# Creates and returns a new token with the given _properties_.
|
|
@@ -604,7 +650,7 @@ module Crass
|
|
|
604
650
|
|
|
605
651
|
# Preprocesses _input_ to prepare it for the tokenizer.
|
|
606
652
|
#
|
|
607
|
-
# 3.3.
|
|
653
|
+
# 3.3. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#input-preprocessing
|
|
608
654
|
def preprocess(input)
|
|
609
655
|
input = input.to_s.encode('UTF-8',
|
|
610
656
|
:invalid => :replace,
|
|
@@ -619,7 +665,7 @@ module Crass
|
|
|
619
665
|
# identifier. If _text_ is `nil`, the current and next two characters in the
|
|
620
666
|
# input stream will be checked, but will not be consumed.
|
|
621
667
|
#
|
|
622
|
-
# 4.3.10.
|
|
668
|
+
# 4.3.10. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#would-start-an-identifier
|
|
623
669
|
def start_identifier?(text = nil)
|
|
624
670
|
text = @s.current + @s.peek(2) if text.nil?
|
|
625
671
|
|
|
@@ -643,7 +689,7 @@ module Crass
|
|
|
643
689
|
# If _text_ is `nil`, the current and next two characters in the input
|
|
644
690
|
# stream will be checked, but will not be consumed.
|
|
645
691
|
#
|
|
646
|
-
# 4.3.11.
|
|
692
|
+
# 4.3.11. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#starts-with-a-number
|
|
647
693
|
def start_number?(text = nil)
|
|
648
694
|
text = @s.current + @s.peek(2) if text.nil?
|
|
649
695
|
|
|
@@ -679,7 +725,7 @@ module Crass
|
|
|
679
725
|
# valid escape sequence. If _text_ is `nil`, the current and next character
|
|
680
726
|
# in the input stream will be checked, but will not be consumed.
|
|
681
727
|
#
|
|
682
|
-
# 4.3.9.
|
|
728
|
+
# 4.3.9. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#starts-with-a-valid-escape
|
|
683
729
|
def valid_escape?(text = nil)
|
|
684
730
|
text = @s.current + @s.peek if text.nil?
|
|
685
731
|
!!(text[0] == '\\' && text[1] != "\n")
|
data/lib/crass/version.rb
CHANGED
data/lib/crass.rb
CHANGED
|
@@ -6,7 +6,19 @@ module Crass
|
|
|
6
6
|
|
|
7
7
|
# Parses _input_ as a CSS stylesheet and returns a parse tree.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
9
|
+
# Options:
|
|
10
|
+
#
|
|
11
|
+
# * **:maximum_depth** - Maximum nesting depth for simple blocks and
|
|
12
|
+
# functions. Constructs nested more deeply than this are discarded to
|
|
13
|
+
# prevent stack exhaustion. Defaults to {Parser::DEFAULT_MAXIMUM_DEPTH}.
|
|
14
|
+
#
|
|
15
|
+
# * **:preserve_comments** - If `true`, comments will be preserved as
|
|
16
|
+
# `:comment` tokens.
|
|
17
|
+
#
|
|
18
|
+
# * **:preserve_hacks** - If `true`, certain non-standard browser hacks
|
|
19
|
+
# such as the IE "*" hack will be preserved even though they violate
|
|
20
|
+
# CSS 3 syntax rules.
|
|
21
|
+
#
|
|
10
22
|
def self.parse(input, options = {})
|
|
11
23
|
Parser.parse_stylesheet(input, options)
|
|
12
24
|
end
|
|
@@ -14,7 +26,7 @@ module Crass
|
|
|
14
26
|
# Parses _input_ as a string of CSS properties (such as the contents of an
|
|
15
27
|
# HTML element's `style` attribute) and returns a parse tree.
|
|
16
28
|
#
|
|
17
|
-
# See {
|
|
29
|
+
# See {Crass.parse} for _options_.
|
|
18
30
|
def self.parse_properties(input, options = {})
|
|
19
31
|
Parser.parse_properties(input, options)
|
|
20
32
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: crass
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Grove
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: minitest
|
|
@@ -16,28 +15,28 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: 6.0.6
|
|
20
19
|
type: :development
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
25
|
+
version: 6.0.6
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: rake
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
30
|
- - "~>"
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
32
|
+
version: 13.4.2
|
|
34
33
|
type: :development
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
37
|
- - "~>"
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
39
|
+
version: 13.4.2
|
|
41
40
|
description: Crass is a pure Ruby CSS parser based on the CSS Syntax Level 3 spec.
|
|
42
41
|
email:
|
|
43
42
|
- ryan@wonko.com
|
|
@@ -45,8 +44,8 @@ executables: []
|
|
|
45
44
|
extensions: []
|
|
46
45
|
extra_rdoc_files: []
|
|
47
46
|
files:
|
|
47
|
+
- ".github/workflows/tests.yml"
|
|
48
48
|
- ".gitignore"
|
|
49
|
-
- ".travis.yml"
|
|
50
49
|
- ".yardopts"
|
|
51
50
|
- Gemfile
|
|
52
51
|
- HISTORY.md
|
|
@@ -63,8 +62,12 @@ files:
|
|
|
63
62
|
homepage: https://github.com/rgrove/crass/
|
|
64
63
|
licenses:
|
|
65
64
|
- MIT
|
|
66
|
-
metadata:
|
|
67
|
-
|
|
65
|
+
metadata:
|
|
66
|
+
bug_tracker_uri: https://github.com/rgrove/crass/issues
|
|
67
|
+
changelog_uri: https://github.com/rgrove/crass/blob/v1.0.7/HISTORY.md
|
|
68
|
+
documentation_uri: https://www.rubydoc.info/gems/crass/1.0.7
|
|
69
|
+
rubygems_mfa_required: 'true'
|
|
70
|
+
source_code_uri: https://github.com/rgrove/crass/tree/v1.0.7
|
|
68
71
|
rdoc_options: []
|
|
69
72
|
require_paths:
|
|
70
73
|
- lib
|
|
@@ -79,8 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
79
82
|
- !ruby/object:Gem::Version
|
|
80
83
|
version: '0'
|
|
81
84
|
requirements: []
|
|
82
|
-
rubygems_version:
|
|
83
|
-
signing_key:
|
|
85
|
+
rubygems_version: 4.0.10
|
|
84
86
|
specification_version: 4
|
|
85
87
|
summary: CSS parser based on the CSS Syntax Level 3 spec.
|
|
86
88
|
test_files: []
|