From e23bf8e70dcc2b27f66430f0c4b00a4db2cdef5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ryszard=20Wi=C5=9Bniewski?= Date: Mon, 9 May 2011 11:23:16 +0200 Subject: [PATCH] Added ResXmlEncoders class and use it instead of encoding methods in AndrolibResources. --- .../brut/androlib/res/AndrolibResources.java | 93 ------------ .../res/data/value/ResStringValue.java | 5 +- .../res/decoder/AXmlResourceParser.java | 4 +- .../androlib/res/decoder/StringBlock.java | 11 +- .../brut/androlib/res/xml/ResXmlEncoders.java | 136 ++++++++++++++++++ 5 files changed, 148 insertions(+), 101 deletions(-) create mode 100644 apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java diff --git a/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java b/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java index f5b5bdf3..84c442cd 100644 --- a/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java +++ b/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java @@ -27,7 +27,6 @@ import brut.androlib.res.util.*; import brut.common.BrutException; import brut.directory.*; import brut.util.*; -import java.awt.event.KeyEvent; import java.io.*; import java.util.*; import java.util.logging.Logger; @@ -485,98 +484,6 @@ final public class AndrolibResources { } } - public static String escapeTextForResXml(String value) { - return escapeTextForResXml(value, true); - } - - public static String escapeTextForResXml(String value, - boolean escapeChars) { - if (value.isEmpty()) { - return value; - } - - if (escapeChars) { - value = escapeCharsForResXml(value); - } - - StringBuilder out = new StringBuilder(value.length() + 10); - char[] chars = value.toCharArray(); - - switch (chars[0]) { - case '@': - case '#': - case '?': - out.append('\\'); - } - - boolean space = true; - for (char c : chars) { - if (c == ' ') { - if (space) { - out.append("\\u0020"); - } else { - out.append(c); - space = true; - } - continue; - } - - space = false; - out.append(c); - } - - if (space && out.charAt(out.length() - 1) == ' ') { - out.deleteCharAt(out.length() - 1); - out.append("\\u0020"); - } - - return out.toString(); - } - - public static String escapeCharsForResXml(String value) { - if (value.isEmpty()) { - return value; - } - - StringBuilder out = new StringBuilder(value.length() + 10); - for (char c : value.toCharArray()) { - switch (c) { - case '\\': - case '\'': - out.append('\\'); - break; - case '"': - out.append("\\""); - continue; - case '\n': - out.append("\\n"); - continue; - case '&': - out.append("&"); - continue; - case '<': - out.append("<"); - continue; - default: - if (! isPrintableChar(c)) { - out.append(String.format("\\u%04x", (int)c)); - continue; - } - } - out.append(c); - } - - return out.toString(); - } - - public static boolean isPrintableChar(char c) { - Character.UnicodeBlock block = Character.UnicodeBlock.of(c); - return ! Character.isISOControl(c) - && c != KeyEvent.CHAR_UNDEFINED - && block != null - && block != Character.UnicodeBlock.SPECIALS; - } - // TODO: dirty static hack. I have to refactor decoding mechanisms. public static boolean sKeepBroken = false; diff --git a/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStringValue.java b/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStringValue.java index 4872e902..ff769545 100644 --- a/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStringValue.java +++ b/apktool-lib/src/main/java/brut/androlib/res/data/value/ResStringValue.java @@ -17,6 +17,7 @@ package brut.androlib.res.data.value; import brut.androlib.AndrolibException; +import brut.androlib.res.xml.ResXmlEncoders; /** * @author Ryszard Wiśniewski @@ -33,12 +34,12 @@ public class ResStringValue extends ResScalarValue { @Override public String encodeAsResXmlAttr() { - return mRawValue; + return ResXmlEncoders.encodeAsResXmlAttr(mRawValue); } @Override public String encodeAsResXmlValue() { - return mRawValue; + return ResXmlEncoders.encodeAsXmlValue(mRawValue); } @Override diff --git a/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java b/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java index c31d4d3a..80a58c48 100644 --- a/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java +++ b/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java @@ -23,6 +23,7 @@ import java.io.Reader; import org.xmlpull.v1.XmlPullParserException; import android.util.TypedValue; import brut.androlib.AndrolibException; +import brut.androlib.res.xml.ResXmlEncoders; import brut.util.ExtDataInput; import com.mindprod.ledatastream.LEDataInputStream; import java.util.logging.Level; @@ -312,7 +313,8 @@ public class AXmlResourceParser implements XmlResourceParser { if (mAttrDecoder != null) { try { return mAttrDecoder.decode(valueType, valueData, - valueRaw == -1 ? null : m_strings.getString(valueRaw), + valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars( + m_strings.getString(valueRaw)), getAttributeNameResource(index)); } catch (AndrolibException ex) { setFirstError(ex); diff --git a/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java b/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java index df897fdd..fd6c7a4c 100644 --- a/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java +++ b/apktool-lib/src/main/java/brut/androlib/res/decoder/StringBlock.java @@ -17,6 +17,7 @@ package brut.androlib.res.decoder; import brut.androlib.res.AndrolibResources; +import brut.androlib.res.xml.ResXmlEncoders; import brut.util.ExtDataInput; import java.io.IOException; import java.nio.ByteBuffer; @@ -126,7 +127,7 @@ public class StringBlock { } int[] style = getStyle(index); if (style == null) { - return AndrolibResources.escapeTextForResXml(raw); + return ResXmlEncoders.escapeXmlChars(raw); } StringBuilder html = new StringBuilder(raw.length() + 32); int[] opened = new int[style.length / 3]; @@ -149,7 +150,7 @@ public class StringBlock { break; } if (offset <= end) { - html.append(AndrolibResources.escapeCharsForResXml( + html.append(ResXmlEncoders.escapeXmlChars( raw.substring(offset, end + 1))); offset = end + 1; } @@ -157,7 +158,7 @@ public class StringBlock { } depth = j + 1; if (offset < start) { - html.append(AndrolibResources.escapeCharsForResXml( + html.append(ResXmlEncoders.escapeXmlChars( raw.substring(offset, start))); offset = start; } @@ -168,7 +169,7 @@ public class StringBlock { style[i + 1] = -1; opened[depth++] = i; } - return AndrolibResources.escapeTextForResXml(html.toString(), false); + return html.toString(); } private void outputStyleTag(String tag, StringBuilder builder, @@ -199,7 +200,7 @@ public class StringBlock { val = tag.substring(pos2 + 1); } - builder.append(AndrolibResources.escapeCharsForResXml(val)) + builder.append(ResXmlEncoders.escapeXmlChars(val)) .append('"'); } } diff --git a/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java b/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java new file mode 100644 index 00000000..63fa3976 --- /dev/null +++ b/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlEncoders.java @@ -0,0 +1,136 @@ +/** + * Copyright 2011 Ryszard Wiśniewski + * + * 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 brut.androlib.res.xml; + +import java.awt.event.KeyEvent; + +/** + * @author Ryszard Wiśniewski + */ +public final class ResXmlEncoders { + + public static String escapeXmlChars(String str) { + return str.replace("&", "&").replace("<", "<"); + } + + public static String encodeAsResXmlAttr(String str) { + if (str.isEmpty()) { + return str; + } + + char[] chars = str.toCharArray(); + StringBuilder out = new StringBuilder(str.length() + 10); + + switch (chars[0]) { + case '#': + case '@': + case '?': + out.append('\\'); + } + + for (char c : chars) { + switch (c) { + case '\\': + out.append('\\'); + break; + case '"': + out.append("""); + continue; + case '\n': + out.append("\\n"); + continue; + default: + if (!isPrintableChar(c)) { + out.append(String.format("\\u%04x", (int) c)); + continue; + } + } + out.append(c); + } + + return out.toString(); + } + + public static String encodeAsXmlValue(String str) { + if (str.isEmpty()) { + return str; + } + + char[] chars = str.toCharArray(); + StringBuilder out = new StringBuilder(str.length() + 10); + + switch (chars[0]) { + case '#': + case '@': + case '?': + out.append('\\'); + } + + boolean isInStyleTag = false; + boolean enclose = false; + boolean wasSpace = true; + for (char c : chars) { + if (isInStyleTag) { + if (c == '>') { + isInStyleTag = false; + } + } else if (c == ' ') { + if (wasSpace) { + enclose = true; + } + wasSpace = true; + } else { + wasSpace = false; + switch (c) { + case '\\': + out.append('\\'); + break; + case '\'': + case '\n': + enclose = true; + break; + case '"': + out.append('\\'); + break; + case '<': + isInStyleTag = true; + break; + default: + if (!isPrintableChar(c)) { + out.append(String.format("\\u%04x", (int) c)); + continue; + } + } + } + out.append(c); + } + + if (enclose || wasSpace) { + out.insert(0, '"').append('"'); + } + + return out.toString(); + } + + private static boolean isPrintableChar(char c) { + Character.UnicodeBlock block = Character.UnicodeBlock.of(c); + return !Character.isISOControl(c) + && c != KeyEvent.CHAR_UNDEFINED + && block != null + && block != Character.UnicodeBlock.SPECIALS; + } +}