fixed #773
This commit is contained in:
parent
8359397929
commit
9c48ce3fe6
|
@ -1,277 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.mariotaku.commons.text.CodePointArray;
|
||||
import org.mariotaku.twidere.model.SpanItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
import static org.mariotaku.twidere.util.HtmlEscapeHelper.escape;
|
||||
import static org.mariotaku.twidere.util.HtmlEscapeHelper.unescape;
|
||||
|
||||
public class HtmlBuilder {
|
||||
|
||||
private final CodePointArray source;
|
||||
private final int sourceLength;
|
||||
private final boolean throwExceptions, sourceIsEscaped, shouldReEscape;
|
||||
|
||||
private final ArrayList<SpanSpec> spanSpecs = new ArrayList<>();
|
||||
|
||||
public HtmlBuilder(final String source, final boolean strict, final boolean sourceIsEscaped,
|
||||
final boolean shouldReEscape) {
|
||||
this(new CodePointArray(source), strict, sourceIsEscaped, shouldReEscape);
|
||||
}
|
||||
|
||||
public HtmlBuilder(final CodePointArray source, final boolean strict, final boolean sourceIsEscaped,
|
||||
final boolean shouldReEscape) {
|
||||
if (source == null) throw new NullPointerException();
|
||||
this.source = source;
|
||||
this.sourceLength = source.length();
|
||||
this.throwExceptions = strict;
|
||||
this.sourceIsEscaped = sourceIsEscaped;
|
||||
this.shouldReEscape = shouldReEscape;
|
||||
}
|
||||
|
||||
public boolean addLink(final String link, final String display, final int start, final int end) {
|
||||
return addLink(link, display, start, end, false);
|
||||
}
|
||||
|
||||
public boolean addLink(final String link, final String display, final int start, final int end,
|
||||
final boolean displayIsHtml) {
|
||||
if (start < 0 || end < 0 || start > end || end > sourceLength) {
|
||||
final String message = String.format(Locale.US, "text:%s, length:%d, start:%d, end:%d", source,
|
||||
sourceLength, start, end);
|
||||
if (throwExceptions) throw new StringIndexOutOfBoundsException(message);
|
||||
return false;
|
||||
}
|
||||
if (hasLink(start, end)) {
|
||||
final String message = String.format(Locale.US,
|
||||
"link already added in this range! text:%s, link:%s, display:%s, start:%d, end:%d", source, link,
|
||||
display, start, end);
|
||||
if (throwExceptions) throw new IllegalArgumentException(message);
|
||||
return false;
|
||||
}
|
||||
return spanSpecs.add(new LinkSpec(link, display, start, end, displayIsHtml));
|
||||
}
|
||||
|
||||
public Pair<String, SpanItem[]> buildWithIndices() {
|
||||
if (spanSpecs.isEmpty()) return new Pair<>(escapeSource(), new SpanItem[0]);
|
||||
Collections.sort(spanSpecs);
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int linksSize = spanSpecs.size();
|
||||
SpanItem[] items = new SpanItem[linksSize];
|
||||
for (int i = 0; i < linksSize; i++) {
|
||||
final SpanSpec spec = spanSpecs.get(i);
|
||||
final int start = spec.getStart(), end = spec.getEnd();
|
||||
if (i == 0) {
|
||||
if (start >= 0 && start <= sourceLength) {
|
||||
appendSource(sb, 0, start, false, sourceIsEscaped);
|
||||
}
|
||||
} else if (i > 0) {
|
||||
final int lastEnd = spanSpecs.get(i - 1).end;
|
||||
if (lastEnd >= 0 && lastEnd <= start && start <= sourceLength) {
|
||||
appendSource(sb, lastEnd, start, false, sourceIsEscaped);
|
||||
}
|
||||
}
|
||||
int spanStart = sb.length();
|
||||
if (start >= 0 && start <= end && end <= sourceLength) {
|
||||
spec.appendTo(sb);
|
||||
}
|
||||
final SpanItem item = new SpanItem();
|
||||
item.start = spanStart;
|
||||
item.end = sb.length();
|
||||
item.orig_start = start;
|
||||
item.orig_end = end;
|
||||
if (spec instanceof LinkSpec) {
|
||||
item.link = ((LinkSpec) spec).link;
|
||||
}
|
||||
items[i] = item;
|
||||
if (i == linksSize - 1 && end >= 0 && end <= sourceLength) {
|
||||
appendSource(sb, end, sourceLength, false, sourceIsEscaped);
|
||||
}
|
||||
}
|
||||
return new Pair<>(sb.toString(), items);
|
||||
}
|
||||
|
||||
public boolean hasLink(final int start, final int end) {
|
||||
for (final SpanSpec spec : spanSpecs) {
|
||||
final int specStart = spec.getStart(), specEnd = spec.getEnd();
|
||||
if (start >= specStart && start <= specEnd || end >= specStart && end <= specEnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HtmlBuilder{" +
|
||||
", codePoints=" + source +
|
||||
", codePointsLength=" + sourceLength +
|
||||
", throwExceptions=" + throwExceptions +
|
||||
", sourceIsEscaped=" + sourceIsEscaped +
|
||||
", shouldReEscape=" + shouldReEscape +
|
||||
", links=" + spanSpecs +
|
||||
'}';
|
||||
}
|
||||
|
||||
private void appendSource(final StringBuilder builder, final int start, final int end, boolean escapeSource, boolean sourceEscaped) {
|
||||
if (sourceEscaped == escapeSource) {
|
||||
append(builder, source.substring(start, end), escapeSource, sourceEscaped);
|
||||
} else if (escapeSource) {
|
||||
append(builder, escape(source.substring(start, end)), true, sourceEscaped);
|
||||
} else {
|
||||
append(builder, unescape(source.substring(start, end)), false, sourceEscaped);
|
||||
}
|
||||
}
|
||||
|
||||
private static void append(final StringBuilder builder, final String text, boolean escapeText, boolean textEscaped) {
|
||||
if (textEscaped == escapeText) {
|
||||
builder.append(text);
|
||||
} else if (escapeText) {
|
||||
builder.append(escape(text));
|
||||
} else {
|
||||
builder.append(unescape(text));
|
||||
}
|
||||
}
|
||||
|
||||
private String escapeSource() {
|
||||
final String source = this.source.substring(0, this.source.length());
|
||||
if (sourceIsEscaped == shouldReEscape) return source;
|
||||
return shouldReEscape ? escape(source) : unescape(source);
|
||||
}
|
||||
|
||||
static abstract class SpanSpec implements Comparable<SpanSpec> {
|
||||
|
||||
final int start, end;
|
||||
|
||||
public final int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public final int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public SpanSpec(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull final SpanSpec that) {
|
||||
return start - that.start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
SpanSpec spanSpec = (SpanSpec) o;
|
||||
|
||||
if (start != spanSpec.start) return false;
|
||||
return end == spanSpec.end;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = start;
|
||||
result = 31 * result + end;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SpanSpec{" +
|
||||
"start=" + start +
|
||||
", end=" + end +
|
||||
'}';
|
||||
}
|
||||
|
||||
public abstract void appendTo(StringBuilder sb);
|
||||
}
|
||||
|
||||
static final class LinkSpec extends SpanSpec {
|
||||
|
||||
final String link, display;
|
||||
|
||||
final boolean displayIsHtml;
|
||||
|
||||
LinkSpec(final String link, final String display, final int start, final int end, final boolean displayIsHtml) {
|
||||
super(start, end);
|
||||
this.link = link;
|
||||
this.display = display;
|
||||
this.displayIsHtml = displayIsHtml;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void appendTo(StringBuilder sb) {
|
||||
if (isEmpty(display)) {
|
||||
append(sb, link, false, false);
|
||||
} else {
|
||||
append(sb, display, false, displayIsHtml);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
LinkSpec linkSpec = (LinkSpec) o;
|
||||
|
||||
if (displayIsHtml != linkSpec.displayIsHtml) return false;
|
||||
if (link != null ? !link.equals(linkSpec.link) : linkSpec.link != null) return false;
|
||||
return display != null ? display.equals(linkSpec.display) : linkSpec.display == null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + (link != null ? link.hashCode() : 0);
|
||||
result = 31 * result + (display != null ? display.hashCode() : 0);
|
||||
result = 31 * result + (displayIsHtml ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LinkSpec{" +
|
||||
"link='" + link + '\'' +
|
||||
", display='" + display + '\'' +
|
||||
", displayIsHtml=" + displayIsHtml +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.text.translate.AggregateTranslator;
|
||||
import org.apache.commons.lang3.text.translate.CodePointTranslator;
|
||||
import org.apache.commons.lang3.text.translate.EntityArrays;
|
||||
import org.apache.commons.lang3.text.translate.LookupTranslator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
public class HtmlEscapeHelper {
|
||||
|
||||
public static final AggregateTranslator ESCAPE_HTML = new AggregateTranslator(StringEscapeUtils.ESCAPE_HTML4,
|
||||
new UnicodeControlCharacterToHtmlTranslator());
|
||||
public static final LookupTranslator ESCAPE_BASIC = new LookupTranslator(EntityArrays.BASIC_ESCAPE());
|
||||
|
||||
private HtmlEscapeHelper() {
|
||||
}
|
||||
|
||||
public static String escape(final CharSequence text) {
|
||||
if (text == null) return null;
|
||||
return ESCAPE_HTML.translate(text);
|
||||
}
|
||||
|
||||
public static String toPlainText(final String string) {
|
||||
if (string == null) return null;
|
||||
return unescape(string.replace("<br/>", "\n").replaceAll("<!--.*?-->|<[^>]+>", ""));
|
||||
}
|
||||
|
||||
public static String unescape(final String string) {
|
||||
if (string == null) return null;
|
||||
return StringEscapeUtils.unescapeHtml4(string);
|
||||
}
|
||||
|
||||
public static String escapeBasic(CharSequence text) {
|
||||
return ESCAPE_BASIC.translate(text);
|
||||
}
|
||||
|
||||
private static class UnicodeControlCharacterToHtmlTranslator extends CodePointTranslator {
|
||||
|
||||
@Override
|
||||
public boolean translate(int codePoint, Writer out) throws IOException {
|
||||
if (Character.isISOControl(codePoint)) {
|
||||
out.append("&#x");
|
||||
final char[] chars = Character.toChars(codePoint);
|
||||
for (char c : chars) {
|
||||
out.append(Integer.toHexString(c));
|
||||
}
|
||||
out.append(';');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,6 @@ import org.mariotaku.twidere.util.database.FilterQueryBuilder;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
|
@ -34,10 +33,7 @@ import kotlin.Pair;
|
|||
*/
|
||||
public class InternalTwitterContentUtils {
|
||||
|
||||
public static final int TWITTER_BULK_QUERY_COUNT = 100;
|
||||
private static final Pattern PATTERN_TWITTER_STATUS_LINK = Pattern.compile("https?://twitter\\.com/(?:#!/)?(\\w+)/status(es)?/(\\d+)");
|
||||
private static final CharSequenceTranslator UNESCAPE_TWITTER_RAW_TEXT = new LookupTranslator(EntityArrays.BASIC_UNESCAPE());
|
||||
private static final CharSequenceTranslator ESCAPE_TWITTER_RAW_TEXT = new LookupTranslator(EntityArrays.BASIC_ESCAPE());
|
||||
|
||||
private InternalTwitterContentUtils() {
|
||||
}
|
||||
|
@ -73,9 +69,8 @@ public class InternalTwitterContentUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isFiltered(final SQLiteDatabase database, final ParcelableStatus status,
|
||||
final boolean filterRTs) {
|
||||
if (database == null || status == null) return false;
|
||||
public static boolean isFiltered(@NonNull final SQLiteDatabase database,
|
||||
@NonNull final ParcelableStatus status, final boolean filterRTs) {
|
||||
return isFiltered(database, status.user_key, status.text_plain, status.quoted_text_plain,
|
||||
status.spans, status.quoted_spans, status.source, status.quoted_source,
|
||||
status.retweeted_by_user_key, status.quoted_user_key, filterRTs);
|
||||
|
@ -93,6 +88,7 @@ public class InternalTwitterContentUtils {
|
|||
return authority != null && authority.endsWith(".twimg.com") ? baseUrl + "/" + type : baseUrl;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getBestBannerType(final int width, int height) {
|
||||
if (height > 0 && width / height >= 3) {
|
||||
if (width <= 300) return "300x100";
|
||||
|
@ -115,9 +111,10 @@ public class InternalTwitterContentUtils {
|
|||
final UrlEntity[] urls = user.getDescriptionEntities();
|
||||
if (urls != null) {
|
||||
for (final UrlEntity url : urls) {
|
||||
final String expanded_url = url.getExpandedUrl();
|
||||
if (expanded_url != null) {
|
||||
builder.addLink(expanded_url, url.getDisplayUrl(), url.getStart(), url.getEnd());
|
||||
final String expandedUrl = url.getExpandedUrl();
|
||||
if (expandedUrl != null) {
|
||||
builder.addLink(expandedUrl, url.getDisplayUrl(), url.getStart(), url.getEnd(),
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,7 +225,7 @@ public class InternalTwitterContentUtils {
|
|||
final String mediaUrl = getMediaUrl(mediaEntity);
|
||||
if (mediaUrl != null && getStartEndForEntity(mediaEntity, startEnd)) {
|
||||
builder.addLink(mediaEntity.getExpandedUrl(), mediaEntity.getDisplayUrl(),
|
||||
startEnd[0], startEnd[1]);
|
||||
startEnd[0], startEnd[1], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +235,7 @@ public class InternalTwitterContentUtils {
|
|||
final String expandedUrl = urlEntity.getExpandedUrl();
|
||||
if (expandedUrl != null && getStartEndForEntity(urlEntity, startEnd)) {
|
||||
builder.addLink(expandedUrl, urlEntity.getDisplayUrl(), startEnd[0],
|
||||
startEnd[1]);
|
||||
startEnd[1], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ fun Account.toParcelable(accountKey: UserKey, position: Long = 0): ParcelableUse
|
|||
obj.description_plain = obj.description_unescaped
|
||||
obj.description_spans = descriptionHtml?.spanItems
|
||||
} else {
|
||||
obj.description_unescaped = HtmlEscapeHelper.unescape(note)
|
||||
obj.description_unescaped = note?.let(HtmlEscapeHelper::unescape)
|
||||
obj.description_plain = obj.description_unescaped
|
||||
}
|
||||
obj.url = url
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.mariotaku.twidere.util.HtmlEscapeHelper
|
|||
* Created by mariotaku on 2017/4/19.
|
||||
*/
|
||||
val Application.sourceHtml: String get() {
|
||||
if (website == null) return HtmlEscapeHelper.escape(name)
|
||||
val name = this.name ?: return ""
|
||||
val website = this.website ?: return name.let(HtmlEscapeHelper::escape).orEmpty()
|
||||
return "<a href='${HtmlEscapeHelper.escape(website)}'>${HtmlEscapeHelper.escape(name)}</a>"
|
||||
}
|
|
@ -158,7 +158,9 @@ class AddStatusFilterDialogFragment : BaseDialogFragment() {
|
|||
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_KEYWORD, hashtag))
|
||||
}
|
||||
val source = HtmlEscapeHelper.toPlainText(status.source)
|
||||
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_SOURCE, source))
|
||||
if (source != null) {
|
||||
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_SOURCE, source))
|
||||
}
|
||||
return list.toTypedArray()
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util
|
||||
|
||||
import org.mariotaku.commons.text.CodePointArray
|
||||
import org.mariotaku.twidere.model.SpanItem
|
||||
import java.util.*
|
||||
|
||||
class HtmlBuilder(
|
||||
private val source: CodePointArray,
|
||||
private val throwExceptions: Boolean,
|
||||
private val sourceIsEscaped: Boolean,
|
||||
private val shouldReEscape: Boolean
|
||||
) {
|
||||
|
||||
private val sourceLength = source.length()
|
||||
|
||||
private val spanSpecs = ArrayList<SpanSpec>()
|
||||
|
||||
constructor(source: String, strict: Boolean, sourceIsEscaped: Boolean, shouldReEscape: Boolean)
|
||||
: this(CodePointArray(source), strict, sourceIsEscaped, shouldReEscape)
|
||||
|
||||
fun addLink(link: String, display: String, start: Int, end: Int,
|
||||
displayIsHtml: Boolean = false): Boolean {
|
||||
if (start < 0 || end < 0 || start > end || end > sourceLength) {
|
||||
if (throwExceptions) {
|
||||
val message = "text:$source, length:$sourceLength, start:$start, end:$end"
|
||||
throw StringIndexOutOfBoundsException(message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (hasLink(start, end)) {
|
||||
if (throwExceptions) {
|
||||
val message = "link already added in this range! text:$source, link:$link, display:$display, start:$start, end:end"
|
||||
throw IllegalArgumentException(message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return spanSpecs.add(LinkSpec(link, display, start, end, displayIsHtml))
|
||||
}
|
||||
|
||||
fun buildWithIndices(): Pair<String, Array<SpanItem>> {
|
||||
if (spanSpecs.isEmpty()) return Pair(escapeSource(), emptyArray())
|
||||
Collections.sort(spanSpecs)
|
||||
val sb = StringBuilder()
|
||||
val linksSize = spanSpecs.size
|
||||
val items = arrayOfNulls<SpanItem>(linksSize)
|
||||
for (i in 0..linksSize - 1) {
|
||||
val spec = spanSpecs[i]
|
||||
val start = spec.start
|
||||
val end = spec.end
|
||||
if (i == 0) {
|
||||
if (start in 0..sourceLength) {
|
||||
appendSource(sb, 0, start, false, sourceIsEscaped)
|
||||
}
|
||||
} else if (i > 0) {
|
||||
val lastEnd = spanSpecs[i - 1].end
|
||||
if (lastEnd in 0..start && start <= sourceLength) {
|
||||
appendSource(sb, lastEnd, start, false, sourceIsEscaped)
|
||||
}
|
||||
}
|
||||
val spanStart = sb.length
|
||||
if (start in 0..end && end <= sourceLength) {
|
||||
spec.appendTo(sb)
|
||||
}
|
||||
val item = SpanItem()
|
||||
item.start = spanStart
|
||||
item.end = sb.length
|
||||
item.orig_start = start
|
||||
item.orig_end = end
|
||||
if (spec is LinkSpec) {
|
||||
item.link = spec.link
|
||||
}
|
||||
items[i] = item
|
||||
if (i == linksSize - 1 && end >= 0 && end <= sourceLength) {
|
||||
appendSource(sb, end, sourceLength, false, sourceIsEscaped)
|
||||
}
|
||||
}
|
||||
return Pair(sb.toString(), items.requireNoNulls())
|
||||
}
|
||||
|
||||
fun hasLink(start: Int, end: Int): Boolean {
|
||||
for (spec in spanSpecs) {
|
||||
val specStart = spec.start
|
||||
val specEnd = spec.end
|
||||
if (start in specStart..specEnd || end in specStart..specEnd) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "HtmlBuilder{" +
|
||||
", codePoints=" + source +
|
||||
", codePointsLength=" + sourceLength +
|
||||
", throwExceptions=" + throwExceptions +
|
||||
", sourceIsEscaped=" + sourceIsEscaped +
|
||||
", shouldReEscape=" + shouldReEscape +
|
||||
", links=" + spanSpecs +
|
||||
'}'
|
||||
}
|
||||
|
||||
private fun appendSource(builder: StringBuilder, start: Int, end: Int, escapeSource: Boolean, sourceEscaped: Boolean) {
|
||||
if (sourceEscaped == escapeSource) {
|
||||
builder.append(source.substring(start, end), escapeSource, sourceEscaped)
|
||||
} else if (escapeSource) {
|
||||
builder.append(HtmlEscapeHelper.escape(source.substring(start, end)), true, sourceEscaped)
|
||||
} else {
|
||||
builder.append(HtmlEscapeHelper.unescape(source.substring(start, end)), false, sourceEscaped)
|
||||
}
|
||||
}
|
||||
|
||||
private fun escapeSource(): String {
|
||||
val source = this.source.substring(0, this.source.length())
|
||||
if (sourceIsEscaped == shouldReEscape) return source
|
||||
return if (shouldReEscape) HtmlEscapeHelper.escape(source) else HtmlEscapeHelper.unescape(source)
|
||||
}
|
||||
|
||||
private interface SpanSpec : Comparable<SpanSpec> {
|
||||
val start: Int
|
||||
val end: Int
|
||||
|
||||
override fun compareTo(other: SpanSpec): Int {
|
||||
return start - other.start
|
||||
}
|
||||
|
||||
fun appendTo(sb: StringBuilder)
|
||||
}
|
||||
|
||||
private data class LinkSpec(
|
||||
val link: String,
|
||||
val display: String?,
|
||||
override val start: Int,
|
||||
override val end: Int,
|
||||
val displayIsHtml: Boolean
|
||||
) : SpanSpec {
|
||||
|
||||
override fun appendTo(sb: StringBuilder) {
|
||||
if (display != null) {
|
||||
sb.append(display, false, displayIsHtml)
|
||||
} else {
|
||||
sb.append(link, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun StringBuilder.append(text: String, escapeText: Boolean, textEscaped: Boolean) {
|
||||
if (textEscaped == escapeText) {
|
||||
append(text)
|
||||
} else if (escapeText) {
|
||||
append(HtmlEscapeHelper.escape(text))
|
||||
} else {
|
||||
append(HtmlEscapeHelper.unescape(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util
|
||||
|
||||
import org.apache.commons.lang3.StringEscapeUtils
|
||||
import org.apache.commons.lang3.text.translate.AggregateTranslator
|
||||
import org.apache.commons.lang3.text.translate.CodePointTranslator
|
||||
import org.apache.commons.lang3.text.translate.EntityArrays
|
||||
import org.apache.commons.lang3.text.translate.LookupTranslator
|
||||
import java.io.IOException
|
||||
import java.io.Writer
|
||||
|
||||
object HtmlEscapeHelper {
|
||||
|
||||
val ESCAPE_HTML = AggregateTranslator(
|
||||
StringEscapeUtils.ESCAPE_HTML4,
|
||||
UnicodeControlCharacterToHtmlTranslator()
|
||||
)
|
||||
val ESCAPE_BASIC = LookupTranslator(*EntityArrays.BASIC_ESCAPE())
|
||||
|
||||
val UNESCAPE_HTML = AggregateTranslator(
|
||||
StringEscapeUtils.UNESCAPE_HTML4,
|
||||
LookupTranslator(*EntityArrays.APOS_UNESCAPE())
|
||||
)
|
||||
|
||||
fun escape(text: CharSequence): String {
|
||||
return ESCAPE_HTML.translate(text)
|
||||
}
|
||||
|
||||
fun toPlainText(string: String): String {
|
||||
return unescape(string.replace("<br/>", "\n").replace("<!--.*?-->|<[^>]+>".toRegex(), ""))
|
||||
}
|
||||
|
||||
fun unescape(string: String): String {
|
||||
return UNESCAPE_HTML.translate(string)
|
||||
}
|
||||
|
||||
fun escapeBasic(text: CharSequence): String {
|
||||
return ESCAPE_BASIC.translate(text)
|
||||
}
|
||||
|
||||
private class UnicodeControlCharacterToHtmlTranslator : CodePointTranslator() {
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun translate(codePoint: Int, out: Writer): Boolean {
|
||||
if (Character.isISOControl(codePoint)) {
|
||||
out.append("&#x")
|
||||
val chars = Character.toChars(codePoint)
|
||||
for (c in chars) {
|
||||
out.append(Integer.toHexString(c.toInt()))
|
||||
}
|
||||
out.append(';')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue