package net.osmand.render; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Stack; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import net.osmand.LogUtil; import org.apache.commons.logging.Log; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class OsmandRenderingRulesParser { private final static Log log = LogUtil.getLog(OsmandRenderingRulesParser.class); public static class EffectAttributes { public int color = 0; public float strokeWidth = 0; public String pathEffect = null; public int shadowColor = 0; public float shadowRadius = 0; public String cap = null; } public static class TextAttributes { public int textColor = 0; public float textSize = 0; public boolean textBold = false; public String textShield = null; public int textMinDistance = 0; public boolean textOnPath = false; public int textWrapWidth = 0; public float textHaloRadius = 0; public int textDy = 0; public String ref = null; } protected static class SwitchState { List filters = new ArrayList(); } public static class FilterState { public int minzoom = -1; public int maxzoom = -1; public String tag = null; public String val = null; public int layer = 0; public int textLength = 0; public float order = 0; public int orderType = -1; public String shader = null; // point public String icon = null; public EffectAttributes main = new EffectAttributes(); public TextAttributes text = null; public List effectAttributes = new ArrayList(3); protected EffectAttributes getEffectAttributes(int i) { i -= 2; while (i >= effectAttributes.size()) { effectAttributes.add(new EffectAttributes()); } return effectAttributes.get(i); } } public interface RenderingRuleVisitor { /** * @param state - one of the point, polygon, line, text state * @param filter */ public void visitRule(int state, FilterState filter); public void rendering(String name, String depends, int defaultColor); } public final static int POINT_STATE = 1; public final static int POLYGON_STATE = 2; public final static int LINE_STATE = 3; public final static int TEXT_STATE = 4; public final static int ORDER_STATE = 5; public void parseRenderingRules(InputStream is, RenderingRuleVisitor visitor) throws IOException, SAXException { try { final SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); saxParser.parse(is, new RenderingRulesHandler(saxParser, visitor)); } catch (ParserConfigurationException e) { throw new SAXException(e); } } private class RenderingRulesHandler extends DefaultHandler { private final SAXParser parser; private final RenderingRuleVisitor visitor; private int state; Stack stack = new Stack(); public RenderingRulesHandler(SAXParser parser, RenderingRuleVisitor visitor){ this.parser = parser; this.visitor = visitor; } @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { name = parser.isNamespaceAware() ? localName : name; if("filter".equals(name)){ //$NON-NLS-1$ FilterState st = parseFilterAttributes(attributes); stack.push(st); } else if("order".equals(name)){ //$NON-NLS-1$ state = ORDER_STATE; } else if("text".equals(name)){ //$NON-NLS-1$ state = TEXT_STATE; } else if("point".equals(name)){ //$NON-NLS-1$ state = POINT_STATE; } else if("line".equals(name)){ //$NON-NLS-1$ state = LINE_STATE; } else if("polygon".equals(name)){ //$NON-NLS-1$ state = POLYGON_STATE; } else if("switch".equals(name)){ //$NON-NLS-1$ SwitchState st = new SwitchState(); stack.push(st); } else if("case".equals(name)){ //$NON-NLS-1$ FilterState st = parseFilterAttributes(attributes); ((SwitchState)stack.peek()).filters.add(st); } else if("renderer".equals(name)){ //$NON-NLS-1$ String dc = attributes.getValue("defaultColor"); int defaultColor = 0; if(dc != null && dc.length() > 0){ defaultColor = parseColor(dc); } visitor.rendering(attributes.getValue("name"), attributes.getValue("depends"), defaultColor); //$NON-NLS-1$ //$NON-NLS-2$ } else { log.warn("Unknown tag" + name); //$NON-NLS-1$ } } @Override public void endElement(String uri, String localName, String name) throws SAXException { name = parser.isNamespaceAware() ? localName : name; if ("filter".equals(name)) { //$NON-NLS-1$ List list = popAndAggregateState(); for (FilterState pop : list) { if (pop.tag != null && (pop.minzoom != -1 || state == ORDER_STATE)) { visitor.visitRule(state, pop); } } } else if("switch".equals(name)){ //$NON-NLS-1$ stack.pop(); } } public List popAndAggregateState() { FilterState pop = (FilterState) stack.pop(); List res = null; for (int i = stack.size() - 1; i >= 0; i--) { Object o = stack.get(i); if(o instanceof FilterState){ if(res == null){ mergeStateInto((FilterState) o, pop); } else { for(FilterState f : res){ mergeStateInto((FilterState) o, f); } } } else { List filters = ((SwitchState)o).filters; if (res == null) { res = new ArrayList(); res.add(pop); } int l = res.size(); for (int t = 0; t < filters.size() - 1; t++) { for (int j = 0; j < l; j++) { FilterState n = new FilterState(); mergeStateInto(res.get(j), n); res.add(n); } } for (int j = 0; j < res.size(); j++) { mergeStateInto(filters.get(j / l), res.get(j)); } } } if(res == null){ return Collections.singletonList(pop); } else { return res; } } public void mergeStateInto(FilterState toMerge, FilterState mergeInto){ if(toMerge.maxzoom != -1 && mergeInto.maxzoom == -1){ mergeInto.maxzoom = toMerge.maxzoom; } if(toMerge.minzoom != -1 && mergeInto.minzoom == -1){ mergeInto.minzoom = toMerge.minzoom; } if(toMerge.icon != null && mergeInto.icon == null){ mergeInto.icon = toMerge.icon; } if(toMerge.tag != null && mergeInto.tag == null){ mergeInto.tag = toMerge.tag; } if(toMerge.orderType != -1 && mergeInto.orderType == -1){ mergeInto.orderType = toMerge.orderType; } if(toMerge.layer != 0 && mergeInto.layer == 0){ mergeInto.layer = toMerge.layer; } if(toMerge.order != 0 && mergeInto.order == 0){ mergeInto.order = toMerge.order; } if(toMerge.textLength != 0 && mergeInto.textLength == 0){ mergeInto.textLength = toMerge.textLength; } if(toMerge.val != null && mergeInto.val == null){ mergeInto.val = toMerge.val; } if(toMerge.text != null){ if(mergeInto.text == null){ mergeInto.text = new TextAttributes(); } if(toMerge.text.textColor != 0 && mergeInto.text.textColor == 0){ mergeInto.text.textColor = toMerge.text.textColor; } if(toMerge.text.textSize != 0 && mergeInto.text.textSize == 0){ mergeInto.text.textSize = toMerge.text.textSize; } if(toMerge.text.textBold && !mergeInto.text.textBold){ mergeInto.text.textBold = toMerge.text.textBold; } if(toMerge.text.textShield != null && mergeInto.text.textShield == null){ mergeInto.text.textShield = toMerge.text.textShield; } if(toMerge.text.ref != null && mergeInto.text.ref == null){ mergeInto.text.ref = toMerge.text.ref; } if(toMerge.text.textMinDistance != 0 && mergeInto.text.textMinDistance == 0){ mergeInto.text.textMinDistance = toMerge.text.textMinDistance; } if(toMerge.text.textDy != 0 && mergeInto.text.textDy == 0){ mergeInto.text.textDy = toMerge.text.textDy; } if(toMerge.text.textHaloRadius != 0 && mergeInto.text.textHaloRadius == 0){ mergeInto.text.textHaloRadius = toMerge.text.textHaloRadius; } if(toMerge.text.textWrapWidth != 0 && mergeInto.text.textWrapWidth == 0){ mergeInto.text.textWrapWidth = toMerge.text.textWrapWidth; } if(toMerge.text.textOnPath && !mergeInto.text.textOnPath){ mergeInto.text.textOnPath = toMerge.text.textOnPath; } } mergeStateInto(toMerge.main, mergeInto.main); while(mergeInto.effectAttributes.size() < toMerge.effectAttributes.size()){ mergeInto.effectAttributes.add(new EffectAttributes()); } for(int i=0; i