From 9c48ce3fe6c4f037049cec3751e496271930fa5c Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Sun, 23 Apr 2017 14:39:49 +0800 Subject: [PATCH] fixed #773 --- .../mariotaku/twidere/util/HtmlBuilder.java | 277 ------------------ .../twidere/util/HtmlEscapeHelper.java | 75 ----- .../util/InternalTwitterContentUtils.java | 21 +- .../model/api/mastodon/AccountExtensions.kt | 2 +- .../api/mastodon/ApplicationExtensions.kt | 3 +- .../fragment/AddStatusFilterDialogFragment.kt | 4 +- .../org/mariotaku/twidere/util/HtmlBuilder.kt | 179 +++++++++++ .../twidere/util/HtmlEscapeHelper.kt | 75 +++++ 8 files changed, 269 insertions(+), 367 deletions(-) delete mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/HtmlBuilder.java delete mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/HtmlEscapeHelper.java create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlBuilder.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlEscapeHelper.kt diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/HtmlBuilder.java b/twidere/src/main/java/org/mariotaku/twidere/util/HtmlBuilder.java deleted file mode 100644 index b9698bfa9..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/util/HtmlBuilder.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -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 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 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 { - - 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(); - } - } - -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/HtmlEscapeHelper.java b/twidere/src/main/java/org/mariotaku/twidere/util/HtmlEscapeHelper.java deleted file mode 100644 index c8efb9e7d..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/util/HtmlEscapeHelper.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -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("
", "\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; - } - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java index 8a14fa585..0cb45ac4e 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java @@ -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); } } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/AccountExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/AccountExtensions.kt index 37bd365fd..c511f4660 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/AccountExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/AccountExtensions.kt @@ -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 diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/ApplicationExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/ApplicationExtensions.kt index 833c5c1bc..c287f3bff 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/ApplicationExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/mastodon/ApplicationExtensions.kt @@ -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 "${HtmlEscapeHelper.escape(name)}" } \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AddStatusFilterDialogFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AddStatusFilterDialogFragment.kt index ea23d3794..5eca01179 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AddStatusFilterDialogFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AddStatusFilterDialogFragment.kt @@ -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() } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlBuilder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlBuilder.kt new file mode 100644 index 000000000..1888bd816 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlBuilder.kt @@ -0,0 +1,179 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +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() + + 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> { + if (spanSpecs.isEmpty()) return Pair(escapeSource(), emptyArray()) + Collections.sort(spanSpecs) + val sb = StringBuilder() + val linksSize = spanSpecs.size + val items = arrayOfNulls(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 { + 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)) + } + } + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlEscapeHelper.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlEscapeHelper.kt new file mode 100644 index 000000000..8d2c691ce --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/HtmlEscapeHelper.kt @@ -0,0 +1,75 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2014 Mariotaku Lee + * + * 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 . + */ + +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("
", "\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 + } + } +}