diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..c1470e0 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,287 @@ +{ + "env": { + "browser": true, + "commonjs": true + }, + "extends": "eslint:recommended", + "rules": { + "accessor-pairs": "error", + "array-bracket-newline": "error", + "array-bracket-spacing": [ + "error", + "never" + ], + "array-callback-return": "error", + "array-element-newline": "error", + "arrow-body-style": "error", + "arrow-parens": "error", + "arrow-spacing": "error", + "block-scoped-var": "off", + "block-spacing": "error", + "brace-style": [ + "error", + "1tbs" + ], + "callback-return": "error", + "camelcase": "error", + "capitalized-comments": "off", + "class-methods-use-this": "error", + "comma-dangle": "error", + "comma-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "comma-style": [ + "error", + "last" + ], + "complexity": "error", + "computed-property-spacing": [ + "error", + "never" + ], + "consistent-return": "off", + "consistent-this": "error", + "curly": "error", + "default-case": "error", + "dot-location": "error", + "dot-notation": "error", + "eol-last": "error", + "eqeqeq": "error", + "for-direction": "error", + "func-call-spacing": "error", + "func-name-matching": "error", + "func-names": [ + "error", + "never" + ], + "func-style": [ + "error", + "expression" + ], + "function-paren-newline": "error", + "generator-star-spacing": "error", + "getter-return": "error", + "global-require": "error", + "guard-for-in": "error", + "handle-callback-err": "error", + "id-blacklist": "error", + "id-length": "off", + "id-match": "error", + "implicit-arrow-linebreak": "error", + "indent": "off", + "indent-legacy": "off", + "init-declarations": "off", + "jsx-quotes": "error", + "key-spacing": "error", + "keyword-spacing": "off", + "line-comment-position": "off", + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": "off", + "lines-around-directive": "off", + "lines-between-class-members": "error", + "max-depth": "error", + "max-len": "off", + "max-lines": "off", + "max-nested-callbacks": "error", + "max-params": "error", + "max-statements": "off", + "max-statements-per-line": "error", + "multiline-comment-style": "off", + "multiline-ternary": "error", + "new-cap": "error", + "new-parens": "error", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "off", + "no-alert": "error", + "no-array-constructor": "error", + "no-await-in-loop": "error", + "no-bitwise": "error", + "no-buffer-constructor": "error", + "no-caller": "error", + "no-catch-shadow": "error", + "no-confusing-arrow": "error", + "no-constant-condition": [ + "error", + { + "checkLoops": false + } + ], + "no-continue": "off", + "no-div-regex": "error", + "no-duplicate-imports": "error", + "no-else-return": "off", + "no-empty-function": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-coercion": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-inner-declarations": [ + "error", + "functions" + ], + "no-invalid-this": "off", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "off", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": "error", + "no-mixed-requires": "error", + "no-multi-assign": "error", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": "error", + "no-native-reassign": "error", + "no-negated-condition": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": [ + "error", + { + "allowForLoopAfterthoughts": true + } + ], + "no-process-env": "error", + "no-process-exit": "error", + "no-proto": "error", + "no-prototype-builtins": "error", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": "error", + "no-return-assign": "error", + "no-return-await": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-spaced-func": "error", + "no-sync": "error", + "no-tabs": "error", + "no-template-curly-in-string": "error", + "no-ternary": "error", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef-init": "error", + "no-undefined": "off", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unused-expressions": "error", + "no-use-before-define": "error", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "off", + "no-void": "error", + "no-warning-comments": "off", + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": "error", + "object-curly-newline": "error", + "object-curly-spacing": "error", + "object-property-newline": "error", + "object-shorthand": "off", + "one-var": "off", + "one-var-declaration-per-line": [ + "error", + "initializations" + ], + "operator-assignment": [ + "error", + "always" + ], + "operator-linebreak": "error", + "padded-blocks": "off", + "padding-line-between-statements": "error", + "prefer-arrow-callback": "error", + "prefer-const": "error", + "prefer-destructuring": "off", + "prefer-numeric-literals": "error", + "prefer-promise-reject-errors": "error", + "prefer-reflect": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "off", + "quote-props": "off", + "quotes": "off", + "radix": "error", + "require-await": "error", + "require-jsdoc": "error", + "rest-spread-spacing": "error", + "semi": "error", + "semi-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "semi-style": [ + "error", + "last" + ], + "sort-imports": "error", + "sort-keys": "off", + "sort-vars": "error", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-in-parens": [ + "error", + "never" + ], + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": "off", + "strict": [ + "error", + "function" + ], + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": "error", + "template-tag-spacing": "error", + "unicode-bom": [ + "error", + "never" + ], + "valid-jsdoc": "error", + "vars-on-top": "off", + "wrap-regex": "error", + "yield-star-spacing": "error", + "yoda": [ + "error", + "never" + ] + } +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..58962c1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "4.2.1" +before_install: npm install -g grunt-cli diff --git a/README.md b/README.md index c3728e6..6824f15 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -css.js +css.js [![Build Status](https://travis-ci.org/jotform/css.js.svg)](https://travis-ci.org/jotform/css.js) ====== A lightweight, battle tested, fast, css parser in JavaScript @@ -12,7 +12,7 @@ Please read the story behind it [here] Demo ====== -Check it out [plunker demo] +Check out [plunker demo] [plunker demo]: http://embed.plnkr.co/qMRJpJ92BHNrJuCnbRFB/preview @@ -22,34 +22,56 @@ Development Following commands will prepare development enviroment by installing dependencies: ``` - npm install - bower install +npm install ``` And to execute unit tests and produce css.min.js, execute ``` - grunt +grunt +``` + +How To Install +====== + +``` +npm install jotform-css.js ``` How To Use ====== +On the browser +------ Simply parse css string, and log the output +```html + + ``` - - -``` +On the server +------ +```js +var cssString = ' .someSelector { margin:40px 10px; padding:5px}'; +//require parser constructor +var cssjs = require("./css.js"); +//initialize parser object +var parser = new cssjs.cssjs(); +//parse css string +var parsed = parser.parseCSS(cssString); + +console.log(parsed); +``` diff --git a/bower.json b/bower.json deleted file mode 100755 index 5dda10a..0000000 --- a/bower.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "css.js", - "main": "css.js", - "version": "0.0.1", - "homepage": "https://github.com/cettox/css.js", - "authors": [ - "Kemal DAG " - ], - "description": "A lightweight, fast, stable css parser and utility library", - "keywords": [ - "css", - "css", - "parser", - "css", - "utility" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "jquery": "~2.1.3", - "qunit": "~1.16.0" - } -} diff --git a/css.js b/css.js old mode 100755 new mode 100644 index b35458f..f3061b3 --- a/css.js +++ b/css.js @@ -1,6 +1,6 @@ /* jshint unused:false */ -/* global base64_decode, CSSWizardView, window, console, jQuery */ -(function($) { +/* global window, console */ +(function(global) { 'use strict'; var fi = function() { @@ -10,7 +10,7 @@ this.cssRegex = new RegExp('([\\s\\S]*?){([\\s\\S]*?)}', 'gi'); this.cssMediaQueryRegex = '((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; this.cssKeyframeRegex = '((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; - this.combinedCSSRegex = '((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'; //to match css & media queries together + this.combinedCSSRegex = '((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'; //to match css & media queries together this.cssCommentsRegex = '(\\/\\*[\\s\\S]*?\\*\\/)'; this.cssImportStatementRegex = new RegExp('@import .*?;', 'gi'); }; @@ -103,6 +103,9 @@ selector = selector.replace(commentsRegex, '').trim(); } + // Never have more than a single line break in a row + selector = selector.replace(/\n+/, "\n"); + //determine the type if (selector.indexOf('@media') !== -1) { //we have a media query @@ -116,7 +119,7 @@ } css.push(cssObject); } else { - //we have standart css + //we have standard css var rules = this.parseRules(arr[6]); var style = { selector: selector, @@ -174,7 +177,7 @@ }); } else { //if there is no ':', but what if it was mis splitted value which starts with base64 - if (line.trim().substr(0, 7) == 'base64,') { //hack :) + if (line.trim().substr(0, 7) === 'base64,') { //hack :) ret[ret.length - 1].value += line.trim(); } else { //add rule, even if it is defective @@ -201,7 +204,7 @@ } var ret = false; for (var i = 0; i < rules.length; i++) { - if (rules[i].directive == directive) { + if (rules[i].directive === directive) { ret = rules[i]; if (value === rules[i].value) { break; @@ -233,7 +236,7 @@ } } - if (found.length < 2) { + if (selector === '@imports' || found.length < 2) { return found; } else { var base = found[0]; @@ -272,7 +275,7 @@ var found = this.findBySelector(cssObjectArray, obj.selector); //found compressed if (found.length !== 0) { - compressed.push(found[0]); + compressed = compressed.concat(found); done[obj.selector] = true; } } @@ -289,7 +292,7 @@ returns the changed(new,removed,updated) values on css1 parameter, on same structure if two css objects are the same, then returns false - + if a css directive exists in css1 and css2, and its value is different, it is included in diff if a css directive exists in css1 and not css2, it is then included in diff if a css directive exists in css2 but not css1, then it is deleted in css1, it would be included in diff but will be marked as type='DELETED' @@ -368,7 +371,7 @@ } for (i = 0; i < cssObjectArray.length; i++) { var cobj = cssObjectArray[i]; - if (cobj.type === 'media' ||  (cobj.type === 'keyframes')) { + if (cobj.type === 'media' || (cobj.type === 'keyframes')) { continue; } cobj.rules = this.compactRules(cobj.rules); @@ -377,7 +380,7 @@ /* inserts new css objects into a bigger css object - with same selectors groupped together + with same selectors grouped together @param cssObjectArray, array of bigger css object to be pushed into @param minimalObject, single css object @@ -395,14 +398,14 @@ if (reverse === false) { for (var i = 0; i < cssObjectArray.length; i++) { - if (cssObjectArray[i].selector === minimalObject.selector) { + if (cssObjectArray[i].selector === pushSelector) { cssObject = cssObjectArray[i]; break; } } } else { for (var j = cssObjectArray.length - 1; j > -1; j--) { - if (cssObjectArray[j].selector === minimalObject.selector) { + if (cssObjectArray[j].selector === pushSelector) { cssObject = cssObjectArray[j]; break; } @@ -419,7 +422,7 @@ var oldRule = this.findCorrespondingRule(cssObject.rules, rule.directive); if (oldRule === false) { cssObject.rules.push(rule); - } else if (rule.type == 'DELETED') { + } else if (rule.type === 'DELETED') { oldRule.type = 'DELETED'; } else { //rule found just update value @@ -428,7 +431,7 @@ } } } else { - cssObject.subStyles = minimalObject.subStyles; //TODO, make this intelligent too + cssObject.subStyles = cssObject.subStyles.concat(minimalObject.subStyles); //TODO, make this intelligent too } } @@ -439,7 +442,7 @@ @param rules, array of rules - @returns rules array, compacted by deleting all unneccessary rules + @returns rules array, compacted by deleting all unnecessary rules */ fi.prototype.compactRules = function(rules) { var newRules = []; @@ -465,7 +468,7 @@ } //append imports for (var i = 0; i < cssBase.length; i++) { - if (cssBase[i].type == 'imports') { + if (cssBase[i].type === 'imports') { ret += cssBase[i].styles + '\n\n'; } } @@ -479,7 +482,7 @@ comments = tmp.comments + '\n'; } - if (tmp.type == 'media') { //also put media queries to output + if (tmp.type === 'media') { //also put media queries to output ret += comments + tmp.selector + '{\n'; ret += this.getCSSForEditor(tmp.subStyles, depth + 1); ret += '}\n\n'; @@ -492,7 +495,7 @@ //append keyFrames for (i = 0; i < cssBase.length; i++) { - if (cssBase[i].type == 'keyframes') { + if (cssBase[i].type === 'keyframes') { ret += cssBase[i].styles + '\n\n'; } } @@ -503,7 +506,7 @@ fi.prototype.getImports = function(cssObjectArray) { var imps = []; for (var i = 0; i < cssObjectArray.length; i++) { - if (cssObjectArray[i].type == 'imports') { + if (cssObjectArray[i].type === 'imports') { imps.push(cssObjectArray[i].styles); } } @@ -520,7 +523,7 @@ continue; } if (rules[i].defective === undefined) { - ret += this.getSpaces(depth) + rules[i].directive + ' : ' + rules[i].value + ';\n'; + ret += this.getSpaces(depth) + rules[i].directive + ': ' + rules[i].value + ';\n'; } else { ret += this.getSpaces(depth) + rules[i].value + ';\n'; } @@ -630,12 +633,12 @@ format = false; } - if (this.testMode === false && format!=='nonamespace') { + if (this.testMode === false && format !== 'nonamespace') { //apply namespacing classes css = this.applyNamespacing(css); } - if (typeof css != 'string') { + if (typeof css !== 'string') { css = this.getCSSForEditor(css); } //apply formatting for css @@ -647,9 +650,9 @@ return this.testMode('create style #' + id, css); //if test mode, just pass result to callback } - var __el = document.getElementById( id ); - if(__el){ - __el.parentNode.removeChild( __el ); + var __el = document.getElementById(id); + if (__el) { + __el.parentNode.removeChild(__el); } var head = document.head || document.getElementsByTagName('head')[0], @@ -667,6 +670,6 @@ } }; - window.cssjs = fi; + global.cssjs = fi; -})(); \ No newline at end of file +})(this); diff --git a/css.min.js b/css.min.js index 3478253..5fece1e 100644 --- a/css.min.js +++ b/css.min.js @@ -1,2 +1,3 @@ -/*! css.js 16-01-2015 */ -!function(){"use strict";var a=function(){this.cssImportStatements=[],this.cssKeyframeStatements=[],this.cssRegex=new RegExp("([\\s\\S]*?){([\\s\\S]*?)}","gi"),this.cssMediaQueryRegex="((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})",this.cssKeyframeRegex="((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})",this.combinedCSSRegex="((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})",this.cssCommentsRegex="(\\/\\*[\\s\\S]*?\\*\\/)",this.cssImportStatementRegex=new RegExp("@import .*?;","gi")};a.prototype.stripComments=function(a){var b=new RegExp(this.cssCommentsRegex,"gi");return a.replace(b,"")},a.prototype.parseCSS=function(a){if(void 0===a)return[];for(var b=[];;){var c=this.cssImportStatementRegex.exec(a);if(null===c)break;this.cssImportStatements.push(c[0]),b.push({selector:"@imports",type:"imports",styles:c[0]})}a=a.replace(this.cssImportStatementRegex,"");for(var d,e=new RegExp(this.cssKeyframeRegex,"gi");;){if(d=e.exec(a),null===d)break;b.push({selector:"@keyframes",type:"keyframes",styles:d[0]})}a=a.replace(e,"");for(var f=new RegExp(this.combinedCSSRegex,"gi");;){if(d=f.exec(a),null===d)break;var g="";g=void 0===d[2]?d[5].split("\r\n").join("\n").trim():d[2].split("\r\n").join("\n").trim();var h=new RegExp(this.cssCommentsRegex,"gi"),i=h.exec(g);if(null!==i&&(g=g.replace(h,"").trim()),-1!==g.indexOf("@media")){var j={selector:g,type:"media",subStyles:this.parseCSS(d[3]+"\n}")};null!==i&&(j.comments=i[0]),b.push(j)}else{var k=this.parseRules(d[6]),l={selector:g,rules:k};"@font-face"===g&&(l.type="font-face"),null!==i&&(l.comments=i[0]),b.push(l)}}return b},a.prototype.parseRules=function(a){a=a.split("\r\n").join("\n");var b=[];a=a.split(";");for(var c=0;c0&&b.push({directive:"",value:d,defective:!0})}return b},a.prototype.findCorrespondingRule=function(a,b,c){void 0===c&&(c=!1);for(var d=!1,e=0;e-1;f--)if(a[f].selector===b.selector){d=a[f];break}if(d===!1)a.push(b);else if("media"!==b.type)for(var g=0;gc;c++)b+=" ";return b},a.prototype.applyNamespacing=function(a,b){var c=a,d="."+this.cssPreviewNamespace;void 0!==b&&(d=b),"string"==typeof a&&(c=this.parseCSS(a));for(var e=0;e-1||f.selector.indexOf("keyframes")>-1||f.selector.indexOf("@import")>-1||f.selector.indexOf(".form-all")>-1||f.selector.indexOf("#stage")>-1))if("media"!==f.type){for(var g=f.selector.split(","),h=[],i=0;i0&&t.push({directive:"",value:r,defective:!0})}return t},t.prototype.findCorrespondingRule=function(e,t,s){void 0===s&&(s=!1);for(var r=!1,i=0;i-1;o--)if(e[o].selector===r){i=e[o];break}if(!1===i)e.push(t);else if("media"!==t.type)for(var l=0;l-1||n.selector.indexOf("keyframes")>-1||n.selector.indexOf("@import")>-1||n.selector.indexOf(".form-all")>-1||n.selector.indexOf("#stage")>-1))if("media"!==n.type){for(var o=n.selector.split(","),l=[],p=0;p