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 }