/*! CSSLint v1.0.4 Copyright (c) 2016 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. 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. */ var exports = exports || {}; /*! Parser-Lib Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved. 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. */ /* Version v1.1.0, Build time: 6-December-2016 10:31:29 */ var parserlib = (function () { var require; require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o). * @namespace parserlib.css * @class Combinator * @extends parserlib.util.SyntaxUnit * @constructor * @param {String} text The text representation of the unit. * @param {int} line The line of text on which the unit resides. * @param {int} col The column of text on which the unit resides. */ function Combinator(text, line, col) { SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE); /** * The type of modifier. * @type String * @property type */ this.type = "unknown"; //pretty simple if (/^\s+$/.test(text)) { this.type = "descendant"; } else if (text === ">") { this.type = "child"; } else if (text === "+") { this.type = "adjacent-sibling"; } else if (text === "~") { this.type = "sibling"; } } Combinator.prototype = new SyntaxUnit(); Combinator.prototype.constructor = Combinator; },{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){ "use strict"; module.exports = Matcher; var StringReader = require("../util/StringReader"); var SyntaxError = require("../util/SyntaxError"); /** * This class implements a combinator library for matcher functions. * The combinators are described at: * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators */ function Matcher(matchFunc, toString) { this.match = function(expression) { // Save/restore marks to ensure that failed matches always restore // the original location in the expression. var result; expression.mark(); result = matchFunc(expression); if (result) { expression.drop(); } else { expression.restore(); } return result; }; this.toString = typeof toString === "function" ? toString : function() { return toString; }; } /** Precedence table of combinators. */ Matcher.prec = { MOD: 5, SEQ: 4, ANDAND: 3, OROR: 2, ALT: 1 }; /** Simple recursive-descent grammar to build matchers from strings. */ Matcher.parse = function(str) { var reader, eat, expr, oror, andand, seq, mod, term, result; reader = new StringReader(str); eat = function(matcher) { var result = reader.readMatch(matcher); if (result === null) { throw new SyntaxError( "Expected "+matcher, reader.getLine(), reader.getCol()); } return result; }; expr = function() { // expr = oror (" | " oror)* var m = [ oror() ]; while (reader.readMatch(" | ") !== null) { m.push(oror()); } return m.length === 1 ? m[0] : Matcher.alt.apply(Matcher, m); }; oror = function() { // oror = andand ( " || " andand)* var m = [ andand() ]; while (reader.readMatch(" || ") !== null) { m.push(andand()); } return m.length === 1 ? m[0] : Matcher.oror.apply(Matcher, m); }; andand = function() { // andand = seq ( " && " seq)* var m = [ seq() ]; while (reader.readMatch(" && ") !== null) { m.push(seq()); } return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m); }; seq = function() { // seq = mod ( " " mod)* var m = [ mod() ]; while (reader.readMatch(/\s(?![&|\]])/y) !== null) { m.push(mod()); } return m.length === 1 ? m[0] : Matcher.seq.apply(Matcher, m); }; mod = function() { // mod = term ( "?" | "*" | "+" | "#" | "{,}" )? var m = term(); if (reader.readMatch("?") !== null) { return m.question(); } else if (reader.readMatch("*") !== null) { return m.star(); } else if (reader.readMatch("+") !== null) { return m.plus(); } else if (reader.readMatch("#") !== null) { return m.hash(); } else if (reader.readMatch(/\{\s*/y) !== null) { var min = eat(/\d+/y); eat(/\s*,\s*/y); var max = eat(/\d+/y); eat(/\s*\}/y); return m.braces(+min, +max); } return m; }; term = function() { // term = | literal | "[ " expression " ]" if (reader.readMatch("[ ") !== null) { var m = expr(); eat(" ]"); return m; } return Matcher.fromType(eat(/[^ ?*+#{]+/y)); }; result = expr(); if (!reader.eof()) { throw new SyntaxError( "Expected end of string", reader.getLine(), reader.getCol()); } return result; }; /** * Convert a string to a matcher (parsing simple alternations), * or do nothing if the argument is already a matcher. */ Matcher.cast = function(m) { if (m instanceof Matcher) { return m; } return Matcher.parse(m); }; /** * Create a matcher for a single type. */ Matcher.fromType = function(type) { // Late require of ValidationTypes to break a dependency cycle. var ValidationTypes = require("./ValidationTypes"); return new Matcher(function(expression) { return expression.hasNext() && ValidationTypes.isType(expression, type); }, type); }; /** * Create a matcher for one or more juxtaposed words, which all must * occur, in the given order. */ Matcher.seq = function() { var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); if (ms.length === 1) { return ms[0]; } return new Matcher(function(expression) { var i, result = true; for (i = 0; result && i < ms.length; i++) { result = ms[i].match(expression); } return result; }, function(prec) { var p = Matcher.prec.SEQ; var s = ms.map(function(m) { return m.toString(p); }).join(" "); if (prec > p) { s = "[ " + s + " ]"; } return s; }); }; /** * Create a matcher for one or more alternatives, where exactly one * must occur. */ Matcher.alt = function() { var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); if (ms.length === 1) { return ms[0]; } return new Matcher(function(expression) { var i, result = false; for (i = 0; !result && i < ms.length; i++) { result = ms[i].match(expression); } return result; }, function(prec) { var p = Matcher.prec.ALT; var s = ms.map(function(m) { return m.toString(p); }).join(" | "); if (prec > p) { s = "[ " + s + " ]"; } return s; }); }; /** * Create a matcher for two or more options. This implements the * double bar (||) and double ampersand (&&) operators, as well as * variants of && where some of the alternatives are optional. * This will backtrack through even successful matches to try to * maximize the number of items matched. */ Matcher.many = function(required) { var ms = Array.prototype.slice.call(arguments, 1).reduce(function(acc, v) { if (v.expand) { // Insert all of the options for the given complex rule as // individual options. var ValidationTypes = require("./ValidationTypes"); acc.push.apply(acc, ValidationTypes.complex[v.expand].options); } else { acc.push(Matcher.cast(v)); } return acc; }, []); if (required === true) { required = ms.map(function() { return true; }); } var result = new Matcher(function(expression) { var seen = [], max = 0, pass = 0; var success = function(matchCount) { if (pass === 0) { max = Math.max(matchCount, max); return matchCount === ms.length; } else { return matchCount === max; } }; var tryMatch = function(matchCount) { for (var i = 0; i < ms.length; i++) { if (seen[i]) { continue; } expression.mark(); if (ms[i].match(expression)) { seen[i] = true; // Increase matchCount iff this was a required element // (or if all the elements are optional) if (tryMatch(matchCount + ((required === false || required[i]) ? 1 : 0))) { expression.drop(); return true; } // Backtrack: try *not* matching using this rule, and // let's see if it leads to a better overall match. expression.restore(); seen[i] = false; } else { expression.drop(); } } return success(matchCount); }; if (!tryMatch(0)) { // Couldn't get a complete match, retrace our steps to make the // match with the maximum # of required elements. pass++; tryMatch(0); } if (required === false) { return max > 0; } // Use finer-grained specification of which matchers are required. for (var i = 0; i < ms.length; i++) { if (required[i] && !seen[i]) { return false; } } return true; }, function(prec) { var p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND; var s = ms.map(function(m, i) { if (required !== false && !required[i]) { return m.toString(Matcher.prec.MOD) + "?"; } return m.toString(p); }).join(required === false ? " || " : " && "); if (prec > p) { s = "[ " + s + " ]"; } return s; }); result.options = ms; return result; }; /** * Create a matcher for two or more options, where all options are * mandatory but they may appear in any order. */ Matcher.andand = function() { var args = Array.prototype.slice.call(arguments); args.unshift(true); return Matcher.many.apply(Matcher, args); }; /** * Create a matcher for two or more options, where options are * optional and may appear in any order, but at least one must be * present. */ Matcher.oror = function() { var args = Array.prototype.slice.call(arguments); args.unshift(false); return Matcher.many.apply(Matcher, args); }; /** Instance methods on Matchers. */ Matcher.prototype = { constructor: Matcher, // These are expected to be overridden in every instance. match: function() { throw new Error("unimplemented"); }, toString: function() { throw new Error("unimplemented"); }, // This returns a standalone function to do the matching. func: function() { return this.match.bind(this); }, // Basic combinators then: function(m) { return Matcher.seq(this, m); }, or: function(m) { return Matcher.alt(this, m); }, andand: function(m) { return Matcher.many(true, this, m); }, oror: function(m) { return Matcher.many(false, this, m); }, // Component value multipliers star: function() { return this.braces(0, Infinity, "*"); }, plus: function() { return this.braces(1, Infinity, "+"); }, question: function() { return this.braces(0, 1, "?"); }, hash: function() { return this.braces(1, Infinity, "#", Matcher.cast(",")); }, braces: function(min, max, marker, optSep) { var m1 = this, m2 = optSep ? optSep.then(this) : this; if (!marker) { marker = "{" + min + "," + max + "}"; } return new Matcher(function(expression) { var result = true, i; for (i = 0; i < max; i++) { if (i > 0 && optSep) { result = m2.match(expression); } else { result = m1.match(expression); } if (!result) { break; } } return i >= min; }, function() { return m1.toString(Matcher.prec.MOD) + marker; }); } }; },{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){ "use strict"; module.exports = MediaFeature; var SyntaxUnit = require("../util/SyntaxUnit"); var Parser = require("./Parser"); /** * Represents a media feature, such as max-width:500. * @namespace parserlib.css * @class MediaFeature * @extends parserlib.util.SyntaxUnit * @constructor * @param {SyntaxUnit} name The name of the feature. * @param {SyntaxUnit} value The value of the feature or null if none. */ function MediaFeature(name, value) { SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE); /** * The name of the media feature * @type String * @property name */ this.name = name; /** * The value for the feature or null if there is none. * @type SyntaxUnit * @property value */ this.value = value; } MediaFeature.prototype = new SyntaxUnit(); MediaFeature.prototype.constructor = MediaFeature; },{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){ "use strict"; module.exports = MediaQuery; var SyntaxUnit = require("../util/SyntaxUnit"); var Parser = require("./Parser"); /** * Represents an individual media query. * @namespace parserlib.css * @class MediaQuery * @extends parserlib.util.SyntaxUnit * @constructor * @param {String} modifier The modifier "not" or "only" (or null). * @param {String} mediaType The type of media (i.e., "print"). * @param {Array} parts Array of selectors parts making up this selector. * @param {int} line The line of text on which the unit resides. * @param {int} col The column of text on which the unit resides. */ function MediaQuery(modifier, mediaType, features, line, col) { SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); /** * The media modifier ("not" or "only") * @type String * @property modifier */ this.modifier = modifier; /** * The mediaType (i.e., "print") * @type String * @property mediaType */ this.mediaType = mediaType; /** * The parts that make up the selector. * @type Array * @property features */ this.features = features; } MediaQuery.prototype = new SyntaxUnit(); MediaQuery.prototype.constructor = MediaQuery; },{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){ "use strict"; module.exports = Parser; var EventTarget = require("../util/EventTarget"); var SyntaxError = require("../util/SyntaxError"); var SyntaxUnit = require("../util/SyntaxUnit"); var Combinator = require("./Combinator"); var MediaFeature = require("./MediaFeature"); var MediaQuery = require("./MediaQuery"); var PropertyName = require("./PropertyName"); var PropertyValue = require("./PropertyValue"); var PropertyValuePart = require("./PropertyValuePart"); var Selector = require("./Selector"); var SelectorPart = require("./SelectorPart"); var SelectorSubPart = require("./SelectorSubPart"); var TokenStream = require("./TokenStream"); var Tokens = require("./Tokens"); var Validation = require("./Validation"); /** * A CSS3 parser. * @namespace parserlib.css * @class Parser * @constructor * @param {Object} options (Optional) Various options for the parser: * starHack (true|false) to allow IE6 star hack as valid, * underscoreHack (true|false) to interpret leading underscores * as IE6-7 targeting for known properties, ieFilters (true|false) * to indicate that IE < 8 filters should be accepted and not throw * syntax errors. */ function Parser(options) { //inherit event functionality EventTarget.call(this); this.options = options || {}; this._tokenStream = null; } //Static constants Parser.DEFAULT_TYPE = 0; Parser.COMBINATOR_TYPE = 1; Parser.MEDIA_FEATURE_TYPE = 2; Parser.MEDIA_QUERY_TYPE = 3; Parser.PROPERTY_NAME_TYPE = 4; Parser.PROPERTY_VALUE_TYPE = 5; Parser.PROPERTY_VALUE_PART_TYPE = 6; Parser.SELECTOR_TYPE = 7; Parser.SELECTOR_PART_TYPE = 8; Parser.SELECTOR_SUB_PART_TYPE = 9; Parser.prototype = function() { var proto = new EventTarget(), //new prototype prop, additions = { __proto__: null, //restore constructor constructor: Parser, //instance constants - yuck DEFAULT_TYPE : 0, COMBINATOR_TYPE : 1, MEDIA_FEATURE_TYPE : 2, MEDIA_QUERY_TYPE : 3, PROPERTY_NAME_TYPE : 4, PROPERTY_VALUE_TYPE : 5, PROPERTY_VALUE_PART_TYPE : 6, SELECTOR_TYPE : 7, SELECTOR_PART_TYPE : 8, SELECTOR_SUB_PART_TYPE : 9, //----------------------------------------------------------------- // Grammar //----------------------------------------------------------------- _stylesheet: function() { /* * stylesheet * : [ CHARSET_SYM S* STRING S* ';' ]? * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* * [ namespace [S|CDO|CDC]* ]* * [ [ ruleset | media | page | font_face | keyframes_rule | supports_rule ] [S|CDO|CDC]* ]* * ; */ var tokenStream = this._tokenStream, count, token, tt; this.fire("startstylesheet"); //try to read character set this._charset(); this._skipCruft(); //try to read imports - may be more than one while (tokenStream.peek() === Tokens.IMPORT_SYM) { this._import(); this._skipCruft(); } //try to read namespaces - may be more than one while (tokenStream.peek() === Tokens.NAMESPACE_SYM) { this._namespace(); this._skipCruft(); } //get the next token tt = tokenStream.peek(); //try to read the rest while (tt > Tokens.EOF) { try { switch (tt) { case Tokens.MEDIA_SYM: this._media(); this._skipCruft(); break; case Tokens.PAGE_SYM: this._page(); this._skipCruft(); break; case Tokens.FONT_FACE_SYM: this._font_face(); this._skipCruft(); break; case Tokens.KEYFRAMES_SYM: this._keyframes(); this._skipCruft(); break; case Tokens.VIEWPORT_SYM: this._viewport(); this._skipCruft(); break; case Tokens.DOCUMENT_SYM: this._document(); this._skipCruft(); break; case Tokens.SUPPORTS_SYM: this._supports(); this._skipCruft(); break; case Tokens.UNKNOWN_SYM: //unknown @ rule tokenStream.get(); if (!this.options.strict) { //fire error event this.fire({ type: "error", error: null, message: "Unknown @ rule: " + tokenStream.LT(0).value + ".", line: tokenStream.LT(0).startLine, col: tokenStream.LT(0).startCol }); //skip braces count=0; while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) === Tokens.LBRACE) { count++; //keep track of nesting depth } while (count) { tokenStream.advance([Tokens.RBRACE]); count--; } } else { //not a syntax error, rethrow it throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol); } break; case Tokens.S: this._readWhitespace(); break; default: if (!this._ruleset()) { //error handling for known issues switch (tt) { case Tokens.CHARSET_SYM: token = tokenStream.LT(1); this._charset(false); throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); case Tokens.IMPORT_SYM: token = tokenStream.LT(1); this._import(false); throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); case Tokens.NAMESPACE_SYM: token = tokenStream.LT(1); this._namespace(false); throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); default: tokenStream.get(); //get the last token this._unexpectedToken(tokenStream.token()); } } } } catch (ex) { if (ex instanceof SyntaxError && !this.options.strict) { this.fire({ type: "error", error: ex, message: ex.message, line: ex.line, col: ex.col }); } else { throw ex; } } tt = tokenStream.peek(); } if (tt !== Tokens.EOF) { this._unexpectedToken(tokenStream.token()); } this.fire("endstylesheet"); }, _charset: function(emit) { var tokenStream = this._tokenStream, charset, token, line, col; if (tokenStream.match(Tokens.CHARSET_SYM)) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; this._readWhitespace(); tokenStream.mustMatch(Tokens.STRING); token = tokenStream.token(); charset = token.value; this._readWhitespace(); tokenStream.mustMatch(Tokens.SEMICOLON); if (emit !== false) { this.fire({ type: "charset", charset:charset, line: line, col: col }); } } }, _import: function(emit) { /* * import * : IMPORT_SYM S* * [STRING|URI] S* media_query_list? ';' S* */ var tokenStream = this._tokenStream, uri, importToken, mediaList = []; //read import symbol tokenStream.mustMatch(Tokens.IMPORT_SYM); importToken = tokenStream.token(); this._readWhitespace(); tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); //grab the URI value uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, "$1"); this._readWhitespace(); mediaList = this._media_query_list(); //must end with a semicolon tokenStream.mustMatch(Tokens.SEMICOLON); this._readWhitespace(); if (emit !== false) { this.fire({ type: "import", uri: uri, media: mediaList, line: importToken.startLine, col: importToken.startCol }); } }, _namespace: function(emit) { /* * namespace * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* */ var tokenStream = this._tokenStream, line, col, prefix, uri; //read import symbol tokenStream.mustMatch(Tokens.NAMESPACE_SYM); line = tokenStream.token().startLine; col = tokenStream.token().startCol; this._readWhitespace(); //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT if (tokenStream.match(Tokens.IDENT)) { prefix = tokenStream.token().value; this._readWhitespace(); } tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); /*if (!tokenStream.match(Tokens.STRING)){ tokenStream.mustMatch(Tokens.URI); }*/ //grab the URI value uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); this._readWhitespace(); //must end with a semicolon tokenStream.mustMatch(Tokens.SEMICOLON); this._readWhitespace(); if (emit !== false) { this.fire({ type: "namespace", prefix: prefix, uri: uri, line: line, col: col }); } }, _supports: function(emit) { /* * supports_rule * : SUPPORTS_SYM S* supports_condition S* group_rule_body * ; */ var tokenStream = this._tokenStream, line, col; if (tokenStream.match(Tokens.SUPPORTS_SYM)) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; this._readWhitespace(); this._supports_condition(); this._readWhitespace(); tokenStream.mustMatch(Tokens.LBRACE); this._readWhitespace(); if (emit !== false) { this.fire({ type: "startsupports", line: line, col: col }); } var error; while (true) { if (!this._ruleset()) { var token = tokenStream.LT(1); if (token.type === tokenStream._tokenData.MEDIA_SYM) { this._media(); error = new SyntaxError("@media not allowed here.", token.startLine, token.startCol); } break; } } tokenStream.mustMatch(Tokens.RBRACE); this._readWhitespace(); this.fire({ type: "endsupports", line: line, col: col }); if (error) { throw error; } } }, _supports_condition: function() { /* * supports_condition * : supports_negation | supports_conjunction | supports_disjunction | * supports_condition_in_parens * ; */ var tokenStream = this._tokenStream, ident; if (tokenStream.match(Tokens.IDENT)) { ident = tokenStream.token().value.toLowerCase(); if (ident === "not") { tokenStream.mustMatch(Tokens.S); this._supports_condition_in_parens(); } else { tokenStream.unget(); } } else { this._supports_condition_in_parens(); this._readWhitespace(); while (tokenStream.peek() === Tokens.IDENT) { ident = tokenStream.LT(1).value.toLowerCase(); if (ident === "and" || ident === "or") { tokenStream.mustMatch(Tokens.IDENT); this._readWhitespace(); this._supports_condition_in_parens(); this._readWhitespace(); } } } }, _supports_condition_in_parens: function() { /* * supports_condition_in_parens * : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition | * general_enclosed * ; */ var tokenStream = this._tokenStream, ident; if (tokenStream.match(Tokens.LPAREN)) { this._readWhitespace(); if (tokenStream.match(Tokens.IDENT)) { // look ahead for not keyword, if not given, continue with declaration condition. ident = tokenStream.token().value.toLowerCase(); if (ident === "not") { this._readWhitespace(); this._supports_condition(); this._readWhitespace(); tokenStream.mustMatch(Tokens.RPAREN); } else { tokenStream.unget(); this._supports_declaration_condition(false); } } else { this._supports_condition(); this._readWhitespace(); tokenStream.mustMatch(Tokens.RPAREN); } } else { this._supports_declaration_condition(); } }, _supports_declaration_condition: function(requireStartParen) { /* * supports_declaration_condition * : '(' S* declaration ')' * ; */ var tokenStream = this._tokenStream; if (requireStartParen !== false) { tokenStream.mustMatch(Tokens.LPAREN); } this._readWhitespace(); this._declaration(); tokenStream.mustMatch(Tokens.RPAREN); }, _media: function() { /* * media * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* * ; */ var tokenStream = this._tokenStream, line, col, mediaList;// = []; //look for @media tokenStream.mustMatch(Tokens.MEDIA_SYM); line = tokenStream.token().startLine; col = tokenStream.token().startCol; this._readWhitespace(); mediaList = this._media_query_list(); tokenStream.mustMatch(Tokens.LBRACE); this._readWhitespace(); this.fire({ type: "startmedia", media: mediaList, line: line, col: col }); while (true) { if (tokenStream.peek() === Tokens.PAGE_SYM) { this._page(); } else if (tokenStream.peek() === Tokens.FONT_FACE_SYM) { this._font_face(); } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM) { this._viewport(); } else if (tokenStream.peek() === Tokens.DOCUMENT_SYM) { this._document(); } else if (tokenStream.peek() === Tokens.SUPPORTS_SYM) { this._supports(); } else if (tokenStream.peek() === Tokens.MEDIA_SYM) { this._media(); } else if (!this._ruleset()) { break; } } tokenStream.mustMatch(Tokens.RBRACE); this._readWhitespace(); this.fire({ type: "endmedia", media: mediaList, line: line, col: col }); }, //CSS3 Media Queries _media_query_list: function() { /* * media_query_list * : S* [media_query [ ',' S* media_query ]* ]? * ; */ var tokenStream = this._tokenStream, mediaList = []; this._readWhitespace(); if (tokenStream.peek() === Tokens.IDENT || tokenStream.peek() === Tokens.LPAREN) { mediaList.push(this._media_query()); } while (tokenStream.match(Tokens.COMMA)) { this._readWhitespace(); mediaList.push(this._media_query()); } return mediaList; }, /* * Note: "expression" in the grammar maps to the _media_expression * method. */ _media_query: function() { /* * media_query * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* * | expression [ AND S* expression ]* * ; */ var tokenStream = this._tokenStream, type = null, ident = null, token = null, expressions = []; if (tokenStream.match(Tokens.IDENT)) { ident = tokenStream.token().value.toLowerCase(); //since there's no custom tokens for these, need to manually check if (ident !== "only" && ident !== "not") { tokenStream.unget(); ident = null; } else { token = tokenStream.token(); } } this._readWhitespace(); if (tokenStream.peek() === Tokens.IDENT) { type = this._media_type(); if (token === null) { token = tokenStream.token(); } } else if (tokenStream.peek() === Tokens.LPAREN) { if (token === null) { token = tokenStream.LT(1); } expressions.push(this._media_expression()); } if (type === null && expressions.length === 0) { return null; } else { this._readWhitespace(); while (tokenStream.match(Tokens.IDENT)) { if (tokenStream.token().value.toLowerCase() !== "and") { this._unexpectedToken(tokenStream.token()); } this._readWhitespace(); expressions.push(this._media_expression()); } } return new MediaQuery(ident, type, expressions, token.startLine, token.startCol); }, //CSS3 Media Queries _media_type: function() { /* * media_type * : IDENT * ; */ return this._media_feature(); }, /** * Note: in CSS3 Media Queries, this is called "expression". * Renamed here to avoid conflict with CSS3 Selectors * definition of "expression". Also note that "expr" in the * grammar now maps to "expression" from CSS3 selectors. * @method _media_expression * @private */ _media_expression: function() { /* * expression * : '(' S* media_feature S* [ ':' S* expr ]? ')' S* * ; */ var tokenStream = this._tokenStream, feature = null, token, expression = null; tokenStream.mustMatch(Tokens.LPAREN); feature = this._media_feature(); this._readWhitespace(); if (tokenStream.match(Tokens.COLON)) { this._readWhitespace(); token = tokenStream.LT(1); expression = this._expression(); } tokenStream.mustMatch(Tokens.RPAREN); this._readWhitespace(); return new MediaFeature(feature, expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null); }, //CSS3 Media Queries _media_feature: function() { /* * media_feature * : IDENT * ; */ var tokenStream = this._tokenStream; this._readWhitespace(); tokenStream.mustMatch(Tokens.IDENT); return SyntaxUnit.fromToken(tokenStream.token()); }, //CSS3 Paged Media _page: function() { /* * page: * PAGE_SYM S* IDENT? pseudo_page? S* * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* * ; */ var tokenStream = this._tokenStream, line, col, identifier = null, pseudoPage = null; //look for @page tokenStream.mustMatch(Tokens.PAGE_SYM); line = tokenStream.token().startLine; col = tokenStream.token().startCol; this._readWhitespace(); if (tokenStream.match(Tokens.IDENT)) { identifier = tokenStream.token().value; //The value 'auto' may not be used as a page name and MUST be treated as a syntax error. if (identifier.toLowerCase() === "auto") { this._unexpectedToken(tokenStream.token()); } } //see if there's a colon upcoming if (tokenStream.peek() === Tokens.COLON) { pseudoPage = this._pseudo_page(); } this._readWhitespace(); this.fire({ type: "startpage", id: identifier, pseudo: pseudoPage, line: line, col: col }); this._readDeclarations(true, true); this.fire({ type: "endpage", id: identifier, pseudo: pseudoPage, line: line, col: col }); }, //CSS3 Paged Media _margin: function() { /* * margin : * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* * ; */ var tokenStream = this._tokenStream, line, col, marginSym = this._margin_sym(); if (marginSym) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; this.fire({ type: "startpagemargin", margin: marginSym, line: line, col: col }); this._readDeclarations(true); this.fire({ type: "endpagemargin", margin: marginSym, line: line, col: col }); return true; } else { return false; } }, //CSS3 Paged Media _margin_sym: function() { /* * margin_sym : * TOPLEFTCORNER_SYM | * TOPLEFT_SYM | * TOPCENTER_SYM | * TOPRIGHT_SYM | * TOPRIGHTCORNER_SYM | * BOTTOMLEFTCORNER_SYM | * BOTTOMLEFT_SYM | * BOTTOMCENTER_SYM | * BOTTOMRIGHT_SYM | * BOTTOMRIGHTCORNER_SYM | * LEFTTOP_SYM | * LEFTMIDDLE_SYM | * LEFTBOTTOM_SYM | * RIGHTTOP_SYM | * RIGHTMIDDLE_SYM | * RIGHTBOTTOM_SYM * ; */ var tokenStream = this._tokenStream; if (tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM, Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM, Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM, Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) { return SyntaxUnit.fromToken(tokenStream.token()); } else { return null; } }, _pseudo_page: function() { /* * pseudo_page * : ':' IDENT * ; */ var tokenStream = this._tokenStream; tokenStream.mustMatch(Tokens.COLON); tokenStream.mustMatch(Tokens.IDENT); //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed return tokenStream.token().value; }, _font_face: function() { /* * font_face * : FONT_FACE_SYM S* * '{' S* declaration [ ';' S* declaration ]* '}' S* * ; */ var tokenStream = this._tokenStream, line, col; //look for @page tokenStream.mustMatch(Tokens.FONT_FACE_SYM); line = tokenStream.token().startLine; col = tokenStream.token().startCol; this._readWhitespace(); this.fire({ type: "startfontface", line: line, col: col }); this._readDeclarations(true); this.fire({ type: "endfontface", line: line, col: col }); }, _viewport: function() { /* * viewport * : VIEWPORT_SYM S* * '{' S* declaration? [ ';' S* declaration? ]* '}' S* * ; */ var tokenStream = this._tokenStream, line, col; tokenStream.mustMatch(Tokens.VIEWPORT_SYM); line = tokenStream.token().startLine; col = tokenStream.token().startCol; this._readWhitespace(); this.fire({ type: "startviewport", line: line, col: col }); this._readDeclarations(true); this.fire({ type: "endviewport", line: line, col: col }); }, _document: function() { /* * document * : DOCUMENT_SYM S* * _document_function [ ',' S* _document_function ]* S* * '{' S* ruleset* '}' * ; */ var tokenStream = this._tokenStream, token, functions = [], prefix = ""; tokenStream.mustMatch(Tokens.DOCUMENT_SYM); token = tokenStream.token(); if (/^@\-([^\-]+)\-/.test(token.value)) { prefix = RegExp.$1; } this._readWhitespace(); functions.push(this._document_function()); while (tokenStream.match(Tokens.COMMA)) { this._readWhitespace(); functions.push(this._document_function()); } tokenStream.mustMatch(Tokens.LBRACE); this.fire({ type: "startdocument", functions: functions, prefix: prefix, line: token.startLine, col: token.startCol }); this._readWhitespace(); // Stylus hack var ok = true; while (ok) { switch (tokenStream.peek()) { case Tokens.PAGE_SYM: this._page(); break; case Tokens.FONT_FACE_SYM: this._font_face(); break; case Tokens.VIEWPORT_SYM: this._viewport(); break; case Tokens.MEDIA_SYM: this._media(); break; case Tokens.KEYFRAMES_SYM: this._keyframes(); break; case Tokens.DOCUMENT_SYM: this._document(); break; case Tokens.SUPPORTS_SYM: this._supports(); break; default: ok = Boolean(this._ruleset()); } } tokenStream.mustMatch(Tokens.RBRACE); token = tokenStream.token(); this.fire({ type: "enddocument", functions: functions, prefix: prefix, line: token.startLine, col: token.startCol }); this._readWhitespace(); }, _document_function: function() { /* * document_function * : function | URI S* * ; */ var tokenStream = this._tokenStream, value; if (tokenStream.match(Tokens.URI)) { value = tokenStream.token().value; this._readWhitespace(); } else { value = this._function(); value = value && value.text; } return value; }, _operator: function(inFunction) { /* * operator (outside function) * : '/' S* | ',' S* | /( empty )/ * operator (inside function) * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/ * ; */ var tokenStream = this._tokenStream, token = null; if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) || (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))) { token = tokenStream.token(); this._readWhitespace(); } return token ? PropertyValuePart.fromToken(token) : null; }, _combinator: function() { /* * combinator * : PLUS S* | GREATER S* | TILDE S* | S+ * ; */ var tokenStream = this._tokenStream, value = null, token; if (tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])) { token = tokenStream.token(); value = new Combinator(token.value, token.startLine, token.startCol); this._readWhitespace(); } return value; }, _unary_operator: function() { /* * unary_operator * : '-' | '+' * ; */ var tokenStream = this._tokenStream; if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])) { return tokenStream.token().value; } else { return null; } }, _property: function() { /* * property * : IDENT S* * ; */ var tokenStream = this._tokenStream, value = null, hack = null, tokenValue, token, line, col; //check for star hack - throws error if not allowed if (tokenStream.peek() === Tokens.STAR && this.options.starHack) { tokenStream.get(); token = tokenStream.token(); hack = token.value; line = token.startLine; col = token.startCol; } if (tokenStream.match(Tokens.IDENT)) { token = tokenStream.token(); tokenValue = token.value; //check for underscore hack - no error if not allowed because it's valid CSS syntax if (tokenValue.charAt(0) === "_" && this.options.underscoreHack) { hack = "_"; tokenValue = tokenValue.substring(1); } value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol)); this._readWhitespace(); } return value; }, //Augmented with CSS3 Selectors _ruleset: function() { /* * ruleset * : selectors_group * '{' S* declaration? [ ';' S* declaration? ]* '}' S* * ; */ var tokenStream = this._tokenStream, tt, selectors; /* * Error Recovery: If even a single selector fails to parse, * then the entire ruleset should be thrown away. */ try { selectors = this._selectors_group(); } catch (ex) { if (ex instanceof SyntaxError && !this.options.strict) { //fire error event this.fire({ type: "error", error: ex, message: ex.message, line: ex.line, col: ex.col }); //skip over everything until closing brace tt = tokenStream.advance([Tokens.RBRACE]); if (tt === Tokens.RBRACE) { //if there's a right brace, the rule is finished so don't do anything } else { //otherwise, rethrow the error because it wasn't handled properly throw ex; } } else { //not a syntax error, rethrow it throw ex; } //trigger parser to continue return true; } //if it got here, all selectors parsed if (selectors) { this.fire({ type: "startrule", selectors: selectors, line: selectors[0].line, col: selectors[0].col }); this._readDeclarations(true); this.fire({ type: "endrule", selectors: selectors, line: selectors[0].line, col: selectors[0].col }); } return selectors; }, //CSS3 Selectors _selectors_group: function() { /* * selectors_group * : selector [ COMMA S* selector ]* * ; */ var tokenStream = this._tokenStream, selectors = [], selector; selector = this._selector(); if (selector !== null) { selectors.push(selector); while (tokenStream.match(Tokens.COMMA)) { this._readWhitespace(); selector = this._selector(); if (selector !== null) { selectors.push(selector); } else { this._unexpectedToken(tokenStream.LT(1)); } } } return selectors.length ? selectors : null; }, //CSS3 Selectors _selector: function() { /* * selector * : simple_selector_sequence [ combinator simple_selector_sequence ]* * ; */ var tokenStream = this._tokenStream, selector = [], nextSelector = null, combinator = null, ws = null; //if there's no simple selector, then there's no selector nextSelector = this._simple_selector_sequence(); if (nextSelector === null) { return null; } selector.push(nextSelector); do { //look for a combinator combinator = this._combinator(); if (combinator !== null) { selector.push(combinator); nextSelector = this._simple_selector_sequence(); //there must be a next selector if (nextSelector === null) { this._unexpectedToken(tokenStream.LT(1)); } else { //nextSelector is an instance of SelectorPart selector.push(nextSelector); } } else { //if there's not whitespace, we're done if (this._readWhitespace()) { //add whitespace separator ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol); //combinator is not required combinator = this._combinator(); //selector is required if there's a combinator nextSelector = this._simple_selector_sequence(); if (nextSelector === null) { if (combinator !== null) { this._unexpectedToken(tokenStream.LT(1)); } } else { if (combinator !== null) { selector.push(combinator); } else { selector.push(ws); } selector.push(nextSelector); } } else { break; } } } while (true); return new Selector(selector, selector[0].line, selector[0].col); }, //CSS3 Selectors _simple_selector_sequence: function() { /* * simple_selector_sequence * : [ type_selector | universal ] * [ HASH | class | attrib | pseudo | any | negation ]* * | [ HASH | class | attrib | pseudo | any | negation ]+ * ; */ var tokenStream = this._tokenStream, //parts of a simple selector elementName = null, modifiers = [], //complete selector text selectorText= "", //the different parts after the element name to search for components = [ //HASH function() { return tokenStream.match(Tokens.HASH) ? new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : null; }, this._class, this._attrib, this._pseudo, this._any, this._negation ], i = 0, len = components.length, component = null, line, col; //get starting line and column for the selector line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol; elementName = this._type_selector(); if (!elementName) { elementName = this._universal(); } if (elementName !== null) { selectorText += elementName; } while (true) { //whitespace means we're done if (tokenStream.peek() === Tokens.S) { break; } //check for each component while (i < len && component === null) { component = components[i++].call(this); } if (component === null) { //we don't have a selector if (selectorText === "") { return null; } else { break; } } else { i = 0; modifiers.push(component); selectorText += component.toString(); component = null; } } return selectorText !== "" ? new SelectorPart(elementName, modifiers, selectorText, line, col) : null; }, //CSS3 Selectors _type_selector: function() { /* * type_selector * : [ namespace_prefix ]? element_name * ; */ var tokenStream = this._tokenStream, ns = this._namespace_prefix(), elementName = this._element_name(); if (!elementName) { /* * Need to back out the namespace that was read due to both * type_selector and universal reading namespace_prefix * first. Kind of hacky, but only way I can figure out * right now how to not change the grammar. */ if (ns) { tokenStream.unget(); if (ns.length > 1) { tokenStream.unget(); } } return null; } else { if (ns) { elementName.text = ns + elementName.text; elementName.col -= ns.length; } return elementName; } }, //CSS3 Selectors _class: function() { /* * class * : '.' IDENT * ; */ var tokenStream = this._tokenStream, token; if (tokenStream.match(Tokens.DOT)) { tokenStream.mustMatch(Tokens.IDENT); token = tokenStream.token(); return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); } else { return null; } }, //CSS3 Selectors _element_name: function() { /* * element_name * : IDENT * ; */ var tokenStream = this._tokenStream, token; if (tokenStream.match(Tokens.IDENT)) { token = tokenStream.token(); return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); } else { return null; } }, //CSS3 Selectors _namespace_prefix: function() { /* * namespace_prefix * : [ IDENT | '*' ]? '|' * ; */ var tokenStream = this._tokenStream, value = ""; //verify that this is a namespace prefix if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE) { if (tokenStream.match([Tokens.IDENT, Tokens.STAR])) { value += tokenStream.token().value; } tokenStream.mustMatch(Tokens.PIPE); value += "|"; } return value.length ? value : null; }, //CSS3 Selectors _universal: function() { /* * universal * : [ namespace_prefix ]? '*' * ; */ var tokenStream = this._tokenStream, value = "", ns; ns = this._namespace_prefix(); if (ns) { value += ns; } if (tokenStream.match(Tokens.STAR)) { value += "*"; } return value.length ? value : null; }, //CSS3 Selectors _attrib: function() { /* * attrib * : '[' S* [ namespace_prefix ]? IDENT S* * [ [ PREFIXMATCH | * SUFFIXMATCH | * SUBSTRINGMATCH | * '=' | * INCLUDES | * DASHMATCH ] S* [ IDENT | STRING ] S* * ]? ']' * ; */ var tokenStream = this._tokenStream, value = null, ns, token; if (tokenStream.match(Tokens.LBRACKET)) { token = tokenStream.token(); value = token.value; value += this._readWhitespace(); ns = this._namespace_prefix(); if (ns) { value += ns; } tokenStream.mustMatch(Tokens.IDENT); value += tokenStream.token().value; value += this._readWhitespace(); if (tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH, Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])) { value += tokenStream.token().value; value += this._readWhitespace(); tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); value += tokenStream.token().value; value += this._readWhitespace(); if (tokenStream.match([Tokens.IDENT])) { if (tokenStream.token().value.toLowerCase() == 'i') { value += tokenStream.token().value; value += this._readWhitespace(); } else { tokenStream.unget(); } } } tokenStream.mustMatch(Tokens.RBRACKET); return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol); } else { return null; } }, //CSS3 Selectors _pseudo: function() { /* * pseudo * : ':' ':'? [ IDENT | functional_pseudo ] * ; */ var tokenStream = this._tokenStream, pseudo = null, colons = ":", line, col; if (tokenStream.match(Tokens.COLON)) { if (tokenStream.match(Tokens.COLON)) { colons += ":"; } if (tokenStream.match(Tokens.IDENT)) { pseudo = tokenStream.token().value; line = tokenStream.token().startLine; col = tokenStream.token().startCol - colons.length; } else if (tokenStream.peek() === Tokens.FUNCTION) { line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol - colons.length; pseudo = this._functional_pseudo(); } if (pseudo) { pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col); } else { var startLine = tokenStream.LT(1).startLine, startCol = tokenStream.LT(0).startCol; throw new SyntaxError("Expected a `FUNCTION` or `IDENT` after colon at line " + startLine + ", col " + startCol + ".", startLine, startCol); } } return pseudo; }, //CSS3 Selectors _functional_pseudo: function() { /* * functional_pseudo * : FUNCTION S* expression ')' * ; */ var tokenStream = this._tokenStream, value = null; if (tokenStream.match(Tokens.FUNCTION)) { value = tokenStream.token().value; value += this._readWhitespace(); value += this._expression(); tokenStream.mustMatch(Tokens.RPAREN); value += ")"; } return value; }, //CSS3 Selectors _expression: function() { /* * expression * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ * ; */ var tokenStream = this._tokenStream, value = ""; while (tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH, Tokens.FREQ, Tokens.ANGLE, Tokens.TIME, Tokens.RESOLUTION, Tokens.SLASH])) { value += tokenStream.token().value; value += this._readWhitespace(); } return value.length ? value : null; }, //CSS3 Selectors _any: function() { /* * any * : ANY S* any_arg S* ')' * ; */ var tokenStream = this._tokenStream, line, col, value = "", arg, subpart = null; if (tokenStream.match(Tokens.ANY)) { value = tokenStream.token().value; line = tokenStream.token().startLine; col = tokenStream.token().startCol; value += this._readWhitespace(); arg = this._selectors_group(); value += arg; value += this._readWhitespace(); tokenStream.match(Tokens.RPAREN); value += tokenStream.token().value; subpart = new SelectorSubPart(value, "any", line, col); subpart.args.push(arg); } return subpart; }, //CSS3 Selectors _negation: function() { /* * negation * : NOT S* negation_arg S* ')' * ; */ var tokenStream = this._tokenStream, line, col, value = "", arg, subpart = null; if (tokenStream.match(Tokens.NOT)) { value = tokenStream.token().value; line = tokenStream.token().startLine; col = tokenStream.token().startCol; value += this._readWhitespace(); arg = this._negation_arg(); value += arg; value += this._readWhitespace(); tokenStream.match(Tokens.RPAREN); value += tokenStream.token().value; subpart = new SelectorSubPart(value, "not", line, col); subpart.args.push(arg); } return subpart; }, //CSS3 Selectors _negation_arg: function() { /* * negation_arg * : type_selector | universal | HASH | class | attrib | pseudo * ; */ var tokenStream = this._tokenStream, args = [ this._type_selector, this._universal, function() { return tokenStream.match(Tokens.HASH) ? new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : null; }, this._class, this._attrib, this._pseudo ], arg = null, i = 0, len = args.length, line, col, part; line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol; while (i < len && arg === null) { arg = args[i].call(this); i++; } //must be a negation arg if (arg === null) { this._unexpectedToken(tokenStream.LT(1)); } //it's an element name if (arg.type === "elementName") { part = new SelectorPart(arg, [], arg.toString(), line, col); } else { part = new SelectorPart(null, [arg], arg.toString(), line, col); } return part; }, _declaration: function() { /* * declaration * : property ':' S* expr prio? * | /( empty )/ * ; */ var tokenStream = this._tokenStream, property = null, expr = null, prio = null, invalid = null, propertyName= ""; property = this._property(); if (property !== null) { tokenStream.mustMatch(Tokens.COLON); // whitespace is a part of custom property value if (property.text.startsWith("--")) { expr = this._customProperty(); } else { this._readWhitespace(); expr = this._expr(); } //if there's no parts for the value, it's an error if (!expr || expr.length === 0) { this._unexpectedToken(tokenStream.LT(1)); } prio = this._prio(); /* * If hacks should be allowed, then only check the root * property. If hacks should not be allowed, treat * _property or *property as invalid properties. */ propertyName = property.toString(); if (this.options.starHack && property.hack === "*" || this.options.underscoreHack && property.hack === "_") { propertyName = property.text; } try { this._validateProperty(propertyName, expr); } catch (ex) { invalid = ex; } this.fire({ type: "property", property: property, value: expr, important: prio, line: property.line, col: property.col, invalid: invalid }); return true; } else { return false; } }, _prio: function() { /* * prio * : IMPORTANT_SYM S* * ; */ var tokenStream = this._tokenStream, result = tokenStream.match(Tokens.IMPORTANT_SYM); this._readWhitespace(); return result; }, _expr: function(inFunction) { /* * expr * : term [ operator term ]* * ; */ var values = [], //valueParts = [], value = null, operator = null; value = this._term(inFunction); if (value !== null) { values.push(value); do { operator = this._operator(inFunction); //if there's an operator, keep building up the value parts if (operator) { values.push(operator); } /*else { //if there's not an operator, you have a full value values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); valueParts = []; }*/ value = this._term(inFunction); if (value === null) { break; } const last = values[values.length - 1]; if (last && last.line === value.line && last.col === value.col && last.text === value.text) { break; } values.push(value); } while (true); } //cleanup /*if (valueParts.length) { values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); }*/ return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null; }, _customProperty: function() { const reader = this._tokenStream._reader; const value = []; const endings = []; // a custom property value may end before these characters // that belong to the parent declaration, not to the custom property let end = /[;!})]/; readValue: while (!reader.eof()) { const chunk = reader.readMatch(/([^;!'"{}()[\]/]|\/(?!\*))+/y); if (chunk) value.push(chunk); reader.mark(); let c = reader.read(); value.push(c); switch (c) { case '/': value.push(reader.readMatch(/([^*]|\*(?!\/))*(\*\/|$)/y)); continue; case '"': case "'": reader.reset(); value.pop(); value.push(this._tokenStream.readString()); continue; case '{': endings.push(end); end = '}'; continue; case '(': endings.push(end); end = ')'; continue; case '[': endings.push(end); end = ']'; continue; case ';': case '!': if (endings.length) continue; reader.reset(); // fallthrough case '}': case ')': case ']': if (end instanceof RegExp ? !end.test(c) : c !== end) { reader.reset(); return null; } end = endings.pop(); if (end) continue; if (c === '}' || c === ')') { // unget parent } reader.reset(); value.pop(); } break readValue; } } if (!value[0]) return null; const {startCol: col, startLine: line} = this._tokenStream._token; return new PropertyValue([ new PropertyValuePart(value.join(''), line, col), ], line, col); }, _term: function(inFunction) { /* * term * : unary_operator? * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | * TIME S* | FREQ S* | function | ie_function ] * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor * ; */ var tokenStream = this._tokenStream, unary = null, value = null, endChar = null, part = null, token, line, col; //returns the operator or null unary = this._unary_operator(); if (unary !== null) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; } //exception for IE filters if (tokenStream.peek() === Tokens.IE_FUNCTION && this.options.ieFilters) { value = this._ie_function(); if (unary === null) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; } //see if it's a simple block } else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])) { token = tokenStream.token(); endChar = token.endChar; value = token.value + this._expr(inFunction).text; if (unary === null) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; } tokenStream.mustMatch(Tokens.type(endChar)); value += endChar; this._readWhitespace(); //see if there's a simple match } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH, Tokens.ANGLE, Tokens.TIME, Tokens.DIMENSION, Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) { value = tokenStream.token().value; if (unary === null) { line = tokenStream.token().startLine; col = tokenStream.token().startCol; // Correct potentially-inaccurate IDENT parsing in // PropertyValuePart constructor. part = PropertyValuePart.fromToken(tokenStream.token()); } this._readWhitespace(); } else { //see if it's a color token = this._hexcolor(); if (token === null) { //if there's no unary, get the start of the next token for line/col info if (unary === null) { line = tokenStream.LT(1).startLine; col = tokenStream.LT(1).startCol; } //has to be a function if (value === null) { /* * This checks for alpha(opacity=0) style of IE * functions. IE_FUNCTION only presents progid: style. */ if (tokenStream.LA(3) === Tokens.EQUALS && this.options.ieFilters) { value = this._ie_function(); } else { value = this._function(); if (value) { part = new PropertyValuePart(unary !== null ? unary + value.text : value.text, line, col); part.expr = value.expr; } } } if (value === null) { const usoVar = this._isUsoVar(); if (usoVar) { ([line, col, value] = usoVar); } } /*if (value === null) { return null; //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); }*/ } else { value = token.value; if (unary === null) { line = token.startLine; col = token.startCol; } } } return part !== null ? part : value !== null ? new PropertyValuePart(unary !== null ? unary + value : value, line, col) : null; }, _function() { /* * function * : FUNCTION S* expr ')' S* * ; */ var tokenStream = this._tokenStream, functionText = [], expr = null, lt; if (tokenStream.match(Tokens.FUNCTION)) { functionText.push(tokenStream.token().value); this._readWhitespace(); expr = this._expr(true); functionText.push(String(expr)); //START: Horrible hack in case it's an IE filter if (this.options.ieFilters && tokenStream.peek() === Tokens.EQUALS) { do { if (this._readWhitespace()) { functionText.push(tokenStream.token().value); } //might be second time in the loop if (tokenStream.LA(0) === Tokens.COMMA) { functionText.push(tokenStream.token().value); } tokenStream.match(Tokens.IDENT); functionText.push(tokenStream.token().value); tokenStream.match(Tokens.EQUALS); functionText.push(tokenStream.token().value); //functionText.push(this._term()); lt = tokenStream.peek(); while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN && lt !== Tokens.EOF) { tokenStream.get(); functionText.push(tokenStream.token().value); lt = tokenStream.peek(); } } while (tokenStream.match([Tokens.COMMA, Tokens.S])); } //END: Horrible Hack tokenStream.mustMatch(Tokens.RPAREN); functionText.push(")"); this._readWhitespace(); } return !functionText[0] ? null : {expr, text: functionText.join('')}; }, _ie_function: function() { /* (My own extension) * ie_function * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S* * ; */ var tokenStream = this._tokenStream, functionText = null, lt; //IE function can begin like a regular function, too if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) { functionText = tokenStream.token().value; do { if (this._readWhitespace()) { functionText += tokenStream.token().value; } //might be second time in the loop if (tokenStream.LA(0) === Tokens.COMMA) { functionText += tokenStream.token().value; } tokenStream.match(Tokens.IDENT); functionText += tokenStream.token().value; tokenStream.match(Tokens.EQUALS); functionText += tokenStream.token().value; //functionText += this._term(); lt = tokenStream.peek(); while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { tokenStream.get(); functionText += tokenStream.token().value; lt = tokenStream.peek(); } } while (tokenStream.match([Tokens.COMMA, Tokens.S])); tokenStream.match(Tokens.RPAREN); functionText += ")"; this._readWhitespace(); } return functionText; }, _hexcolor: function() { /* * There is a constraint on the color that it must * have either 3,4 or 6,8 hex-digits (i.e., [0-9a-fA-F]) * after the "#"; e.g., "#000" is OK, but "#abcd" is not. * * hexcolor * : HASH S* * ; */ var tokenStream = this._tokenStream, token = null, color; if (tokenStream.match(Tokens.HASH)) { token = tokenStream.token(); color = token.value; const len = color.length; if (len !== 4 && len !== 5 && len !== 7 && len !== 9 || !/^#([a-f\d]{3}(?:[a-f\d](?:[a-f\d]{2}){0,2})?)$/i.test(color)) { throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); } this._readWhitespace(); } return token; }, //----------------------------------------------------------------- // Animations methods //----------------------------------------------------------------- _keyframes: function() { /* * keyframes: * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { * ; */ var tokenStream = this._tokenStream, token, tt, name, prefix = ""; tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); token = tokenStream.token(); if (/^@\-([^\-]+)\-/.test(token.value)) { prefix = RegExp.$1; } this._readWhitespace(); name = this._keyframe_name(); this._readWhitespace(); tokenStream.mustMatch(Tokens.LBRACE); this.fire({ type: "startkeyframes", name: name, prefix: prefix, line: token.startLine, col: token.startCol }); this._readWhitespace(); tt = tokenStream.peek(); //check for key while (tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) { this._keyframe_rule(); this._readWhitespace(); tt = tokenStream.peek(); } this.fire({ type: "endkeyframes", name: name, prefix: prefix, line: token.startLine, col: token.startCol }); this._readWhitespace(); tokenStream.mustMatch(Tokens.RBRACE); this._readWhitespace(); }, _keyframe_name: function() { /* * keyframe_name: * : IDENT * | STRING * ; */ var tokenStream = this._tokenStream; tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); return SyntaxUnit.fromToken(tokenStream.token()); }, _keyframe_rule: function() { /* * keyframe_rule: * : key_list S* * '{' S* declaration [ ';' S* declaration ]* '}' S* * ; */ var keyList = this._key_list(); this.fire({ type: "startkeyframerule", keys: keyList, line: keyList[0].line, col: keyList[0].col }); this._readDeclarations(true); this.fire({ type: "endkeyframerule", keys: keyList, line: keyList[0].line, col: keyList[0].col }); }, _key_list: function() { /* * key_list: * : key [ S* ',' S* key]* * ; */ var tokenStream = this._tokenStream, keyList = []; //must be least one key keyList.push(this._key()); this._readWhitespace(); while (tokenStream.match(Tokens.COMMA)) { this._readWhitespace(); keyList.push(this._key()); this._readWhitespace(); } return keyList; }, _key: function() { /* * There is a restriction that IDENT can be only "from" or "to". * * key * : PERCENTAGE * | IDENT * ; */ var tokenStream = this._tokenStream, token; if (tokenStream.match(Tokens.PERCENTAGE)) { return SyntaxUnit.fromToken(tokenStream.token()); } else if (tokenStream.match(Tokens.IDENT)) { token = tokenStream.token(); if (/from|to/i.test(token.value)) { return SyntaxUnit.fromToken(token); } tokenStream.unget(); } //if it gets here, there wasn't a valid token, so time to explode this._unexpectedToken(tokenStream.LT(1)); }, //----------------------------------------------------------------- // Helper methods //----------------------------------------------------------------- /** * Not part of CSS grammar, but useful for skipping over * combination of white space and HTML-style comments. * @return {void} * @method _skipCruft * @private */ _skipCruft: function() { while (this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])) { //noop } }, /** * Not part of CSS grammar, but this pattern occurs frequently * in the official CSS grammar. Split out here to eliminate * duplicate code. * @param {Boolean} checkStart Indicates if the rule should check * for the left brace at the beginning. * @param {Boolean} readMargins Indicates if the rule should check * for margin patterns. * @return {void} * @method _readDeclarations * @private */ _readDeclarations: function(checkStart, readMargins) { /* * Reads the pattern * S* '{' S* declaration [ ';' S* declaration ]* '}' S* * or * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect. * A semicolon is only necessary following a declaration if there's another declaration * or margin afterwards. */ var tokenStream = this._tokenStream, tt; this._readWhitespace(); if (checkStart) { tokenStream.mustMatch(Tokens.LBRACE); } this._readWhitespace(); try { while (true) { if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())) { //noop } else if (this._declaration()) { if (!tokenStream.match(Tokens.SEMICOLON)) { break; } } else { break; } //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ // break; //} this._readWhitespace(); } tokenStream.mustMatch(Tokens.RBRACE); this._readWhitespace(); } catch (ex) { if (ex instanceof SyntaxError && !this.options.strict) { //fire error event this.fire({ type: "error", error: ex, message: ex.message, line: ex.line, col: ex.col }); //see if there's another declaration tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]); if (tt === Tokens.SEMICOLON) { //if there's a semicolon, then there might be another declaration this._readDeclarations(false, readMargins); } else if (tt !== Tokens.RBRACE) { //if there's a right brace, the rule is finished so don't do anything //otherwise, rethrow the error because it wasn't handled properly throw ex; } } else { //not a syntax error, rethrow it throw ex; } } }, /** * In some cases, you can end up with two white space tokens in a * row. Instead of making a change in every function that looks for * white space, this function is used to match as much white space * as necessary. * @method _readWhitespace * @return {String} The white space if found, empty string if not. * @private */ _readWhitespace: function() { var tokenStream = this._tokenStream, ws = ""; while (tokenStream.match(Tokens.S)) { ws += tokenStream.token().value; } return ws; }, _isUsoVar() { const tokenStream = this._tokenStream; for (let i = tokenStream._ltIndex - 1; i >= 0; i--) { const {type, value, startLine, startCol} = tokenStream._lt[i]; if (type === tokenStream._tokenData.S) { // NOP } else if (type === tokenStream._tokenData.COMMENT && value[2] === '[' && value[3] === '[' && value.endsWith(']]*/')) { return [startLine, startCol, value]; } else { return false; } } }, /** * Throws an error when an unexpected token is found. * @param {Object} token The token that was found. * @method _unexpectedToken * @return {void} * @private */ _unexpectedToken: function(token) { throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); }, /** * Helper method used for parsing subparts of a style sheet. * @return {void} * @method _verifyEnd * @private */ _verifyEnd: function() { if (this._tokenStream.LA(1) !== Tokens.EOF) { this._unexpectedToken(this._tokenStream.LT(1)); } }, //----------------------------------------------------------------- // Validation methods //----------------------------------------------------------------- _validateProperty: function(property, value) { Validation.validate(property, value); }, //----------------------------------------------------------------- // Parsing methods //----------------------------------------------------------------- parse: function(input) { this._tokenStream = new TokenStream(input, Tokens); this._stylesheet(); }, parseStyleSheet: function(input) { //just passthrough return this.parse(input); }, parseMediaQuery: function(input) { this._tokenStream = new TokenStream(input, Tokens); var result = this._media_query(); //if there's anything more, then it's an invalid selector this._verifyEnd(); //otherwise return result return result; }, /** * Parses a property value (everything after the semicolon). * @return {parserlib.css.PropertyValue} The property value. * @throws parserlib.util.SyntaxError If an unexpected token is found. * @method parserPropertyValue */ parsePropertyValue: function(input) { this._tokenStream = new TokenStream(input, Tokens); this._readWhitespace(); var result = this._expr(); //okay to have a trailing white space this._readWhitespace(); //if there's anything more, then it's an invalid selector this._verifyEnd(); //otherwise return result return result; }, /** * Parses a complete CSS rule, including selectors and * properties. * @param {String} input The text to parser. * @return {Boolean} True if the parse completed successfully, false if not. * @method parseRule */ parseRule: function(input) { this._tokenStream = new TokenStream(input, Tokens); //skip any leading white space this._readWhitespace(); var result = this._ruleset(); //skip any trailing white space this._readWhitespace(); //if there's anything more, then it's an invalid selector this._verifyEnd(); //otherwise return result return result; }, /** * Parses a single CSS selector (no comma) * @param {String} input The text to parse as a CSS selector. * @return {Selector} An object representing the selector. * @throws parserlib.util.SyntaxError If an unexpected token is found. * @method parseSelector */ parseSelector: function(input) { this._tokenStream = new TokenStream(input, Tokens); //skip any leading white space this._readWhitespace(); var result = this._selector(); //skip any trailing white space this._readWhitespace(); //if there's anything more, then it's an invalid selector this._verifyEnd(); //otherwise return result return result; }, /** * Parses an HTML style attribute: a set of CSS declarations * separated by semicolons. * @param {String} input The text to parse as a style attribute * @return {void} * @method parseStyleAttribute */ parseStyleAttribute: function(input) { input += "}"; // for error recovery in _readDeclarations() this._tokenStream = new TokenStream(input, Tokens); this._readDeclarations(); } }; //copy over onto prototype for (prop in additions) { if (Object.prototype.hasOwnProperty.call(additions, prop)) { proto[prop] = additions[prop]; } } return proto; }(); /* nth : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S* ; */ },{"../util/EventTarget":23,"../util/SyntaxError":25,"../util/SyntaxUnit":26,"./Combinator":2,"./MediaFeature":4,"./MediaQuery":5,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./TokenStream":17,"./Tokens":18,"./Validation":19}],7:[function(require,module,exports){ "use strict"; /* exported Properties */ var Properties = module.exports = { __proto__: null, //A "align-items" : "normal | stretch | | [ ? ]", "align-content" : "", "align-self" : "", "all" : "initial | inherit | unset", "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | | ", "alignment-baseline" : "auto | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", "animation" : 1, "animation-delay" : "