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

Last change on this file since 8432 was 8432, checked in by bastiK, 9 years ago

fixed #11479 - buildings not rendering in mapnik style (patch by michael2402)

  • Property svn:eol-style set to native
File size: 26.9 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.lang.reflect.Field;
12import java.nio.charset.StandardCharsets;
13import java.text.MessageFormat;
14import java.util.ArrayList;
15import java.util.BitSet;
16import java.util.Collections;
17import java.util.HashMap;
18import java.util.HashSet;
19import java.util.Iterator;
20import java.util.List;
21import java.util.Locale;
22import java.util.Map;
23import java.util.Map.Entry;
24import java.util.Set;
25import java.util.concurrent.locks.ReadWriteLock;
26import java.util.concurrent.locks.ReentrantReadWriteLock;
27import java.util.zip.ZipEntry;
28import java.util.zip.ZipFile;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.data.Version;
32import org.openstreetmap.josm.data.osm.Node;
33import org.openstreetmap.josm.data.osm.OsmPrimitive;
34import org.openstreetmap.josm.data.osm.Relation;
35import org.openstreetmap.josm.data.osm.Way;
36import org.openstreetmap.josm.gui.mappaint.Cascade;
37import org.openstreetmap.josm.gui.mappaint.Environment;
38import org.openstreetmap.josm.gui.mappaint.LineElemStyle;
39import org.openstreetmap.josm.gui.mappaint.MultiCascade;
40import org.openstreetmap.josm.gui.mappaint.Range;
41import org.openstreetmap.josm.gui.mappaint.StyleKeys;
42import org.openstreetmap.josm.gui.mappaint.StyleSetting;
43import org.openstreetmap.josm.gui.mappaint.StyleSetting.BooleanStyleSetting;
44import org.openstreetmap.josm.gui.mappaint.StyleSource;
45import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.KeyCondition;
46import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.KeyMatchType;
47import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.KeyValueCondition;
48import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Op;
49import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.SimpleKeyValueCondition;
50import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
51import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
52import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.OptimizedGeneralSelector;
53import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
54import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
55import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.TokenMgrError;
56import org.openstreetmap.josm.gui.preferences.SourceEntry;
57import org.openstreetmap.josm.io.CachedFile;
58import org.openstreetmap.josm.tools.CheckParameterUtil;
59import org.openstreetmap.josm.tools.LanguageInfo;
60import org.openstreetmap.josm.tools.Utils;
61
62/**
63 * This is a mappaint style that is based on MapCSS rules.
64 */
65public class MapCSSStyleSource extends StyleSource {
66
67 /**
68 * The accepted MIME types sent in the HTTP Accept header.
69 * @since 6867
70 */
71 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";
72
73 // all rules
74 public final List<MapCSSRule> rules = new ArrayList<>();
75 // rule indices, filtered by primitive type
76 public final MapCSSRuleIndex nodeRules = new MapCSSRuleIndex(); // nodes
77 public final MapCSSRuleIndex wayRules = new MapCSSRuleIndex(); // ways without tag area=no
78 public final MapCSSRuleIndex wayNoAreaRules = new MapCSSRuleIndex(); // ways with tag area=no
79 public final MapCSSRuleIndex relationRules = new MapCSSRuleIndex(); // relations that are not multipolygon relations
80 public final MapCSSRuleIndex multipolygonRules = new MapCSSRuleIndex(); // multipolygon relations
81 public final MapCSSRuleIndex canvasRules = new MapCSSRuleIndex(); // rules to apply canvas properties
82
83 private Color backgroundColorOverride;
84 private String css = null;
85 private ZipFile zipFile;
86
87 /**
88 * This lock prevents concurrent execution of {@link MapCSSRuleIndex#clear() } /
89 * {@link MapCSSRuleIndex#initIndex()} and {@link MapCSSRuleIndex#getRuleCandidates }.
90 *
91 * For efficiency reasons, these methods are synchronized higher up the
92 * stack trace.
93 */
94 public static final ReadWriteLock STYLE_SOURCE_LOCK = new ReentrantReadWriteLock();
95
96 /**
97 * Set of all supported MapCSS keys.
98 */
99 public static final Set<String> SUPPORTED_KEYS = new HashSet<>();
100 static {
101 Field[] declaredFields = StyleKeys.class.getDeclaredFields();
102 for (Field f : declaredFields) {
103 try {
104 SUPPORTED_KEYS.add((String) f.get(null));
105 if (!f.getName().toLowerCase(Locale.ENGLISH).replace("_", "-").equals(f.get(null))) {
106 throw new RuntimeException(f.getName());
107 }
108 } catch (IllegalArgumentException | IllegalAccessException ex) {
109 throw new RuntimeException(ex);
110 }
111 }
112 for (LineElemStyle.LineType lt : LineElemStyle.LineType.values()) {
113 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.COLOR);
114 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.DASHES);
115 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.DASHES_BACKGROUND_COLOR);
116 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.DASHES_BACKGROUND_OPACITY);
117 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.DASHES_OFFSET);
118 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.LINECAP);
119 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.LINEJOIN);
120 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.MITERLIMIT);
121 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.OFFSET);
122 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.OPACITY);
123 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.REAL_WIDTH);
124 SUPPORTED_KEYS.add(lt.prefix + StyleKeys.WIDTH);
125 }
126 }
127
128 /**
129 * A collection of {@link MapCSSRule}s, that are indexed by tag key and value.
130 *
131 * Speeds up the process of finding all rules that match a certain primitive.
132 *
133 * Rules with a {@link SimpleKeyValueCondition} [key=value] or rules that require a specific key to be set are
134 * indexed. Now you only need to loop the tags of a primitive to retrieve the possibly matching rules.
135 *
136 * To use this index, you need to {@link #add(MapCSSRule)} all rules to it. You then need to call
137 * {@link #initIndex()}. Afterwards, you can use {@link #getRuleCandidates(OsmPrimitive)} to get an iterator over
138 * all rules that might be applied to that primitive.
139 */
140 public static class MapCSSRuleIndex {
141 /**
142 * This is an iterator over all rules that are marked as possible in the bitset.
143 *
144 * @author Michael Zangl
145 */
146 private final class RuleCandidatesIterator implements Iterator<MapCSSRule> {
147 private final BitSet ruleCandidates;
148 private int next;
149
150 private RuleCandidatesIterator(BitSet ruleCandidates) {
151 this.ruleCandidates = ruleCandidates;
152 next = ruleCandidates.nextSetBit(0);
153 }
154
155 @Override
156 public boolean hasNext() {
157 return next >= 0;
158 }
159
160 @Override
161 public MapCSSRule next() {
162 MapCSSRule rule = rules.get(next);
163 next = ruleCandidates.nextSetBit(next + 1);
164 return rule;
165 }
166
167 @Override
168 public void remove() {
169 throw new UnsupportedOperationException();
170 }
171 }
172
173 /**
174 * This is a map of all rules that are only applied if the primitive has a given key (and possibly value)
175 *
176 * @author Michael Zangl
177 */
178 private static final class MapCSSKeyRules {
179 /**
180 * The indexes of rules that might be applied if this tag is present and the value has no special handling.
181 */
182 BitSet generalRules = new BitSet();
183
184 /**
185 * A map that sores the indexes of rules that might be applied if the key=value pair is present on this
186 * primitive. This includes all key=* rules.
187 */
188 Map<String, BitSet> specialRules = new HashMap<>();
189
190 public void addForKey(int ruleIndex) {
191 generalRules.set(ruleIndex);
192 for (BitSet r : specialRules.values()) {
193 r.set(ruleIndex);
194 }
195 }
196
197 public void addForKeyAndValue(String value, int ruleIndex) {
198 BitSet forValue = specialRules.get(value);
199 if (forValue == null) {
200 forValue = new BitSet();
201 forValue.or(generalRules);
202 specialRules.put(value.intern(), forValue);
203 }
204 forValue.set(ruleIndex);
205 }
206
207 public BitSet get(String value) {
208 BitSet forValue = specialRules.get(value);
209 if (forValue != null) return forValue; else return generalRules;
210 }
211 }
212
213 /**
214 * All rules this index is for. Once this index is built, this list is sorted.
215 */
216 private final List<MapCSSRule> rules = new ArrayList<>();
217 /**
218 * All rules that only apply when the given key is present.
219 */
220 private final Map<String, MapCSSKeyRules> index = new HashMap<>();
221 /**
222 * Rules that do not require any key to be present. Only the index in the {@link #rules} array is stored.
223 */
224 private final BitSet remaining = new BitSet();
225
226 /**
227 * Add a rule to this index. This needs to be called before {@link #initIndex()} is called.
228 * @param rule The rule to add.
229 */
230 public void add(MapCSSRule rule) {
231 rules.add(rule);
232 }
233
234 /**
235 * Initialize the index.
236 * <p>
237 * You must own the write lock of STYLE_SOURCE_LOCK when calling this method.
238 */
239 public void initIndex() {
240 Collections.sort(rules);
241 for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) {
242 MapCSSRule r = rules.get(ruleIndex);
243 // find the rightmost selector, this must be a GeneralSelector
244 Selector selRightmost = r.selector;
245 while (selRightmost instanceof ChildOrParentSelector) {
246 selRightmost = ((ChildOrParentSelector) selRightmost).right;
247 }
248 OptimizedGeneralSelector s = (OptimizedGeneralSelector) selRightmost;
249 if (s.conds == null) {
250 remaining.set(ruleIndex);
251 continue;
252 }
253 List<SimpleKeyValueCondition> sk = new ArrayList<>(Utils.filteredCollection(s.conds,
254 SimpleKeyValueCondition.class));
255 if (!sk.isEmpty()) {
256 SimpleKeyValueCondition c = sk.get(sk.size() - 1);
257 getEntryInIndex(c.k).addForKeyAndValue(c.v, ruleIndex);
258 } else {
259 String key = findAnyRequiredKey(s.conds);
260 if (key != null) {
261 getEntryInIndex(key).addForKey(ruleIndex);
262 } else {
263 remaining.set(ruleIndex);
264 }
265 }
266 }
267 }
268
269 /**
270 * Search for any key that condition might depend on.
271 *
272 * @param conds The conditions to search through.
273 * @return An arbitrary key this rule depends on or <code>null</code> if there is no such key.
274 */
275 private String findAnyRequiredKey(List<Condition> conds) {
276 String key = null;
277 for (Condition c : conds) {
278 if (c instanceof KeyCondition) {
279 KeyCondition keyCondition = (KeyCondition) c;
280 if (!keyCondition.negateResult && conditionRequiresKeyPresence(keyCondition.matchType)) {
281 key = keyCondition.label;
282 }
283 } else if (c instanceof KeyValueCondition) {
284 KeyValueCondition keyValueCondition = (KeyValueCondition) c;
285 if (!Op.NEGATED_OPS.contains(keyValueCondition.op)) {
286 key = keyValueCondition.k;
287 }
288 }
289 }
290 return key;
291 }
292
293 private boolean conditionRequiresKeyPresence(KeyMatchType matchType) {
294 return matchType != KeyMatchType.REGEX;
295 }
296
297 private MapCSSKeyRules getEntryInIndex(String key) {
298 MapCSSKeyRules rulesWithMatchingKey = index.get(key);
299 if (rulesWithMatchingKey == null) {
300 rulesWithMatchingKey = new MapCSSKeyRules();
301 index.put(key.intern(), rulesWithMatchingKey);
302 }
303 return rulesWithMatchingKey;
304 }
305
306 /**
307 * Get a subset of all rules that might match the primitive. Rules not included in the result are guaranteed to
308 * not match this primitive.
309 * <p>
310 * You must have a read lock of STYLE_SOURCE_LOCK when calling this method.
311 *
312 * @param osm the primitive to match
313 * @return An iterator over possible rules in the right order.
314 */
315 public Iterator<MapCSSRule> getRuleCandidates(OsmPrimitive osm) {
316 final BitSet ruleCandidates = new BitSet(rules.size());
317 ruleCandidates.or(remaining);
318
319 for (Map.Entry<String, String> e : osm.getKeys().entrySet()) {
320 MapCSSKeyRules v = index.get(e.getKey());
321 if (v != null) {
322 BitSet rs = v.get(e.getValue());
323 ruleCandidates.or(rs);
324 }
325 }
326 return new RuleCandidatesIterator(ruleCandidates);
327 }
328
329 /**
330 * Clear the index.
331 * <p>
332 * You must own the write lock STYLE_SOURCE_LOCK when calling this method.
333 */
334 public void clear() {
335 rules.clear();
336 index.clear();
337 remaining.clear();
338 }
339 }
340
341 /**
342 * Constructs a new, active {@link MapCSSStyleSource}.
343 * @param url URL that {@link org.openstreetmap.josm.io.CachedFile} understands
344 * @param name The name for this StyleSource
345 * @param shortdescription The title for that source.
346 */
347 public MapCSSStyleSource(String url, String name, String shortdescription) {
348 super(url, name, shortdescription);
349 }
350
351 /**
352 * Constructs a new {@link MapCSSStyleSource}
353 * @param entry The entry to copy the data (url, name, ...) from.
354 */
355 public MapCSSStyleSource(SourceEntry entry) {
356 super(entry);
357 }
358
359 /**
360 * <p>Creates a new style source from the MapCSS styles supplied in
361 * {@code css}</p>
362 *
363 * @param css the MapCSS style declaration. Must not be null.
364 * @throws IllegalArgumentException if {@code css} is null
365 */
366 public MapCSSStyleSource(String css) {
367 super(null, null, null);
368 CheckParameterUtil.ensureParameterNotNull(css);
369 this.css = css;
370 }
371
372 @Override
373 public void loadStyleSource() {
374 STYLE_SOURCE_LOCK.writeLock().lock();
375 try {
376 init();
377 rules.clear();
378 nodeRules.clear();
379 wayRules.clear();
380 wayNoAreaRules.clear();
381 relationRules.clear();
382 multipolygonRules.clear();
383 canvasRules.clear();
384 try (InputStream in = getSourceInputStream()) {
385 try {
386 // evaluate @media { ... } blocks
387 MapCSSParser preprocessor = new MapCSSParser(in, "UTF-8", MapCSSParser.LexicalState.PREPROCESSOR);
388 String mapcss = preprocessor.pp_root(this);
389
390 // do the actual mapcss parsing
391 InputStream in2 = new ByteArrayInputStream(mapcss.getBytes(StandardCharsets.UTF_8));
392 MapCSSParser parser = new MapCSSParser(in2, "UTF-8", MapCSSParser.LexicalState.DEFAULT);
393 parser.sheet(this);
394
395 loadMeta();
396 loadCanvas();
397 loadSettings();
398 } finally {
399 closeSourceInputStream(in);
400 }
401 } catch (IOException e) {
402 Main.warn(tr("Failed to load Mappaint styles from ''{0}''. Exception was: {1}", url, e.toString()));
403 Main.error(e);
404 logError(e);
405 } catch (TokenMgrError e) {
406 Main.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
407 Main.error(e);
408 logError(e);
409 } catch (ParseException e) {
410 Main.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
411 Main.error(e);
412 logError(new ParseException(e.getMessage())); // allow e to be garbage collected, it links to the entire token stream
413 }
414 // optimization: filter rules for different primitive types
415 for (MapCSSRule r: rules) {
416 // find the rightmost selector, this must be a GeneralSelector
417 Selector selRightmost = r.selector;
418 while (selRightmost instanceof ChildOrParentSelector) {
419 selRightmost = ((ChildOrParentSelector) selRightmost).right;
420 }
421 MapCSSRule optRule = new MapCSSRule(r.selector.optimizedBaseCheck(), r.declaration);
422 final String base = ((GeneralSelector) selRightmost).getBase();
423 switch (base) {
424 case "node":
425 nodeRules.add(optRule);
426 break;
427 case "way":
428 wayNoAreaRules.add(optRule);
429 wayRules.add(optRule);
430 break;
431 case "area":
432 wayRules.add(optRule);
433 multipolygonRules.add(optRule);
434 break;
435 case "relation":
436 relationRules.add(optRule);
437 multipolygonRules.add(optRule);
438 break;
439 case "*":
440 nodeRules.add(optRule);
441 wayRules.add(optRule);
442 wayNoAreaRules.add(optRule);
443 relationRules.add(optRule);
444 multipolygonRules.add(optRule);
445 break;
446 case "canvas":
447 canvasRules.add(r);
448 break;
449 case "meta":
450 case "setting":
451 break;
452 default:
453 final RuntimeException e = new RuntimeException(MessageFormat.format("Unknown MapCSS base selector {0}", base));
454 Main.warn(tr("Failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
455 Main.error(e);
456 logError(e);
457 }
458 }
459 nodeRules.initIndex();
460 wayRules.initIndex();
461 wayNoAreaRules.initIndex();
462 relationRules.initIndex();
463 multipolygonRules.initIndex();
464 canvasRules.initIndex();
465 } finally {
466 STYLE_SOURCE_LOCK.writeLock().unlock();
467 }
468 }
469
470 @Override
471 public InputStream getSourceInputStream() throws IOException {
472 if (css != null) {
473 return new ByteArrayInputStream(css.getBytes(StandardCharsets.UTF_8));
474 }
475 CachedFile cf = getCachedFile();
476 if (isZip) {
477 File file = cf.getFile();
478 zipFile = new ZipFile(file, StandardCharsets.UTF_8);
479 zipIcons = file;
480 ZipEntry zipEntry = zipFile.getEntry(zipEntryPath);
481 return zipFile.getInputStream(zipEntry);
482 } else {
483 zipFile = null;
484 zipIcons = null;
485 return cf.getInputStream();
486 }
487 }
488
489 @Override
490 public CachedFile getCachedFile() throws IOException {
491 return new CachedFile(url).setHttpAccept(MAPCSS_STYLE_MIME_TYPES);
492 }
493
494 @Override
495 public void closeSourceInputStream(InputStream is) {
496 super.closeSourceInputStream(is);
497 if (isZip) {
498 Utils.close(zipFile);
499 }
500 }
501
502 /**
503 * load meta info from a selector "meta"
504 */
505 private void loadMeta() {
506 Cascade c = constructSpecial("meta");
507 String pTitle = c.get("title", null, String.class);
508 if (title == null) {
509 title = pTitle;
510 }
511 String pIcon = c.get("icon", null, String.class);
512 if (icon == null) {
513 icon = pIcon;
514 }
515 }
516
517 private void loadCanvas() {
518 Cascade c = constructSpecial("canvas");
519 backgroundColorOverride = c.get("fill-color", null, Color.class);
520 if (backgroundColorOverride == null) {
521 backgroundColorOverride = c.get("background-color", null, Color.class);
522 if (backgroundColorOverride != null) {
523 Main.warn(tr("Detected deprecated ''{0}'' in ''{1}'' which will be removed shortly. Use ''{2}'' instead.", "canvas{background-color}", url, "fill-color"));
524 }
525 }
526 }
527
528 private void loadSettings() {
529 settings.clear();
530 settingValues.clear();
531 MultiCascade mc = new MultiCascade();
532 Node n = new Node();
533 String code = LanguageInfo.getJOSMLocaleCode();
534 n.put("lang", code);
535 // create a fake environment to read the meta data block
536 Environment env = new Environment(n, mc, "default", this);
537
538 for (MapCSSRule r : rules) {
539 if (r.selector instanceof GeneralSelector) {
540 GeneralSelector gs = (GeneralSelector) r.selector;
541 if ("setting".equals(gs.getBase())) {
542 if (!gs.matchesConditions(env)) {
543 continue;
544 }
545 env.layer = null;
546 env.layer = gs.getSubpart().getId(env);
547 r.execute(env);
548 }
549 }
550 }
551 for (Entry<String, Cascade> e : mc.getLayers()) {
552 if ("default".equals(e.getKey())) {
553 Main.warn("setting requires layer identifier e.g. 'setting::my_setting {...}'");
554 continue;
555 }
556 Cascade c = e.getValue();
557 String type = c.get("type", null, String.class);
558 StyleSetting set = null;
559 if ("boolean".equals(type)) {
560 set = BooleanStyleSetting.create(c, this, e.getKey());
561 } else {
562 Main.warn("Unkown setting type: "+type);
563 }
564 if (set != null) {
565 settings.add(set);
566 settingValues.put(e.getKey(), set.getValue());
567 }
568 }
569 }
570
571 private Cascade constructSpecial(String type) {
572
573 MultiCascade mc = new MultiCascade();
574 Node n = new Node();
575 String code = LanguageInfo.getJOSMLocaleCode();
576 n.put("lang", code);
577 // create a fake environment to read the meta data block
578 Environment env = new Environment(n, mc, "default", this);
579
580 for (MapCSSRule r : rules) {
581 if (r.selector instanceof GeneralSelector) {
582 GeneralSelector gs = (GeneralSelector) r.selector;
583 if (gs.getBase().equals(type)) {
584 if (!gs.matchesConditions(env)) {
585 continue;
586 }
587 r.execute(env);
588 }
589 }
590 }
591 return mc.getCascade("default");
592 }
593
594 @Override
595 public Color getBackgroundColorOverride() {
596 return backgroundColorOverride;
597 }
598
599 @Override
600 public void apply(MultiCascade mc, OsmPrimitive osm, double scale, boolean pretendWayIsClosed) {
601 Environment env = new Environment(osm, mc, null, this);
602 MapCSSRuleIndex matchingRuleIndex;
603 if (osm instanceof Node) {
604 matchingRuleIndex = nodeRules;
605 } else if (osm instanceof Way) {
606 if (osm.isKeyFalse("area")) {
607 matchingRuleIndex = wayNoAreaRules;
608 } else {
609 matchingRuleIndex = wayRules;
610 }
611 } else {
612 if (((Relation) osm).isMultipolygon()) {
613 matchingRuleIndex = multipolygonRules;
614 } else if (osm.hasKey("#canvas")) {
615 matchingRuleIndex = canvasRules;
616 } else {
617 matchingRuleIndex = relationRules;
618 }
619 }
620
621 // the declaration indices are sorted, so it suffices to save the
622 // last used index
623 int lastDeclUsed = -1;
624
625 Iterator<MapCSSRule> candidates = matchingRuleIndex.getRuleCandidates(osm);
626 while (candidates.hasNext()) {
627 MapCSSRule r = candidates.next();
628 env.clearSelectorMatchingInformation();
629 env.layer = null;
630 String sub = env.layer = r.selector.getSubpart().getId(env);
631 if (r.selector.matches(env)) { // as side effect env.parent will be set (if s is a child selector)
632 Selector s = r.selector;
633 if (s.getRange().contains(scale)) {
634 mc.range = Range.cut(mc.range, s.getRange());
635 } else {
636 mc.range = mc.range.reduceAround(scale, s.getRange());
637 continue;
638 }
639
640 if (r.declaration.idx == lastDeclUsed)
641 continue; // don't apply one declaration more than once
642 lastDeclUsed = r.declaration.idx;
643 if ("*".equals(sub)) {
644 for (Entry<String, Cascade> entry : mc.getLayers()) {
645 env.layer = entry.getKey();
646 if ("*".equals(env.layer)) {
647 continue;
648 }
649 r.execute(env);
650 }
651 }
652 env.layer = sub;
653 r.execute(env);
654 }
655 }
656 }
657
658 public boolean evalSupportsDeclCondition(String feature, Object val) {
659 if (feature == null) return false;
660 if (SUPPORTED_KEYS.contains(feature)) return true;
661 switch (feature) {
662 case "user-agent":
663 {
664 String s = Cascade.convertTo(val, String.class);
665 return "josm".equals(s);
666 }
667 case "min-josm-version":
668 {
669 Float v = Cascade.convertTo(val, Float.class);
670 return v != null && Math.round(v) <= Version.getInstance().getVersion();
671 }
672 case "max-josm-version":
673 {
674 Float v = Cascade.convertTo(val, Float.class);
675 return v != null && Math.round(v) >= Version.getInstance().getVersion();
676 }
677 default:
678 return false;
679 }
680 }
681
682 @Override
683 public String toString() {
684 return Utils.join("\n", rules);
685 }
686}
Note: See TracBrowser for help on using the repository browser.