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