001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.mappaint.mapcss;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.awt.Color;
007 import java.io.ByteArrayInputStream;
008 import java.io.IOException;
009 import java.io.InputStream;
010 import java.util.ArrayList;
011 import java.util.List;
012 import java.util.Map.Entry;
013
014 import org.openstreetmap.josm.data.osm.Node;
015 import org.openstreetmap.josm.data.osm.OsmPrimitive;
016 import org.openstreetmap.josm.gui.mappaint.Cascade;
017 import org.openstreetmap.josm.gui.mappaint.Environment;
018 import org.openstreetmap.josm.gui.mappaint.MultiCascade;
019 import org.openstreetmap.josm.gui.mappaint.Range;
020 import org.openstreetmap.josm.gui.mappaint.StyleSource;
021 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
022 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
023 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
024 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError;
025 import org.openstreetmap.josm.gui.preferences.SourceEntry;
026 import org.openstreetmap.josm.io.MirroredInputStream;
027 import org.openstreetmap.josm.tools.CheckParameterUtil;
028 import org.openstreetmap.josm.tools.LanguageInfo;
029 import org.openstreetmap.josm.tools.Utils;
030
031 public class MapCSSStyleSource extends StyleSource {
032 final public List<MapCSSRule> rules;
033 private Color backgroundColorOverride;
034 private String css = null;
035
036 public MapCSSStyleSource(String url, String name, String shortdescription) {
037 super(url, name, shortdescription);
038 rules = new ArrayList<MapCSSRule>();
039 }
040
041 public MapCSSStyleSource(SourceEntry entry) {
042 super(entry);
043 rules = new ArrayList<MapCSSRule>();
044 }
045
046 /**
047 * <p>Creates a new style source from the MapCSS styles supplied in
048 * {@code css}</p>
049 *
050 * @param css the MapCSS style declaration. Must not be null.
051 * @throws IllegalArgumentException thrown if {@code css} is null
052 */
053 public MapCSSStyleSource(String css) throws IllegalArgumentException{
054 super(null, null, null);
055 CheckParameterUtil.ensureParameterNotNull(css);
056 this.css = css;
057 rules = new ArrayList<MapCSSRule>();
058 }
059
060 @Override
061 public void loadStyleSource() {
062 init();
063 rules.clear();
064 try {
065 MapCSSParser parser = new MapCSSParser(getSourceInputStream(), "UTF-8");
066 parser.sheet(this);
067 loadMeta();
068 loadCanvas();
069 } catch(IOException e) {
070 System.err.println(tr("Warning: failed to load Mappaint styles from ''{0}''. Exception was: {1}", url, e.toString()));
071 e.printStackTrace();
072 logError(e);
073 } catch (TokenMgrError e) {
074 System.err.println(tr("Warning: failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
075 e.printStackTrace();
076 logError(e);
077 } catch (ParseException e) {
078 System.err.println(tr("Warning: failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
079 e.printStackTrace();
080 logError(new ParseException(e.getMessage())); // allow e to be garbage collected, it links to the entire token stream
081 }
082 }
083
084 @Override
085 public InputStream getSourceInputStream() throws IOException {
086 if (css != null)
087 return new ByteArrayInputStream(css.getBytes("UTF-8"));
088
089 MirroredInputStream in = new MirroredInputStream(url);
090 InputStream zip = in.getZipEntry("mapcss", "style");
091 if (zip != null) {
092 zipIcons = in.getFile();
093 return zip;
094 } else {
095 zipIcons = null;
096 return in;
097 }
098 }
099
100 /**
101 * load meta info from a selector "meta"
102 */
103 private void loadMeta() {
104 Cascade c = constructSpecial("meta");
105 String pTitle = c.get("title", null, String.class);
106 if (title == null) {
107 title = pTitle;
108 }
109 String pIcon = c.get("icon", null, String.class);
110 if (icon == null) {
111 icon = pIcon;
112 }
113 }
114
115 private void loadCanvas() {
116 Cascade c = constructSpecial("canvas");
117 backgroundColorOverride = c.get("background-color", null, Color.class);
118 }
119
120 private Cascade constructSpecial(String type) {
121
122 MultiCascade mc = new MultiCascade();
123 Node n = new Node();
124 String code = LanguageInfo.getJOSMLocaleCode();
125 n.put("lang", code);
126 // create a fake environment to read the meta data block
127 Environment env = new Environment(n, mc, "default", this);
128
129 NEXT_RULE:
130 for (MapCSSRule r : rules) {
131 for (Selector s : r.selectors) {
132 if ((s instanceof GeneralSelector)) {
133 GeneralSelector gs = (GeneralSelector) s;
134 if (gs.getBase().equals(type)) {
135 if (!gs.matchesConditions(env)) {
136 continue NEXT_RULE;
137 }
138 r.execute(env);
139 }
140 }
141 }
142 }
143 return mc.getCascade("default");
144 }
145
146 @Override
147 public Color getBackgroundColorOverride() {
148 return backgroundColorOverride;
149 }
150
151 @Override
152 public void apply(MultiCascade mc, OsmPrimitive osm, double scale, OsmPrimitive multipolyOuterWay, boolean pretendWayIsClosed) {
153 Environment env = new Environment(osm, mc, null, this);
154 for (MapCSSRule r : rules) {
155 for (Selector s : r.selectors) {
156 env.clearSelectorMatchingInformation();
157 if (s.matches(env)) { // as side effect env.parent will be set (if s is a child selector)
158 if (s.getRange().contains(scale)) {
159 mc.range = Range.cut(mc.range, s.getRange());
160 } else {
161 mc.range = mc.range.reduceAround(scale, s.getRange());
162 continue;
163 }
164
165 String sub = s.getSubpart();
166 if (sub == null) {
167 sub = "default";
168 }
169
170 if (sub.equals("*")) {
171 for (Entry<String, Cascade> entry : mc.getLayers()) {
172 env.layer = entry.getKey();
173 if (Utils.equal(env.layer, "*")) {
174 continue;
175 }
176 r.execute(env);
177 }
178 }
179 env.layer = sub;
180 r.execute(env);
181 }
182 }
183 }
184 }
185
186 @Override
187 public String toString() {
188 return Utils.join("\n", rules);
189 }
190 }