/* * Copyright (C) 2010 Ryszard Wiśniewski * Copyright (C) 2010 Connor Tumbleson * * 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 mSpans; public StyledString(String text, List spans) { this.mText = text; this.mSpans = spans; } String getText() { return mText; } List getSpans() { return mSpans; } @Override public String toString() { return new Decoder().decode(this); } public static class Span implements Comparable { 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 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 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 it) { Span span = it.next(); String name = span.getName(); Map 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 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("'); } } private static final Logger LOGGER = Logger.getLogger(StyledString.class.getName()); }