diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java index d368778c..dca322bf 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathInlineProcessor.java @@ -1,11 +1,11 @@ package io.noties.markwon.ext.latex; -import androidx.annotation.Nullable; - import org.commonmark.node.Node; import java.util.regex.Pattern; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import io.noties.markwon.inlineparser.InlineProcessor; /** @@ -13,7 +13,24 @@ */ class JLatexMathInlineProcessor extends InlineProcessor { - private static final Pattern RE = Pattern.compile("(\\${2})([\\s\\S]+?)\\1"); + private static final Pattern singleOrDoubleDollar = + Pattern.compile("(\\${2})([\\s\\S]+?)(\\${2})|(\\$)([\\s\\S]+?)(\\$)"); + private static final Pattern doubleDollar = + Pattern.compile("(\\${2})([\\s\\S]+?)\\1"); + + private final boolean allowSingle$; + + @VisibleForTesting + final Pattern pattern; + + JLatexMathInlineProcessor() { + this(false); + } + + JLatexMathInlineProcessor(boolean allowSingle$) { + this.allowSingle$ = allowSingle$; + this.pattern = allowSingle$ ? singleOrDoubleDollar : doubleDollar; + } @Override public char specialCharacter() { @@ -24,13 +41,25 @@ public char specialCharacter() { @Override protected Node parse() { - final String latex = match(RE); + final String latex = match(pattern); if (latex == null) { return null; } final JLatexMathNode node = new JLatexMathNode(); - node.latex(latex.substring(2, latex.length() - 2)); + node.latex(trimDollar(latex)); return node; } + + @SuppressWarnings("DuplicateExpressions") + @VisibleForTesting + String trimDollar(String latex) { + if (allowSingle$) { + return latex.startsWith("$$") && latex.endsWith("$$") + ? latex.substring(2, latex.length() - 2) + : latex.substring(1, latex.length() - 1); + } else { + return latex.substring(2, latex.length() - 2); + } + } } diff --git a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java index dd8a606e..78d1e4c6 100644 --- a/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/io/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -119,6 +119,7 @@ static class Config { final boolean blocksEnabled; final boolean blocksLegacy; final boolean inlinesEnabled; + final boolean allowInlineSingle$; // @since 4.3.0 final ErrorHandler errorHandler; @@ -130,6 +131,7 @@ static class Config { this.blocksEnabled = builder.blocksEnabled; this.blocksLegacy = builder.blocksLegacy; this.inlinesEnabled = builder.inlinesEnabled; + this.allowInlineSingle$ = builder.allowInlineSingle$; this.errorHandler = builder.errorHandler; // @since 4.0.0 ExecutorService executorService = builder.executorService; @@ -160,7 +162,7 @@ public void configure(@NonNull Registry registry) { if (config.inlinesEnabled) { registry.require(MarkwonInlineParserPlugin.class) .factoryBuilder() - .addInlineProcessor(new JLatexMathInlineProcessor()); + .addInlineProcessor(new JLatexMathInlineProcessor(config.allowInlineSingle$)); } } @@ -285,6 +287,7 @@ public static class Builder { private boolean blocksEnabled = true; private boolean blocksLegacy; private boolean inlinesEnabled; + private boolean allowInlineSingle$; // @since 4.3.0 private ErrorHandler errorHandler; @@ -331,6 +334,16 @@ public Builder inlinesEnabled(boolean inlinesEnabled) { return this; } + /** + * @param inlineSingleDollar indicates if $xxx$ is valid. + * @since 4.7.0 + */ + @NonNull + public Builder allowInlinesSingleDollar(boolean inlineSingleDollar){ + this.allowInlineSingle$ = inlineSingleDollar; + return this; + } + @NonNull public Builder errorHandler(@Nullable ErrorHandler errorHandler) { this.errorHandler = errorHandler; diff --git a/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathInlineParserTest.java b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathInlineParserTest.java new file mode 100644 index 00000000..9927b64b --- /dev/null +++ b/markwon-ext-latex/src/test/java/io/noties/markwon/ext/latex/JLatexMathInlineParserTest.java @@ -0,0 +1,98 @@ +package io.noties.markwon.ext.latex; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.regex.Matcher; + +/** + * @author YvesCheung + * 2023/6/14 + */ +@RunWith(Parameterized.class) +public class JLatexMathInlineParserTest { + + @Parameterized.Parameters + public static Object[][] data() { + return new Object[][]{ + {true, "$ a $", "$ a $", " a "}, + {false, "$ a $", "", ""}, + + {true, "ashdksa $ a $sfalhs", "$ a $", " a "}, + {false, "ashdksa $ a $sfalhs", "", ""}, + + {true, "c sajdl asjkdhk $abc$\n sahdila", "$abc$", "abc"}, + {false, "c sajdl asjkdhk $abc$\n sahdila", "", ""}, + + {true, "c sajdl asjkdhk \n$abcd $ sahdila", "$abcd $", "abcd "}, + {false, "c sajdl asjkdhk \n$abcd $ sahdila", "", ""}, + + {true, "$$abc$$", "$$abc$$", "abc"}, + {false, "$$abc$$", "$$abc$$", "abc"}, + + {true, "$$abc$", "$$abc$", "$abc"}, + {false, "$$abc$", "", ""}, + + {true, "lllll bbb xxx $dhaksdhk\n asd ${b}", "$dhaksdhk\n asd $", "dhaksdhk\n asd "}, + {false, "lllll bbb xxx $dhaksdhk\n asd ${b}", "", ""}, + + {true, "aaa yyy $$bb $ dddd$", "$$bb $", "$bb "}, + {false, "aaa yyy $$bb $ dddd$", "", ""}, + + {true, "aaa $sagdkg$$ Hello", "$sagdkg$", "sagdkg"}, + {false, "aaa $sagdkg$$ Hello", "", ""}, + + {true, "lllll bbb xxx dhaksdhk\n asd ${b}", "", ""}, + {false, "lllll bbb xxx dhaksdhk\n asd ${b}", "", ""}, + + {true, "asdhfalkhasl suaflhslia lasdjikaih sahk", "", ""}, + {false, "asdhfalkhasl suaflhslia lasdjikaih sahk", "", ""}, + + {true, "lllll bbb $xxx dhaksdhk\n asd", "", ""}, + {false, "lllll bbb $xxx dhaksdhk\n asd", "", ""} + }; + } + + @Parameterized.Parameter(0) + public boolean allowSingle$; + + @Parameterized.Parameter(1) + public String input; + + @Parameterized.Parameter(2) + public String output; + + @Parameterized.Parameter(3) + public String trimOutput; + + @Test + public void match() { + JLatexMathInlineProcessor processor = new JLatexMathInlineProcessor(allowSingle$); + Matcher matcher = processor.pattern.matcher(input); + if (output == null || "".equals(output)) { + Assert.assertFalse(matcher.find()); + } else { + Assert.assertTrue(matcher.find()); + String actual = matcher.group(); + Assert.assertEquals(output, actual); + Assert.assertEquals(trimOutput, processor.trimDollar(actual)); + } + } + + @Test + public void not_match() { + String[][] testCase = new String[][]{ + new String[]{"lllll bbb xxx dhaksdhk\n asd ${b}"}, + new String[]{"asdhfalkhasl suaflhslia lasdjikaih sahk"}, + new String[]{"lllll bbb $xxx dhaksdhk\n asd"}, + }; + for (String[] c : testCase) { + String input = c[0]; + JLatexMathInlineProcessor processor = new JLatexMathInlineProcessor(true); + Matcher matcher = processor.pattern.matcher(input); + Assert.assertFalse(matcher.find()); + } + } +}