001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint.xml;
003
004import java.awt.Color;
005import java.util.Arrays;
006import java.util.Collection;
007import java.util.LinkedList;
008
009import org.openstreetmap.josm.Main;
010import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference;
011import org.openstreetmap.josm.gui.mappaint.Range;
012import org.openstreetmap.josm.tools.ColorHelper;
013import org.xml.sax.Attributes;
014import org.xml.sax.helpers.DefaultHandler;
015
016public class XmlStyleSourceHandler extends DefaultHandler {
017    private boolean inDoc, inRule, inCondition, inLine, inLineMod, inIcon, inArea, inScaleMax, inScaleMin;
018    private boolean hadLine, hadLineMod, hadIcon, hadArea;
019    private RuleElem rule = new RuleElem();
020
021    private XmlStyleSource style;
022
023    static class RuleElem {
024        private XmlCondition cond = new XmlCondition();
025        private Collection<XmlCondition> conditions;
026        private double scaleMax;
027        private double scaleMin;
028        private LinePrototype line = new LinePrototype();
029        private LinemodPrototype linemod = new LinemodPrototype();
030        private AreaPrototype area = new AreaPrototype();
031        private IconPrototype icon = new IconPrototype();
032        public void init() {
033            conditions = null;
034            scaleMax = Double.POSITIVE_INFINITY;
035            scaleMin = 0;
036            line.init();
037            cond.init();
038            linemod.init();
039            area.init();
040            icon.init();
041        }
042    }
043
044    public XmlStyleSourceHandler(XmlStyleSource style) {
045        this.style = style;
046        inDoc = inRule = inCondition = inLine = inIcon = inArea = false;
047        rule.init();
048    }
049
050    private Color convertColor(String colString) {
051        int i = colString.indexOf('#');
052        Color ret;
053        if (i < 0) {
054            ret = Main.pref.getColor("mappaint."+style.getPrefName()+'.'+colString, Color.red);
055        } else if (i == 0) {
056            ret = ColorHelper.html2color(colString);
057        } else {
058            ret = Main.pref.getColor("mappaint."+style.getPrefName()+'.'+colString.substring(0, i),
059                    ColorHelper.html2color(colString.substring(i)));
060        }
061        return ret;
062    }
063
064    @Override
065    public void startDocument() {
066        inDoc = true;
067    }
068
069    @Override
070    public void endDocument() {
071        inDoc = false;
072    }
073
074    private void error(String message) {
075        String warning = style.getDisplayString() + " (" + rule.cond.key + '=' + rule.cond.value + "): " + message;
076        Main.warn(warning);
077        style.logError(new Exception(warning));
078    }
079
080    private void startElementLine(String qName, Attributes atts, LinePrototype line) {
081        for (int count = 0; count < atts.getLength(); count++) {
082            switch (atts.getQName(count)) {
083            case "width":
084                String val = atts.getValue(count);
085                if (!(val.startsWith("+") || val.startsWith("-") || val.endsWith("%"))) {
086                    line.setWidth(Integer.parseInt(val));
087                }
088                break;
089            case "colour":
090                line.color = convertColor(atts.getValue(count));
091                break;
092            case "realwidth":
093                line.realWidth = Integer.valueOf(atts.getValue(count));
094                break;
095            case "dashed":
096                Float[] dashed;
097                try {
098                    String[] parts = atts.getValue(count).split(",");
099                    dashed = new Float[parts.length];
100                    for (int i = 0; i < parts.length; i++) {
101                        dashed[i] = (float) Integer.parseInt(parts[i]);
102                    }
103                } catch (NumberFormatException nfe) {
104                    boolean isDashed = Boolean.parseBoolean(atts.getValue(count));
105                    if (isDashed) {
106                        dashed = new Float[]{9f};
107                    } else {
108                        dashed = null;
109                    }
110                }
111                line.setDashed(dashed == null ? null : Arrays.asList(dashed));
112                break;
113            case "dashedcolour":
114                line.dashedColor = convertColor(atts.getValue(count));
115                break;
116            case "priority":
117                line.priority = Integer.parseInt(atts.getValue(count));
118                break;
119            case "mode":
120                if (line instanceof LinemodPrototype)
121                    break;
122            default:
123                error("The element \"" + qName + "\" has unknown attribute \"" + atts.getQName(count) + "\"!");
124            }
125        }
126    }
127
128    private void startElementLinemod(String qName, Attributes atts, LinemodPrototype line) {
129        startElementLine(qName, atts, line);
130        for (int count = 0; count < atts.getLength(); count++) {
131            switch (atts.getQName(count)) {
132            case "width":
133                String val = atts.getValue(count);
134                if (val.startsWith("+")) {
135                    line.setWidth(Integer.parseInt(val.substring(1)));
136                    line.widthMode = LinemodPrototype.WidthMode.OFFSET;
137                } else if (val.startsWith("-")) {
138                    line.setWidth(Integer.parseInt(val));
139                    line.widthMode = LinemodPrototype.WidthMode.OFFSET;
140                } else if (val.endsWith("%")) {
141                    line.setWidth(Integer.parseInt(val.substring(0, val.length()-1)));
142                    line.widthMode = LinemodPrototype.WidthMode.PERCENT;
143                } else {
144                    line.setWidth(Integer.parseInt(val));
145                }
146                break;
147            case "mode":
148                line.over = !"under".equals(atts.getValue(count));
149                break;
150            }
151        }
152    }
153
154    @Override
155    public void startElement(String uri, String name, String qName, Attributes atts) {
156        if (inDoc) {
157            switch(qName) {
158            case "rule":
159                inRule = true;
160                break;
161            case "rules":
162                if (style.name == null) {
163                    style.name = atts.getValue("name");
164                }
165                if (style.title == null) {
166                    style.title = atts.getValue("shortdescription");
167                }
168                if (style.icon == null) {
169                    style.icon = atts.getValue("icon");
170                }
171                break;
172            case "scale_max":
173                inScaleMax = true;
174                break;
175            case "scale_min":
176                inScaleMin = true;
177                break;
178            case "condition":
179                if (inRule) {
180                    inCondition = true;
181                    XmlCondition c = rule.cond;
182                    if (c.key != null) {
183                        if (rule.conditions == null) {
184                            rule.conditions = new LinkedList<>();
185                        }
186                        rule.conditions.add(new XmlCondition(rule.cond));
187                        c = new XmlCondition();
188                        rule.conditions.add(c);
189                    }
190                    for (int count = 0; count < atts.getLength(); count++) {
191                        switch (atts.getQName(count)) {
192                        case "k":
193                            c.key = atts.getValue(count);
194                            break;
195                        case "v":
196                            c.value = atts.getValue(count);
197                            break;
198                        case "b":
199                            c.boolValue = atts.getValue(count);
200                            break;
201                        default:
202                            error("The element \"" + qName + "\" has unknown attribute \"" + atts.getQName(count) + "\"!");
203                        }
204                    }
205                    if (c.key == null) {
206                        error("The condition has no key!");
207                    }
208                }
209                break;
210            case "line":
211                hadLine = inLine = true;
212                startElementLine(qName, atts, rule.line);
213                break;
214            case "linemod":
215                hadLineMod = inLineMod = true;
216                startElementLinemod(qName, atts, rule.linemod);
217                break;
218            case "icon":
219                inIcon = true;
220                for (int count = 0; count < atts.getLength(); count++) {
221                    switch (atts.getQName(count)) {
222                    case "src":
223                        IconReference icon = new IconReference(atts.getValue(count), style);
224                        hadIcon = (icon != null);
225                        rule.icon.icon = icon;
226                        break;
227                    case "annotate":
228                        rule.icon.annotate = Boolean.valueOf(atts.getValue(count));
229                        break;
230                    case "priority":
231                        rule.icon.priority = Integer.parseInt(atts.getValue(count));
232                        break;
233                    default:
234                        error("The element \"" + qName + "\" has unknown attribute \"" + atts.getQName(count) + "\"!");
235                    }
236                }
237                break;
238            case "area":
239                hadArea = inArea = true;
240                for (int count = 0; count < atts.getLength(); count++) {
241                    switch (atts.getQName(count)) {
242                    case "colour":
243                        rule.area.color = convertColor(atts.getValue(count));
244                        break;
245                    case "closed":
246                        rule.area.closed = Boolean.parseBoolean(atts.getValue(count));
247                        break;
248                    case "priority":
249                        rule.area.priority = Integer.parseInt(atts.getValue(count));
250                        break;
251                    default:
252                        error("The element \"" + qName + "\" has unknown attribute \"" + atts.getQName(count) + "\"!");
253                    }
254                }
255                break;
256            default:
257                error("The element \"" + qName + "\" is unknown!");
258            }
259        }
260    }
261
262    @Override
263    public void endElement(String uri, String name, String qName) {
264        if (inRule && "rule".equals(qName)) {
265            if (hadLine) {
266                style.add(rule.cond, rule.conditions,
267                        new LinePrototype(rule.line, new Range(rule.scaleMin, rule.scaleMax)));
268            }
269            if (hadLineMod) {
270                style.add(rule.cond, rule.conditions,
271                        new LinemodPrototype(rule.linemod, new Range(rule.scaleMin, rule.scaleMax)));
272            }
273            if (hadIcon) {
274                style.add(rule.cond, rule.conditions,
275                        new IconPrototype(rule.icon, new Range(rule.scaleMin, rule.scaleMax)));
276            }
277            if (hadArea) {
278                style.add(rule.cond, rule.conditions,
279                        new AreaPrototype(rule.area, new Range(rule.scaleMin, rule.scaleMax)));
280            }
281            inRule = false;
282            hadLine = hadLineMod = hadIcon = hadArea = false;
283            rule.init();
284        } else if (inCondition && "condition".equals(qName)) {
285            inCondition = false;
286        } else if (inLine && "line".equals(qName)) {
287            inLine = false;
288        } else if (inLineMod && "linemod".equals(qName)) {
289            inLineMod = false;
290        } else if (inIcon && "icon".equals(qName)) {
291            inIcon = false;
292        } else if (inArea && "area".equals(qName)) {
293            inArea = false;
294        } else if ("scale_max".equals(qName)) {
295            inScaleMax = false;
296        } else if ("scale_min".equals(qName)) {
297            inScaleMin = false;
298        }
299    }
300
301    @Override
302    public void characters(char[] ch, int start, int length) {
303        if (inScaleMax) {
304            rule.scaleMax = Long.parseLong(new String(ch, start, length));
305        } else if (inScaleMin) {
306            rule.scaleMin = Long.parseLong(new String(ch, start, length));
307        }
308    }
309}