123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- 'use strict';
-
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.parseMediaFeature = parseMediaFeature;
- exports.parseMediaQuery = parseMediaQuery;
- exports.parseMediaList = parseMediaList;
-
- var _Node = require('./nodes/Node');
-
- var _Node2 = _interopRequireDefault(_Node);
-
- var _Container = require('./nodes/Container');
-
- var _Container2 = _interopRequireDefault(_Container);
-
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
- /**
- * Parses a media feature expression, e.g. `max-width: 10px`, `(color)`
- *
- * @param {string} string - the source expression string, can be inside parens
- * @param {Number} index - the index of `string` in the overall input
- *
- * @return {Array} an array of Nodes, the first element being a media feature,
- * the secont - its value (may be missing)
- */
-
- function parseMediaFeature(string) {
- var index = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
-
- var modesEntered = [{
- mode: 'normal',
- character: null
- }];
- var result = [];
- var lastModeIndex = 0;
- var mediaFeature = '';
- var colon = null;
- var mediaFeatureValue = null;
- var indexLocal = index;
-
- var stringNormalized = string;
- // Strip trailing parens (if any), and correct the starting index
- if (string[0] === '(' && string[string.length - 1] === ')') {
- stringNormalized = string.substring(1, string.length - 1);
- indexLocal++;
- }
-
- for (var i = 0; i < stringNormalized.length; i++) {
- var character = stringNormalized[i];
-
- // If entering/exiting a string
- if (character === '\'' || character === '"') {
- if (modesEntered[lastModeIndex].isCalculationEnabled === true) {
- modesEntered.push({
- mode: 'string',
- isCalculationEnabled: false,
- character: character
- });
- lastModeIndex++;
- } else if (modesEntered[lastModeIndex].mode === 'string' && modesEntered[lastModeIndex].character === character && stringNormalized[i - 1] !== '\\') {
- modesEntered.pop();
- lastModeIndex--;
- }
- }
-
- // If entering/exiting interpolation
- if (character === '{') {
- modesEntered.push({
- mode: 'interpolation',
- isCalculationEnabled: true
- });
- lastModeIndex++;
- } else if (character === '}') {
- modesEntered.pop();
- lastModeIndex--;
- }
-
- // If a : is met outside of a string, function call or interpolation, than
- // this : separates a media feature and a value
- if (modesEntered[lastModeIndex].mode === 'normal' && character === ':') {
- var mediaFeatureValueStr = stringNormalized.substring(i + 1);
- mediaFeatureValue = {
- type: 'value',
- before: /^(\s*)/.exec(mediaFeatureValueStr)[1],
- after: /(\s*)$/.exec(mediaFeatureValueStr)[1],
- value: mediaFeatureValueStr.trim()
- };
- // +1 for the colon
- mediaFeatureValue.sourceIndex = mediaFeatureValue.before.length + i + 1 + indexLocal;
- colon = {
- type: 'colon',
- sourceIndex: i + indexLocal,
- after: mediaFeatureValue.before,
- value: ':' };
- break;
- }
-
- mediaFeature += character;
- }
-
- // Forming a media feature node
- mediaFeature = {
- type: 'media-feature',
- before: /^(\s*)/.exec(mediaFeature)[1],
- after: /(\s*)$/.exec(mediaFeature)[1],
- value: mediaFeature.trim()
- };
- mediaFeature.sourceIndex = mediaFeature.before.length + indexLocal;
- result.push(mediaFeature);
-
- if (colon !== null) {
- colon.before = mediaFeature.after;
- result.push(colon);
- }
-
- if (mediaFeatureValue !== null) {
- result.push(mediaFeatureValue);
- }
-
- return result;
- }
-
- /**
- * Parses a media query, e.g. `screen and (color)`, `only tv`
- *
- * @param {string} string - the source media query string
- * @param {Number} index - the index of `string` in the overall input
- *
- * @return {Array} an array of Nodes and Containers
- */
-
- function parseMediaQuery(string) {
- var index = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
-
- var result = [];
-
- // How many timies the parser entered parens/curly braces
- var localLevel = 0;
- // Has any keyword, media type, media feature expression or interpolation
- // ('element' hereafter) started
- var insideSomeValue = false;
- var node = void 0;
-
- function resetNode() {
- return {
- before: '',
- after: '',
- value: ''
- };
- }
-
- node = resetNode();
-
- for (var i = 0; i < string.length; i++) {
- var character = string[i];
- // If not yet entered any element
- if (!insideSomeValue) {
- if (character.search(/\s/) !== -1) {
- // A whitespace
- // Don't form 'after' yet; will do it later
- node.before += character;
- } else {
- // Not a whitespace - entering an element
- // Expression start
- if (character === '(') {
- node.type = 'media-feature-expression';
- localLevel++;
- }
- node.value = character;
- node.sourceIndex = index + i;
- insideSomeValue = true;
- }
- } else {
- // Already in the middle of some alement
- node.value += character;
-
- // Here parens just increase localLevel and don't trigger a start of
- // a media feature expression (since they can't be nested)
- // Interpolation start
- if (character === '{' || character === '(') {
- localLevel++;
- }
- // Interpolation/function call/media feature expression end
- if (character === ')' || character === '}') {
- localLevel--;
- }
- }
-
- // If exited all parens/curlies and the next symbol
- if (insideSomeValue && localLevel === 0 && (character === ')' || i === string.length - 1 || string[i + 1].search(/\s/) !== -1)) {
- if (['not', 'only', 'and'].indexOf(node.value) !== -1) {
- node.type = 'keyword';
- }
- // if it's an expression, parse its contents
- if (node.type === 'media-feature-expression') {
- node.nodes = parseMediaFeature(node.value, node.sourceIndex);
- }
- result.push(Array.isArray(node.nodes) ? new _Container2.default(node) : new _Node2.default(node));
- node = resetNode();
- insideSomeValue = false;
- }
- }
-
- // Now process the result array - to specify undefined types of the nodes
- // and specify the `after` prop
- for (var _i = 0; _i < result.length; _i++) {
- node = result[_i];
- if (_i > 0) {
- result[_i - 1].after = node.before;
- }
-
- // Node types. Might not be set because contains interpolation/function
- // calls or fully consists of them
- if (node.type === undefined) {
- if (_i > 0) {
- // only `and` can follow an expression
- if (result[_i - 1].type === 'media-feature-expression') {
- node.type = 'keyword';
- continue;
- }
- // Anything after 'only|not' is a media type
- if (result[_i - 1].value === 'not' || result[_i - 1].value === 'only') {
- node.type = 'media-type';
- continue;
- }
- // Anything after 'and' is an expression
- if (result[_i - 1].value === 'and') {
- node.type = 'media-feature-expression';
- continue;
- }
-
- if (result[_i - 1].type === 'media-type') {
- // if it is the last element - it might be an expression
- // or 'and' depending on what is after it
- if (!result[_i + 1]) {
- node.type = 'media-feature-expression';
- } else {
- node.type = result[_i + 1].type === 'media-feature-expression' ? 'keyword' : 'media-feature-expression';
- }
- }
- }
-
- if (_i === 0) {
- // `screen`, `fn( ... )`, `#{ ... }`. Not an expression, since then
- // its type would have been set by now
- if (!result[_i + 1]) {
- node.type = 'media-type';
- continue;
- }
-
- // `screen and` or `#{...} (max-width: 10px)`
- if (result[_i + 1] && (result[_i + 1].type === 'media-feature-expression' || result[_i + 1].type === 'keyword')) {
- node.type = 'media-type';
- continue;
- }
- if (result[_i + 2]) {
- // `screen and (color) ...`
- if (result[_i + 2].type === 'media-feature-expression') {
- node.type = 'media-type';
- result[_i + 1].type = 'keyword';
- continue;
- }
- // `only screen and ...`
- if (result[_i + 2].type === 'keyword') {
- node.type = 'keyword';
- result[_i + 1].type = 'media-type';
- continue;
- }
- }
- if (result[_i + 3]) {
- // `screen and (color) ...`
- if (result[_i + 3].type === 'media-feature-expression') {
- node.type = 'keyword';
- result[_i + 1].type = 'media-type';
- result[_i + 2].type = 'keyword';
- continue;
- }
- }
- }
- }
- }
- return result;
- }
-
- /**
- * Parses a media query list. Takes a possible `url()` at the start into
- * account, and divides the list into media queries that are parsed separately
- *
- * @param {string} string - the source media query list string
- *
- * @return {Array} an array of Nodes/Containers
- */
-
- function parseMediaList(string) {
- var result = [];
- var interimIndex = 0;
- var levelLocal = 0;
-
- // Check for a `url(...)` part (if it is contents of an @import rule)
- var doesHaveUrl = /^(\s*)url\s*\(/.exec(string);
- if (doesHaveUrl !== null) {
- var i = doesHaveUrl[0].length;
- var parenthesesLv = 1;
- while (parenthesesLv > 0) {
- var character = string[i];
- if (character === '(') {
- parenthesesLv++;
- }
- if (character === ')') {
- parenthesesLv--;
- }
- i++;
- }
- result.unshift(new _Node2.default({
- type: 'url',
- value: string.substring(0, i).trim(),
- sourceIndex: doesHaveUrl[1].length,
- before: doesHaveUrl[1],
- after: /^(\s*)/.exec(string.substring(i))[1]
- }));
- interimIndex = i;
- }
-
- // Start processing the media query list
- for (var _i2 = interimIndex; _i2 < string.length; _i2++) {
- var _character = string[_i2];
-
- // Dividing the media query list into comma-separated media queries
- // Only count commas that are outside of any parens
- // (i.e., not part of function call params list, etc.)
- if (_character === '(') {
- levelLocal++;
- }
- if (_character === ')') {
- levelLocal--;
- }
- if (levelLocal === 0 && _character === ',') {
- var _mediaQueryString = string.substring(interimIndex, _i2);
- var _spaceBefore = /^(\s*)/.exec(_mediaQueryString)[1];
- result.push(new _Container2.default({
- type: 'media-query',
- value: _mediaQueryString.trim(),
- sourceIndex: interimIndex + _spaceBefore.length,
- nodes: parseMediaQuery(_mediaQueryString, interimIndex),
- before: _spaceBefore,
- after: /(\s*)$/.exec(_mediaQueryString)[1]
- }));
- interimIndex = _i2 + 1;
- }
- }
-
- var mediaQueryString = string.substring(interimIndex);
- var spaceBefore = /^(\s*)/.exec(mediaQueryString)[1];
- result.push(new _Container2.default({
- type: 'media-query',
- value: mediaQueryString.trim(),
- sourceIndex: interimIndex + spaceBefore.length,
- nodes: parseMediaQuery(mediaQueryString, interimIndex),
- before: spaceBefore,
- after: /(\s*)$/.exec(mediaQueryString)[1]
- }));
-
- return result;
- }
|