ARSCLib/src/main/java/com/reandroid/xml/parser/XMLDocumentParser.java
REAndroid bb46abb499 encode XML source correctly
* add 's' suffix on type directories
* write uncompressed files at decoding
2023-01-12 13:46:58 -05:00

422 lines
13 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.xml.parser;
import com.reandroid.xml.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class XMLDocumentParser {
private final XmlPullParser mParser;
private XMLDocument mResDocument;
private XMLElement mCurrentElement;
private boolean mNameSpaceCreated;
private StringBuilder mCurrentText;
private List<XMLComment> mComments;
private int mIndent;
public XMLDocumentParser(XmlPullParser parser){
this.mParser=parser;
}
public XMLDocumentParser(InputStream in) throws XMLParseException {
this(createParser(in));
}
public XMLDocumentParser(File file) throws XMLParseException {
this(createParser(file));
}
public XMLDocumentParser(String text) throws XMLParseException {
this(createParser(text));
}
public XMLDocument parse() throws XMLParseException {
try {
XMLDocument document= parseDocument();
close();
return document;
} catch (XmlPullParserException | IOException e) {
XMLParseException ex=new XMLParseException(e.getMessage());
ex.setStackTrace(e.getStackTrace());
throw ex;
}
}
private void close(){
closeReader();
closeFileInputStream();
mResDocument=null;
mCurrentElement=null;
mCurrentText=null;
mComments=null;
}
private void closeFileInputStream(){
if(!(mParser instanceof MXParser)){
return;
}
MXParser parser=(MXParser) mParser;
InputStream inputStream = parser.getInputStream();
if(!(inputStream instanceof FileInputStream)){
return;
}
try {
inputStream.close();
} catch (IOException ignored) {
}
}
private void closeReader(){
if(!(mParser instanceof MXParser)){
return;
}
MXParser parser=(MXParser) mParser;
Reader reader = parser.getReader();
if(reader!=null){
try {
reader.close();
} catch (IOException ignored) {
}
}
}
private XMLDocument parseDocument() throws XmlPullParserException, IOException {
mResDocument=null;
int type;
while ((type=mParser.nextToken()) !=XmlPullParser.END_DOCUMENT){
event(type);
}
event(XmlPullParser.END_DOCUMENT);
if(mResDocument==null){
throw new XmlPullParserException("Failed to parse/empty document");
}
return mResDocument;
}
private void event(int type) {
if (type == XmlPullParser.START_DOCUMENT){
onStartDocument();
}else if (type == XmlPullParser.END_DOCUMENT){
onEndDocument();
}else if (type == XmlPullParser.START_TAG){
onStartTag();
}else if (type == XmlPullParser.END_TAG){
onEndTag();
}else if (type == XmlPullParser.TEXT){
onText();
}else if (type == XmlPullParser.ENTITY_REF){
onEntityRef();
}else if (type == XmlPullParser.COMMENT){
onComment();
}else if (type == XmlPullParser.IGNORABLE_WHITESPACE){
onIgnorableWhiteSpace();
}else {
onUnknownType(type);
}
}
private void onStartDocument(){
mResDocument=new XMLDocument();
mIndent=-1;
}
private void onEndDocument(){
flushComments(null);
applyIndent(mResDocument);
}
private void onStartTag(){
String name=mParser.getName();
flushTextContent();
if(mCurrentElement==null){
if(mResDocument==null){
onStartDocument();
}
mCurrentElement=new XMLElement(name);
mResDocument.setDocumentElement(mCurrentElement);
}else {
mCurrentElement=mCurrentElement.createElement(name);
}
mCurrentElement.setColumnNumber(mParser.getColumnNumber());
mCurrentElement.setLineNumber(mParser.getLineNumber());
checkIndent();
flushComments(mCurrentElement);
String ns=mParser.getNamespace();
if(!XMLUtil.isEmpty(ns)){
String prefix=mParser.getPrefix();
if(!XMLUtil.isEmpty(prefix)){
String tagName=appendPrefix(prefix,name);
mCurrentElement.setTagName(tagName);
checkNamespace(prefix, ns);
}
}
loadAttributes();
}
private void loadAttributes(){
int max=mParser.getAttributeCount();
for(int i=0; i<max; i++){
onAttribute(i);
}
}
private void onAttribute(int i){
String attrName=mParser.getAttributeName(i);
String attrValue=mParser.getAttributeValue(i);
String prefix=mParser.getAttributePrefix(i);
if(!XMLUtil.isEmpty(prefix)){
prefix=validateNameSpace(prefix, i);
attrName=appendPrefix(prefix, attrName);
checkNamespace(prefix, i);
}
XMLAttribute attr=mCurrentElement.setAttribute(attrName, attrValue);
addIds(attr, i);
}
private void addIds(XMLAttribute attr, int i){
if(attr==null){
return;
}
XmlPullParser parser=mParser;
if(!(parser instanceof AttrIDProvider)){
return;
}
AttrIDProvider idProvider=(AttrIDProvider)parser;
int nameId=idProvider.getAttributeNameResourceId(i);
int valueId=idProvider.getAttributeValueResourceId(i);
attr.setNameId(nameId);
attr.setValueId(valueId);
}
private String appendPrefix(String prefix, String attrName){
if(!prefix.endsWith(":")){
prefix=prefix+":";
}
if(!attrName.startsWith(prefix)){
attrName=prefix+attrName;
}
return attrName;
}
private void checkNamespace(String prefix, int i){
NameSpaceItem nsItem=mCurrentElement.getNameSpaceItemForPrefix(prefix);
if(nsItem!=null){
return;
}
String nsUri=null;
try {
nsUri=mParser.getNamespaceUri(i);
} catch (XmlPullParserException e) {
}
checkNamespace(prefix, nsUri);
}
private String validateNameSpace(String prefix, int i){
XmlPullParser parser=mParser;
if(!(parser instanceof AttrIDProvider)){
return prefix;
}
AttrIDProvider idProvider=(AttrIDProvider)parser;
int resId=idProvider.getAttributeNameResourceId(i);
if(!isResourceId(resId)){
return prefix;
}
boolean isAndroid;
if(!isAndroid(resId)){
if(!"android".equals(prefix)){
return prefix;
}
isAndroid=false;
}else {
if("android".equals(prefix)){
return prefix;
}
isAndroid=true;
}
NameSpaceItem ns;
if(isAndroid){
ns=NameSpaceItem.getAndroid();
}else {
ns=NameSpaceItem.getApp();
}
String prefixNew=ns.getPrefix();
NameSpaceItem nsItem=mCurrentElement.getNameSpaceItemForPrefix(prefixNew);
if(nsItem==null){
mCurrentElement.addNameSpace(ns);
mNameSpaceCreated=true;
}
return prefixNew;
}
private void checkNamespace(String prefix, String nsUri){
NameSpaceItem nsItem=mCurrentElement.getNameSpaceItemForPrefix(prefix);
if(nsItem!=null){
return;
}
nsItem=new NameSpaceItem(prefix, nsUri);
mCurrentElement.addNameSpace(nsItem);
mNameSpaceCreated=true;
}
private void onEndTag(){
flushTextContent();
if(mNameSpaceCreated){
mCurrentElement.applyNameSpaceItems();
mNameSpaceCreated=false;
}
mCurrentElement=mCurrentElement.getParent();
}
private void onText(){
String textContent=mParser.getText();
appendText(textContent);
}
private void appendText(String text){
if(text==null){
return;
}
if(mCurrentText==null){
mCurrentText=new StringBuilder();
}
mCurrentText.append(text);
}
private void flushTextContent(){
if(mCurrentText==null){
return;
}
String text=mCurrentText.toString();
mCurrentText=null;
if(text.trim().length()==0 && !mCurrentElement.hasTextContent()){
return;
}
mCurrentElement.addText(new XMLText(text));
}
private void onEntityRef(){
String name=mParser.getName();
if(XMLUtil.isEmpty(name)){
return;
}
appendText("&");
appendText(name);
appendText(";");
}
private void onComment(){
String commentText=mParser.getText();
addComment(commentText);
}
private void addComment(String commentText){
if(XMLUtil.isEmpty(commentText)){
return;
}
XMLComment commentElement=new XMLComment();
commentElement.setCommentText(commentText);
commentElement.setColumnNumber(mParser.getColumnNumber());
commentElement.setLineNumber(mParser.getLineNumber());
addComment(commentElement);
}
private void addComment(XMLComment ce){
if(ce==null){
return;
}
if(mComments==null){
mComments=new ArrayList<>();
}
mComments.add(ce);
}
private void flushComments(XMLElement element){
if(mComments==null){
return;
}
if(element!=null){
element.addComments(mComments);
}
mComments.clear();
mComments=null;
}
private void onIgnorableWhiteSpace(){
}
private void onIgnore(int type){
}
private void onUnknownType(int type){
String typeName=toTypeName(type);
//System.err.println("Unknown TYPE = "+typeName+" "+type);
}
private String toTypeName(int type){
String[] allTypes=XmlPullParser.TYPES;
if(type<0 || type>=allTypes.length){
return "type:"+type;
}
return allTypes[type];
}
private void checkIndent(){
if(mIndent>=0){
return;
}
String txt=mParser.getText();
if(txt==null){
return;
}
int len=txt.length();
int col=mParser.getColumnNumber();
mIndent=col-len;
if(mIndent<0){
mIndent=0;
}
}
private void applyIndent(XMLDocument resDocument){
if(mIndent<=0 || mIndent>5 || resDocument==null){
mIndent=-1;
return;
}
resDocument.setIndent(mIndent);
mIndent=-1;
}
private static XmlPullParser createParser(String text) throws XMLParseException {
if(text == null){
throw new XMLParseException("Text is null, failed to create XmlPullParser");
}
InputStream in = new ByteArrayInputStream(text.getBytes());
return createParser(in);
}
private static XmlPullParser createParser(File file) throws XMLParseException {
if(file == null){
throw new XMLParseException("File is null, failed to create XmlPullParser");
}
if(!file.isFile()){
throw new XMLParseException("No such file : "+file.getAbsolutePath());
}
InputStream in;
try {
in=new FileInputStream(file);
return createParser(in);
} catch (FileNotFoundException e) {
throw new XMLParseException(e.getMessage());
}
}
private static XmlPullParser createParser(InputStream in) throws XMLParseException {
try {
XmlPullParser parser = new MXParserNonValidating();
parser.setInput(in, null);
return parser;
} catch (XmlPullParserException e) {
throw new XMLParseException(e.getMessage());
}
}
private static boolean isAndroid(int id){
int pkgId=toPackageId(id);
return pkgId>0 && pkgId<=ANDROID_PACKAGE_MAX;
}
private static boolean isResourceId(int id){
int pkgId=toPackageId(id);
return pkgId>0 && pkgId<128;
}
private static int toPackageId(int id){
if(id<=0xff){
return id;
}
return ((id >> 24) & 0xff);
}
private static final int ANDROID_PACKAGE_MAX=3;
}