/*
* 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.UnsupportedEncodingException;
/**
* @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 StringBlock (IntReader reader) throws IOException {
int resType = reader.readInt();
if(resType != CHUNK_TYPE)
throw new IOException("Expected chunk of type 0x"+Integer.toHexString(CHUNK_TYPE)+
", read 0x"+Integer.toHexString(resType)+".");
int chunkSize=reader.readInt();
int stringCount=reader.readInt();
int styleOffsetCount=reader.readInt();
int flags = reader.readInt();
int stringsOffset=reader.readInt();
int stylesOffset=reader.readInt();
m_isUTF8 = (flags & UTF8_FLAG) != 0;
m_stringOffsets=reader.readIntArray(stringCount);
if (styleOffsetCount!=0) {
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+").");
}
m_strings=reader.readByteArray(size);
}
if (stylesOffset!=0) {
int size=(chunkSize-stylesOffset);
if ((size%4)!=0) {
throw new IOException("Style data size is not multiple of 4 ("+size+").");
}
m_styles=reader.readIntArray(size/4);
}
}
/**
* 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.
* Returns null if index is invalid or object was not initialized.
*/
public String getString(int index) {
if (index < 0
|| m_stringOffsets == null
|| index >= m_stringOffsets.length)
{
return null;
}
int offset = m_stringOffsets[index];
int length;
if(m_isUTF8)
{
offset += getVarint(m_strings, offset)[1];
int[] varint = getVarint(m_strings, offset);
offset += varint[1];
length = varint[0];
byte[] result = new byte[length];
System.arraycopy(m_strings, offset, result, 0, length);
try {
return new String(result,"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}else{
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();
}
return null;
}
private final int[] getVarint(byte[] array, int offset) {
int val = array[offset];
boolean more = (val & 0x80) != 0;
val &= 0x7f;
if (! more) {
return new int[]{val, 1};
} else {
return new int[]{val << 8 | array[offset + 1] & 0xff, 2};
}
}
/**
* 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<start) {
html.append(raw,offset,start);
offset=start;
}
if (i==-1) {
break;
}
html.append('<');
html.append(getString(style[i]));
html.append('>');
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
/**
* 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<m_styles.length;++i) {
if (m_styles[i]==-1) {
break;
}
count+=1;
}
if (count==0 || (count%3)!=0) {
return null;
}
style=new int[count];
for (int i=offset,j=0;i<m_styles.length;) {
if (m_styles[i]==-1) {
break;
}
style[j++]=m_styles[i++];
}
return style;
}
private final int getShort(byte[] array, int offset) {
return (array[offset + 1] & 0xff) << 8 | array[offset] & 0xff;
}
private int[] m_stringOffsets;
private byte[] m_strings;
private int[] m_styleOffsets;
private int[] m_styles;
private boolean m_isUTF8;
private static final int UTF8_FLAG = 0x100;
private static final int CHUNK_TYPE=0x001C0001;
}