Css Parser

var css = new _CSS("a {color: red;} pre {margin: 0;}", "<html><head></head><body><a>link</a></body></html>");
//var css = new _CSS(css[, html]);
//html: HTML-source; String. (default: document.body.parentElement.outerHTML)


console.log(css.json());
//{
// IMPORT: [],
// KEYFRAME: {},
// MEDIA: {},
// STYLE: {
// a: {color: "red"},
// pre: {margin: "0"}
// }
//}

console.log(css.json({
filter: true
}));
//{
// IMPORT: [],
// KEYFRAME: {},
// MEDIA: {},
// STYLE: {
// a: {color: "red"}
// }
//}


console.log(css.string());
//a {
// color: red;
//}
//pre {
// margin: 0;
//}

console.log(css.string({
filter: true,
specificity: true,
space: 8
}));
///* 0.0.1 */
//a {
// color: red;
//}

console.log(css.string({
min: true
}));
//a {color: red;}pre {margin: 0;}


arguments for css.string() and css.json()

filter: If it's true, delete unnecessary selectors for the HTML source (the second argument of new _CSS()).


arguments only for css.string()

specificity: If it's true, display CSS-specificity (link).

space: The number of space characters to use as white space.

min: If it's true, the outputted style will be minified.

/*
    CSS Parser for client-side javascript
    created by 7happy7
    http://www.wikidot.com/user:info/7happy7
 
    CC BY-SA 3.0
*/
 
