mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-30 06:14:25 +02:00
adapt AOSP xml serializer
This commit is contained in:
parent
dd5233f13c
commit
092383e3e0
561
src/main/java/com/android/org/kxml2/io/KXmlSerializer.java
Normal file
561
src/main/java/com/android/org/kxml2/io/KXmlSerializer.java
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE. */
|
||||||
|
|
||||||
|
package com.android.org.kxml2.io;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.xmlpull.v1.*;
|
||||||
|
|
||||||
|
public class KXmlSerializer implements XmlSerializer {
|
||||||
|
|
||||||
|
private static final int BUFFER_LEN = 8192;
|
||||||
|
private final char[] mText = new char[BUFFER_LEN];
|
||||||
|
private int mPos;
|
||||||
|
private Writer writer;
|
||||||
|
private boolean pending;
|
||||||
|
private int auto;
|
||||||
|
private int depth;
|
||||||
|
private String[] elementStack = new String[12];
|
||||||
|
private int[] nspCounts = new int[4];
|
||||||
|
private String[] nspStack = new String[8];
|
||||||
|
private boolean[] indent = new boolean[4];
|
||||||
|
private boolean firstAttributeWritten;
|
||||||
|
private int indentAttributeReference;
|
||||||
|
private boolean unicode;
|
||||||
|
private String encoding;
|
||||||
|
|
||||||
|
private void append(char c) throws IOException {
|
||||||
|
if(mPos >= BUFFER_LEN){
|
||||||
|
flushBuffer();
|
||||||
|
}
|
||||||
|
mText[mPos++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void append(String str, int i, int length) throws IOException {
|
||||||
|
while (length > 0){
|
||||||
|
if(mPos == BUFFER_LEN){
|
||||||
|
flushBuffer();
|
||||||
|
}
|
||||||
|
int batch = BUFFER_LEN - mPos;
|
||||||
|
if(batch > length){
|
||||||
|
batch = length;
|
||||||
|
}
|
||||||
|
str.getChars(i, i + batch, mText, mPos);
|
||||||
|
i += batch;
|
||||||
|
length -= batch;
|
||||||
|
mPos += batch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendSpace(int length) throws IOException {
|
||||||
|
while (length > 0){
|
||||||
|
if(mPos == BUFFER_LEN){
|
||||||
|
flushBuffer();
|
||||||
|
}
|
||||||
|
int batch = BUFFER_LEN - mPos;
|
||||||
|
if(batch > length){
|
||||||
|
batch = length;
|
||||||
|
}
|
||||||
|
Arrays.fill(mText, mPos, mPos + batch, ' ');
|
||||||
|
length -= batch;
|
||||||
|
mPos += batch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void append(String str) throws IOException {
|
||||||
|
append(str, 0, str.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushBuffer() throws IOException {
|
||||||
|
if(mPos > 0){
|
||||||
|
writer.write(mText, 0, mPos);
|
||||||
|
writer.flush();
|
||||||
|
mPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void check(boolean close) throws IOException {
|
||||||
|
if(!pending)
|
||||||
|
return;
|
||||||
|
|
||||||
|
depth++;
|
||||||
|
pending = false;
|
||||||
|
|
||||||
|
if(indent.length <= depth){
|
||||||
|
boolean[] hlp = new boolean[depth + 4];
|
||||||
|
System.arraycopy(indent, 0, hlp, 0, depth);
|
||||||
|
indent = hlp;
|
||||||
|
}
|
||||||
|
indent[depth] = indent[depth - 1];
|
||||||
|
|
||||||
|
for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++){
|
||||||
|
append(" xmlns");
|
||||||
|
if(!nspStack[i * 2].isEmpty()){
|
||||||
|
append(':');
|
||||||
|
append(nspStack[i * 2]);
|
||||||
|
}
|
||||||
|
else if(getNamespace().isEmpty() && !nspStack[i * 2 + 1].isEmpty())
|
||||||
|
throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
|
||||||
|
append("=\"");
|
||||||
|
writeEscaped(nspStack[i * 2 + 1], '"');
|
||||||
|
append('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nspCounts.length <= depth + 1){
|
||||||
|
int[] hlp = new int[depth + 8];
|
||||||
|
System.arraycopy(nspCounts, 0, hlp, 0, depth + 1);
|
||||||
|
nspCounts = hlp;
|
||||||
|
}
|
||||||
|
|
||||||
|
nspCounts[depth + 1] = nspCounts[depth];
|
||||||
|
if(close){
|
||||||
|
append(" />");
|
||||||
|
} else {
|
||||||
|
append('>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeEscaped(String s, int quot) throws IOException {
|
||||||
|
for (int i = 0; i < s.length(); i++){
|
||||||
|
char c = s.charAt(i);
|
||||||
|
switch (c){
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
case '\t':
|
||||||
|
if(quot == -1)
|
||||||
|
append(c);
|
||||||
|
else
|
||||||
|
append("&#"+((int) c)+';');
|
||||||
|
break;
|
||||||
|
case '&' :
|
||||||
|
append("&");
|
||||||
|
break;
|
||||||
|
case '>' :
|
||||||
|
append(">");
|
||||||
|
break;
|
||||||
|
case '<' :
|
||||||
|
append("<");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(c == quot){
|
||||||
|
append(c == '"' ? """ : "'");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
boolean allowedInXml = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
|
||||||
|
if(allowedInXml){
|
||||||
|
if(unicode || c < 127){
|
||||||
|
append(c);
|
||||||
|
} else {
|
||||||
|
append("&#" + ((int) c) + ";");
|
||||||
|
}
|
||||||
|
} else if(Character.isHighSurrogate(c) && i < s.length() - 1){
|
||||||
|
writeSurrogate(c, s.charAt(i + 1));
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
reportInvalidCharacter(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static void reportInvalidCharacter(char ch){
|
||||||
|
throw new IllegalArgumentException("Illegal character (U+" + Integer.toHexString((int) ch) + ")");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void docdecl(String dd) throws IOException {
|
||||||
|
append("<!DOCTYPE");
|
||||||
|
append(dd);
|
||||||
|
append('>');
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void endDocument() throws IOException {
|
||||||
|
while (depth > 0){
|
||||||
|
endTag(elementStack[depth * 3 - 3], elementStack[depth * 3 - 1]);
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void entityRef(String name) throws IOException {
|
||||||
|
check(false);
|
||||||
|
append('&');
|
||||||
|
append(name);
|
||||||
|
append(';');
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean getFeature(String name){
|
||||||
|
return "http://xmlpull.org/v1/doc/features.html#indent-output"
|
||||||
|
.equals(name) && indent[depth];
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String getPrefix(String namespace, boolean create){
|
||||||
|
try {
|
||||||
|
return getPrefix(namespace, false, create);
|
||||||
|
}
|
||||||
|
catch (IOException e){
|
||||||
|
throw new RuntimeException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private String getPrefix(String namespace, boolean includeDefault, boolean create)
|
||||||
|
throws IOException {
|
||||||
|
int[] nspCounts = this.nspCounts;
|
||||||
|
int depth = this.depth;
|
||||||
|
String[] nspStack = this.nspStack;
|
||||||
|
|
||||||
|
for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0;i -= 2){
|
||||||
|
if(nspStack[i + 1].equals(namespace)
|
||||||
|
&& (includeDefault
|
||||||
|
|| !nspStack[i].isEmpty())){
|
||||||
|
String cand = nspStack[i];
|
||||||
|
for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++){
|
||||||
|
if(nspStack[j].equals(cand)){
|
||||||
|
cand = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(cand != null){
|
||||||
|
return cand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!create){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String prefix;
|
||||||
|
|
||||||
|
if(namespace.isEmpty()) {
|
||||||
|
prefix = "";
|
||||||
|
}else {
|
||||||
|
do {
|
||||||
|
prefix = "n" + (auto++);
|
||||||
|
for (int i = nspCounts[depth + 1] * 2 - 2;i >= 0;i -= 2){
|
||||||
|
if(prefix.equals(nspStack[i])){
|
||||||
|
prefix = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (prefix == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean p = pending;
|
||||||
|
pending = false;
|
||||||
|
setPrefix(prefix, namespace);
|
||||||
|
pending = p;
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getProperty(String name){
|
||||||
|
throw new RuntimeException("Unsupported property: "+name);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void ignorableWhitespace(String s) throws IOException {
|
||||||
|
text(s);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setFeature(String name, boolean value){
|
||||||
|
if("http://xmlpull.org/v1/doc/features.html#indent-output".equals(name)){
|
||||||
|
indent[depth] = value;
|
||||||
|
firstAttributeWritten = false;
|
||||||
|
}else {
|
||||||
|
throw new RuntimeException("Unsupported Feature: "+name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setProperty(String name, Object value){
|
||||||
|
throw new RuntimeException("Unsupported Property:" + value);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setPrefix(String prefix, String namespace)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
check(false);
|
||||||
|
if(prefix == null) {
|
||||||
|
prefix = "";
|
||||||
|
}
|
||||||
|
if(namespace == null) {
|
||||||
|
namespace = "";
|
||||||
|
}
|
||||||
|
String defined = getPrefix(namespace, true, false);
|
||||||
|
if(prefix.equals(defined)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = (nspCounts[depth + 1]++) << 1;
|
||||||
|
|
||||||
|
if(nspStack.length < pos + 1){
|
||||||
|
String[] hlp = new String[nspStack.length + 16];
|
||||||
|
System.arraycopy(nspStack, 0, hlp, 0, pos);
|
||||||
|
nspStack = hlp;
|
||||||
|
}
|
||||||
|
|
||||||
|
nspStack[pos++] = prefix;
|
||||||
|
nspStack[pos] = namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutput(Writer writer){
|
||||||
|
this.writer = writer;
|
||||||
|
nspCounts[0] = 2;
|
||||||
|
nspCounts[1] = 2;
|
||||||
|
nspStack[0] = "";
|
||||||
|
nspStack[1] = "";
|
||||||
|
nspStack[2] = "xml";
|
||||||
|
nspStack[3] = "http://www.w3.org/XML/1998/namespace";
|
||||||
|
pending = false;
|
||||||
|
auto = 0;
|
||||||
|
depth = 0;
|
||||||
|
|
||||||
|
unicode = false;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void setOutput(OutputStream os, String encoding)
|
||||||
|
throws IOException {
|
||||||
|
if(os == null) {
|
||||||
|
throw new IllegalArgumentException("os == null");
|
||||||
|
}
|
||||||
|
setOutput(encoding == null
|
||||||
|
? new OutputStreamWriter(os)
|
||||||
|
: new OutputStreamWriter(os, encoding));
|
||||||
|
this.encoding = encoding;
|
||||||
|
if(encoding != null && encoding.toLowerCase(Locale.US).startsWith("utf")){
|
||||||
|
unicode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void startDocument(String encoding, Boolean standalone) throws IOException {
|
||||||
|
append("<?xml version='1.0' ");
|
||||||
|
|
||||||
|
if(encoding != null){
|
||||||
|
this.encoding = encoding;
|
||||||
|
if(encoding.toLowerCase(Locale.US).startsWith("utf")){
|
||||||
|
unicode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.encoding != null){
|
||||||
|
append("encoding='");
|
||||||
|
append(this.encoding);
|
||||||
|
append("' ");
|
||||||
|
}
|
||||||
|
if(standalone != null){
|
||||||
|
append("standalone='");
|
||||||
|
append(standalone ? "yes" : "no");
|
||||||
|
append("' ");
|
||||||
|
}
|
||||||
|
append("?>");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public XmlSerializer startTag(String namespace, String name)
|
||||||
|
throws IOException {
|
||||||
|
check(false);
|
||||||
|
firstAttributeWritten = false;
|
||||||
|
indentAttributeReference = 0;
|
||||||
|
if(indent[depth]){
|
||||||
|
append('\r');
|
||||||
|
append('\n');
|
||||||
|
int spaceLength = 2 * depth;
|
||||||
|
appendSpace(spaceLength);
|
||||||
|
indentAttributeReference = spaceLength;
|
||||||
|
}
|
||||||
|
int esp = depth * 3;
|
||||||
|
if(elementStack.length < esp + 3){
|
||||||
|
String[] hlp = new String[elementStack.length + 12];
|
||||||
|
System.arraycopy(elementStack, 0, hlp, 0, esp);
|
||||||
|
elementStack = hlp;
|
||||||
|
}
|
||||||
|
String prefix = namespace == null?
|
||||||
|
"" : getPrefix(namespace, true, true);
|
||||||
|
|
||||||
|
if(namespace != null && namespace.isEmpty()){
|
||||||
|
for (int i = nspCounts[depth]; i < nspCounts[depth + 1]; i++){
|
||||||
|
if(nspStack[i * 2].isEmpty() && !nspStack[i * 2 + 1].isEmpty()){
|
||||||
|
throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elementStack[esp++] = namespace;
|
||||||
|
elementStack[esp++] = prefix;
|
||||||
|
elementStack[esp] = name;
|
||||||
|
append('<');
|
||||||
|
indentAttributeReference += 1;
|
||||||
|
if(!prefix.isEmpty()){
|
||||||
|
append(prefix);
|
||||||
|
append(':');
|
||||||
|
indentAttributeReference += prefix.length() + 1;
|
||||||
|
}
|
||||||
|
append(name);
|
||||||
|
indentAttributeReference += name.length();
|
||||||
|
pending = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public XmlSerializer attribute(String namespace, String name, String value)
|
||||||
|
throws IOException {
|
||||||
|
if(!pending) {
|
||||||
|
throw new IllegalStateException("illegal position for attribute");
|
||||||
|
}
|
||||||
|
if(namespace == null) {
|
||||||
|
namespace = "";
|
||||||
|
}
|
||||||
|
String prefix = namespace.isEmpty() ?
|
||||||
|
"" : getPrefix(namespace, false, true);
|
||||||
|
attributeIndent();
|
||||||
|
append(' ');
|
||||||
|
if(!prefix.isEmpty()){
|
||||||
|
append(prefix);
|
||||||
|
append(':');
|
||||||
|
}
|
||||||
|
append(name);
|
||||||
|
append('=');
|
||||||
|
char q = value.indexOf('"') == -1 ? '"' : '\'';
|
||||||
|
append(q);
|
||||||
|
writeEscaped(value, q);
|
||||||
|
append(q);
|
||||||
|
firstAttributeWritten = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
check(false);
|
||||||
|
flushBuffer();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public XmlSerializer endTag(String namespace, String name)throws IOException {
|
||||||
|
if(!pending) {
|
||||||
|
depth--;
|
||||||
|
}
|
||||||
|
if((namespace == null
|
||||||
|
&& elementStack[depth * 3] != null)
|
||||||
|
|| (namespace != null
|
||||||
|
&& !namespace.equals(elementStack[depth * 3]))
|
||||||
|
|| !elementStack[depth * 3 + 2].equals(name)) {
|
||||||
|
throw new IllegalArgumentException("</{"+namespace+"}"+name+"> does not match start");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pending){
|
||||||
|
check(true);
|
||||||
|
depth--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(indent[depth + 1]){
|
||||||
|
append('\r');
|
||||||
|
append('\n');
|
||||||
|
appendSpace(2 * depth);
|
||||||
|
}
|
||||||
|
append("</");
|
||||||
|
String prefix = elementStack[depth * 3 + 1];
|
||||||
|
if(!prefix.isEmpty()){
|
||||||
|
append(prefix);
|
||||||
|
append(':');
|
||||||
|
}
|
||||||
|
append(name);
|
||||||
|
append('>');
|
||||||
|
}
|
||||||
|
|
||||||
|
nspCounts[depth + 1] = nspCounts[depth];
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String getNamespace(){
|
||||||
|
return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 3];
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String getName(){
|
||||||
|
return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 1];
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int getDepth(){
|
||||||
|
return pending ? depth + 1 : depth;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public XmlSerializer text(String text) throws IOException {
|
||||||
|
check(false);
|
||||||
|
indent[depth] = false;
|
||||||
|
writeEscaped(text, -1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public XmlSerializer text(char[] text, int start, int len)
|
||||||
|
throws IOException {
|
||||||
|
text(new String(text, start, len));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void cdsect(String data) throws IOException {
|
||||||
|
check(false);
|
||||||
|
data = data.replace("]]>", "]]]]><![CDATA[>");
|
||||||
|
append("<![CDATA[");
|
||||||
|
for (int i = 0; i < data.length(); ++i){
|
||||||
|
char ch = data.charAt(i);
|
||||||
|
boolean allowedInCdata = (ch >= 0x20 && ch <= 0xd7ff) ||
|
||||||
|
(ch == '\t' || ch == '\n' || ch == '\r') ||
|
||||||
|
(ch >= 0xe000 && ch <= 0xfffd);
|
||||||
|
if(allowedInCdata){
|
||||||
|
append(ch);
|
||||||
|
} else if(Character.isHighSurrogate(ch) && i < data.length() - 1){
|
||||||
|
// Character entities aren't valid in CDATA, so break out for this.
|
||||||
|
append("]]>");
|
||||||
|
writeSurrogate(ch, data.charAt(++i));
|
||||||
|
append("<![CDATA[");
|
||||||
|
} else {
|
||||||
|
reportInvalidCharacter(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append("]]>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSurrogate(char high, char low) throws IOException {
|
||||||
|
if(!Character.isLowSurrogate(low)){
|
||||||
|
throw new IllegalArgumentException("Bad surrogate pair (U+" + Integer.toHexString((int) high) +
|
||||||
|
" U+" + Integer.toHexString((int) low) + ")");
|
||||||
|
}
|
||||||
|
int codePoint = Character.toCodePoint(high, low);
|
||||||
|
append("&#" + codePoint + ";");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void comment(String comment) throws IOException {
|
||||||
|
check(false);
|
||||||
|
append("<!--");
|
||||||
|
append(comment);
|
||||||
|
append("-->");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void processingInstruction(String pi)
|
||||||
|
throws IOException {
|
||||||
|
check(false);
|
||||||
|
append("<?");
|
||||||
|
append(pi);
|
||||||
|
append("?>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attributeIndent() throws IOException {
|
||||||
|
if(!firstAttributeWritten || !indent[depth]){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int length = this.indentAttributeReference;
|
||||||
|
if(length <= 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
append('\r');
|
||||||
|
append('\n');
|
||||||
|
appendSpace(length);
|
||||||
|
}
|
||||||
|
}
|
@ -528,6 +528,9 @@ public class ApkModule implements ApkFile {
|
|||||||
if(inputSource==null){
|
if(inputSource==null){
|
||||||
throw new FileNotFoundException("No such file in apk: " + path);
|
throw new FileNotFoundException("No such file in apk: " + path);
|
||||||
}
|
}
|
||||||
|
return loadResXmlDocument(inputSource);
|
||||||
|
}
|
||||||
|
public ResXmlDocument loadResXmlDocument(InputSource inputSource) throws IOException{
|
||||||
ResXmlDocument resXmlDocument = new ResXmlDocument();
|
ResXmlDocument resXmlDocument = new ResXmlDocument();
|
||||||
resXmlDocument.setApkFile(this);
|
resXmlDocument.setApkFile(this);
|
||||||
resXmlDocument.readBytes(inputSource.openStream());
|
resXmlDocument.readBytes(inputSource.openStream());
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.reandroid.apk;
|
package com.reandroid.apk;
|
||||||
|
|
||||||
|
import com.reandroid.apk.xmldecoder.ResXmlDocumentSerializer;
|
||||||
import com.reandroid.archive.InputSource;
|
import com.reandroid.archive.InputSource;
|
||||||
import com.reandroid.apk.xmldecoder.XMLBagDecoder;
|
import com.reandroid.apk.xmldecoder.XMLBagDecoder;
|
||||||
import com.reandroid.apk.xmldecoder.XMLNamespaceValidator;
|
import com.reandroid.apk.xmldecoder.XMLNamespaceValidator;
|
||||||
@ -32,10 +33,12 @@ import com.reandroid.xml.XMLAttribute;
|
|||||||
import com.reandroid.xml.XMLDocument;
|
import com.reandroid.xml.XMLDocument;
|
||||||
import com.reandroid.xml.XMLElement;
|
import com.reandroid.xml.XMLElement;
|
||||||
import com.reandroid.xml.XMLException;
|
import com.reandroid.xml.XMLException;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ApkModuleXmlDecoder {
|
public class ApkModuleXmlDecoder {
|
||||||
@ -43,13 +46,22 @@ import java.util.*;
|
|||||||
private final Map<Integer, Set<ResConfig>> decodedEntries;
|
private final Map<Integer, Set<ResConfig>> decodedEntries;
|
||||||
private XMLBagDecoder xmlBagDecoder;
|
private XMLBagDecoder xmlBagDecoder;
|
||||||
private final Set<String> mDecodedPaths;
|
private final Set<String> mDecodedPaths;
|
||||||
|
private ResXmlDocumentSerializer documentSerializer;
|
||||||
|
private boolean useAndroidSerializer;
|
||||||
public ApkModuleXmlDecoder(ApkModule apkModule){
|
public ApkModuleXmlDecoder(ApkModule apkModule){
|
||||||
this.apkModule=apkModule;
|
this.apkModule=apkModule;
|
||||||
this.decodedEntries = new HashMap<>();
|
this.decodedEntries = new HashMap<>();
|
||||||
this.mDecodedPaths = new HashSet<>();
|
this.mDecodedPaths = new HashSet<>();
|
||||||
|
this.useAndroidSerializer = true;
|
||||||
|
}
|
||||||
|
public void setUseAndroidSerializer(boolean useAndroidSerializer) {
|
||||||
|
this.useAndroidSerializer = useAndroidSerializer;
|
||||||
}
|
}
|
||||||
public void sanitizeFilePaths(){
|
public void sanitizeFilePaths(){
|
||||||
PathSanitizer sanitizer = new PathSanitizer(apkModule);
|
sanitizeFilePaths(false);
|
||||||
|
}
|
||||||
|
public void sanitizeFilePaths(boolean sanitizeResourceFiles){
|
||||||
|
PathSanitizer sanitizer = new PathSanitizer(apkModule, sanitizeResourceFiles);
|
||||||
sanitizer.sanitize();
|
sanitizer.sanitize();
|
||||||
}
|
}
|
||||||
public void decodeTo(File outDir)
|
public void decodeTo(File outDir)
|
||||||
@ -66,14 +78,14 @@ import java.util.*;
|
|||||||
|
|
||||||
decodePublicXml(tableBlock, outDir);
|
decodePublicXml(tableBlock, outDir);
|
||||||
|
|
||||||
decodeAndroidManifest(tableBlock, outDir);
|
decodeAndroidManifest(outDir);
|
||||||
|
|
||||||
addDecodedPath(TableBlock.FILE_NAME);
|
addDecodedPath(TableBlock.FILE_NAME);
|
||||||
|
|
||||||
logMessage("Decoding resource files ...");
|
logMessage("Decoding resource files ...");
|
||||||
List<ResFile> resFileList=apkModule.listResFiles();
|
List<ResFile> resFileList=apkModule.listResFiles();
|
||||||
for(ResFile resFile:resFileList){
|
for(ResFile resFile:resFileList){
|
||||||
decodeResFile(tableBlock, outDir, resFile);
|
decodeResFile(outDir, resFile);
|
||||||
}
|
}
|
||||||
decodeValues(tableBlock, outDir, tableBlock);
|
decodeValues(tableBlock, outDir, tableBlock);
|
||||||
|
|
||||||
@ -104,10 +116,10 @@ import java.util.*;
|
|||||||
UncompressedFiles uncompressedFiles = apkModule.getUncompressedFiles();
|
UncompressedFiles uncompressedFiles = apkModule.getUncompressedFiles();
|
||||||
uncompressedFiles.toJson().write(file);
|
uncompressedFiles.toJson().write(file);
|
||||||
}
|
}
|
||||||
private void decodeResFile(EntryStore entryStore, File outDir, ResFile resFile)
|
private void decodeResFile(File outDir, ResFile resFile)
|
||||||
throws IOException, XMLException {
|
throws IOException{
|
||||||
if(resFile.isBinaryXml()){
|
if(resFile.isBinaryXml()){
|
||||||
decodeResXml(entryStore, outDir, resFile);
|
decodeResXml(outDir, resFile);
|
||||||
}else {
|
}else {
|
||||||
decodeResRaw(outDir, resFile);
|
decodeResRaw(outDir, resFile);
|
||||||
}
|
}
|
||||||
@ -133,12 +145,10 @@ import java.util.*;
|
|||||||
|
|
||||||
addDecodedEntry(entry);
|
addDecodedEntry(entry);
|
||||||
}
|
}
|
||||||
private void decodeResXml(EntryStore entryStore, File outDir, ResFile resFile)
|
private void decodeResXml(File outDir, ResFile resFile)
|
||||||
throws IOException, XMLException{
|
throws IOException{
|
||||||
Entry entry = resFile.pickOne();
|
Entry entry = resFile.pickOne();
|
||||||
PackageBlock packageBlock = entry.getPackageBlock();
|
PackageBlock packageBlock = entry.getPackageBlock();
|
||||||
ResXmlDocument resXmlDocument = apkModule.loadResXmlDocument(
|
|
||||||
resFile.getInputSource().getName());
|
|
||||||
|
|
||||||
File pkgDir = new File(outDir, getPackageDirName(packageBlock));
|
File pkgDir = new File(outDir, getPackageDirName(packageBlock));
|
||||||
String alias = resFile.buildPath(ApkUtil.RES_DIR_NAME);
|
String alias = resFile.buildPath(ApkUtil.RES_DIR_NAME);
|
||||||
@ -147,14 +157,20 @@ import java.util.*;
|
|||||||
File file = new File(pkgDir, path);
|
File file = new File(pkgDir, path);
|
||||||
|
|
||||||
logVerbose("Decoding: " + path);
|
logVerbose("Decoding: " + path);
|
||||||
XMLNamespaceValidator namespaceValidator=new XMLNamespaceValidator(resXmlDocument);
|
serializeXml(packageBlock.getId(), resFile.getInputSource(), file);
|
||||||
namespaceValidator.validate();
|
|
||||||
XMLDocument xmlDocument= resXmlDocument.decodeToXml(entryStore, packageBlock.getId());
|
|
||||||
xmlDocument.save(file, true);
|
|
||||||
|
|
||||||
resFile.setFilePath(alias);
|
resFile.setFilePath(alias);
|
||||||
|
addDecodedEntry(entry);
|
||||||
addDecodedEntry(resFile.pickOne());
|
}
|
||||||
|
private ResXmlDocumentSerializer getDocumentSerializer(){
|
||||||
|
if(documentSerializer == null){
|
||||||
|
documentSerializer = new ResXmlDocumentSerializer(apkModule);
|
||||||
|
documentSerializer.setValidateXmlNamespace(true);
|
||||||
|
}
|
||||||
|
return documentSerializer;
|
||||||
|
}
|
||||||
|
private TableBlock getTableBlock(){
|
||||||
|
return apkModule.getTableBlock();
|
||||||
}
|
}
|
||||||
private void decodePublicXml(TableBlock tableBlock, File outDir)
|
private void decodePublicXml(TableBlock tableBlock, File outDir)
|
||||||
throws IOException{
|
throws IOException{
|
||||||
@ -190,8 +206,8 @@ import java.util.*;
|
|||||||
resourceIds.loadPackageBlock(packageBlock);
|
resourceIds.loadPackageBlock(packageBlock);
|
||||||
resourceIds.writeXml(file);
|
resourceIds.writeXml(file);
|
||||||
}
|
}
|
||||||
private void decodeAndroidManifest(EntryStore entryStore, File outDir)
|
private void decodeAndroidManifest(File outDir)
|
||||||
throws IOException, XMLException {
|
throws IOException {
|
||||||
if(!apkModule.hasAndroidManifestBlock()){
|
if(!apkModule.hasAndroidManifestBlock()){
|
||||||
logMessage("Don't have: "+ AndroidManifestBlock.FILE_NAME);
|
logMessage("Don't have: "+ AndroidManifestBlock.FILE_NAME);
|
||||||
return;
|
return;
|
||||||
@ -199,13 +215,58 @@ import java.util.*;
|
|||||||
File file=new File(outDir, AndroidManifestBlock.FILE_NAME);
|
File file=new File(outDir, AndroidManifestBlock.FILE_NAME);
|
||||||
logMessage("Decoding: "+file.getName());
|
logMessage("Decoding: "+file.getName());
|
||||||
AndroidManifestBlock manifestBlock=apkModule.getAndroidManifestBlock();
|
AndroidManifestBlock manifestBlock=apkModule.getAndroidManifestBlock();
|
||||||
XMLNamespaceValidator namespaceValidator=new XMLNamespaceValidator(manifestBlock);
|
|
||||||
namespaceValidator.validate();
|
|
||||||
int currentPackageId = manifestBlock.guessCurrentPackageId();
|
int currentPackageId = manifestBlock.guessCurrentPackageId();
|
||||||
XMLDocument xmlDocument=manifestBlock.decodeToXml(entryStore, currentPackageId);
|
serializeXml(currentPackageId, manifestBlock, file);
|
||||||
xmlDocument.save(file, true);
|
|
||||||
addDecodedPath(AndroidManifestBlock.FILE_NAME);
|
addDecodedPath(AndroidManifestBlock.FILE_NAME);
|
||||||
}
|
}
|
||||||
|
private void serializeXml(int currentPackageId, ResXmlDocument document, File outFile)
|
||||||
|
throws IOException {
|
||||||
|
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
|
||||||
|
namespaceValidator.validate();
|
||||||
|
if(useAndroidSerializer){
|
||||||
|
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||||
|
if(currentPackageId != 0){
|
||||||
|
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
serializer.write(document, outFile);
|
||||||
|
} catch (XmlPullParserException ex) {
|
||||||
|
throw new IOException("Error: "+outFile.getName(), ex);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
try {
|
||||||
|
XMLDocument xmlDocument = document.decodeToXml(getTableBlock(), currentPackageId);
|
||||||
|
xmlDocument.save(outFile, true);
|
||||||
|
} catch (XMLException ex) {
|
||||||
|
throw new IOException("Error: "+outFile.getName(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void serializeXml(int currentPackageId, InputSource inputSource, File outFile)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
if(useAndroidSerializer){
|
||||||
|
ResXmlDocumentSerializer serializer = getDocumentSerializer();
|
||||||
|
if(currentPackageId != 0){
|
||||||
|
serializer.getDecoder().setCurrentPackageId(currentPackageId);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
serializer.write(inputSource, outFile);
|
||||||
|
} catch (XmlPullParserException ex) {
|
||||||
|
throw new IOException("Error: "+outFile.getName(), ex);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
try {
|
||||||
|
ResXmlDocument document = apkModule.loadResXmlDocument(inputSource);
|
||||||
|
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
|
||||||
|
namespaceValidator.validate();
|
||||||
|
XMLDocument xmlDocument = document.decodeToXml(getTableBlock(), currentPackageId);
|
||||||
|
xmlDocument.save(outFile, true);
|
||||||
|
} catch (XMLException ex) {
|
||||||
|
throw new IOException("Error: "+outFile.getName(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private void addDecodedEntry(Entry entry){
|
private void addDecodedEntry(Entry entry){
|
||||||
if(entry.isNull()){
|
if(entry.isNull()){
|
||||||
return;
|
return;
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* 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.apk.xmldecoder;
|
||||||
|
|
||||||
|
import com.android.org.kxml2.io.KXmlSerializer;
|
||||||
|
import com.reandroid.apk.ApkModule;
|
||||||
|
import com.reandroid.archive.InputSource;
|
||||||
|
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
|
||||||
|
import com.reandroid.arsc.chunk.xml.ResXmlPullParser;
|
||||||
|
import com.reandroid.arsc.decoder.Decoder;
|
||||||
|
import com.reandroid.xml.XmlParserToSerializer;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class ResXmlDocumentSerializer implements ResXmlPullParser.DocumentLoadedListener{
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
private final ResXmlPullParser parser;
|
||||||
|
private final XmlSerializer serializer;
|
||||||
|
private final XmlParserToSerializer parserToSerializer;
|
||||||
|
private boolean validateXmlNamespace;
|
||||||
|
public ResXmlDocumentSerializer(ResXmlPullParser parser){
|
||||||
|
this.parser = parser;
|
||||||
|
this.serializer = new KXmlSerializer();
|
||||||
|
this.parserToSerializer = new XmlParserToSerializer(parser, serializer);
|
||||||
|
}
|
||||||
|
public ResXmlDocumentSerializer(Decoder decoder){
|
||||||
|
this(new ResXmlPullParser(decoder));
|
||||||
|
}
|
||||||
|
public ResXmlDocumentSerializer(ApkModule apkModule){
|
||||||
|
this(createDecoder(apkModule));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(InputSource inputSource, File file)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
write(inputSource.openStream(), file);
|
||||||
|
}
|
||||||
|
public void write(InputSource inputSource, OutputStream outputStream)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
write(inputSource.openStream(), outputStream);
|
||||||
|
inputSource.disposeInputSource();
|
||||||
|
}
|
||||||
|
public void write(InputStream inputStream, OutputStream outputStream)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
synchronized (mLock){
|
||||||
|
this.parser.setInput(inputStream, null);
|
||||||
|
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
|
||||||
|
this.serializer.setOutput(writer);
|
||||||
|
this.parserToSerializer.write();
|
||||||
|
writer.close();
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void write(InputStream inputStream, File file)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
File dir = file.getParentFile();
|
||||||
|
if(dir != null && !dir.exists()){
|
||||||
|
dir.mkdirs();
|
||||||
|
}
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
|
write(inputStream, outputStream);
|
||||||
|
}
|
||||||
|
public void write(ResXmlDocument xmlDocument, File file)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
File dir = file.getParentFile();
|
||||||
|
if(dir != null && !dir.exists()){
|
||||||
|
dir.mkdirs();
|
||||||
|
}
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
|
write(xmlDocument, outputStream);
|
||||||
|
}
|
||||||
|
public void write(ResXmlDocument xmlDocument, OutputStream outputStream)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
|
||||||
|
write(xmlDocument, writer);
|
||||||
|
writer.close();
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
public void write(ResXmlDocument xmlDocument, Writer writer)
|
||||||
|
throws IOException, XmlPullParserException {
|
||||||
|
synchronized (mLock){
|
||||||
|
this.parser.setResXmlDocument(xmlDocument);
|
||||||
|
this.serializer.setOutput(writer);
|
||||||
|
this.parserToSerializer.write();
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Decoder getDecoder(){
|
||||||
|
return parser.getDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValidateXmlNamespace(boolean validateXmlNamespace) {
|
||||||
|
this.validateXmlNamespace = validateXmlNamespace;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public ResXmlDocument onDocumentLoaded(ResXmlDocument resXmlDocument) {
|
||||||
|
if(!validateXmlNamespace){
|
||||||
|
return resXmlDocument;
|
||||||
|
}
|
||||||
|
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(resXmlDocument);
|
||||||
|
namespaceValidator.validate();
|
||||||
|
return resXmlDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Decoder createDecoder(ApkModule apkModule){
|
||||||
|
Decoder decoder = Decoder.create(apkModule.getTableBlock());
|
||||||
|
decoder.setApkFile(apkModule);
|
||||||
|
return decoder;
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ public class ResXmlPullParser implements XmlResourceParser {
|
|||||||
private final ParserEventList mEventList = new ParserEventList();
|
private final ParserEventList mEventList = new ParserEventList();
|
||||||
private ResXmlDocument mDocument;
|
private ResXmlDocument mDocument;
|
||||||
private boolean mDocumentCreatedHere;
|
private boolean mDocumentCreatedHere;
|
||||||
|
private DocumentLoadedListener documentLoadedListener;
|
||||||
|
|
||||||
public ResXmlPullParser(Decoder decoder){
|
public ResXmlPullParser(Decoder decoder){
|
||||||
this.mDecoder = decoder;
|
this.mDecoder = decoder;
|
||||||
@ -345,18 +346,7 @@ public class ResXmlPullParser implements XmlResourceParser {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
|
public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
|
||||||
synchronized (this){
|
loadResXmlDocument(inputStream);
|
||||||
ResXmlDocument xmlDocument = new ResXmlDocument();
|
|
||||||
try {
|
|
||||||
xmlDocument.readBytes(inputStream);
|
|
||||||
} catch (IOException exception) {
|
|
||||||
XmlPullParserException pullParserException = new XmlPullParserException(exception.getMessage());
|
|
||||||
pullParserException.initCause(exception);
|
|
||||||
throw pullParserException;
|
|
||||||
}
|
|
||||||
setResXmlDocument(xmlDocument);
|
|
||||||
this.mDocumentCreatedHere = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String getInputEncoding() {
|
public String getInputEncoding() {
|
||||||
@ -617,4 +607,31 @@ public class ResXmlPullParser implements XmlResourceParser {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDocumentLoadedListener(DocumentLoadedListener documentLoadedListener) {
|
||||||
|
this.documentLoadedListener = documentLoadedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadResXmlDocument(InputStream inputStream) throws XmlPullParserException {
|
||||||
|
synchronized (this){
|
||||||
|
ResXmlDocument xmlDocument = new ResXmlDocument();
|
||||||
|
try {
|
||||||
|
xmlDocument.readBytes(inputStream);
|
||||||
|
} catch (IOException exception) {
|
||||||
|
XmlPullParserException pullParserException = new XmlPullParserException(exception.getMessage());
|
||||||
|
pullParserException.initCause(exception);
|
||||||
|
throw pullParserException;
|
||||||
|
}
|
||||||
|
DocumentLoadedListener listener = this.documentLoadedListener;
|
||||||
|
if(listener != null){
|
||||||
|
xmlDocument = listener.onDocumentLoaded(xmlDocument);
|
||||||
|
}
|
||||||
|
setResXmlDocument(xmlDocument);
|
||||||
|
this.mDocumentCreatedHere = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface DocumentLoadedListener{
|
||||||
|
public ResXmlDocument onDocumentLoaded(ResXmlDocument resXmlDocument);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
101
src/main/java/com/reandroid/xml/XmlParserToSerializer.java
Normal file
101
src/main/java/com/reandroid/xml/XmlParserToSerializer.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.content.res.XmlResourceParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class XmlParserToSerializer {
|
||||||
|
private final XmlSerializer serializer;
|
||||||
|
private final XmlResourceParser parser;
|
||||||
|
public XmlParserToSerializer(XmlResourceParser parser, XmlSerializer serializer){
|
||||||
|
this.parser = parser;
|
||||||
|
this.serializer = serializer;
|
||||||
|
}
|
||||||
|
public void write() throws IOException, XmlPullParserException {
|
||||||
|
XmlResourceParser parser = this.parser;
|
||||||
|
int event = parser.next();
|
||||||
|
while (nextEvent(event)){
|
||||||
|
event = parser.next();
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
private void close(){
|
||||||
|
parser.close();
|
||||||
|
}
|
||||||
|
private boolean nextEvent(int event) throws IOException, XmlPullParserException {
|
||||||
|
boolean hasNext = true;
|
||||||
|
switch (event){
|
||||||
|
case XmlResourceParser.START_DOCUMENT:
|
||||||
|
onStartDocument();
|
||||||
|
break;
|
||||||
|
case XmlResourceParser.START_TAG:
|
||||||
|
onStartTag();
|
||||||
|
break;
|
||||||
|
case XmlResourceParser.TEXT:
|
||||||
|
onText();
|
||||||
|
break;
|
||||||
|
case XmlResourceParser.COMMENT:
|
||||||
|
onComment();
|
||||||
|
break;
|
||||||
|
case XmlResourceParser.END_TAG:
|
||||||
|
onEndTag();
|
||||||
|
break;
|
||||||
|
case XmlResourceParser.END_DOCUMENT:
|
||||||
|
onEndDocument();
|
||||||
|
hasNext = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return hasNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStartDocument() throws IOException{
|
||||||
|
serializer.startDocument("utf-8", null);
|
||||||
|
}
|
||||||
|
private void onStartTag() throws IOException, XmlPullParserException {
|
||||||
|
XmlResourceParser parser = this.parser;
|
||||||
|
XmlSerializer serializer = this.serializer;
|
||||||
|
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||||
|
int nsCount = parser.getNamespaceCount(parser.getDepth());
|
||||||
|
for(int i=0; i<nsCount; i++){
|
||||||
|
String prefix = parser.getNamespacePrefix(i);
|
||||||
|
String namespace = parser.getNamespaceUri(i);
|
||||||
|
serializer.setPrefix(prefix, namespace);
|
||||||
|
}
|
||||||
|
serializer.startTag(parser.getNamespace(), parser.getName());
|
||||||
|
int attrCount = parser.getAttributeCount();
|
||||||
|
for(int i=0; i<attrCount; i++){
|
||||||
|
serializer.attribute(parser.getAttributeNamespace(i),
|
||||||
|
parser.getAttributeName(i),
|
||||||
|
parser.getAttributeValue(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void onText() throws IOException{
|
||||||
|
serializer.text(parser.getText());
|
||||||
|
}
|
||||||
|
private void onComment() throws IOException{
|
||||||
|
serializer.comment(parser.getText());
|
||||||
|
}
|
||||||
|
private void onEndTag() throws IOException{
|
||||||
|
serializer.endTag(parser.getNamespace(), parser.getName());
|
||||||
|
}
|
||||||
|
private void onEndDocument() throws IOException{
|
||||||
|
serializer.endDocument();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user