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
 |