Bug 964255 - Introduces a css declarations parser in the rule-view and fixes background-url edition; r=bgrins
This commit is contained in:
153
browser/devtools/styleinspector/css-parsing-utils.js
Normal file
153
browser/devtools/styleinspector/css-parsing-utils.js
Normal file
@@ -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;
|
||||
Reference in New Issue
Block a user