better xml namespace handling

This commit is contained in:
REAndroid 2023-05-07 16:58:15 +02:00
parent 75d6ed704b
commit b07b8bddfb
7 changed files with 166 additions and 176 deletions

View File

@ -211,8 +211,7 @@ public class ApkModuleXmlDecoder extends ApkDecoder implements Predicate<Entry>
} }
private void serializeXml(int currentPackageId, ResXmlDocument document, File outFile) private void serializeXml(int currentPackageId, ResXmlDocument document, File outFile)
throws IOException { throws IOException {
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document); XMLNamespaceValidator.validateNamespaces(document);
namespaceValidator.validate();
ResXmlDocumentSerializer serializer = getDocumentSerializer(); ResXmlDocumentSerializer serializer = getDocumentSerializer();
if(currentPackageId != 0){ if(currentPackageId != 0){
serializer.getDecoder().setCurrentPackageId(currentPackageId); serializer.getDecoder().setCurrentPackageId(currentPackageId);

View File

@ -121,8 +121,7 @@ public class ResXmlDocumentSerializer implements ResXmlPullParser.DocumentLoaded
if(!validateXmlNamespace){ if(!validateXmlNamespace){
return resXmlDocument; return resXmlDocument;
} }
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(resXmlDocument); XMLNamespaceValidator.validateNamespaces(resXmlDocument);
namespaceValidator.validate();
return resXmlDocument; return resXmlDocument;
} }
private IOException getError(Exception exception){ private IOException getError(Exception exception){

View File

@ -17,8 +17,7 @@ package com.reandroid.apk.xmldecoder;
import com.reandroid.arsc.chunk.xml.*; import com.reandroid.arsc.chunk.xml.*;
import java.util.ArrayList; import java.util.Collection;
import java.util.List;
public class XMLNamespaceValidator { public class XMLNamespaceValidator {
private static final String URI_ANDROID = "http://schemas.android.com/apk/res/android"; private static final String URI_ANDROID = "http://schemas.android.com/apk/res/android";
@ -26,45 +25,57 @@ public class XMLNamespaceValidator {
private static final String PREFIX_ANDROID = "android"; private static final String PREFIX_ANDROID = "android";
private static final String PREFIX_APP = "app"; private static final String PREFIX_APP = "app";
private final ResXmlDocument xmlBlock; private final ResXmlDocument xmlBlock;
private List<ResXmlAttribute> mAttributeList;
private ResXmlElement mRootElement;
private ResXmlStartNamespace mNsAndroid;
private ResXmlStartNamespace mNsApp;
public XMLNamespaceValidator(ResXmlDocument xmlBlock){ public XMLNamespaceValidator(ResXmlDocument xmlBlock){
this.xmlBlock=xmlBlock; this.xmlBlock=xmlBlock;
} }
public void validate(){ public void validate(){
if(getRootElement()==null){ validateNamespaces(xmlBlock);
return;
} }
for(ResXmlAttribute attribute:getAttributes()){
validate(attribute); public static boolean isValid(ResXmlAttribute attribute){
}
}
private void validate(ResXmlAttribute attribute){
int resourceId = attribute.getNameResourceID(); int resourceId = attribute.getNameResourceID();
if(resourceId == 0){ if(resourceId == 0){
removeNamespace(attribute); return attribute.getUri() == null;
return;
} }
int pkgId=toPackageId(resourceId); if(isAndroid(toPackageId(resourceId))){
if(isAndroid(pkgId)){ return isValidAndroidNamespace(attribute);
setAndroidNamespace(attribute);
}else { }else {
setAppNamespace(attribute); return isValidAppNamespace(attribute);
} }
} }
private void removeNamespace(ResXmlAttribute attribute){ public static void validateNamespaces(ResXmlDocument resXmlDocument){
validateNamespaces(resXmlDocument.getResXmlElement());
}
public static void validateNamespaces(ResXmlElement element){
validateNamespaces(element.listAttributes());
for(ResXmlElement child : element.listElements()){
validateNamespaces(child);
}
}
private static void validateNamespaces(Collection<ResXmlAttribute> attributeList){
for(ResXmlAttribute attribute : attributeList){
validateNamespace(attribute);
}
}
private static void validateNamespace(ResXmlAttribute attribute){
int resourceId = attribute.getNameResourceID();
if(resourceId == 0){
attribute.setNamespaceReference(-1); attribute.setNamespaceReference(-1);
}
private void setAppNamespace(ResXmlAttribute attribute){
if(isValidAppNamespace(attribute)){
return; return;
} }
ResXmlStartNamespace ns = getOrCreateApp(); if(isAndroid(toPackageId(resourceId))){
attribute.setNamespaceReference(ns.getUriReference()); if(!isValidAndroidNamespace(attribute)){
attribute.setNamespace(URI_ANDROID, PREFIX_ANDROID);
} }
private boolean isValidAppNamespace(ResXmlAttribute attribute){ }else {
if(!isValidAppNamespace(attribute)){
attribute.setNamespace(URI_APP, PREFIX_APP);
}
}
}
private static boolean isValidAppNamespace(ResXmlAttribute attribute){
String uri = attribute.getUri(); String uri = attribute.getUri();
String prefix = attribute.getNamePrefix(); String prefix = attribute.getNamePrefix();
if(URI_ANDROID.equals(uri) || PREFIX_ANDROID.equals(prefix)){ if(URI_ANDROID.equals(uri) || PREFIX_ANDROID.equals(prefix)){
@ -75,62 +86,15 @@ public class XMLNamespaceValidator {
} }
return true; return true;
} }
private void setAndroidNamespace(ResXmlAttribute attribute){ private static boolean isValidAndroidNamespace(ResXmlAttribute attribute){
if(URI_ANDROID.equals(attribute.getUri()) return URI_ANDROID.equals(attribute.getUri())
&& PREFIX_ANDROID.equals(attribute.getNamePrefix())){ && PREFIX_ANDROID.equals(attribute.getNamePrefix());
return;
} }
ResXmlStartNamespace ns = getOrCreateAndroid();
attribute.setNamespaceReference(ns.getUriReference()); private static boolean isAndroid(int pkgId){
}
private ResXmlStartNamespace getOrCreateApp(){
if(mNsApp !=null){
return mNsApp;
}
ResXmlElement root=getRootElement();
ResXmlStartNamespace ns = root.getOrCreateNamespace(URI_APP, PREFIX_APP);
String prefix=ns.getPrefix();
if(PREFIX_ANDROID.equals(prefix) || isEmpty(prefix)){
ns.setPrefix(PREFIX_APP);
}
mNsApp = ns;
return mNsApp;
}
private ResXmlStartNamespace getOrCreateAndroid(){
if(mNsAndroid !=null){
return mNsAndroid;
}
ResXmlElement root=getRootElement();
ResXmlStartNamespace ns = root.getOrCreateNamespace(URI_ANDROID, PREFIX_ANDROID);
if(!PREFIX_ANDROID.equals(ns.getPrefix())){
ns.setPrefix(PREFIX_ANDROID);
}
mNsAndroid =ns;
return mNsAndroid;
}
private ResXmlElement getRootElement() {
if(mRootElement==null){
mRootElement = xmlBlock.getResXmlElement();
}
return mRootElement;
}
private List<ResXmlAttribute> getAttributes(){
if(mAttributeList==null){
mAttributeList=listAttributes(xmlBlock.getResXmlElement());
}
return mAttributeList;
}
private List<ResXmlAttribute> listAttributes(ResXmlElement element){
List<ResXmlAttribute> results = new ArrayList<>(element.listAttributes());
for(ResXmlElement child:element.listElements()){
results.addAll(listAttributes(child));
}
return results;
}
private boolean isAndroid(int pkgId){
return pkgId==1; return pkgId==1;
} }
private int toPackageId(int resId){ private static int toPackageId(int resId){
return (resId >> 24 & 0xFF); return (resId >> 24 & 0xFF);
} }
private static boolean isEmpty(String str){ private static boolean isEmpty(String str){

View File

@ -184,6 +184,18 @@ public class ResXmlAttribute extends ValueItem implements AttributeValue, Compar
int getNamespaceReference(){ int getNamespaceReference(){
return getInteger(getBytesInternal(), OFFSET_NS); return getInteger(getBytesInternal(), OFFSET_NS);
} }
public void setNamespace(String uri, String prefix){
if(uri == null || prefix == null){
setNamespaceReference(-1);
return;
}
ResXmlElement parentElement = getParentResXmlElement();
if(parentElement == null){
return;
}
ResXmlStartNamespace ns = parentElement.getOrCreateNamespace(uri, prefix);
setNamespaceReference(ns.getUriReference());
}
public void setNamespaceReference(int ref){ public void setNamespaceReference(int ref){
if(ref == getNamespaceReference()){ if(ref == getNamespaceReference()){
return; return;

View File

@ -110,7 +110,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
} }
return null; return null;
} }
public String getEndComment(){ String getEndComment(){
ResXmlEndElement end = getEndElement(); ResXmlEndElement end = getEndElement();
if(end!=null){ if(end!=null){
return end.getComment(); return end.getComment();
@ -143,6 +143,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
public ResXmlAttribute newAttribute(){ public ResXmlAttribute newAttribute(){
return getStartElement().newAttribute(); return getStartElement().newAttribute();
} }
@Override
void onRemoved(){ void onRemoved(){
for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){ for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){
startNamespace.onRemoved(); startNamespace.onRemoved();
@ -151,14 +152,11 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
if(start != null){ if(start != null){
start.onRemoved(); start.onRemoved();
} }
ResXmlText resXmlText=getResXmlText(); for(ResXmlNode xmlNode : listXmlNodes()){
if(resXmlText!=null){ xmlNode.onRemoved();
resXmlText.onRemoved();
}
for(ResXmlElement child:listElements()){
child.onRemoved();
} }
} }
@Override
void linkStringReferences(){ void linkStringReferences(){
for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){ for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){
startNamespace.linkStringReferences(); startNamespace.linkStringReferences();
@ -167,12 +165,8 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
if(start != null){ if(start != null){
start.linkStringReferences(); start.linkStringReferences();
} }
ResXmlText resXmlText=getResXmlText(); for(ResXmlNode xmlNode : getXmlNodes()){
if(resXmlText!=null){ xmlNode.linkStringReferences();
resXmlText.linkStringReferences();
}
for(ResXmlElement child:listElements()){
child.linkStringReferences();
} }
} }
public ResXmlElement createChildElement(){ public ResXmlElement createChildElement(){
@ -355,13 +349,11 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
@Override @Override
public int getDepth(){ public int getDepth(){
int depth = 0;
ResXmlElement parent = getParentResXmlElement(); ResXmlElement parent = getParentResXmlElement();
while (parent!=null){ if(parent != null){
depth++; return parent.getDepth() + 1;
parent = parent.getParentResXmlElement();
} }
return depth; return 0;
} }
@Override @Override
void addEvents(ParserEventList parserEventList){ void addEvents(ParserEventList parserEventList){
@ -423,7 +415,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
if(xmlNode==null){ if(xmlNode==null){
continue; continue;
} }
xmlNode.onRemove(); xmlNode.onRemoved();
mBody.remove(xmlNode); mBody.remove(xmlNode);
} }
} }
@ -433,6 +425,22 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
public int countResXmlNodes(){ public int countResXmlNodes(){
return mBody.size(); return mBody.size();
} }
public boolean hasText(){
for(ResXmlNode xmlNode : getXmlNodes()){
if(xmlNode instanceof ResXmlTextNode){
return true;
}
}
return false;
}
public boolean hasElement(){
for(ResXmlNode xmlNode : getXmlNodes()){
if(xmlNode instanceof ResXmlElement){
return true;
}
}
return false;
}
public List<ResXmlNode> listXmlNodes(){ public List<ResXmlNode> listXmlNodes(){
return new ArrayList<>(getXmlNodes()); return new ArrayList<>(getXmlNodes());
} }
@ -503,11 +511,29 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
} }
return null; return null;
} }
public ResXmlStartNamespace getNamespace(String uri, String prefix){
if(uri == null || prefix == null){
return null;
}
for(ResXmlStartNamespace ns : mStartNamespaceList.getChildes()){
if(uri.equals(ns.getUri()) && prefix.equals(ns.getPrefix())){
return ns;
}
}
ResXmlElement xmlElement = getParentResXmlElement();
if(xmlElement != null){
return xmlElement.getNamespace(uri, prefix);
}
return null;
}
public ResXmlStartNamespace getOrCreateNamespace(String uri, String prefix){ public ResXmlStartNamespace getOrCreateNamespace(String uri, String prefix){
ResXmlStartNamespace exist=getStartNamespaceByUri(uri); ResXmlStartNamespace exist = getNamespace(uri, prefix);
if(exist != null){ if(exist != null){
return exist; return exist;
} }
return getRootResXmlElement().createNamespace(uri, prefix);
}
public ResXmlStartNamespace createNamespace(String uri, String prefix){
ResXmlStartNamespace startNamespace = new ResXmlStartNamespace(); ResXmlStartNamespace startNamespace = new ResXmlStartNamespace();
ResXmlEndNamespace endNamespace = new ResXmlEndNamespace(); ResXmlEndNamespace endNamespace = new ResXmlEndNamespace();
startNamespace.setEnd(endNamespace); startNamespace.setEnd(endNamespace);
@ -562,7 +588,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
public void addStartNamespace(ResXmlStartNamespace item){ public void addStartNamespace(ResXmlStartNamespace item){
mStartNamespaceList.add(item); mStartNamespaceList.add(item);
} }
public List<ResXmlEndNamespace> getEndNamespaceList(){ private List<ResXmlEndNamespace> getEndNamespaceList(){
return mEndNamespaceList.getChildes(); return mEndNamespaceList.getChildes();
} }
public void addEndNamespace(ResXmlEndNamespace item){ public void addEndNamespace(ResXmlEndNamespace item){
@ -596,26 +622,17 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
public ResXmlStartElement getStartElement(){ public ResXmlStartElement getStartElement(){
return mStartElementContainer.getItem(); return mStartElementContainer.getItem();
} }
public void setStartElement(ResXmlStartElement item){ private void setStartElement(ResXmlStartElement item){
mStartElementContainer.setItem(item); mStartElementContainer.setItem(item);
} }
public ResXmlEndElement getEndElement(){ private ResXmlEndElement getEndElement(){
return mEndElementContainer.getItem(); return mEndElementContainer.getItem();
} }
public void setEndElement(ResXmlEndElement item){ private void setEndElement(ResXmlEndElement item){
mEndElementContainer.setItem(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){ public void addResXmlTextNode(ResXmlTextNode xmlTextNode){
mBody.add(xmlTextNode); mBody.add(xmlTextNode);
} }
@ -624,16 +641,6 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
addResXmlTextNode(new ResXmlTextNode(xmlText)); 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){ public void addResXmlText(String text){
if(text==null){ if(text==null){
return; return;
@ -995,13 +1002,14 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert<JSONObject>
public String toString(){ public String toString(){
ResXmlStartElement start = getStartElement(); ResXmlStartElement start = getStartElement();
if(start!=null){ if(start!=null){
ResXmlText text=getResXmlText();
StringBuilder builder=new StringBuilder(); StringBuilder builder=new StringBuilder();
builder.append("<"); builder.append("<");
builder.append(start.toString()); builder.append(start.toString());
if(text!=null){ if(hasText() && !hasElement()){
builder.append(">"); builder.append(">");
builder.append(text.toString()); for(ResXmlText xmlText : listXmlText()){
builder.append(xmlText.getText());
}
builder.append("</"); builder.append("</");
builder.append(start.getTagName()); builder.append(start.getTagName());
builder.append(">"); builder.append(">");

View File

@ -23,8 +23,8 @@ public abstract class ResXmlNode extends FixedBlockContainer implements JSONCon
ResXmlNode(int childesCount) { ResXmlNode(int childesCount) {
super(childesCount); super(childesCount);
} }
void onRemove(){ abstract void onRemoved();
} abstract void linkStringReferences();
public abstract int getDepth(); public abstract int getDepth();
abstract void addEvents(ParserEventList parserEventList); abstract void addEvents(ParserEventList parserEventList);

View File

@ -75,6 +75,14 @@ public class ResXmlTextNode extends ResXmlNode {
getResXmlText().setTextReference(ref); getResXmlText().setTextReference(ref);
} }
@Override @Override
void onRemoved(){
getResXmlText().onRemoved();
}
@Override
void linkStringReferences(){
getResXmlText().linkStringReferences();
}
@Override
public String toString(){ public String toString(){
String txt=getText(); String txt=getText();
if(txt!=null){ if(txt!=null){