Apktool/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/StyledString.java

172 lines
5.6 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.decoder;
import brut.androlib.res.xml.ResXmlEncoders;
import com.google.common.base.Splitter;
import com.google.common.base.Splitter.MapSplitter;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class StyledString {
private final String mText;
private final List<Span> mSpans;
public StyledString(String text, List<Span> spans) {
this.mText = text;
this.mSpans = spans;
}
String getText() {
return mText;
}
List<Span> getSpans() {
return mSpans;
}
@Override
public String toString() {
return new Decoder().decode(this);
}
public static class Span implements Comparable<Span> {
private static final MapSplitter ATTRIBUTES_SPLITTER =
Splitter.on(';').omitEmptyStrings().withKeyValueSeparator(Splitter.on('=').limit(2));
private final String tag;
private final int firstChar;
private final int lastChar;
public Span(String tag, int firstChar, int lastChar) {
this.tag = tag;
this.firstChar = firstChar;
this.lastChar = lastChar;
}
public String getTag() {
return tag;
}
public int getFirstChar() {
return firstChar;
}
public int getLastChar() {
return lastChar;
}
public String getName() {
int separatorIdx = tag.indexOf(';');
return separatorIdx == -1 ? tag : tag.substring(0, separatorIdx);
}
public Map<String, String> getAttributes() {
int separatorIdx = tag.indexOf(';');
return separatorIdx == -1 ? null : ATTRIBUTES_SPLITTER.split(
tag.substring(separatorIdx + 1, tag.endsWith(";") ? tag.length() - 1 : tag.length())
);
}
@Override
public int compareTo(Span o) {
int res = Integer.compare(firstChar, o.firstChar);
if (res != 0) {
return res;
}
res = Integer.compare(lastChar, o.lastChar);
if (res != 0) {
return -res;
}
return -tag.compareTo(o.tag);
}
}
private static class Decoder {
private String text;
private StringBuilder xmlValue;
private int lastOffset;
String decode(StyledString styledString) {
text = styledString.getText();
xmlValue = new StringBuilder(text.length() * 2);
lastOffset = 0;
// recurse top-level tags
PeekingIterator<Span> it = Iterators.peekingIterator(styledString.getSpans().iterator());
while (it.hasNext()) {
decodeIterate(it);
}
// write the remaining encoded raw text
if (lastOffset < text.length()) {
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset)));
}
return xmlValue.toString();
}
private void decodeIterate(PeekingIterator<Span> it) {
Span span = it.next();
String name = span.getName();
Map<String, String> attributes = span.getAttributes();
int spanStart = span.getFirstChar();
int spanEnd = span.getLastChar() + 1;
// write encoded raw text preceding the opening tag
if (spanStart > lastOffset) {
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset, spanStart)));
}
lastOffset = spanStart;
// write opening tag
xmlValue.append('<').append(name);
if (attributes != null) {
for (Map.Entry<String, String> attrEntry : attributes.entrySet()) {
xmlValue.append(' ').append(attrEntry.getKey()).append("=\"")
.append(ResXmlEncoders.escapeXmlChars(attrEntry.getValue())).append('"');
}
}
// if an opening tag is followed by a matching closing tag, write as an empty-element tag
if (spanStart == spanEnd) {
xmlValue.append("/>");
return;
}
xmlValue.append('>');
// recurse nested tags
while (it.hasNext() && it.peek().getFirstChar() < spanEnd) {
decodeIterate(it);
}
// write encoded raw text preceding the closing tag
if (spanEnd > lastOffset) {
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset, spanEnd)));
}
lastOffset = spanEnd;
// write closing tag
xmlValue.append("</").append(name).append('>');
}
}
private static final Logger LOGGER = Logger.getLogger(StyledString.class.getName());
}