forked from elimuhub/parser-lib
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME.md parser-lib
465 lines (341 loc) · 16.8 KB
/
README.md parser-lib
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# CSS Parser
[](https://travis-ci.org/CSSLint/parser-lib)
## Introduction
The ParserLib CSS parser is a CSS3 SAX-inspired parser written in JavaScript.
It handles standard CSS syntax as well as validation (checking of
property names and values) although it is not guaranteed to thoroughly
validate all possible CSS properties.
## Adding to your project
The CSS parser is built for a number of different JavaScript
environments. The most recently released version of the parser
can be found in the `dist` directory when you check out the
repository; run `npm run build` to regenerate them from the
latest sources.
### Node.js
You can use the CSS parser in a `Node.js` script via the standard
`npm` package manager as the `parserlib` package (`npm install parserlib`):
```js
var parserlib = require("parserlib");
var parser = new parserlib.css.Parser();
```
Alternatively, you can copy a single file version of the parser from
`dist/node-parserlib.js` to your own project, and use it as follows:
```js
var parserlib = require("./node-parserlib");
```
### Rhino
To use the CSS parser in a Rhino script, copy the file
`dist/parserlib.js` to your project and then include it at the beginning:
```js
load("parserlib.js");
```
### HTML page
To use the CSS parser on an HTML page, you can either include the entire
library on your page:
```html
<script src="parserlib.js"></script>
```
Or include it as its component parts, the ParserLib core and the CSS parser:
```html
<script src="parserlib-core.js"></script>
<script src="parserlib-css.js"></script>
```
Note that parsing large JavaScript files may cause the browser to
become unresponsive. All three of these files are located in the
`dist` directory.
## Basic usage
You can create a new instance of the parser by using the following code:
```js
var parser = new parserlib.css.Parser();
```
The constructor accepts an options object that specifies additional features
the parser should use. The available options are:
* `starHack` - set to true to treat properties with a leading asterisk as if
the asterisk wasn't there. Default is `false`.
* `underscoreHack` - set to true to treat properties with a leading underscore
as if the underscore wasn't there. Default is `false`.
* `ieFilters` - set to true to accept IE < 8 style `filter` properties.
Default is `false`.
* `strict` - set to true to disable error recovery and stop on the first
syntax error. Default is `false`.
Here's an example with some options set:
```js
var parser = new parserlib.css.Parser({ starHack: true, underscoreHack: true });
```
You can then parse a string of CSS code by passing into the `parse()` method:
```js
parser.parse(someCSSText);
```
The `parse()` method throws an error if a non-recoverable syntax error occurs,
otherwise it finishes silently.
This method does not return a value nor does it build up an abstract syntax
tree (AST) for you, it simply parses the CSS text and fires events at important
moments along the parse.
Note: The `parseStyleSheet()` method is provided for compatibility with
SAC-based APIs but does the exact same thing as `parse()`.
## Understanding syntax units
The CSS parser defines several types that inherit from `parserlib.util.SyntaxUnit`.
These types are designed to give you easy access to all relevant parts of the CSS syntax.
### Media Queries
The `parserlib.css.MediaFeature` type represents a specific media feature in a
media query, such as `(orientation: portrait)` or `(color)`. Essentially, this
type of object represents anything enclosed in parentheses in a media query.
Object of this type have the following properties:
* `name` - the name of the media feature such as "orientation"
* `value` - the value of the media feature (may be `null`)
The `parserlib.css.MediaQuery` type represents all parts of a media query.
Each instance has the following properties:
* `modifier` - either "not" or "only"
* `mediaType` - the actual media type such as "print"
* `features` - an array of `parserlib.css.MediaFeature` objects
For example, consider the following media query:
```css
only screen and (max-device-width: 768px) and (orientation: portrait)
```
A corresponding object would have the following values:
* `modifier` = "only"
* `mediaType` = "screen"
* `features` = array of (`name`="max-device-width", `value`="768px") and (`name`="orientation", `value`="portrait")
### Properties
The `parserlib.css.PropertyName` type represents a property name. Each instance has the following properties:
* `hack` - if star or underscore hacks are allowed, either `*` or `_` if present (`null` if not present or hacks are not allowed)
When star hacks are allowed, the `text` property becomes the actual property name,
so `*width` has `hack` equal to `*` and `text` equal to "width". If no hacks are allowed,
then `*width` causes a syntax error while `_width` has `hack` equal to `null` and `text` equal to `_width`.
The `parserlib.css.PropertyValue` type represents a property value. Since property values in CSS are complex,
this type of object wraps the various parts into a single interface. Each instance has the following properties:
* `parts` - array of `PropertyValuePart` objects
The `parts` array always has at least one item.
The `parserlib.css.PropertyValuePart` type represents an individual part of a
property value. Each instance has the following properties:
* `type` - the type of value part ("unknown", "dimension", "percentage", "integer", "number", "color", "uri", "string", "identifier" or "operator")
A part is considered any atomic piece of a property value not including white space. Consider the following:
```css
font: 1em/1.5em "Times New Roman", Times, serif;
```
The `PropertyName` is "font" and the `PropertyValue` represents everything after the colon.
The parts are "1em" (dimension), "/" (operator), "1.5em" (dimension), "Times New Roman" (string),
"," (operator), "Times" (identifier), "," (operator), and "serif" (identifier).
### Selectors
The `parserlib.css.Selector` type represents a single selector. Each instance
has a `parts` property, which is an array of `parserlib.css.SelectorPart` objects,
which represent atomic parts of the selector, and `parserlib.css.Combinator`
objects, which represent combinators in the selector.
Consider the following selector:
```css
li.selected > a:hover
```
This selector has three parts: `li.selected`, `>`, and `a:hover`. The first
part is a `SelectorPart`, the second is a `Combinator`, and the third is a
`SelectorPart`. Each `SelectorPart` is made up of an optional element name
followed by an ID, class, attribute condition, pseudo class, and/or pseudo element.
Each instance of `parserlib.css.SelectorPart` has an `elementName` property, which represents
the element name as a `parserlib.css.SelectorSubPart` object or `null` if there isn't one,
and a `modifiers` property, which is an array of `parserlib.css.SelectorSubPart` objects.
Each `SelectorSubPart` object represents the smallest individual piece of a selector
and has a `type` property indicating the type of subpart, "elementName", "class", "attribute",
"pseudo", "id", "not". If the `type` is "not", then the `args` property contains an array
of `SelectorPart` arguments that were passed to `not()`.
Each instance of `parserlib.css.Combinator` has an additional `type` property that indicates
the type of combinator: "descendant", "child", "sibling", or "adjacent-sibling".
## Using events
The CSS parser fires events as it parses text. The events correspond to important parts
of the parsing algorithm and are designed to provide developers with all of the information
necessary to create lint checkers, ASTs, and other data structures.
For many events, the `event` object contains additional information. This additional
information is most frequently in the form of a `parserlib.util.SyntaxUnit` object,
which has three properties:
1. `text` - the string value
2. `line` - the line on which this token appeared
3. `col` - the column within the line at which this token appeared
The `toString()` method for these objects is overridden to be the same value as `text`,
so that you can treat the object as a string for comparison and concatenation purposes.
You should assign your event handlers before calling the `parse()` method.
### `startstylesheet` and `endstylesheet` events
The `startstylesheet` event fires just before parsing of the CSS text begins
and the `endstylesheet` event fires just after all of the CSS text has been parsed.
There is no additional information provided for these events. Example:
```js
parser.addListener("startstylesheet", function() {
console.log("Starting to parse stylesheet");
});
parser.addListener("endstylesheet", function() {
console.log("Finished parsing stylesheet");
});
```
### `charset` event
The `charset` event fires when the `@charset` directive is found in a stylesheet.
Since `@charset` is required to appear first in a stylesheet, any other occurances
cause a syntax error. The `charset` event provides an `event` object with a property
called `charset`, which contains the name of the character set for the stylesheet. Example:
```js
parser.addListener("charset", function(event) {
console.log("Character set is " + event.charset);
});
```
### `namespace` event
The `namespace` event fires when the `@namespace` directive is found in a stylesheet.
The `namespace` event provides an `event` object with two properties: `prefix`,
which is the namespace prefix, and `uri`, which is the namespace URI. Example:
```js
parser.addListener("namespace", function(event) {
console.log("Namespace with prefix=" + event.prefix + " and URI=" + event.uri);
});
```
### `import` event
The `import` event fires when the `@import` directive is found in a stylesheet.
The `import` event provides an `event` object with two properties: `uri`,
which is the URI to import, and `media`, which is an array of media queries
for which this URI applies. The `media` array contains zero or more
`parserlib.css.MediaQuery` objects. Example:
```js
parser.addListener("import", function(event) {
console.log("Importing " + event.uri + " for media types [" + event.media + "]");
});
```
### `startfontface` and `endfontface` events
The `startfontface` event fires when `@font-face` is encountered and the `endfontface` event
fires just after the closing right brace (`}`) is encountered after `@font-face`.
There is no additional information available on the `event` object. Example:
```js
parser.addListener("startfontface", function(event) {
console.log("Starting font face");
});
parser.addListener("endfontface", function(event) {
console.log("Ending font face");
});
```
### `startpage` and `endpage` events
The `startpage` event fires when `@page` is encountered and the `endpage` event
fires just after the closing right brace (`}`) is encountered after `@page`.
The `event` object has two properties: `id`, which is the page ID, and `pseudo`,
which is the page pseudo class. Example:
```js
parser.addListener("startpage", function(event) {
console.log("Starting page with ID=" + event.id + " and pseudo=" + event.pseudo);
});
parser.addListener("endpage", function(event) {
console.log("Ending page with ID=" + event.id + " and pseudo=" + event.pseudo);
});
```
### `startpagemargin` and `endpagemargin` events
The `startpagemargin` event fires when a page margin directive (such as `@top-left`)
is encountered and the `endfontface` event fires just after the closing right brace (`}`)
is encountered after the page margin. The `event` object has a `margin` property,
which contains the actual page margin encountered. Example:
```js
parser.addListener("startpagemargin", function(event) {
console.log("Starting page margin " + event.margin);
});
parser.addListener("endpagemargin", function(event) {
console.log("Ending page margin " + event.margin);
});
```
### `startmedia` and `endmedia` events
The `startmedia` event fires when `@media` is encountered and the `endmedia`
event fires just after the closing right brace (`}`) is encountered after
`@media`. The `event` object has one property, `media`, which is an array of
`parserlib.css.MediaQuery` objects. Example:
```js
parser.addListener("startpagemargin", function(event) {
console.log("Starting page margin " + event.margin);
});
parser.addListener("endpagemargin", function(event) {
console.log("Ending page margin " + event.margin);
});
```
### `startkeyframes` and `endkeyframes` events
The `startkeyframes` event fires when `@keyframes` (or any vendor prefixed version)
is encountered and the `endkeyframes` event fires just after the closing right brace (`}`)
is encountered after `@keyframes`. The `event` object has one property, `name`,
which is the name of the animation. Example:
```js
parser.addListener("startkeyframes", function(event) {
console.log("Starting animation definition " + event.name);
});
parser.addListener("endkeyframes", function(event) {
console.log("Ending animation definition " + event.name);
});
```
### `startrule` and `endrule` events
The `startrule` event fires just after all selectors on a rule have been parsed
and the `endrule` event fires just after the closing right brace (`}`)
is encountered for the rule. The `event` object has one additional property, `selectors`,
which is an array of `parserlib.css.Selector` objects. Example:
```js
parser.addListener("startrule", function(event) {
console.log("Starting rule with " + event.selectors.length + " selector(s)");
for (var i = 0, len = event.selectors.length; i < len; i++) {
var selector = event.selectors[i];
console.log(" Selector #1 (" + selector.line + "," + selector.col + ")");
for (var j = 0, count=selector.parts.length; j < count; j++) {
console.log(" Unit #" + (j + 1));
if (selector.parts[j] instanceof parserlib.css.SelectorPart) {
console.log(" Element name: " + selector.parts[j].elementName);
for (var k = 0; k < selector.parts[j].modifiers.length; k++) {
console.log(" Modifier: " + selector.parts[j].modifiers[k]);
}
} else {
console.log(" Combinator: " + selector.parts[j]);
}
}
}
});
parser.addListener("endrule", function(event) {
console.log("Ending rule with selectors [" + event.selectors + "]");
});
```
### `property` event
The `property` event fires whenever a CSS property (`name:value`) is encountered,
which may be inside of a rule, a media block, a page block, etc. The `event` object
has four additional properties: `property`, which is the name of the property as a
`parserlib.css.PropertyName` object, `value`, which is an instance of
`parserlib.css.PropertyValue` (both types inherit from `parserlib.util.SyntaxUnit`),
`important`, which is a Boolean value indicating if the property is flagged
with `!important`, and `invalid` which is a Boolean value indicating
whether the property value failed validation. Example:
```js
parser.addListener("property", function(event) {
console.log("Property '" + event.property + "' has a value of '" + event.value + "' and " + (event.important ? "is" : "isn't") + " important. (" + event.property.line + "," + event.property.col + ")");
});
```
### `error` event
The `error` event fires whenever a recoverable error occurs during parsing.
When in strict mode, this event does not fire. The `event` object contains three
additional properties: `message`, which is the error message, `line`, which is the line
on which the error occurred, and `col`, which is the column on that line in which
the error occurred. Example:
```js
parser.addListener("error", function(event) {
console.log("Parse error: " + event.message + " (" + event.line + "," + event.col + ")", "error");
});
```
## Error recovery
The CSS parser's goal is to be on-par with error recovery of CSS parsers in browsers.
To that end, the following error recovery mechanisms are in place:
* **Properties** - a syntactically incorrect property definition will be
skipped over completely. For instance, the second property below is dropped:
```css
a:hover {
color: red;
font:: Helvetica; /* dropped! */
text-decoration: underline;
}
```
* **Selectors** - if there's a syntax error in *any* selector, the entire rule
is skipped over. For instance, the following rule is completely skipped:
```css
a:hover, foo ... bar {
color: red;
font: Helvetica;
text-decoration: underline;
}
```
* **@ Rules** - there are certain @ rules that are only valid in certain
contexts. The parser will skip over `@charset`, `@namespace`, and `@import`
if they're found anywhere other than the beginning of the input.
* **Unknown @ Rules** - any @ rules that isn't recognized is automatically
skipped, meaning the entire block after it is not parsed.
## Running Tests
You can run the tests via `npm test` from the repository's root. You
may need to run `npm install` first to install the necessary dependencies.