176 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
		
		
			
		
	
	
			176 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| 
								 | 
							
								const blank = {
							 | 
						||
| 
								 | 
							
								  ' ': true,
							 | 
						||
| 
								 | 
							
								  '\n': true,
							 | 
						||
| 
								 | 
							
								  '\t': true,
							 | 
						||
| 
								 | 
							
								  '\r': true,
							 | 
						||
| 
								 | 
							
								  '\f': true
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function Parser () {
							 | 
						||
| 
								 | 
							
								  this.styles = []
							 | 
						||
| 
								 | 
							
								  this.selectors = []
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @description 解析 css 字符串
							 | 
						||
| 
								 | 
							
								 * @param {string} content css 内容
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								Parser.prototype.parse = function (content) {
							 | 
						||
| 
								 | 
							
								  new Lexer(this).parse(content)
							 | 
						||
| 
								 | 
							
								  return this.styles
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @description 解析到一个选择器
							 | 
						||
| 
								 | 
							
								 * @param {string} name 名称
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								Parser.prototype.onSelector = function (name) {
							 | 
						||
| 
								 | 
							
								  // 不支持的选择器
							 | 
						||
| 
								 | 
							
								  if (name.includes('[') || name.includes('*') || name.includes('@')) return
							 | 
						||
| 
								 | 
							
								  const selector = {}
							 | 
						||
| 
								 | 
							
								  // 伪类
							 | 
						||
| 
								 | 
							
								  if (name.includes(':')) {
							 | 
						||
| 
								 | 
							
								    const info = name.split(':')
							 | 
						||
| 
								 | 
							
								    const pseudo = info.pop()
							 | 
						||
| 
								 | 
							
								    if (pseudo === 'before' || pseudo === 'after') {
							 | 
						||
| 
								 | 
							
								      selector.pseudo = pseudo
							 | 
						||
| 
								 | 
							
								      name = info[0]
							 | 
						||
| 
								 | 
							
								    } else return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // 分割交集选择器
							 | 
						||
| 
								 | 
							
								  function splitItem (str) {
							 | 
						||
| 
								 | 
							
								    const arr = []
							 | 
						||
| 
								 | 
							
								    let i, start
							 | 
						||
| 
								 | 
							
								    for (i = 1, start = 0; i < str.length; i++) {
							 | 
						||
| 
								 | 
							
								      if (str[i] === '.' || str[i] === '#') {
							 | 
						||
| 
								 | 
							
								        arr.push(str.substring(start, i))
							 | 
						||
| 
								 | 
							
								        start = i
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (!arr.length) {
							 | 
						||
| 
								 | 
							
								      return str
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      arr.push(str.substring(start, i))
							 | 
						||
| 
								 | 
							
								      return arr
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // 后代选择器
							 | 
						||
| 
								 | 
							
								  if (name.includes(' ')) {
							 | 
						||
| 
								 | 
							
								    selector.list = []
							 | 
						||
| 
								 | 
							
								    const list = name.split(' ')
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < list.length; i++) {
							 | 
						||
| 
								 | 
							
								      if (list[i].length) {
							 | 
						||
| 
								 | 
							
								        // 拆分子选择器
							 | 
						||
| 
								 | 
							
								        const arr = list[i].split('>')
							 | 
						||
| 
								 | 
							
								        for (let j = 0; j < arr.length; j++) {
							 | 
						||
| 
								 | 
							
								          selector.list.push(splitItem(arr[j]))
							 | 
						||
| 
								 | 
							
								          if (j < arr.length - 1) {
							 | 
						||
| 
								 | 
							
								            selector.list.push('>')
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    selector.key = splitItem(name)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.selectors.push(selector)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @description 解析到选择器内容
							 | 
						||
| 
								 | 
							
								 * @param {string} content 内容
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								Parser.prototype.onContent = function (content) {
							 | 
						||
| 
								 | 
							
								  // 并集选择器
							 | 
						||
| 
								 | 
							
								  for (let i = 0; i < this.selectors.length; i++) {
							 | 
						||
| 
								 | 
							
								    this.selectors[i].style = content
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  this.styles = this.styles.concat(this.selectors)
							 | 
						||
| 
								 | 
							
								  this.selectors = []
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @description css 词法分析器
							 | 
						||
| 
								 | 
							
								 * @param {object} handler 高层处理器
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function Lexer (handler) {
							 | 
						||
| 
								 | 
							
								  this.selector = ''
							 | 
						||
| 
								 | 
							
								  this.style = ''
							 | 
						||
| 
								 | 
							
								  this.handler = handler
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Lexer.prototype.parse = function (content) {
							 | 
						||
| 
								 | 
							
								  this.i = 0
							 | 
						||
| 
								 | 
							
								  this.content = content
							 | 
						||
| 
								 | 
							
								  this.state = this.blank
							 | 
						||
| 
								 | 
							
								  for (let len = content.length; this.i < len; this.i++) {
							 | 
						||
| 
								 | 
							
								    this.state(content[this.i])
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Lexer.prototype.comment = function () {
							 | 
						||
| 
								 | 
							
								  this.i = this.content.indexOf('*/', this.i) + 1
							 | 
						||
| 
								 | 
							
								  if (!this.i) {
							 | 
						||
| 
								 | 
							
								    this.i = this.content.length
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Lexer.prototype.blank = function (c) {
							 | 
						||
| 
								 | 
							
								  if (!blank[c]) {
							 | 
						||
| 
								 | 
							
								    if (c === '/' && this.content[this.i + 1] === '*') {
							 | 
						||
| 
								 | 
							
								      this.comment()
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.selector += c
							 | 
						||
| 
								 | 
							
								    this.state = this.name
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Lexer.prototype.name = function (c) {
							 | 
						||
| 
								 | 
							
								  if (c === '/' && this.content[this.i + 1] === '*') {
							 | 
						||
| 
								 | 
							
								    this.comment()
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (c === '{' || c === ',' || c === ';') {
							 | 
						||
| 
								 | 
							
								    this.handler.onSelector(this.selector.trimEnd())
							 | 
						||
| 
								 | 
							
								    this.selector = ''
							 | 
						||
| 
								 | 
							
								    if (c !== '{') {
							 | 
						||
| 
								 | 
							
								      while (blank[this.content[++this.i]]);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (this.content[this.i] === '{') {
							 | 
						||
| 
								 | 
							
								      this.floor = 1
							 | 
						||
| 
								 | 
							
								      this.state = this.val
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      this.selector += this.content[this.i]
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else if (blank[c]) {
							 | 
						||
| 
								 | 
							
								    this.selector += ' '
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    this.selector += c
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Lexer.prototype.val = function (c) {
							 | 
						||
| 
								 | 
							
								  if (c === '/' && this.content[this.i + 1] === '*') {
							 | 
						||
| 
								 | 
							
								    this.comment()
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (c === '{') {
							 | 
						||
| 
								 | 
							
								    this.floor++
							 | 
						||
| 
								 | 
							
								  } else if (c === '}') {
							 | 
						||
| 
								 | 
							
								    this.floor--
							 | 
						||
| 
								 | 
							
								    if (!this.floor) {
							 | 
						||
| 
								 | 
							
								      this.handler.onContent(this.style)
							 | 
						||
| 
								 | 
							
								      this.style = ''
							 | 
						||
| 
								 | 
							
								      this.state = this.blank
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  this.style += c
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = Parser
							 |