fcp-css-analytic.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /**
  2. * 注册命名空间:baidu.cssAnalytic
  3. */
  4. baidu.namespace.register("baidu.cssAnalytic");
  5. /**
  6. *
  7. * css分析器
  8. * 支持一下css规则:
  9. * css规则
  10. * 1、@charset "utf-8"; //设置字符集
  11. * 2、@import url("a.css"); //import
  12. * 3、_property:value //ie6
  13. * 4、*property:value //ie6,7
  14. * 5、property:value\9; //ie6,7,8,9
  15. * 6、property//:value //非ie6
  16. * 7、* html selector{} //各种选择符
  17. * 8、@media all and (-webkit-min-device-pixel-ratio:10000),not all and (-webkit-min-device-pixel-ratio:0) { ... } //设备
  18. * 9、@-moz-xxx //firefox
  19. * 10、property:value !important; //important
  20. * 11、property:expression(onmouseover=function(){}) //expression,值里有可能有 { 和 }
  21. * 12、-webkit-border-radious:value //浏览器私有,减号开头
  22. * 13、filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="ds_laby.png", sizingMethod='crop') //ie下的filter,有(和)
  23. *
  24. * @author lichengyin (FCP:PHP代码)
  25. * @cover zhaoxianlie (FCPHelper:将PHP代码重写为Javascript代码)
  26. */
  27. baidu.cssAnalytic = function(){
  28. /**
  29. * 当前解析到的位置
  30. * @var int
  31. */
  32. this.parsePos = 0;
  33. this.content = '';
  34. this.contentLength = 0;
  35. this._output = [];
  36. this._pre_type = ''; //上一个特殊类型
  37. this.run = function($content){
  38. this.content = $content.trim().replace(/\n/g, "\n");
  39. this.contentLength = this.content.length;
  40. this.tokenAnalytic();
  41. return this._output;
  42. };
  43. this.tokenAnalytic = function(){
  44. var $token;
  45. while (true){
  46. $token = this.getNextToken();
  47. if ($token){
  48. if ($token[1] === baidu.FL.FL_EOF) break;
  49. this._output.push($token);
  50. if ($token[1] === baidu.FL.CSS_PROPERTY) {
  51. this._output.push([':', baidu.FL.CSS_COLON]);
  52. } else if ($token[1] === baidu.FL.CSS_VALUE) {
  53. this._output.push([';', baidu.FL.CSS_SEMICOLON]);
  54. }
  55. }
  56. }
  57. };
  58. this.getNextToken = function(){
  59. if (this.parsePos >= this.contentLength){
  60. return ['', baidu.FL.FL_EOF];
  61. }
  62. $char = this.content[this.parsePos];
  63. this.parsePos++;
  64. if ($char === "\x0d") return ''; //\r
  65. if ($char === "\x0a") return [$char, baidu.FL.FL_NEW_LINE];
  66. //避免出现多个空格在一起的情况
  67. if ($char.trim() === '' || $char === ';') return '';
  68. var $result ;
  69. //处理@开头的;如:@charset "utf-8";@import url("a.css"), @media xxx{}
  70. if ($char === '@'){
  71. $result = this._getAtToken($char);
  72. if ($result) return $result;
  73. }else if ($char === '{'){
  74. switch (this._pre_type){
  75. case baidu.FL.CSS_DEVICE_DESC :
  76. this._pre_type = baidu.FL.CSS_DEVICE_START;
  77. return [$char, baidu.FL.CSS_DEVICE_START];
  78. default :
  79. this._pre_type = baidu.FL.CSS_SELECTOER_START;
  80. return [$char, baidu.FL.CSS_SELECTOER_START];
  81. }
  82. }else if ($char === '}'){
  83. switch (this._pre_type){
  84. case baidu.FL.CSS_SELECTOER_END:
  85. this._pre_type = baidu.FL.CSS_DEVICE_END;
  86. return [$char, baidu.FL.CSS_DEVICE_END];
  87. default:
  88. for(var $i=this._output.length-1;$i>=0;$i--){
  89. var $item = this._output[$i];
  90. if($item[1] === baidu.FL.CSS_SELECTOER_START){
  91. this._pre_type = baidu.FL.CSS_SELECTOER_END;
  92. return [$char, baidu.FL.CSS_SELECTOER_END];
  93. }else if($item[1] === baidu.FL.CSS_DEVICE_START){
  94. this._pre_type = baidu.FL.CSS_DEVICE_END;
  95. return [$char, baidu.FL.CSS_DEVICE_END];
  96. }
  97. }
  98. this._pre_type = baidu.FL.CSS_SELECTOER_END;
  99. return [$char, baidu.FL.CSS_SELECTOER_END];
  100. }
  101. }else if (this.content.substr( this.parsePos - 1, 2) === '/*'){
  102. $result = this._getCommentToken($char);
  103. if ($result) return $result;
  104. }else if ($char === "\x0d" || $char === "\x0a"){
  105. return [$char, baidu.FL.FL_NEW_LINE];
  106. }
  107. switch (this._pre_type){
  108. case baidu.FL.CSS_SELECTOER_START :
  109. case baidu.FL.CSS_VALUE :
  110. $result = this._getPropertyToken($char);
  111. this._pre_type = baidu.FL.CSS_PROPERTY;
  112. return $result;
  113. case baidu.FL.CSS_PROPERTY :
  114. $result = this._getValueToken($char);
  115. this._pre_type = baidu.FL.CSS_VALUE;
  116. return $result;
  117. case baidu.FL.CSS_DEVICE_START:
  118. var $pos = this.parsePos;
  119. $result = this._getPropertyToken($char);
  120. var $str = $result[0];
  121. if($str.indexOf('{') > -1){
  122. this.parsePos = $pos;
  123. $result = this._getSelectorToken($char);
  124. this._pre_type = baidu.FL.CSS_DEVICE_START;
  125. if ($result) return $result;
  126. }else{
  127. this._pre_type = baidu.FL.CSS_PROPERTY;
  128. return $result;
  129. }
  130. default:
  131. $result = this._getSelectorToken($char);
  132. if ($result) return $result;
  133. }
  134. return [$char, baidu.FL.CSS_NORMAL];
  135. };
  136. /**
  137. * 处理@开头的;如:@charset "utf-8";@import url("a.css"), @media xxx{}
  138. * @param {Object} $char
  139. */
  140. this._getAtToken = function($char){
  141. $resultString = $char;
  142. while (this.content[this.parsePos] !== ';'
  143. && this.content[this.parsePos] !== '{'
  144. && this.parsePos < this.contentLength){
  145. $resultString += this.content[this.parsePos];
  146. this.parsePos++;
  147. }
  148. if (this.content[this.parsePos] === ';'){
  149. $resultString += ';';
  150. this.parsePos++;
  151. return [$resultString.trim(), baidu.FL.CSS_AT];
  152. }
  153. this._pre_type = baidu.FL.CSS_DEVICE_DESC;
  154. return [$resultString.trim(), baidu.FL.CSS_DEVICE_DESC];
  155. };
  156. /**
  157. * comment
  158. * @param {Object} $char
  159. * @param {Object} $fromSelector=false
  160. */
  161. this._getCommentToken = function($char, $fromSelector){
  162. this.parsePos++;
  163. $resultString = '';
  164. while (!(this.content[this.parsePos] === '*'
  165. && this.content[this.parsePos + 1]
  166. && this.content[this.parsePos + 1] === '/')
  167. && this.parsePos < this.contentLength){
  168. $resultString += this.content[this.parsePos];
  169. this.parsePos++;
  170. }
  171. this.parsePos += 2;
  172. if ($fromSelector){
  173. return '/*' + $resultString + '*/';
  174. }
  175. return ['/*' + $resultString + '*/' , baidu.FL.CSS_COMMENT];
  176. };
  177. /**
  178. * selector content
  179. * 选择符里可能还有注释,注释里可能含有{}等字符
  180. */
  181. this._getSelectorToken = function($char){
  182. var $resultString = $char;
  183. while (this.content[this.parsePos] !== '{'
  184. && this.content[this.parsePos] !== '}'
  185. && this.parsePos < this.contentLength){
  186. //如果选择符中含有注释
  187. if (this.content[this.parsePos] === '/' &&
  188. this.content[this.parsePos+1] &&
  189. this.content[this.parsePos+1] === '*'){
  190. $resultString += this._getCommentToken('/', true);
  191. }else{
  192. $resultString += this.content[this.parsePos];
  193. this.parsePos++;
  194. }
  195. }
  196. return [$resultString.trim(), baidu.FL.CSS_SELECTOER];
  197. };
  198. /**
  199. * css property
  200. * @param {Object} $char
  201. */
  202. this._getPropertyToken = function($char){
  203. $resultString = $char;
  204. while (this.content[this.parsePos] !== ':' &&
  205. this.content[this.parsePos] !== ';' &&
  206. this.content[this.parsePos] !== '}' &&
  207. this.parsePos < this.contentLength){
  208. $resultString += this.content[this.parsePos];
  209. this.parsePos++;
  210. }
  211. //增加对div{color}的容错机制
  212. if (this.content[this.parsePos] !== '}'){
  213. this.parsePos++;
  214. }
  215. return [$resultString.trim().toLowerCase(), baidu.FL.CSS_PROPERTY];
  216. };
  217. /**
  218. * css value
  219. * @param {Object} $char
  220. */
  221. this._getValueToken = function($char){
  222. var $resultString = $char;
  223. var $isExpression = false;
  224. while (this.content[this.parsePos] !== ';'
  225. && this.content[this.parsePos] !== '}'
  226. && this.parsePos < this.contentLength){
  227. $char = this.content[this.parsePos];
  228. this.parsePos++;
  229. $resultString += $char;
  230. if (!$isExpression && $resultString.toLowerCase() === 'expression('){
  231. $isExpression = true;
  232. $resultString += this._getJSToken();
  233. }
  234. }
  235. if (this.content[this.parsePos] === ';'){
  236. this.parsePos++;
  237. }
  238. //将多个空格变成一个空格
  239. $resultString = $resultString.trim().replace(/\s+/ig, " ");
  240. return [$resultString, baidu.FL.CSS_VALUE];
  241. };
  242. /**
  243. * 处理expression里的javascript
  244. */
  245. this._getJSToken = function(){
  246. var $string = '',$char;
  247. while (this.parsePos < this.contentLength){
  248. $char = this.content[this.parsePos];
  249. this.parsePos++;
  250. $string += $char;
  251. //这里使用js分析器,然后判断(和) 个数是否相等
  252. if ($char === ')' && this._checkJSToken('(' + $string)){
  253. break;
  254. }
  255. }
  256. return $string;
  257. };
  258. /**
  259. * check js for expression
  260. * @param array $output
  261. */
  262. this._checkJSToken = function($output){
  263. var $expr_start = 0;
  264. var $expr_end = 0;
  265. for (var $i=0,$count=$output.length;$i<$count;$i++){
  266. var $item = $output[$i];
  267. if ($item[0] === '(') $expr_start++;
  268. else if ($item[0] === ')') $expr_end++;
  269. }
  270. return $expr_start === $expr_end;
  271. };
  272. };