!function(w) {
    var pse = ["active","any-link","blank","current","default","defined","dir","drop","empty","enabled","first","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","left","link","local-link","not","nth-col","nth-last-col","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","state","target","target-within","user-invalid","valid","visited","where","after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error","-webkit-.+?","-moz-.+?","-ms-.+?"];
 
    var REG = {
        PSEUDO_S: new RegExp('(\\:{1,2}(?:' + pse.join('|') + ')(?:$| |\\(.*?\\)))', "g"),
        COMMENT_S: new RegExp('\\/\\*[\\s\\S]*?\\*\\/','g'),
        SELECTOR_S: new RegExp('([\\s\\S]*?){([\\s\\S]*?)}','g'),
        SELECTOR: new RegExp('([\\s\\S]*?){([\\s\\S]*?)}'),
        MEDIA_S: new RegExp('((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})','g'),
        MEDIA: new RegExp('((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'),
        KEYFRAME_S: new RegExp('((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})','g'),
        KEYFRAME: new RegExp('((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'),
        IMPORT_S: new RegExp('@import (?:url\\(.*?\\)|).*?;','g'),
        CHARSET_S: new RegExp('@charset .*?;','g')
    };
    var styleFormator = function(style, target, html) {
        var selector = style.match(REG.SELECTOR_S);
        var res = target || {};
        if (selector) {
            for (var s of selector) {
                var base = s.match(REG.SELECTOR);
                var sel = base[1].trim();
                var raw = sel.replace(REG.PSEUDO_S, "").trim();
                res[sel] = res[sel] || {};
                res[sel]._status = {
                    invalid: (function() {
                        try {
                            return html.querySelector(raw) ? false : true;
                        }catch (e) {
                            return null;
                        }
                    })()
                };
                var variables = base[2].split(";");
                for (var v of variables) {
                    var key = v.split(":")[0].trim();
                    var value = v.split(":").slice(1).join(":").trim();
                    if (key !== "" && value !== "") {
                        res[sel][key] = value;
                    }
                }
            }
        }
        return res;
    }
    var CSSParserObject = function(css,html) {
        this.RAW = css || "";
        this.HTML = html ? (function(){
            var p = new DOMParser();
            try {
                return p.parseFromString(html, "text/html")
            }catch (e) {
                throw new Error("invalid HTML");
            }
        })() : w.document;
 
        var STYLES = {
            STYLE: {},
            MEDIA: {},
            KEYFRAME: {},
            IMPORT: [],
            CHARSET: null
        };
 
        /* parse */
        var a = this.RAW.replace(REG.COMMENT_S, "");
 
        var _charset = a.match(REG.CHARSET_S);
        if (_charset) {
            STYLES.CHARSET = _charset[_charset.length-1];
        }
        a = a.replace(REG.CHARSET_S, "");
 
        var _import = a.match(REG.IMPORT_S);
        if (_import) {
            STYLES.IMPORT = STYLES.IMPORT.concat(_import)
        }
        a = a.replace(REG.IMPORT_S, "");
 
        var mediaStyle = a.match(REG.MEDIA_S);
        if (mediaStyle) {
            for (var s of mediaStyle) {
                var base = s.match(REG.MEDIA);
                var sel = base[2].trim();
                STYLES.MEDIA[sel] = STYLES.MEDIA[sel] || {};
                styleFormator(base[3], STYLES.MEDIA[sel], this.HTML);
            }
        }
        a = a.replace(REG.MEDIA_S, "");
 
        var keyStyle = a.match(REG.KEYFRAME_S);
        if (keyStyle) {
            for (var s of keyStyle) {
                var base = s.match(REG.KEYFRAME);
                var sel = base[2].trim();
                STYLES.KEYFRAME[sel] = STYLES.KEYFRAME[sel] || {};
                styleFormator(base[3], STYLES.KEYFRAME[sel], this.HTML);
            }
        }
        a = a.replace(REG.KEYFRAME_S, "");
 
        styleFormator(a, STYLES.STYLE, this.HTML);
 
        this.STYLE = STYLES;
        return this;
    }
 
    var _j = function(_o, filter) {
        var o = Object.assign({}, _o);
        for(var k of Object.keys(o).filter(function(r) {return r!=="CHARSET"&&r!=="IMPORT"})) {
            if(k=="STYLE") {
                for(var _k in o[k]) {
                    if(filter&&o[k][_k]._status&&o[k][_k]._status.invalid) {
                        delete o[k][_k];
                    }else {
                        delete o[k][_k]._status;
                    }
                }
            }else {
                for(var _k in o[k]) {
                    for(var __k in o[k][_k]) {
                        if(filter&&o[k][_k][__k]._status&&o[k][_k][__k]._status.invalid) {
                            delete o[k][_k][__k];
                            if(!Object.keys(o[k][_k]).length) {
                                delete o[k][_k];
                                break;
                            }
                        }else {
                            delete o[k][_k][__k]._status;
                        }
                    }
                }
            }
        }
        return o;
    }
 
    CSSParserObject.prototype.json = function({filter=false}={}) {
        return _j(this.STYLE, filter);
    }
 
    CSSParserObject.prototype.string = function({space=2, filter=false, specificity=false, min=false}={}) {
        var o = _j(this.STYLE, filter);
 
        var INDENT = function(lv) {
            return min ? "" : " ".repeat(space).repeat(lv);
        }
 
        var key_format = function(sels, ind="") {
            return (sels.split(",").map(function(v) {return v.trim()}).join(",\n" + ind));
        }
        var spec_calc = function(sels, esc, ind="") {
            if(!specificity || esc) {
                return "";
            }
            var res = [];
            sels = sels.split(",").map(function(r) {return r.trim();}).filter(function(r) {return r;});
            function sep(key, value) {return value.split(key).length - 1;}
 
            for(var sel of sels) {
                var count = {a: 0, b: 0, c: 0};
                sel.split(/ [\+|\>] |\*(?!\=)| /ig).filter(function(r) {
                    return r;
                }).forEach(function(r) {
                    if(sep("#", r)) count.a += sep("#", r);
                    if(sep(".", r)) count.b += sep(".", r);
                    if(sep(/\[.*?\]/g, r)) count.b += sep(/\[.*?\]/g, r);
                    if(sep(/^[a-zA-Z]/, r)) count.c += sep(/^[a-zA-Z]/, r);
                    if(sep(/\:\:|\:/g, r)) count.c += sep(/\:\:|\:/g, r);
                })
                res.push(count.a + "." + count.b + "." + count.c);
            }
 
            return "/* " + res.join(", ") + " */\n" + ind;
        }
 
        var _s = (o.CHARSET ? o.CHARSET + "\n" : "") + (o.IMPORT.length ? o.IMPORT.join("\n") + "\n" : "");
 
        if (Object.keys(o.STYLE).length) {
            for (var key in o.STYLE) {
                _s += spec_calc(key) + key_format(key) + " {\n";
                for (var _key of Object.keys(o.STYLE[key]).sort()) {
                    _s += INDENT(1) + _key + ": " + o.STYLE[key][_key].split(/\n/).map(function(v) {return v.trim()}).join("").split(",").map(function(v) {return v.trim()}).join(",") + ";\n"
                }
                _s += "}\n";
            }
        }
        for (var que of ["KEYFRAME", "MEDIA"]) {
            if (Object.keys(o[que]).length) {
                for (var key in o[que]) {
                    _s += key + " {\n";
                    for (var _key in o[que][key]) {
                        _s += INDENT(1) + spec_calc(_key, que=="KEYFRAME", INDENT(1)) + key_format(_key, INDENT(1)) + " {\n"
                        for (var __key of Object.keys(o[que][key][_key]).sort()) {
                            _s += INDENT(2) + __key + ": " + o[que][key][_key][__key].split(/\n/).map(function(v) {return v.trim()}).join("").split(",").map(function(v) {return v.trim()}).join(",") + ";\n"
                        }
                        _s += INDENT(1) + "}\n"
                    }
                    _s += "}\n";
                }
            }
        }
        return (min ? _s.replace(/\n/g, "") : _s).trim();
    }
 
    w._CSS = CSSParserObject;
}(window);
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License