Merge pull request #572 from scria1000/spoiler-optimize

Optimize spoiler parser
This commit is contained in:
Docile-Alligator 2021-12-07 21:13:18 +08:00 committed by GitHub
commit 57ab519333
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -3,6 +3,7 @@ package ml.docilealligator.infinityforreddit.markdown;
import android.graphics.Color; import android.graphics.Color;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.util.Pair;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -13,8 +14,7 @@ import org.commonmark.node.HtmlBlock;
import org.commonmark.node.HtmlInline; import org.commonmark.node.HtmlInline;
import org.commonmark.parser.Parser; import org.commonmark.parser.Parser;
import java.util.LinkedHashMap; import java.util.ArrayList;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
@ -58,15 +58,15 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin {
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText()); SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());
LinkedHashMap<Integer, Integer> spoilers = parse(markdownStringBuilder); ArrayList<Pair<Integer, Integer>> spoilers = parse(markdownStringBuilder);
if (spoilers.size() == 0) { if (spoilers.size() == 0) {
return; return;
} }
int offset = 2;
for (Map.Entry<Integer, Integer> entry : spoilers.entrySet()) { int offset = 2;
int spoilerStart = entry.getKey() - offset; for (Pair<Integer, Integer> spoiler : spoilers) {
int spoilerEnd = entry.getValue() - offset; int spoilerStart = spoiler.first - offset;
int spoilerEnd = spoiler.second - offset;
// Try not to set a spoiler span if it's inside a CodeSpan // Try not to set a spoiler span if it's inside a CodeSpan
CodeSpan[] codeSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeSpan.class); CodeSpan[] codeSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeSpan.class);
@ -78,6 +78,7 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin {
SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor);
markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
offset += 4; offset += 4;
continue;
} }
for (CodeSpan codeSpan : codeSpans) { for (CodeSpan codeSpan : codeSpans) {
@ -117,86 +118,45 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin {
// Don't allow more than one new line after every non-blank line // Don't allow more than one new line after every non-blank line
// Try not to care about recursing spoilers, we just want the outermost spoiler because // Try not to care about recursing spoilers, we just want the outermost spoiler because
// spoiler revealing-hiding breaks with recursing spoilers // spoiler revealing-hiding breaks with recursing spoilers
private LinkedHashMap<Integer, Integer> parse(SpannableStringBuilder markdown) { private ArrayList<Pair<Integer, Integer>> parse(SpannableStringBuilder markdown) {
final int MAX_NEW_LINE = 1; final int MAX_NEW_LINE = 1;
var openSpoilerStack = new Stack<Integer>(); int length = markdown.length();
var closedSpoilerMap = new LinkedHashMap<Integer, Integer>(); Stack<Integer> openSpoilerStack = new Stack<>();
int variable_max_depth = calculateBalance(0, markdown) + 1; ArrayList<Pair<Integer, Integer>> closedSpoilers = new ArrayList<>();
int new_lines = 0; int new_lines = 0;
int depth = 0; for (int i = 0; i < length; i++) {
for (int i = 0; i < markdown.length(); i++) { if (markdown.charAt(i) == '\n') {
if (markdown.charAt(i) == '\u2000' || markdown.charAt(i) == '\t') {
continue;
} else if (markdown.charAt(i) == '>' && (i + 1) < markdown.length() && markdown.charAt(i + 1) == '!') {
openSpoilerStack.push(i + 1);
depth++;
} else if (openSpoilerStack.size() > 0
&& markdown.charAt(i) == '!' && (i + 1) < markdown.length()
&& markdown.charAt(i + 1) == '<') {
var pos = i + 1;
for (int j = 0; j < depth; j++) {
if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.peek();
if (pos + 1 <= i) {
if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.peek();
break;
} else {
if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.pop();
}
}
if (depth <= variable_max_depth && pos + 1 <= i) //Spoiler content cannot be zero or less length
{
openSpoilerStack.clear();
closedSpoilerMap.put(pos + 1, i);
}
depth--;
} else if (markdown.charAt(i) == '\n') {
new_lines++;
if (openSpoilerStack.size() >= 1 && new_lines > MAX_NEW_LINE) {
openSpoilerStack.clear();
new_lines = 0;
depth = 0;
variable_max_depth = calculateBalance(i, markdown) + 1;
}
} else {
new_lines = 0;
}
if (openSpoilerStack.size() >= 32) // No
{
openSpoilerStack.clear();
closedSpoilerMap.clear();
continue;
}
}
return closedSpoilerMap;
}
private int calculateBalance(int index, SpannableStringBuilder line) {
final int MAX_NEW_LINE = 1;
int new_lines = 0;
int opening = 0;
int closing = 0;
for (int i = index; i < line.length(); i++) {
if (line.charAt(i) == '\u0020' || line.charAt(i) == '\t') {
continue;
} else if (line.charAt(i) == '>'
&& (i + 1) < line.length()
&& line.charAt(i + 1) == '!') {
opening++;
} else if (line.charAt(i) == '!' && (i + 1) < line.length()
&& line.charAt(i + 1) == '<') {
closing++;
} else if (line.charAt(i) == '\n') {
new_lines++; new_lines++;
if (new_lines > MAX_NEW_LINE) { if (new_lines > MAX_NEW_LINE) {
break; openSpoilerStack.clear();
new_lines = 0;
}
} else if ((markdown.charAt(i) != '>')
&& (markdown.charAt(i) != '<')
&& (markdown.charAt(i) != '!')) {
new_lines = 0;
} else if ((i + 1 < length)
&& markdown.charAt(i) == '>'
&& markdown.charAt(i + 1) == '!') {
openSpoilerStack.push(i + 2);
} else if ((i + 1 < length) && (i - 1 >= 0)
&& openSpoilerStack.size() > 0
&& markdown.charAt(i - 1) != '>'
&& markdown.charAt(i) == '!'
&& markdown.charAt(i + 1) == '<') {
var pos = openSpoilerStack.pop();
if (!closedSpoilers.isEmpty()
&& closedSpoilers.get(closedSpoilers.size() - 1).first > pos
&& closedSpoilers.get(closedSpoilers.size() - 1).second < i) {
closedSpoilers.remove(closedSpoilers.size() - 1);
}
if (pos != i) {
closedSpoilers.add(Pair.create(pos, i));
} }
} else { } else {
new_lines = 0; new_lines = 0;
continue;
} }
} }
return Math.abs(opening - closing); return closedSpoilers;
} }
} }