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