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 |