/** * Copyright 2011 Ryszard Wiśniewski <brut.alll@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 * * 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 brut.util.Duo; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.List; /** * @author Ryszard Wiśniewski <brut.alll@gmail.com> */ 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; 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 '\\': out.append('\\'); break; case '\'': case '\n': enclose = true; break; case '"': out.append('\\'); break; case '<': isInStyleTag = true; if (enclose) { out.insert(startPos, '"').append('"'); } break; default: if (!isPrintableChar(c)) { 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, 2); 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, 2); 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.substring(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; int length = str.length(); List<Integer> nonPositional = new ArrayList<>(); List<Integer> positional = new ArrayList<>(); 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; } }