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)
throws IOException {
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
namespaceValidator.validate();
XMLNamespaceValidator.validateNamespaces(document);
ResXmlDocumentSerializer serializer = getDocumentSerializer();
if(currentPackageId != 0){
serializer.getDecoder().setCurrentPackageId(currentPackageId);

View File

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

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -17,54 +17,65 @@ package com.reandroid.apk.xmldecoder;
import com.reandroid.arsc.chunk.xml.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
public class XMLNamespaceValidator {
private static final String URI_ANDROID="http://schemas.android.com/apk/res/android";
private static final String URI_APP="http://schemas.android.com/apk/res-auto";
private static final String PREFIX_ANDROID ="android";
private static final String PREFIX_APP="app";
private static final String URI_ANDROID = "http://schemas.android.com/apk/res/android";
private static final String URI_APP = "http://schemas.android.com/apk/res-auto";
private static final String PREFIX_ANDROID = "android";
private static final String PREFIX_APP = "app";
private final ResXmlDocument xmlBlock;
private List<ResXmlAttribute> mAttributeList;
private ResXmlElement mRootElement;
private ResXmlStartNamespace mNsAndroid;
private ResXmlStartNamespace mNsApp;
public XMLNamespaceValidator(ResXmlDocument xmlBlock){
this.xmlBlock=xmlBlock;
}
public void validate(){
if(getRootElement()==null){
return;
}
for(ResXmlAttribute attribute:getAttributes()){
validate(attribute);
}
validateNamespaces(xmlBlock);
}
private void validate(ResXmlAttribute attribute){
int resourceId=attribute.getNameResourceID();
if(resourceId==0){
removeNamespace(attribute);
return;
public static boolean isValid(ResXmlAttribute attribute){
int resourceId = attribute.getNameResourceID();
if(resourceId == 0){
return attribute.getUri() == null;
}
int pkgId=toPackageId(resourceId);
if(isAndroid(pkgId)){
setAndroidNamespace(attribute);
if(isAndroid(toPackageId(resourceId))){
return isValidAndroidNamespace(attribute);
}else {
setAppNamespace(attribute);
return isValidAppNamespace(attribute);
}
}
private void removeNamespace(ResXmlAttribute attribute){
attribute.setNamespaceReference(-1);
public static void validateNamespaces(ResXmlDocument resXmlDocument){
validateNamespaces(resXmlDocument.getResXmlElement());
}
private void setAppNamespace(ResXmlAttribute attribute){
if(isValidAppNamespace(attribute)){
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);
return;
}
ResXmlStartNamespace ns = getOrCreateApp();
attribute.setNamespaceReference(ns.getUriReference());
if(isAndroid(toPackageId(resourceId))){
if(!isValidAndroidNamespace(attribute)){
attribute.setNamespace(URI_ANDROID, PREFIX_ANDROID);
}
}else {
if(!isValidAppNamespace(attribute)){
attribute.setNamespace(URI_APP, PREFIX_APP);
}
}
}
private boolean isValidAppNamespace(ResXmlAttribute attribute){
private static boolean isValidAppNamespace(ResXmlAttribute attribute){
String uri = attribute.getUri();
String prefix = attribute.getNamePrefix();
if(URI_ANDROID.equals(uri) || PREFIX_ANDROID.equals(prefix)){
@ -75,62 +86,15 @@ public class XMLNamespaceValidator {
}
return true;
}
private void setAndroidNamespace(ResXmlAttribute attribute){
if(URI_ANDROID.equals(attribute.getUri())
&& PREFIX_ANDROID.equals(attribute.getNamePrefix())){
return;
}
ResXmlStartNamespace ns = getOrCreateAndroid();
attribute.setNamespaceReference(ns.getUriReference());
private static boolean isValidAndroidNamespace(ResXmlAttribute attribute){
return URI_ANDROID.equals(attribute.getUri())
&& PREFIX_ANDROID.equals(attribute.getNamePrefix());
}
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){
private static boolean isAndroid(int pkgId){
return pkgId==1;
}
private int toPackageId(int resId){
private static int toPackageId(int resId){
return (resId >> 24 & 0xFF);
}
private static boolean isEmpty(String str){

View File

@ -184,6 +184,18 @@ public class ResXmlAttribute extends ValueItem implements AttributeValue, Compar
int getNamespaceReference(){
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){
if(ref == getNamespaceReference()){
return;

View File

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

View File

@ -1,18 +1,18 @@
/*
* 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.
*/
/*
* 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.container.FixedBlockContainer;
@ -23,10 +23,10 @@ public abstract class ResXmlNode extends FixedBlockContainer implements JSONCon
ResXmlNode(int childesCount) {
super(childesCount);
}
void onRemove(){
}
abstract void onRemoved();
abstract void linkStringReferences();
public abstract int getDepth();
abstract void addEvents(ParserEventList parserEventList);
public static final String NAME_node_type="node_type";
public static final String NAME_node_type = "node_type";
}

View File

@ -1,18 +1,18 @@
/*
* 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.
*/
/*
* 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;
@ -75,6 +75,14 @@ public class ResXmlTextNode extends ResXmlNode {
getResXmlText().setTextReference(ref);
}
@Override
void onRemoved(){
getResXmlText().onRemoved();
}
@Override
void linkStringReferences(){
getResXmlText().linkStringReferences();
}
@Override
public String toString(){
String txt=getText();
if(txt!=null){