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

Last change on this file since 14801 was 14801, checked in by Don-vip, 5 years ago

fix #17358 - mapcss regular expression matches for key (patch by michael2402, regression from #17021)

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