Update to commonmark-java 0.13.0

This commit is contained in:
Dimitry Ivanov 2019-11-14 17:38:24 +03:00
parent d2e4730179
commit 6c4ffd1778
18 changed files with 239 additions and 322 deletions

View File

@ -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 = [

View File

@ -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);

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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("]");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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() {

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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

View File

@ -54,7 +54,7 @@ public class InlineParserActivity extends Activity {
// * no HTML entities (&amp;)
// * 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();