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