Java tutorial
/* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.dennishoersch.web.css.parser; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; /** * Parses a given stylesheet into a set of {@link Rule}s. Rules might contain styles or sub rules (for example @media queries and contained rules). * <p>The stylesheets are normalized such that rules with the same selector are combined into one rule and rules with the same styles are combined to one block.</p> * <p>Properties with the same name override in order they appear in the stylesheet. Except if the values contain vendor-prefixes.</p> * * <p><i>The stylesheet must not contain @import tags and has to be strict, which means styles in a rule must be separated by semicolon.</i></p> * * @author hoersch */ public class Parser { private static final Splitter _STYLE_SPLITTER = Splitter.on(";").omitEmptyStrings().trimResults(); private static final Splitter _SELECTOR_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults(); public static Stylesheet parse(String stylesheet) { if (stylesheet == null) { throw new NullPointerException("stylesheet"); } String stylesheet_ = Util.stripUnnecessary(stylesheet); if (stylesheet_.contains("@import")) { throw new IllegalArgumentException( "Stylesheet must not contain any @import tag, but is '" + stylesheet + "'!"); } List<Rule> rules = parseIntern(stylesheet_); return new Stylesheet(rules); } private static List<Rule> parseIntern(String stylesheet) { List<Rule> result = new ArrayList<>(); ParseResult parseResult = null; for (int i = 0;; i = parseResult.getNext()) { parseResult = parseNext(stylesheet, i); if (parseResult == null) { break; } result.addAll(parseResult.getRules()); } result = merge(result); result = mergeByContent(result); return result; } public static ParseResult parseNext(String stylesheet, int i) { int next = -1; if (i < stylesheet.length()) { next = stylesheet.indexOf('{', i); if (next < 0) { System.err.println("keine klammer auf mehr"); return null; } String selector = stylesheet.substring(i, next); int k = next; for (int count = 0; k < stylesheet.length(); k++) { char c = stylesheet.charAt(k); if (c == '{') { count++; } else if (c == '}') { count--; } if (count == 0) { break; } } String ruleset = stylesheet.substring(next + 1, k); RuleParseResult ruleParseResult = parseRule(ruleset); List<Rule> rules = new ArrayList<>(); for (String selector_ : _SELECTOR_SPLITTER.split(selector)) { rules.add(new Rule(selector_, ruleParseResult.getStyles(), ruleParseResult.getSubRules())); } return new ParseResult(rules, k + 1); } return null; } private static RuleParseResult parseRule(String ruleset) { List<Rule> subRules = null; List<Style> styles = null; if (ruleset.contains("{")) { subRules = parseIntern(ruleset); } else { styles = parseStyles(ruleset); } return new RuleParseResult(styles, subRules); } private static class RuleParseResult { private final List<Style> _styles; private final List<Rule> _subRules; RuleParseResult(List<Style> styles, List<Rule> subRules) { _styles = styles; _subRules = subRules; } /** * @return the styles */ public List<Style> getStyles() { return _styles; } /** * @return the subRules */ public List<Rule> getSubRules() { return _subRules; } } private static List<Style> parseStyles(String styles) { Multimap<String, Style> reduced = LinkedHashMultimap.create(); for (String style_ : _STYLE_SPLITTER.split(styles)) { Style style = new Style(style_); reduced.put(style.getName(), style); } // Wenn keiner der Werte zu einem Style ein Vendor-Prefix enthlt, dann // kann der letzte alle anderen berschreiben List<Style> result = Lists.newArrayList(); for (Map.Entry<String, Collection<Style>> entry : reduced.asMap().entrySet()) { Collection<Style> values = entry.getValue(); if (Iterables.any(values, HasVendorPrefixValue.INSTANCE)) { result.addAll(values); } else { result.add(Iterables.getLast(values)); } } return result; } private enum HasVendorPrefixValue implements Predicate<Style> { INSTANCE; //@formatter:off private static final List<String> _VENDOR_PREFIXES = new ImmutableList.Builder<String>().add("-moz") .add("-webkit").add("-ms").add("-o").add("-wap").add("-xv").build(); //@formatter:on @Override public boolean apply(Style input) { return Iterables.any(_VENDOR_PREFIXES, startsWith(input.getValue())); } private static Predicate<String> startsWith(final String string) { return new Predicate<String>() { @Override public boolean apply(String input) { return string.startsWith(input); } }; } } private static class ParseResult { private final List<Rule> _rules; private final int _next; /** * @param rules * @param next */ public ParseResult(List<Rule> rules, int next) { this._rules = rules; this._next = next; } /** * @return the rules */ public List<Rule> getRules() { return _rules; } /** * @return the next */ public int getNext() { return _next; } } private static List<Rule> merge(List<Rule> rules) { List<Rule> result = Lists.newArrayList(); Map<String, StringBuilder> mergedRules = Maps.newLinkedHashMap(); for (Rule rule : rules) { mergeRules(rule, mergedRules); } for (Map.Entry<String, StringBuilder> entry : mergedRules.entrySet()) { String ruleset = entry.getValue().toString(); RuleParseResult ruleParseResult = parseRule(ruleset); result.add(new Rule(entry.getKey(), ruleParseResult.getStyles(), ruleParseResult.getSubRules())); } return result; } private static void mergeRules(Rule rule, Map<String, StringBuilder> mergedRules) { StringBuilder styles = mergedRules.get(rule.getSelector()); if (styles == null) { styles = new StringBuilder(); mergedRules.put(rule.getSelector(), styles); } // erstmal nur alles anhngen // berschriebene werden durch Rule automatisch beachtet styles.append(rule.getContent()); } private static List<Rule> mergeByContent(List<Rule> rules) { List<Rule> result = Lists.newArrayList(); Map<String, StringBuilder> mergedRules = Maps.newLinkedHashMap(); for (Rule rule : rules) { String content = rule.getContent(); StringBuilder selectors = mergedRules.get(content); if (selectors == null) { selectors = new StringBuilder(rule.getSelector()); mergedRules.put(content, selectors); } else { selectors.append(",").append(rule.getSelector()); } } for (Map.Entry<String, StringBuilder> entry : mergedRules.entrySet()) { String ruleset = entry.getKey(); RuleParseResult ruleParseResult = parseRule(ruleset); result.add(new Rule(entry.getValue().toString(), ruleParseResult.getStyles(), ruleParseResult.getSubRules())); } return result; } }