441 lines
14 KiB
JavaScript
441 lines
14 KiB
JavaScript
(function () {
|
|
|
|
'use strict';
|
|
|
|
function cssbeautify(style, opt) {
|
|
|
|
var options, index = 0, length = style.length, blocks, formatted = '',
|
|
ch, ch2, str, state, State, depth, quote, comment,
|
|
openbracesuffix = true,
|
|
autosemicolon = false,
|
|
trimRight;
|
|
|
|
options = arguments.length > 1 ? opt : {};
|
|
if (typeof options.indent === 'undefined') {
|
|
options.indent = ' ';
|
|
}
|
|
if (typeof options.openbrace === 'string') {
|
|
openbracesuffix = (options.openbrace === 'end-of-line');
|
|
}
|
|
if (typeof options.autosemicolon === 'boolean') {
|
|
autosemicolon = options.autosemicolon;
|
|
}
|
|
|
|
function isWhitespace(c) {
|
|
return (c === ' ') || (c === '\n') || (c === '\t') || (c === '\r') || (c === '\f');
|
|
}
|
|
|
|
function isQuote(c) {
|
|
return (c === '\'') || (c === '"');
|
|
}
|
|
|
|
// FIXME: handle Unicode characters
|
|
function isName(c) {
|
|
return (ch >= 'a' && ch <= 'z') ||
|
|
(ch >= 'A' && ch <= 'Z') ||
|
|
(ch >= '0' && ch <= '9') ||
|
|
'-_*.:#[]'.indexOf(c) >= 0;
|
|
}
|
|
|
|
function appendIndent() {
|
|
var i;
|
|
for (i = depth; i > 0; i -= 1) {
|
|
formatted += options.indent;
|
|
}
|
|
}
|
|
|
|
function openBlock() {
|
|
formatted = trimRight(formatted);
|
|
if (openbracesuffix) {
|
|
formatted += ' {';
|
|
} else {
|
|
formatted += '\n';
|
|
appendIndent();
|
|
formatted += '{';
|
|
}
|
|
if (ch2 !== '\n') {
|
|
formatted += '\n';
|
|
}
|
|
depth += 1;
|
|
}
|
|
|
|
function closeBlock() {
|
|
var last;
|
|
depth -= 1;
|
|
formatted = trimRight(formatted);
|
|
|
|
if (formatted.length > 0 && autosemicolon) {
|
|
last = formatted.charAt(formatted.length - 1);
|
|
if (last !== ';' && last !== '{') {
|
|
formatted += ';';
|
|
}
|
|
}
|
|
|
|
formatted += '\n';
|
|
appendIndent();
|
|
formatted += '}';
|
|
blocks.push(formatted);
|
|
formatted = '';
|
|
}
|
|
|
|
if (String.prototype.trimRight) {
|
|
trimRight = function (s) {
|
|
return s.trimRight();
|
|
};
|
|
} else {
|
|
// old Internet Explorer
|
|
trimRight = function (s) {
|
|
return s.replace(/\s+$/, '');
|
|
};
|
|
}
|
|
|
|
State = {
|
|
Start: 0,
|
|
AtRule: 1,
|
|
Block: 2,
|
|
Selector: 3,
|
|
Ruleset: 4,
|
|
Property: 5,
|
|
Separator: 6,
|
|
Expression: 7,
|
|
URL: 8
|
|
};
|
|
|
|
depth = 0;
|
|
state = State.Start;
|
|
comment = false;
|
|
blocks = [];
|
|
|
|
// We want to deal with LF (\n) only
|
|
style = style.replace(/\r\n/g, '\n');
|
|
|
|
while (index < length) {
|
|
ch = style.charAt(index);
|
|
ch2 = style.charAt(index + 1);
|
|
index += 1;
|
|
|
|
// Inside a string literal?
|
|
if (isQuote(quote)) {
|
|
formatted += ch;
|
|
if (ch === quote) {
|
|
quote = null;
|
|
}
|
|
if (ch === '\\' && ch2 === quote) {
|
|
// Don't treat escaped character as the closing quote
|
|
formatted += ch2;
|
|
index += 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Starting a string literal?
|
|
if (isQuote(ch)) {
|
|
formatted += ch;
|
|
quote = ch;
|
|
continue;
|
|
}
|
|
|
|
// Comment
|
|
if (comment) {
|
|
formatted += ch;
|
|
if (ch === '*' && ch2 === '/') {
|
|
comment = false;
|
|
formatted += ch2;
|
|
index += 1;
|
|
}
|
|
continue;
|
|
}
|
|
if (ch === '/' && ch2 === '*') {
|
|
comment = true;
|
|
formatted += ch;
|
|
formatted += ch2;
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (state === State.Start) {
|
|
|
|
if (blocks.length === 0) {
|
|
if (isWhitespace(ch) && formatted.length === 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Copy white spaces and control characters
|
|
if (ch <= ' ' || ch.charCodeAt(0) >= 128) {
|
|
state = State.Start;
|
|
formatted += ch;
|
|
continue;
|
|
}
|
|
|
|
// Selector or at-rule
|
|
if (isName(ch) || (ch === '@')) {
|
|
|
|
// Clear trailing whitespaces and linefeeds.
|
|
str = trimRight(formatted);
|
|
|
|
if (str.length === 0) {
|
|
// If we have empty string after removing all the trailing
|
|
// spaces, that means we are right after a block.
|
|
// Ensure a blank line as the separator.
|
|
if (blocks.length > 0) {
|
|
formatted = '\n\n';
|
|
}
|
|
} else {
|
|
// After finishing a ruleset or directive statement,
|
|
// there should be one blank line.
|
|
if (str.charAt(str.length - 1) === '}' ||
|
|
str.charAt(str.length - 1) === ';') {
|
|
|
|
formatted = str + '\n\n';
|
|
} else {
|
|
// After block comment, keep all the linefeeds but
|
|
// start from the first column (remove whitespaces prefix).
|
|
while (true) {
|
|
ch2 = formatted.charAt(formatted.length - 1);
|
|
if (ch2 !== ' ' && ch2.charCodeAt(0) !== 9) {
|
|
break;
|
|
}
|
|
formatted = formatted.substr(0, formatted.length - 1);
|
|
}
|
|
}
|
|
}
|
|
formatted += ch;
|
|
state = (ch === '@') ? State.AtRule : State.Selector;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (state === State.AtRule) {
|
|
|
|
// ';' terminates a statement.
|
|
if (ch === ';') {
|
|
formatted += ch;
|
|
state = State.Start;
|
|
continue;
|
|
}
|
|
|
|
// '{' starts a block
|
|
if (ch === '{') {
|
|
str = trimRight(formatted);
|
|
openBlock();
|
|
state = (str === '@font-face') ? State.Ruleset : State.Block;
|
|
continue;
|
|
}
|
|
|
|
formatted += ch;
|
|
continue;
|
|
}
|
|
|
|
if (state === State.Block) {
|
|
|
|
// Selector
|
|
if (isName(ch)) {
|
|
|
|
// Clear trailing whitespaces and linefeeds.
|
|
str = trimRight(formatted);
|
|
|
|
if (str.length === 0) {
|
|
// If we have empty string after removing all the trailing
|
|
// spaces, that means we are right after a block.
|
|
// Ensure a blank line as the separator.
|
|
if (blocks.length > 0) {
|
|
formatted = '\n\n';
|
|
}
|
|
} else {
|
|
// Insert blank line if necessary.
|
|
if (str.charAt(str.length - 1) === '}') {
|
|
formatted = str + '\n\n';
|
|
} else {
|
|
// After block comment, keep all the linefeeds but
|
|
// start from the first column (remove whitespaces prefix).
|
|
while (true) {
|
|
ch2 = formatted.charAt(formatted.length - 1);
|
|
if (ch2 !== ' ' && ch2.charCodeAt(0) !== 9) {
|
|
break;
|
|
}
|
|
formatted = formatted.substr(0, formatted.length - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
appendIndent();
|
|
formatted += ch;
|
|
state = State.Selector;
|
|
continue;
|
|
}
|
|
|
|
// '}' resets the state.
|
|
if (ch === '}') {
|
|
closeBlock();
|
|
state = State.Start;
|
|
continue;
|
|
}
|
|
|
|
formatted += ch;
|
|
continue;
|
|
}
|
|
|
|
if (state === State.Selector) {
|
|
|
|
// '{' starts the ruleset.
|
|
if (ch === '{') {
|
|
openBlock();
|
|
state = State.Ruleset;
|
|
continue;
|
|
}
|
|
|
|
// '}' resets the state.
|
|
if (ch === '}') {
|
|
closeBlock();
|
|
state = State.Start;
|
|
continue;
|
|
}
|
|
|
|
formatted += ch;
|
|
continue;
|
|
}
|
|
|
|
if (state === State.Ruleset) {
|
|
|
|
// '}' finishes the ruleset.
|
|
if (ch === '}') {
|
|
closeBlock();
|
|
state = State.Start;
|
|
if (depth > 0) {
|
|
state = State.Block;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Make sure there is no blank line or trailing spaces inbetween
|
|
if (ch === '\n') {
|
|
formatted = trimRight(formatted);
|
|
formatted += '\n';
|
|
continue;
|
|
}
|
|
|
|
// property name
|
|
if (!isWhitespace(ch)) {
|
|
formatted = trimRight(formatted);
|
|
formatted += '\n';
|
|
appendIndent();
|
|
formatted += ch;
|
|
state = State.Property;
|
|
continue;
|
|
}
|
|
formatted += ch;
|
|
continue;
|
|
}
|
|
|
|
if (state === State.Property) {
|
|
|
|
// ':' concludes the property.
|
|
if (ch === ':') {
|
|
formatted = trimRight(formatted);
|
|
formatted += ': ';
|
|
state = State.Expression;
|
|
if (isWhitespace(ch2)) {
|
|
state = State.Separator;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// '}' finishes the ruleset.
|
|
if (ch === '}') {
|
|
closeBlock();
|
|
state = State.Start;
|
|
if (depth > 0) {
|
|
state = State.Block;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
formatted += ch;
|
|
continue;
|
|
}
|
|
|
|
if (state === State.Separator) {
|
|
|
|
// Non-whitespace starts the expression.
|
|
if (!isWhitespace(ch)) {
|
|
formatted += ch;
|
|
state = State.Expression;
|
|
continue;
|
|
}
|
|
|
|
// Anticipate string literal.
|
|
if (isQuote(ch2)) {
|
|
state = State.Expression;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (state === State.Expression) {
|
|
|
|
// '}' finishes the ruleset.
|
|
if (ch === '}') {
|
|
closeBlock();
|
|
state = State.Start;
|
|
if (depth > 0) {
|
|
state = State.Block;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// ';' completes the declaration.
|
|
if (ch === ';') {
|
|
formatted = trimRight(formatted);
|
|
formatted += ';\n';
|
|
state = State.Ruleset;
|
|
continue;
|
|
}
|
|
|
|
formatted += ch;
|
|
|
|
if (ch === '(') {
|
|
if (formatted.charAt(formatted.length - 2) === 'l' &&
|
|
formatted.charAt(formatted.length - 3) === 'r' &&
|
|
formatted.charAt(formatted.length - 4) === 'u') {
|
|
|
|
// URL starts with '(' and closes with ')'.
|
|
state = State.URL;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (state === State.URL) {
|
|
|
|
|
|
// ')' finishes the URL (only if it is not escaped).
|
|
if (ch === ')' && formatted.charAt(formatted.length - 1 !== '\\')) {
|
|
formatted += ch;
|
|
state = State.Expression;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// The default action is to copy the character (to prevent
|
|
// infinite loop).
|
|
formatted += ch;
|
|
}
|
|
|
|
formatted = blocks.join('') + formatted;
|
|
|
|
return formatted;
|
|
}
|
|
|
|
if (typeof exports !== 'undefined') {
|
|
// Node.js module.
|
|
module.exports = exports = cssbeautify;
|
|
} else if (typeof window === 'object') {
|
|
// Browser loading.
|
|
window.cssbeautify = cssbeautify;
|
|
}
|
|
|
|
}());
|