diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java index 1cca0cfe..e399bece 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java @@ -3,6 +3,7 @@ package ml.docilealligator.infinityforreddit.markdown; import android.graphics.Color; import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.util.Pair; import android.widget.TextView; import androidx.annotation.NonNull; @@ -13,8 +14,7 @@ import org.commonmark.node.HtmlBlock; import org.commonmark.node.HtmlInline; import org.commonmark.parser.Parser; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.ArrayList; import java.util.Set; import java.util.Stack; @@ -52,21 +52,21 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { public void afterSetText(@NonNull TextView textView) { textView.setHighlightColor(Color.TRANSPARENT); - if(textView.getText().length() < 5) { + if (textView.getText().length() < 5) { return; } SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText()); - LinkedHashMap spoilers = parse(markdownStringBuilder); - if(spoilers.size() == 0) { + ArrayList> spoilers = parse(markdownStringBuilder); + if (spoilers.size() == 0) { return; } - int offset = 2; - for (Map.Entry entry : spoilers.entrySet()) { - int spoilerStart = entry.getKey() - offset; - int spoilerEnd = entry.getValue() - offset; + int offset = 2; + for (Pair spoiler : spoilers) { + int spoilerStart = spoiler.first - offset; + int spoilerEnd = spoiler.second - offset; // Try not to set a spoiler span if it's inside a CodeSpan CodeSpan[] codeSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeSpan.class); @@ -78,6 +78,7 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); offset += 4; + continue; } for (CodeSpan codeSpan : codeSpans) { @@ -108,7 +109,7 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { } } } - if(offset > 2) { + if (offset > 2) { textView.setText(markdownStringBuilder); } } @@ -117,86 +118,44 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { // 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 // spoiler revealing-hiding breaks with recursing spoilers - private LinkedHashMap parse(SpannableStringBuilder markdown) { + private ArrayList> parse(SpannableStringBuilder markdown) { final int MAX_NEW_LINE = 1; - var openSpoilerStack = new Stack(); - var closedSpoilerMap = new LinkedHashMap(); - int variable_max_depth = calculateBalance(0, markdown) + 1; + int length = markdown.length(); + Stack openSpoilerStack = new Stack<>(); + ArrayList> closedSpoilers = new ArrayList<>(); int new_lines = 0; - int depth = 0; - for (int i = 0; i < markdown.length(); i++) { - 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') { + for (int i = 0; i < length; i++) { + 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; } + if ((markdown.charAt(i) != '>') && (markdown.charAt(i) != '<') && (markdown.charAt(i) != '!')) { + continue; + } + if ((i + 1 < length) + && markdown.charAt(i) == '>' + && markdown.charAt(i + 1) == '!') { + openSpoilerStack.push(i + 2); + continue; + } + 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); + } + closedSpoilers.add(Pair.create(pos, i)); + } } - return closedSpoilerMap; + return closedSpoilers; } - - 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++; - if (new_lines > MAX_NEW_LINE) { - break; - } - } else { - new_lines = 0; - continue; - } - } - return Math.abs(opening - closing); - } - }