UNPKG

@dooboostore/dom-render

Version:
498 lines 12.8 kB
// http://www.w3.org/TR/CSS21/grammar.html // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 const commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g; export default function parse(css, options = {}) { /** * Positional. */ let lineno = 1; let column = 1; /** * Update lineno and column based on `str`. */ function updatePosition(str) { const lines = str.match(/\n/g); if (lines) lineno += lines.length; const i = str.lastIndexOf('\n'); column = ~i ? str.length - i : column + str.length; } /** * Mark position and patch `node.position`. */ function position() { const start = { line: lineno, column: column }; return function (node) { node.position = new Position(start); whitespace(); return node; }; } /** * Store position information for a node */ class Position { constructor(start) { this.start = start; this.end = { line: lineno, column: column }; this.source = options.source; this.content = css; } } /** * Error `msg`. */ const errorsList = []; function error(msg) { const err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg); err['reason'] = msg; err['filename'] = options.source; err['line'] = lineno; err['column'] = column; err['source'] = css; if (options.silent) { errorsList.push(err); throw err; } else { throw err; } } /** * Parse stylesheet. */ function stylesheet() { const rulesList = rules(); return { type: 'stylesheet', stylesheet: { source: options.source, rules: rulesList, parsingErrors: errorsList } }; } /** * Opening brace. */ function open() { return match(/^{\s*/); } /** * Closing brace. */ function close() { return match(/^}/); } /** * Parse ruleset. */ function rules() { let node; const rules = []; whitespace(); comments(rules); while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) { if (node) { rules.push(node); comments(rules); } } return rules; } /** * Match `re` and return captures. */ function match(re) { const m = re.exec(css); if (!m) return null; const str = m[0]; updatePosition(str); css = css.slice(str.length); return m; } /** * Parse whitespace. */ function whitespace() { match(/^\s*/); } /** * Parse comments; */ function comments(rules = []) { let c; while ((c = comment())) { rules.push(c); } return rules; } /** * Parse comment. */ function comment() { const pos = position(); if ('/' != css.charAt(0) || '*' != css.charAt(1)) return null; let i = 2; while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i; i += 2; if ("" === css.charAt(i - 1)) { error('End of comment missing'); } const str = css.slice(2, i - 2); column += 2; updatePosition(str); css = css.slice(i); column += 2; return pos({ type: 'comment', comment: str }); } /** * Parse selector. */ function selector() { const m = match(/^([^{]+)/); if (!m) return undefined; /* @fix Remove all comments from selectors * http://ostermiller.org/findcomment.html */ return trim(m[0]) .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '') .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function (m) { return m.replace(/,/g, '\u200C'); }) .split(/\s*(?![^(]*\]),\s*/) .map(function (s) { return s.replace(/\u200C/g, ','); }); } /** * Parse declaration. */ function declaration() { const pos = position(); // prop const prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/); if (!prop) return null; const propValue = trim(prop[0]); // : if (!match(/^:\s*/)) error("property missing ':'"); // val const val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/); const ret = pos({ type: 'declaration', property: propValue.replace(commentre, ''), value: val ? trim(val[0]).replace(commentre, '') : '' }); // ; match(/^[;\s]*/); return ret; } /** * Parse declarations. */ function declarations() { const decls = []; if (!open()) error("missing '{'"); comments(decls); // declarations let decl; while ((decl = declaration())) { decls.push(decl); comments(decls); } if (!close()) error("missing '}'"); return decls; } /** * Parse keyframe. */ function keyframe() { const m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/); if (!m) return null; const vals = []; const pos = position(); vals.push(m[1]); match(/^,\s*/); return pos({ type: 'keyframe', values: vals, declarations: declarations() }); } /** * Parse keyframes. */ function atkeyframes() { const pos = position(); const m1 = match(/^@([-\w]+)?keyframes\s*/); if (!m1) return null; const vendor = m1[1]; // identifier const m2 = match(/^([-\w]+)\s*/); if (!m2) error("@keyframes missing name"); const name = m2[1]; if (!open()) error("@keyframes missing '{'"); let frame; let frames = comments(); while ((frame = keyframe())) { frames.push(frame); frames = frames.concat(comments()); } if (!close()) error("@keyframes missing '}'"); return pos({ type: 'keyframes', name: name, vendor: vendor, keyframes: frames }); } /** * Parse supports. */ function atsupports() { const pos = position(); const m = match(/^@supports *([^{]+)/); if (!m) return null; const supports = trim(m[1]); if (!open()) error("@supports missing '{'"); const style = comments().concat(rules()); if (!close()) error("@supports missing '}'"); return pos({ type: 'supports', supports: supports, rules: style }); } /** * Parse host. */ function athost() { const pos = position(); const m = match(/^@host\s*/); if (!m) return null; if (!open()) error("@host missing '{'"); const style = comments().concat(rules()); if (!close()) error("@host missing '}'"); return pos({ type: 'host', rules: style }); } /** * Parse media. */ function atmedia() { const pos = position(); const m = match(/^@media *([^{]+)/); if (!m) return null; const media = trim(m[1]); if (!open()) error("@media missing '{'"); const style = comments().concat(rules()); if (!close()) error("@media missing '}'"); return pos({ type: 'media', media: media, rules: style }); } /** * Parse custom-media. */ function atcustommedia() { const pos = position(); const m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/); if (!m) return null; return pos({ type: 'custom-media', name: trim(m[1]), media: trim(m[2]) }); } /** * Parse paged media. */ function atpage() { const pos = position(); const m = match(/^@page */); if (!m) return null; const sel = selector() || []; if (!open()) error("@page missing '{'"); let decls = comments(); // declarations let decl; while ((decl = declaration())) { decls.push(decl); decls = decls.concat(comments()); } if (!close()) error("@page missing '}'"); return pos({ type: 'page', selectors: sel, declarations: decls }); } /** * Parse document. */ function atdocument() { const pos = position(); const m = match(/^@([-\w]+)?document *([^{]+)/); if (!m) return null; const vendor = trim(m[1]); const doc = trim(m[2]); if (!open()) error("@document missing '{'"); const style = comments().concat(rules()); if (!close()) error("@document missing '}'"); return pos({ type: 'document', document: doc, vendor: vendor, rules: style }); } /** * Parse font-face. */ function atfontface() { const pos = position(); const m = match(/^@font-face\s*/); if (!m) return null; if (!open()) error("@font-face missing '{'"); let decls = comments(); // declarations let decl; while ((decl = declaration())) { decls.push(decl); decls = decls.concat(comments()); } if (!close()) error("@font-face missing '}'"); return pos({ type: 'font-face', declarations: decls }); } /** * Parse import */ const atimport = _compileAtrule('import'); /** * Parse charset */ const atcharset = _compileAtrule('charset'); /** * Parse namespace */ const atnamespace = _compileAtrule('namespace'); /** * Parse non-block at-rules */ function _compileAtrule(name) { const re = new RegExp('^@' + name + '\\s*([^;]+);'); return function () { const pos = position(); const m = match(re); if (!m) return null; const ret = { type: name }; ret[name] = m[1].trim(); return pos(ret); }; } /** * Parse at rule. */ function atrule() { if (css[0] != '@') return null; return atkeyframes() || atmedia() || atcustommedia() || atsupports() || atimport() || atcharset() || atnamespace() || atdocument() || atpage() || athost() || atfontface(); } /** * Parse rule. */ function rule() { const pos = position(); const sel = selector(); if (!sel) error('selector missing'); comments(); return pos({ type: 'rule', selectors: sel, declarations: declarations() }); } return addParent(stylesheet()); } /** * Trim `str`. */ function trim(str) { return str ? str.replace(/^\s+|\s+$/g, '') : ''; } /** * Adds non-enumerable parent node reference to each node. */ function addParent(obj, parent) { const isNode = obj && typeof obj.type === 'string'; const childParent = isNode ? obj : parent; for (const k in obj) { const value = obj[k]; if (Array.isArray(value)) { value.forEach(function (v) { addParent(v, childParent); }); } else if (value && typeof value === 'object') { addParent(value, childParent); } } if (isNode) { Object.defineProperty(obj, 'parent', { configurable: true, writable: true, enumerable: false, value: parent || null }); } return obj; } //# sourceMappingURL=index.js.map