216 lines
6.7 KiB
Java
216 lines
6.7 KiB
Java
/*
|
|
* 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.xml;
|
|
|
|
import brut.util.Duo;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
import java.awt.event.KeyEvent;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public final class ResXmlEncoders {
|
|
|
|
public static String escapeXmlChars(String str) {
|
|
return StringUtils.replace(StringUtils.replace(str, "&", "&"), "<", "<");
|
|
}
|
|
|
|
public static String encodeAsResXmlAttr(String str) {
|
|
if (str == null || 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 == null || 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;
|
|
int startPos = 0;
|
|
boolean enclose = false;
|
|
boolean wasSpace = true;
|
|
for (char c : chars) {
|
|
if (isInStyleTag) {
|
|
if (c == '>') {
|
|
isInStyleTag = false;
|
|
startPos = out.length() + 1;
|
|
enclose = false;
|
|
}
|
|
} else if (c == ' ') {
|
|
if (wasSpace) {
|
|
enclose = true;
|
|
}
|
|
wasSpace = true;
|
|
} else {
|
|
wasSpace = false;
|
|
switch (c) {
|
|
case '\\':
|
|
case '"':
|
|
out.append('\\');
|
|
break;
|
|
case '\'':
|
|
case '\n':
|
|
enclose = true;
|
|
break;
|
|
case '<':
|
|
isInStyleTag = true;
|
|
if (enclose) {
|
|
out.insert(startPos, '"').append('"');
|
|
}
|
|
break;
|
|
default:
|
|
if (!isPrintableChar(c)) {
|
|
|
|
// lets not write trailing \u0000 if we are at end of string
|
|
if ((out.length() + 1) == str.length() && c == '\u0000') {
|
|
continue;
|
|
}
|
|
out.append(String.format("\\u%04x", (int) c));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
out.append(c);
|
|
}
|
|
|
|
if (enclose || wasSpace) {
|
|
out.insert(startPos, '"').append('"');
|
|
}
|
|
return out.toString();
|
|
}
|
|
|
|
public static boolean hasMultipleNonPositionalSubstitutions(String str) {
|
|
Duo<List<Integer>, List<Integer>> tuple = findSubstitutions(str, 4);
|
|
return ! tuple.m1.isEmpty() && tuple.m1.size() + tuple.m2.size() > 1;
|
|
}
|
|
|
|
public static String enumerateNonPositionalSubstitutionsIfRequired(String str) {
|
|
Duo<List<Integer>, List<Integer>> tuple = findSubstitutions(str, 4);
|
|
if (tuple.m1.isEmpty() || tuple.m1.size() + tuple.m2.size() < 2) {
|
|
return str;
|
|
}
|
|
List<Integer> subs = tuple.m1;
|
|
|
|
StringBuilder out = new StringBuilder();
|
|
int pos = 0;
|
|
int count = 0;
|
|
for (Integer sub : subs) {
|
|
out.append(str, pos, ++sub).append(++count).append('$');
|
|
pos = sub;
|
|
}
|
|
out.append(str.substring(pos));
|
|
|
|
return out.toString();
|
|
}
|
|
|
|
/**
|
|
* It returns a tuple of:
|
|
* - a list of offsets of non positional substitutions. non-pos is defined as any "%" which isn't "%%" nor "%\d+\$"
|
|
* - a list of offsets of positional substitutions
|
|
*/
|
|
private static Duo<List<Integer>, List<Integer>> findSubstitutions(String str, int nonPosMax) {
|
|
if (nonPosMax == -1) {
|
|
nonPosMax = Integer.MAX_VALUE;
|
|
}
|
|
int pos;
|
|
int pos2 = 0;
|
|
List<Integer> nonPositional = new ArrayList<>();
|
|
List<Integer> positional = new ArrayList<>();
|
|
|
|
if (str == null) {
|
|
return new Duo<>(nonPositional, positional);
|
|
}
|
|
|
|
int length = str.length();
|
|
|
|
while ((pos = str.indexOf('%', pos2)) != -1) {
|
|
pos2 = pos + 1;
|
|
if (pos2 == length) {
|
|
nonPositional.add(pos);
|
|
break;
|
|
}
|
|
char c = str.charAt(pos2++);
|
|
if (c == '%') {
|
|
continue;
|
|
}
|
|
if (c >= '0' && c <= '9' && pos2 < length) {
|
|
while ((c = str.charAt(pos2++)) >= '0' && c <= '9' && pos2 < length);
|
|
if (c == '$') {
|
|
positional.add(pos);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
nonPositional.add(pos);
|
|
if (nonPositional.size() >= nonPosMax) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new Duo<>(nonPositional, positional);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|