ARSCLib/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlAttribute.java
2023-02-18 11:52:30 -05:00

437 lines
14 KiB
Java
Executable File

/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.arsc.chunk.xml;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.group.EntryGroup;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.item.*;
import com.reandroid.arsc.pool.ResXmlStringPool;
import com.reandroid.arsc.pool.StringPool;
import com.reandroid.arsc.value.ValueItem;
import com.reandroid.arsc.value.ValueType;
import com.reandroid.common.EntryStore;
import com.reandroid.json.JSONObject;
import com.reandroid.xml.XMLAttribute;
import com.reandroid.xml.XMLException;
import java.io.IOException;
public class ResXmlAttribute extends ValueItem implements Comparable<ResXmlAttribute>{
private ReferenceItem mNSReference;
private ReferenceItem mNameReference;
private ReferenceItem mNameIdReference;
private ReferenceItem mValueStringReference;
public ResXmlAttribute(int attributeUnitSize) {
super(attributeUnitSize, OFFSET_SIZE);
byte[] bts = getBytesInternal();
putInteger(bts, OFFSET_NS, -1);
putInteger(bts, OFFSET_NAME, -1);
putInteger(bts, OFFSET_STRING, -1);
}
public ResXmlAttribute() {
this(20);
}
public String getUri(){
return getString(getNamespaceReference());
}
public String getFullName(){
String name=getName();
if(name==null){
return null;
}
String prefix=getNamePrefix();
if(prefix==null){
return name;
}
return prefix+":"+name;
}
public String getName(){
return getString(getNameReference());
}
public String getNamePrefix(){
ResXmlElement xmlElement=getParentResXmlElement();
if(xmlElement==null){
return null;
}
ResXmlStartNamespace startNamespace=xmlElement.getStartNamespaceByUriRef(getNamespaceReference());
if(startNamespace==null){
return null;
}
return startNamespace.getPrefix();
}
@Deprecated
public String getValueString(){
return getString(getValueStringReference());
}
public int getNameResourceID(){
ResXmlID xmlID = getResXmlID();
if(xmlID != null){
return xmlID.get();
}
return 0;
}
public void setNameResourceID(int resourceId){
ResXmlIDMap xmlIDMap=getResXmlIDMap();
if(xmlIDMap==null){
return;
}
ResXmlID xmlID = xmlIDMap.getOrCreate(resourceId);
setNameReference(xmlID.getIndex());
}
public void setName(String name, int resourceId){
unlink(mNameReference);
unLinkNameId(getResXmlID());
ResXmlString xmlString = getOrCreateAttributeName(name, resourceId);
if(xmlString==null){
return;
}
setNameReference(xmlString.getIndex());
mNameReference = link(OFFSET_NAME);
linkNameId();
}
private ResXmlString getOrCreateAttributeName(String name, int resourceId){
StringPool<?> stringPool = getStringPool();
if(stringPool==null){
return null;
}
ResXmlStringPool resXmlStringPool = (ResXmlStringPool) stringPool;
return resXmlStringPool.getOrCreateAttribute(resourceId, name);
}
public ResXmlElement getParentResXmlElement(){
return getParent(ResXmlElement.class);
}
public int getAttributesUnitSize(){
return OFFSET_SIZE + super.getSize();
}
public void setAttributesUnitSize(int size){
int eight = size - OFFSET_SIZE;
super.setSize(eight);
}
private String getString(int ref){
if(ref<0){
return null;
}
StringPool<?> stringPool = getStringPool();
if(stringPool == null){
return null;
}
StringItem stringItem = stringPool.get(ref);
if(stringItem == null){
return null;
}
return stringItem.getHtml();
}
private ResXmlID getResXmlID(){
ResXmlIDMap xmlIDMap = getResXmlIDMap();
if(xmlIDMap == null){
return null;
}
return xmlIDMap.getResXmlIDArray().get(getNameReference());
}
private ResXmlIDMap getResXmlIDMap(){
ResXmlElement xmlElement=getParentResXmlElement();
if(xmlElement!=null){
return xmlElement.getResXmlIDMap();
}
return null;
}
int getNamespaceReference(){
return getInteger(getBytesInternal(), OFFSET_NS);
}
public void setNamespaceReference(int ref){
if(ref == getNamespaceReference()){
return;
}
unlink(mNSReference);
putInteger(getBytesInternal(), OFFSET_NS, ref);
mNSReference = link(OFFSET_NS);
}
int getNameReference(){
return getInteger(getBytesInternal(), OFFSET_NAME);
}
void setNameReference(int ref){
if(ref == getNameReference()){
return;
}
unLinkNameId(getResXmlID());
unlink(mNameReference);
putInteger(getBytesInternal(), OFFSET_NAME, ref);
mNameReference = link(OFFSET_NAME);
linkNameId();
}
int getValueStringReference(){
return getInteger(getBytesInternal(), OFFSET_STRING);
}
void setValueStringReference(int ref){
if(ref == getValueStringReference()){
return;
}
unlink(mValueStringReference);
putInteger(getBytesInternal(), OFFSET_STRING, ref);
mValueStringReference = link(OFFSET_STRING);
}
@Override
public void onReadBytes(BlockReader reader) throws IOException {
super.onReadBytes(reader);
linkAll();
}
@Override
public void onRemoved(){
super.onRemoved();
unlinkAll();
}
@Override
protected void onUnlinkDataString(StringItem stringItem){
if(!stringItem.hasReference()){
stringItem.set("");
}
}
@Override
protected void onDataChanged(){
if(getValueType()==ValueType.STRING){
setValueStringReference(getData());
}else {
setValueStringReference(-1);
}
}
private void linkNameId(){
ResXmlID xmlID = getResXmlID();
if(xmlID==null){
return;
}
unLinkNameId(xmlID);
ReferenceItem referenceItem = new ReferenceBlock<>(this, OFFSET_NAME);
xmlID.addReference(referenceItem);
mNameIdReference = referenceItem;
}
private void unLinkNameId(ResXmlID xmlID){
ReferenceItem referenceItem = mNameIdReference;
if(referenceItem==null || xmlID == null){
return;
}
xmlID.removeReference(referenceItem);
mNameIdReference = null;
}
private void linkAll(){
unlink(mNSReference);
mNSReference = link(OFFSET_NS);
unlink(mNameReference);
mNameReference = link(OFFSET_NAME);
unlink(mValueStringReference);
mValueStringReference = link(OFFSET_STRING);
linkNameId();
}
private void unlinkAll(){
unlink(mNSReference);
unlink(mNameReference);
unlink(mValueStringReference);
mNSReference = null;
mNameReference = null;
mValueStringReference = null;
unLinkNameId(getResXmlID());
}
private ReferenceItem link(int offset){
if(offset<0){
return null;
}
StringPool<?> stringPool = getStringPool();
if(stringPool == null){
return null;
}
int ref = getInteger(getBytesInternal(), offset);
StringItem stringItem = stringPool.get(ref);
if(stringItem == null){
return null;
}
ReferenceItem referenceItem = new ReferenceBlock<>(this, offset);
stringItem.addReference(referenceItem);
return referenceItem;
}
private void unlink(ReferenceItem reference){
if(reference == null){
return;
}
StringPool<?> stringPool = getStringPool();
if(stringPool==null){
return;
}
StringItem stringItem = stringPool.get(reference.get());
if(stringItem==null){
return;
}
stringItem.removeReference(reference);
if(!stringItem.hasReference()){
stringItem.set("");
}
}
@Override
public int compareTo(ResXmlAttribute other) {
int id1=getNameResourceID();
int id2=other.getNameResourceID();
if(id1==0 && id2!=0){
return 1;
}
if(id2==0 && id1!=0){
return -1;
}
if(id1!=0){
return Integer.compare(id1, id2);
}
String name1=getName();
if(name1==null){
name1="";
}
String name2=other.getName();
if(name2==null){
name2="";
}
return name1.compareTo(name2);
}
@Override
public JSONObject toJson() {
JSONObject jsonObject= new JSONObject();
jsonObject.put(NAME_name, getName());
jsonObject.put(NAME_id, getNameResourceID());
jsonObject.put(NAME_namespace_uri, getUri());
ValueType valueType=getValueType();
jsonObject.put(NAME_value_type, valueType.name());
if(valueType==ValueType.STRING){
jsonObject.put(NAME_data, getValueAsString());
}else if(valueType==ValueType.INT_BOOLEAN){
jsonObject.put(NAME_data, getValueAsBoolean());
}else {
jsonObject.put(NAME_data, getData());
}
return jsonObject;
}
@Override
public void fromJson(JSONObject json) {
String name = json.optString(NAME_name, "");
int id = json.optInt(NAME_id, 0);
setName(name, id);
String uri= json.optString(NAME_namespace_uri, null);
if(uri!=null){
ResXmlStartNamespace ns = getParentResXmlElement().getStartNamespaceByUri(uri);
setNamespaceReference(ns.getUriReference());
}
ValueType valueType=ValueType.fromName(json.getString(NAME_value_type));
if(valueType==ValueType.STRING){
setValueAsString(json.optString(NAME_data, ""));
}else if(valueType==ValueType.INT_BOOLEAN){
setValueAsBoolean(json.getBoolean(NAME_data));
}else {
setValueType(valueType);
setData(json.getInt(NAME_data));
}
}
public XMLAttribute decodeToXml(EntryStore entryStore, int currentPackageId) throws XMLException {
int resourceId=getNameResourceID();
String name;
if(resourceId==0){
name=getName();
}else {
EntryGroup group = entryStore.getEntryGroup(resourceId);
if(group==null){
//Lets ignore such error until XML encoder implemented
//throw new XMLException("Failed to decode attribute name: "
//+ String.format("@0x%08x", resourceId));
name=String.format("@0x%08x", resourceId);
}else {
name=group.getSpecName();
}
}
String prefix = getNamePrefix();
if(prefix!=null){
name=prefix+":"+name;
}
ValueType valueType = getValueType();
int raw = getData();
String value;
if(valueType==ValueType.STRING){
value = ValueDecoder.escapeSpecialCharacter(getValueAsString());
}else {
value = ValueDecoder.decode(entryStore,
currentPackageId,
resourceId,
valueType,
raw);
}
XMLAttribute attribute = new XMLAttribute(name, value);
attribute.setNameId(resourceId);
if(valueType==ValueType.REFERENCE||valueType==ValueType.ATTRIBUTE){
attribute.setValueId(raw);
}
return attribute;
}
@Override
public String toString(){
String fullName = getFullName();
if(fullName!=null ){
int id=getNameResourceID();
if(id!=0){
fullName=fullName+"(@"+String.format("0x%08x",id)+")";
}
String valStr;
ValueType valueType=getValueType();
if(valueType==ValueType.STRING){
valStr=getValueAsString();
}else if (valueType==ValueType.INT_BOOLEAN){
valStr = String.valueOf(getValueAsBoolean());
}else if (valueType==ValueType.INT_DEC){
valStr = String.valueOf(getData());
}else {
valStr = "["+valueType+"] " + String.format("0x%08x",getData());
}
if(valStr!=null){
return fullName+"=\""+valStr+"\"";
}
return fullName+"["+valueType+"]=\""+ getData()+"\"";
}
StringBuilder builder= new StringBuilder();
builder.append(getClass().getSimpleName());
builder.append(": ");
builder.append(getIndex());
builder.append("{NamespaceReference=").append(getNamespaceReference());
builder.append(", NameReference=").append(getNameReference());
builder.append(", ValueStringReference=").append(getValueStringReference());
builder.append(", ValueSize=").append(getSize());
builder.append(", ValueTypeByte=").append(getType() & 0xff);
builder.append(", Data=").append(getData());
builder.append("}");
return builder.toString();
}
public static final String NAME_id = "id";
public static final String NAME_name = "name";
public static final String NAME_namespace_uri = "namespace_uri";
private static final int OFFSET_NS = 0;
private static final int OFFSET_NAME = 4;
private static final int OFFSET_STRING = 8;
private static final int OFFSET_SIZE = 12;
}