Try to handle unescaped space inside link blocks

* Commonmark spec does not parse links with unescaped spaces in the link query
* Maybe we can do the parsing ourselves by extending 'InlineProcessor', instead of using slow regex
* Reuse `fixSuperScript`
This commit is contained in:
scria1000 2021-11-13 01:34:28 +03:00
parent 1960bb5ea1
commit 5fae42c210
14 changed files with 97 additions and 157 deletions

View File

@ -70,7 +70,6 @@ import ml.docilealligator.infinityforreddit.comment.SendComment;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
@ -172,8 +171,13 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver((view, link) -> {
@ -220,8 +224,13 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
textView.setTextColor(markdownColor);

View File

@ -132,7 +132,7 @@ public class EditCommentActivity extends BaseActivity implements UploadImageEnab
mFullName = getIntent().getStringExtra(EXTRA_FULLNAME);
mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null);
mCommentContent = getIntent().getStringExtra(EXTRA_CONTENT);
mCommentContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">",">");
contentEditText.setText(mCommentContent);
if (savedInstanceState != null) {

View File

@ -141,7 +141,7 @@ public class EditPostActivity extends BaseActivity implements UploadImageEnabled
mFullName = getIntent().getStringExtra(EXTRA_FULLNAME);
mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null);
titleTextView.setText(getIntent().getStringExtra(EXTRA_TITLE));
mPostContent = getIntent().getStringExtra(EXTRA_CONTENT);
mPostContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">",">");;
contentEditText.setText(mPostContent);
if (savedInstanceState != null) {

View File

@ -33,7 +33,6 @@ import org.commonmark.ext.gfm.tables.TableBlock;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -61,9 +60,9 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed;
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
public class FullMarkdownActivity extends BaseActivity {
@ -143,25 +142,13 @@ public class FullMarkdownActivity extends BaseActivity {
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
/*
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
StringBuilder markdownStringBuilder = new StringBuilder(markdown);
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
ArrayList<Integer> matched = new ArrayList<>();
while (matcher.find()) {
matched.add(matcher.start());
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
for (int i = matched.size() - 1; i >= 0; i--) {
markdownStringBuilder.replace(matched.get(i), matched.get(i) + 1, "&gt;");
}
return super.processMarkdown(markdownStringBuilder.toString());
}
*/
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);

View File

@ -37,7 +37,6 @@ import org.greenrobot.eventbus.Subscribe;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -67,9 +66,9 @@ import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFi
import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager;
import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent;
import ml.docilealligator.infinityforreddit.utils.JSONUtils;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@ -168,25 +167,13 @@ public class WikiActivity extends BaseActivity {
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
/*
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
StringBuilder markdownStringBuilder = new StringBuilder(markdown);
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
ArrayList<Integer> matched = new ArrayList<>();
while (matcher.find()) {
matched.add(matcher.start());
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
for (int i = matched.size() - 1; i >= 0; i--) {
markdownStringBuilder.replace(matched.get(i), matched.get(i) + 1, "&gt;");
}
return super.processMarkdown(markdownStringBuilder.toString());
}
*/
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);

View File

@ -30,7 +30,6 @@ import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -63,7 +62,6 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.customviews.CommentIndentationView;
import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
@ -128,25 +126,13 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
/*
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
StringBuilder markdownStringBuilder = new StringBuilder(markdown);
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
ArrayList<Integer> matched = new ArrayList<>();
while (matcher.find()) {
matched.add(matcher.start());
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
for (int i = matched.size() - 1; i >= 0; i--) {
markdownStringBuilder.replace(matched.get(i), matched.get(i) + 1, "&gt;");
}
return super.processMarkdown(markdownStringBuilder.toString());
}
*/
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);

View File

@ -47,10 +47,7 @@ import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.html.HtmlEmptyTagReplacement;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.html.tag.ImageHandler;
import io.noties.markwon.html.tag.LinkHandler;
import io.noties.markwon.html.tag.SuperScriptHandler;
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
@ -74,7 +71,6 @@ import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView;
import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment;
import ml.docilealligator.infinityforreddit.post.Post;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
@ -175,25 +171,13 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
/*
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
StringBuilder markdownStringBuilder = new StringBuilder(markdown);
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
ArrayList<Integer> matched = new ArrayList<>();
while (matcher.find()) {
matched.add(matcher.start());
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
for (int i = matched.size() - 1; i >= 0; i--) {
markdownStringBuilder.replace(matched.get(i), matched.get(i) + 1, "&gt;");
}
return super.processMarkdown(markdownStringBuilder.toString());
}
*/
@Override
public void afterSetText(@NonNull TextView textView) {
SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText());

View File

@ -48,8 +48,8 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.message.FetchMessage;
import ml.docilealligator.infinityforreddit.message.Message;
import ml.docilealligator.infinityforreddit.message.ReadMessage;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Retrofit;
public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, RecyclerView.ViewHolder> {
@ -111,25 +111,12 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, Recycl
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
/*
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
StringBuilder markdownStringBuilder = new StringBuilder(markdown);
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
ArrayList<Integer> matched = new ArrayList<>();
while (matcher.find()) {
matched.add(matcher.start());
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
for (int i = matched.size() - 1; i >= 0; i--) {
markdownStringBuilder.replace(matched.get(i), matched.get(i) + 1, "&gt;");
}
return super.processMarkdown(markdownStringBuilder.toString());
}
*/
@Override
public void afterSetText(@NonNull TextView textView) {

View File

@ -112,7 +112,6 @@ import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment;
import ml.docilealligator.infinityforreddit.post.Post;
import ml.docilealligator.infinityforreddit.post.PostPagingSource;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
@ -238,25 +237,13 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
/*
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
StringBuilder markdownStringBuilder = new StringBuilder(markdown);
Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<");
Matcher matcher = spoilerPattern.matcher(markdownStringBuilder);
ArrayList<Integer> matched = new ArrayList<>();
while (matcher.find()) {
matched.add(matcher.start());
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
for (int i = matched.size() - 1; i >= 0; i--) {
markdownStringBuilder.replace(matched.get(i), matched.get(i) + 1, "&gt;");
}
return super.processMarkdown(markdownStringBuilder.toString());
}
*/
@Override
public void afterSetText(@NonNull TextView textView) {
textView.setHighlightColor(Color.TRANSPARENT);

View File

@ -39,7 +39,6 @@ import ml.docilealligator.infinityforreddit.activities.ViewPrivateMessagesActivi
import ml.docilealligator.infinityforreddit.activities.ViewUserDetailActivity;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.message.Message;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
@ -78,8 +77,13 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver((view, link) -> {

View File

@ -34,8 +34,8 @@ import ml.docilealligator.infinityforreddit.Rule;
import ml.docilealligator.infinityforreddit.activities.LinkResolverActivity;
import ml.docilealligator.infinityforreddit.bottomsheetfragments.UrlMenuBottomSheetFragment;
import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecyclerViewAdapter.RuleViewHolder> {
private Markwon markwon;
@ -52,8 +52,13 @@ public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecycler
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver((view, link) -> {

View File

@ -57,8 +57,8 @@ import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFi
import ml.docilealligator.infinityforreddit.subreddit.FetchSubredditData;
import ml.docilealligator.infinityforreddit.subreddit.SubredditData;
import ml.docilealligator.infinityforreddit.subreddit.SubredditViewModel;
import ml.docilealligator.infinityforreddit.utils.ProcessRedditSuperscript;
import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor;
import ml.docilealligator.infinityforreddit.utils.Utils;
import retrofit2.Retrofit;
public class SidebarFragment extends Fragment {
@ -116,8 +116,13 @@ public class SidebarFragment extends Fragment {
.usePlugin(HtmlPlugin.create(plugin -> {
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
}))
.usePlugin(new ProcessRedditSuperscript())
.usePlugin(new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return super.processMarkdown(Utils.fixSuperScript(new StringBuilder(markdown)));
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
textView.setTextColor(markdownColor);

View File

@ -1,46 +0,0 @@
package ml.docilealligator.infinityforreddit.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.noties.markwon.AbstractMarkwonPlugin;
public class ProcessRedditSuperscript extends AbstractMarkwonPlugin {
private static final Pattern re = Pattern.compile("(?:\\^\\((.+?)\\))|(?:\\^(\\S+))");
@Override
public String processMarkdown(String markdown) {
StringBuilder builder = new StringBuilder();
Matcher matcher = re.matcher(markdown);
int start = 0;
while (matcher.find()) {
try {
String match;
if ((match = matcher.group(1)) != null || (match = matcher.group(2)) != null) {
builder.append(markdown.substring(start, matcher.start()))
.append("<sup>")
.append(match)
.append("</sup>");
start = matcher.end();
} else {
throw new NullPointerException();
}
} catch(NullPointerException e) {
e.printStackTrace();
} finally {
continue;
}
}
if (start < markdown.length()) {
builder.append(markdown.substring(start));
}
if(builder.length() > 0) {
return builder.toString(); }
else {
return markdown;
}
}
}

View File

@ -61,12 +61,11 @@ public class Utils {
private static final long YEAR_MILLIS = 12 * MONTH_MILLIS;
public static String modifyMarkdown(String markdown) {
StringBuilder regexed = new StringBuilder(markdown
StringBuilder regexed = new StringBuilder(trimAndEscapeSpaceInLinks(markdown)
.replaceAll("((?<=[\\s])|^)/[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com$0)")
.replaceAll("((?<=[\\s])|^)[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com/$0)")
.replaceAll("\\^{2,}", "^")
.replaceAll("(</?sup)>", "$1&gt;")
.replaceAll(">!", "&gt;!")
.replaceAll(">!", "&gt;!") // If it's in a code black, html entity remains escaped
.replaceAll("(^|^ *|\\n *)#(?!($|\\s|#))", "$0 ")
.replaceAll("(^|^ *|\\n *)##(?!($|\\s|#))", "$0 ")
.replaceAll("(^|^ *|\\n *)###(?!($|\\s|#))", "$0 ")
@ -78,7 +77,53 @@ public class Utils {
return regexed.toString();
}
private static String fixSuperScript(StringBuilder regexed) {
// Also matches links inside code blocks as a side effect
private static String trimAndEscapeSpaceInLinks(String markdown){
String TEXT = "\\[([^\\[]\\n?[\\S\\h]+?\\n?[\\S\\h]*?)]";
String LINK = "(\\n?[\\h]*?\\n?https?:\\/\\/[\\S\\h]+?\n?)";
String TITLE = "(?:\\s\"(\\n?[\\S\\h]+?\\n?[\\S\\h]*?)?\")?";
Pattern pattern = Pattern.compile(TEXT + "\\(" + LINK + TITLE + "\\n?\\h*?\\)");
Matcher matcher = pattern.matcher(markdown);
StringBuilder builder = new StringBuilder();
int start = 0;
while (matcher.find()) try {
String match1;
String match2;
String match3;
if ((match1 = matcher.group(1)) != null && (match2 = matcher.group(2)) != null) {
match1 = match1.trim().replaceAll("\\s+", " ");
match2 = match2.trim().replaceAll(" ", "%20");
match3 = matcher.group(3);
builder.append(markdown.substring(start, matcher.start()))
.append("[")
.append(match1)
.append("]")
.append("(")
.append(match2);
if (match3 != null) {
builder.append(" \"")
.append(match3)
.append("\"");
}
builder.append(")");
start = matcher.end();
}
} catch (NullPointerException e) {
e.printStackTrace();
}
if (start < markdown.length()) {
builder.append(markdown.substring(start));
}
if(builder.length() > 0) {
return builder.toString(); }
else {
return markdown;
}
}
public static String fixSuperScript(StringBuilder regexed) {
boolean hasBracket = false;
int nCarets = 0;
for (int i = 0; i < regexed.length(); i++) {