mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-30 14:24:25 +02:00
1025 lines
36 KiB
Java
Executable File
1025 lines
36 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.chunk.ChunkType;
|
|
import com.reandroid.arsc.base.Block;
|
|
import com.reandroid.arsc.container.BlockList;
|
|
import com.reandroid.arsc.container.SingleBlockContainer;
|
|
import com.reandroid.arsc.header.HeaderBlock;
|
|
import com.reandroid.arsc.io.BlockReader;
|
|
import com.reandroid.arsc.pool.ResXmlStringPool;
|
|
import com.reandroid.common.EntryStore;
|
|
import com.reandroid.json.JSONConvert;
|
|
import com.reandroid.json.JSONArray;
|
|
import com.reandroid.json.JSONObject;
|
|
import com.reandroid.xml.*;
|
|
|
|
|
|
import java.io.IOException;
|
|
import java.util.*;
|
|
|
|
public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>,
|
|
Comparator<ResXmlNode> {
|
|
private final BlockList<ResXmlStartNamespace> mStartNamespaceList;
|
|
private final SingleBlockContainer<ResXmlStartElement> mStartElementContainer;
|
|
private final BlockList<ResXmlNode> mBody;
|
|
private final SingleBlockContainer<ResXmlEndElement> mEndElementContainer;
|
|
private final BlockList<ResXmlEndNamespace> mEndNamespaceList;
|
|
private int mLevel;
|
|
public ResXmlElement() {
|
|
super(5);
|
|
this.mStartNamespaceList = new BlockList<>();
|
|
this.mStartElementContainer= new SingleBlockContainer<>();
|
|
this.mBody = new BlockList<>();
|
|
this.mEndElementContainer = new SingleBlockContainer<>();
|
|
this.mEndNamespaceList = new BlockList<>();
|
|
addChild(0, mStartNamespaceList);
|
|
addChild(1, mStartElementContainer);
|
|
addChild(2, mBody);
|
|
addChild(3, mEndElementContainer);
|
|
addChild(4, mEndNamespaceList);
|
|
}
|
|
public void changeIndex(ResXmlElement element, int index){
|
|
int i = 0;
|
|
for(ResXmlNode xmlNode:mBody.getChildes()){
|
|
if(i == index){
|
|
element.setIndex(i);
|
|
i++;
|
|
}
|
|
if(xmlNode==element){
|
|
continue;
|
|
}
|
|
xmlNode.setIndex(i);
|
|
i++;
|
|
}
|
|
mBody.sort(this);
|
|
}
|
|
public int lastIndexOf(String tagName){
|
|
List<ResXmlElement> elementList = listElements(tagName);
|
|
int i = elementList.size();
|
|
if(i==0){
|
|
return -1;
|
|
}
|
|
i--;
|
|
return elementList.get(i).getIndex();
|
|
}
|
|
public int indexOf(String tagName){
|
|
ResXmlElement element = getElementByTagName(tagName);
|
|
if(element!=null){
|
|
return element.getIndex();
|
|
}
|
|
return -1;
|
|
}
|
|
public int indexOf(ResXmlElement element){
|
|
int index = 0;
|
|
for(ResXmlNode xmlNode:mBody.getChildes()){
|
|
if(xmlNode==element){
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
return -1;
|
|
}
|
|
public void setAttributesUnitSize(int size, boolean setToAll){
|
|
ResXmlStartElement startElement = getStartElement();
|
|
startElement.setAttributesUnitSize(size);
|
|
if(setToAll){
|
|
for(ResXmlElement child:listElements()){
|
|
child.setAttributesUnitSize(size, setToAll);
|
|
}
|
|
}
|
|
}
|
|
public String getStartComment(){
|
|
ResXmlStartElement start = getStartElement();
|
|
if(start!=null){
|
|
return start.getComment();
|
|
}
|
|
return null;
|
|
}
|
|
public String getEndComment(){
|
|
ResXmlEndElement end = getEndElement();
|
|
if(end!=null){
|
|
return end.getComment();
|
|
}
|
|
return null;
|
|
}
|
|
public int getStartLineNumber(){
|
|
ResXmlStartElement start = getStartElement();
|
|
if(start!=null){
|
|
return start.getLineNumber();
|
|
}
|
|
return 0;
|
|
}
|
|
public int getEndLineNumber(){
|
|
ResXmlEndElement end = getEndElement();
|
|
if(end!=null){
|
|
return end.getLineNumber();
|
|
}
|
|
return 0;
|
|
}
|
|
public void setComment(String comment){
|
|
getStartElement().setComment(comment);
|
|
}
|
|
public void calculatePositions(){
|
|
ResXmlStartElement start = getStartElement();
|
|
if(start!=null){
|
|
start.calculatePositions();
|
|
}
|
|
}
|
|
public ResXmlAttribute newAttribute(){
|
|
return getStartElement().newAttribute();
|
|
}
|
|
void onRemoved(){
|
|
for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){
|
|
startNamespace.onRemoved();
|
|
}
|
|
ResXmlStartElement start = getStartElement();
|
|
if(start!=null){
|
|
start.onRemoved();
|
|
}
|
|
ResXmlText resXmlText=getResXmlText();
|
|
if(resXmlText!=null){
|
|
resXmlText.onRemoved();
|
|
}
|
|
for(ResXmlElement child:listElements()){
|
|
child.onRemoved();
|
|
}
|
|
}
|
|
void linkStringReferences(){
|
|
for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){
|
|
startNamespace.linkStringReferences();
|
|
}
|
|
ResXmlStartElement start = getStartElement();
|
|
if(start!=null){
|
|
start.linkStringReferences();
|
|
}
|
|
ResXmlText resXmlText=getResXmlText();
|
|
if(resXmlText!=null){
|
|
resXmlText.linkStringReferences();
|
|
}
|
|
for(ResXmlElement child:listElements()){
|
|
child.linkStringReferences();
|
|
}
|
|
}
|
|
public ResXmlElement createChildElement(){
|
|
return createChildElement(null);
|
|
}
|
|
public ResXmlElement createChildElement(String tag){
|
|
int lineNo=getStartElement().getLineNumber()+1;
|
|
ResXmlElement resXmlElement=new ResXmlElement();
|
|
resXmlElement.newStartElement(lineNo);
|
|
|
|
addElement(resXmlElement);
|
|
|
|
if(tag!=null){
|
|
resXmlElement.setTag(tag);
|
|
}
|
|
return resXmlElement;
|
|
}
|
|
public ResXmlAttribute getOrCreateAndroidAttribute(String name, int resourceId){
|
|
return getOrCreateAttribute(NS_ANDROID_URI, NS_ANDROID_PREFIX, name, resourceId);
|
|
}
|
|
public ResXmlAttribute getOrCreateAttribute(String uri, String prefix, String name, int resourceId){
|
|
ResXmlAttribute attribute=searchAttribute(name, resourceId);
|
|
if(attribute==null){
|
|
attribute = createAttribute(name, resourceId);
|
|
if(uri!=null){
|
|
ResXmlElement root = getRootResXmlElement();
|
|
ResXmlStartNamespace ns = root.getOrCreateNamespace(uri, prefix);
|
|
attribute.setNamespaceReference(ns.getUriReference());
|
|
}
|
|
}
|
|
return attribute;
|
|
}
|
|
public ResXmlAttribute getOrCreateAttribute(String name, int resourceId){
|
|
ResXmlAttribute attribute=searchAttribute(name, resourceId);
|
|
if(attribute==null){
|
|
attribute=createAttribute(name, resourceId);
|
|
}
|
|
return attribute;
|
|
}
|
|
public ResXmlAttribute createAndroidAttribute(String name, int resourceId){
|
|
ResXmlAttribute attribute=createAttribute(name, resourceId);
|
|
ResXmlStartNamespace ns = getOrCreateNamespace(NS_ANDROID_URI, NS_ANDROID_PREFIX);
|
|
attribute.setNamespaceReference(ns.getUriReference());
|
|
return attribute;
|
|
}
|
|
public ResXmlAttribute createAttribute(String name, int resourceId){
|
|
ResXmlAttribute attribute=new ResXmlAttribute();
|
|
addAttribute(attribute);
|
|
attribute.setName(name, resourceId);
|
|
return attribute;
|
|
}
|
|
public void addAttribute(ResXmlAttribute attribute){
|
|
getStartElement().getResXmlAttributeArray().add(attribute);
|
|
}
|
|
public ResXmlElement getElementByTagName(String name){
|
|
if(name==null){
|
|
return null;
|
|
}
|
|
for(ResXmlElement child:listElements()){
|
|
if(name.equals(child.getTag())||name.equals(child.getTagName())){
|
|
return child;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
private ResXmlAttribute searchAttribute(String name, int resourceId){
|
|
if(resourceId==0){
|
|
return searchAttributeByName(name);
|
|
}
|
|
return searchAttributeByResourceId(resourceId);
|
|
}
|
|
// Searches attribute with resource id = 0
|
|
public ResXmlAttribute searchAttributeByName(String name){
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.searchAttributeByName(name);
|
|
}
|
|
return null;
|
|
}
|
|
public ResXmlAttribute searchAttributeByResourceId(int resourceId){
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.searchAttributeByResourceId(resourceId);
|
|
}
|
|
return null;
|
|
}
|
|
public void setTag(String tag){
|
|
ResXmlStringPool pool = getStringPool();
|
|
if(pool==null){
|
|
return;
|
|
}
|
|
ensureStartEndElement();
|
|
ResXmlStartElement start=getStartElement();
|
|
String prefix=null;
|
|
String name=tag;
|
|
int i=tag.lastIndexOf(':');
|
|
if(i>=0){
|
|
prefix=tag.substring(0,i);
|
|
i++;
|
|
name=tag.substring(i);
|
|
}
|
|
start.setName(name);
|
|
ResXmlStartNamespace ns = getStartNamespaceByPrefix(prefix);
|
|
if(ns!=null){
|
|
start.setNamespaceReference(ns.getUriReference());
|
|
}
|
|
}
|
|
public String getTagName(){
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.getTagName();
|
|
}
|
|
return null;
|
|
}
|
|
public String getTag(){
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.getName();
|
|
}
|
|
return null;
|
|
}
|
|
public String getTagUri(){
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.getUri();
|
|
}
|
|
return null;
|
|
}
|
|
public String getTagPrefix(){
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.getPrefix();
|
|
}
|
|
return null;
|
|
}
|
|
public int getAttributeCount() {
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.getResXmlAttributeArray().childesCount();
|
|
}
|
|
return 0;
|
|
}
|
|
public ResXmlAttribute getAttributeAt(int index){
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.getResXmlAttributeArray().get(index);
|
|
}
|
|
return null;
|
|
}
|
|
public Collection<ResXmlAttribute> listAttributes(){
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
return startElement.listResXmlAttributes();
|
|
}
|
|
return new ArrayList<>();
|
|
}
|
|
public ResXmlStringPool getStringPool(){
|
|
Block parent=getParent();
|
|
while (parent!=null){
|
|
if(parent instanceof ResXmlDocument){
|
|
return ((ResXmlDocument)parent).getStringPool();
|
|
}
|
|
if(parent instanceof ResXmlElement){
|
|
return ((ResXmlElement)parent).getStringPool();
|
|
}
|
|
parent=parent.getParent();
|
|
}
|
|
return null;
|
|
}
|
|
public ResXmlIDMap getResXmlIDMap(){
|
|
ResXmlDocument resXmlDocument = getParentDocument();
|
|
if(resXmlDocument!=null){
|
|
return resXmlDocument.getResXmlIDMap();
|
|
}
|
|
return null;
|
|
}
|
|
public ResXmlDocument getParentDocument(){
|
|
return getParentInstance(ResXmlDocument.class);
|
|
}
|
|
|
|
@Override
|
|
public int getDepth(){
|
|
int depth = 0;
|
|
ResXmlElement parent = getParentResXmlElement();
|
|
while (parent!=null){
|
|
depth++;
|
|
parent = parent.getParentResXmlElement();
|
|
}
|
|
return depth;
|
|
}
|
|
@Override
|
|
void addEvents(ParserEventList parserEventList){
|
|
String comment = getStartComment();
|
|
if(comment!=null){
|
|
parserEventList.add(
|
|
new ParserEvent(ParserEvent.COMMENT, this, comment, false));
|
|
}
|
|
parserEventList.add(new ParserEvent(ParserEvent.START_TAG, this));
|
|
for(ResXmlNode xmlNode:getXmlNodes()){
|
|
xmlNode.addEvents(parserEventList);
|
|
}
|
|
comment = getEndComment();
|
|
if(comment!=null){
|
|
parserEventList.add(
|
|
new ParserEvent(ParserEvent.COMMENT, this, comment, true));
|
|
}
|
|
parserEventList.add(new ParserEvent(ParserEvent.END_TAG, this));
|
|
}
|
|
public int getLevel(){
|
|
return mLevel;
|
|
}
|
|
private void setLevel(int level){
|
|
mLevel = level;
|
|
}
|
|
public void addElement(ResXmlElement element){
|
|
mBody.add(element);
|
|
}
|
|
public boolean removeAttribute(ResXmlAttribute resXmlAttribute){
|
|
if(resXmlAttribute != null){
|
|
resXmlAttribute.onRemoved();
|
|
}
|
|
return getStartElement().getResXmlAttributeArray().remove(resXmlAttribute);
|
|
}
|
|
public boolean removeElement(ResXmlElement element){
|
|
if(element !=null && element.getParent()!=null){
|
|
element.onRemoved();
|
|
}
|
|
return mBody.remove(element);
|
|
}
|
|
public boolean removeNode(ResXmlNode node){
|
|
if(node instanceof ResXmlElement){
|
|
return removeElement((ResXmlElement) node);
|
|
}
|
|
return mBody.remove(node);
|
|
}
|
|
public int countElements(){
|
|
int result = 0;
|
|
for(ResXmlNode xmlNode: getXmlNodes()){
|
|
if(xmlNode instanceof ResXmlElement){
|
|
result++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
public void clearChildes(){
|
|
List<ResXmlNode> copyOfNodeList=new ArrayList<>(mBody.getChildes());
|
|
for(ResXmlNode xmlNode:copyOfNodeList){
|
|
if(xmlNode==null){
|
|
continue;
|
|
}
|
|
xmlNode.onRemove();
|
|
mBody.remove(xmlNode);
|
|
}
|
|
}
|
|
public ResXmlNode getResXmlNode(int position){
|
|
return mBody.get(position);
|
|
}
|
|
public int countResXmlNodes(){
|
|
return mBody.size();
|
|
}
|
|
public List<ResXmlNode> listXmlNodes(){
|
|
return new ArrayList<>(getXmlNodes());
|
|
}
|
|
private List<ResXmlNode> getXmlNodes(){
|
|
return mBody.getChildes();
|
|
}
|
|
public List<ResXmlText> listXmlText(){
|
|
List<ResXmlText> results=new ArrayList<>();
|
|
for(ResXmlNode xmlNode: getXmlNodes()){
|
|
if(xmlNode instanceof ResXmlTextNode){
|
|
results.add(((ResXmlTextNode) xmlNode).getResXmlText());
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
public List<ResXmlTextNode> listXmlTextNodes(){
|
|
List<ResXmlTextNode> results=new ArrayList<>();
|
|
for(ResXmlNode xmlNode: getXmlNodes()){
|
|
if(xmlNode instanceof ResXmlTextNode){
|
|
results.add((ResXmlTextNode) xmlNode);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
public List<ResXmlElement> listElements(){
|
|
List<ResXmlElement> results=new ArrayList<>();
|
|
for(ResXmlNode xmlNode: getXmlNodes()){
|
|
if(xmlNode instanceof ResXmlElement){
|
|
results.add((ResXmlElement) xmlNode);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
public List<ResXmlElement> listElements(String name){
|
|
List<ResXmlElement> results=new ArrayList<>();
|
|
if(name==null){
|
|
return results;
|
|
}
|
|
for(ResXmlElement element:listElements()){
|
|
if(name.equals(element.getTag())||name.equals(element.getTagName())){
|
|
results.add(element);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
public ResXmlElement getRootResXmlElement(){
|
|
ResXmlElement parent=getParentResXmlElement();
|
|
if(parent!=null){
|
|
return parent.getRootResXmlElement();
|
|
}
|
|
return this;
|
|
}
|
|
public ResXmlElement getParentResXmlElement(){
|
|
return getParentInstance(ResXmlElement.class);
|
|
}
|
|
public ResXmlStartNamespace getStartNamespaceByUriRef(int uriRef){
|
|
if(uriRef<0){
|
|
return null;
|
|
}
|
|
for(ResXmlStartNamespace ns:mStartNamespaceList.getChildes()){
|
|
if(uriRef==ns.getUriReference()){
|
|
return ns;
|
|
}
|
|
}
|
|
ResXmlElement xmlElement=getParentResXmlElement();
|
|
if(xmlElement!=null){
|
|
return xmlElement.getStartNamespaceByUriRef(uriRef);
|
|
}
|
|
return null;
|
|
}
|
|
public ResXmlStartNamespace getOrCreateNamespace(String uri, String prefix){
|
|
ResXmlStartNamespace exist=getStartNamespaceByUri(uri);
|
|
if(exist!=null){
|
|
return exist;
|
|
}
|
|
ResXmlStartNamespace startNamespace=new ResXmlStartNamespace();
|
|
ResXmlEndNamespace endNamespace=new ResXmlEndNamespace();
|
|
startNamespace.setEnd(endNamespace);
|
|
|
|
addStartNamespace(startNamespace);
|
|
addEndNamespace(endNamespace);
|
|
|
|
startNamespace.setUri(uri);
|
|
startNamespace.setPrefix(prefix);
|
|
|
|
return startNamespace;
|
|
}
|
|
public ResXmlStartNamespace getStartNamespaceByUri(String uri){
|
|
if(uri==null){
|
|
return null;
|
|
}
|
|
for(ResXmlStartNamespace ns:mStartNamespaceList.getChildes()){
|
|
if(uri.equals(ns.getUri())){
|
|
return ns;
|
|
}
|
|
}
|
|
ResXmlElement xmlElement=getParentResXmlElement();
|
|
if(xmlElement!=null){
|
|
return xmlElement.getStartNamespaceByUri(uri);
|
|
}
|
|
return null;
|
|
}
|
|
public ResXmlStartNamespace getStartNamespaceByPrefix(String prefix){
|
|
if(prefix==null){
|
|
return null;
|
|
}
|
|
for(ResXmlStartNamespace ns:mStartNamespaceList.getChildes()){
|
|
if(prefix.equals(ns.getPrefix())){
|
|
return ns;
|
|
}
|
|
}
|
|
ResXmlElement xmlElement=getParentResXmlElement();
|
|
if(xmlElement!=null){
|
|
return xmlElement.getStartNamespaceByPrefix(prefix);
|
|
}
|
|
return null;
|
|
}
|
|
public List<ResXmlStartNamespace> getStartNamespaceList(){
|
|
return mStartNamespaceList.getChildes();
|
|
}
|
|
public void addStartNamespace(ResXmlStartNamespace item){
|
|
mStartNamespaceList.add(item);
|
|
}
|
|
public List<ResXmlEndNamespace> getEndNamespaceList(){
|
|
return mEndNamespaceList.getChildes();
|
|
}
|
|
public void addEndNamespace(ResXmlEndNamespace item){
|
|
mEndNamespaceList.add(item);
|
|
}
|
|
|
|
ResXmlStartElement newStartElement(int lineNo){
|
|
ResXmlStartElement startElement=new ResXmlStartElement();
|
|
setStartElement(startElement);
|
|
|
|
ResXmlEndElement endElement=new ResXmlEndElement();
|
|
startElement.setResXmlEndElement(endElement);
|
|
|
|
setEndElement(endElement);
|
|
endElement.setResXmlStartElement(startElement);
|
|
|
|
startElement.setLineNumber(lineNo);
|
|
endElement.setLineNumber(lineNo);
|
|
|
|
return startElement;
|
|
}
|
|
|
|
public ResXmlStartElement getStartElement(){
|
|
return mStartElementContainer.getItem();
|
|
}
|
|
public void setStartElement(ResXmlStartElement item){
|
|
mStartElementContainer.setItem(item);
|
|
}
|
|
|
|
public ResXmlEndElement getEndElement(){
|
|
return mEndElementContainer.getItem();
|
|
}
|
|
public void setEndElement(ResXmlEndElement item){
|
|
mEndElementContainer.setItem(item);
|
|
}
|
|
|
|
// Use listXmlText() instead to be removed on next version
|
|
@Deprecated
|
|
public ResXmlText getResXmlText(){
|
|
List<ResXmlText> xmlTextList=listXmlText();
|
|
if(xmlTextList.size()==0){
|
|
return null;
|
|
}
|
|
return xmlTextList.get(0);
|
|
}
|
|
public void addResXmlTextNode(ResXmlTextNode xmlTextNode){
|
|
mBody.add(xmlTextNode);
|
|
}
|
|
public void addResXmlText(ResXmlText xmlText){
|
|
if(xmlText!=null){
|
|
addResXmlTextNode(new ResXmlTextNode(xmlText));
|
|
}
|
|
}
|
|
// Use addResXmlText()
|
|
@Deprecated
|
|
public void setResXmlText(ResXmlText xmlText){
|
|
addResXmlText(xmlText);
|
|
}
|
|
@Deprecated
|
|
public void setResXmlText(String text){
|
|
clearChildes();
|
|
addResXmlText(text);
|
|
}
|
|
public void addResXmlText(String text){
|
|
if(text==null){
|
|
return;
|
|
}
|
|
ResXmlTextNode xmlTextNode=new ResXmlTextNode();
|
|
addResXmlTextNode(xmlTextNode);
|
|
xmlTextNode.setText(text);
|
|
}
|
|
|
|
private boolean isBalanced(){
|
|
return isElementBalanced() && isNamespaceBalanced();
|
|
}
|
|
private boolean isNamespaceBalanced(){
|
|
return (mStartNamespaceList.size()==mEndNamespaceList.size());
|
|
}
|
|
private boolean isElementBalanced(){
|
|
return (hasStartElement() && hasEndElement());
|
|
}
|
|
private boolean hasStartElement(){
|
|
return mStartElementContainer.hasItem();
|
|
}
|
|
private boolean hasEndElement(){
|
|
return mEndElementContainer.hasItem();
|
|
}
|
|
|
|
private void linkStartEnd(){
|
|
linkStartEndElement();
|
|
linkStartEndNameSpaces();
|
|
}
|
|
private void linkStartEndElement(){
|
|
ResXmlStartElement start=getStartElement();
|
|
ResXmlEndElement end=getEndElement();
|
|
if(start==null || end==null){
|
|
return;
|
|
}
|
|
start.setResXmlEndElement(end);
|
|
end.setResXmlStartElement(start);
|
|
}
|
|
private void ensureStartEndElement(){
|
|
ResXmlStartElement start=getStartElement();
|
|
ResXmlEndElement end=getEndElement();
|
|
if(start!=null && end!=null){
|
|
return;
|
|
}
|
|
if(start==null){
|
|
start=new ResXmlStartElement();
|
|
setStartElement(start);
|
|
}
|
|
if(end==null){
|
|
end=new ResXmlEndElement();
|
|
setEndElement(end);
|
|
}
|
|
linkStartEndElement();
|
|
}
|
|
private void linkStartEndNameSpaces(){
|
|
if(!isNamespaceBalanced()){
|
|
return;
|
|
}
|
|
int max=mStartNamespaceList.size();
|
|
for(int i=0;i<max;i++){
|
|
ResXmlStartNamespace start=mStartNamespaceList.get(i);
|
|
ResXmlEndNamespace end=mEndNamespaceList.get(max-i-1);
|
|
start.setEnd(end);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onReadBytes(BlockReader reader) throws IOException {
|
|
int pos = reader.getPosition();
|
|
while (readNext(reader)){
|
|
if(pos==reader.getPosition()){
|
|
break;
|
|
}
|
|
pos=reader.getPosition();
|
|
}
|
|
}
|
|
private boolean readNext(BlockReader reader) throws IOException {
|
|
int pos = reader.getPosition();
|
|
if(isBalanced()){
|
|
return false;
|
|
}
|
|
HeaderBlock headerBlock=reader.readHeaderBlock();
|
|
if(headerBlock==null){
|
|
return false;
|
|
}
|
|
ChunkType chunkType=headerBlock.getChunkType();
|
|
if(chunkType==null){
|
|
unknownChunk(reader, headerBlock);
|
|
return false;
|
|
}
|
|
if(chunkType==ChunkType.XML_START_ELEMENT){
|
|
onStartElement(reader);
|
|
}else if(chunkType==ChunkType.XML_END_ELEMENT){
|
|
onEndElement(reader);
|
|
}else if(chunkType==ChunkType.XML_START_NAMESPACE){
|
|
onStartNamespace(reader);
|
|
}else if(chunkType==ChunkType.XML_END_NAMESPACE){
|
|
onEndNamespace(reader);
|
|
}else if(chunkType==ChunkType.XML_CDATA){
|
|
onXmlText(reader);
|
|
}else{
|
|
unexpectedChunk(reader, headerBlock);
|
|
}
|
|
if(!isBalanced()){
|
|
if(!reader.isAvailable()){
|
|
unBalancedFinish(reader);
|
|
}else if(pos!=reader.getPosition()){
|
|
return true;
|
|
}
|
|
}
|
|
linkStartEnd();
|
|
onFinishedRead(reader, headerBlock);
|
|
return false;
|
|
}
|
|
private void onFinishedRead(BlockReader reader, HeaderBlock headerBlock) throws IOException{
|
|
int avail=reader.available();
|
|
if(avail>0 && getLevel()==0){
|
|
onFinishedUnexpected(reader);
|
|
return;
|
|
}
|
|
onFinishedSuccess(reader, headerBlock);
|
|
}
|
|
private void onFinishedSuccess(BlockReader reader, HeaderBlock headerBlock) throws IOException{
|
|
|
|
}
|
|
private void onFinishedUnexpected(BlockReader reader) throws IOException{
|
|
StringBuilder builder=new StringBuilder();
|
|
builder.append("Unexpected finish reading: reader=").append(reader.toString());
|
|
HeaderBlock header = reader.readHeaderBlock();
|
|
if(header!=null){
|
|
builder.append(", next header=");
|
|
builder.append(header.toString());
|
|
}
|
|
throw new IOException(builder.toString());
|
|
}
|
|
private void onStartElement(BlockReader reader) throws IOException{
|
|
if(hasStartElement()){
|
|
ResXmlElement childElement=new ResXmlElement();
|
|
addElement(childElement);
|
|
childElement.setLevel(getLevel()+1);
|
|
childElement.readBytes(reader);
|
|
}else{
|
|
ResXmlStartElement startElement=new ResXmlStartElement();
|
|
setStartElement(startElement);
|
|
startElement.readBytes(reader);
|
|
}
|
|
}
|
|
private void onEndElement(BlockReader reader) throws IOException{
|
|
if(hasEndElement()){
|
|
multipleEndElement(reader);
|
|
return;
|
|
}
|
|
ResXmlEndElement endElement=new ResXmlEndElement();
|
|
setEndElement(endElement);
|
|
endElement.readBytes(reader);
|
|
}
|
|
private void onStartNamespace(BlockReader reader) throws IOException{
|
|
ResXmlStartNamespace startNamespace=new ResXmlStartNamespace();
|
|
addStartNamespace(startNamespace);
|
|
startNamespace.readBytes(reader);
|
|
}
|
|
private void onEndNamespace(BlockReader reader) throws IOException{
|
|
ResXmlEndNamespace endNamespace=new ResXmlEndNamespace();
|
|
addEndNamespace(endNamespace);
|
|
endNamespace.readBytes(reader);
|
|
}
|
|
private void onXmlText(BlockReader reader) throws IOException{
|
|
ResXmlText xmlText=new ResXmlText();
|
|
addResXmlText(xmlText);
|
|
xmlText.readBytes(reader);
|
|
}
|
|
|
|
private void unknownChunk(BlockReader reader, HeaderBlock headerBlock) throws IOException{
|
|
throw new IOException("Unknown chunk: "+headerBlock.toString());
|
|
}
|
|
private void multipleEndElement(BlockReader reader) throws IOException{
|
|
throw new IOException("Multiple end element: "+reader.toString());
|
|
}
|
|
private void unexpectedChunk(BlockReader reader, HeaderBlock headerBlock) throws IOException{
|
|
throw new IOException("Unexpected chunk: "+headerBlock.toString());
|
|
}
|
|
private void unBalancedFinish(BlockReader reader) throws IOException{
|
|
if(!isNamespaceBalanced()){
|
|
throw new IOException("Unbalanced namespace: start="
|
|
+mStartNamespaceList.size()+", end="+mEndNamespaceList.size());
|
|
}
|
|
|
|
if(!isElementBalanced()){
|
|
// Should not happen unless corrupted file, auto corrected above
|
|
StringBuilder builder=new StringBuilder();
|
|
builder.append("Unbalanced element: start=");
|
|
ResXmlStartElement startElement=getStartElement();
|
|
if(startElement!=null){
|
|
builder.append(startElement);
|
|
}else {
|
|
builder.append("null");
|
|
}
|
|
builder.append(", end=");
|
|
ResXmlEndElement endElement=getEndElement();
|
|
if(endElement!=null){
|
|
builder.append(endElement);
|
|
}else {
|
|
builder.append("null");
|
|
}
|
|
throw new IOException(builder.toString());
|
|
}
|
|
}
|
|
@Override
|
|
public JSONObject toJson() {
|
|
JSONObject jsonObject=new JSONObject();
|
|
jsonObject.put(NAME_node_type, NAME_element);
|
|
ResXmlStartElement start = getStartElement();
|
|
jsonObject.put(NAME_line, start.getLineNumber());
|
|
int i=0;
|
|
JSONArray nsList=new JSONArray();
|
|
for(ResXmlStartNamespace namespace:getStartNamespaceList()){
|
|
JSONObject ns=new JSONObject();
|
|
ns.put(NAME_namespace_uri, namespace.getUri());
|
|
ns.put(NAME_namespace_prefix, namespace.getPrefix());
|
|
nsList.put(i, ns);
|
|
i++;
|
|
}
|
|
if(i>0){
|
|
jsonObject.put(NAME_namespaces, nsList);
|
|
}
|
|
jsonObject.put(NAME_name, start.getName());
|
|
String comment=start.getComment();
|
|
if(comment!=null){
|
|
jsonObject.put(NAME_comment, comment);
|
|
}
|
|
String uri=start.getUri();
|
|
if(uri!=null){
|
|
jsonObject.put(NAME_namespace_uri, uri);
|
|
}
|
|
JSONArray attrArray=start.getResXmlAttributeArray().toJson();
|
|
jsonObject.put(NAME_attributes, attrArray);
|
|
i=0;
|
|
JSONArray childes=new JSONArray();
|
|
for(ResXmlNode xmlNode: getXmlNodes()){
|
|
childes.put(i, xmlNode.toJson());
|
|
i++;
|
|
}
|
|
if(i>0){
|
|
jsonObject.put(NAME_childes, childes);
|
|
}
|
|
return jsonObject;
|
|
}
|
|
@Override
|
|
public void fromJson(JSONObject json) {
|
|
ResXmlStartElement start = getStartElement();
|
|
int lineNo=json.optInt(NAME_line, 1);
|
|
if(start==null){
|
|
start = newStartElement(lineNo);
|
|
}else {
|
|
start.setLineNumber(lineNo);
|
|
}
|
|
JSONArray nsArray = json.optJSONArray(NAME_namespaces);
|
|
if(nsArray!=null){
|
|
int length=nsArray.length();
|
|
for(int i=0;i<length;i++){
|
|
JSONObject nsObject=nsArray.getJSONObject(i);
|
|
String uri=nsObject.optString(NAME_namespace_uri, "");
|
|
String prefix=nsObject.optString(NAME_namespace_prefix, "");
|
|
getOrCreateNamespace(uri,prefix);
|
|
}
|
|
}
|
|
setTag(json.getString(NAME_name));
|
|
start.setComment(json.optString(NAME_comment, null));
|
|
String text= json.optString(NAME_text, null);
|
|
if(text!=null){
|
|
addResXmlText(text);
|
|
}
|
|
String uri = json.optString(NAME_namespace_uri, null);
|
|
if(uri!=null){
|
|
ResXmlStartNamespace ns = getStartNamespaceByUri(uri);
|
|
if(ns==null){
|
|
throw new IllegalArgumentException("Undefined uri: "
|
|
+uri+", must be defined in array: "+NAME_namespaces);
|
|
}
|
|
start.setNamespaceReference(ns.getUriReference());
|
|
}
|
|
JSONArray attributes = json.optJSONArray(NAME_attributes);
|
|
if(attributes!=null){
|
|
start.getResXmlAttributeArray().fromJson(attributes);
|
|
}
|
|
JSONArray childArray= json.optJSONArray(NAME_childes);
|
|
if(childArray!=null){
|
|
int length=childArray.length();
|
|
for(int i=0;i<length;i++){
|
|
JSONObject childObject=childArray.getJSONObject(i);
|
|
if(isTextNode(childObject)){
|
|
ResXmlTextNode xmlTextNode=new ResXmlTextNode();
|
|
addResXmlTextNode(xmlTextNode);
|
|
xmlTextNode.fromJson(childObject);
|
|
}else {
|
|
ResXmlElement childElement = createChildElement();
|
|
childElement.fromJson(childObject);
|
|
}
|
|
}
|
|
}
|
|
start.calculatePositions();
|
|
}
|
|
private boolean isTextNode(JSONObject childObject){
|
|
String type=childObject.optString(NAME_node_type, null);
|
|
if(ResXmlTextNode.NAME_text.equals(type)){
|
|
return true;
|
|
}
|
|
if(NAME_element.equals(type)){
|
|
return false;
|
|
}
|
|
// support older ARSCLib versions
|
|
return childObject.has(NAME_text);
|
|
}
|
|
|
|
/**
|
|
* Decodes binary {@link ResXmlElement} to readable {@link XMLElement}
|
|
* @param entryStore : used for decoding attribute name and values
|
|
* @param currentPackageId : is id of current package defining this xml, used for
|
|
* decoding reference names e.g @{package.name}:string/entry_name
|
|
* */
|
|
public XMLElement decodeToXml(EntryStore entryStore, int currentPackageId) throws XMLException {
|
|
XMLElement xmlElement = new XMLElement(getTagName());
|
|
xmlElement.setLineNumber(getStartElement().getLineNumber());
|
|
for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){
|
|
xmlElement.addAttribute(startNamespace.decodeToXml());
|
|
}
|
|
for(ResXmlAttribute resXmlAttribute:listAttributes()){
|
|
XMLAttribute xmlAttribute =
|
|
resXmlAttribute.decodeToXml(entryStore, currentPackageId);
|
|
xmlElement.addAttribute(xmlAttribute);
|
|
}
|
|
String comment=getStartComment();
|
|
if(comment!=null){
|
|
xmlElement.addComment(new XMLComment(comment));
|
|
}
|
|
comment=getEndComment();
|
|
if(comment!=null){
|
|
xmlElement.addComment(new XMLComment(comment));
|
|
}
|
|
for(ResXmlNode xmlNode: getXmlNodes()){
|
|
if(xmlNode instanceof ResXmlElement){
|
|
ResXmlElement childResXmlElement=(ResXmlElement)xmlNode;
|
|
XMLElement childXMLElement =
|
|
childResXmlElement.decodeToXml(entryStore, currentPackageId);
|
|
xmlElement.addChild(childXMLElement);
|
|
}else if(xmlNode instanceof ResXmlTextNode){
|
|
ResXmlTextNode childResXmlTextNode=(ResXmlTextNode)xmlNode;
|
|
XMLText xmlText = childResXmlTextNode.decodeToXml();
|
|
xmlElement.addText(xmlText);
|
|
}
|
|
}
|
|
return xmlElement;
|
|
}
|
|
@Override
|
|
public int compare(ResXmlNode node1, ResXmlNode node2) {
|
|
return Integer.compare(node1.getIndex(), node2.getIndex());
|
|
}
|
|
@Override
|
|
public String toString(){
|
|
ResXmlStartElement start = getStartElement();
|
|
if(start!=null){
|
|
ResXmlText text=getResXmlText();
|
|
StringBuilder builder=new StringBuilder();
|
|
builder.append("<");
|
|
builder.append(start.toString());
|
|
if(text!=null){
|
|
builder.append(">");
|
|
builder.append(text.toString());
|
|
builder.append("</");
|
|
builder.append(start.getTagName());
|
|
builder.append(">");
|
|
}else {
|
|
builder.append("/>");
|
|
}
|
|
return builder.toString();
|
|
}
|
|
return "NULL";
|
|
}
|
|
static ResXmlElement newResXmlElement(String tag){
|
|
ResXmlElement resXmlElement=new ResXmlElement();
|
|
ResXmlStartElement startElement=new ResXmlStartElement();
|
|
resXmlElement.setStartElement(startElement);
|
|
ResXmlEndElement endElement=new ResXmlEndElement();
|
|
resXmlElement.setEndElement(endElement);
|
|
resXmlElement.setTag(tag);
|
|
return resXmlElement;
|
|
}
|
|
|
|
public static final String NS_ANDROID_URI = "http://schemas.android.com/apk/res/android";
|
|
public static final String NS_ANDROID_PREFIX = "android";
|
|
|
|
static final String NAME_element = "element";
|
|
static final String NAME_name = "name";
|
|
static final String NAME_comment = "comment";
|
|
static final String NAME_text = "text";
|
|
static final String NAME_namespaces = "namespaces";
|
|
static final String NAME_namespace_uri = "namespace_uri";
|
|
static final String NAME_namespace_prefix = "namespace_prefix";
|
|
private static final String NAME_line = "line";
|
|
static final String NAME_attributes = "attributes";
|
|
static final String NAME_childes = "childes";
|
|
}
|