mirror of https://github.com/NekoX-Dev/NekoX.git
299 lines
12 KiB
Java
299 lines
12 KiB
Java
package org.telegram.messenger.utils;
|
|
|
|
import android.graphics.Typeface;
|
|
import android.os.Build;
|
|
import android.text.Editable;
|
|
import android.text.Html;
|
|
import android.text.Spannable;
|
|
import android.text.SpannableString;
|
|
import android.text.Spanned;
|
|
import android.text.style.StrikethroughSpan;
|
|
import android.text.style.StyleSpan;
|
|
import android.text.style.URLSpan;
|
|
import android.text.style.UnderlineSpan;
|
|
|
|
import org.telegram.messenger.FileLog;
|
|
import org.telegram.messenger.MediaDataController;
|
|
import org.telegram.tgnet.TLRPC;
|
|
import org.telegram.ui.Components.AnimatedEmojiSpan;
|
|
import org.telegram.ui.Components.URLSpanReplacement;
|
|
import org.xml.sax.Attributes;
|
|
import org.xml.sax.ContentHandler;
|
|
import org.xml.sax.Locator;
|
|
import org.xml.sax.SAXException;
|
|
import org.xml.sax.XMLReader;
|
|
|
|
import java.util.ArrayDeque;
|
|
import java.util.ArrayList;
|
|
|
|
public class CopyUtilities {
|
|
|
|
private final static int TYPE_SPOILER = 0;
|
|
private final static int TYPE_MONO = 1;
|
|
|
|
public static Spannable fromHTML(String html) {
|
|
Spanned spanned;
|
|
try {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
spanned = Html.fromHtml("<inject>" + html + "</inject>", Html.FROM_HTML_MODE_COMPACT, null, new HTMLTagAttributesHandler(new HTMLTagHandler()));
|
|
} else {
|
|
spanned = Html.fromHtml("<inject>" + html + "</inject>", null, new HTMLTagAttributesHandler(new HTMLTagHandler()));
|
|
}
|
|
} catch (Exception e) {
|
|
FileLog.e("Html.fromHtml", e);
|
|
return null;
|
|
}
|
|
if (spanned == null) {
|
|
return null;
|
|
}
|
|
|
|
Object[] spans = spanned.getSpans(0, spanned.length(), Object.class);
|
|
ArrayList<TLRPC.MessageEntity> entities = new ArrayList<>(spans.length);
|
|
for (int i = 0; i < spans.length; ++i) {
|
|
Object span = spans[i];
|
|
int start = spanned.getSpanStart(span);
|
|
int end = spanned.getSpanEnd(span);
|
|
if (span instanceof StyleSpan) {
|
|
int style = ((StyleSpan) span).getStyle();
|
|
if ((style & Typeface.BOLD) > 0) {
|
|
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityBold(), start, end));
|
|
}
|
|
if ((style & Typeface.ITALIC) > 0) {
|
|
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityItalic(), start, end));
|
|
}
|
|
} else if (span instanceof UnderlineSpan) {
|
|
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityUnderline(), start, end));
|
|
} else if (span instanceof StrikethroughSpan) {
|
|
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityStrike(), start, end));
|
|
} else if (span instanceof ParsedSpan) {
|
|
ParsedSpan parsedSpan = (ParsedSpan) span;
|
|
if (parsedSpan.type == TYPE_SPOILER) {
|
|
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntitySpoiler(), start, end));
|
|
} else if (parsedSpan.type == TYPE_MONO) {
|
|
entities.add(setEntityStartEnd(new TLRPC.TL_messageEntityPre(), start, end));
|
|
}
|
|
} else if (span instanceof AnimatedEmojiSpan) {
|
|
TLRPC.TL_messageEntityCustomEmoji entity = new TLRPC.TL_messageEntityCustomEmoji();
|
|
entity.document_id = ((AnimatedEmojiSpan) span).documentId;
|
|
entity.document = ((AnimatedEmojiSpan) span).document;
|
|
entities.add(setEntityStartEnd(entity, start, end));
|
|
}
|
|
}
|
|
|
|
SpannableString spannable = new SpannableString(spanned.toString());
|
|
MediaDataController.addTextStyleRuns(entities, spannable, spannable);
|
|
for (int i = 0; i < spans.length; ++i) {
|
|
Object span = spans[i];
|
|
if (span instanceof URLSpan) {
|
|
int start = spanned.getSpanStart(span);
|
|
int end = spanned.getSpanEnd(span);
|
|
String text = spanned.subSequence(start, end).toString();
|
|
String url = ((URLSpan) span).getURL();
|
|
if (text.equals(url)) {
|
|
spannable.setSpan(new URLSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
} else {
|
|
spannable.setSpan(new URLSpanReplacement(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
}
|
|
}
|
|
}
|
|
MediaDataController.addAnimatedEmojiSpans(entities, spannable, null);
|
|
return spannable;
|
|
}
|
|
|
|
private static TLRPC.MessageEntity setEntityStartEnd(TLRPC.MessageEntity entity, int spanStart, int spanEnd) {
|
|
entity.offset = spanStart;
|
|
entity.length = spanEnd - spanStart;
|
|
return entity;
|
|
}
|
|
|
|
public static class HTMLTagAttributesHandler implements Html.TagHandler, ContentHandler {
|
|
public interface TagHandler {
|
|
boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes);
|
|
}
|
|
|
|
public static String getValue(Attributes attributes, String name) {
|
|
for (int i = 0, n = attributes.getLength(); i < n; ++i) {
|
|
if (name.equals(attributes.getLocalName(i))) {
|
|
return attributes.getValue(i);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private final TagHandler handler;
|
|
private ContentHandler wrapped;
|
|
private Editable text;
|
|
private ArrayDeque<Boolean> tagStatus = new ArrayDeque<>();
|
|
|
|
private HTMLTagAttributesHandler(TagHandler handler) {
|
|
this.handler = handler;
|
|
}
|
|
|
|
@Override
|
|
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
|
|
if (wrapped == null) {
|
|
text = output;
|
|
wrapped = xmlReader.getContentHandler();
|
|
xmlReader.setContentHandler(this);
|
|
tagStatus.addLast(Boolean.FALSE);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
|
|
boolean isHandled = handler.handleTag(true, localName, text, attributes);
|
|
tagStatus.addLast(isHandled);
|
|
if (!isHandled) {
|
|
wrapped.startElement(uri, localName, qName, attributes);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void endElement(String uri, String localName, String qName) throws SAXException {
|
|
if (!tagStatus.removeLast()) {
|
|
wrapped.endElement(uri, localName, qName);
|
|
}
|
|
handler.handleTag(false, localName, text, null);
|
|
}
|
|
|
|
@Override
|
|
public void setDocumentLocator(Locator locator) {
|
|
wrapped.setDocumentLocator(locator);
|
|
}
|
|
|
|
@Override
|
|
public void startDocument() throws SAXException {
|
|
wrapped.startDocument();
|
|
}
|
|
|
|
@Override
|
|
public void endDocument() throws SAXException {
|
|
wrapped.endDocument();
|
|
}
|
|
|
|
@Override
|
|
public void startPrefixMapping(String prefix, String uri) throws SAXException {
|
|
wrapped.startPrefixMapping(prefix, uri);
|
|
}
|
|
|
|
@Override
|
|
public void endPrefixMapping(String prefix) throws SAXException {
|
|
wrapped.endPrefixMapping(prefix);
|
|
}
|
|
|
|
@Override
|
|
public void characters(char[] ch, int start, int length) throws SAXException {
|
|
wrapped.characters(ch, start, length);
|
|
}
|
|
|
|
@Override
|
|
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
|
|
wrapped.ignorableWhitespace(ch, start, length);
|
|
}
|
|
|
|
@Override
|
|
public void processingInstruction(String target, String data) throws SAXException {
|
|
wrapped.processingInstruction(target, data);
|
|
}
|
|
|
|
@Override
|
|
public void skippedEntity(String name) throws SAXException {
|
|
wrapped.skippedEntity(name);
|
|
}
|
|
}
|
|
|
|
private static class HTMLTagHandler implements HTMLTagAttributesHandler.TagHandler {
|
|
|
|
@Override
|
|
public boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes) {
|
|
if (tag.startsWith("animated-emoji")) {
|
|
if (opening) {
|
|
String documentIdString = HTMLTagAttributesHandler.getValue(attributes, "data-document-id");
|
|
if (documentIdString != null) {
|
|
long documentId = Long.parseLong(documentIdString);
|
|
output.setSpan(new AnimatedEmojiSpan(documentId, null), output.length(), output.length(), Spanned.SPAN_MARK_MARK);
|
|
return true;
|
|
}
|
|
} else {
|
|
AnimatedEmojiSpan obj = getLast(output, AnimatedEmojiSpan.class);
|
|
if (obj != null) {
|
|
int where = output.getSpanStart(obj);
|
|
output.removeSpan(obj);
|
|
if (where != output.length()) {
|
|
output.setSpan(obj, where, output.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
} else if (tag.equals("spoiler")) {
|
|
if (opening) {
|
|
output.setSpan(new ParsedSpan(TYPE_SPOILER), output.length(), output.length(), Spanned.SPAN_MARK_MARK);
|
|
return true;
|
|
} else {
|
|
ParsedSpan obj = getLast(output, ParsedSpan.class, TYPE_SPOILER);
|
|
if (obj != null) {
|
|
int where = output.getSpanStart(obj);
|
|
output.removeSpan(obj);
|
|
if (where != output.length()) {
|
|
output.setSpan(obj, where, output.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
} else if (tag.equals("pre")) {
|
|
if (opening) {
|
|
output.setSpan(new ParsedSpan(TYPE_MONO), output.length(), output.length(), Spanned.SPAN_MARK_MARK);
|
|
return true;
|
|
} else {
|
|
ParsedSpan obj = getLast(output, ParsedSpan.class, TYPE_MONO);
|
|
if (obj != null) {
|
|
int where = output.getSpanStart(obj);
|
|
output.removeSpan(obj);
|
|
if (where != output.length()) {
|
|
output.setSpan(obj, where, output.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private <T> T getLast(Editable text, Class<T> kind) {
|
|
T[] objs = text.getSpans(0, text.length(), kind);
|
|
if (objs.length == 0) {
|
|
return null;
|
|
} else {
|
|
for (int i = objs.length; i > 0; i--) {
|
|
if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) {
|
|
return objs[i - 1];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private <T extends ParsedSpan> T getLast(Editable text, Class<T> kind, int type) {
|
|
T[] objs = text.getSpans(0, text.length(), kind);
|
|
if (objs.length == 0) {
|
|
return null;
|
|
} else {
|
|
for (int i = objs.length; i > 0; i--) {
|
|
if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK && objs[i - 1].type == type) {
|
|
return objs[i - 1];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static class ParsedSpan {
|
|
final int type;
|
|
|
|
private ParsedSpan(int type) {
|
|
this.type = type;
|
|
}
|
|
}
|
|
}
|