diff --git a/src/brut/androlib/res/decoder/AXmlResourceParser.java b/src/brut/androlib/res/decoder/AXmlResourceParser.java new file mode 100644 index 00000000..9b25ba5d --- /dev/null +++ b/src/brut/androlib/res/decoder/AXmlResourceParser.java @@ -0,0 +1,927 @@ +/* + * 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 brut.androlib.res.decoder; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import org.xmlpull.v1.XmlPullParserException; +import android.util.TypedValue; + +/** + * @author Dmitry Skiba + * + * Binary xml files parser. + * + * Parser has only two states: + * (1) Operational state, which parser obtains after first successful call + * to next() and retains until open(), close(), or failed call to next(). + * (2) Closed state, which parser obtains after open(), close(), or failed + * call to next(). In this state methods return invalid values or throw exceptions. + * + * TODO: + * * check all methods in closed state + * + */ +public class AXmlResourceParser implements XmlResourceParser { + + public AXmlResourceParser() { + resetEventInfo(); + } + + public void open(InputStream stream) { + close(); + if (stream!=null) { + m_reader=new IntReader(stream,false); + } + } + + public void close() { + if (!m_operational) { + return; + } + m_operational=false; + m_reader.close(); + m_reader=null; + m_strings=null; + m_resourceIDs=null; + m_namespaces.reset(); + resetEventInfo(); + } + + /////////////////////////////////// iteration + + public int next() throws XmlPullParserException,IOException { + if (m_reader==null) { + throw new XmlPullParserException("Parser is not opened.",this,null); + } + try { + doNext(); + return m_event; + } + catch (IOException e) { + close(); + throw e; + } + } + + public int nextToken() throws XmlPullParserException,IOException { + return next(); + } + + public int nextTag() throws XmlPullParserException,IOException { + int eventType=next(); + if (eventType==TEXT && isWhitespace()) { + eventType=next(); + } + if (eventType!=START_TAG && eventType!=END_TAG) { + throw new XmlPullParserException("Expected start or end tag.",this,null); + } + return eventType; + } + + public String nextText() throws XmlPullParserException,IOException { + if(getEventType()!=START_TAG) { + throw new XmlPullParserException("Parser must be on START_TAG to read next text.",this,null); + } + int eventType=next(); + if (eventType==TEXT) { + String result=getText(); + eventType=next(); + if (eventType!=END_TAG) { + throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.",this,null); + } + return result; + } else if (eventType==END_TAG) { + return ""; + } else { + throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.",this,null); + } + } + + public void require(int type,String namespace,String name) throws XmlPullParserException,IOException { + if (type!=getEventType() || + (namespace!=null && !namespace.equals(getNamespace())) || + (name!=null && !name.equals(getName()))) + { + throw new XmlPullParserException(TYPES[type]+" is expected.",this,null); + } + } + + public int getDepth() { + return m_namespaces.getDepth()-1; + } + + public int getEventType() throws XmlPullParserException { + return m_event; + } + + public int getLineNumber() { + return m_lineNumber; + } + + public String getName() { + if (m_name==-1 || (m_event!=START_TAG && m_event!=END_TAG)) { + return null; + } + return m_strings.getString(m_name); + } + + public String getText() { + if (m_name==-1 || m_event!=TEXT) { + return null; + } + return m_strings.getString(m_name); + } + + public char[] getTextCharacters(int[] holderForStartAndLength) { + String text=getText(); + if (text==null) { + return null; + } + holderForStartAndLength[0]=0; + holderForStartAndLength[1]=text.length(); + char[] chars=new char[text.length()]; + text.getChars(0,text.length(),chars,0); + return chars; + } + + public String getNamespace() { + return m_strings.getString(m_namespaceUri); + } + + public String getPrefix() { + int prefix=m_namespaces.findPrefix(m_namespaceUri); + return m_strings.getString(prefix); + } + + public String getPositionDescription() { + return "XML line #"+getLineNumber(); + } + + public int getNamespaceCount(int depth) throws XmlPullParserException { + return m_namespaces.getAccumulatedCount(depth); + } + + public String getNamespacePrefix(int pos) throws XmlPullParserException { + int prefix=m_namespaces.getPrefix(pos); + return m_strings.getString(prefix); + } + + public String getNamespaceUri(int pos) throws XmlPullParserException { + int uri=m_namespaces.getUri(pos); + return m_strings.getString(uri); + } + + /////////////////////////////////// attributes + + public String getClassAttribute() { + if (m_classAttribute==-1) { + return null; + } + int offset=getAttributeOffset(m_classAttribute); + int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; + return m_strings.getString(value); + } + + public String getIdAttribute() { + if (m_idAttribute==-1) { + return null; + } + int offset=getAttributeOffset(m_idAttribute); + int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; + return m_strings.getString(value); + } + + public int getIdAttributeResourceValue(int defaultValue) { + if (m_idAttribute==-1) { + return defaultValue; + } + int offset=getAttributeOffset(m_idAttribute); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType!=TypedValue.TYPE_REFERENCE) { + return defaultValue; + } + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + + public int getStyleAttribute() { + if (m_styleAttribute==-1) { + return 0; + } + int offset=getAttributeOffset(m_styleAttribute); + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + + public int getAttributeCount() { + if (m_event!=START_TAG) { + return -1; + } + return m_attributes.length/ATTRIBUTE_LENGHT; + } + + public String getAttributeNamespace(int index) { + int offset=getAttributeOffset(index); + int namespace=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI]; + if (namespace==-1) { + return ""; + } + return m_strings.getString(namespace); + } + + public String getAttributePrefix(int index) { + int offset=getAttributeOffset(index); + int uri=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI]; + int prefix=m_namespaces.findPrefix(uri); + if (prefix==-1) { + return ""; + } + return m_strings.getString(prefix); + } + + public String getAttributeName(int index) { + int offset=getAttributeOffset(index); + int name=m_attributes[offset+ATTRIBUTE_IX_NAME]; + if (name==-1) { + return ""; + } + return m_strings.getString(name); + } + + public int getAttributeNameResource(int index) { + int offset=getAttributeOffset(index); + int name=m_attributes[offset+ATTRIBUTE_IX_NAME]; + if (m_resourceIDs==null || + name<0 || name>=m_resourceIDs.length) + { + return 0; + } + return m_resourceIDs[name]; + } + + public int getAttributeValueType(int index) { + int offset=getAttributeOffset(index); + return m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + } + + public int getAttributeValueData(int index) { + int offset=getAttributeOffset(index); + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + + public String getAttributeValue(int index) { + int offset=getAttributeOffset(index); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType==TypedValue.TYPE_STRING) { + int valueString=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; + return m_strings.getString(valueString); + } + int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + return "";//TypedValue.coerceToString(valueType,valueData); + } + + public boolean getAttributeBooleanValue(int index,boolean defaultValue) { + return getAttributeIntValue(index,defaultValue?1:0)!=0; + } + + public float getAttributeFloatValue(int index,float defaultValue) { + int offset=getAttributeOffset(index); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType==TypedValue.TYPE_FLOAT) { + int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + return Float.intBitsToFloat(valueData); + } + return defaultValue; + } + + public int getAttributeIntValue(int index,int defaultValue) { + int offset=getAttributeOffset(index); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType>=TypedValue.TYPE_FIRST_INT && + valueType<=TypedValue.TYPE_LAST_INT) + { + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + return defaultValue; + } + + public int getAttributeUnsignedIntValue(int index,int defaultValue) { + return getAttributeIntValue(index,defaultValue); + } + + public int getAttributeResourceValue(int index,int defaultValue) { + int offset=getAttributeOffset(index); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType==TypedValue.TYPE_REFERENCE) { + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + return defaultValue; + } + + public String getAttributeValue(String namespace,String attribute) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return null; + } + return getAttributeValue(index); + } + + public boolean getAttributeBooleanValue(String namespace,String attribute,boolean defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeBooleanValue(index,defaultValue); + } + + public float getAttributeFloatValue(String namespace,String attribute,float defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeFloatValue(index,defaultValue); + } + + public int getAttributeIntValue(String namespace,String attribute,int defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeIntValue(index,defaultValue); + } + + public int getAttributeUnsignedIntValue(String namespace,String attribute,int defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeUnsignedIntValue(index,defaultValue); + } + + public int getAttributeResourceValue(String namespace,String attribute,int defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeResourceValue(index,defaultValue); + } + + public int getAttributeListValue(int index,String[] options,int defaultValue) { + // TODO implement + return 0; + } + + public int getAttributeListValue(String namespace,String attribute,String[] options,int defaultValue) { + // TODO implement + return 0; + } + + public String getAttributeType(int index) { + return "CDATA"; + } + + public boolean isAttributeDefault(int index) { + return false; + } + + /////////////////////////////////// dummies + + public void setInput(InputStream stream,String inputEncoding) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + public void setInput(Reader reader) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public String getInputEncoding() { + return null; + } + + public int getColumnNumber() { + return -1; + } + + public boolean isEmptyElementTag() throws XmlPullParserException { + return false; + } + + public boolean isWhitespace() throws XmlPullParserException { + return false; + } + + public void defineEntityReplacementText(String entityName,String replacementText) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public String getNamespace(String prefix) { + throw new RuntimeException(E_NOT_SUPPORTED); + } + + public Object getProperty(String name) { + return null; + } + public void setProperty(String name,Object value) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public boolean getFeature(String feature) { + return false; + } + public void setFeature(String name,boolean value) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + ///////////////////////////////////////////// implementation + + /** + * Namespace stack, holds prefix+uri pairs, as well as + * depth information. + * All information is stored in one int[] array. + * Array consists of depth frames: + * Data=DepthFrame*; + * DepthFrame=Count+[Prefix+Uri]*+Count; + * Count='count of Prefix+Uri pairs'; + * Yes, count is stored twice, to enable bottom-up traversal. + * increaseDepth adds depth frame, decreaseDepth removes it. + * push/pop operations operate only in current depth frame. + * decreaseDepth removes any remaining (not pop'ed) namespace pairs. + * findXXX methods search all depth frames starting + * from the last namespace pair of current depth frame. + * All functions that operate with int, use -1 as 'invalid value'. + * + * !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !! + * + */ + private static final class NamespaceStack { + public NamespaceStack() { + m_data=new int[32]; + } + + public final void reset() { + m_dataLength=0; + m_count=0; + m_depth=0; + } + + public final int getTotalCount() { + return m_count; + } + + public final int getCurrentCount() { + if (m_dataLength==0) { + return 0; + } + int offset=m_dataLength-1; + return m_data[offset]; + } + + public final int getAccumulatedCount(int depth) { + if (m_dataLength==0 || depth<0) { + return 0; + } + if (depth>m_depth) { + depth=m_depth; + } + int accumulatedCount=0; + int offset=0; + for (;depth!=0;--depth) { + int count=m_data[offset]; + accumulatedCount+=count; + offset+=(2+count*2); + } + return accumulatedCount; + } + + public final void push(int prefix,int uri) { + if (m_depth==0) { + increaseDepth(); + } + ensureDataCapacity(2); + int offset=m_dataLength-1; + int count=m_data[offset]; + m_data[offset-1-count*2]=count+1; + m_data[offset]=prefix; + m_data[offset+1]=uri; + m_data[offset+2]=count+1; + m_dataLength+=2; + m_count+=1; + } + + public final boolean pop(int prefix,int uri) { + if (m_dataLength==0) { + return false; + } + int offset=m_dataLength-1; + int count=m_data[offset]; + for (int i=0,o=offset-2;i!=count;++i,o-=2) { + if (m_data[o]!=prefix || m_data[o+1]!=uri) { + continue; + } + count-=1; + if (i==0) { + m_data[o]=count; + o-=(1+count*2); + m_data[o]=count; + } else { + m_data[offset]=count; + offset-=(1+2+count*2); + m_data[offset]=count; + System.arraycopy( + m_data,o+2, + m_data,o, + m_dataLength-o); + } + m_dataLength-=2; + m_count-=1; + return true; + } + return false; + } + + public final boolean pop() { + if (m_dataLength==0) { + return false; + } + int offset=m_dataLength-1; + int count=m_data[offset]; + if (count==0) { + return false; + } + count-=1; + offset-=2; + m_data[offset]=count; + offset-=(1+count*2); + m_data[offset]=count; + m_dataLength-=2; + m_count-=1; + return true; + } + + public final int getPrefix(int index) { + return get(index,true); + } + + public final int getUri(int index) { + return get(index,false); + } + + public final int findPrefix(int uri) { + return find(uri,false); + } + + public final int findUri(int prefix) { + return find(prefix,true); + } + + public final int getDepth() { + return m_depth; + } + + public final void increaseDepth() { + ensureDataCapacity(2); + int offset=m_dataLength; + m_data[offset]=0; + m_data[offset+1]=0; + m_dataLength+=2; + m_depth+=1; + } + public final void decreaseDepth() { + if (m_dataLength==0) { + return; + } + int offset=m_dataLength-1; + int count=m_data[offset]; + if ((offset-1-count*2)==0) { + return; + } + m_dataLength-=2+count*2; + m_count-=count; + m_depth-=1; + } + + private void ensureDataCapacity(int capacity) { + int available=(m_data.length-m_dataLength); + if (available>capacity) { + return; + } + int newLength=(m_data.length+available)*2; + int[] newData=new int[newLength]; + System.arraycopy(m_data,0,newData,0,m_dataLength); + m_data=newData; + } + + private final int find(int prefixOrUri,boolean prefix) { + if (m_dataLength==0) { + return -1; + } + int offset=m_dataLength-1; + for (int i=m_depth;i!=0;--i) { + int count=m_data[offset]; + offset-=2; + for (;count!=0;--count) { + if (prefix) { + if (m_data[offset]==prefixOrUri) { + return m_data[offset+1]; + } + } else { + if (m_data[offset+1]==prefixOrUri) { + return m_data[offset]; + } + } + offset-=2; + } + } + return -1; + } + + private final int get(int index,boolean prefix) { + if (m_dataLength==0 || index<0) { + return -1; + } + int offset=0; + for (int i=m_depth;i!=0;--i) { + int count=m_data[offset]; + if (index>=count) { + index-=count; + offset+=(2+count*2); + continue; + } + offset+=(1+index*2); + if (!prefix) { + offset+=1; + } + return m_data[offset]; + } + return -1; + } + + private int[] m_data; + private int m_dataLength; + private int m_count; + private int m_depth; + } + + /////////////////////////////////// package-visible + +// final void fetchAttributes(int[] styleableIDs,TypedArray result) { +// result.resetIndices(); +// if (m_attributes==null || m_resourceIDs==null) { +// return; +// } +// boolean needStrings=false; +// for (int i=0,e=styleableIDs.length;i!=e;++i) { +// int id=styleableIDs[i]; +// for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) { +// int name=m_attributes[o+ATTRIBUTE_IX_NAME]; +// if (name>=m_resourceIDs.length || +// m_resourceIDs[name]!=id) +// { +// continue; +// } +// int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE]; +// int valueData; +// int assetCookie; +// if (valueType==TypedValue.TYPE_STRING) { +// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING]; +// assetCookie=-1; +// needStrings=true; +// } else { +// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA]; +// assetCookie=0; +// } +// result.addValue(i,valueType,valueData,assetCookie,id,0); +// } +// } +// if (needStrings) { +// result.setStrings(m_strings); +// } +// } + + final StringBlock getStrings() { + return m_strings; + } + + /////////////////////////////////// + + private final int getAttributeOffset(int index) { + if (m_event!=START_TAG) { + throw new IndexOutOfBoundsException("Current event is not START_TAG."); + } + int offset=index*5; + if (offset>=m_attributes.length) { + throw new IndexOutOfBoundsException("Invalid attribute index ("+index+")."); + } + return offset; + } + + private final int findAttribute(String namespace,String attribute) { + if (m_strings==null || attribute==null) { + return -1; + } + int name=m_strings.find(attribute); + if (name==-1) { + return -1; + } + int uri=(namespace!=null)? + m_strings.find(namespace): + -1; + for (int o=0;o!=m_attributes.length;++o) { + if (name==m_attributes[o+ATTRIBUTE_IX_NAME] && + (uri==-1 || uri==m_attributes[o+ATTRIBUTE_IX_NAMESPACE_URI])) + { + return o/ATTRIBUTE_LENGHT; + } + } + return -1; + } + + private final void resetEventInfo() { + m_event=-1; + m_lineNumber=-1; + m_name=-1; + m_namespaceUri=-1; + m_attributes=null; + m_idAttribute=-1; + m_classAttribute=-1; + m_styleAttribute=-1; + } + + private final void doNext() throws IOException { + // Delayed initialization. + if (m_strings==null) { + ChunkUtil.readCheckType(m_reader,CHUNK_AXML_FILE); + /*chunkSize*/m_reader.skipInt(); + m_strings=StringBlock.read(m_reader); + m_namespaces.increaseDepth(); + m_operational=true; + } + + if (m_event==END_DOCUMENT) { + return; + } + + int event=m_event; + resetEventInfo(); + + while (true) { + if (m_decreaseDepth) { + m_decreaseDepth=false; + m_namespaces.decreaseDepth(); + } + + // Fake END_DOCUMENT event. + if (event==END_TAG && + m_namespaces.getDepth()==1 && + m_namespaces.getCurrentCount()==0) + { + m_event=END_DOCUMENT; + break; + } + + int chunkType; + if (event==START_DOCUMENT) { + // Fake event, see CHUNK_XML_START_TAG handler. + chunkType=CHUNK_XML_START_TAG; + } else { + chunkType=m_reader.readInt(); + } + + if (chunkType==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); + continue; + } + + if (chunkTypeCHUNK_XML_LAST) { + throw new IOException("Invalid chunk type ("+chunkType+")."); + } + + // Fake START_DOCUMENT event. + if (chunkType==CHUNK_XML_START_TAG && event==-1) { + m_event=START_DOCUMENT; + break; + } + + // Common header. + /*chunkSize*/m_reader.skipInt(); + int lineNumber=m_reader.readInt(); + /*0xFFFFFFFF*/m_reader.skipInt(); + + if (chunkType==CHUNK_XML_START_NAMESPACE || + chunkType==CHUNK_XML_END_NAMESPACE) + { + if (chunkType==CHUNK_XML_START_NAMESPACE) { + int prefix=m_reader.readInt(); + int uri=m_reader.readInt(); + m_namespaces.push(prefix,uri); + } else { + /*prefix*/m_reader.skipInt(); + /*uri*/m_reader.skipInt(); + m_namespaces.pop(); + } + continue; + } + + m_lineNumber=lineNumber; + + if (chunkType==CHUNK_XML_START_TAG) { + m_namespaceUri=m_reader.readInt(); + m_name=m_reader.readInt(); + /*flags?*/m_reader.skipInt(); + int attributeCount=m_reader.readInt(); + m_idAttribute=(attributeCount>>>16)-1; + attributeCount&=0xFFFF; + m_classAttribute=m_reader.readInt(); + m_styleAttribute=(m_classAttribute>>>16)-1; + m_classAttribute=(m_classAttribute & 0xFFFF)-1; + m_attributes=m_reader.readIntArray(attributeCount*ATTRIBUTE_LENGHT); + for (int i=ATTRIBUTE_IX_VALUE_TYPE;i>>24); + i+=ATTRIBUTE_LENGHT; + } + m_namespaces.increaseDepth(); + m_event=START_TAG; + break; + } + + if (chunkType==CHUNK_XML_END_TAG) { + m_namespaceUri=m_reader.readInt(); + m_name=m_reader.readInt(); + m_event=END_TAG; + m_decreaseDepth=true; + break; + } + + if (chunkType==CHUNK_XML_TEXT) { + m_name=m_reader.readInt(); + /*?*/m_reader.skipInt(); + /*?*/m_reader.skipInt(); + m_event=TEXT; + break; + } + } + } + + /////////////////////////////////// data + + /* + * All values are essentially indices, e.g. m_name is + * an index of name in m_strings. + */ + + private IntReader m_reader; + private boolean m_operational=false; + + private StringBlock m_strings; + private int[] m_resourceIDs; + private NamespaceStack m_namespaces=new NamespaceStack(); + + private boolean m_decreaseDepth; + + private int m_event; + private int m_lineNumber; + private int m_name; + private int m_namespaceUri; + private int[] m_attributes; + private int m_idAttribute; + private int m_classAttribute; + private int m_styleAttribute; + + private static final String + E_NOT_SUPPORTED ="Method is not supported."; + + private static final int + ATTRIBUTE_IX_NAMESPACE_URI =0, + ATTRIBUTE_IX_NAME =1, + ATTRIBUTE_IX_VALUE_STRING =2, + ATTRIBUTE_IX_VALUE_TYPE =3, + ATTRIBUTE_IX_VALUE_DATA =4, + ATTRIBUTE_LENGHT =5; + + private 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; +} diff --git a/src/brut/androlib/res/decoder/StringBlock.java b/src/brut/androlib/res/decoder/StringBlock.java new file mode 100644 index 00000000..20206640 --- /dev/null +++ b/src/brut/androlib/res/decoder/StringBlock.java @@ -0,0 +1,245 @@ +/* + * 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 brut.androlib.res.decoder; + +import java.io.IOException; + +/** + * @author Dmitry Skiba + * + * Block of strings, used in binary xml and arsc. + * + * TODO: + * - implement get() + * + */ +public class StringBlock { + + /** + * Reads whole (including chunk type) string block from stream. + * Stream must be at the chunk type. + */ + public static StringBlock read(IntReader reader) throws IOException { + ChunkUtil.readCheckType(reader,CHUNK_TYPE); + int chunkSize=reader.readInt(); + int stringCount=reader.readInt(); + int styleOffsetCount=reader.readInt(); + /*?*/reader.readInt(); + int stringsOffset=reader.readInt(); + int stylesOffset=reader.readInt(); + + StringBlock block=new StringBlock(); + block.m_stringOffsets=reader.readIntArray(stringCount); + if (styleOffsetCount!=0) { + block.m_styleOffsets=reader.readIntArray(styleOffsetCount); + } + { + int size=((stylesOffset==0)?chunkSize:stylesOffset)-stringsOffset; + if ((size%4)!=0) { + throw new IOException("String data size is not multiple of 4 ("+size+")."); + } + block.m_strings=reader.readIntArray(size/4); + } + if (stylesOffset!=0) { + int size=(chunkSize-stylesOffset); + if ((size%4)!=0) { + throw new IOException("Style data size is not multiple of 4 ("+size+")."); + } + block.m_styles=reader.readIntArray(size/4); + } + + return block; + } + + /** + * Returns number of strings in block. + */ + public int getCount() { + return m_stringOffsets!=null? + m_stringOffsets.length: + 0; + } + + /** + * Returns raw string (without any styling information) at specified index. + */ + public String getString(int index) { + if (index<0 || + m_stringOffsets==null || + index>=m_stringOffsets.length) + { + return null; + } + int offset=m_stringOffsets[index]; + int length=getShort(m_strings,offset); + StringBuilder result=new StringBuilder(length); + for (;length!=0;length-=1) { + offset+=2; + result.append((char)getShort(m_strings,offset)); + } + return result.toString(); + } + + /** + * Not yet implemented. + * + * Returns string with style information (if any). + */ + public CharSequence get(int index) { + return getString(index); + } + + /** + * Returns string with style tags (html-like). + */ + public String getHTML(int index) { + String raw=getString(index); + if (raw==null) { + return raw; + } + int[] style=getStyle(index); + if (style==null) { + return raw; + } + StringBuilder html=new StringBuilder(raw.length()+32); + int offset=0; + while (true) { + int i=-1; + for (int j=0;j!=style.length;j+=3) { + if (style[j+1]==-1) { + continue; + } + if (i==-1 || style[i+1]>style[j+1]) { + i=j; + } + } + int start=((i!=-1)?style[i+1]:raw.length()); + for (int j=0;j!=style.length;j+=3) { + int end=style[j+2]; + if (end==-1 || end>=start) { + continue; + } + if (offset<=end) { + html.append(raw,offset,end+1); + offset=end+1; + } + style[j+2]=-1; + html.append('<'); + html.append('/'); + html.append(getString(style[j])); + html.append('>'); + } + if (offset'); + style[i+1]=-1; + } + return html.toString(); + } + + /** + * Finds index of the string. + * Returns -1 if the string was not found. + */ + public int find(String string) { + if (string==null) { + return -1; + } + for (int i=0;i!=m_stringOffsets.length;++i) { + int offset=m_stringOffsets[i]; + int length=getShort(m_strings,offset); + if (length!=string.length()) { + continue; + } + int j=0; + for (;j!=length;++j) { + offset+=2; + if (string.charAt(j)!=getShort(m_strings,offset)) { + break; + } + } + if (j==length) { + return i; + } + } + return -1; + } + + ///////////////////////////////////////////// implementation + + private StringBlock() { + } + + /** + * Returns style information - array of int triplets, + * where in each triplet: + * * first int is index of tag name ('b','i', etc.) + * * second int is tag start index in string + * * third int is tag end index in string + */ + private int[] getStyle(int index) { + if (m_styleOffsets==null || m_styles==null || + index>=m_styleOffsets.length) + { + return null; + } + int offset=m_styleOffsets[index]/4; + int style[]; + { + int count=0; + for (int i=offset;i>> 16); + } + } + + private int[] m_stringOffsets; + private int[] m_strings; + private int[] m_styleOffsets; + private int[] m_styles; + + private static final int CHUNK_TYPE=0x001C0001; +}