refactor: clean up external pull parser and introduce brut.j.xml (#3709)
Some checks failed
Analyze / Analyze (push) Has been cancelled
CI / analyze-mac-aapt (aapt2_64) (push) Has been cancelled
CI / analyze-mac-aapt (aapt_64) (push) Has been cancelled
CI / analyze-linux-aapt (aapt) (push) Has been cancelled
CI / analyze-linux-aapt (aapt2) (push) Has been cancelled
CI / analyze-linux-aapt (aapt2_64) (push) Has been cancelled
CI / analyze-linux-aapt (aapt_64) (push) Has been cancelled
CI / analyze-windows-aapt (aapt.exe) (push) Has been cancelled
CI / analyze-windows-aapt (aapt2.exe) (push) Has been cancelled
CI / analyze-windows-aapt (aapt2_64.exe) (push) Has been cancelled
CI / analyze-windows-aapt (aapt_64.exe) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, macOS-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, ubuntu-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (11, windows-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, macOS-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, ubuntu-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (17, windows-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, macOS-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, ubuntu-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (21, windows-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, macOS-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, ubuntu-latest) (push) Has been cancelled
CI / Build/Test (JDK ${{ matrix.java }}, ${{ matrix.os }}) (8, windows-latest) (push) Has been cancelled
CI / Build apktool.jar (push) Has been cancelled

* refactor: clean up external pull parser and introduce brut.j.xml

We have no need for an XML pull parser in the project,
it was only used for testing, which is now done with XPath.

The external xpp3 library from org.ogce is obsolete and has
the issue of including javax.xml.namespace.QName which conflicts
with the JRE implementation that exists for a very long time now.
This makes direct usages of QName produce very obscure NPEs that
took me hours to figure out. This patch will allow further
optimization that is WIP.
The external library was replaced by the basic xmlpull API.

The MXSerializer has been cleaned and the features used by apktool
have been integrated into the custom implementation, now part of
a separate module called brut.j.xml.
Writing has been optimized by buffering write operations, inspired
by KXmlSerializer used by Android itself.

A class XmlPullUtils also written that allows copying from a
XmlPullParser into a XmlSerializer with or without an EventHandler.
We use it for AndroidManifestPullStreamDecoder (with EventHandler,
to allow omitting the uses-sdk tag), and for ResXmlPullStreamDecoder
(direct copy, without EventHandler).

saveDocument in ResXmlPatcher was tweaked to output proper output -
a new line after declaration and a new line after root element's
end tag.

TL;DR mostly behind the scene refactor, no end user changes.
This commit is contained in:
Igor Eisberg 2024-10-15 13:54:03 +03:00 committed by GitHub
parent 7033f4ee2f
commit b49e77087d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 887 additions and 839 deletions

View File

@ -6,9 +6,6 @@
static ** valueOf(java.lang.String);
}
# https://github.com/iBotPeaches/Apktool/issues/3602#issuecomment-2117317880
-dontwarn org.xmlpull.mxp1**
# https://github.com/iBotPeaches/Apktool/pull/3670#issuecomment-2296326878
-dontwarn com.google.j2objc.annotations.Weak
-dontwarn com.google.j2objc.annotations.RetainedWith

View File

@ -25,13 +25,13 @@ tasks {
}
dependencies {
api(project(":brut.j.dir"))
api(project(":brut.j.util"))
api(project(":brut.j.common"))
api(project(":brut.j.util"))
api(project(":brut.j.dir"))
api(project(":brut.j.xml"))
implementation(libs.baksmali)
implementation(libs.smali)
implementation(libs.xmlpull)
implementation(libs.guava)
implementation(libs.commons.lang3)
implementation(libs.commons.io)

View File

@ -21,13 +21,12 @@ import brut.androlib.apk.ApkInfo;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.*;
import brut.androlib.res.decoder.*;
import brut.androlib.res.util.ExtMXSerializer;
import brut.androlib.res.util.ExtXmlSerializer;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.androlib.res.xml.ResXmlPatcher;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.directory.FileDirectory;
import brut.xmlpull.MXSerializer;
import org.apache.commons.io.IOUtils;
import org.xmlpull.v1.XmlSerializer;
@ -75,7 +74,8 @@ public class ResourcesDecoder {
}
AXmlResourceParser axmlParser = new AndroidManifestResourceParser(mResTable);
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, getResXmlSerializer());
XmlSerializer xmlSerializer = newXmlSerializer();
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, xmlSerializer);
Directory inApk, out;
InputStream inputStream = null;
@ -157,7 +157,8 @@ public class ResourcesDecoder {
decoders.setDecoder("9patch", new Res9patchStreamDecoder());
AXmlResourceParser axmlParser = new AXmlResourceParser(mResTable);
decoders.setDecoder("xml", new ResXmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
XmlSerializer xmlSerializer = newXmlSerializer();
decoders.setDecoder("xml", new ResXmlPullStreamDecoder(axmlParser, xmlSerializer));
ResFileDecoder fileDecoder = new ResFileDecoder(decoders);
Directory in, out, outRes;
@ -170,7 +171,6 @@ public class ResourcesDecoder {
throw new AndrolibException(ex);
}
ExtMXSerializer xmlSerializer = getResXmlSerializer();
for (ResPackage pkg : mResTable.listMainPackages()) {
LOGGER.info("Decoding file-resources...");
@ -191,20 +191,24 @@ public class ResourcesDecoder {
}
}
private ExtMXSerializer getResXmlSerializer() {
ExtMXSerializer serial = new ExtMXSerializer();
serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " ");
serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, System.getProperty("line.separator"));
serial.setProperty(ExtXmlSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8");
serial.setDisabledAttrEscape(true);
return serial;
private XmlSerializer newXmlSerializer() throws AndrolibException {
try {
XmlSerializer serial = new MXSerializer();
serial.setFeature(MXSerializer.FEATURE_ATTR_VALUE_NO_ESCAPE, true);
serial.setProperty(MXSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8");
serial.setProperty(MXSerializer.PROPERTY_INDENTATION, " ");
serial.setProperty(MXSerializer.PROPERTY_LINE_SEPARATOR, System.getProperty("line.separator"));
return serial;
} catch (IllegalArgumentException | IllegalStateException ex) {
throw new AndrolibException(ex);
}
}
private void generateValuesFile(ResValuesFile valuesFile, Directory out,
ExtXmlSerializer serial) throws AndrolibException {
XmlSerializer serial) throws AndrolibException {
try {
OutputStream outStream = out.getFileOutput(valuesFile.getPath());
serial.setOutput((outStream), null);
serial.setOutput(outStream, null);
serial.startDocument(null, null);
serial.startTag(null, "resources");
@ -216,7 +220,6 @@ public class ResourcesDecoder {
}
serial.endTag(null, "resources");
serial.newLine();
serial.endDocument();
serial.flush();
outStream.close();

View File

@ -611,12 +611,12 @@ public class AXmlResourceParser implements XmlResourceParser {
}
@Override
public boolean getFeature(String feature) {
public boolean getFeature(String name) {
return false;
}
@Override
public void setFeature(String name, boolean value) throws XmlPullParserException {
public void setFeature(String name, boolean state) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}

View File

@ -20,122 +20,28 @@ import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.AXmlDecodingException;
import brut.androlib.exceptions.RawXmlEncounteredException;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.util.ExtXmlSerializer;
import brut.xmlpull.XmlPullUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.wrapper.XmlPullParserWrapper;
import org.xmlpull.v1.wrapper.XmlPullWrapperFactory;
import org.xmlpull.v1.wrapper.XmlSerializerWrapper;
import org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper;
import org.xmlpull.v1.XmlSerializer;
import java.io.*;
public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
public AndroidManifestPullStreamDecoder(AXmlResourceParser parser,
ExtXmlSerializer serializer) {
this.mParser = parser;
this.mSerial = serializer;
private final AXmlResourceParser mParser;
private final XmlSerializer mSerial;
public AndroidManifestPullStreamDecoder(AXmlResourceParser parser, XmlSerializer serial) {
mParser = parser;
mSerial = serial;
}
@Override
public void decode(InputStream in, OutputStream out)
throws AndrolibException {
public void decode(InputStream in, OutputStream out) throws AndrolibException {
try {
XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance();
XmlPullParserWrapper par = factory.newPullParserWrapper(mParser);
final ResTable resTable = mParser.getResTable();
XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, factory) {
final boolean hideSdkInfo = !resTable.getAnalysisMode();
@Override
public void event(XmlPullParser pp)
throws XmlPullParserException, IOException {
int type = pp.getEventType();
if (type == XmlPullParser.START_TAG) {
if ("manifest".equals(pp.getName())) {
try {
parseManifest(pp);
} catch (AndrolibException ignored) {}
} else if ("uses-sdk".equals(pp.getName())) {
try {
parseUsesSdk(pp);
} catch (AndrolibException ignored) {}
if (hideSdkInfo) {
return;
}
}
} else if (type == XmlPullParser.END_TAG
&& "uses-sdk".equals(pp.getName())) {
if (hideSdkInfo) {
return;
}
}
super.event(pp);
}
private void parseManifest(XmlPullParser pp)
throws AndrolibException {
for (int i = 0; i < pp.getAttributeCount(); i++) {
String ns = pp.getAttributeNamespace(i);
String name = pp.getAttributeName(i);
String value = pp.getAttributeValue(i);
if (value.isEmpty()) {
continue;
}
if (ns.isEmpty()) {
if (name.equals("package")) {
resTable.setPackageRenamed(value);
}
} else if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
switch (name) {
case "versionCode":
resTable.setVersionCode(value);
break;
case "versionName":
resTable.setVersionName(value);
break;
}
}
}
}
private void parseUsesSdk(XmlPullParser pp)
throws AndrolibException {
for (int i = 0; i < pp.getAttributeCount(); i++) {
String ns = pp.getAttributeNamespace(i);
String name = pp.getAttributeName(i);
String value = pp.getAttributeValue(i);
if (value.isEmpty()) {
continue;
}
if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
switch (name) {
case "minSdkVersion":
case "targetSdkVersion":
case "maxSdkVersion":
case "compileSdkVersion":
resTable.addSdkInfo(name, value);
break;
}
}
}
}
};
par.setInput(in, null);
ser.setOutput(out, null);
while (par.nextToken() != XmlPullParser.END_DOCUMENT) {
ser.event(par);
}
ser.flush();
mParser.setInput(in, null);
mSerial.setOutput(out, null);
XmlPullUtils.copy(mParser, mSerial, new EventHandler(mParser.getResTable()));
} catch (XmlPullParserException ex) {
throw new AXmlDecodingException("Could not decode XML", ex);
} catch (IOException ex) {
@ -143,6 +49,90 @@ public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
}
}
private final AXmlResourceParser mParser;
private final ExtXmlSerializer mSerial;
private static class EventHandler implements XmlPullUtils.EventHandler {
private final ResTable mResTable;
private final boolean mHideSdkInfo;
public EventHandler(ResTable resTable) {
mResTable = resTable;
mHideSdkInfo = !resTable.getAnalysisMode();
}
@Override
public boolean onEvent(XmlPullParser in, XmlSerializer out) throws XmlPullParserException {
int type = in.getEventType();
if (type == XmlPullParser.START_TAG) {
String name = in.getName();
if (name.equals("manifest")) {
parseManifest(in);
} else if (name.equals("uses-sdk")) {
parseUsesSdk(in);
if (mHideSdkInfo) {
return true;
}
}
} else if (type == XmlPullParser.END_TAG) {
String name = in.getName();
if (name.equals("uses-sdk")) {
if (mHideSdkInfo) {
return true;
}
}
}
return false;
}
private void parseManifest(XmlPullParser in) {
for (int i = 0; i < in.getAttributeCount(); i++) {
String ns = in.getAttributeNamespace(i);
String name = in.getAttributeName(i);
String value = in.getAttributeValue(i);
if (value.isEmpty()) {
continue;
}
if (ns.isEmpty()) {
if (name.equals("package")) {
mResTable.setPackageRenamed(value);
}
} else if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
switch (name) {
case "versionCode":
mResTable.setVersionCode(value);
break;
case "versionName":
mResTable.setVersionName(value);
break;
}
}
}
}
private void parseUsesSdk(XmlPullParser in) {
for (int i = 0; i < in.getAttributeCount(); i++) {
String ns = in.getAttributeNamespace(i);
String name = in.getAttributeName(i);
String value = in.getAttributeValue(i);
if (value.isEmpty()) {
continue;
}
if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
switch (name) {
case "minSdkVersion":
case "targetSdkVersion":
case "maxSdkVersion":
case "compileSdkVersion":
mResTable.addSdkInfo(name, value);
break;
}
}
}
}
}
}

View File

@ -19,45 +19,31 @@ package brut.androlib.res.decoder;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.AXmlDecodingException;
import brut.androlib.exceptions.RawXmlEncounteredException;
import brut.androlib.res.util.ExtXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import brut.xmlpull.XmlPullUtils;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.wrapper.XmlPullParserWrapper;
import org.xmlpull.v1.wrapper.XmlPullWrapperFactory;
import org.xmlpull.v1.wrapper.XmlSerializerWrapper;
import org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper;
import org.xmlpull.v1.XmlSerializer;
import java.io.*;
public class ResXmlPullStreamDecoder implements ResStreamDecoder {
public ResXmlPullStreamDecoder(AXmlResourceParser parser,
ExtXmlSerializer serializer) {
this.mParser = parser;
this.mSerial = serializer;
private final AXmlResourceParser mParser;
private final XmlSerializer mSerial;
public ResXmlPullStreamDecoder(AXmlResourceParser parser, XmlSerializer serial) {
mParser = parser;
mSerial = serial;
}
@Override
public void decode(InputStream in, OutputStream out)
throws AndrolibException {
public void decode(InputStream in, OutputStream out) throws AndrolibException {
try {
XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance();
XmlPullParserWrapper par = factory.newPullParserWrapper(mParser);
XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, factory);
par.setInput(in, null);
ser.setOutput(out, null);
while (par.nextToken() != XmlPullParser.END_DOCUMENT) {
ser.event(par);
}
ser.flush();
mParser.setInput(in, null);
mSerial.setOutput(out, null);
XmlPullUtils.copy(mParser, mSerial);
} catch (XmlPullParserException ex) {
throw new AXmlDecodingException("Could not decode XML", ex);
} catch (IOException ex) {
throw new RawXmlEncounteredException("Could not decode XML", ex);
}
}
private final AXmlResourceParser mParser;
private final ExtXmlSerializer mSerial;
}

View File

@ -1,76 +0,0 @@
/*
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
*
* 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
*
* https://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 brut.androlib.res.util;
import org.xmlpull.renamed.MXSerializer;
import java.io.*;
public class ExtMXSerializer extends MXSerializer implements ExtXmlSerializer {
@Override
public void startDocument(String encoding, Boolean standalone)
throws IOException, IllegalArgumentException, IllegalStateException {
super.startDocument(encoding != null ? encoding : mDefaultEncoding, standalone);
this.newLine();
}
@Override
protected void writeAttributeValue(String value, Writer out) throws IOException {
if (mIsDisabledAttrEscape) {
out.write(value == null ? "" : value);
return;
}
super.writeAttributeValue(value, out);
}
@Override
public void setOutput(OutputStream os, String encoding) throws IOException {
super.setOutput(os, encoding != null ? encoding : mDefaultEncoding);
}
@Override
public Object getProperty(String name) throws IllegalArgumentException {
if (PROPERTY_DEFAULT_ENCODING.equals(name)) {
return mDefaultEncoding;
}
return super.getProperty(name);
}
@Override
public void setProperty(String name, Object value) throws IllegalArgumentException, IllegalStateException {
if (PROPERTY_DEFAULT_ENCODING.equals(name)) {
mDefaultEncoding = (String) value;
} else {
super.setProperty(name, value);
}
}
@Override
public ExtXmlSerializer newLine() throws IOException {
super.out.write(lineSeparator);
return this;
}
@Override
public void setDisabledAttrEscape(boolean disabled) {
mIsDisabledAttrEscape = disabled;
}
private String mDefaultEncoding;
private boolean mIsDisabledAttrEscape = false;
}

View File

@ -1,32 +0,0 @@
/*
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
*
* 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
*
* https://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 brut.androlib.res.util;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
public interface ExtXmlSerializer extends XmlSerializer {
ExtXmlSerializer newLine() throws IOException;
void setDisabledAttrEscape(boolean disabled);
String PROPERTY_SERIALIZER_INDENTATION = "http://xmlpull.org/v1/doc/properties.html#serializer-indentation";
String PROPERTY_SERIALIZER_LINE_SEPARATOR = "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator";
String PROPERTY_DEFAULT_ENCODING = "DEFAULT_ENCODING";
}

View File

@ -23,12 +23,15 @@ import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.io.*;
import java.util.*;
import java.util.logging.Logger;
@ -436,11 +439,19 @@ public final class ResXmlPatcher {
private static void saveDocument(File file, Document doc)
throws IOException, SAXException, ParserConfigurationException, TransformerException {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(file);
transformer.transform(source, result);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
byte[] xmlDecl = "<?xml version=\"1.0\" encoding=\"utf-8\"?>".getBytes(StandardCharsets.US_ASCII);
byte[] newLine = System.getProperty("line.separator").getBytes(StandardCharsets.US_ASCII);
try (OutputStream output = Files.newOutputStream(file.toPath())) {
output.write(xmlDecl);
output.write(newLine);
transformer.transform(new DOMSource(doc), new StreamResult(output));
output.write(newLine);
}
}
private static final String ACCESS_EXTERNAL_DTD = "http://javax.xml.XMLConstants/property/accessExternalDTD";

View File

@ -25,12 +25,16 @@ import brut.directory.Directory;
import brut.directory.FileDirectory;
import brut.util.OS;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
@ -40,43 +44,24 @@ import java.util.Map;
public abstract class TestUtils {
public static Map<String, String> parseStringsXml(File file)
throws BrutException {
public static Map<String, String> parseStringsXml(File file) throws BrutException {
try {
XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
xpp.setInput(new FileReader(file));
Document doc = getDocumentFromFile(file);
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "/resources/string[@name]";
NodeList nodes = (NodeList) xPath.evaluate(expression, doc, XPathConstants.NODESET);
int eventType;
String key = null;
Map<String, String> map = new HashMap<>();
while ((eventType = xpp.next()) != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
if ("string".equals(xpp.getName())) {
int attrCount = xpp.getAttributeCount();
for (int i = 0; i < attrCount; i++) {
if ("name".equals(xpp.getAttributeName(i))) {
key = xpp.getAttributeValue(i);
break;
}
}
}
break;
case XmlPullParser.END_TAG:
if ("string".equals(xpp.getName())) {
key = null;
}
break;
case XmlPullParser.TEXT:
if (key != null) {
map.put(key, xpp.getText());
}
break;
}
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
Node nameAttr = attrs.getNamedItem("name");
map.put(nameAttr.getNodeValue(), node.getTextContent());
}
return map;
} catch (IOException | XmlPullParserException ex) {
} catch (XPathExpressionException ex) {
throw new BrutException(ex);
}
}
@ -84,7 +69,7 @@ public abstract class TestUtils {
public static Document getDocumentFromFile(File file) throws BrutException {
try {
return ResXmlPatcher.loadDocument(file);
} catch (ParserConfigurationException | SAXException | IOException ex) {
} catch (IOException | SAXException | ParserConfigurationException ex) {
throw new BrutException(ex);
}
}

View File

@ -11,5 +11,5 @@
<meta-data name="test_int" value="12345" />
</application>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<permission android:featureFlag="brut.feature.flag" android:label="Test Permission" android:name="brut.permission.TEST" android:permissionGroup="android.permission-group.UNDEFINED" android:protectionLevel="signature"/>
<permission android:featureFlag="brut.feature.flag" android:label="Test Permission" android:name="brut.permission.TEST" android:permissionGroup="android.permission-group.UNDEFINED" android:protectionLevel="signature" />
</manifest>

View File

@ -1,5 +1,5 @@
dependencies {
implementation(project(":brut.j.common"))
implementation(project(":brut.j.util"))
implementation(libs.commons.io)
implementation(project(":brut.j.common"))
implementation(project(":brut.j.util"))
implementation(libs.commons.io)
}

View File

@ -0,0 +1,3 @@
dependencies {
api(libs.xmlpull)
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
*
* 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
*
* https://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 brut.xmlpull;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.*;
public class XmlPullUtils {
private static final String PROPERTY_XMLDECL_STANDALONE
= "http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone";
public interface EventHandler {
boolean onEvent(XmlPullParser in, XmlSerializer out) throws XmlPullParserException;
}
public static void copy(XmlPullParser in, XmlSerializer out)
throws XmlPullParserException, IOException {
copy(in, out, null);
}
public static void copy(XmlPullParser in, XmlSerializer out, EventHandler handler)
throws XmlPullParserException, IOException {
Boolean standalone = (Boolean) in.getProperty(PROPERTY_XMLDECL_STANDALONE);
// Some parsers may have already consumed the event that starts the
// document, so we manually emit that event here for consistency
if (in.getEventType() == XmlPullParser.START_DOCUMENT) {
out.startDocument(in.getInputEncoding(), standalone);
}
while (true) {
int event = in.nextToken();
if (event == XmlPullParser.START_DOCUMENT) {
out.startDocument(in.getInputEncoding(), standalone);
continue;
}
if (event == XmlPullParser.END_DOCUMENT) {
out.endDocument();
break;
}
if (handler != null && handler.onEvent(in, out)) {
continue;
}
switch (event) {
case XmlPullParser.START_TAG:
if (!in.getFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES)) {
int nsStart = in.getNamespaceCount(in.getDepth() - 1);
int nsEnd = in.getNamespaceCount(in.getDepth());
for (int i = nsStart; i < nsEnd; i++) {
String prefix = in.getNamespacePrefix(i);
String ns = in.getNamespaceUri(i);
out.setPrefix(prefix, ns);
}
}
out.startTag(normalizeNamespace(in.getNamespace()), in.getName());
for (int i = 0; i < in.getAttributeCount(); i++) {
String ns = normalizeNamespace(in.getAttributeNamespace(i));
String name = in.getAttributeName(i);
String value = in.getAttributeValue(i);
out.attribute(ns, name, value);
}
break;
case XmlPullParser.END_TAG:
out.endTag(normalizeNamespace(in.getNamespace()), in.getName());
break;
case XmlPullParser.TEXT:
out.text(in.getText());
break;
case XmlPullParser.CDSECT:
out.cdsect(in.getText());
break;
case XmlPullParser.ENTITY_REF:
out.entityRef(in.getName());
break;
case XmlPullParser.IGNORABLE_WHITESPACE:
out.ignorableWhitespace(in.getText());
break;
case XmlPullParser.PROCESSING_INSTRUCTION:
out.processingInstruction(in.getText());
break;
case XmlPullParser.COMMENT:
out.comment(in.getText());
break;
case XmlPullParser.DOCDECL:
out.docdecl(in.getText());
break;
default:
throw new IllegalStateException("Unknown event: " + event);
}
}
}
/**
* Some parsers may return an empty string when a namespace in unsupported,
* which can confuse serializers. This method normalizes empty strings to
* be null.
*/
private static String normalizeNamespace(String namespace) {
if (namespace == null || namespace.isEmpty()) {
return null;
} else {
return namespace;
}
}
}

View File

@ -83,7 +83,7 @@ subprojects {
targetCompatibility = JavaVersion.VERSION_1_8
}
val mavenProjects = arrayOf("apktool-lib", "apktool-cli", "brut.j.common", "brut.j.util", "brut.j.dir")
val mavenProjects = arrayOf("brut.j.common", "brut.j.util", "brut.j.dir", "brut.j.xml", "apktool-lib", "apktool-cli")
if (project.name in mavenProjects) {
apply(plugin = "maven-publish")

View File

@ -8,7 +8,7 @@ guava = "33.3.1-jre"
junit = "4.13.2"
r8 = "8.5.35"
smali = "3.0.8"
xmlpull = "1.1.6"
xmlpull = "1.1.3.1"
xmlunit = "2.10.0"
[libraries]
@ -21,5 +21,5 @@ guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit = { module = "junit:junit", version.ref = "junit" }
r8 = { module = "com.android.tools:r8", version.ref = "r8" }
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
xmlpull = { module = "org.ogce:xpp3", version.ref = "xmlpull" }
xmlpull = { module = "xmlpull:xmlpull", version.ref = "xmlpull" }
xmlunit = { module = "org.xmlunit:xmlunit-legacy", version.ref = "xmlunit" }

View File

@ -1,5 +1,5 @@
rootProject.name = "apktool-cli"
include("brut.j.common", "brut.j.util", "brut.j.dir", "brut.apktool:apktool-lib", "brut.apktool:apktool-cli")
include("brut.j.common", "brut.j.util", "brut.j.dir", "brut.j.xml", "brut.apktool:apktool-lib", "brut.apktool:apktool-cli")
dependencyResolutionManagement {
versionCatalogs {