/* Copyright (c) 2002-2011 by XMLVM.org * * Project Info: http://www.xmlvm.org * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package android.internal; import java.io.FileInputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.StateListDrawable; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Display; import android.view.InflateException; class DrawableParser extends DefaultHandler { private static final int UNKNOWN_DRAWABLE = 0; private static final int STATELIST_DRAWABLE = 1; private static final int GRADIENT_DRAWABLE = 2; private Context context; private String prefix; private Drawable drawable; private int drawableType = UNKNOWN_DRAWABLE; public DrawableParser(Context context) { this.context = context; } @Override public void startPrefixMapping(String prefix, String namespaceURI) throws SAXException { if (namespaceURI.equals("http://schemas.android.com/apk/res/android")) { this.prefix = prefix + ":"; } } @Override public void startElement(String namespaceURI, String elementName, String qualifiedName, Attributes attributes) throws SAXException { AttributeSet attrs = new ResourceAttributes(context, prefix, attributes); if (qualifiedName.equals("selector")) { drawable = StateListDrawable.xmlvmCreateStateListDrawable(attrs); drawableType = STATELIST_DRAWABLE; } else if (qualifiedName.equals("shape")) { drawable = GradientDrawable.xmlvmCreateGradientDrawable(attrs); drawableType = GRADIENT_DRAWABLE; } else { switch (drawableType) { case STATELIST_DRAWABLE: didStartStateListDrawableElement(qualifiedName, attrs); break; case GRADIENT_DRAWABLE: didStartGradientDrawableElement(qualifiedName, attrs); } } } @Override public void endElement(String namespaceURI, String elementName, String qualifiedName) throws SAXException { } private void didStartStateListDrawableElement(String elementName, AttributeSet attrs) { if (elementName.equals("item")) { processStateListItem(attrs); } else { throw new InflateException("Element not valid for StateListDrawable: " + elementName); } } private void didStartGradientDrawableElement(String elementName, AttributeSet attrs) { // TODO: Implement parsing the various gradient elements if (elementName.equals("solid")) { int color = attrs.getAttributeIntValue(null, "color", 0); ((GradientDrawable) drawable).setColor(color); } else if (elementName.equals("padding")) { DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); int left = (int) Dimension.resolveDimension(context, attrs.getAttributeValue(null, "left"), metrics); left = left < 0 ? 0 : left; int top = (int) Dimension.resolveDimension(context, attrs .getAttributeValue(null, "top"), metrics); top = top < 0 ? 0 : top; int right = (int) Dimension.resolveDimension(context, attrs.getAttributeValue(null, "right"), metrics); right = right < 0 ? 0 : right; int bottom = (int) Dimension.resolveDimension(context, attrs.getAttributeValue(null, "bottom"), metrics); bottom = bottom < 0 ? 0 : bottom; ((GradientDrawable) drawable).xmlvmSetPadding(left, top, right, bottom); } } private void processStateListItem(AttributeSet attrs) { List<Integer> states = new ArrayList<Integer>(); boolean b = attrs.getAttributeBooleanValue(null, State.STATE_PRESSED_NAME, false); if (b) { states.add(new Integer(State.STATE_PRESSED_ID)); } b = attrs.getAttributeBooleanValue(null, State.STATE_CHECKED_NAME, false); if (b) { states.add(new Integer(State.STATE_CHECKED_ID)); } int drawableId = attrs.getAttributeResourceValue(null, "drawable", -1); Drawable d = context.getResources().getDrawable(drawableId); int stateArray[] = new int[states.size()]; for (int i = 0; i < states.size(); i++) { stateArray[i] = states.get(i).intValue(); } ((StateListDrawable) drawable).xmlvmAddState(stateArray, d); } Drawable getDrawable() { return drawable; } } class XMLResourceParser extends DefaultHandler { private Context context; private String prefix; private Map<String, Integer> nameToIdMap; private Map<Integer, Object> resourceMap; private Integer currentId; private StringBuffer currentCDATA; private ArrayList<String> currentStringArrayValue; private DisplayMetrics metrics; public XMLResourceParser(Context context, Map<String, Integer> nameToIdMap, Map<Integer, Object> resourceMap) { this.context = context; this.nameToIdMap = nameToIdMap; this.resourceMap = resourceMap; currentCDATA = null; // TODO: Fix this // Should be retrieved using // Activity.getWindowManager().getDefaulDisplay // Works as long as Display implementation remains unchanged metrics = new DisplayMetrics(); new Display().getMetrics(metrics); } @Override public void startPrefixMapping(String prefix, String namespaceURI) throws SAXException { if (namespaceURI.equals("http://schemas.android.com/apk/res/android")) { this.prefix = prefix + ":"; } } @Override public void startElement(String namespaceURI, String elementName, String qualifiedName, Attributes attributes) throws SAXException { AttributeSet attrs = new ResourceAttributes(context, prefix, attributes); if (qualifiedName.equals("string")) { String name = attrs.getAttributeValue("", "name"); currentId = nameToIdMap.get("string/" + name); currentCDATA = new StringBuffer(); } else if (qualifiedName.equals("string-array")) { String name = attrs.getAttributeValue("", "name"); currentId = nameToIdMap.get("array/" + name); currentStringArrayValue = new ArrayList<String>(); } else if (qualifiedName.equals("dimen")) { String name = attrs.getAttributeValue("", "name"); // TODO that is the prefix for dimen? currentId = nameToIdMap.get("dimen/" + name); currentCDATA = new StringBuffer(); } else if (qualifiedName.equals("item")) { currentCDATA = new StringBuffer(); } } @Override public void endElement(String namespaceURI, String elementName, String qualifiedName) throws SAXException { if (!resourceMap.containsKey(currentId)) { if (qualifiedName.equals("dimen")) { Float f = new Float(Dimension.resolveDimension(context, currentCDATA.toString(), metrics)); resourceMap.put(currentId, f); } else if (qualifiedName.equals("string")) { resourceMap.put(currentId, currentCDATA.toString()); currentCDATA = null; } else if (qualifiedName.equals("string-array")) { // TODO should use ArrayList.toString() but the ObjC wrapper // don't // support it String[] stringArray = new String[currentStringArrayValue.size()]; int i = 0; for (String s : currentStringArrayValue) { stringArray[i++] = s; } resourceMap.put(currentId, stringArray); currentStringArrayValue = null; } else if (qualifiedName.equals("item")) { // Inside string-array if (currentStringArrayValue != null) { currentStringArrayValue.add(currentCDATA.toString()); currentCDATA = null; } } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { if (currentCDATA != null) { currentCDATA.append(new String(ch, start, length)); } } } public class ResourceParser { public static Drawable parseDrawable(Context context, String fileName) { String resourceName = getResourceName(fileName); String resourceDir = getResourceDirectory(fileName); String filePath = CommonDeviceAPIFinder.instance().getFileSystem().getPathForResource(resourceName, "xml", resourceDir); DrawableParser delegate = new DrawableParser(context); SAXParser xmlParser = createParser(context); try { xmlParser.parse(new FileInputStream(filePath), delegate); } catch(Exception e) { throw new InflateException("Unable to inflate drawable resource: " + fileName + ".xml"); } return delegate.getDrawable(); } public static void parse(Context context, String fileName, Map<String, Integer> nameToIdMap, Map<Integer, Object> resourceMap) { String resourceName = getResourceName(fileName); String resourceDir = getResourceDirectory(fileName); String filePath = CommonDeviceAPIFinder.instance().getFileSystem().getPathForResource(resourceName, "xml", resourceDir); XMLResourceParser delegate = new XMLResourceParser(context, nameToIdMap, resourceMap); SAXParser xmlParser = createParser(context); try { xmlParser.parse(new FileInputStream(filePath), delegate); } catch(Exception e) { throw new InflateException("Unable to inflate string resources: " + fileName + ".xml"); } } private static SAXParser createParser(Context context) { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); SAXParser saxParser = null; try { saxParser = factory.newSAXParser(); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } return saxParser; } private static String getResourceName(String resourcePath) { int i = resourcePath.lastIndexOf('/'); return i >= 0 ? resourcePath.substring(i + 1) : resourcePath; } private static String getResourceDirectory(String resourcePath) { int i = resourcePath.lastIndexOf('/'); return i >= 0 ? resourcePath.substring(0, i) : null; } }