mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-05-01 06:34:26 +02:00
483 lines
16 KiB
Java
Executable File
483 lines
16 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;
|
|
import java.util.Objects;
|
|
|
|
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){
|
|
if(Objects.equals(name, getName()) && resourceId==getNameResourceID()){
|
|
return;
|
|
}
|
|
unlink(mNameReference);
|
|
unLinkNameId(getResXmlID());
|
|
ResXmlString xmlString = getOrCreateAttributeName(name, resourceId);
|
|
if(xmlString==null){
|
|
return;
|
|
}
|
|
setNameReference(xmlString.getIndex());
|
|
mNameReference = link(OFFSET_NAME);
|
|
linkNameId();
|
|
}
|
|
private void linkStartNameSpace(){
|
|
ResXmlElement xmlElement=getParentResXmlElement();
|
|
if(xmlElement==null){
|
|
return;
|
|
}
|
|
ResXmlStartNamespace startNamespace=xmlElement.getStartNamespaceByUriRef(getNamespaceReference());
|
|
if(startNamespace==null){
|
|
return;
|
|
}
|
|
startNamespace.addAttributeReference(this);
|
|
}
|
|
private void unLinkStartNameSpace(){
|
|
ResXmlElement xmlElement=getParentResXmlElement();
|
|
if(xmlElement==null){
|
|
return;
|
|
}
|
|
ResXmlStartNamespace startNamespace=xmlElement.getStartNamespaceByUriRef(getNamespaceReference());
|
|
if(startNamespace==null){
|
|
return;
|
|
}
|
|
startNamespace.removeAttributeReference(this);
|
|
}
|
|
private ResXmlString getOrCreateAttributeName(String name, int resourceId){
|
|
ResXmlStringPool stringPool = getStringPool();
|
|
if(stringPool==null){
|
|
return null;
|
|
}
|
|
return stringPool.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);
|
|
linkStartNameSpace();
|
|
}
|
|
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();
|
|
linkStartNameSpace();
|
|
}
|
|
@Override
|
|
public void onRemoved(){
|
|
super.onRemoved();
|
|
unLinkStartNameSpace();
|
|
unlinkAll();
|
|
}
|
|
@Override
|
|
protected void onUnlinkDataString(ReferenceItem referenceItem){
|
|
unlink(referenceItem);
|
|
}
|
|
@Override
|
|
protected void onDataChanged(){
|
|
if(getValueType()==ValueType.STRING){
|
|
setValueStringReference(getData());
|
|
}else {
|
|
setValueStringReference(-1);
|
|
}
|
|
}
|
|
@Override
|
|
public ResXmlDocument getParentChunk() {
|
|
ResXmlElement element = getParentResXmlElement();
|
|
if(element!=null){
|
|
return element.getParentDocument();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
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;
|
|
if(xmlID.hasReference()){
|
|
return;
|
|
}
|
|
ResXmlIDMap xmlIDMap = getResXmlIDMap();
|
|
if(xmlIDMap == null){
|
|
return;
|
|
}
|
|
xmlIDMap.removeSafely(xmlID);
|
|
}
|
|
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;
|
|
}
|
|
ResXmlStringPool stringPool = getStringPool();
|
|
if(stringPool==null){
|
|
return;
|
|
}
|
|
stringPool.removeReference(reference);
|
|
}
|
|
@Override
|
|
public ResXmlStringPool getStringPool(){
|
|
StringPool<?> stringPool = super.getStringPool();
|
|
if(stringPool instanceof ResXmlStringPool){
|
|
return (ResXmlStringPool) stringPool;
|
|
}
|
|
return null;
|
|
}
|
|
@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);
|
|
if(ns==null){
|
|
ns = getParentResXmlElement().getRootResXmlElement()
|
|
.getOrCreateNamespace(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;
|
|
}
|