diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/ParseBenchmark.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/ParseBenchmark.java
new file mode 100644
index 000000000..bac622f0f
--- /dev/null
+++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/ParseBenchmark.java
@@ -0,0 +1,96 @@
+package org.commonmark.integration;
+
+import org.commonmark.node.Image;
+import org.commonmark.node.Node;
+import org.commonmark.parser.InlineParser;
+import org.commonmark.parser.Parser;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@BenchmarkMode(Mode.SingleShotTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@Measurement(batchSize = 10000, iterations = 5)
+@Warmup(batchSize = 10000, iterations = 5)
+public class ParseBenchmark {
+ private static final Parser regularParser = Parser.builder().build();
+
+ private static final InlineParser.NodeExtension nodeExtensionWithRegexFirst = new InlineParser.NodeExtension() {
+ private final Pattern pattern = Pattern.compile("~(?
[a-zA-Z]+)~(?[\\/a-zA-Z.]+)");
+
+ @Override
+ public List lookup(String inline) {
+ List nodesBreakDown = new ArrayList<>();
+
+ Matcher matcher = pattern.matcher(inline);
+ while (matcher.find()) {
+ nodesBreakDown.add(InlineBreakdown.of(
+ new Image(matcher.group("destination"), matcher.group("title")),
+ matcher.start(),
+ matcher.end()));
+ }
+ return nodesBreakDown;
+ }
+ };
+ private static final InlineParser.NodeExtension nodeExtensionWithRegexSecond = new InlineParser.NodeExtension() {
+ private final Pattern pattern = Pattern.compile("(?[\\/a-zA-Z.]+)\\|(?[a-zA-Z]+)");
+
+ @Override
+ public List lookup(String inline) {
+ List nodesBreakDown = new ArrayList<>();
+
+ Matcher matcher = pattern.matcher(inline);
+ while (matcher.find()) {
+ nodesBreakDown.add(InlineBreakdown.of(
+ new Image(matcher.group("destination"), matcher.group("title")),
+ matcher.start(),
+ matcher.end()));
+ }
+ return nodesBreakDown;
+ }
+ };
+ private static final Parser parserOneExtension = Parser.builder()
+ .nodeExtension(nodeExtensionWithRegexFirst)
+ .build();
+
+ private static final Parser parserTwoExtensions = Parser.builder()
+ .nodeExtension(nodeExtensionWithRegexFirst)
+ .nodeExtension(nodeExtensionWithRegexSecond)
+ .build();
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(".*" + ParseBenchmark.class.getSimpleName())
+ .build();
+
+ new Runner(opt).run();
+ }
+
+ @Benchmark
+ public Node parseImageRegularUsage() {
+ return regularParser.parse("Some text  another point ");
+ }
+
+ @Benchmark
+ public Node parseImageByNodeExtension() {
+ return parserOneExtension.parse("Some text ~image~/url.png another point ~image~/url.png");
+ }
+
+ @Benchmark
+ public Node parseImageByTwoNodeExtension() {
+ return parserTwoExtensions.parse("Some text /url.png|something another point ~image~/url.png");
+ }
+}
diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java
index d1b43a7b4..bc208b214 100644
--- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java
+++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java
@@ -63,6 +63,7 @@ public class DocumentParser implements ParserState {
private final List blockParserFactories;
private final InlineParserFactory inlineParserFactory;
private final List delimiterProcessors;
+ private final List nodeExtensions;
private final DocumentBlockParser documentBlockParser;
private final Map definitions = new LinkedHashMap<>();
@@ -71,10 +72,12 @@ public class DocumentParser implements ParserState {
private Set allBlockParsers = new LinkedHashSet<>();
public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory,
- List delimiterProcessors) {
+ List delimiterProcessors,
+ List nodeExtensions) {
this.blockParserFactories = blockParserFactories;
this.inlineParserFactory = inlineParserFactory;
this.delimiterProcessors = delimiterProcessors;
+ this.nodeExtensions = nodeExtensions;
this.documentBlockParser = new DocumentBlockParser();
activateBlockParser(this.documentBlockParser);
@@ -415,7 +418,7 @@ private void addDefinitionsFrom(ParagraphParser paragraphParser) {
* Walk through a block & children recursively, parsing string content into inline content where appropriate.
*/
private void processInlines() {
- InlineParserContextImpl context = new InlineParserContextImpl(delimiterProcessors, definitions);
+ InlineParserContextImpl context = new InlineParserContextImpl(delimiterProcessors, definitions, nodeExtensions);
InlineParser inlineParser = inlineParserFactory.create(context);
for (BlockParser blockParser : allBlockParsers) {
diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java
index bff085ad8..ba84bbb59 100644
--- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java
+++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java
@@ -1,6 +1,7 @@
package org.commonmark.internal;
import org.commonmark.node.LinkReferenceDefinition;
+import org.commonmark.parser.InlineParser;
import org.commonmark.parser.InlineParserContext;
import org.commonmark.parser.delimiter.DelimiterProcessor;
@@ -11,11 +12,14 @@ public class InlineParserContextImpl implements InlineParserContext {
private final List delimiterProcessors;
private final Map linkReferenceDefinitions;
+ private final List nodeExtensions;
public InlineParserContextImpl(List delimiterProcessors,
- Map linkReferenceDefinitions) {
+ Map linkReferenceDefinitions,
+ List nodeExtensions) {
this.delimiterProcessors = delimiterProcessors;
this.linkReferenceDefinitions = linkReferenceDefinitions;
+ this.nodeExtensions = nodeExtensions;
}
@Override
@@ -27,4 +31,9 @@ public List getCustomDelimiterProcessors() {
public LinkReferenceDefinition getLinkReferenceDefinition(String label) {
return linkReferenceDefinitions.get(label);
}
+
+ @Override
+ public List nodeExtensions() {
+ return nodeExtensions;
+ }
}
diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java
index dfb50149a..68f5e775a 100644
--- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java
+++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java
@@ -6,12 +6,29 @@
import org.commonmark.internal.util.Html5Entities;
import org.commonmark.internal.util.LinkScanner;
import org.commonmark.internal.util.Parsing;
-import org.commonmark.node.*;
+import org.commonmark.node.Code;
+import org.commonmark.node.HardLineBreak;
+import org.commonmark.node.HtmlInline;
+import org.commonmark.node.Image;
+import org.commonmark.node.Link;
+import org.commonmark.node.LinkReferenceDefinition;
+import org.commonmark.node.Node;
+import org.commonmark.node.SoftLineBreak;
+import org.commonmark.node.Text;
import org.commonmark.parser.InlineParser;
import org.commonmark.parser.InlineParserContext;
import org.commonmark.parser.delimiter.DelimiterProcessor;
-import java.util.*;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -56,6 +73,7 @@ public class InlineParserImpl implements InlineParser {
private final BitSet delimiterCharacters;
private final Map delimiterProcessors;
private final InlineParserContext context;
+ private final List nodeExtensions;
private String input;
private int index;
@@ -77,6 +95,7 @@ public InlineParserImpl(InlineParserContext inlineParserContext) {
this.specialCharacters = calculateSpecialCharacters(delimiterCharacters);
this.context = inlineParserContext;
+ this.nodeExtensions = inlineParserContext.nodeExtensions();
}
public static BitSet calculateDelimiterCharacters(Set characters) {
@@ -148,9 +167,11 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr
public void parse(String content, Node block) {
reset(content.trim());
+ ArrayDeque customNodes = new ArrayDeque<>(customNodesByExtensions());
+
Node previous = null;
while (true) {
- Node node = parseInline(previous);
+ Node node = parseInline(previous, customNodes);
previous = node;
if (node != null) {
block.appendChild(node);
@@ -184,7 +205,22 @@ private Text text(String text) {
* On success, return the new inline node.
* On failure, return null.
*/
- private Node parseInline(Node previous) {
+ private Node parseInline(Node previous, ArrayDeque customNodes) {
+ NodeExtension.InlineBreakdown inlineBreakdown = customNodes.isEmpty()
+ ? NodeExtension.InlineBreakdown.EMPTY
+ : customNodes.getFirst();
+ int nextBeginIndexForCustomNode = inlineBreakdown.getBeginIndex();
+
+ if (inlineBreakdown.getBeginIndex() == index) {
+ while (index < inlineBreakdown.getEndIndex()) {
+ index++;
+ }
+ return customNodes.pollFirst().getNode();
+ } else if (inlineBreakdown.getBeginIndex() < index) {
+ customNodes.pollFirst();
+ nextBeginIndexForCustomNode = input.length();
+ }
+
char c = peek();
if (c == '\0') {
return null;
@@ -225,7 +261,7 @@ private Node parseInline(Node previous) {
DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c);
node = parseDelimiters(delimiterProcessor, c);
} else {
- node = parseString();
+ node = parseString(Math.min(nextBeginIndexForCustomNode, input.length()));
}
break;
}
@@ -384,6 +420,25 @@ private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimit
return node;
}
+ private List customNodesByExtensions() {
+ List nodes = new ArrayList<>();
+
+ for (NodeExtension nodeExtension : nodeExtensions) {
+ List inlineBreakdowns = nodeExtension.lookup(input);
+ if (inlineBreakdowns != null) {
+ nodes.addAll(inlineBreakdowns);
+ }
+ }
+
+ Collections.sort(nodes, new Comparator() {
+ @Override
+ public int compare(NodeExtension.InlineBreakdown node1, NodeExtension.InlineBreakdown node2) {
+ return Integer.compare(node1.getBeginIndex(), node2.getBeginIndex());
+ }
+ });
+ return nodes;
+ }
+
/**
* Add open bracket to delimiter stack and add a text node to block's children.
*/
@@ -652,10 +707,10 @@ private Node parseEntity() {
/**
* Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string.
+ * @param length
*/
- private Node parseString() {
+ private Node parseString(int length) {
int begin = index;
- int length = input.length();
while (index != length) {
if (specialCharacters.get(input.charAt(index))) {
break;
diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java
index 492c3cc8a..d9980e0a8 100644
--- a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java
+++ b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java
@@ -2,6 +2,8 @@
import org.commonmark.node.Node;
+import java.util.List;
+
/**
* Parser for inline content (text, links, emphasized text, etc).
*/
@@ -12,4 +14,65 @@ public interface InlineParser {
* @param node the node to append resulting nodes to (as children)
*/
void parse(String input, Node node);
+
+ /**
+ * Extension for inline content
+ */
+ interface NodeExtension {
+ /**
+ * The listing contains the all nodes that should be applied to this current line with the respective line positions
+ *
+ * @param input is the content to be parsed as custom nodes
+ * @return list of nodes with their location in the current line
+ */
+ List lookup(String input);
+
+ class InlineBreakdown {
+ public static final InlineBreakdown EMPTY =
+ new InlineBreakdown(null, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ private final Node node;
+ private final int beginIndex;
+ private final int endIndex;
+
+ private InlineBreakdown(Node node, int beginIndex, int endIndex) {
+ this.node = node;
+ this.beginIndex = beginIndex;
+ this.endIndex = endIndex;
+ }
+
+ public Node getNode() {
+ return this.node;
+ }
+
+ public int getBeginIndex() {
+ return beginIndex;
+ }
+
+ public int getEndIndex() {
+ return endIndex;
+ }
+
+
+ /**
+ * Inline Object to provide the {@code node} and node's line position
+ * @param node such as the {@code Image}, {@code Link}, etc.
+ * @param beginIndex as String.subString usage, the beginning index, inclusive.
+ * @param endIndex as String.subString usage, the ending index, exclusive.
+ * @exception IllegalArgumentException if the {@code endIndex} < {@code beginIndex}
+ * @return {@code InlineBreakdown} built,
+ * if {@code beginIndex} == {@code endIndex} it will considered empty object
+ */
+ public static InlineBreakdown of(Node node, int beginIndex, int endIndex) {
+ if (endIndex < beginIndex){
+ throw new IllegalArgumentException("beginIndex "+ beginIndex +" must be greater then endIndex " + endIndex);
+ }
+
+ if (beginIndex == endIndex) {
+ return EMPTY;
+ }
+
+ return new InlineBreakdown(node, beginIndex, endIndex);
+ }
+ }
+ }
}
diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java
index 467742e2c..c97c48aa8 100644
--- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java
+++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java
@@ -22,4 +22,6 @@ public interface InlineParserContext {
* @return the definition if one exists, {@code null} otherwise
*/
LinkReferenceDefinition getLinkReferenceDefinition(String label);
+
+ List nodeExtensions();
}
diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java
index 5e15158ad..ba1351210 100644
--- a/commonmark/src/main/java/org/commonmark/parser/Parser.java
+++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java
@@ -31,17 +31,20 @@ public class Parser {
private final List delimiterProcessors;
private final InlineParserFactory inlineParserFactory;
private final List postProcessors;
+ private final List nodeExtensions;
private Parser(Builder builder) {
this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes);
this.inlineParserFactory = builder.getInlineParserFactory();
this.postProcessors = builder.postProcessors;
this.delimiterProcessors = builder.delimiterProcessors;
+ this.nodeExtensions = builder.nodeExtensions;
// Try to construct an inline parser. Invalid configuration might result in an exception, which we want to
// detect as soon as possible.
this.inlineParserFactory.create(new InlineParserContextImpl(delimiterProcessors,
- Collections.emptyMap()));
+ Collections.emptyMap(),
+ nodeExtensions));
}
/**
@@ -99,7 +102,7 @@ public Node parseReader(Reader input) throws IOException {
}
private DocumentParser createDocumentParser() {
- return new DocumentParser(blockParserFactories, inlineParserFactory, delimiterProcessors);
+ return new DocumentParser(blockParserFactories, inlineParserFactory, delimiterProcessors, nodeExtensions);
}
private Node postProcess(Node document) {
@@ -115,6 +118,7 @@ private Node postProcess(Node document) {
public static class Builder {
private final List blockParserFactories = new ArrayList<>();
private final List delimiterProcessors = new ArrayList<>();
+ private final List nodeExtensions = new ArrayList<>();
private final List postProcessors = new ArrayList<>();
private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes();
private InlineParserFactory inlineParserFactory;
@@ -257,6 +261,11 @@ public InlineParser create(InlineParserContext inlineParserContext) {
}
};
}
+
+ public Builder nodeExtension(InlineParser.NodeExtension nodeExtension) {
+ this.nodeExtensions.add(nodeExtension);
+ return this;
+ }
}
/**
diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java
index e058de378..b0e752f32 100644
--- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java
+++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java
@@ -1,11 +1,24 @@
package org.commonmark.test;
-import org.commonmark.node.*;
+import org.commonmark.node.Block;
+import org.commonmark.node.BulletList;
+import org.commonmark.node.CustomBlock;
+import org.commonmark.node.Heading;
+import org.commonmark.node.Image;
+import org.commonmark.node.Node;
+import org.commonmark.node.Paragraph;
+import org.commonmark.node.Text;
+import org.commonmark.node.ThematicBreak;
import org.commonmark.parser.InlineParser;
import org.commonmark.parser.InlineParserContext;
import org.commonmark.parser.InlineParserFactory;
import org.commonmark.parser.Parser;
-import org.commonmark.parser.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 org.commonmark.renderer.html.HtmlRenderer;
import org.commonmark.testutil.TestResources;
import org.junit.Test;
@@ -13,7 +26,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -23,7 +36,13 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import static org.hamcrest.CoreMatchers.*;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -35,7 +54,7 @@ public void ioReaderTest() throws IOException {
InputStream input1 = TestResources.getSpec().openStream();
Node document1;
- try (InputStreamReader reader = new InputStreamReader(input1, Charset.forName("UTF-8"))) {
+ try (InputStreamReader reader = new InputStreamReader(input1, StandardCharsets.UTF_8)) {
document1 = parser.parseReader(reader);
}
@@ -122,9 +141,298 @@ public InlineParser create(InlineParserContext inlineParserContext) {
assertThat(parser.parse(input).getFirstChild().getFirstChild(), instanceOf(ThematicBreak.class));
}
+ @Test
+ public void convertLineToCustomNodeByNodeExtensionHandler() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image(), 0, 8));
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("some text");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Image.class));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionLeftDefaultTextIfEmptyListFromExtension() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return emptyList();
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("some text");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild()), equalTo("some text"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionLeftDefaultTextIfNulListFromExtension() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return null;
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("some text");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild()), equalTo("some text"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionWithCustomNodeInTheLineBeginOneSingleCharacter() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image(), 0, 1));
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("C");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Image.class));
+ assertThat(document.getFirstChild().getLastChild(), instanceOf(Image.class));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionWithCustomNodeInTheLineBegin() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image(), 0, 18));
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("CONVERTED_TO_IMAGE some");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Image.class));
+ assertThat(document.getFirstChild().getLastChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getLastChild()), equalTo(" some"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionWithCustomNodeInTheLineEnd() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image(), 5, 23));
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("some CONVERTED_TO_IMAGE");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild()), equalTo("some "));
+ assertThat(document.getFirstChild().getFirstChild().getNext(), instanceOf(Image.class));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionWithCustomNodeInTheMiddle() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image(), 5, 23));
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("some CONVERTED_TO_IMAGE second");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild()), equalTo("some "));
+ assertThat(document.getFirstChild().getFirstChild().getNext(), instanceOf(Image.class));
+ assertThat(document.getFirstChild().getLastChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getLastChild()), equalTo(" second"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionWithMoreThenOneOccurrence() {
+ final Node image1 = new Image();
+ final Node image2 = new Image();
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ List inlinesBreakdown = new ArrayList<>();
+ inlinesBreakdown.add(InlineBreakdown.of(image1, 5, 25));
+ inlinesBreakdown.add(InlineBreakdown.of(image2, 33, 53));
+ return inlinesBreakdown;
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("some CONVERTED_TO_IMAGE_1 second CONVERTED_TO_IMAGE_2 third");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild()), equalTo("some "));
+ assertThat(document.getFirstChild().getFirstChild().getNext(), equalTo(image1));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext().getNext()),
+ equalTo(" second "));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext().getNext(), equalTo(image2));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext().getNext().getNext(),
+ instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext().getNext().getNext().getNext()),
+ equalTo(" third"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionWithMoreThenOneOccurrenceSortingByIndex() {
+ final Node image1 = new Image();
+ final Node image2 = new Image();
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ List inlinesBreakdown = new ArrayList<>();
+ inlinesBreakdown.add(InlineBreakdown.of(image2, 33, 53));
+ inlinesBreakdown.add(InlineBreakdown.of(image1, 5, 25));
+ return inlinesBreakdown;
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("some CONVERTED_TO_IMAGE_1 second CONVERTED_TO_IMAGE_2 third");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild()), equalTo("some "));
+ assertThat(document.getFirstChild().getFirstChild().getNext(), equalTo(image1));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext().getNext()),
+ equalTo(" second "));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext().getNext(), equalTo(image2));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext().getNext().getNext(),
+ instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext().getNext().getNext().getNext()),
+ equalTo(" third"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionShouldIgnoreIfBeginIndexEqualEndIndex() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image(), 5, 5));
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("some second");
+ assertThat(getLiteral(document.getFirstChild().getFirstChild()), equalTo("some second"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void convertLineByNodeExtensionShouldRaiseExceptionIfBeginIndexLessThenEndIndex() {
+ InlineParser.NodeExtension.InlineBreakdown.of(new Image(), 4, 3);
+ }
+
+ @Test
+ public void convertLineByNodeExtensionShouldIgnoreTheNextComponentIfStartingBeforeThePreviousEndIndex() {
+ final Node image1 = new Image();
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ List inlinesBreakdown = new ArrayList<>();
+ inlinesBreakdown.add(InlineBreakdown.of(image1, 0, 3));
+ inlinesBreakdown.add(InlineBreakdown.of(new Image(), 2, 5));
+ return inlinesBreakdown;
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("foo jack some");
+ assertThat(document.getFirstChild().getFirstChild(), equalTo(image1));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext()),
+ equalTo(" jack some"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionShouldIgnoreTheNextComponentIfStartingEqualTheNextOneInTheList() {
+ final Node image1 = new Image();
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ List inlinesBreakdown = new ArrayList<>();
+ inlinesBreakdown.add(InlineBreakdown.of(image1, 0, 3));
+ inlinesBreakdown.add(InlineBreakdown.of(new Image(), 0, 4));
+ return inlinesBreakdown;
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+
+ Node document = parser.parse("foo jack some");
+ assertThat(document.getFirstChild().getFirstChild(), equalTo(image1));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext()),
+ equalTo(" jack some"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionWithMoreThenExtension() {
+ final Node image1 = new Image();
+ final Node image2 = new Image();
+ InlineParser.NodeExtension nodeExtensionOne = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(image2, 33, 53));
+ }
+ };
+ InlineParser.NodeExtension nodeExtensionTwo = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(image1, 5, 25));
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtensionOne).nodeExtension(nodeExtensionTwo).build();
+
+ Node document = parser.parse("some CONVERTED_TO_IMAGE_1 second CONVERTED_TO_IMAGE_2 third");
+ assertThat(document.getFirstChild().getFirstChild(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild()), equalTo("some "));
+ assertThat(document.getFirstChild().getFirstChild().getNext(), equalTo(image1));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext(), instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext().getNext()),
+ equalTo(" second "));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext().getNext(), equalTo(image2));
+ assertThat(document.getFirstChild().getFirstChild().getNext().getNext().getNext().getNext(),
+ instanceOf(Text.class));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext().getNext().getNext().getNext()),
+ equalTo(" third"));
+ }
+
+ @Test
+ public void convertLineByNodeExtensionShouldIgnoreTheNextComponentIfStartingEqualTheNextOneInTheExtensions() {
+ final Node image1 = new Image();
+ InlineParser.NodeExtension nodeExtensionOne = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(image1, 0, 3));
+ }
+ };
+ InlineParser.NodeExtension nodeExtensionTwo = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image(), 0, 4));
+ }
+ };
+
+ Parser parser = Parser.builder().nodeExtension(nodeExtensionOne).nodeExtension(nodeExtensionTwo).build();
+
+ Node document = parser.parse("foo jack some");
+ assertThat(document.getFirstChild().getFirstChild(), equalTo(image1));
+ assertThat(getLiteral(document.getFirstChild().getFirstChild().getNext()),
+ equalTo(" jack some"));
+ }
+
@Test
public void threading() throws Exception {
- final Parser parser = Parser.builder().build();
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image("www.image", "some"), 5, 6));
+ }
+ };
+
+ final Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
final String spec = TestResources.readAsString(TestResources.getSpec());
HtmlRenderer renderer = HtmlRenderer.builder().build();
@@ -136,7 +444,7 @@ public void threading() throws Exception {
for (int i = 0; i < 40; i++) {
Future future = executorService.submit(new Callable() {
@Override
- public Node call() throws Exception {
+ public Node call() {
return parser.parse(spec);
}
});
@@ -154,7 +462,14 @@ private String firstText(Node n) {
assertThat(n, notNullValue());
n = n.getFirstChild();
}
- return ((Text) n).getLiteral();
+ return getLiteral(n);
+ }
+
+ private String getLiteral(Node node) {
+ if (node instanceof Text) {
+ return ((Text)node).getLiteral();
+ }
+ return node.toString();
}
private static class DashBlock extends CustomBlock {
diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java
index 9ff646630..c0f029245 100644
--- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java
+++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java
@@ -1,6 +1,7 @@
package org.commonmark.test;
import org.commonmark.node.*;
+import org.commonmark.parser.InlineParser;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.NodeRenderer;
import org.commonmark.renderer.html.*;
@@ -11,11 +12,19 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
public class UsageExampleTest {
@@ -62,6 +71,63 @@ public AttributeProvider create(AttributeProviderContext context) {
renderer.render(document));
}
+ @Test
+ public void customizeNodeImageFromDifferentTextSyntax() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ @Override
+ public List lookup(String inline) {
+ return singletonList(InlineBreakdown.of(new Image("/url.png", "image"), 10, 25));
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+ HtmlRenderer renderer = HtmlRenderer.builder()
+ .attributeProviderFactory(new AttributeProviderFactory() {
+ public AttributeProvider create(AttributeProviderContext context) {
+ return new ImageAttributeProvider();
+ }
+ })
+ .build();
+
+ Node document = parser.parse("Some text ~image~/url.png");
+ assertEquals("Some text 
\n",
+ renderer.render(document));
+ }
+
+ @Test
+ public void customizeNodesFromDifferentTextSyntaxUsingRegex() {
+ InlineParser.NodeExtension nodeExtension = new InlineParser.NodeExtension() {
+ Pattern pattern = Pattern.compile("~(?[a-zA-Z]+)~(?[\\/a-zA-Z.]+)");
+
+ @Override
+ public List lookup(String inline) {
+ List nodesBreakDown = new ArrayList<>();
+
+ Matcher matcher = pattern.matcher(inline);
+ while (matcher.find()) {
+ nodesBreakDown.add(InlineBreakdown.of(
+ new Image(matcher.group("destination"), matcher.group("title")),
+ matcher.start(),
+ matcher.end()));
+ }
+ return nodesBreakDown;
+ }
+ };
+ Parser parser = Parser.builder().nodeExtension(nodeExtension).build();
+ HtmlRenderer renderer = HtmlRenderer.builder()
+ .attributeProviderFactory(new AttributeProviderFactory() {
+ public AttributeProvider create(AttributeProviderContext context) {
+ return new ImageAttributeProvider();
+ }
+ })
+ .build();
+
+ Node document = parser.parse("Some text ~image~/url.png anything ~SOME~/other.jpg the third one ~some~/other");
+ assertEquals("Some text
" +
+ "anything
" +
+ "the third one 
\n",
+ renderer.render(document));
+ }
+
@Test
public void customizeRendering() {
Parser parser = Parser.builder().build();