123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888 |
- // Credits: https://github.com/reworkcss/css/tree/master/lib/parse
- // Licensed under MIT
- // Forge: modified while{} loop declarations on some lines to reduce errors on logging
- // http://www.w3.org/TR/CSS21/grammar.html
- // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
- const commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
- export function parse(css, options) {
- options = options || {};
- /**
- * Positional.
- */
- var lineno = 1;
- var column = 1;
- /**
- * Update lineno and column based on `str`.
- */
- function updatePosition(str) {
- var lines = str.match(/\n/g);
- if (lines) lineno += lines.length;
- var i = str.lastIndexOf("\n");
- column = ~i ? str.length - i : column + str.length;
- }
- /**
- * Mark position and patch `node.position`.
- */
- function position() {
- var start = { line: lineno, column: column };
- return function (node) {
- node.position = new Position(start);
- whitespace();
- return node;
- };
- }
- /**
- * Store position information for a node
- */
- class Position {
- /**
- * Non-enumerable source string
- */
- content = css;
- constructor(start) {
- this.start = start;
- this.end = { line: lineno, column: column };
- this.source = options.source;
- }
- }
- /**
- * Error `msg`.
- */
- var errorsList = [];
- function error(msg) {
- var 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);
- } else {
- throw err;
- }
- }
- /**
- * Parse stylesheet.
- */
- function stylesheet() {
- var 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() {
- var node;
- var rules = [];
- whitespace();
- comments(rules);
- while (css.length && css.charAt(0) != "}" && (node = atrule() || rule())) {
- if (node !== false) {
- rules.push(node);
- comments(rules);
- }
- }
- return rules;
- }
- /**
- * Match `re` and return captures.
- */
- function match(re) {
- var m = re.exec(css);
- if (!m) return;
- var str = m[0];
- updatePosition(str);
- css = css.slice(str.length);
- return m;
- }
- /**
- * Parse whitespace.
- */
- function whitespace() {
- match(/^\s*/);
- }
- /**
- * Parse comments;
- */
- function comments(rules) {
- rules = rules || [];
- for (var c; (c = comment()); ) {
- if (c !== false) {
- rules.push(c);
- }
- }
- return rules;
- }
- /**
- * Parse comment.
- */
- function comment() {
- var pos = position();
- if ("/" != css.charAt(0) || "*" != css.charAt(1)) return;
- var i = 2;
- while ("" != css.charAt(i) && ("*" != css.charAt(i) || "/" != css.charAt(i + 1))) ++i;
- i += 2;
- if ("" === css.charAt(i - 1)) {
- return error("End of comment missing");
- }
- var 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() {
- var m = match(/^([^{]+)/);
- if (!m) return;
- /* @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() {
- var pos = position();
- // prop
- var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
- if (!prop) return;
- prop = trim(prop[0]);
- // :
- if (!match(/^:\s*/)) return error("property missing ':'");
- // val
- var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);
- var ret = pos({
- type: "declaration",
- property: prop.replace(commentre, ""),
- value: val ? trim(val[0]).replace(commentre, "") : "",
- });
- // ;
- match(/^[;\s]*/);
- return ret;
- }
- /**
- * Parse declarations.
- */
- function declarations() {
- var decls = [];
- if (!open()) return error("missing '{'");
- comments(decls);
- // declarations
- for (var decl; (decl = declaration()); ) {
- if (decl !== false) {
- decls.push(decl);
- comments(decls);
- }
- }
- if (!close()) return error("missing '}'");
- return decls;
- }
- /**
- * Parse keyframe.
- */
- function keyframe() {
- var vals = [];
- var pos = position();
- for (var m; (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)); ) {
- vals.push(m[1]);
- match(/^,\s*/);
- }
- if (!vals.length) return;
- return pos({
- type: "keyframe",
- values: vals,
- declarations: declarations(),
- });
- }
- /**
- * Parse keyframes.
- */
- function atkeyframes() {
- var pos = position();
- var m = match(/^@([-\w]+)?keyframes\s*/);
- if (!m) return;
- var vendor = m[1];
- // identifier
- var m = match(/^([-\w]+)\s*/);
- if (!m) return error("@keyframes missing name");
- var name = m[1];
- if (!open()) return error("@keyframes missing '{'");
- var frames = comments();
- for (var frame; (frame = keyframe()); ) {
- frames.push(frame);
- frames = frames.concat(comments());
- }
- if (!close()) return error("@keyframes missing '}'");
- return pos({
- type: "keyframes",
- name: name,
- vendor: vendor,
- keyframes: frames,
- });
- }
- /**
- * Parse supports.
- */
- function atsupports() {
- var pos = position();
- var m = match(/^@supports *([^{]+)/);
- if (!m) return;
- var supports = trim(m[1]);
- if (!open()) return error("@supports missing '{'");
- var style = comments().concat(rules());
- if (!close()) return error("@supports missing '}'");
- return pos({
- type: "supports",
- supports: supports,
- rules: style,
- });
- }
- /**
- * Parse host.
- */
- function athost() {
- var pos = position();
- var m = match(/^@host\s*/);
- if (!m) return;
- if (!open()) return error("@host missing '{'");
- var style = comments().concat(rules());
- if (!close()) return error("@host missing '}'");
- return pos({
- type: "host",
- rules: style,
- });
- }
- /**
- * Parse media.
- */
- function atmedia() {
- var pos = position();
- var m = match(/^@media *([^{]+)/);
- if (!m) return;
- var media = trim(m[1]);
- if (!open()) return error("@media missing '{'");
- var style = comments().concat(rules());
- if (!close()) return error("@media missing '}'");
- return pos({
- type: "media",
- media: media,
- rules: style,
- });
- }
- /**
- * Parse custom-media.
- */
- function atcustommedia() {
- var pos = position();
- var m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/);
- if (!m) return;
- return pos({
- type: "custom-media",
- name: trim(m[1]),
- media: trim(m[2]),
- });
- }
- /**
- * Parse paged media.
- */
- function atpage() {
- var pos = position();
- var m = match(/^@page */);
- if (!m) return;
- var sel = selector() || [];
- if (!open()) return error("@page missing '{'");
- var decls = comments();
- // declarations
- for (var decl; (decl = declaration()); ) {
- decls.push(decl);
- decls = decls.concat(comments());
- }
- if (!close()) return error("@page missing '}'");
- return pos({
- type: "page",
- selectors: sel,
- declarations: decls,
- });
- }
- /**
- * Parse document.
- */
- function atdocument() {
- var pos = position();
- var m = match(/^@([-\w]+)?document *([^{]+)/);
- if (!m) return;
- var vendor = trim(m[1]);
- var doc = trim(m[2]);
- if (!open()) return error("@document missing '{'");
- var style = comments().concat(rules());
- if (!close()) return error("@document missing '}'");
- return pos({
- type: "document",
- document: doc,
- vendor: vendor,
- rules: style,
- });
- }
- /**
- * Parse font-face.
- */
- function atfontface() {
- var pos = position();
- var m = match(/^@font-face\s*/);
- if (!m) return;
- if (!open()) return error("@font-face missing '{'");
- var decls = comments();
- // declarations
- for (var decl; (decl = declaration()); ) {
- decls.push(decl);
- decls = decls.concat(comments());
- }
- if (!close()) return error("@font-face missing '}'");
- return pos({
- type: "font-face",
- declarations: decls,
- });
- }
- /**
- * Parse import
- */
- var atimport = _compileAtrule("import");
- /**
- * Parse charset
- */
- var atcharset = _compileAtrule("charset");
- /**
- * Parse namespace
- */
- var atnamespace = _compileAtrule("namespace");
- /**
- * Parse non-block at-rules
- */
- function _compileAtrule(name) {
- var re = new RegExp("^@" + name + "\\s*([^;]+);");
- return function () {
- var pos = position();
- var m = match(re);
- if (!m) return;
- var ret = { type: name };
- ret[name] = m[1].trim();
- return pos(ret);
- };
- }
- /**
- * Parse at rule.
- */
- function atrule() {
- if (css[0] != "@") return;
- return (
- atkeyframes() ||
- atmedia() ||
- atcustommedia() ||
- atsupports() ||
- atimport() ||
- atcharset() ||
- atnamespace() ||
- atdocument() ||
- atpage() ||
- athost() ||
- atfontface()
- );
- }
- /**
- * Parse rule.
- */
- function rule() {
- var pos = position();
- var sel = selector();
- if (!sel) return error("selector missing");
- comments();
- return pos({
- type: "rule",
- selectors: sel,
- declarations: declarations(),
- });
- }
- return addParent(stylesheet());
- }
- /**
- * Trim `str`.
- */
- export function trim(str) {
- return str ? str.replace(/^\s+|\s+$/g, "") : "";
- }
- /**
- * Adds non-enumerable parent node reference to each node.
- */
- export function addParent(obj, parent) {
- var isNode = obj && typeof obj.type === "string";
- var childParent = isNode ? obj : parent;
- for (var k in obj) {
- var 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;
- }
- // Credits: https://github.com/reworkcss/css/blob/master/lib/stringify
- // Forge: derived the identity.js module only
- export class Compiler {
- constructor(options) {
- options ||= {};
- this.indentation = typeof options.indent === "string" ? options.indent : " ";
- }
- /**
- * Emit `str`
- */
- emit(str) {
- return str;
- }
- /**
- * Visit `node`.
- */
- visit(node) {
- return this[node.type](node);
- }
- /**
- * Map visit over array of `nodes`, optionally using a `delim`
- */
- mapVisit(nodes, delim) {
- var buf = "";
- delim = delim || "";
- for (var i = 0, length = nodes.length; i < length; i++) {
- buf += this.visit(nodes[i]);
- if (delim && i < length - 1) buf += this.emit(delim);
- }
- return buf;
- }
- /**
- * Compile `node`.
- */
- compile(node) {
- return this.stylesheet(node);
- }
- /**
- * Visit stylesheet node.
- */
- stylesheet(node) {
- return this.mapVisit(node.stylesheet.rules, "\n\n");
- }
- /**
- * Visit comment node.
- */
- comment(node) {
- return this.emit(this.indent() + "/*" + node.comment + "*/", node.position);
- }
- /**
- * Visit import node.
- */
- import(node) {
- return this.emit("@import " + node.import + ";", node.position);
- }
- /**
- * Visit media node.
- */
- media(node) {
- return (
- this.emit("@media " + node.media, node.position) +
- this.emit(" {\n" + this.indent(1)) +
- this.mapVisit(node.rules, "\n\n") +
- this.emit(this.indent(-1) + "\n}")
- );
- }
- /**
- * Visit document node.
- */
- document(node) {
- var doc = "@" + (node.vendor || "") + "document " + node.document;
- return (
- this.emit(doc, node.position) +
- this.emit(" " + " {\n" + this.indent(1)) +
- this.mapVisit(node.rules, "\n\n") +
- this.emit(this.indent(-1) + "\n}")
- );
- }
- /**
- * Visit charset node.
- */
- charset(node) {
- return this.emit("@charset " + node.charset + ";", node.position);
- }
- /**
- * Visit namespace node.
- */
- namespace(node) {
- return this.emit("@namespace " + node.namespace + ";", node.position);
- }
- /**
- * Visit supports node.
- */
- supports(node) {
- return (
- this.emit("@supports " + node.supports, node.position) +
- this.emit(" {\n" + this.indent(1)) +
- this.mapVisit(node.rules, "\n\n") +
- this.emit(this.indent(-1) + "\n}")
- );
- }
- /**
- * Visit keyframes node.
- */
- keyframes(node) {
- return (
- this.emit("@" + (node.vendor || "") + "keyframes " + node.name, node.position) +
- this.emit(" {\n" + this.indent(1)) +
- this.mapVisit(node.keyframes, "\n") +
- this.emit(this.indent(-1) + "}")
- );
- }
- /**
- * Visit keyframe node.
- */
- keyframe(node) {
- var decls = node.declarations;
- return (
- this.emit(this.indent()) +
- this.emit(node.values.join(", "), node.position) +
- this.emit(" {\n" + this.indent(1)) +
- this.mapVisit(decls, "\n") +
- this.emit(this.indent(-1) + "\n" + this.indent() + "}\n")
- );
- }
- /**
- * Visit page node.
- */
- page(node) {
- var sel = node.selectors.length ? node.selectors.join(", ") + " " : "";
- return (
- this.emit("@page " + sel, node.position) +
- this.emit("{\n") +
- this.emit(this.indent(1)) +
- this.mapVisit(node.declarations, "\n") +
- this.emit(this.indent(-1)) +
- this.emit("\n}")
- );
- }
- /**
- * Visit font-face node.
- */
- ["font-face"] = function (node) {
- return (
- this.emit("@font-face ", node.position) +
- this.emit("{\n") +
- this.emit(this.indent(1)) +
- this.mapVisit(node.declarations, "\n") +
- this.emit(this.indent(-1)) +
- this.emit("\n}")
- );
- };
- /**
- * Visit host node.
- */
- host(node) {
- return (
- this.emit("@host", node.position) +
- this.emit(" {\n" + this.indent(1)) +
- this.mapVisit(node.rules, "\n\n") +
- this.emit(this.indent(-1) + "\n}")
- );
- }
- /**
- * Visit custom-media node.
- */
- ["custom-media"] = function (node) {
- return this.emit("@custom-media " + node.name + " " + node.media + ";", node.position);
- };
- /**
- * Visit rule node.
- */
- rule(node) {
- var indent = this.indent();
- var decls = node.declarations;
- if (!decls.length) return "";
- return (
- this.emit(
- node.selectors
- .map(function (s) {
- return indent + s;
- })
- .join(",\n"),
- node.position
- ) +
- this.emit(" {\n") +
- this.emit(this.indent(1)) +
- this.mapVisit(decls, "\n") +
- this.emit(this.indent(-1)) +
- this.emit("\n" + this.indent() + "}")
- );
- }
- /**
- * Visit declaration node.
- */
- declaration(node) {
- return (
- this.emit(this.indent()) +
- this.emit(node.property + ": " + node.value, node.position) +
- this.emit(";")
- );
- }
- /**
- * Increase, decrease or return current indentation.
- */
- indent(level) {
- this.level = this.level || 1;
- if (null != level) {
- this.level += level;
- return "";
- }
- return Array(this.level).join(this.indentation);
- }
- }
- // Credits: https://github.com/reworkcss/css/tree/master/lib/stringify
- // Licensed under MIT
- // Forge: removed unused options
- /**
- * Stringfy the given AST `node`.
- *
- * @param {Object} node
- * @param {Object} [_options]
- * @return {String}
- * @api public
- */
- export function stringify(node, _options) {
- _options ||= {};
- var compiler = new Compiler(_options);
- var code = compiler.compile(node);
- return code;
- }
|