source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java@ 7074

Last change on this file since 7074 was 7074, checked in by bastiK, 10 years ago

fixed #9989 - mapcss canvas{default-points: false; default-lines: false;} broken

  • Property svn:eol-style set to native
File size: 11.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.io.ByteArrayInputStream;
8import java.io.File;
9import java.io.IOException;
10import java.io.InputStream;
11import java.util.ArrayList;
12import java.util.List;
13import java.util.Map.Entry;
14import java.util.zip.ZipEntry;
15import java.util.zip.ZipFile;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.data.Version;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.Way;
23import org.openstreetmap.josm.gui.mappaint.Cascade;
24import org.openstreetmap.josm.gui.mappaint.Environment;
25import org.openstreetmap.josm.gui.mappaint.MultiCascade;
26import org.openstreetmap.josm.gui.mappaint.Range;
27import org.openstreetmap.josm.gui.mappaint.StyleSource;
28import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
29import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
30import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
31import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
32import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError;
33import org.openstreetmap.josm.gui.preferences.SourceEntry;
34import org.openstreetmap.josm.io.MirroredInputStream;
35import org.openstreetmap.josm.tools.CheckParameterUtil;
36import org.openstreetmap.josm.tools.LanguageInfo;
37import org.openstreetmap.josm.tools.Utils;
38
39public class MapCSSStyleSource extends StyleSource {
40
41 /**
42 * The accepted MIME types sent in the HTTP Accept header.
43 * @since 6867
44 */
45 public static final String MAPCSS_STYLE_MIME_TYPES = "text/x-mapcss, text/mapcss, text/css; q=0.9, text/plain; q=0.8, application/zip, application/octet-stream; q=0.5";
46
47 // all rules
48 public final List<MapCSSRule> rules = new ArrayList<>();
49 // rules filtered by primitive type
50 public final List<MapCSSRule> nodeRules = new ArrayList<>();
51 public final List<MapCSSRule> wayRules = new ArrayList<>();
52 public final List<MapCSSRule> relationRules = new ArrayList<>();
53 public final List<MapCSSRule> multipolygonRules = new ArrayList<>();
54 public final List<MapCSSRule> canvasRules = new ArrayList<>();
55
56 private Color backgroundColorOverride;
57 private String css = null;
58 private ZipFile zipFile;
59
60 public MapCSSStyleSource(String url, String name, String shortdescription) {
61 super(url, name, shortdescription);
62 }
63
64 public MapCSSStyleSource(SourceEntry entry) {
65 super(entry);
66 }
67
68 /**
69 * <p>Creates a new style source from the MapCSS styles supplied in
70 * {@code css}</p>
71 *
72 * @param css the MapCSS style declaration. Must not be null.
73 * @throws IllegalArgumentException thrown if {@code css} is null
74 */
75 public MapCSSStyleSource(String css) throws IllegalArgumentException{
76 super(null, null, null);
77 CheckParameterUtil.ensureParameterNotNull(css);
78 this.css = css;
79 }
80
81 @Override
82 public void loadStyleSource() {
83 init();
84 rules.clear();
85 nodeRules.clear();
86 wayRules.clear();
87 relationRules.clear();
88 multipolygonRules.clear();
89 canvasRules.clear();
90 try (InputStream in = getSourceInputStream()) {
91 try {
92 // evaluate @media { ... } blocks
93 MapCSSParser preprocessor = new MapCSSParser(in, "UTF-8", MapCSSParser.LexicalState.PREPROCESSOR);
94 String mapcss = preprocessor.pp_root(this);
95
96 // do the actual mapcss parsing
97 InputStream in2 = new ByteArrayInputStream(mapcss.getBytes(Utils.UTF_8));
98 MapCSSParser parser = new MapCSSParser(in2, "UTF-8", MapCSSParser.LexicalState.DEFAULT);
99 parser.sheet(this);
100
101 loadMeta();
102 loadCanvas();
103 } finally {
104 closeSourceInputStream(in);
105 }
106 } catch (IOException e) {
107 Main.warn(tr("Failed to load Mappaint styles from ''{0}''. Exception was: {1}", url, e.toString()));
108 Main.error(e);
109 logError(e);
110 } catch (TokenMgrError e) {
111 Main.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
112 Main.error(e);
113 logError(e);
114 } catch (ParseException e) {
115 Main.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
116 Main.error(e);
117 logError(new ParseException(e.getMessage())); // allow e to be garbage collected, it links to the entire token stream
118 }
119 // optimization: filter rules for different primitive types
120 for (MapCSSRule r: rules) {
121 // find the rightmost selector, this must be a GeneralSelector
122 Selector selRightmost = r.selector;
123 while (selRightmost instanceof ChildOrParentSelector) {
124 selRightmost = ((ChildOrParentSelector) selRightmost).right;
125 }
126 MapCSSRule optRule = new MapCSSRule(r.selector.optimizedBaseCheck(), r.declaration);
127 switch (((GeneralSelector) selRightmost).getBase()) {
128 case "node":
129 nodeRules.add(optRule);
130 break;
131 case "way":
132 wayRules.add(optRule);
133 break;
134 case "area":
135 wayRules.add(optRule);
136 multipolygonRules.add(optRule);
137 break;
138 case "relation":
139 relationRules.add(optRule);
140 multipolygonRules.add(optRule);
141 break;
142 case "*":
143 nodeRules.add(optRule);
144 wayRules.add(optRule);
145 relationRules.add(optRule);
146 multipolygonRules.add(optRule);
147 break;
148 case "canvas":
149 canvasRules.add(r);
150 }
151 }
152 }
153
154 @Override
155 public InputStream getSourceInputStream() throws IOException {
156 if (css != null) {
157 return new ByteArrayInputStream(css.getBytes(Utils.UTF_8));
158 }
159 MirroredInputStream in = new MirroredInputStream(url, null, MAPCSS_STYLE_MIME_TYPES);
160 if (isZip) {
161 File file = in.getFile();
162 Utils.close(in);
163 zipFile = new ZipFile(file);
164 zipIcons = file;
165 ZipEntry zipEntry = zipFile.getEntry(zipEntryPath);
166 return zipFile.getInputStream(zipEntry);
167 } else {
168 zipFile = null;
169 zipIcons = null;
170 return in;
171 }
172 }
173
174 @Override
175 public void closeSourceInputStream(InputStream is) {
176 super.closeSourceInputStream(is);
177 if (isZip) {
178 Utils.close(zipFile);
179 }
180 }
181
182 /**
183 * load meta info from a selector "meta"
184 */
185 private void loadMeta() {
186 Cascade c = constructSpecial("meta");
187 String pTitle = c.get("title", null, String.class);
188 if (title == null) {
189 title = pTitle;
190 }
191 String pIcon = c.get("icon", null, String.class);
192 if (icon == null) {
193 icon = pIcon;
194 }
195 }
196
197 private void loadCanvas() {
198 Cascade c = constructSpecial("canvas");
199 backgroundColorOverride = c.get("background-color", null, Color.class);
200 }
201
202 private Cascade constructSpecial(String type) {
203
204 MultiCascade mc = new MultiCascade();
205 Node n = new Node();
206 String code = LanguageInfo.getJOSMLocaleCode();
207 n.put("lang", code);
208 // create a fake environment to read the meta data block
209 Environment env = new Environment(n, mc, "default", this);
210
211 for (MapCSSRule r : rules) {
212 if ((r.selector instanceof GeneralSelector)) {
213 GeneralSelector gs = (GeneralSelector) r.selector;
214 if (gs.getBase().equals(type)) {
215 if (!gs.matchesConditions(env)) {
216 continue;
217 }
218 r.execute(env);
219 }
220 }
221 }
222 return mc.getCascade("default");
223 }
224
225 @Override
226 public Color getBackgroundColorOverride() {
227 return backgroundColorOverride;
228 }
229
230 @Override
231 public void apply(MultiCascade mc, OsmPrimitive osm, double scale, OsmPrimitive multipolyOuterWay, boolean pretendWayIsClosed) {
232 Environment env = new Environment(osm, mc, null, this);
233 List<MapCSSRule> matchingRules;
234 if (osm instanceof Node) {
235 matchingRules = nodeRules;
236 } else if (osm instanceof Way) {
237 matchingRules = wayRules;
238 } else {
239 if (((Relation) osm).isMultipolygon()) {
240 matchingRules = multipolygonRules;
241 } else if (osm.hasKey("#canvas")) {
242 matchingRules = canvasRules;
243 } else {
244 matchingRules = relationRules;
245 }
246 }
247
248 // the declaration indices are sorted, so it suffices to save the
249 // last used index
250 int lastDeclUsed = -1;
251
252 for (MapCSSRule r : matchingRules) {
253 env.clearSelectorMatchingInformation();
254 if (r.selector.matches(env)) { // as side effect env.parent will be set (if s is a child selector)
255 Selector s = r.selector;
256 if (s.getRange().contains(scale)) {
257 mc.range = Range.cut(mc.range, s.getRange());
258 } else {
259 mc.range = mc.range.reduceAround(scale, s.getRange());
260 continue;
261 }
262
263 if (r.declaration.idx == lastDeclUsed) continue; // don't apply one declaration more than once
264 lastDeclUsed = r.declaration.idx;
265 String sub = s.getSubpart();
266 if (sub == null) {
267 sub = "default";
268 }
269 else if ("*".equals(sub)) {
270 for (Entry<String, Cascade> entry : mc.getLayers()) {
271 env.layer = entry.getKey();
272 if (Utils.equal(env.layer, "*")) {
273 continue;
274 }
275 r.execute(env);
276 }
277 }
278 env.layer = sub;
279 r.execute(env);
280 }
281 }
282 }
283
284 public boolean evalMediaExpression(String feature, Object val) {
285 if ("user-agent".equals(feature)) {
286 String s = Cascade.convertTo(val, String.class);
287 if ("josm".equals(s)) return true;
288 }
289 if ("min-josm-version".equals(feature)) {
290 Float v = Cascade.convertTo(val, Float.class);
291 if (v != null) return Math.round(v) <= Version.getInstance().getVersion();
292 }
293 if ("max-josm-version".equals(feature)) {
294 Float v = Cascade.convertTo(val, Float.class);
295 if (v != null) return Math.round(v) >= Version.getInstance().getVersion();
296 }
297 return false;
298 }
299
300 @Override
301 public String toString() {
302 return Utils.join("\n", rules);
303 }
304}
Note: See TracBrowser for help on using the repository browser.