/*
* Copyright 2014 Matthias Braun
*
* This library is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.bges;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.css.sac.InputSource;
import org.w3c.dom.css.CSSRule;
import org.w3c.dom.css.CSSRuleList;
import org.w3c.dom.css.CSSStyleDeclaration;
import org.w3c.dom.css.CSSStyleRule;
import org.w3c.dom.css.CSSStyleSheet;
import org.w3c.dom.css.CSSValue;
import com.google.common.base.Optional;
import com.steadystate.css.dom.CSSStyleSheetImpl;
import com.steadystate.css.parser.CSSOMParser;
/**
* Provides constants and functions for dealing with Cascading Style Sheets.
*
* @author Matthias Braun
* @see <a href="http://cssparser.sourceforge.net/apidocs/index.html">CSS
* parser</a>
*
*
*/
public final class CssUtil {
/**
* Selectors and their attributes found in a typical CSS file.
*/
public static final String FONT_SIZE = "font-size",
FONT_FAMILY = "font-family", STROKE = "stroke",
STROKE_OPACITY = "stroke-opacity", STROKE_WIDTH = "stroke-width",
FILL_OPACITY = "fill-opacity", FILL = "fill",
BACKGROUND_COLOR = "background-color", WIDTH = "width",
HEIGHT = "height", BODY = "body";
private static final Logger LOG = LoggerFactory.getLogger(CssUtil.class);
/** This is a utility class not meant to be instantiated by others. */
private CssUtil() {
}
/**
* Get the value of a property from a CSS file.
*
* @param cssFile
* The Cascading Style Sheet file.
* @param selector
* The selector to which the property belongs.
* @param property
* The property whose value we want to know.
* @return The property's value wrapped in an {@code Optional} in case the
* property doesn't exist or the CSS file could not be read.
*/
public static Optional<String> getValue(final File cssFile,
final String selector, final String property) {
String value = null;
final List<CSSStyleRule> rules = getRules(getStyleSheet(cssFile));
final Optional<CSSStyleDeclaration> styleMaybe = getStyle(rules,
selector);
if (styleMaybe.isPresent()) {
final CSSStyleDeclaration style = styleMaybe.get();
final CSSValue val = style.getPropertyCSSValue(property);
if (val == null) {
// The property might be a CSS shorthand property
final String strVal = style.getPropertyValue(property);
if (!strVal.isEmpty()) {
value = strVal;
}
} else {
value = val.getCssText();
}
}
return Optional.fromNullable(value);
}
/**
* Set a new value of property in a CSS file.
* <p>
* This either overwrites the value of an existing property or it creates a
* new property with the specified value.
*
* @param cssFile
* The CSS file we want to change.
* @param selector
* The selector to which the property belongs.
* @param property
* The property whose value we want to change.
* @param newValue
* This is the value the property has after we changed it.
* @return Whether the setting of the value was successful.
*/
public static boolean setValue(final File cssFile, final String selector,
final String property, final String newValue) {
return setValue(cssFile, selector, property, newValue, "");
}
/**
* Set a new value for a property in a CSS file.
* <p>
* This either overwrites the value of an existing property or it creates a
* new property with the specified value.
*
* @param cssFile
* The CSS file we want to change.
* @param selector
* The selector to which the property belongs.
* @param property
* The property whose value we want to change.
* @param newValue
* This is the value the property has after we changed it.
* @param priority
* The new priority the property should get (e.g., 'important').
* The empty string means that the priority isn't set.
* @return Whether the setting of the value was successful.
*/
public static boolean setValue(final File cssFile, final String selector,
final String property, final String newValue, final String priority) {
boolean success = false;
final CSSStyleSheet styleSheet = getStyleSheet(cssFile);
final List<CSSStyleRule> rules = getRules(styleSheet);
final Optional<CSSStyleDeclaration> styleMaybe = getStyle(rules,
selector);
if (styleMaybe.isPresent()) {
final CSSStyleDeclaration style = styleMaybe.get();
style.setProperty(property, newValue, priority);
success = IOUtil.write(styleSheet.toString(), cssFile);
}
return success;
}
/**
* Get all valid {@code CSSStyleRule}s from a {@code CSSStyleSheet}.
* <p>
* If a rule is not valid CSS the parser will continue with the next rule.
*
* @param styleSheet
* The {@code CSSStyleSheet} containing the rules.
* @return The CSS rules.
*/
private static List<CSSStyleRule> getRules(final CSSStyleSheet styleSheet) {
final List<CSSStyleRule> rules = new ArrayList<>();
final CSSRuleList ruleList = styleSheet.getCssRules();
for (int i = 0; i < ruleList.getLength(); i++) {
final CSSRule rule = ruleList.item(i);
if (rule instanceof CSSStyleRule) {
final CSSStyleRule styleRule = (CSSStyleRule) rule;
rules.add(styleRule);
}
}
return rules;
}
/**
* Find the {@code CSSStyleDeclaration} belonging to a selector among a list
* of {@code CSSStyleRule}s.
*
* @param rules
* The {@code CSSStyleRule}s that may contain the selector we are
* looking for.
* @param selector
* The selector that defines in what rule we are interested.
* @return The style declaration of the rule wrapped in an {@code Optional}
* in case the rule wasn't found.
*/
private static Optional<CSSStyleDeclaration> getStyle(
final List<CSSStyleRule> rules, final String selector) {
CSSStyleDeclaration styleDeclaration = null;
for (final CSSStyleRule rule : rules) {
final String currSelector = rule.getSelectorText();
if (currSelector.equals(selector)) {
styleDeclaration = rule.getStyle();
break;
}
}
return Optional.fromNullable(styleDeclaration);
}
/**
* Turn a {@code File} into an {@code CSSStyleSheet}.
*
* @param cssFile
* The file containing the Cascading Style Sheet.
* @return The parsed {@code CSSStyleSheet} or an initialized but empty
* {@code CSSStyleSheetImpl} if the parsing was unsuccessful.
*/
private static CSSStyleSheet getStyleSheet(final File cssFile) {
CSSStyleSheet sheet = null;
final InputSource source = new InputSource(cssFile.toURI().toString());
try {
sheet = new CSSOMParser().parseStyleSheet(source, null, null);
} catch (final IOException e) {
LOG.error("IOException while reading {}", cssFile, e);
}
if (sheet == null) {
sheet = new CSSStyleSheetImpl();
}
return sheet;
}
}