mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-11-04 21:06:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			603 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			603 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// 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: {
 | 
						|
        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;
 | 
						|
}
 |