/*
* Copyright 2008 Android4ME
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package parser.axml.res;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* @author Dmitry Skiba
* <p/>
* Parser for Android's binary xml files (axml).
* <p/>
* Parser follows XmlPullParser interface, but does not implement it.
* You can implement XmlPullParser ontop of this class and contribute
* it to Android4ME project. See http://code.google.com/p/android4me
* <p/>
* TODO:
* - clarify interface methods,
* not all behavior from XmlPullParser is supported
* - add more sanity checks
* - understand ? values
*/
public class AXMLParser {
public static final int
CHUNK_AXML_FILE = 0x00080003,
CHUNK_RESOURCEIDS = 0x00080180,
CHUNK_XML_FIRST = 0x00100100,
CHUNK_XML_START_NAMESPACE = 0x00100100,
CHUNK_XML_END_NAMESPACE = 0x00100101,
CHUNK_XML_START_TAG = 0x00100102,
CHUNK_XML_END_TAG = 0x00100103,
CHUNK_XML_TEXT = 0x00100104,
CHUNK_XML_LAST = 0x00100104;
private IntReader m_reader;
@SuppressWarnings("unused")
private InputStream m_stream;
private int m_offset;
private int m_event = 0;
private boolean m_started;
private StringBlock m_strings;
private int[] m_resourceIDs;
private int m_tagType;
private int m_tagSourceLine;
private int m_tagName;
private TagAttribute[] m_tagAttributes;
private Map<Integer, Integer> m_namespaces = new HashMap<Integer, Integer>();
///////////////////////////////////////////// implementation
public AXMLParser(InputStream stream) throws IOException {
m_reader = new IntReader(stream, false);
m_offset = 0;
m_started = false;
start();
}
// See next() in XmlPullParser.
public int next() throws IOException {
m_tagType = m_reader.readInt();/*other 3 bytes?*/
if (m_tagType == CHUNK_RESOURCEIDS) {
int chunkSize = m_reader.readInt();
if (chunkSize < 8 || (chunkSize % 4) != 0) {
throw new IOException("Invalid resource ids size (" + chunkSize + ").");
}
m_resourceIDs = m_reader.readIntArray(chunkSize / 4 - 2);
return next();
}
if (m_tagType < CHUNK_XML_FIRST || m_tagType > CHUNK_XML_LAST) {
throw new IOException("Invalid chunk type (" + m_tagType + ").");
}
/*common header*/
/*some source length*/
m_reader.readInt();
m_tagSourceLine = m_reader.readInt();
/*0xFFFFFFFF*/
m_reader.readInt();
m_tagName = -1;
m_tagAttributes = null;
switch (m_tagType) {
case CHUNK_XML_START_NAMESPACE: {
int prefix = m_reader.readInt();
int uri = m_reader.readInt();
m_namespaces.put(uri, prefix);
m_event++;
if (m_event == 1)
return 0;
return next();
}
case CHUNK_XML_START_TAG: {
/*0xFFFFFFFF*/
m_reader.readInt();
m_tagName = m_reader.readInt();
/*flags?*/
m_reader.readInt();
int attributeCount = m_reader.readInt();
/*?*/
m_reader.readInt();
m_tagAttributes = new TagAttribute[attributeCount];
for (int i = 0; i != attributeCount; ++i) {
TagAttribute attribute = new TagAttribute();
attribute.namespace = m_reader.readInt();
attribute.name = m_reader.readInt();
attribute.valueString = m_reader.readInt();
attribute.valueType = (m_reader.readInt() >>> 24);/*other 3 bytes?*/
attribute.value = m_reader.readInt();
m_tagAttributes[i] = attribute;
}
break;
}
case CHUNK_XML_END_TAG: {
/*0xFFFFFFFF*/
m_reader.readInt();
m_tagName = m_reader.readInt();
break;
}
case CHUNK_XML_TEXT: {
m_tagName = m_reader.readInt();
/*?*/
m_reader.readInt();
/*?*/
m_reader.readInt();
break;
}
case CHUNK_XML_END_NAMESPACE: {
/*namespace?*/
m_reader.readInt();
/*name?*/
m_reader.readInt();
m_event--;
if (m_event == 0)
return 1;
else
return next();
}
default: {
throw new IOException("Invalid tag type (" + m_tagType + ").");
}
}
return m_tagType;
}
// See getEventType() in XmlPullParser.
public int getEventType() {
return m_tagType;
}
// private final String readString() throws IOException {
// int length=readShort();
// StringBuilder builder=new StringBuilder(length);
// for (int i=0;i!=length;++i) {
// builder.append((char)readShort());
// }
// readShort();
// return builder.toString();
// }
/////////////////////////////////// data
// See getName() in XmlPullParser.
public String getName() {
if (m_tagName == -1) {
return null;
}
return m_strings.getString(m_tagName);
}
// See getLineNumber() in XmlPullParser.
public int getLineNumber() {
return m_tagSourceLine;
}
// See getAttributeCount() in XmlPullParser.
public int getAttributeCount() {
if (m_tagAttributes == null) {
return -1;
}
return m_tagAttributes.length;
}
// See getAttributeNamespace() in XmlPullParser.
public String getAttributeNamespace(int index) {
return m_strings.getString(getAttribute(index).namespace);
}
public String getAttributePrefix(int index) {
int uri = getAttribute(index).namespace;
Integer prefix = m_namespaces.get(uri);
if (prefix == null) {
return "";
}
return m_strings.getString(prefix);
}
// See getAttributeName() in XmlPullParser.
public String getAttributeName(int index) {
return m_strings.getString(getAttribute(index).name);
}
// Returns resource ID for attribute name.
public int getAttributeNameResourceID(int index) {
int resourceIndex = getAttribute(index).name;
if (m_resourceIDs == null ||
resourceIndex < 0 || resourceIndex >= m_resourceIDs.length) {
return 0;
}
return m_resourceIDs[resourceIndex];
}
// See TypedValue.TYPE_ values.
public int getAttributeValueType(int index) {
return getAttribute(index).valueType;
}
// Returns string value if attribute type is TypedValue.TYPE_STRING,
// or empty string otherwise.
public String getAttributeValueString(int index) {
return m_strings.getString(getAttribute(index).valueString);
}
// Returns integer value for attribute.
// Value interpretation is based on type.
// For TypedValue.TYPE_STRING meaning is unknown.
public int getAttributeValue(int index) {
return getAttribute(index).value;
}
private void start() throws IOException {
if (m_started) {
return;
}
m_started = true;
int signature = m_reader.readInt();
if (signature != CHUNK_AXML_FILE) {
throw new IOException("Invalid signature (" + signature + ").");
}
/*chunk size*/
m_reader.skipInt();
m_strings = new StringBlock(m_reader);
// Align to 4byte boundary
m_reader.readInt(m_offset % 4);
/*chunk signature*/
m_reader.readInt();
int resourceIDLength = m_reader.readInt() - 8;
if (resourceIDLength < 0) {
throw new IOException("Invalid resource id length (" + resourceIDLength + ").");
}
m_resourceIDs = new int[resourceIDLength / 4];
for (int i = 0; i != m_resourceIDs.length; ++i) {
m_resourceIDs[i] = m_reader.readInt();
}
}
private TagAttribute getAttribute(int index) {
if (m_tagAttributes == null) {
throw new IndexOutOfBoundsException("Attributes are not available.");
}
if (index >= m_tagAttributes.length) {
throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ").");
}
return m_tagAttributes[index];
}
private static final class TagAttribute {
public int namespace;
public int name;
public int valueString;
public int valueType;
public int value;
}
}