Create inline plugin
This commit is contained in:
parent
da2276be67
commit
6006812f56
1
markwon-ext-inline-latex/.gitignore
vendored
Normal file
1
markwon-ext-inline-latex/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
50
markwon-ext-inline-latex/build.gradle
Normal file
50
markwon-ext-inline-latex/build.gradle
Normal file
@ -0,0 +1,50 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon-core')
|
||||
implementation project(':markwon-ext-latex')
|
||||
implementation project(':markwon-span-ext')
|
||||
|
||||
deps.with {
|
||||
// add a compileOnly dependency, so if this artifact is present
|
||||
// we will try to obtain a SpanFactory for a Strikethrough node and use
|
||||
// it to be consistent with markdown (please note that we do not use markwon plugin
|
||||
// for that in case if different implementation is used)
|
||||
compileOnly it['commonmark-strikethrough']
|
||||
|
||||
testImplementation it['ix-java']
|
||||
}
|
||||
|
||||
deps.test.with {
|
||||
testImplementation it['junit']
|
||||
testImplementation it['robolectric']
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.noties.markwon.ext.inlinelatex;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("io.noties.markwon.ext.inlinelatex.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
5
markwon-ext-inline-latex/src/main/AndroidManifest.xml
Normal file
5
markwon-ext-inline-latex/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.noties.markwon.ext.inlinelatex">
|
||||
|
||||
</manifest>
|
@ -0,0 +1,117 @@
|
||||
package io.noties.markwon.ext.inlinelatex;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.internal.util.Parsing;
|
||||
import org.commonmark.node.Block;
|
||||
import org.commonmark.parser.block.AbstractBlockParser;
|
||||
import org.commonmark.parser.block.AbstractBlockParserFactory;
|
||||
import org.commonmark.parser.block.BlockContinue;
|
||||
import org.commonmark.parser.block.BlockStart;
|
||||
import org.commonmark.parser.block.MatchedBlockParser;
|
||||
import org.commonmark.parser.block.ParserState;
|
||||
|
||||
import io.noties.markwon.ext.latex.JLatexMathBlock;
|
||||
|
||||
public class InLineLatexBlockParser extends AbstractBlockParser {
|
||||
|
||||
private static final char DOLLAR = '$';
|
||||
private static final char SPACE = ' ';
|
||||
|
||||
private final JLatexMathBlock block = new JLatexMathBlock();
|
||||
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
|
||||
private final int signs;
|
||||
|
||||
InLineLatexBlockParser(int signs) {
|
||||
this.signs = signs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockContinue tryContinue(ParserState parserState) {
|
||||
final int nextNonSpaceIndex = parserState.getNextNonSpaceIndex();
|
||||
final CharSequence line = parserState.getLine();
|
||||
final int length = line.length();
|
||||
|
||||
// check for closing
|
||||
if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) {
|
||||
if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
|
||||
// okay, we have our number of signs
|
||||
// let's consume spaces until the end
|
||||
if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) {
|
||||
return BlockContinue.finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BlockContinue.atIndex(parserState.getIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLine(CharSequence line) {
|
||||
builder.append(line);
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeBlock() {
|
||||
block.latex(builder.toString());
|
||||
}
|
||||
|
||||
public static class Factory extends AbstractBlockParserFactory {
|
||||
|
||||
@Override
|
||||
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
|
||||
|
||||
// let's define the spec:
|
||||
// * 0-3 spaces before are allowed (Parsing.CODE_BLOCK_INDENT = 4)
|
||||
// * 2+ subsequent `$` signs
|
||||
// * any optional amount of spaces
|
||||
// * new line
|
||||
// * block is closed when the same amount of opening signs is met
|
||||
|
||||
final int indent = state.getIndent();
|
||||
|
||||
// check if it's an indented code block
|
||||
if (indent >= Parsing.CODE_BLOCK_INDENT) {
|
||||
return BlockStart.none();
|
||||
}
|
||||
|
||||
final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
|
||||
final CharSequence line = state.getLine();
|
||||
final int length = line.length();
|
||||
|
||||
final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length);
|
||||
|
||||
// 2 is minimum
|
||||
if (signs < 2) {
|
||||
return BlockStart.none();
|
||||
}
|
||||
|
||||
// consume spaces until the end of the line, if any other content is found -> NONE
|
||||
if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) {
|
||||
return BlockStart.none();
|
||||
}
|
||||
|
||||
return BlockStart.of(new InLineLatexBlockParser(signs))
|
||||
.atIndex(length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static int consume(char c, @NonNull CharSequence line, int start, int end) {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (c != line.charAt(i)) {
|
||||
return i - start;
|
||||
}
|
||||
}
|
||||
// all consumed
|
||||
return end - start;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package io.noties.markwon.ext.inlinelatex;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
|
||||
public class InLineLatexGroupNode extends CustomNode {
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package io.noties.markwon.ext.inlinelatex;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
import org.commonmark.node.Delimited;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class InLineLatexNode extends CustomNode {
|
||||
|
||||
private String latex;
|
||||
|
||||
public String latex() {
|
||||
return latex;
|
||||
}
|
||||
|
||||
public void latex(String latex) {
|
||||
this.latex = latex;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package io.noties.markwon.ext.inlinelatex;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import androidx.annotation.NonNull;
|
||||
import org.commonmark.parser.Parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||
import io.noties.markwon.MarkwonVisitor;
|
||||
|
||||
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
|
||||
import io.noties.markwon.span.ext.CenteredImageSpan;
|
||||
import ru.noties.jlatexmath.JLatexMathDrawable;
|
||||
|
||||
public class InLineLatexPlugIn extends AbstractMarkwonPlugin {
|
||||
private static float mLatextSize;
|
||||
private static int mScreenWidth;
|
||||
@NonNull
|
||||
public static InLineLatexPlugIn create(float latexSize, int screenWidth)
|
||||
{
|
||||
mLatextSize = latexSize;
|
||||
mScreenWidth = screenWidth;
|
||||
return new InLineLatexPlugIn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(@NonNull Registry registry) {
|
||||
registry.require(MarkwonInlineParserPlugin.class).factoryBuilder().addInlineProcessor(new InLineLatexProcessor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureParser(@NonNull Parser.Builder builder) {
|
||||
builder.customBlockParserFactory(new InLineLatexBlockParser.Factory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||
builder.on(InLineLatexNode.class, new MarkwonVisitor.NodeVisitor<InLineLatexNode>() {
|
||||
@Override
|
||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull InLineLatexNode inLineLinkNode) {
|
||||
final String latex = inLineLinkNode.latex();
|
||||
int latxtColor = Color.BLACK;
|
||||
int backgroundColor = Color.TRANSPARENT;
|
||||
int errorColor = Color.parseColor("#ff3d00");
|
||||
if (!TextUtils.isEmpty(latex)) {
|
||||
if (latex.trim().equalsIgnoreCase("{")) {
|
||||
String errTxt = "LaTeX syntax error";
|
||||
ForegroundColorSpan fgColorSpan = new ForegroundColorSpan(errorColor);
|
||||
visitor.builder().append(errTxt, fgColorSpan);
|
||||
} else {
|
||||
try {
|
||||
int txtLength = latex.length();
|
||||
float textWidth = mScreenWidth;
|
||||
final JLatexMathDrawable latexDrawable = JLatexMathDrawable.builder(replaceLatexTag(latex))
|
||||
.textSize(mLatextSize)
|
||||
.color(latxtColor)
|
||||
.background(backgroundColor)
|
||||
.fitCanvas(false) // It will fix the truncated issue of inline latex
|
||||
.build();
|
||||
float latexWidth = latexDrawable.getIntrinsicWidth();
|
||||
// Inline latex wrap
|
||||
if (latexWidth > textWidth) {
|
||||
float oneLetterWidth = latexWidth / txtLength;
|
||||
if (oneLetterWidth < 22) {
|
||||
oneLetterWidth = 22;
|
||||
}
|
||||
int allowLen = Math.round(textWidth / oneLetterWidth) - 2;
|
||||
List<String> spiltedText = splitStringByLen(latex, allowLen);
|
||||
for (int txtIndex = 0; txtIndex < spiltedText.size(); txtIndex ++) {
|
||||
String subText = spiltedText.get(txtIndex);
|
||||
int subTextLen = subText.length();
|
||||
final JLatexMathDrawable subLatexDrawable = JLatexMathDrawable.builder(replaceLatexTag(subText))
|
||||
.textSize(mLatextSize)
|
||||
.color(latxtColor)
|
||||
.background(backgroundColor)
|
||||
.fitCanvas(true) // It will fix the truncated issue of inline latex
|
||||
.build();
|
||||
visitor.builder().append(subText, new CenteredImageSpan(subLatexDrawable));
|
||||
}
|
||||
} else {
|
||||
visitor.builder().append(latex, new CenteredImageSpan(latexDrawable));
|
||||
}
|
||||
visitor.builder().append(' ');
|
||||
} catch (Exception e) {
|
||||
String errTxt = "LaTeX syntax error";
|
||||
ForegroundColorSpan fgColorSpan = new ForegroundColorSpan(errorColor);
|
||||
visitor.builder().append(errTxt, fgColorSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return InLineLatexProcessor.prepare(markdown);
|
||||
}
|
||||
|
||||
public String replaceLatexTag(String latex) {
|
||||
String latexText = latex.replaceAll("\\\\exist ", "\\\\exists ");
|
||||
return latexText;
|
||||
}
|
||||
|
||||
public List<String> splitStringByLen(String text, int length) {
|
||||
List<String> strings = new ArrayList<String>();
|
||||
int index = 0;
|
||||
while (index < text.length()) {
|
||||
String splitText = text.substring(index, Math.min(index + length,text.length()));
|
||||
int nWhistSpace = splitText.lastIndexOf(" ");
|
||||
if (nWhistSpace > 0 && splitText.length() >= length) {
|
||||
String splitWord = splitText.substring(0, nWhistSpace);
|
||||
strings.add(splitWord);
|
||||
index += nWhistSpace;
|
||||
} else {
|
||||
strings.add(splitText);
|
||||
index += length;
|
||||
}
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package io.noties.markwon.ext.inlinelatex;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.parser.delimiter.DelimiterProcessor;
|
||||
import org.commonmark.parser.delimiter.DelimiterRun;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.noties.markwon.inlineparser.InlineProcessor;
|
||||
|
||||
public class InLineLatexProcessor extends InlineProcessor {
|
||||
|
||||
@NonNull
|
||||
public static InLineLatexProcessor create() {
|
||||
return new InLineLatexProcessor();
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static String prepare(@NonNull String input) {
|
||||
final StringBuilder builder = new StringBuilder(input);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static final Pattern RE = Pattern.compile("(\\${2})([\\s\\S]+?)\\1");
|
||||
|
||||
@Override
|
||||
public char specialCharacter() {
|
||||
return '$';
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Node parse() {
|
||||
|
||||
final String latex = match(RE);
|
||||
if (latex == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final InLineLatexNode node = new InLineLatexNode();
|
||||
node.latex(latex.substring(2, latex.length() - 2));
|
||||
return node;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.noties.markwon.ext.inlinelatex;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
1
markwon-ext-inline/.gitignore
vendored
Normal file
1
markwon-ext-inline/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
48
markwon-ext-inline/build.gradle
Normal file
48
markwon-ext-inline/build.gradle
Normal file
@ -0,0 +1,48 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon-core')
|
||||
|
||||
deps.with {
|
||||
// add a compileOnly dependency, so if this artifact is present
|
||||
// we will try to obtain a SpanFactory for a Strikethrough node and use
|
||||
// it to be consistent with markdown (please note that we do not use markwon plugin
|
||||
// for that in case if different implementation is used)
|
||||
compileOnly it['commonmark-strikethrough']
|
||||
|
||||
testImplementation it['ix-java']
|
||||
}
|
||||
|
||||
deps.test.with {
|
||||
testImplementation it['junit']
|
||||
testImplementation it['robolectric']
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.noties.markwon.ext.inline;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("io.noties.markwon.ext.inline.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
5
markwon-ext-inline/src/main/AndroidManifest.xml
Normal file
5
markwon-ext-inline/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.noties.markwon.ext.inline">
|
||||
|
||||
</manifest>
|
@ -0,0 +1,6 @@
|
||||
package io.noties.markwon.ext.inline;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
|
||||
public class InLineLinkGroupNode extends CustomNode {
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package io.noties.markwon.ext.inline;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
import org.commonmark.node.Delimited;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class InLineLinkNode extends CustomNode implements Delimited {
|
||||
|
||||
public static final String DELIMITER_STRING = "#";
|
||||
|
||||
|
||||
private final String link;
|
||||
|
||||
public InLineLinkNode(@NonNull String link) {
|
||||
this.link = link;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String link() {
|
||||
return link;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getOpeningDelimiter() {
|
||||
return DELIMITER_STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClosingDelimiter() {
|
||||
return DELIMITER_STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InLineLinkNode{" +
|
||||
"link='" + link +'\'' + '\"' + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package io.noties.markwon.ext.inline;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import org.commonmark.parser.Parser;
|
||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||
import io.noties.markwon.LinkResolver;
|
||||
import io.noties.markwon.MarkwonVisitor;
|
||||
import io.noties.markwon.core.spans.LinkSpan;
|
||||
|
||||
public class InLineLinkPlugIn extends AbstractMarkwonPlugin {
|
||||
|
||||
@NonNull
|
||||
public static InLineLinkPlugIn create() {
|
||||
return new InLineLinkPlugIn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureParser(@NonNull Parser.Builder builder) {
|
||||
builder.customDelimiterProcessor(InLineLinkProcessor.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||
builder.on(InLineLinkNode.class, new MarkwonVisitor.NodeVisitor<InLineLinkNode>() {
|
||||
@Override
|
||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull InLineLinkNode inLineLinkNode) {
|
||||
|
||||
final String link = inLineLinkNode.link();
|
||||
if (!TextUtils.isEmpty(link)) {
|
||||
final int length = visitor.length();
|
||||
visitor.builder().append(link);
|
||||
visitor.setSpans(length, new LinkSpan(visitor.configuration().theme(), link, new LinkResolver() {
|
||||
@Override
|
||||
public void resolve(@NonNull View view, @NonNull String link) {
|
||||
}
|
||||
}));
|
||||
visitor.builder().append(' ');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return InLineLinkProcessor.prepare(markdown);
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package io.noties.markwon.ext.inline;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Patterns;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.parser.delimiter.DelimiterProcessor;
|
||||
import org.commonmark.parser.delimiter.DelimiterRun;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class InLineLinkProcessor implements DelimiterProcessor {
|
||||
|
||||
private static final String TO_FIND = InLineLinkNode.DELIMITER_STRING;
|
||||
private static final Pattern PATTERN = Pattern.compile("#[0-9]{1,10}");
|
||||
|
||||
@NonNull
|
||||
public static InLineLinkProcessor create() {
|
||||
return new InLineLinkProcessor();
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static String prepare(@NonNull String input) {
|
||||
final StringBuilder builder = new StringBuilder(input);
|
||||
prepare(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static void prepare(@NonNull StringBuilder builder) {
|
||||
|
||||
int start = builder.indexOf(TO_FIND);
|
||||
int end;
|
||||
|
||||
while (start > -1) {
|
||||
|
||||
end = inLineDefinitionEnd(start + TO_FIND.length(), builder);
|
||||
if (iconDefinitionValid(builder.subSequence(start, end))) {
|
||||
builder.insert(end, '#');
|
||||
}
|
||||
// move to next
|
||||
start = builder.indexOf(TO_FIND, end);
|
||||
}
|
||||
}
|
||||
|
||||
private static int inLineDefinitionEnd(int index, @NonNull StringBuilder builder) {
|
||||
|
||||
// all spaces, new lines, non-words or digits,
|
||||
|
||||
char c;
|
||||
|
||||
int end = -1;
|
||||
for (int i = index; i < builder.length(); i++) {
|
||||
c = builder.charAt(i);
|
||||
if (Character.isWhitespace(c) || !Character.isDigit(c)) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (end == -1) {
|
||||
end = builder.length();
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
private static boolean iconDefinitionValid(@NonNull CharSequence cs) {
|
||||
final Matcher matcher = PATTERN.matcher(cs);
|
||||
return matcher.matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getOpeningCharacter() {
|
||||
return '#';
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getClosingCharacter() {
|
||||
return '#';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinLength() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
|
||||
return opener.length() >= 1 && closer.length() >= 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Text opener, Text closer, int delimiterUse) {
|
||||
|
||||
final InLineLinkGroupNode inLineLinkGroupNode = new InLineLinkGroupNode();
|
||||
|
||||
final Node next = opener.getNext();
|
||||
|
||||
boolean handled = false;
|
||||
|
||||
// process only if we have exactly one Text node
|
||||
|
||||
if (next instanceof Text) {
|
||||
final String text = ((Text) next).getLiteral();
|
||||
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
// attempt to match
|
||||
InLineLinkNode iconNode = new InLineLinkNode(InLineLinkNode.DELIMITER_STRING + text);
|
||||
inLineLinkGroupNode.appendChild(iconNode);
|
||||
next.unlink();
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!handled) {
|
||||
|
||||
// restore delimiters if we didn't match
|
||||
|
||||
inLineLinkGroupNode.appendChild(new Text(InLineLinkNode.DELIMITER_STRING));
|
||||
|
||||
Node node;
|
||||
for (Node tmp = opener.getNext(); tmp != null && tmp != closer; tmp = node) {
|
||||
node = tmp.getNext();
|
||||
// append a child anyway
|
||||
inLineLinkGroupNode.appendChild(tmp);
|
||||
}
|
||||
|
||||
inLineLinkGroupNode.appendChild(new Text(InLineLinkNode.DELIMITER_STRING));
|
||||
}
|
||||
opener.insertBefore(inLineLinkGroupNode);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.noties.markwon.ext.inline;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user