You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							603 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							603 lines
						
					
					
						
							11 KiB
						
					
					
				// http://www.w3.org/TR/CSS21/grammar.html | 
						|
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 | 
						|
var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g | 
						|
 | 
						|
module.exports = function(css, options){ | 
						|
  options = options || {}; | 
						|
 | 
						|
  /** | 
						|
   * Positional. | 
						|
   */ | 
						|
 | 
						|
  var lineno = 1; | 
						|
  var column = 1; | 
						|
 | 
						|
  /** | 
						|
   * Update lineno and column based on `str`. | 
						|
   */ | 
						|
 | 
						|
  function updatePosition(str) { | 
						|
    var lines = str.match(/\n/g); | 
						|
    if (lines) lineno += lines.length; | 
						|
    var i = str.lastIndexOf('\n'); | 
						|
    column = ~i ? str.length - i : column + str.length; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Mark position and patch `node.position`. | 
						|
   */ | 
						|
 | 
						|
  function position() { | 
						|
    var start = { line: lineno, column: column }; | 
						|
    return function(node){ | 
						|
      node.position = new Position(start); | 
						|
      whitespace(); | 
						|
      return node; | 
						|
    }; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Store position information for a node | 
						|
   */ | 
						|
 | 
						|
  function Position(start) { | 
						|
    this.start = start; | 
						|
    this.end = { line: lineno, column: column }; | 
						|
    this.source = options.source; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Non-enumerable source string | 
						|
   */ | 
						|
 | 
						|
  Position.prototype.content = css; | 
						|
 | 
						|
  /** | 
						|
   * Error `msg`. | 
						|
   */ | 
						|
 | 
						|
  var errorsList = []; | 
						|
 | 
						|
  function error(msg) { | 
						|
    var err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg); | 
						|
    err.reason = msg; | 
						|
    err.filename = options.source; | 
						|
    err.line = lineno; | 
						|
    err.column = column; | 
						|
    err.source = css; | 
						|
 | 
						|
    if (options.silent) { | 
						|
      errorsList.push(err); | 
						|
    } else { | 
						|
      throw err; | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse stylesheet. | 
						|
   */ | 
						|
 | 
						|
  function stylesheet() { | 
						|
    var rulesList = rules(); | 
						|
 | 
						|
    return { | 
						|
      type: 'stylesheet', | 
						|
      stylesheet: { | 
						|
        source: options.source, | 
						|
        rules: rulesList, | 
						|
        parsingErrors: errorsList | 
						|
      } | 
						|
    }; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Opening brace. | 
						|
   */ | 
						|
 | 
						|
  function open() { | 
						|
    return match(/^{\s*/); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Closing brace. | 
						|
   */ | 
						|
 | 
						|
  function close() { | 
						|
    return match(/^}/); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse ruleset. | 
						|
   */ | 
						|
 | 
						|
  function rules() { | 
						|
    var node; | 
						|
    var rules = []; | 
						|
    whitespace(); | 
						|
    comments(rules); | 
						|
    while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) { | 
						|
      if (node !== false) { | 
						|
        rules.push(node); | 
						|
        comments(rules); | 
						|
      } | 
						|
    } | 
						|
    return rules; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Match `re` and return captures. | 
						|
   */ | 
						|
 | 
						|
  function match(re) { | 
						|
    var m = re.exec(css); | 
						|
    if (!m) return; | 
						|
    var str = m[0]; | 
						|
    updatePosition(str); | 
						|
    css = css.slice(str.length); | 
						|
    return m; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse whitespace. | 
						|
   */ | 
						|
 | 
						|
  function whitespace() { | 
						|
    match(/^\s*/); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse comments; | 
						|
   */ | 
						|
 | 
						|
  function comments(rules) { | 
						|
    var c; | 
						|
    rules = rules || []; | 
						|
    while (c = comment()) { | 
						|
      if (c !== false) { | 
						|
        rules.push(c); | 
						|
      } | 
						|
    } | 
						|
    return rules; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse comment. | 
						|
   */ | 
						|
 | 
						|
  function comment() { | 
						|
    var pos = position(); | 
						|
    if ('/' != css.charAt(0) || '*' != css.charAt(1)) return; | 
						|
 | 
						|
    var i = 2; | 
						|
    while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i; | 
						|
    i += 2; | 
						|
 | 
						|
    if ("" === css.charAt(i-1)) { | 
						|
      return error('End of comment missing'); | 
						|
    } | 
						|
 | 
						|
    var str = css.slice(2, i - 2); | 
						|
    column += 2; | 
						|
    updatePosition(str); | 
						|
    css = css.slice(i); | 
						|
    column += 2; | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'comment', | 
						|
      comment: str | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse selector. | 
						|
   */ | 
						|
 | 
						|
  function selector() { | 
						|
    var m = match(/^([^{]+)/); | 
						|
    if (!m) return; | 
						|
    /* @fix Remove all comments from selectors | 
						|
     * http://ostermiller.org/findcomment.html */ | 
						|
    return trim(m[0]) | 
						|
      .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '') | 
						|
      .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function(m) { | 
						|
        return m.replace(/,/g, '\u200C'); | 
						|
      }) | 
						|
      .split(/\s*(?![^(]*\)),\s*/) | 
						|
      .map(function(s) { | 
						|
        return s.replace(/\u200C/g, ','); | 
						|
      }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse declaration. | 
						|
   */ | 
						|
 | 
						|
  function declaration() { | 
						|
    var pos = position(); | 
						|
 | 
						|
    // prop | 
						|
    var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/); | 
						|
    if (!prop) return; | 
						|
    prop = trim(prop[0]); | 
						|
 | 
						|
    // : | 
						|
    if (!match(/^:\s*/)) return error("property missing ':'"); | 
						|
 | 
						|
    // val | 
						|
    var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/); | 
						|
 | 
						|
    var ret = pos({ | 
						|
      type: 'declaration', | 
						|
      property: prop.replace(commentre, ''), | 
						|
      value: val ? trim(val[0]).replace(commentre, '') : '' | 
						|
    }); | 
						|
 | 
						|
    // ; | 
						|
    match(/^[;\s]*/); | 
						|
 | 
						|
    return ret; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse declarations. | 
						|
   */ | 
						|
 | 
						|
  function declarations() { | 
						|
    var decls = []; | 
						|
 | 
						|
    if (!open()) return error("missing '{'"); | 
						|
    comments(decls); | 
						|
 | 
						|
    // declarations | 
						|
    var decl; | 
						|
    while (decl = declaration()) { | 
						|
      if (decl !== false) { | 
						|
        decls.push(decl); | 
						|
        comments(decls); | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    if (!close()) return error("missing '}'"); | 
						|
    return decls; | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse keyframe. | 
						|
   */ | 
						|
 | 
						|
  function keyframe() { | 
						|
    var m; | 
						|
    var vals = []; | 
						|
    var pos = position(); | 
						|
 | 
						|
    while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) { | 
						|
      vals.push(m[1]); | 
						|
      match(/^,\s*/); | 
						|
    } | 
						|
 | 
						|
    if (!vals.length) return; | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'keyframe', | 
						|
      values: vals, | 
						|
      declarations: declarations() | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse keyframes. | 
						|
   */ | 
						|
 | 
						|
  function atkeyframes() { | 
						|
    var pos = position(); | 
						|
    var m = match(/^@([-\w]+)?keyframes\s*/); | 
						|
 | 
						|
    if (!m) return; | 
						|
    var vendor = m[1]; | 
						|
 | 
						|
    // identifier | 
						|
    var m = match(/^([-\w]+)\s*/); | 
						|
    if (!m) return error("@keyframes missing name"); | 
						|
    var name = m[1]; | 
						|
 | 
						|
    if (!open()) return error("@keyframes missing '{'"); | 
						|
 | 
						|
    var frame; | 
						|
    var frames = comments(); | 
						|
    while (frame = keyframe()) { | 
						|
      frames.push(frame); | 
						|
      frames = frames.concat(comments()); | 
						|
    } | 
						|
 | 
						|
    if (!close()) return error("@keyframes missing '}'"); | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'keyframes', | 
						|
      name: name, | 
						|
      vendor: vendor, | 
						|
      keyframes: frames | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse supports. | 
						|
   */ | 
						|
 | 
						|
  function atsupports() { | 
						|
    var pos = position(); | 
						|
    var m = match(/^@supports *([^{]+)/); | 
						|
 | 
						|
    if (!m) return; | 
						|
    var supports = trim(m[1]); | 
						|
 | 
						|
    if (!open()) return error("@supports missing '{'"); | 
						|
 | 
						|
    var style = comments().concat(rules()); | 
						|
 | 
						|
    if (!close()) return error("@supports missing '}'"); | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'supports', | 
						|
      supports: supports, | 
						|
      rules: style | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse host. | 
						|
   */ | 
						|
 | 
						|
  function athost() { | 
						|
    var pos = position(); | 
						|
    var m = match(/^@host\s*/); | 
						|
 | 
						|
    if (!m) return; | 
						|
 | 
						|
    if (!open()) return error("@host missing '{'"); | 
						|
 | 
						|
    var style = comments().concat(rules()); | 
						|
 | 
						|
    if (!close()) return error("@host missing '}'"); | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'host', | 
						|
      rules: style | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse media. | 
						|
   */ | 
						|
 | 
						|
  function atmedia() { | 
						|
    var pos = position(); | 
						|
    var m = match(/^@media *([^{]+)/); | 
						|
 | 
						|
    if (!m) return; | 
						|
    var media = trim(m[1]); | 
						|
 | 
						|
    if (!open()) return error("@media missing '{'"); | 
						|
 | 
						|
    var style = comments().concat(rules()); | 
						|
 | 
						|
    if (!close()) return error("@media missing '}'"); | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'media', | 
						|
      media: media, | 
						|
      rules: style | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
 | 
						|
  /** | 
						|
   * Parse custom-media. | 
						|
   */ | 
						|
 | 
						|
  function atcustommedia() { | 
						|
    var pos = position(); | 
						|
    var m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/); | 
						|
    if (!m) return; | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'custom-media', | 
						|
      name: trim(m[1]), | 
						|
      media: trim(m[2]) | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse paged media. | 
						|
   */ | 
						|
 | 
						|
  function atpage() { | 
						|
    var pos = position(); | 
						|
    var m = match(/^@page */); | 
						|
    if (!m) return; | 
						|
 | 
						|
    var sel = selector() || []; | 
						|
 | 
						|
    if (!open()) return error("@page missing '{'"); | 
						|
    var decls = comments(); | 
						|
 | 
						|
    // declarations | 
						|
    var decl; | 
						|
    while (decl = declaration()) { | 
						|
      decls.push(decl); | 
						|
      decls = decls.concat(comments()); | 
						|
    } | 
						|
 | 
						|
    if (!close()) return error("@page missing '}'"); | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'page', | 
						|
      selectors: sel, | 
						|
      declarations: decls | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse document. | 
						|
   */ | 
						|
 | 
						|
  function atdocument() { | 
						|
    var pos = position(); | 
						|
    var m = match(/^@([-\w]+)?document *([^{]+)/); | 
						|
    if (!m) return; | 
						|
 | 
						|
    var vendor = trim(m[1]); | 
						|
    var doc = trim(m[2]); | 
						|
 | 
						|
    if (!open()) return error("@document missing '{'"); | 
						|
 | 
						|
    var style = comments().concat(rules()); | 
						|
 | 
						|
    if (!close()) return error("@document missing '}'"); | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'document', | 
						|
      document: doc, | 
						|
      vendor: vendor, | 
						|
      rules: style | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse font-face. | 
						|
   */ | 
						|
 | 
						|
  function atfontface() { | 
						|
    var pos = position(); | 
						|
    var m = match(/^@font-face\s*/); | 
						|
    if (!m) return; | 
						|
 | 
						|
    if (!open()) return error("@font-face missing '{'"); | 
						|
    var decls = comments(); | 
						|
 | 
						|
    // declarations | 
						|
    var decl; | 
						|
    while (decl = declaration()) { | 
						|
      decls.push(decl); | 
						|
      decls = decls.concat(comments()); | 
						|
    } | 
						|
 | 
						|
    if (!close()) return error("@font-face missing '}'"); | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'font-face', | 
						|
      declarations: decls | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse import | 
						|
   */ | 
						|
 | 
						|
  var atimport = _compileAtrule('import'); | 
						|
 | 
						|
  /** | 
						|
   * Parse charset | 
						|
   */ | 
						|
 | 
						|
  var atcharset = _compileAtrule('charset'); | 
						|
 | 
						|
  /** | 
						|
   * Parse namespace | 
						|
   */ | 
						|
 | 
						|
  var atnamespace = _compileAtrule('namespace'); | 
						|
 | 
						|
  /** | 
						|
   * Parse non-block at-rules | 
						|
   */ | 
						|
 | 
						|
 | 
						|
  function _compileAtrule(name) { | 
						|
    var re = new RegExp('^@' + name + '\\s*([^;]+);'); | 
						|
    return function() { | 
						|
      var pos = position(); | 
						|
      var m = match(re); | 
						|
      if (!m) return; | 
						|
      var ret = { type: name }; | 
						|
      ret[name] = m[1].trim(); | 
						|
      return pos(ret); | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse at rule. | 
						|
   */ | 
						|
 | 
						|
  function atrule() { | 
						|
    if (css[0] != '@') return; | 
						|
 | 
						|
    return atkeyframes() | 
						|
      || atmedia() | 
						|
      || atcustommedia() | 
						|
      || atsupports() | 
						|
      || atimport() | 
						|
      || atcharset() | 
						|
      || atnamespace() | 
						|
      || atdocument() | 
						|
      || atpage() | 
						|
      || athost() | 
						|
      || atfontface(); | 
						|
  } | 
						|
 | 
						|
  /** | 
						|
   * Parse rule. | 
						|
   */ | 
						|
 | 
						|
  function rule() { | 
						|
    var pos = position(); | 
						|
    var sel = selector(); | 
						|
 | 
						|
    if (!sel) return error('selector missing'); | 
						|
    comments(); | 
						|
 | 
						|
    return pos({ | 
						|
      type: 'rule', | 
						|
      selectors: sel, | 
						|
      declarations: declarations() | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  return addParent(stylesheet()); | 
						|
}; | 
						|
 | 
						|
/** | 
						|
 * Trim `str`. | 
						|
 */ | 
						|
 | 
						|
function trim(str) { | 
						|
  return str ? str.replace(/^\s+|\s+$/g, '') : ''; | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Adds non-enumerable parent node reference to each node. | 
						|
 */ | 
						|
 | 
						|
function addParent(obj, parent) { | 
						|
  var isNode = obj && typeof obj.type === 'string'; | 
						|
  var childParent = isNode ? obj : parent; | 
						|
 | 
						|
  for (var k in obj) { | 
						|
    var value = obj[k]; | 
						|
    if (Array.isArray(value)) { | 
						|
      value.forEach(function(v) { addParent(v, childParent); }); | 
						|
    } else if (value && typeof value === 'object') { | 
						|
      addParent(value, childParent); | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  if (isNode) { | 
						|
    Object.defineProperty(obj, 'parent', { | 
						|
      configurable: true, | 
						|
      writable: true, | 
						|
      enumerable: false, | 
						|
      value: parent || null | 
						|
    }); | 
						|
  } | 
						|
 | 
						|
  return obj; | 
						|
}
 | 
						|
 |