Update to commonmark-java 0.13.0
This commit is contained in:
parent
d2e4730179
commit
6c4ffd1778
@ -52,7 +52,7 @@ ext {
|
||||
'push-aar-gradle': 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle'
|
||||
]
|
||||
|
||||
final def commonMarkVersion = '0.12.1'
|
||||
final def commonMarkVersion = '0.13.0'
|
||||
final def daggerVersion = '2.10'
|
||||
|
||||
deps = [
|
||||
|
@ -18,6 +18,7 @@ import org.commonmark.node.HtmlInline;
|
||||
import org.commonmark.node.Image;
|
||||
import org.commonmark.node.IndentedCodeBlock;
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.node.LinkReferenceDefinition;
|
||||
import org.commonmark.node.ListItem;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.OrderedList;
|
||||
@ -155,6 +156,11 @@ class MarkwonVisitorImpl implements MarkwonVisitor {
|
||||
visit((Node) text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(LinkReferenceDefinition linkReferenceDefinition) {
|
||||
visit((Node) linkReferenceDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(CustomBlock customBlock) {
|
||||
visit((Node) customBlock);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.noties.markwon.editor;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
@ -113,7 +114,7 @@ public abstract class MarkwonEditorTextWatcher implements TextWatcher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
public void afterTextChanged(Editable s) {
|
||||
|
||||
if (selfChange) {
|
||||
return;
|
||||
@ -126,11 +127,14 @@ public abstract class MarkwonEditorTextWatcher implements TextWatcher {
|
||||
future.cancel(true);
|
||||
}
|
||||
|
||||
// copy current content (it's not good to pass EditText editable to other thread)
|
||||
final SpannableStringBuilder builder = new SpannableStringBuilder(s);
|
||||
|
||||
future = executorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
editor.preRender(s, new MarkwonEditor.PreRenderResultListener() {
|
||||
editor.preRender(builder, new MarkwonEditor.PreRenderResultListener() {
|
||||
@Override
|
||||
public void onPreRenderResult(@NonNull final MarkwonEditor.PreRenderResult result) {
|
||||
final EditText et = editText;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.noties.markwon.inlineparser;
|
||||
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@ -24,22 +25,20 @@ public class AutolinkInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
String m;
|
||||
if ((m = match(EMAIL_AUTOLINK)) != null) {
|
||||
String dest = m.substring(1, m.length() - 1);
|
||||
Link node = new Link("mailto:" + dest, null);
|
||||
node.appendChild(new Text(dest));
|
||||
appendNode(node);
|
||||
return true;
|
||||
return node;
|
||||
} else if ((m = match(AUTOLINK)) != null) {
|
||||
String dest = m.substring(1, m.length() - 1);
|
||||
Link node = new Link(dest, null);
|
||||
node.appendChild(new Text(dest));
|
||||
appendNode(node);
|
||||
return true;
|
||||
return node;
|
||||
} else {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.noties.markwon.inlineparser;
|
||||
|
||||
import org.commonmark.node.HardLineBreak;
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -17,17 +18,18 @@ public class BackslashInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
index++;
|
||||
Node node;
|
||||
if (peek() == '\n') {
|
||||
appendNode(new HardLineBreak());
|
||||
node = new HardLineBreak();
|
||||
index++;
|
||||
} else if (index < input.length() && ESCAPABLE.matcher(input.substring(index, index + 1)).matches()) {
|
||||
appendText(input, index, index + 1);
|
||||
node = text(input, index, index + 1);
|
||||
index++;
|
||||
} else {
|
||||
appendText("\\");
|
||||
node = text("\\");
|
||||
}
|
||||
return true;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.noties.markwon.inlineparser;
|
||||
|
||||
import org.commonmark.internal.util.Parsing;
|
||||
import org.commonmark.node.Code;
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -15,18 +17,16 @@ public class BackticksInlineProcessor extends InlineProcessor {
|
||||
|
||||
private static final Pattern TICKS_HERE = Pattern.compile("^`+");
|
||||
|
||||
private static final Pattern WHITESPACE = MarkwonInlineParser.WHITESPACE;
|
||||
|
||||
@Override
|
||||
public char specialCharacter() {
|
||||
return '`';
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
String ticks = match(TICKS_HERE);
|
||||
if (ticks == null) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
int afterOpenTicks = index;
|
||||
String matched;
|
||||
@ -34,15 +34,23 @@ public class BackticksInlineProcessor extends InlineProcessor {
|
||||
if (matched.equals(ticks)) {
|
||||
Code node = new Code();
|
||||
String content = input.substring(afterOpenTicks, index - ticks.length());
|
||||
String literal = WHITESPACE.matcher(content.trim()).replaceAll(" ");
|
||||
node.setLiteral(literal);
|
||||
appendNode(node);
|
||||
return true;
|
||||
content = content.replace('\n', ' ');
|
||||
|
||||
// spec: If the resulting string both begins and ends with a space character, but does not consist
|
||||
// entirely of space characters, a single space character is removed from the front and back.
|
||||
if (content.length() >= 3 &&
|
||||
content.charAt(0) == ' ' &&
|
||||
content.charAt(content.length() - 1) == ' ' &&
|
||||
Parsing.hasNonSpace(content)) {
|
||||
content = content.substring(1, content.length() - 1);
|
||||
}
|
||||
|
||||
node.setLiteral(content);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
// If we got here, we didn't match a closing backtick sequence.
|
||||
index = afterOpenTicks;
|
||||
appendText(ticks);
|
||||
return true;
|
||||
return text(ticks);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.noties.markwon.inlineparser;
|
||||
|
||||
import org.commonmark.internal.Bracket;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
|
||||
/**
|
||||
@ -15,19 +16,20 @@ public class BangInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
int startIndex = index;
|
||||
index++;
|
||||
if (peek() == '[') {
|
||||
index++;
|
||||
|
||||
Text node = appendText("![");
|
||||
Text node = text("![");
|
||||
|
||||
// Add entry to stack for this opener
|
||||
addBracket(Bracket.image(node, startIndex + 1, lastBracket(), lastDelimiter()));
|
||||
|
||||
return node;
|
||||
} else {
|
||||
appendText("!");
|
||||
return text("!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import org.commonmark.internal.Bracket;
|
||||
import org.commonmark.internal.util.Escaping;
|
||||
import org.commonmark.node.Image;
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.node.LinkReferenceDefinition;
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@ -26,7 +27,7 @@ public class CloseBracketInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
index++;
|
||||
int startIndex = index;
|
||||
|
||||
@ -34,15 +35,13 @@ public class CloseBracketInlineProcessor extends InlineProcessor {
|
||||
Bracket opener = lastBracket();
|
||||
if (opener == null) {
|
||||
// No matching opener, just return a literal.
|
||||
appendText("]");
|
||||
return true;
|
||||
return text("]");
|
||||
}
|
||||
|
||||
if (!opener.allowed) {
|
||||
// Matching opener but it's not allowed, just return a literal.
|
||||
appendText("]");
|
||||
removeLastBracket();
|
||||
return true;
|
||||
return text("]");
|
||||
}
|
||||
|
||||
// Check to see if we have a link/image
|
||||
@ -76,7 +75,8 @@ public class CloseBracketInlineProcessor extends InlineProcessor {
|
||||
|
||||
// See if there's a link label like `[bar]` or `[]`
|
||||
int beforeLabel = index;
|
||||
int labelLength = parseLinkLabel();
|
||||
parseLinkLabel();
|
||||
int labelLength = index - beforeLabel;
|
||||
String ref = null;
|
||||
if (labelLength > 2) {
|
||||
ref = input.substring(beforeLabel, beforeLabel + labelLength);
|
||||
@ -88,10 +88,11 @@ public class CloseBracketInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
if (ref != null) {
|
||||
Link link = referenceMap().get(Escaping.normalizeReference(ref));
|
||||
if (link != null) {
|
||||
dest = link.getDestination();
|
||||
title = link.getTitle();
|
||||
String label = Escaping.normalizeReference(ref);
|
||||
LinkReferenceDefinition definition = context.getLinkReferenceDefinition(label);
|
||||
if (definition != null) {
|
||||
dest = definition.getDestination();
|
||||
title = definition.getTitle();
|
||||
isLinkOrImage = true;
|
||||
}
|
||||
}
|
||||
@ -107,7 +108,6 @@ public class CloseBracketInlineProcessor extends InlineProcessor {
|
||||
linkOrImage.appendChild(node);
|
||||
node = next;
|
||||
}
|
||||
appendNode(linkOrImage);
|
||||
|
||||
// Process delimiters such as emphasis inside link/image
|
||||
processDelimiters(opener.previousDelimiter);
|
||||
@ -128,15 +128,13 @@ public class CloseBracketInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return linkOrImage;
|
||||
|
||||
} else { // no link or image
|
||||
|
||||
appendText("]");
|
||||
index = startIndex;
|
||||
removeLastBracket();
|
||||
|
||||
index = startIndex;
|
||||
return true;
|
||||
return text("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.noties.markwon.inlineparser;
|
||||
|
||||
import org.commonmark.internal.util.Escaping;
|
||||
import org.commonmark.internal.util.Html5Entities;
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -11,8 +13,7 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class EntityInlineProcessor extends InlineProcessor {
|
||||
|
||||
private static final String ENTITY = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});";
|
||||
private static final Pattern ENTITY_HERE = Pattern.compile('^' + ENTITY, Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern ENTITY_HERE = Pattern.compile('^' + Escaping.ENTITY, Pattern.CASE_INSENSITIVE);
|
||||
|
||||
@Override
|
||||
public char specialCharacter() {
|
||||
@ -20,13 +21,12 @@ public class EntityInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
String m;
|
||||
if ((m = match(ENTITY_HERE)) != null) {
|
||||
appendText(Html5Entities.entityToString(m));
|
||||
return true;
|
||||
return text(Html5Entities.entityToString(m));
|
||||
} else {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package io.noties.markwon.inlineparser;
|
||||
|
||||
import org.commonmark.internal.util.Parsing;
|
||||
import org.commonmark.node.HtmlInline;
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -26,15 +27,14 @@ public class HtmlInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
String m = match(HTML_TAG);
|
||||
if (m != null) {
|
||||
HtmlInline node = new HtmlInline();
|
||||
node.setLiteral(m);
|
||||
appendNode(node);
|
||||
return true;
|
||||
return node;
|
||||
} else {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,8 @@ public abstract class InlineProcessor {
|
||||
/**
|
||||
* @return boolean indicating if parsing succeeded
|
||||
*/
|
||||
protected abstract boolean parse();
|
||||
@Nullable
|
||||
protected abstract Node parse();
|
||||
|
||||
|
||||
protected MarkwonInlineParserContext context;
|
||||
@ -44,13 +45,14 @@ public abstract class InlineProcessor {
|
||||
protected String input;
|
||||
protected int index;
|
||||
|
||||
public boolean parse(@NonNull MarkwonInlineParserContext context) {
|
||||
@Nullable
|
||||
public Node parse(@NonNull MarkwonInlineParserContext context) {
|
||||
this.context = context;
|
||||
this.block = context.block();
|
||||
this.input = context.input();
|
||||
this.index = context.index();
|
||||
|
||||
final boolean result = parse();
|
||||
final Node result = parse();
|
||||
|
||||
// synchronize index
|
||||
context.setIndex(index);
|
||||
@ -66,11 +68,6 @@ public abstract class InlineProcessor {
|
||||
return context.lastDelimiter();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Map<String, Link> referenceMap() {
|
||||
return context.referenceMap();
|
||||
}
|
||||
|
||||
protected void addBracket(Bracket bracket) {
|
||||
context.addBracket(bracket);
|
||||
}
|
||||
@ -127,18 +124,14 @@ public abstract class InlineProcessor {
|
||||
this.index = context.index();
|
||||
}
|
||||
|
||||
protected void appendNode(@NonNull Node node) {
|
||||
context.appendNode(node);
|
||||
@NonNull
|
||||
protected Text text(@NonNull String text) {
|
||||
return context.text(text);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Text appendText(@NonNull CharSequence text, int beginIndex, int endIndex) {
|
||||
return context.appendText(text, beginIndex, endIndex);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Text appendText(@NonNull CharSequence text) {
|
||||
return context.appendText(text);
|
||||
protected Text text(@NonNull String text, int start, int end) {
|
||||
return context.text(text, start, end);
|
||||
}
|
||||
|
||||
protected char peek() {
|
||||
|
@ -5,11 +5,11 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.commonmark.internal.Bracket;
|
||||
import org.commonmark.internal.Delimiter;
|
||||
import org.commonmark.internal.ReferenceParser;
|
||||
import org.commonmark.internal.inline.AsteriskDelimiterProcessor;
|
||||
import org.commonmark.internal.inline.UnderscoreDelimiterProcessor;
|
||||
import org.commonmark.internal.util.Escaping;
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.internal.util.LinkScanner;
|
||||
import org.commonmark.node.LinkReferenceDefinition;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.parser.InlineParser;
|
||||
@ -32,11 +32,13 @@ import static io.noties.markwon.inlineparser.InlineParserUtils.mergeTextNodesBet
|
||||
|
||||
/**
|
||||
* @see #factoryBuilder()
|
||||
* @see #factoryBuilderNoDefaults()
|
||||
* @see FactoryBuilder
|
||||
* @since 4.2.0-SNAPSHOT
|
||||
*/
|
||||
public class MarkwonInlineParser implements InlineParser, ReferenceParser, MarkwonInlineParserContext {
|
||||
public class MarkwonInlineParser implements InlineParser, MarkwonInlineParserContext {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface FactoryBuilder {
|
||||
|
||||
/**
|
||||
@ -76,52 +78,51 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
InlineParserFactory build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link FactoryBuilder} and includes all defaults.
|
||||
*
|
||||
* @see #factoryBuilderNoDefaults()
|
||||
*/
|
||||
@NonNull
|
||||
public static FactoryBuilder factoryBuilder() {
|
||||
return new FactoryBuilderImpl();
|
||||
return new FactoryBuilderImpl().includeDefaults();
|
||||
}
|
||||
|
||||
private static final String ESCAPED_CHAR = "\\\\" + Escaping.ESCAPABLE;
|
||||
/**
|
||||
* NB, this return an <em>empty</em> builder, so if no {@link FactoryBuilder#includeDefaults()}
|
||||
* is called, it means effectively <strong>no inline parsing</strong> (unless further calls
|
||||
* to {@link FactoryBuilder#addInlineProcessor(InlineProcessor)} or {@link FactoryBuilder#addDelimiterProcessor(DelimiterProcessor)}).
|
||||
*/
|
||||
@NonNull
|
||||
public static FactoryBuilder factoryBuilderNoDefaults() {
|
||||
return new FactoryBuilderImpl();
|
||||
}
|
||||
|
||||
private static final String ASCII_PUNCTUATION = "!\"#\\$%&'\\(\\)\\*\\+,\\-\\./:;<=>\\?@\\[\\\\\\]\\^_`\\{\\|\\}~";
|
||||
private static final Pattern PUNCTUATION = Pattern
|
||||
.compile("^[" + ASCII_PUNCTUATION + "\\p{Pc}\\p{Pd}\\p{Pe}\\p{Pf}\\p{Pi}\\p{Po}\\p{Ps}]");
|
||||
|
||||
private static final Pattern LINK_TITLE = Pattern.compile(
|
||||
"^(?:\"(" + ESCAPED_CHAR + "|[^\"\\x00])*\"" +
|
||||
'|' +
|
||||
"'(" + ESCAPED_CHAR + "|[^'\\x00])*'" +
|
||||
'|' +
|
||||
"\\((" + ESCAPED_CHAR + "|[^)\\x00])*\\))");
|
||||
|
||||
private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile("^(?:[<](?:[^<> \\t\\n\\\\]|\\\\.)*[>])");
|
||||
|
||||
private static final Pattern LINK_LABEL = Pattern.compile("^\\[(?:[^\\\\\\[\\]]|\\\\.)*\\]");
|
||||
|
||||
private static final Pattern SPNL = Pattern.compile("^ *(?:\n *)?");
|
||||
|
||||
private static final Pattern UNICODE_WHITESPACE_CHAR = Pattern.compile("^[\\p{Zs}\t\r\n\f]");
|
||||
|
||||
private static final Pattern LINE_END = Pattern.compile("^ *(?:\n|$)");
|
||||
|
||||
static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE);
|
||||
static final Pattern WHITESPACE = Pattern.compile("\\s+");
|
||||
|
||||
private final InlineParserContext inlineParserContext;
|
||||
|
||||
private final boolean referencesEnabled;
|
||||
|
||||
private final BitSet specialCharacters;
|
||||
private final Map<Character, List<InlineProcessor>> inlineProcessors;
|
||||
private final Map<Character, DelimiterProcessor> delimiterProcessors;
|
||||
|
||||
// currently we still hold a reference to it because we decided not to
|
||||
// pass previous node argument to inline-processors (current usage is limited with NewLineInlineProcessor)
|
||||
private Node block;
|
||||
private String input;
|
||||
private int index;
|
||||
|
||||
/**
|
||||
* Link references by ID, needs to be built up using parseReference before calling parse.
|
||||
*/
|
||||
private Map<String, Link> referenceMap = new HashMap<>(1);
|
||||
|
||||
/**
|
||||
* Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different
|
||||
* from the algorithm described in the spec.)
|
||||
@ -135,9 +136,11 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
|
||||
// might we construct these in factory?
|
||||
public MarkwonInlineParser(
|
||||
@NonNull InlineParserContext inlineParserContext,
|
||||
boolean referencesEnabled,
|
||||
@NonNull List<InlineProcessor> inlineProcessors,
|
||||
@NonNull List<DelimiterProcessor> delimiterProcessors) {
|
||||
this.inlineParserContext = inlineParserContext;
|
||||
this.referencesEnabled = referencesEnabled;
|
||||
this.inlineProcessors = calculateInlines(inlineProcessors);
|
||||
this.delimiterProcessors = calculateDelimiterProcessors(delimiterProcessors);
|
||||
@ -218,117 +221,29 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
*/
|
||||
@Override
|
||||
public void parse(String content, Node block) {
|
||||
this.block = block;
|
||||
this.input = content.trim();
|
||||
this.index = 0;
|
||||
this.lastDelimiter = null;
|
||||
this.lastBracket = null;
|
||||
reset(content.trim());
|
||||
|
||||
boolean moreToParse;
|
||||
do {
|
||||
moreToParse = parseInline();
|
||||
} while (moreToParse);
|
||||
// we still reference it
|
||||
this.block = block;
|
||||
|
||||
while (true) {
|
||||
Node node = parseInline();
|
||||
if (node != null) {
|
||||
block.appendChild(node);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processDelimiters(null);
|
||||
mergeChildTextNodes(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a link reference, modifying the internal reference map.
|
||||
*/
|
||||
@Override
|
||||
public int parseReference(String s) {
|
||||
|
||||
if (!referencesEnabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
this.input = s;
|
||||
private void reset(String content) {
|
||||
this.input = content;
|
||||
this.index = 0;
|
||||
String dest;
|
||||
String title;
|
||||
int matchChars;
|
||||
int startIndex = index;
|
||||
|
||||
// label:
|
||||
matchChars = parseLinkLabel();
|
||||
if (matchChars == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String rawLabel = input.substring(0, matchChars);
|
||||
|
||||
// colon:
|
||||
if (peek() != ':') {
|
||||
return 0;
|
||||
}
|
||||
index++;
|
||||
|
||||
// link url
|
||||
spnl();
|
||||
|
||||
dest = parseLinkDestination();
|
||||
if (dest == null || dest.length() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int beforeTitle = index;
|
||||
spnl();
|
||||
title = parseLinkTitle();
|
||||
if (title == null) {
|
||||
// rewind before spaces
|
||||
index = beforeTitle;
|
||||
}
|
||||
|
||||
boolean atLineEnd = true;
|
||||
if (index != input.length() && match(LINE_END) == null) {
|
||||
if (title == null) {
|
||||
atLineEnd = false;
|
||||
} else {
|
||||
// the potential title we found is not at the line end,
|
||||
// but it could still be a legal link reference if we
|
||||
// discard the title
|
||||
title = null;
|
||||
// rewind before spaces
|
||||
index = beforeTitle;
|
||||
// and instead check if the link URL is at the line end
|
||||
atLineEnd = match(LINE_END) != null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!atLineEnd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String normalizedLabel = Escaping.normalizeReference(rawLabel);
|
||||
if (normalizedLabel.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!referenceMap.containsKey(normalizedLabel)) {
|
||||
Link link = new Link(dest, title);
|
||||
referenceMap.put(normalizedLabel, link);
|
||||
}
|
||||
return index - startIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Text appendText(@NonNull CharSequence text, int beginIndex, int endIndex) {
|
||||
return appendText(text.subSequence(beginIndex, endIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Text appendText(@NonNull CharSequence text) {
|
||||
Text node = new Text(text.toString());
|
||||
appendNode(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendNode(@NonNull Node node) {
|
||||
block.appendChild(node);
|
||||
this.lastDelimiter = null;
|
||||
this.lastBracket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -336,43 +251,44 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
* On success, add the result to block's children and return true.
|
||||
* On failure, return false.
|
||||
*/
|
||||
private boolean parseInline() {
|
||||
@Nullable
|
||||
private Node parseInline() {
|
||||
|
||||
final char c = peek();
|
||||
|
||||
if (c == '\0') {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean res = false;
|
||||
Node node = null;
|
||||
|
||||
final List<InlineProcessor> inlines = this.inlineProcessors.get(c);
|
||||
|
||||
if (inlines != null) {
|
||||
for (InlineProcessor inline : inlines) {
|
||||
res = inline.parse(this);
|
||||
if (res) {
|
||||
node = inline.parse(this);
|
||||
if (node != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c);
|
||||
if (delimiterProcessor != null) {
|
||||
res = parseDelimiters(delimiterProcessor, c);
|
||||
node = parseDelimiters(delimiterProcessor, c);
|
||||
} else {
|
||||
res = parseString();
|
||||
node = parseString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
if (node != null) {
|
||||
return node;
|
||||
} else {
|
||||
index++;
|
||||
// When we get here, it's only for a single special character that turned out to not have a special meaning.
|
||||
// So we shouldn't have a single surrogate here, hence it should be ok to turn it into a String.
|
||||
String literal = String.valueOf(c);
|
||||
appendText(literal);
|
||||
return text(literal);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -395,6 +311,26 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Text text(@NonNull String text) {
|
||||
return new Text(text);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Text text(@NonNull String text, int beginIndex, int endIndex) {
|
||||
return new Text(text.substring(beginIndex, endIndex));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public LinkReferenceDefinition getLinkReferenceDefinition(String label) {
|
||||
return referencesEnabled
|
||||
? inlineParserContext.getLinkReferenceDefinition(label)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the char at the current input index, or {@code '\0'} in case there are no more characters.
|
||||
*/
|
||||
@ -439,12 +375,6 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
return lastDelimiter;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Map<String, Link> referenceMap() {
|
||||
return referenceMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBracket(Bracket bracket) {
|
||||
if (lastBracket != null) {
|
||||
@ -462,24 +392,24 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
* Parse zero or more space characters, including at most one newline.
|
||||
*/
|
||||
@Override
|
||||
public boolean spnl() {
|
||||
public void spnl() {
|
||||
match(SPNL);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters.
|
||||
*/
|
||||
private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) {
|
||||
@Nullable
|
||||
private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) {
|
||||
DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar);
|
||||
if (res == null) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
int length = res.count;
|
||||
int startIndex = index;
|
||||
|
||||
index += length;
|
||||
Text node = appendText(input, startIndex, index);
|
||||
Text node = text(input, startIndex, index);
|
||||
|
||||
// Add entry to stack for this opener
|
||||
lastDelimiter = new Delimiter(node, delimiterChar, res.canOpen, res.canClose, lastDelimiter);
|
||||
@ -489,7 +419,7 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
lastDelimiter.previous.next = lastDelimiter;
|
||||
}
|
||||
|
||||
return true;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -498,57 +428,21 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
@Override
|
||||
@Nullable
|
||||
public String parseLinkDestination() {
|
||||
String res = match(LINK_DESTINATION_BRACES);
|
||||
if (res != null) { // chop off surrounding <..>:
|
||||
if (res.length() == 2) {
|
||||
return "";
|
||||
} else {
|
||||
return Escaping.unescapeString(res.substring(1, res.length() - 1));
|
||||
}
|
||||
} else {
|
||||
int startIndex = index;
|
||||
parseLinkDestinationWithBalancedParens();
|
||||
return Escaping.unescapeString(input.substring(startIndex, index));
|
||||
int afterDest = LinkScanner.scanLinkDestination(input, index);
|
||||
if (afterDest == -1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseLinkDestinationWithBalancedParens() {
|
||||
int parens = 0;
|
||||
while (true) {
|
||||
char c = peek();
|
||||
switch (c) {
|
||||
case '\0':
|
||||
return;
|
||||
case '\\':
|
||||
// check if we have an escapable character
|
||||
if (index + 1 < input.length() && ESCAPABLE.matcher(input.substring(index + 1, index + 2)).matches()) {
|
||||
// skip over the escaped character (after switch)
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
// otherwise, we treat this as a literal backslash
|
||||
break;
|
||||
case '(':
|
||||
parens++;
|
||||
break;
|
||||
case ')':
|
||||
if (parens == 0) {
|
||||
return;
|
||||
} else {
|
||||
parens--;
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
// ASCII space
|
||||
return;
|
||||
default:
|
||||
// or control character
|
||||
if (Character.isISOControl(c)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
index++;
|
||||
String dest;
|
||||
if (peek() == '<') {
|
||||
// chop off surrounding <..>:
|
||||
dest = input.substring(index + 1, afterDest - 1);
|
||||
} else {
|
||||
dest = input.substring(index, afterDest);
|
||||
}
|
||||
|
||||
index = afterDest;
|
||||
return Escaping.unescapeString(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -557,13 +451,15 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
@Override
|
||||
@Nullable
|
||||
public String parseLinkTitle() {
|
||||
String title = match(LINK_TITLE);
|
||||
if (title != null) {
|
||||
// chop off quotes from title and unescape:
|
||||
return Escaping.unescapeString(title.substring(1, title.length() - 1));
|
||||
} else {
|
||||
int afterTitle = LinkScanner.scanLinkTitle(input, index);
|
||||
if (afterTitle == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// chop off ', " or parens
|
||||
String title = input.substring(index + 1, afterTitle - 1);
|
||||
index = afterTitle;
|
||||
return Escaping.unescapeString(title);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -571,19 +467,28 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
*/
|
||||
@Override
|
||||
public int parseLinkLabel() {
|
||||
String m = match(LINK_LABEL);
|
||||
// Spec says "A link label can have at most 999 characters inside the square brackets"
|
||||
if (m == null || m.length() > 1001) {
|
||||
if (index >= input.length() || input.charAt(index) != '[') {
|
||||
return 0;
|
||||
} else {
|
||||
return m.length();
|
||||
}
|
||||
|
||||
int startContent = index + 1;
|
||||
int endContent = LinkScanner.scanLinkLabelContent(input, startContent);
|
||||
// spec: A link label can have at most 999 characters inside the square brackets.
|
||||
int contentLength = endContent - startContent;
|
||||
if (endContent == -1 || contentLength > 999) {
|
||||
return 0;
|
||||
}
|
||||
if (endContent >= input.length() || input.charAt(endContent) != ']') {
|
||||
return 0;
|
||||
}
|
||||
index = endContent + 1;
|
||||
return contentLength + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string.
|
||||
*/
|
||||
private boolean parseString() {
|
||||
private Node parseString() {
|
||||
int begin = index;
|
||||
int length = input.length();
|
||||
while (index != length) {
|
||||
@ -593,10 +498,9 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
index++;
|
||||
}
|
||||
if (begin != index) {
|
||||
appendText(input, begin, index);
|
||||
return true;
|
||||
return text(input, begin, index);
|
||||
} else {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -909,7 +813,11 @@ public class MarkwonInlineParser implements InlineParser, ReferenceParser, Markw
|
||||
} else {
|
||||
delimiterProcessors = this.delimiterProcessors;
|
||||
}
|
||||
return new MarkwonInlineParser(referencesEnabled, inlineProcessors, delimiterProcessors);
|
||||
return new MarkwonInlineParser(
|
||||
inlineParserContext,
|
||||
referencesEnabled,
|
||||
inlineProcessors,
|
||||
delimiterProcessors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import androidx.annotation.Nullable;
|
||||
import org.commonmark.internal.Bracket;
|
||||
import org.commonmark.internal.Delimiter;
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.node.LinkReferenceDefinition;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
|
||||
@ -28,14 +29,11 @@ public interface MarkwonInlineParserContext {
|
||||
|
||||
Delimiter lastDelimiter();
|
||||
|
||||
@NonNull
|
||||
Map<String, Link> referenceMap();
|
||||
|
||||
void addBracket(Bracket bracket);
|
||||
|
||||
void removeLastBracket();
|
||||
|
||||
boolean spnl();
|
||||
void spnl();
|
||||
|
||||
/**
|
||||
* Returns the char at the current input index, or {@code '\0'} in case there are no more characters.
|
||||
@ -45,13 +43,14 @@ public interface MarkwonInlineParserContext {
|
||||
@Nullable
|
||||
String match(@NonNull Pattern re);
|
||||
|
||||
void appendNode(@NonNull Node node);
|
||||
@NonNull
|
||||
Text text(@NonNull String text);
|
||||
|
||||
@NonNull
|
||||
Text appendText(@NonNull CharSequence text, int beginIndex, int endIndex);
|
||||
Text text(@NonNull String text, int beginIndex, int endIndex);
|
||||
|
||||
@NonNull
|
||||
Text appendText(@NonNull CharSequence text);
|
||||
@Nullable
|
||||
LinkReferenceDefinition getLinkReferenceDefinition(String label);
|
||||
|
||||
@Nullable
|
||||
String parseLinkDestination();
|
||||
|
@ -21,29 +21,28 @@ public class NewLineInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
index++; // assume we're at a \n
|
||||
|
||||
Node lastChild = block.getLastChild();
|
||||
final Node previous = block.getLastChild();
|
||||
|
||||
// Check previous text for trailing spaces.
|
||||
// The "endsWith" is an optimization to avoid an RE match in the common case.
|
||||
if (lastChild != null && lastChild instanceof Text && ((Text) lastChild).getLiteral().endsWith(" ")) {
|
||||
Text text = (Text) lastChild;
|
||||
if (previous instanceof Text && ((Text) previous).getLiteral().endsWith(" ")) {
|
||||
Text text = (Text) previous;
|
||||
String literal = text.getLiteral();
|
||||
Matcher matcher = FINAL_SPACE.matcher(literal);
|
||||
int spaces = matcher.find() ? matcher.end() - matcher.start() : 0;
|
||||
if (spaces > 0) {
|
||||
text.setLiteral(literal.substring(0, literal.length() - spaces));
|
||||
}
|
||||
appendNode(spaces >= 2 ? new HardLineBreak() : new SoftLineBreak());
|
||||
if (spaces >= 2) {
|
||||
return new HardLineBreak();
|
||||
} else {
|
||||
return new SoftLineBreak();
|
||||
}
|
||||
} else {
|
||||
appendNode(new SoftLineBreak());
|
||||
return new SoftLineBreak();
|
||||
}
|
||||
|
||||
// gobble leading spaces in next line
|
||||
while (peek() == ' ') {
|
||||
index++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.noties.markwon.inlineparser;
|
||||
|
||||
import org.commonmark.internal.Bracket;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
|
||||
/**
|
||||
@ -15,15 +16,15 @@ public class OpenBracketInlineProcessor extends InlineProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
int startIndex = index;
|
||||
index++;
|
||||
|
||||
Text node = appendText("[");
|
||||
Text node = text("[");
|
||||
|
||||
// Add entry to stack for this opener
|
||||
addBracket(Bracket.link(node, startIndex, lastBracket(), lastDelimiter()));
|
||||
|
||||
return true;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import org.commonmark.testutil.example.Example;
|
||||
public class InlineParserSpecTest extends SpecTestCase {
|
||||
|
||||
private static final Parser PARSER = Parser.builder()
|
||||
.inlineParserFactory(MarkwonInlineParser.factoryBuilder().includeDefaults().build())
|
||||
.inlineParserFactory(MarkwonInlineParser.factoryBuilder().build())
|
||||
.build();
|
||||
|
||||
// The spec says URL-escaping is optional, but the examples assume that it's enabled.
|
||||
|
@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.commonmark.node.Link;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.InlineParserFactory;
|
||||
import org.commonmark.parser.Parser;
|
||||
|
||||
@ -71,7 +71,8 @@ public class CustomExtensionActivity2 extends Activity {
|
||||
|
||||
final InlineParserFactory inlineParserFactory = MarkwonInlineParser.factoryBuilder()
|
||||
// include all current defaults (otherwise will be empty - contain only our inline-processors)
|
||||
.includeDefaults()
|
||||
// included by default, to create factory-builder without defaults call `factoryBuilderNoDefaults`
|
||||
// .includeDefaults()
|
||||
.addInlineProcessor(new IssueInlineProcessor())
|
||||
.addInlineProcessor(new UserInlineProcessor())
|
||||
.build();
|
||||
@ -98,15 +99,14 @@ public class CustomExtensionActivity2 extends Activity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
final String id = match(RE);
|
||||
if (id != null) {
|
||||
final Link link = new Link(createIssueOrPullRequestLinkDestination(id), null);
|
||||
link.appendChild(new Text("#" + id));
|
||||
appendNode(link);
|
||||
return true;
|
||||
link.appendChild(text("#" + id));
|
||||
return link;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -125,15 +125,14 @@ public class CustomExtensionActivity2 extends Activity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean parse() {
|
||||
protected Node parse() {
|
||||
final String user = match(RE);
|
||||
if (user != null) {
|
||||
final Link link = new Link(createUserLinkDestination(user), null);
|
||||
link.appendChild(new Text("@" + user));
|
||||
appendNode(link);
|
||||
return true;
|
||||
link.appendChild(text("@" + user));
|
||||
return link;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -54,7 +54,7 @@ public class InlineParserActivity extends Activity {
|
||||
// * no HTML entities (&)
|
||||
// * no HTML tags
|
||||
// markdown blocks are still parsed
|
||||
final InlineParserFactory inlineParserFactory = MarkwonInlineParser.factoryBuilder()
|
||||
final InlineParserFactory inlineParserFactory = MarkwonInlineParser.factoryBuilderNoDefaults()
|
||||
.referencesEnabled(true)
|
||||
.addInlineProcessor(new OpenBracketInlineProcessor())
|
||||
.addInlineProcessor(new CloseBracketInlineProcessor())
|
||||
@ -78,7 +78,6 @@ public class InlineParserActivity extends Activity {
|
||||
// parses all as usual, but ignores code (inline and block)
|
||||
|
||||
final InlineParserFactory inlineParserFactory = MarkwonInlineParser.factoryBuilder()
|
||||
.includeDefaults()
|
||||
.excludeInlineProcessor(BackticksInlineProcessor.class)
|
||||
.build();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user