diff -r 000000000000 -r 6474c204b198 browser/devtools/styleinspector/css-parsing-utils.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/devtools/styleinspector/css-parsing-utils.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,153 @@ +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const cssTokenizer = require("devtools/sourceeditor/css-tokenizer"); + +/** + * Returns the string enclosed in quotes + */ +function quoteString(string) { + let hasDoubleQuotes = string.contains('"'); + let hasSingleQuotes = string.contains("'"); + + if (hasDoubleQuotes && !hasSingleQuotes) { + // In this case, no escaping required, just enclose in single-quotes + return "'" + string + "'"; + } + + // In all other cases, enclose in double-quotes, and escape any double-quote + // that may be in the string + return '"' + string.replace(/"/g, '\"') + '"'; +} + +/** + * Returns an array of CSS declarations given an string. + * For example, parseDeclarations("width: 1px; height: 1px") would return + * [{name:"width", value: "1px"}, {name: "height", "value": "1px"}] + * + * The input string is assumed to only contain declarations so { and } characters + * will be treated as part of either the property or value, depending where it's + * found. + * + * @param {string} inputString + * An input string of CSS + * @return {Array} an array of objects with the following signature: + * [{"name": string, "value": string, "priority": string}, ...] + */ +function parseDeclarations(inputString) { + let tokens = cssTokenizer(inputString); + + let declarations = [{name: "", value: "", priority: ""}]; + + let current = "", hasBang = false, lastProp; + for (let token of tokens) { + lastProp = declarations[declarations.length - 1]; + + if (token.tokenType === ":") { + if (!lastProp.name) { + // Set the current declaration name if there's no name yet + lastProp.name = current.trim(); + current = ""; + hasBang = false; + } else { + // Otherwise, just append ':' to the current value (declaration value + // with colons) + current += ":"; + } + } else if (token.tokenType === ";") { + lastProp.value = current.trim(); + current = ""; + hasBang = false; + declarations.push({name: "", value: "", priority: ""}); + } else { + switch(token.tokenType) { + case "IDENT": + if (token.value === "important" && hasBang) { + lastProp.priority = "important"; + hasBang = false; + } else { + if (hasBang) { + current += "!"; + } + current += token.value; + } + break; + case "WHITESPACE": + current += " "; + break; + case "DIMENSION": + current += token.repr; + break; + case "HASH": + current += "#" + token.value; + break; + case "URL": + current += "url(" + quoteString(token.value) + ")"; + break; + case "FUNCTION": + current += token.value + "("; + break; + case ")": + current += token.tokenType; + break; + case "EOF": + break; + case "DELIM": + if (token.value === "!") { + hasBang = true; + } else { + current += token.value; + } + break; + case "STRING": + current += quoteString(token.value); + break; + case "{": + case "}": + current += token.tokenType; + break; + default: + current += token.value; + break; + } + } + } + + // Handle whatever trailing properties or values might still be there + if (current) { + if (!lastProp.name) { + // Trailing property found, e.g. p1:v1;p2:v2;p3 + lastProp.name = current.trim(); + } else { + // Trailing value found, i.e. value without an ending ; + lastProp.value += current.trim(); + } + } + + // Remove declarations that have neither a name nor a value + declarations = declarations.filter(prop => prop.name || prop.value); + + return declarations; +}; +exports.parseDeclarations = parseDeclarations; + +/** + * Expects a single CSS value to be passed as the input and parses the value + * and priority. + * + * @param {string} value The value from the text editor. + * @return {object} an object with 'value' and 'priority' properties. + */ +function parseSingleValue(value) { + let declaration = parseDeclarations("a: " + value + ";")[0]; + return { + value: declaration ? declaration.value : "", + priority: declaration ? declaration.priority : "" + }; +}; +exports.parseSingleValue = parseSingleValue;