From d8d9fa3569080d50e3b552162d552905a44d32c8 Mon Sep 17 00:00:00 2001 From: samukce Date: Sun, 5 Jul 2020 23:15:30 -0700 Subject: [PATCH 01/13] WIP - create inline parse by nodeSetup approach --- commonmark/pom.xml | 5 + .../experimental/InlineParserImpl.java | 262 ++++++++++++++++++ .../experimental/NodeBreakLinePattern.java | 5 + .../commonmark/experimental/NodeCreator.java | 5 + .../experimental/NodePatternIdentifier.java | 23 ++ .../commonmark/experimental/NodeSetup.java | 5 + .../org/commonmark/experimental/PreNode.java | 38 +++ .../experimental/TextIdentifier.java | 25 ++ .../experimental/TextNodeIdentifierSetup.java | 7 + .../RepeatableSymbolContainerIdentifier.java | 61 ++++ .../RepeatableSymbolContainerPattern.java | 28 ++ .../SingleSymbolContainerIdentifier.java | 30 ++ .../SingleSymbolContainerPattern.java | 26 ++ .../identifier/StartSymbolIdentifier.java | 56 ++++ .../identifier/StartSymbolPattern.java | 35 +++ ...nlineParserImplMultipleNodesSetupTest.java | 141 ++++++++++ .../experimental/InlineParserImplTest.java | 120 ++++++++ ...peatableSymbolContainerIdentifierTest.java | 109 ++++++++ .../SingleSymbolContainerIdentifierTest.java | 102 +++++++ .../identifier/StartSymbolIdentifierTest.java | 81 ++++++ .../identifier/TextIdentifierBaseTest.java | 18 ++ pom.xml | 6 + 22 files changed, 1188 insertions(+) create mode 100644 commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/NodeBreakLinePattern.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/NodePatternIdentifier.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/NodeSetup.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/PreNode.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/TextIdentifier.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerPattern.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerPattern.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolIdentifier.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolPattern.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/identifier/StartSymbolIdentifierTest.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/identifier/TextIdentifierBaseTest.java diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 03fcc85ff..834fc8626 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -27,6 +27,11 @@ jmh-generator-annprocess test + + org.mockito + mockito-core + test + diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java new file mode 100644 index 000000000..b58275f83 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java @@ -0,0 +1,262 @@ +package org.commonmark.experimental; + +import org.commonmark.node.Node; +import org.commonmark.parser.InlineParser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.commonmark.experimental.TextIdentifier.INVALID_INDEX; + +public class InlineParserImpl implements InlineParser { + private final Builder.NodeSetupSingleInstance nodeSetupSingleInstance; + private final Map nodeSetupsByCharacterTrigger; + private final Set nodeSetupActives = new HashSet<>(); + + private InlineParserImpl(Builder.NodeSetupSingleInstance nodeSetupSingleInstance, + Map nodeSetupsByCharacterTrigger) { + this.nodeSetupSingleInstance = nodeSetupSingleInstance; + this.nodeSetupsByCharacterTrigger = nodeSetupsByCharacterTrigger; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public void parse(String input, Node node) { + + } + + public void readLine(String text) { + nodeSetupActives.clear(); + + final int length = text.length(); + final PreNode[] positionsReservedByNodePriority = new PreNode[length]; + int index = 0; + + while (index < length) { + final char character = text.charAt(index); + + NodeGroupHandler startTrigger = nodeSetupsByCharacterTrigger.get(character); + if (startTrigger != null) { + nodeSetupActives.add(startTrigger); + } + + for (NodeGroupHandler nodeSetupHandler : nodeSetupActives) { + nodeSetupHandler.check(text, character, index, positionsReservedByNodePriority); + } + + index++; + } + + processNodesInLine(text, positionsReservedByNodePriority); + } + + private void processNodesInLine(String text, PreNode[] positionsReservedByPriority) { + int index = 0; + int lastIndexEmpty = 0; + + while (index < text.length()) { + PreNode preNode = positionsReservedByPriority[index]; + + if (preNode != null) { + if (lastIndexEmpty != index) { + nodeSetupSingleInstance.getNodeCreator() + .build(text.substring(lastIndexEmpty, index), null); + } + + preNode.getNodeCreator() + .build(text.substring(preNode.getStartIndex(), preNode.getEndIndex()), + preNode.getInternalBlocks()); + + lastIndexEmpty = preNode.getEndIndex(); + index = preNode.getEndIndex(); + } else { + index++; + } + } + + if (lastIndexEmpty != index) { + nodeSetupSingleInstance.getNodeCreator() + .build(text.substring(lastIndexEmpty, index), null); + } + } + + private static class DefaultLiteralNodeSetup implements NodeSetup { + @Override + public NodeCreator nodeCreator() { + return new NodeCreator() { + @Override + public String build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks) { + return found; + } + }; + } + } + + public static class Builder { + private final List nodeSetups = new ArrayList<>(); + private NodeSetup literalNodeSetup = new DefaultLiteralNodeSetup(); + + public Builder nodeSetup(TextNodeIdentifierSetup... extensions) { + nodeSetups.addAll(Arrays.asList(extensions)); + return this; + } + + public InlineParserImpl build() { + return new InlineParserImpl(literalNodeSetupSingle(literalNodeSetup), bootParse(nodeSetups)); + } + + private NodeSetupSingleInstance literalNodeSetupSingle(NodeSetup literalNodeSetup) { + return new NodeSetupSingleInstance(literalNodeSetup.nodeCreator()); + } + + private Map bootParse(List nodeSetups) { + orderHighestPriorityFirst(nodeSetups); + + Map extensionByStartBy = new HashMap<>(nodeSetups.size()); + + for (TextNodeIdentifierSetup nodeSetup : nodeSetups) { + TextIdentifier textIdentifier = nodeSetup.textIdentifier(); + + NodeBreakLinePattern preProcessLine = textIdentifier.getNodeBreakLinePattern(); + + NodeGroupHandler nodeGroupHandler = extensionByStartBy.get(preProcessLine.characterTrigger()); + if (nodeGroupHandler == null) { + nodeGroupHandler = new NodeGroupHandler(); + } + + nodeGroupHandler.add(new NodeSetupSingleInstance(textIdentifier, + nodeSetup.nodeCreator(), + nodeSetup.priority())); + + extensionByStartBy.put(preProcessLine.characterTrigger(), nodeGroupHandler); + } + return extensionByStartBy; + } + + private void orderHighestPriorityFirst(List nodeSetups) { + Comparator nodeSetupComparator = new Comparator() { + @Override + public int compare(TextNodeIdentifierSetup o1, TextNodeIdentifierSetup o2) { + return Integer.compare(o1.priority(), o2.priority()); + } + }; + Comparator nodeSetupComparator1 = Collections.reverseOrder(nodeSetupComparator); + Collections.sort(nodeSetups, nodeSetupComparator1); + } + + public Builder literalNodeSetup(NodeSetup literalNodeSetup) { + this.literalNodeSetup = literalNodeSetup; + return this; + } + + private static class NodeSetupSingleInstance { + private final TextIdentifier textIdentifier; + private final int priority; + private final NodeCreator nodeCreator; + + public NodeSetupSingleInstance(TextIdentifier textIdentifier, NodeCreator nodeCreator, int priority) { + this.textIdentifier = textIdentifier; + this.nodeCreator = nodeCreator; + this.priority = priority; + } + + public NodeSetupSingleInstance(NodeCreator nodeCreator) { + this(null, nodeCreator, 0); + } + + public TextIdentifier getTextIdentifier() { + return textIdentifier; + } + + public int getPriority() { + return priority; + } + + public NodeCreator getNodeCreator() { + return nodeCreator; + } + } + } + + private static class NodeGroupHandler { + private final List nodeSetups = new ArrayList<>(); + private final NodeIdentifier nodeIdentifier = new NodeIdentifier(); + private int lastIndex = INVALID_INDEX; + + public void check(final String text, final char character, final int index, + final PreNode[] positionsReservedByPriority) { + nodeIdentifier.setPositions(positionsReservedByPriority); + + for (Builder.NodeSetupSingleInstance nodeSetup : nodeSetups) { + TextIdentifier textIdentifier = nodeSetup.getTextIdentifier(); + if (lastIndex > index) { + textIdentifier.reset(); + } + + nodeIdentifier.setNodeSetup(nodeSetup); + textIdentifier.checkByCharacter(text, character, index, nodeIdentifier); + } + + lastIndex = index; + } + + public void add(Builder.NodeSetupSingleInstance nodeSetup) { + nodeSetups.add(nodeSetup); + } + } + + private static class NodeIdentifier implements NodePatternIdentifier { + private PreNode[] positionsReservedByPriority; + private Builder.NodeSetupSingleInstance nodeSetup; + + @Override + public void found(int startIndex, int endIndex, InternalBlocks[] internalBlocks) { + boolean occupied = false; + int currentPriority = nodeSetup.getPriority(); + + int j = endIndex; + while (j > startIndex) { + PreNode preNode = positionsReservedByPriority[--j]; + + if (preNode != null) { + j = preNode.getStartIndex(); + + if (preNode.getPriority() >= currentPriority) { + occupied = true; + break; + } + } + } + + if (!occupied) { + PreNode preNode = new PreNode(nodeSetup.getNodeCreator(), + nodeSetup.getPriority(), + startIndex, + endIndex, + internalBlocks + ); + for (int i = startIndex; i < endIndex; i++) { + positionsReservedByPriority[i] = preNode; + } + } + } + + public void setPositions(PreNode[] positionsReservedByPriority) { + this.positionsReservedByPriority = positionsReservedByPriority; + } + + public void setNodeSetup(Builder.NodeSetupSingleInstance nodeSetup) { + this.nodeSetup = nodeSetup; + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/NodeBreakLinePattern.java b/commonmark/src/main/java/org/commonmark/experimental/NodeBreakLinePattern.java new file mode 100644 index 000000000..ab767c797 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/NodeBreakLinePattern.java @@ -0,0 +1,5 @@ +package org.commonmark.experimental; + +public interface NodeBreakLinePattern { + char characterTrigger(); +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java b/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java new file mode 100644 index 000000000..fa9f763bf --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java @@ -0,0 +1,5 @@ +package org.commonmark.experimental; + +public interface NodeCreator { + String build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks); +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/NodePatternIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/NodePatternIdentifier.java new file mode 100644 index 000000000..bd33a9797 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/NodePatternIdentifier.java @@ -0,0 +1,23 @@ +package org.commonmark.experimental; + +public interface NodePatternIdentifier { + void found(int startIndex, int endIndex, InternalBlocks[] internalBlocks); + + class InternalBlocks { + private final int relativeStartIndex; + private final int relativeEndIndex; + + public InternalBlocks(int relativeStartIndex, int relativeEndIndex) { + this.relativeStartIndex = relativeStartIndex; + this.relativeEndIndex = relativeEndIndex; + } + + public int getRelativeStartIndex() { + return relativeStartIndex; + } + + public int getRelativeEndIndex() { + return relativeEndIndex; + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/NodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/NodeSetup.java new file mode 100644 index 000000000..581697a88 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/NodeSetup.java @@ -0,0 +1,5 @@ +package org.commonmark.experimental; + +public interface NodeSetup { + NodeCreator nodeCreator(); +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/PreNode.java b/commonmark/src/main/java/org/commonmark/experimental/PreNode.java new file mode 100644 index 000000000..3ca815699 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/PreNode.java @@ -0,0 +1,38 @@ +package org.commonmark.experimental; + +public class PreNode { + private final NodeCreator nodeCreator; + private final int startIndex; + private final int endIndex; + private final int priority; + private final NodePatternIdentifier.InternalBlocks[] internalBlocks; + + public PreNode(NodeCreator nodeCreator, int priority, int startIndex, int endIndex, + NodePatternIdentifier.InternalBlocks[] internalBlocks) { + this.nodeCreator = nodeCreator; + this.startIndex = startIndex; + this.endIndex = endIndex; + this.priority = priority; + this.internalBlocks = internalBlocks; + } + + public int getStartIndex() { + return startIndex; + } + + public int getEndIndex() { + return endIndex; + } + + public int getPriority() { + return priority; + } + + public NodeCreator getNodeCreator() { + return nodeCreator; + } + + public NodePatternIdentifier.InternalBlocks[] getInternalBlocks() { + return internalBlocks; + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/TextIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/TextIdentifier.java new file mode 100644 index 000000000..672ccc452 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/TextIdentifier.java @@ -0,0 +1,25 @@ +package org.commonmark.experimental; + +public abstract class TextIdentifier { + public static final int INVALID_INDEX = Integer.MIN_VALUE; + + private final NodeBreakLinePattern nodeBreakLinePattern; + protected int startIndex = INVALID_INDEX; + protected int endIndex = INVALID_INDEX; + + public TextIdentifier(NodeBreakLinePattern nodeBreakLinePattern) { + this.nodeBreakLinePattern = nodeBreakLinePattern; + } + + public abstract void checkByCharacter(String text, char character, int index, + NodePatternIdentifier nodePatternIdentifier); + + protected void reset() { + startIndex = INVALID_INDEX; + endIndex = INVALID_INDEX; + } + + public NodeBreakLinePattern getNodeBreakLinePattern() { + return nodeBreakLinePattern; + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java b/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java new file mode 100644 index 000000000..0a4c8b708 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java @@ -0,0 +1,7 @@ +package org.commonmark.experimental; + +public interface TextNodeIdentifierSetup extends NodeSetup { + TextIdentifier textIdentifier(); + + int priority(); +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java new file mode 100644 index 000000000..db3efab99 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java @@ -0,0 +1,61 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.TextIdentifier; + +public class RepeatableSymbolContainerIdentifier extends TextIdentifier { + private final RepeatableSymbolContainerPattern nodeBreakLinePattern; + private final int patternSizeLessOne; + private int countStartSymbols; + private int countEndSymbols; + + public RepeatableSymbolContainerIdentifier(RepeatableSymbolContainerPattern nodeBreakLinePattern) { + super(nodeBreakLinePattern); + this.nodeBreakLinePattern = nodeBreakLinePattern; + this.patternSizeLessOne = nodeBreakLinePattern.getSize() - 1; + } + + @Override + public void checkByCharacter(String text, char character, int index, NodePatternIdentifier nodePatternIdentifier) { + + final boolean symbolMatch = character == nodeBreakLinePattern.characterTrigger(); + + resetCounterIfSymbolDoesNotMatch(symbolMatch); + + if (startIndex == INVALID_INDEX && symbolMatch) { + if (countStartSymbols == patternSizeLessOne) { + startIndex = index - countStartSymbols; + } else { + countStartSymbols++; + } + } else if (endIndex == INVALID_INDEX && symbolMatch + && index - startIndex - countStartSymbols - 1 > nodeBreakLinePattern.getMinSize()) { + if (countEndSymbols == patternSizeLessOne) { + endIndex = index + 1; + } else { + countEndSymbols++; + } + } + + if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { + nodePatternIdentifier.found(startIndex, endIndex, null); + reset(); + } + } + + private void resetCounterIfSymbolDoesNotMatch(boolean symbolMatch) { + if (countStartSymbols > 0 && !symbolMatch) { + countStartSymbols = 0; + } + if (countEndSymbols > 0 && !symbolMatch) { + countEndSymbols = 0; + } + } + + @Override + protected void reset() { + super.reset(); + countStartSymbols = 0; + countEndSymbols = 0; + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerPattern.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerPattern.java new file mode 100644 index 000000000..2e1a5da0d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerPattern.java @@ -0,0 +1,28 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodeBreakLinePattern; + +public class RepeatableSymbolContainerPattern implements NodeBreakLinePattern { + private final char symbol; + private final int size; + private final int minSize; + + public RepeatableSymbolContainerPattern(char symbol, int size) { + this.symbol = symbol; + this.size = size; + this.minSize = 1; + } + + @Override + public char characterTrigger() { + return symbol; + } + + public int getSize() { + return size; + } + + public int getMinSize() { + return minSize; + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java new file mode 100644 index 000000000..20fd29afd --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java @@ -0,0 +1,30 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.TextIdentifier; + +public class SingleSymbolContainerIdentifier extends TextIdentifier { + private final SingleSymbolContainerPattern nodeBreakLinePattern; + + public SingleSymbolContainerIdentifier(SingleSymbolContainerPattern nodeBreakLinePattern) { + super(nodeBreakLinePattern); + this.nodeBreakLinePattern = nodeBreakLinePattern; + } + + @Override + public void checkByCharacter(String text, char character, int index, NodePatternIdentifier nodePatternIdentifier) { + + if (startIndex == INVALID_INDEX && character == nodeBreakLinePattern.characterTrigger()) { + startIndex = index; + } else if (endIndex == INVALID_INDEX + && character == nodeBreakLinePattern.characterTrigger() + && index - startIndex > nodeBreakLinePattern.getMinSize() ) { + endIndex = index + 1; + } + + if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { + nodePatternIdentifier.found(startIndex, endIndex, null); + reset(); + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerPattern.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerPattern.java new file mode 100644 index 000000000..4c4e57650 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerPattern.java @@ -0,0 +1,26 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodeBreakLinePattern; + +public class SingleSymbolContainerPattern implements NodeBreakLinePattern { + private final char symbol; + private final int minSize; + + public SingleSymbolContainerPattern(char symbol) { + this(symbol, 1); + } + + public SingleSymbolContainerPattern(char symbol, int minSize) { + this.symbol = symbol; + this.minSize = minSize; + } + + @Override + public char characterTrigger() { + return symbol; + } + + public int getMinSize() { + return minSize; + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolIdentifier.java new file mode 100644 index 000000000..930e9567d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolIdentifier.java @@ -0,0 +1,56 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.TextIdentifier; + +public class StartSymbolIdentifier extends TextIdentifier { + private final StartSymbolPattern nodeBreakLinePattern; + + public StartSymbolIdentifier(StartSymbolPattern nodeBreakLinePattern) { + super(nodeBreakLinePattern); + this.nodeBreakLinePattern = nodeBreakLinePattern; + } + + @Override + public void checkByCharacter(String text, char character, int index, NodePatternIdentifier nodePatternIdentifier) { + + if (startIndex == INVALID_INDEX + && character == nodeBreakLinePattern.characterTrigger() + && isFoundStart(text, index) + ) { + startIndex = index; + } else if (startIndex != INVALID_INDEX + && endIndex == INVALID_INDEX + && isFoundEnd(text, index)) { + endIndex = index + 1; + } + + if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { + nodePatternIdentifier.found(startIndex, endIndex, null); + reset(); + } + } + + private boolean isFoundStart(String text, int index) { + return index == 0 + || isNotDigitAndIsNotLetter(text.charAt(index - 1)); + } + + private boolean isFoundEnd(String text, int index) { + int lastIndex = text.length() - 1; + return index == lastIndex + || (index < lastIndex + && isNotInDontStopList(text.charAt(index + 1)) + && isNotDigitAndIsNotLetter(text.charAt(index + 1)) + ); + } + + private boolean isNotInDontStopList(char charAt) { + return !nodeBreakLinePattern.getDontStopAt().get(charAt); + } + + private boolean isNotDigitAndIsNotLetter(char character) { + return !Character.isDigit(character) + && !Character.isLetter(character); + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolPattern.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolPattern.java new file mode 100644 index 000000000..767531ff4 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolPattern.java @@ -0,0 +1,35 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodeBreakLinePattern; + +import java.util.BitSet; + +public class StartSymbolPattern implements NodeBreakLinePattern { + private final char startSymbol; + private final BitSet dontStopAt; + + public StartSymbolPattern(char startSymbol) { + this(startSymbol, new char[0]); + } + + public StartSymbolPattern(char startSymbol, char... dontStopAt) { + this.startSymbol = startSymbol; + this.dontStopAt = new BitSet(); + fillDontStopCharacterSet(dontStopAt); + } + + public BitSet getDontStopAt() { + return dontStopAt; + } + + private void fillDontStopCharacterSet(char[] dontStopAt) { + for (Character character : dontStopAt) { + this.dontStopAt.set(character); + } + } + + @Override + public char characterTrigger() { + return startSymbol; + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java new file mode 100644 index 000000000..8d97563b8 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java @@ -0,0 +1,141 @@ +package org.commonmark.experimental; + +import org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; +import org.commonmark.experimental.identifier.RepeatableSymbolContainerIdentifier; +import org.commonmark.experimental.identifier.RepeatableSymbolContainerPattern; +import org.commonmark.experimental.identifier.SingleSymbolContainerIdentifier; +import org.commonmark.experimental.identifier.SingleSymbolContainerPattern; +import org.commonmark.experimental.identifier.StartSymbolIdentifier; +import org.commonmark.experimental.identifier.StartSymbolPattern; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +public class InlineParserImplMultipleNodesSetupTest { + private NodeCreator nodeCreatorOne; + private NodeCreator nodeCreatorTwo; + + private NodeSetup literalNodeSetup; + + @Before + public void setUp() { + nodeCreatorOne = mock(NodeCreator.class); + nodeCreatorTwo = mock(NodeCreator.class); + + literalNodeSetup = mock(NodeSetup.class); + when(literalNodeSetup.nodeCreator()).thenReturn(mock(NodeCreator.class)); + } + + @Test + public void shouldConsiderTwoNodesSetup() { + setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*')), + new StartSymbolIdentifier(new StartSymbolPattern('@'))) + .readLine("*image.gif* @second"); + + verify(nodeCreatorOne).build(eq("*image.gif*"), eq((InternalBlocks[]) null)); + verify(nodeCreatorTwo).build(eq("@second"), eq((InternalBlocks[]) null)); + } + + @Test + public void shouldConsiderTwoNodeSetupWithSameStartSymbolAndSamePriorityTheFirstToAdded() { + setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*')), + 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*'))) + .readLine("*image.gif*"); + + verify(nodeCreatorOne).build(eq("*image.gif*"), eq((InternalBlocks[]) null)); + verifyNoInteractions(nodeCreatorTwo); + } + + @Test + public void shouldConsiderTwoNodeSetupWithSameStartSymbolWinningTheHighPriority() { + setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~')), + 10, new RepeatableSymbolContainerIdentifier(new RepeatableSymbolContainerPattern('~', 2))) + .readLine("~image.gif~ ~~second.jpg~~"); + + verify(nodeCreatorOne).build(eq("~image.gif~"), eq((InternalBlocks[]) null)); + verify(nodeCreatorTwo).build(eq("~~second.jpg~~"), eq((InternalBlocks[]) null)); + } + + @Test + public void shouldConsiderTheNodeSetupByPriorityForOverlapConflict() { + setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), + 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + .readLine("`the ~image.gif~ here`"); + + verify(nodeCreatorOne).build(eq("`the ~image.gif~ here`"), eq((InternalBlocks[]) null)); + verifyNoInteractions(nodeCreatorTwo); + } + + @Test + public void shouldConsiderTheNodeSetupByPriorityForLineConflictForInternalComponent() { + setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), + 1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + .readLine("`the ~image.gif~ here`"); + + verifyNoInteractions(nodeCreatorOne); + verify(nodeCreatorTwo).build(eq("~image.gif~"), eq((InternalBlocks[]) null)); + } + + @Test + public void shouldConsiderTheNodeSetupByPriorityForLineConflictForOverlapByLeftSide() { + setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), + 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + .readLine("`the ~image.gif here` another~"); + + verify(nodeCreatorOne).build(eq("`the ~image.gif here`"), eq((InternalBlocks[]) null)); + verifyNoInteractions(nodeCreatorTwo); + } + + @Test + public void shouldConsiderTheNodeSetupByPriorityForLineConflictMultipleOccurrences() { + setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), + 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + .readLine("~the `image.gif` here `second.jpg` another~"); + + verify(nodeCreatorOne).build(eq("`image.gif`"), eq((InternalBlocks[]) null)); + verify(nodeCreatorOne).build(eq("`second.jpg`"), eq((InternalBlocks[]) null)); + verifyNoInteractions(nodeCreatorTwo); + } + + private InlineParserImpl setupParser(final int priorityOne, final TextIdentifier textIdentifierOne, + final int priorityTwo, final TextIdentifier textIdentifierTwo) { + TextNodeIdentifierSetup nodeSetupOne = textNodeIdentifierSetup(textIdentifierOne, priorityOne, nodeCreatorOne); + TextNodeIdentifierSetup nodeSetupTwo = textNodeIdentifierSetup(textIdentifierTwo, priorityTwo, nodeCreatorTwo); + + return InlineParserImpl.builder() + .literalNodeSetup(literalNodeSetup) + .nodeSetup(nodeSetupOne, nodeSetupTwo) + .build(); + } + + private InlineParserImpl setupParser(final TextIdentifier textIdentifierOne, + final TextIdentifier textIdentifierTwo) { + return setupParser(0, textIdentifierOne, 0, textIdentifierTwo); + } + + private TextNodeIdentifierSetup textNodeIdentifierSetup(final TextIdentifier textIdentifierOne, + final int priority, + final NodeCreator nodeCreatorOne) { + return new TextNodeIdentifierSetup() { + @Override + public TextIdentifier textIdentifier() { + return textIdentifierOne; + } + + @Override + public int priority() { + return priority; + } + + @Override + public NodeCreator nodeCreator() { + return nodeCreatorOne; + } + }; + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java new file mode 100644 index 000000000..ffd4dead4 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java @@ -0,0 +1,120 @@ +package org.commonmark.experimental; + +import org.commonmark.experimental.identifier.SingleSymbolContainerIdentifier; +import org.commonmark.experimental.identifier.SingleSymbolContainerPattern; +import org.commonmark.experimental.identifier.StartSymbolIdentifier; +import org.commonmark.experimental.identifier.StartSymbolPattern; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class InlineParserImplTest { + private NodeCreator nodeCreator; + private NodeCreator literalNodeCreator; + private NodeSetup literalNodeSetup; + + @Before + public void setUp() { + nodeCreator = mock(NodeCreator.class); + literalNodeCreator = mock(NodeCreator.class); + literalNodeSetup = mock(NodeSetup.class); + when(literalNodeSetup.nodeCreator()).thenReturn(literalNodeCreator); + } + + @Test + public void shouldParseTextByNodeSetup() { + setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + .readLine("some ~image.gif~ text"); + + verify(nodeCreator).build(eq("~image.gif~"), eq((NodePatternIdentifier.InternalBlocks[]) null)); + } + + @Test + public void shouldNoticeNodesMultipleTimes() { + setupParser(new StartSymbolIdentifier(new StartSymbolPattern('/', '.'))) + .readLine("/image.gif /something.jpg"); + + verify(nodeCreator).build(eq("/image.gif"), eq((NodePatternIdentifier.InternalBlocks[]) null)); + verify(nodeCreator).build(eq("/something.jpg"), eq((NodePatternIdentifier.InternalBlocks[]) null)); + } + + @Test + public void shouldConsiderStartSymbolBetweenWords() { + setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + .readLine("~image.gif~ something~second.jpg~"); + + verify(nodeCreator).build(eq("~image.gif~"), eq((NodePatternIdentifier.InternalBlocks[]) null)); + verify(nodeCreator).build(eq("~second.jpg~"), eq((NodePatternIdentifier.InternalBlocks[]) null)); + } + + @Test + public void shouldResetConfigurationWhenReadLineASecondTime() { + InlineParserImpl inlineParser = setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*'))); + inlineParser.readLine("some *image text"); + inlineParser.readLine("some *second.gif* text"); + + verify(nodeCreator).build(eq("*second.gif*"), eq((NodePatternIdentifier.InternalBlocks[]) null)); + } + + @Test + public void shouldTakeLiteralBeforeNodeFound() { + setupParser(new StartSymbolIdentifier(new StartSymbolPattern('~'))) + .readLine("some ~node"); + + verify(literalNodeCreator).build(eq("some "), eq((NodePatternIdentifier.InternalBlocks[]) null)); + } + + @Test + public void shouldTakeLiteralAfterNodeFound() { + setupParser(new StartSymbolIdentifier(new StartSymbolPattern('~'))) + .readLine("~node after"); + + verify(literalNodeCreator).build(eq(" after"), eq((NodePatternIdentifier.InternalBlocks[]) null)); + } + + @Test + public void shouldTakeLiteralBetweenNodes() { + setupParser(new StartSymbolIdentifier(new StartSymbolPattern('~'))) + .readLine("~nodeone between ~nodetwo"); + + verify(literalNodeCreator).build(eq(" between "), eq((NodePatternIdentifier.InternalBlocks[]) null)); + } + + @Test + public void shouldTakeLiteralBetweenNodesInTheEndOfTheLine() { + setupParser(new StartSymbolIdentifier(new StartSymbolPattern('~'))) + .readLine(" ~nodeone between ~nodetwo after"); + + verify(literalNodeCreator).build(eq(" "), eq((NodePatternIdentifier.InternalBlocks[]) null)); + verify(literalNodeCreator).build(eq(" between "), eq((NodePatternIdentifier.InternalBlocks[]) null)); + verify(literalNodeCreator).build(eq(" after"), eq((NodePatternIdentifier.InternalBlocks[]) null)); + } + + private InlineParserImpl setupParser(final TextIdentifier textIdentifier) { + TextNodeIdentifierSetup nodeSetup = new TextNodeIdentifierSetup() { + @Override + public TextIdentifier textIdentifier() { + return textIdentifier; + } + + @Override + public int priority() { + return 0; + } + + @Override + public NodeCreator nodeCreator() { + return nodeCreator; + } + }; + + return InlineParserImpl.builder() + .literalNodeSetup(literalNodeSetup) + .nodeSetup(nodeSetup) + .build(); + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java new file mode 100644 index 000000000..18f289138 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java @@ -0,0 +1,109 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodePatternIdentifier; +import org.junit.Before; +import org.junit.Test; + +import static org.commonmark.experimental.identifier.TextIdentifierBaseTest.readLine; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +public class RepeatableSymbolContainerIdentifierTest { + private NodePatternIdentifier nodePatternIdentifier; + private RepeatableSymbolContainerIdentifier textIdentifier; + + @Before + public void setUp() { + nodePatternIdentifier = mock(NodePatternIdentifier.class); + textIdentifier = new RepeatableSymbolContainerIdentifier( + new RepeatableSymbolContainerPattern('~', 2)); + } + + @Test + public void shouldIdentifyMultipleCharacter() { + readLine("some ~~image.gif~~ text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 18, null); + } + + @Test + public void shouldIdentifyMultipleCharacterWheContentSizeIsOne() { + readLine("~~i~~", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(0, 5, null); + } + + @Test + public void shouldNotIdentifyMultipleCharacterIfContainerSymbolsIsSingle() { + readLine("some ~image.gif~ text", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyMultipleCharacterIfNotInSequenceFromTheBegin() { + readLine("some ~ ~image.gif~~ text", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyMultipleCharacterIfNotInSequenceInTheEnd() { + readLine("some ~~image.gif~ ~ text", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyIfEmptyTextBetweenCharacters() { + readLine("~~~~", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldIdentifyMultipleCharacterMultipleTimes() { + readLine("some ~~image.gif~~ and ~~second.jpg~~ text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 18, null); + verify(nodePatternIdentifier).found(23, 37, null); + } + + @Test + public void shouldIdentifyMultipleCharacterWhenStartingInTheLineBegin() { + readLine("~~image.gif~~ text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(0, 13, null); + } + + @Test + public void shouldIdentifyMultipleCharacterWhenEndingTheLine() { + readLine("text ~~image.gif~~", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 18, null); + } + + @Test + public void shouldIdentifyMultipleCharacterBetweenWords() { + readLine("some~~image.gif~~thing", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(4, 17, null); + } + + @Test + public void supportMultipleSymbolsAsSetup() { + textIdentifier = new RepeatableSymbolContainerIdentifier( + new RepeatableSymbolContainerPattern('*', 4)); + + readLine("some ****image.gif**** thing **foo**", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 22, null); + verify(nodePatternIdentifier, times(1)) + .found(anyInt(), anyInt(), eq((NodePatternIdentifier.InternalBlocks[])null)); + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java new file mode 100644 index 000000000..1b7f316d9 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java @@ -0,0 +1,102 @@ +package org.commonmark.experimental.identifier; + + +import org.commonmark.experimental.NodePatternIdentifier; +import org.junit.Before; +import org.junit.Test; + +import static org.commonmark.experimental.identifier.TextIdentifierBaseTest.readLine; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +public class SingleSymbolContainerIdentifierTest { + private NodePatternIdentifier nodePatternIdentifier; + private SingleSymbolContainerIdentifier textIdentifier; + + @Before + public void setUp() { + nodePatternIdentifier = mock(NodePatternIdentifier.class); + textIdentifier = new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~')); + } + + @Test + public void shouldIdentifySingleCharacter() { + readLine("some ~image.gif~ text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 16, null); + } + + @Test + public void shouldNotIdentifySingleCharacterInTextWithoutOpenSymbol() { + readLine("some image.gif~ text", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifySingleCharacterInTextWithoutClosedCharacter() { + readLine("some ~image.gif text", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifySingleCharacterInTextWithoutAnyCharacter() { + readLine("some image.gif text", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldIdentifySingleCharacterBetweenSymbols() { + readLine("~i~", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(0, 3, null); + } + + @Test + public void shouldNotIdentifySingleCharacterIfEmptyTextBetweenCharacters() { + readLine("~~", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldIdentifySingleCharacterIfEmptyTextBetweenCharactersWhenMinSizeZero() { + textIdentifier = new SingleSymbolContainerIdentifier( + new SingleSymbolContainerPattern('~', 0)); + readLine("~~", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(0, 2, null); + } + + @Test + public void shouldIdentifySingleCharacterMultipleTimes() { + readLine("some ~image.gif~ and ~second.jpg~ text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 16, null); + verify(nodePatternIdentifier).found(21, 33, null); + } + + @Test + public void shouldIdentifySingleCharacterWhenStartingInTheLine() { + readLine("~image.gif~ text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(0, 11, null); + } + + @Test + public void shouldIdentifySingleCharacterWhenEndingTheLine() { + readLine("text ~image.gif~", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 16, null); + } + + @Test + public void shouldIdentifySingleCharacterBetweenWords() { + readLine("some~image.gif~thing", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(4, 15, null); + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/StartSymbolIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/StartSymbolIdentifierTest.java new file mode 100644 index 000000000..91a0dac7a --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/StartSymbolIdentifierTest.java @@ -0,0 +1,81 @@ +package org.commonmark.experimental.identifier; + + +import org.commonmark.experimental.NodePatternIdentifier; +import org.junit.Before; +import org.junit.Test; + +import static org.commonmark.experimental.identifier.TextIdentifierBaseTest.readLine; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +public class StartSymbolIdentifierTest { + private NodePatternIdentifier nodePatternIdentifier; + private StartSymbolIdentifier textIdentifier; + + @Before + public void setUp() { + nodePatternIdentifier = mock(NodePatternIdentifier.class); + textIdentifier = new StartSymbolIdentifier(new StartSymbolPattern('@', '/')); + } + + @Test + public void shouldIdentifyStartCharacterUntilSpace() { + readLine("some @some text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 10, null); + } + + @Test + public void shouldIdentifyStartCharacterWithSlashInTheText() { + readLine("some @github/support text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 20, null); + } + + @Test + public void shouldIdentifyStartCharacterUntilSpaceBySpecialCharacter() { + textIdentifier = new StartSymbolIdentifier(new StartSymbolPattern('&')); + + readLine("some &123 text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 9, null); + } + + @Test + public void shouldIdentifyStartCharacterUntilSignal() { + readLine("some @some. Text after", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 10, null); + } + + @Test + public void shouldIdentifyStartCharacterMultipleTimes() { + readLine("some @some and @any text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 10, null); + verify(nodePatternIdentifier).found(15, 19, null); + } + + @Test + public void shouldIdentifyStartSymbolWhenStartingInTheLine() { + readLine("@some text", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(0, 5, null); + } + + @Test + public void shouldIdentifyStartCharacterWhenEndingTheLine() { + readLine("text @some", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 10, null); + } + + @Test + public void shouldNotIdentifyStartSymbolWhenCharacterBetweenWords() { + readLine("some@image.gifthing some", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/TextIdentifierBaseTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/TextIdentifierBaseTest.java new file mode 100644 index 000000000..d32ae7564 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/TextIdentifierBaseTest.java @@ -0,0 +1,18 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.TextIdentifier; + +public class TextIdentifierBaseTest { + public static void readLine(String text, TextIdentifier textIdentifier, NodePatternIdentifier nodePatternIdentifier) { + int index = 0; + + while (index < text.length()) { + char character = text.charAt(index); + + textIdentifier.checkByCharacter(text, character, index, nodePatternIdentifier); + + index++; + } + } +} diff --git a/pom.xml b/pom.xml index a16eb9913..9e5973e7f 100644 --- a/pom.xml +++ b/pom.xml @@ -153,6 +153,12 @@ jmh-generator-annprocess 1.17.5 + + org.mockito + mockito-core + 3.3.3 + test + From 947255b408c23fa9c6747b6980644b10471d9b57 Mon Sep 17 00:00:00 2001 From: samukce Date: Mon, 6 Jul 2020 23:13:19 -0700 Subject: [PATCH 02/13] Able to notice image logo by pattern --- .../experimental/InlineParserImpl.java | 33 +- .../InlineParserNodeSetupFactory.java | 14 + .../commonmark/experimental/NodeCreator.java | 4 +- .../BracketContainerIdentifier.java | 102 ++++++ .../identifier/BracketContainerPattern.java | 77 +++++ .../setup/ImageLogoNodeSetup.java | 43 +++ ...nlineParserImplMultipleNodesSetupTest.java | 17 +- .../experimental/InlineParserImplTest.java | 21 +- .../InlineParserNodeSetupFactoryTest.java | 57 ++++ .../BracketContainerIdentifierTest.java | 300 ++++++++++++++++++ 10 files changed, 633 insertions(+), 35 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerIdentifier.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerPattern.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/identifier/BracketContainerIdentifierTest.java diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java index b58275f83..b4ddb9fc6 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java @@ -1,6 +1,7 @@ package org.commonmark.experimental; import org.commonmark.node.Node; +import org.commonmark.node.Text; import org.commonmark.parser.InlineParser; import java.util.ArrayList; @@ -31,19 +32,15 @@ public static Builder builder() { } @Override - public void parse(String input, Node node) { - - } - - public void readLine(String text) { + public void parse(String input, Node block) { nodeSetupActives.clear(); - final int length = text.length(); + final int length = input.length(); final PreNode[] positionsReservedByNodePriority = new PreNode[length]; int index = 0; while (index < length) { - final char character = text.charAt(index); + final char character = input.charAt(index); NodeGroupHandler startTrigger = nodeSetupsByCharacterTrigger.get(character); if (startTrigger != null) { @@ -51,16 +48,16 @@ public void readLine(String text) { } for (NodeGroupHandler nodeSetupHandler : nodeSetupActives) { - nodeSetupHandler.check(text, character, index, positionsReservedByNodePriority); + nodeSetupHandler.check(input, character, index, positionsReservedByNodePriority); } index++; } - processNodesInLine(text, positionsReservedByNodePriority); + processNodesInLine(input, positionsReservedByNodePriority, block); } - private void processNodesInLine(String text, PreNode[] positionsReservedByPriority) { + private void processNodesInLine(String text, PreNode[] positionsReservedByPriority, Node block) { int index = 0; int lastIndexEmpty = 0; @@ -69,13 +66,13 @@ private void processNodesInLine(String text, PreNode[] positionsReservedByPriori if (preNode != null) { if (lastIndexEmpty != index) { - nodeSetupSingleInstance.getNodeCreator() - .build(text.substring(lastIndexEmpty, index), null); + block.appendChild(nodeSetupSingleInstance.getNodeCreator() + .build(text.substring(lastIndexEmpty, index), null)); } - preNode.getNodeCreator() + block.appendChild(preNode.getNodeCreator() .build(text.substring(preNode.getStartIndex(), preNode.getEndIndex()), - preNode.getInternalBlocks()); + preNode.getInternalBlocks())); lastIndexEmpty = preNode.getEndIndex(); index = preNode.getEndIndex(); @@ -85,8 +82,8 @@ private void processNodesInLine(String text, PreNode[] positionsReservedByPriori } if (lastIndexEmpty != index) { - nodeSetupSingleInstance.getNodeCreator() - .build(text.substring(lastIndexEmpty, index), null); + block.appendChild(nodeSetupSingleInstance.getNodeCreator() + .build(text.substring(lastIndexEmpty, index), null)); } } @@ -95,8 +92,8 @@ private static class DefaultLiteralNodeSetup implements NodeSetup { public NodeCreator nodeCreator() { return new NodeCreator() { @Override - public String build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks) { - return found; + public Node build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks) { + return new Text(found.trim()); } }; } diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java new file mode 100644 index 000000000..54c46b05c --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java @@ -0,0 +1,14 @@ +package org.commonmark.experimental; + +import org.commonmark.experimental.setup.ImageLogoNodeSetup; +import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserContext; + +public class InlineParserNodeSetupFactory implements org.commonmark.parser.InlineParserFactory { + @Override + public InlineParser create(InlineParserContext inlineParserContext) { + return InlineParserImpl.builder() + .nodeSetup(new ImageLogoNodeSetup()) + .build(); + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java b/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java index fa9f763bf..15431acc4 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java +++ b/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java @@ -1,5 +1,7 @@ package org.commonmark.experimental; +import org.commonmark.node.Node; + public interface NodeCreator { - String build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks); + Node build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks); } diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerIdentifier.java new file mode 100644 index 000000000..2a97d4003 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerIdentifier.java @@ -0,0 +1,102 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.TextIdentifier; + +import java.util.List; + +public class BracketContainerIdentifier extends TextIdentifier { + private final List openCloses; + private final BracketContainerPattern nodeBreakLinePattern; + private NodePatternIdentifier.InternalBlocks[] internalBlocks; + private int countBrackets = 0; + private int indexOpenClose = 0; + private boolean nextCharacterOpenExpected = false; + private boolean lastGroupClosed = false; + private BracketContainerPattern.OpenClose currentOpenClose; + + public BracketContainerIdentifier(BracketContainerPattern nodeBreakLinePattern) { + super(nodeBreakLinePattern); + this.openCloses = nodeBreakLinePattern.getOpenCloses(); + this.nodeBreakLinePattern = nodeBreakLinePattern; + this.currentOpenClose = openCloses.get(indexOpenClose); + this.internalBlocks = new NodePatternIdentifier.InternalBlocks[openCloses.size()]; + } + + @Override + public void checkByCharacter(String text, char character, int index, NodePatternIdentifier nodePatternIdentifier) { + boolean isOpenCharacter = currentOpenClose.getOpen() == character; + + checkNextCharacterIsOpenSymbol(index, isOpenCharacter); + + if (nodeBreakLinePattern.characterTrigger() == character + && (startIndex == INVALID_INDEX || countBrackets == 0)) { + startIndex = index; + nextCharacterOpenExpected = nodeBreakLinePattern.isStartBySingleSymbol(); + } + + if (startIndex == INVALID_INDEX || !isCurrentGroupContinuation(isOpenCharacter)) { + return; + } + + boolean countedAny = false; + if (isOpenCharacter) { + countBrackets++; + countedAny = true; + } else if (currentOpenClose.getClose() == character) { + countBrackets--; + countedAny = true; + } + + if (countedAny && countBrackets == 0) { + int startIndexGroup = indexOpenClose == 0 + ? 0 + : internalBlocks[indexOpenClose - 1].getRelativeEndIndex(); + + internalBlocks[indexOpenClose] = new NodePatternIdentifier.InternalBlocks( + startIndexGroup, + index - startIndex + 1); + + if (indexOpenClose < openCloses.size() - 1) { + indexOpenClose++; + currentOpenClose = openCloses.get(indexOpenClose); + lastGroupClosed = true; + } else { + endIndex = index + 1; + } + } + + if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { + nodePatternIdentifier.found(startIndex, endIndex, internalBlocks); + reset(); + } + } + + private void checkNextCharacterIsOpenSymbol(int index, boolean isOpenCharacter) { + if (nextCharacterOpenExpected && startIndex == index - 1 && !isOpenCharacter) { + startIndex = INVALID_INDEX; + nextCharacterOpenExpected = false; + } + } + + private boolean isCurrentGroupContinuation(final boolean isOpenCharacter) { + if (lastGroupClosed && !isOpenCharacter) { + reset(); + return false; + } else { + lastGroupClosed = false; + return true; + } + } + + @Override + protected void reset() { + super.reset(); + this.countBrackets = 0; + this.indexOpenClose = 0; + this.currentOpenClose = openCloses.get(indexOpenClose); + this.lastGroupClosed = false; + this.nextCharacterOpenExpected = false; + this.internalBlocks = new NodePatternIdentifier.InternalBlocks[openCloses.size()]; + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerPattern.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerPattern.java new file mode 100644 index 000000000..c4f3fcad1 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerPattern.java @@ -0,0 +1,77 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodeBreakLinePattern; + +import java.util.List; + +import static java.util.Arrays.asList; + +public class BracketContainerPattern implements NodeBreakLinePattern { + public static final char INVALID_SYMBOL = '\0'; + private final char startSymbol; + private final boolean startBySingleSymbol; + private final List openCloses; + + private BracketContainerPattern(char startSymbol, OpenClose... openClose) { + this.openCloses = asList(openClose); + this.startBySingleSymbol = startSymbol != INVALID_SYMBOL; + this.startSymbol = getStartSymbol(startSymbol); + } + + private char getStartSymbol(char startSymbol) { + if (startSymbol != INVALID_SYMBOL) { + return startSymbol; + } + + if (!openCloses.isEmpty()) { + return openCloses.get(0).open; + } + return INVALID_SYMBOL; + } + + @Override + public char characterTrigger() { + return startSymbol; + } + + public List getOpenCloses() { + return openCloses; + } + + public boolean isStartBySingleSymbol() { + return startBySingleSymbol; + } + + public static class OpenClose { + private final char open; + private final char close; + + private OpenClose(char open, char close) { + this.open = open; + this.close = close; + } + + public char getOpen() { + return open; + } + + public char getClose() { + return close; + } + + public static OpenClose of(char open, char close) { + return new OpenClose(open, close); + } + } + + public static BracketContainerPattern of(OpenClose... openClose) { + return BracketContainerPattern.of(INVALID_SYMBOL, openClose); + } + + public static BracketContainerPattern of(char startSymbol, OpenClose... openClose) { + if (openClose.length == 0) { + throw new IllegalArgumentException("openClose must contain at least 1 configuration"); + } + return new BracketContainerPattern(startSymbol, openClose); + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java new file mode 100644 index 000000000..046ac5d8c --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java @@ -0,0 +1,43 @@ +package org.commonmark.experimental.setup; + +import org.commonmark.experimental.NodeCreator; +import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.TextIdentifier; +import org.commonmark.experimental.TextNodeIdentifierSetup; +import org.commonmark.experimental.identifier.BracketContainerIdentifier; +import org.commonmark.experimental.identifier.BracketContainerPattern; +import org.commonmark.experimental.identifier.BracketContainerPattern.OpenClose; +import org.commonmark.node.Image; +import org.commonmark.node.Node; + +public class ImageLogoNodeSetup implements TextNodeIdentifierSetup { + @Override + public TextIdentifier textIdentifier() { + return new BracketContainerIdentifier( + BracketContainerPattern.of( + '!', + OpenClose.of('[', ']'), + OpenClose.of('(', ')'))); + } + + @Override + public int priority() { + return 0; + } + + @Override + public NodeCreator nodeCreator() { + return new NodeCreator() { + @Override + public Node build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks) { + String title = found.substring( + internalBlocks[0].getRelativeStartIndex() + 2, + internalBlocks[0].getRelativeEndIndex() - 1); + String destination = found.substring( + internalBlocks[1].getRelativeStartIndex() + 1, + internalBlocks[1].getRelativeEndIndex() - 1); + return new Image(destination, title); + } + }; + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java index 8d97563b8..56448d405 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java @@ -7,6 +7,7 @@ import org.commonmark.experimental.identifier.SingleSymbolContainerPattern; import org.commonmark.experimental.identifier.StartSymbolIdentifier; import org.commonmark.experimental.identifier.StartSymbolPattern; +import org.commonmark.node.Node; import org.junit.Before; import org.junit.Test; @@ -21,9 +22,11 @@ public class InlineParserImplMultipleNodesSetupTest { private NodeCreator nodeCreatorTwo; private NodeSetup literalNodeSetup; + private Node nodeParent; @Before public void setUp() { + nodeParent = mock(Node.class); nodeCreatorOne = mock(NodeCreator.class); nodeCreatorTwo = mock(NodeCreator.class); @@ -35,7 +38,7 @@ public void setUp() { public void shouldConsiderTwoNodesSetup() { setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*')), new StartSymbolIdentifier(new StartSymbolPattern('@'))) - .readLine("*image.gif* @second"); + .parse("*image.gif* @second", nodeParent); verify(nodeCreatorOne).build(eq("*image.gif*"), eq((InternalBlocks[]) null)); verify(nodeCreatorTwo).build(eq("@second"), eq((InternalBlocks[]) null)); @@ -45,7 +48,7 @@ public void shouldConsiderTwoNodesSetup() { public void shouldConsiderTwoNodeSetupWithSameStartSymbolAndSamePriorityTheFirstToAdded() { setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*')), 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*'))) - .readLine("*image.gif*"); + .parse("*image.gif*", nodeParent); verify(nodeCreatorOne).build(eq("*image.gif*"), eq((InternalBlocks[]) null)); verifyNoInteractions(nodeCreatorTwo); @@ -55,7 +58,7 @@ public void shouldConsiderTwoNodeSetupWithSameStartSymbolAndSamePriorityTheFirst public void shouldConsiderTwoNodeSetupWithSameStartSymbolWinningTheHighPriority() { setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~')), 10, new RepeatableSymbolContainerIdentifier(new RepeatableSymbolContainerPattern('~', 2))) - .readLine("~image.gif~ ~~second.jpg~~"); + .parse("~image.gif~ ~~second.jpg~~", nodeParent); verify(nodeCreatorOne).build(eq("~image.gif~"), eq((InternalBlocks[]) null)); verify(nodeCreatorTwo).build(eq("~~second.jpg~~"), eq((InternalBlocks[]) null)); @@ -65,7 +68,7 @@ public void shouldConsiderTwoNodeSetupWithSameStartSymbolWinningTheHighPriority( public void shouldConsiderTheNodeSetupByPriorityForOverlapConflict() { setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) - .readLine("`the ~image.gif~ here`"); + .parse("`the ~image.gif~ here`", nodeParent); verify(nodeCreatorOne).build(eq("`the ~image.gif~ here`"), eq((InternalBlocks[]) null)); verifyNoInteractions(nodeCreatorTwo); @@ -75,7 +78,7 @@ public void shouldConsiderTheNodeSetupByPriorityForOverlapConflict() { public void shouldConsiderTheNodeSetupByPriorityForLineConflictForInternalComponent() { setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), 1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) - .readLine("`the ~image.gif~ here`"); + .parse("`the ~image.gif~ here`", nodeParent); verifyNoInteractions(nodeCreatorOne); verify(nodeCreatorTwo).build(eq("~image.gif~"), eq((InternalBlocks[]) null)); @@ -85,7 +88,7 @@ public void shouldConsiderTheNodeSetupByPriorityForLineConflictForInternalCompon public void shouldConsiderTheNodeSetupByPriorityForLineConflictForOverlapByLeftSide() { setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) - .readLine("`the ~image.gif here` another~"); + .parse("`the ~image.gif here` another~", nodeParent); verify(nodeCreatorOne).build(eq("`the ~image.gif here`"), eq((InternalBlocks[]) null)); verifyNoInteractions(nodeCreatorTwo); @@ -95,7 +98,7 @@ public void shouldConsiderTheNodeSetupByPriorityForLineConflictForOverlapByLeftS public void shouldConsiderTheNodeSetupByPriorityForLineConflictMultipleOccurrences() { setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) - .readLine("~the `image.gif` here `second.jpg` another~"); + .parse("~the `image.gif` here `second.jpg` another~", nodeParent); verify(nodeCreatorOne).build(eq("`image.gif`"), eq((InternalBlocks[]) null)); verify(nodeCreatorOne).build(eq("`second.jpg`"), eq((InternalBlocks[]) null)); diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java index ffd4dead4..768f0a03e 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java @@ -4,6 +4,7 @@ import org.commonmark.experimental.identifier.SingleSymbolContainerPattern; import org.commonmark.experimental.identifier.StartSymbolIdentifier; import org.commonmark.experimental.identifier.StartSymbolPattern; +import org.commonmark.node.Node; import org.junit.Before; import org.junit.Test; @@ -16,9 +17,11 @@ public class InlineParserImplTest { private NodeCreator nodeCreator; private NodeCreator literalNodeCreator; private NodeSetup literalNodeSetup; + private Node nodeParent; @Before public void setUp() { + nodeParent = mock(Node.class); nodeCreator = mock(NodeCreator.class); literalNodeCreator = mock(NodeCreator.class); literalNodeSetup = mock(NodeSetup.class); @@ -28,7 +31,7 @@ public void setUp() { @Test public void shouldParseTextByNodeSetup() { setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) - .readLine("some ~image.gif~ text"); + .parse("some ~image.gif~ text", nodeParent); verify(nodeCreator).build(eq("~image.gif~"), eq((NodePatternIdentifier.InternalBlocks[]) null)); } @@ -36,7 +39,7 @@ public void shouldParseTextByNodeSetup() { @Test public void shouldNoticeNodesMultipleTimes() { setupParser(new StartSymbolIdentifier(new StartSymbolPattern('/', '.'))) - .readLine("/image.gif /something.jpg"); + .parse("/image.gif /something.jpg", nodeParent); verify(nodeCreator).build(eq("/image.gif"), eq((NodePatternIdentifier.InternalBlocks[]) null)); verify(nodeCreator).build(eq("/something.jpg"), eq((NodePatternIdentifier.InternalBlocks[]) null)); @@ -45,7 +48,7 @@ public void shouldNoticeNodesMultipleTimes() { @Test public void shouldConsiderStartSymbolBetweenWords() { setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) - .readLine("~image.gif~ something~second.jpg~"); + .parse("~image.gif~ something~second.jpg~", nodeParent); verify(nodeCreator).build(eq("~image.gif~"), eq((NodePatternIdentifier.InternalBlocks[]) null)); verify(nodeCreator).build(eq("~second.jpg~"), eq((NodePatternIdentifier.InternalBlocks[]) null)); @@ -54,8 +57,8 @@ public void shouldConsiderStartSymbolBetweenWords() { @Test public void shouldResetConfigurationWhenReadLineASecondTime() { InlineParserImpl inlineParser = setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*'))); - inlineParser.readLine("some *image text"); - inlineParser.readLine("some *second.gif* text"); + inlineParser.parse("some *image text", nodeParent); + inlineParser.parse("some *second.gif* text", nodeParent); verify(nodeCreator).build(eq("*second.gif*"), eq((NodePatternIdentifier.InternalBlocks[]) null)); } @@ -63,7 +66,7 @@ public void shouldResetConfigurationWhenReadLineASecondTime() { @Test public void shouldTakeLiteralBeforeNodeFound() { setupParser(new StartSymbolIdentifier(new StartSymbolPattern('~'))) - .readLine("some ~node"); + .parse("some ~node", nodeParent); verify(literalNodeCreator).build(eq("some "), eq((NodePatternIdentifier.InternalBlocks[]) null)); } @@ -71,7 +74,7 @@ public void shouldTakeLiteralBeforeNodeFound() { @Test public void shouldTakeLiteralAfterNodeFound() { setupParser(new StartSymbolIdentifier(new StartSymbolPattern('~'))) - .readLine("~node after"); + .parse("~node after", nodeParent); verify(literalNodeCreator).build(eq(" after"), eq((NodePatternIdentifier.InternalBlocks[]) null)); } @@ -79,7 +82,7 @@ public void shouldTakeLiteralAfterNodeFound() { @Test public void shouldTakeLiteralBetweenNodes() { setupParser(new StartSymbolIdentifier(new StartSymbolPattern('~'))) - .readLine("~nodeone between ~nodetwo"); + .parse("~nodeone between ~nodetwo", nodeParent); verify(literalNodeCreator).build(eq(" between "), eq((NodePatternIdentifier.InternalBlocks[]) null)); } @@ -87,7 +90,7 @@ public void shouldTakeLiteralBetweenNodes() { @Test public void shouldTakeLiteralBetweenNodesInTheEndOfTheLine() { setupParser(new StartSymbolIdentifier(new StartSymbolPattern('~'))) - .readLine(" ~nodeone between ~nodetwo after"); + .parse(" ~nodeone between ~nodetwo after", nodeParent); verify(literalNodeCreator).build(eq(" "), eq((NodePatternIdentifier.InternalBlocks[]) null)); verify(literalNodeCreator).build(eq(" between "), eq((NodePatternIdentifier.InternalBlocks[]) null)); diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java new file mode 100644 index 000000000..12156d401 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java @@ -0,0 +1,57 @@ +package org.commonmark.experimental; + + +import org.commonmark.node.Image; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.Paragraph; +import org.commonmark.node.Text; +import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class InlineParserNodeSetupFactoryTest { + private InlineParser inlineParser; + private Paragraph paragraph = new Paragraph(); + + @Before + public void setUp() { + inlineParser = new InlineParserNodeSetupFactory().create(new InlineParserContext() { + @Override + public List getCustomDelimiterProcessors() { + return null; + } + + @Override + public LinkReferenceDefinition getLinkReferenceDefinition(String label) { + return null; + } + }); + + paragraph = new Paragraph(); + } + + @Test + public void shouldTakeLiteralTextInParagraph() { + inlineParser.parse("easy test", paragraph); + + assertThat(paragraph.getFirstChild(), instanceOf(Text.class)); + assertThat(((Text)paragraph.getFirstChild()).getLiteral(), is("easy test")); + } + + @Test + public void shouldNoticeImageLogoNode() { + inlineParser.parse("![foo](/train.jpg)", paragraph); + + assertThat(paragraph.getFirstChild(), instanceOf(Image.class)); + assertThat(((Image)paragraph.getFirstChild()).getTitle(), is("foo")); + assertThat(((Image)paragraph.getFirstChild()).getDestination(), is("/train.jpg")); + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/BracketContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/BracketContainerIdentifierTest.java new file mode 100644 index 000000000..ba16bf5fc --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/BracketContainerIdentifierTest.java @@ -0,0 +1,300 @@ +package org.commonmark.experimental.identifier; + +import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.identifier.BracketContainerPattern.OpenClose; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import static org.commonmark.experimental.identifier.TextIdentifierBaseTest.readLine; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class BracketContainerIdentifierTest { + private NodePatternIdentifier nodePatternIdentifier; + private BracketContainerIdentifier textIdentifier; + private ArgumentCaptor argumentCaptor; + + @Before + public void setUp() { + argumentCaptor = ArgumentCaptor.forClass(NodePatternIdentifier.InternalBlocks[].class); + nodePatternIdentifier = mock(NodePatternIdentifier.class); + textIdentifier = new BracketContainerIdentifier( + BracketContainerPattern.of(OpenClose.of('[', ']'))); + } + + @Test + public void shouldIdentifySimpleBracket() { + readLine("[i]", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(3), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().length, is(1)); + assertThat(argumentCaptor.getValue()[0].getRelativeStartIndex(), is(0)); + assertThat(argumentCaptor.getValue()[0].getRelativeEndIndex(), is(3)); + } + + @Test + public void shouldIdentifyInternalBlockPositionAsRelativeIndex() { + readLine("some [i] end", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(5), eq(8), argumentCaptor.capture()); + + assertThat(argumentCaptor.getValue().length, is(1)); + assertThat(argumentCaptor.getValue()[0].getRelativeStartIndex(), is(0)); + assertThat(argumentCaptor.getValue()[0].getRelativeEndIndex(), is(3)); + } + + @Test + public void shouldIdentifySimpleBracketWithEmptyContent() { + readLine("[]", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(2), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldNotIdentifyIfJustOpenBracket() { + readLine("[i", textIdentifier, nodePatternIdentifier); + + verifyZeroInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyIfJustCloseBracket() { + readLine("i]", textIdentifier, nodePatternIdentifier); + + verifyZeroInteractions(nodePatternIdentifier); + } + + @Test + public void shouldIdentifyTwiceIfTwiceInTheLine() { + readLine("[i][b]", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(3), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().length, is(1)); + assertThat(argumentCaptor.getValue()[0].getRelativeStartIndex(), is(0)); + assertThat(argumentCaptor.getValue()[0].getRelativeEndIndex(), is(3)); + + verify(nodePatternIdentifier).found(eq(3), eq(6), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().length, is(1)); + assertThat(argumentCaptor.getValue()[0].getRelativeStartIndex(), is(0)); + assertThat(argumentCaptor.getValue()[0].getRelativeEndIndex(), is(3)); + } + + @Test + public void shouldIdentifyTwiceIfTwiceInTheLineWithSpaces() { + readLine("[i] [b]", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(3), any(NodePatternIdentifier.InternalBlocks[].class)); + verify(nodePatternIdentifier).found(eq(4), eq(7), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldNotIdentifyIfNotBalancedBracket() { + readLine("[i[]", textIdentifier, nodePatternIdentifier); + readLine("[[i]", textIdentifier, nodePatternIdentifier); + readLine("[[]", textIdentifier, nodePatternIdentifier); + + verifyZeroInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyStartInvertedWithEndSymbol() { + readLine("][", textIdentifier, nodePatternIdentifier); + + verifyZeroInteractions(nodePatternIdentifier); + } + + @Test + public void shouldIdentifyAfterFirstCloseBracket() { + readLine("][i][", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(1), eq(4), any(NodePatternIdentifier.InternalBlocks[].class)); + verify(nodePatternIdentifier, times(1)).found(anyInt(), anyInt(), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldIdentifyDoubleBracketConfiguration() { + bracketIdentifierDoubleSymbol(); + + readLine("[i](b)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(6), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().length, is(2)); + assertThat(argumentCaptor.getValue()[0].getRelativeStartIndex(), is(0)); + assertThat(argumentCaptor.getValue()[0].getRelativeEndIndex(), is(3)); + assertThat(argumentCaptor.getValue()[1].getRelativeStartIndex(), is(3)); + assertThat(argumentCaptor.getValue()[1].getRelativeEndIndex(), is(6)); + } + + @Test + public void shouldIdentifyDoubleBracketConfigurationTwiceInLine() { + bracketIdentifierDoubleSymbol(); + + readLine("[i](b) [d](e)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(6), any(NodePatternIdentifier.InternalBlocks[].class)); + verify(nodePatternIdentifier).found(eq(7), eq(13), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldIdentifyBracketWithBalancedBracketsForBothGroupsTwiceInline() { + bracketIdentifierDoubleSymbol(); + + readLine("[i](b[]()) [e[]](()b)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(10), any(NodePatternIdentifier.InternalBlocks[].class)); + verify(nodePatternIdentifier).found(eq(11), eq(21), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldConsiderNotBalancedWithSymbolOverlapBetweenGroups() { + bracketIdentifierDoubleSymbol(); + + readLine("[i(](b) [e](b)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(7), any(NodePatternIdentifier.InternalBlocks[].class)); + verify(nodePatternIdentifier).found(eq(8), eq(14), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldIdentifyDoubleBracketConfigurationInFirstCloseIfFinalNotBalanced() { + bracketIdentifierDoubleSymbol(); + + readLine("[i](b))", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(6), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldNotIdentifyIfNotBalancedSecondGroup() { + bracketIdentifierDoubleSymbol(); + + readLine("[i](b()", textIdentifier, nodePatternIdentifier); + + verifyZeroInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyIfInvertedGroupOrder() { + bracketIdentifierDoubleSymbol(); + + readLine("(b)[i]", textIdentifier, nodePatternIdentifier); + + verifyZeroInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyIfBetweenGroupsHasSomeExtraCharacter() { + bracketIdentifierDoubleSymbol(); + + readLine("[i] (b)", textIdentifier, nodePatternIdentifier); + readLine("[i]|(b)", textIdentifier, nodePatternIdentifier); + readLine("[i]$(b)", textIdentifier, nodePatternIdentifier); + + verifyZeroInteractions(nodePatternIdentifier); + } + + private void bracketIdentifierDoubleSymbol() { + textIdentifier = new BracketContainerIdentifier( + BracketContainerPattern.of( + OpenClose.of('[', ']'), + OpenClose.of('(', ')'))); + } + + @Test + public void shouldIdentifyDoubleBracketConfigurationStartingByCharacter() { + bracketIdentifierDoubleSymbolStartWith(); + + readLine("![i](b)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(7), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldIdentifyDoubleBracketConfigurationByInternalGroups() { + bracketIdentifierDoubleSymbolStartWith(); + + readLine("![i](b)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(7), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().length, is(2)); + assertThat(argumentCaptor.getValue()[0].getRelativeStartIndex(), is(0)); + assertThat(argumentCaptor.getValue()[0].getRelativeEndIndex(), is(4)); + assertThat(argumentCaptor.getValue()[1].getRelativeStartIndex(), is(4)); + assertThat(argumentCaptor.getValue()[1].getRelativeEndIndex(), is(7)); + } + + @Test + public void shouldIdentifyDoubleBracketConfigurationWithInternalGroupsWithRelativePosition() { + bracketIdentifierDoubleSymbolStartWith(); + + readLine("some ![abcs](defg) foo", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(5), eq(18), argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().length, is(2)); + assertThat(argumentCaptor.getValue()[0].getRelativeStartIndex(), is(0)); + assertThat(argumentCaptor.getValue()[0].getRelativeEndIndex(), is(7)); + assertThat(argumentCaptor.getValue()[1].getRelativeStartIndex(), is(7)); + assertThat(argumentCaptor.getValue()[1].getRelativeEndIndex(), is(13)); + } + + @Test + public void shouldIdentifyDoubleBracketConfigurationStartingByCharacterWithContentStartSymbol() { + bracketIdentifierDoubleSymbolStartWith(); + + readLine("![!i!](!b!)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(0), eq(11), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldNotIdentifyDoubleBracketConfigurationStartingByCharacterWhenHasSpace() { + bracketIdentifierDoubleSymbolStartWith(); + + readLine("! [i](b)", textIdentifier, nodePatternIdentifier); + + verifyZeroInteractions(nodePatternIdentifier); + } + + @Test + public void shouldIdentifyDoubleBracketConfigurationStartingByCharacterWithContentStartSymbolInSecondOccurrence() { + bracketIdentifierDoubleSymbolStartWith(); + + readLine("! [i](b) ![a](c)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(9), eq(16), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldIdentifyDoubleBracketStartAfterSymbolStart() { + bracketIdentifierDoubleSymbolStartWith(); + + readLine("!![i](b)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(1), eq(8), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + @Test + public void shouldIdentifyDoubleBracketStartAfterSymbolAndSpaceStart() { + bracketIdentifierDoubleSymbolStartWith(); + + readLine("! ![i](b)", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(eq(2), eq(9), any(NodePatternIdentifier.InternalBlocks[].class)); + } + + private void bracketIdentifierDoubleSymbolStartWith() { + textIdentifier = new BracketContainerIdentifier( + BracketContainerPattern.of( + '!', + OpenClose.of('[', ']'), + OpenClose.of('(', ')'))); + } +} From 673a827744c8b4d037d019584038001d0830a4bd Mon Sep 17 00:00:00 2001 From: samukce Date: Tue, 7 Jul 2020 21:45:33 -0700 Subject: [PATCH 03/13] Add node setup to emphasis element --- .../InlineParserNodeSetupFactory.java | 2 + .../extractor/BracketContainerExtractor.java | 31 ++++++++ .../SingleSymbolContainerExtractor.java | 13 ++++ .../SingleSymbolContainerPattern.java | 12 ++- .../experimental/setup/EmphasisNodeSetup.java | 39 ++++++++++ .../setup/ImageLogoNodeSetup.java | 26 +++---- .../java/org/commonmark/parser/Parser.java | 1 + ...nlineParserImplMultipleNodesSetupTest.java | 24 +++--- .../experimental/InlineParserImplTest.java | 6 +- .../InlineParserNodeSetupFactoryTest.java | 15 ++++ .../BracketContainerExtractorTest.java | 77 +++++++++++++++++++ .../SingleSymbolContainerExtractorTest.java | 33 ++++++++ .../BracketContainerIdentifierTest.java | 18 ++--- .../SingleSymbolContainerIdentifierTest.java | 4 +- 14 files changed, 259 insertions(+), 42 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/experimental/extractor/BracketContainerExtractor.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/extractor/SingleSymbolContainerExtractor.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/extractor/BracketContainerExtractorTest.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/extractor/SingleSymbolContainerExtractorTest.java diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java index 54c46b05c..d37ba722c 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java @@ -1,5 +1,6 @@ package org.commonmark.experimental; +import org.commonmark.experimental.setup.EmphasisNodeSetup; import org.commonmark.experimental.setup.ImageLogoNodeSetup; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; @@ -9,6 +10,7 @@ public class InlineParserNodeSetupFactory implements org.commonmark.parser.Inlin public InlineParser create(InlineParserContext inlineParserContext) { return InlineParserImpl.builder() .nodeSetup(new ImageLogoNodeSetup()) + .nodeSetup(new EmphasisNodeSetup()) .build(); } } diff --git a/commonmark/src/main/java/org/commonmark/experimental/extractor/BracketContainerExtractor.java b/commonmark/src/main/java/org/commonmark/experimental/extractor/BracketContainerExtractor.java new file mode 100644 index 000000000..1228add0a --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/extractor/BracketContainerExtractor.java @@ -0,0 +1,31 @@ +package org.commonmark.experimental.extractor; + +import org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; +import org.commonmark.experimental.identifier.BracketContainerPattern; + +public class BracketContainerExtractor { + public static final String[] EMPTY_STRING = new String[0]; + + private BracketContainerExtractor() { + } + + public static String[] from(String text, + BracketContainerPattern bracketContainerPattern, + InternalBlocks[] internalBlocks) { + if (text == null || internalBlocks.length == 0) { + return EMPTY_STRING; + } + + int factorBegin = bracketContainerPattern.isStartBySingleSymbol() ? 1 : 0; + String[] contents = new String[internalBlocks.length]; + for (int i = 0; i < internalBlocks.length; i++) { + int beginIndex = internalBlocks[i].getRelativeStartIndex() + factorBegin + 1; + + contents[i] = text.substring( + beginIndex, + internalBlocks[i].getRelativeEndIndex() - 1); + factorBegin = 0; + } + return contents; + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/extractor/SingleSymbolContainerExtractor.java b/commonmark/src/main/java/org/commonmark/experimental/extractor/SingleSymbolContainerExtractor.java new file mode 100644 index 000000000..fb2796838 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/extractor/SingleSymbolContainerExtractor.java @@ -0,0 +1,13 @@ +package org.commonmark.experimental.extractor; + +public class SingleSymbolContainerExtractor { + private SingleSymbolContainerExtractor() { + } + + public static String from(String text) { + if (text == null || text.length() < 2) { + return ""; + } + return text.substring(1, text.length() - 1); + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerPattern.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerPattern.java index 4c4e57650..a0724b004 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerPattern.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerPattern.java @@ -6,15 +6,23 @@ public class SingleSymbolContainerPattern implements NodeBreakLinePattern { private final char symbol; private final int minSize; - public SingleSymbolContainerPattern(char symbol) { + private SingleSymbolContainerPattern(char symbol) { this(symbol, 1); } - public SingleSymbolContainerPattern(char symbol, int minSize) { + private SingleSymbolContainerPattern(char symbol, int minSize) { this.symbol = symbol; this.minSize = minSize; } + public static SingleSymbolContainerPattern of(char symbol) { + return new SingleSymbolContainerPattern(symbol); + } + + public static SingleSymbolContainerPattern of(char symbol, int minSize) { + return new SingleSymbolContainerPattern(symbol, minSize); + } + @Override public char characterTrigger() { return symbol; diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java new file mode 100644 index 000000000..0a2d59e15 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java @@ -0,0 +1,39 @@ +package org.commonmark.experimental.setup; + +import org.commonmark.experimental.NodeCreator; +import org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; +import org.commonmark.experimental.TextIdentifier; +import org.commonmark.experimental.TextNodeIdentifierSetup; +import org.commonmark.experimental.extractor.SingleSymbolContainerExtractor; +import org.commonmark.experimental.identifier.SingleSymbolContainerIdentifier; +import org.commonmark.experimental.identifier.SingleSymbolContainerPattern; +import org.commonmark.node.Emphasis; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + +public class EmphasisNodeSetup implements TextNodeIdentifierSetup { + + private static final SingleSymbolContainerPattern EMPHASIS_PATTERN = SingleSymbolContainerPattern.of('*'); + + @Override + public TextIdentifier textIdentifier() { + return new SingleSymbolContainerIdentifier(EMPHASIS_PATTERN); + } + + @Override + public int priority() { + return 0; + } + + @Override + public NodeCreator nodeCreator() { + return new NodeCreator() { + @Override + public Node build(String found, InternalBlocks[] internalBlocks) { + Emphasis emphasis = new Emphasis("*"); + emphasis.appendChild(new Text(SingleSymbolContainerExtractor.from(found))); + return emphasis; + } + }; + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java index 046ac5d8c..8bd0c046f 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java @@ -1,9 +1,10 @@ package org.commonmark.experimental.setup; import org.commonmark.experimental.NodeCreator; -import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; import org.commonmark.experimental.TextIdentifier; import org.commonmark.experimental.TextNodeIdentifierSetup; +import org.commonmark.experimental.extractor.BracketContainerExtractor; import org.commonmark.experimental.identifier.BracketContainerIdentifier; import org.commonmark.experimental.identifier.BracketContainerPattern; import org.commonmark.experimental.identifier.BracketContainerPattern.OpenClose; @@ -11,13 +12,15 @@ import org.commonmark.node.Node; public class ImageLogoNodeSetup implements TextNodeIdentifierSetup { + + private static final BracketContainerPattern LOGO_IMAGE_PATTERN = BracketContainerPattern.of( + '!', + OpenClose.of('[', ']'), + OpenClose.of('(', ')')); + @Override public TextIdentifier textIdentifier() { - return new BracketContainerIdentifier( - BracketContainerPattern.of( - '!', - OpenClose.of('[', ']'), - OpenClose.of('(', ')'))); + return new BracketContainerIdentifier(LOGO_IMAGE_PATTERN); } @Override @@ -29,14 +32,9 @@ public int priority() { public NodeCreator nodeCreator() { return new NodeCreator() { @Override - public Node build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks) { - String title = found.substring( - internalBlocks[0].getRelativeStartIndex() + 2, - internalBlocks[0].getRelativeEndIndex() - 1); - String destination = found.substring( - internalBlocks[1].getRelativeStartIndex() + 1, - internalBlocks[1].getRelativeEndIndex() - 1); - return new Image(destination, title); + public Node build(String found, InternalBlocks[] internalBlocks) { + String[] content = BracketContainerExtractor.from(found, LOGO_IMAGE_PATTERN, internalBlocks); + return new Image(content[1], content[0]); } }; } diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 5e15158ad..e02d7dfe2 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -1,6 +1,7 @@ package org.commonmark.parser; import org.commonmark.Extension; +import org.commonmark.experimental.InlineParserNodeSetupFactory; import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java index 56448d405..059fecf40 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java @@ -36,7 +36,7 @@ public void setUp() { @Test public void shouldConsiderTwoNodesSetup() { - setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*')), + setupParser(new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('*')), new StartSymbolIdentifier(new StartSymbolPattern('@'))) .parse("*image.gif* @second", nodeParent); @@ -46,8 +46,8 @@ public void shouldConsiderTwoNodesSetup() { @Test public void shouldConsiderTwoNodeSetupWithSameStartSymbolAndSamePriorityTheFirstToAdded() { - setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*')), - 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*'))) + setupParser(0, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('*')), + 0, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('*'))) .parse("*image.gif*", nodeParent); verify(nodeCreatorOne).build(eq("*image.gif*"), eq((InternalBlocks[]) null)); @@ -56,7 +56,7 @@ public void shouldConsiderTwoNodeSetupWithSameStartSymbolAndSamePriorityTheFirst @Test public void shouldConsiderTwoNodeSetupWithSameStartSymbolWinningTheHighPriority() { - setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~')), + setupParser(0, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~')), 10, new RepeatableSymbolContainerIdentifier(new RepeatableSymbolContainerPattern('~', 2))) .parse("~image.gif~ ~~second.jpg~~", nodeParent); @@ -66,8 +66,8 @@ public void shouldConsiderTwoNodeSetupWithSameStartSymbolWinningTheHighPriority( @Test public void shouldConsiderTheNodeSetupByPriorityForOverlapConflict() { - setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), - 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + setupParser(1, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('`')), + 0, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~'))) .parse("`the ~image.gif~ here`", nodeParent); verify(nodeCreatorOne).build(eq("`the ~image.gif~ here`"), eq((InternalBlocks[]) null)); @@ -76,8 +76,8 @@ public void shouldConsiderTheNodeSetupByPriorityForOverlapConflict() { @Test public void shouldConsiderTheNodeSetupByPriorityForLineConflictForInternalComponent() { - setupParser(0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), - 1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + setupParser(0, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('`')), + 1, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~'))) .parse("`the ~image.gif~ here`", nodeParent); verifyNoInteractions(nodeCreatorOne); @@ -86,8 +86,8 @@ public void shouldConsiderTheNodeSetupByPriorityForLineConflictForInternalCompon @Test public void shouldConsiderTheNodeSetupByPriorityForLineConflictForOverlapByLeftSide() { - setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), - 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + setupParser(1, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('`')), + 0, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~'))) .parse("`the ~image.gif here` another~", nodeParent); verify(nodeCreatorOne).build(eq("`the ~image.gif here`"), eq((InternalBlocks[]) null)); @@ -96,8 +96,8 @@ public void shouldConsiderTheNodeSetupByPriorityForLineConflictForOverlapByLeftS @Test public void shouldConsiderTheNodeSetupByPriorityForLineConflictMultipleOccurrences() { - setupParser(1, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('`')), - 0, new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + setupParser(1, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('`')), + 0, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~'))) .parse("~the `image.gif` here `second.jpg` another~", nodeParent); verify(nodeCreatorOne).build(eq("`image.gif`"), eq((InternalBlocks[]) null)); diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java index 768f0a03e..6c7c0a3db 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplTest.java @@ -30,7 +30,7 @@ public void setUp() { @Test public void shouldParseTextByNodeSetup() { - setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + setupParser(new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~'))) .parse("some ~image.gif~ text", nodeParent); verify(nodeCreator).build(eq("~image.gif~"), eq((NodePatternIdentifier.InternalBlocks[]) null)); @@ -47,7 +47,7 @@ public void shouldNoticeNodesMultipleTimes() { @Test public void shouldConsiderStartSymbolBetweenWords() { - setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~'))) + setupParser(new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~'))) .parse("~image.gif~ something~second.jpg~", nodeParent); verify(nodeCreator).build(eq("~image.gif~"), eq((NodePatternIdentifier.InternalBlocks[]) null)); @@ -56,7 +56,7 @@ public void shouldConsiderStartSymbolBetweenWords() { @Test public void shouldResetConfigurationWhenReadLineASecondTime() { - InlineParserImpl inlineParser = setupParser(new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('*'))); + InlineParserImpl inlineParser = setupParser(new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('*'))); inlineParser.parse("some *image text", nodeParent); inlineParser.parse("some *second.gif* text", nodeParent); diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java index 12156d401..84920d931 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java @@ -1,13 +1,17 @@ package org.commonmark.experimental; +import org.commonmark.node.Emphasis; import org.commonmark.node.Image; import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.Node; import org.commonmark.node.Paragraph; import org.commonmark.node.Text; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.Parser; import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.renderer.html.HtmlRenderer; import org.junit.Before; import org.junit.Test; @@ -16,6 +20,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; public class InlineParserNodeSetupFactoryTest { private InlineParser inlineParser; @@ -54,4 +59,14 @@ public void shouldNoticeImageLogoNode() { assertThat(((Image)paragraph.getFirstChild()).getTitle(), is("foo")); assertThat(((Image)paragraph.getFirstChild()).getDestination(), is("/train.jpg")); } + + @Test + public void shouldNoticeEmphasisText() { + inlineParser.parse("This is *Sparta*", paragraph); + + assertThat(paragraph.getFirstChild(), instanceOf(Text.class)); + assertThat(((Text)paragraph.getFirstChild()).getLiteral(), is("This is")); + assertThat(paragraph.getFirstChild().getNext(), instanceOf(Emphasis.class)); + assertThat(((Emphasis)paragraph.getFirstChild().getNext()).getOpeningDelimiter(), is("*")); + } } diff --git a/commonmark/src/test/java/org/commonmark/experimental/extractor/BracketContainerExtractorTest.java b/commonmark/src/test/java/org/commonmark/experimental/extractor/BracketContainerExtractorTest.java new file mode 100644 index 000000000..c8a86bec3 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/extractor/BracketContainerExtractorTest.java @@ -0,0 +1,77 @@ +package org.commonmark.experimental.extractor; + +import org.commonmark.experimental.identifier.BracketContainerPattern; +import org.junit.Test; + +import static org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; +import static org.commonmark.experimental.identifier.BracketContainerPattern.OpenClose; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class BracketContainerExtractorTest { + @Test + public void shouldExtractJustTextSkippingTheContainers() { + String[] content = BracketContainerExtractor.from("(some)", + BracketContainerPattern.of(OpenClose.of('(', ')')), + new InternalBlocks[]{new InternalBlocks(0, 6)}); + + assertThat(content.length, is(1)); + assertThat(content[0], is("some")); + } + + @Test + public void shouldExtractJustTextSkippingTheContainersForMultipleBrackets() { + String[] content = BracketContainerExtractor.from("[test()[]](some()[])", + BracketContainerPattern.of( + OpenClose.of('[', ']'), + OpenClose.of('(', ')')), + new InternalBlocks[]{ + new InternalBlocks(0, 10), + new InternalBlocks(10, 20) + }); + + assertThat(content.length, is(2)); + assertThat(content[0], is("test()[]")); + assertThat(content[1], is("some()[]")); + } + + @Test + public void shouldExtractJustTextSkippingTheContainersAndFirstSymbolForMultipleBrackets() { + String[] content = BracketContainerExtractor.from("![test()[]](some()[])", + BracketContainerPattern.of( + '!', + OpenClose.of('[', ']'), + OpenClose.of('(', ')')), + new InternalBlocks[]{ + new InternalBlocks(0, 11), + new InternalBlocks(11, 21) + }); + + assertThat(content.length, is(2)); + assertThat(content[0], is("test()[]")); + assertThat(content[1], is("some()[]")); + } + + @Test + public void shouldReturnEmptyIfNotNullText() { + String[] content = BracketContainerExtractor.from(null, + BracketContainerPattern.of(OpenClose.of('[', ']')), + new InternalBlocks[]{ + new InternalBlocks(0, 6) + }); + + assertThat(content.length, is(0)); + } + + @Test + public void shouldReturnEmptyIfNoContent() { + String[] content = BracketContainerExtractor.from("()", + BracketContainerPattern.of(OpenClose.of('(', ')')), + new InternalBlocks[]{ + new InternalBlocks(0, 2) + }); + + assertThat(content.length, is(1)); + assertThat(content[0], is("")); + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/extractor/SingleSymbolContainerExtractorTest.java b/commonmark/src/test/java/org/commonmark/experimental/extractor/SingleSymbolContainerExtractorTest.java new file mode 100644 index 000000000..d46f9e045 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/extractor/SingleSymbolContainerExtractorTest.java @@ -0,0 +1,33 @@ +package org.commonmark.experimental.extractor; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class SingleSymbolContainerExtractorTest { + @Test + public void shouldExtractJustTextSkippingTheFirstCharacter() { + assertThat(SingleSymbolContainerExtractor.from("~some~"), is("some")); + } + + @Test + public void shouldReturnEmptyIfNotNullText() { + assertThat(SingleSymbolContainerExtractor.from(null), is("")); + } + + @Test + public void shouldReturnEmptyIfNoContent() { + assertThat(SingleSymbolContainerExtractor.from("~~"), is("")); + } + + @Test + public void shouldReturnEmptyIfOneCharacter() { + assertThat(SingleSymbolContainerExtractor.from("~"), is("")); + } + + @Test + public void shouldReturnEmptyIfEmptyText() { + assertThat(SingleSymbolContainerExtractor.from(""), is("")); + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/BracketContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/BracketContainerIdentifierTest.java index ba16bf5fc..dcd1f4263 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/identifier/BracketContainerIdentifierTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/BracketContainerIdentifierTest.java @@ -15,7 +15,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; public class BracketContainerIdentifierTest { private NodePatternIdentifier nodePatternIdentifier; @@ -62,14 +62,14 @@ public void shouldIdentifySimpleBracketWithEmptyContent() { public void shouldNotIdentifyIfJustOpenBracket() { readLine("[i", textIdentifier, nodePatternIdentifier); - verifyZeroInteractions(nodePatternIdentifier); + verifyNoInteractions(nodePatternIdentifier); } @Test public void shouldNotIdentifyIfJustCloseBracket() { readLine("i]", textIdentifier, nodePatternIdentifier); - verifyZeroInteractions(nodePatternIdentifier); + verifyNoInteractions(nodePatternIdentifier); } @Test @@ -101,14 +101,14 @@ public void shouldNotIdentifyIfNotBalancedBracket() { readLine("[[i]", textIdentifier, nodePatternIdentifier); readLine("[[]", textIdentifier, nodePatternIdentifier); - verifyZeroInteractions(nodePatternIdentifier); + verifyNoInteractions(nodePatternIdentifier); } @Test public void shouldNotIdentifyStartInvertedWithEndSymbol() { readLine("][", textIdentifier, nodePatternIdentifier); - verifyZeroInteractions(nodePatternIdentifier); + verifyNoInteractions(nodePatternIdentifier); } @Test @@ -178,7 +178,7 @@ public void shouldNotIdentifyIfNotBalancedSecondGroup() { readLine("[i](b()", textIdentifier, nodePatternIdentifier); - verifyZeroInteractions(nodePatternIdentifier); + verifyNoInteractions(nodePatternIdentifier); } @Test @@ -187,7 +187,7 @@ public void shouldNotIdentifyIfInvertedGroupOrder() { readLine("(b)[i]", textIdentifier, nodePatternIdentifier); - verifyZeroInteractions(nodePatternIdentifier); + verifyNoInteractions(nodePatternIdentifier); } @Test @@ -198,7 +198,7 @@ public void shouldNotIdentifyIfBetweenGroupsHasSomeExtraCharacter() { readLine("[i]|(b)", textIdentifier, nodePatternIdentifier); readLine("[i]$(b)", textIdentifier, nodePatternIdentifier); - verifyZeroInteractions(nodePatternIdentifier); + verifyNoInteractions(nodePatternIdentifier); } private void bracketIdentifierDoubleSymbol() { @@ -260,7 +260,7 @@ public void shouldNotIdentifyDoubleBracketConfigurationStartingByCharacterWhenHa readLine("! [i](b)", textIdentifier, nodePatternIdentifier); - verifyZeroInteractions(nodePatternIdentifier); + verifyNoInteractions(nodePatternIdentifier); } @Test diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java index 1b7f316d9..88a4c046c 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java @@ -17,7 +17,7 @@ public class SingleSymbolContainerIdentifierTest { @Before public void setUp() { nodePatternIdentifier = mock(NodePatternIdentifier.class); - textIdentifier = new SingleSymbolContainerIdentifier(new SingleSymbolContainerPattern('~')); + textIdentifier = new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~')); } @Test @@ -65,7 +65,7 @@ public void shouldNotIdentifySingleCharacterIfEmptyTextBetweenCharacters() { @Test public void shouldIdentifySingleCharacterIfEmptyTextBetweenCharactersWhenMinSizeZero() { textIdentifier = new SingleSymbolContainerIdentifier( - new SingleSymbolContainerPattern('~', 0)); + SingleSymbolContainerPattern.of('~', 0)); readLine("~~", textIdentifier, nodePatternIdentifier); verify(nodePatternIdentifier).found(0, 2, null); From 28623274ca5777549ce438c4170119992c5fec86 Mon Sep 17 00:00:00 2001 From: samukce Date: Tue, 7 Jul 2020 21:59:58 -0700 Subject: [PATCH 04/13] Able to indentify pattern without spaces --- .../SingleSymbolContainerIdentifier.java | 20 ++++++++++++++++-- .../SingleSymbolContainerIdentifierTest.java | 21 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java index 20fd29afd..f63284ec1 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java @@ -5,6 +5,7 @@ public class SingleSymbolContainerIdentifier extends TextIdentifier { private final SingleSymbolContainerPattern nodeBreakLinePattern; + private char lastCharacter = '\0'; public SingleSymbolContainerIdentifier(SingleSymbolContainerPattern nodeBreakLinePattern) { super(nodeBreakLinePattern); @@ -16,15 +17,30 @@ public void checkByCharacter(String text, char character, int index, NodePattern if (startIndex == INVALID_INDEX && character == nodeBreakLinePattern.characterTrigger()) { startIndex = index; + } else if (index == startIndex + 1 && character == ' ') { + reset(); } else if (endIndex == INVALID_INDEX && character == nodeBreakLinePattern.characterTrigger() - && index - startIndex > nodeBreakLinePattern.getMinSize() ) { - endIndex = index + 1; + && index - startIndex > nodeBreakLinePattern.getMinSize()) { + if (lastCharacter == ' ') { + reset(); + startIndex = index; + } else { + endIndex = index + 1; + } } + lastCharacter = character; + if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { nodePatternIdentifier.found(startIndex, endIndex, null); reset(); } } + + @Override + protected void reset() { + super.reset(); + lastCharacter = '\0'; + } } diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java index 88a4c046c..2f1f95ef1 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java @@ -62,6 +62,27 @@ public void shouldNotIdentifySingleCharacterIfEmptyTextBetweenCharacters() { verifyNoInteractions(nodePatternIdentifier); } + @Test + public void shouldNotIdentifyIfSpaceAfterStartSymbol() { + readLine("~ some~", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyIfSpaceBeforeEndSymbol() { + readLine("~some ~", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldIdentifyCountingAfterFirstSymbolWithSpace() { + readLine("~foo ~some~", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(5, 11, null); + } + @Test public void shouldIdentifySingleCharacterIfEmptyTextBetweenCharactersWhenMinSizeZero() { textIdentifier = new SingleSymbolContainerIdentifier( From f45712b356bde27509c4778bd18e691344538f0b Mon Sep 17 00:00:00 2001 From: samukce Date: Tue, 7 Jul 2020 22:06:55 -0700 Subject: [PATCH 05/13] Check for spaces between single symbol container pattern --- .../SingleSymbolContainerIdentifier.java | 23 +++++++++---------- .../SingleSymbolContainerIdentifierTest.java | 9 +++++++- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java index f63284ec1..3ae0c2072 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java @@ -14,20 +14,19 @@ public SingleSymbolContainerIdentifier(SingleSymbolContainerPattern nodeBreakLin @Override public void checkByCharacter(String text, char character, int index, NodePatternIdentifier nodePatternIdentifier) { - - if (startIndex == INVALID_INDEX && character == nodeBreakLinePattern.characterTrigger()) { - startIndex = index; - } else if (index == startIndex + 1 && character == ' ') { - reset(); - } else if (endIndex == INVALID_INDEX - && character == nodeBreakLinePattern.characterTrigger() - && index - startIndex > nodeBreakLinePattern.getMinSize()) { - if (lastCharacter == ' ') { - reset(); + if (character == nodeBreakLinePattern.characterTrigger()) { + if (startIndex == INVALID_INDEX) { startIndex = index; - } else { - endIndex = index + 1; + } else if (endIndex == INVALID_INDEX) { + if (lastCharacter == ' ' || index - startIndex <= nodeBreakLinePattern.getMinSize()) { + reset(); + startIndex = index; + } else { + endIndex = index + 1; + } } + } else if (index == startIndex + 1 && character == ' ') { + reset(); } lastCharacter = character; diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java index 2f1f95ef1..9f7f93786 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifierTest.java @@ -62,6 +62,13 @@ public void shouldNotIdentifySingleCharacterIfEmptyTextBetweenCharacters() { verifyNoInteractions(nodePatternIdentifier); } + @Test + public void shouldIdentifyContentAfterEmptySymbolWithSpace() { + readLine("~ ~some~", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(2, 8, null); + } + @Test public void shouldNotIdentifyIfSpaceAfterStartSymbol() { readLine("~ some~", textIdentifier, nodePatternIdentifier); @@ -77,7 +84,7 @@ public void shouldNotIdentifyIfSpaceBeforeEndSymbol() { } @Test - public void shouldIdentifyCountingAfterFirstSymbolWithSpace() { + public void shouldIdentifyContentAfterFirstSymbolWithSpace() { readLine("~foo ~some~", textIdentifier, nodePatternIdentifier); verify(nodePatternIdentifier).found(5, 11, null); From 1e3b8824bd1d4afaade37ba342c698e5e62735a7 Mon Sep 17 00:00:00 2001 From: samukce Date: Tue, 7 Jul 2020 22:37:08 -0700 Subject: [PATCH 06/13] Handle white space after symbol --- .../experimental/InlineParserImpl.java | 4 +-- .../commonmark/experimental/NodeCreator.java | 2 +- .../experimental/TextIdentifier.java | 1 + .../RepeatableSymbolContainerIdentifier.java | 27 ++++++++++++++++--- .../SingleSymbolContainerIdentifier.java | 2 +- .../experimental/setup/EmphasisNodeSetup.java | 4 +-- .../setup/ImageLogoNodeSetup.java | 4 +-- ...peatableSymbolContainerIdentifierTest.java | 24 +++++++++++++++-- 8 files changed, 55 insertions(+), 13 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java index b4ddb9fc6..79858b61e 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java @@ -92,8 +92,8 @@ private static class DefaultLiteralNodeSetup implements NodeSetup { public NodeCreator nodeCreator() { return new NodeCreator() { @Override - public Node build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks) { - return new Text(found.trim()); + public Node build(String textFound, NodePatternIdentifier.InternalBlocks[] internalBlocks) { + return new Text(textFound.trim()); } }; } diff --git a/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java b/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java index 15431acc4..a07d9020c 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java +++ b/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java @@ -3,5 +3,5 @@ import org.commonmark.node.Node; public interface NodeCreator { - Node build(String found, NodePatternIdentifier.InternalBlocks[] internalBlocks); + Node build(String textFound, NodePatternIdentifier.InternalBlocks[] internalBlocks); } diff --git a/commonmark/src/main/java/org/commonmark/experimental/TextIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/TextIdentifier.java index 672ccc452..ad5546795 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/TextIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/TextIdentifier.java @@ -1,6 +1,7 @@ package org.commonmark.experimental; public abstract class TextIdentifier { + public static final char INVALID_CHAR = '\0'; public static final int INVALID_INDEX = Integer.MIN_VALUE; private final NodeBreakLinePattern nodeBreakLinePattern; diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java index db3efab99..b12021f86 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java @@ -8,6 +8,7 @@ public class RepeatableSymbolContainerIdentifier extends TextIdentifier { private final int patternSizeLessOne; private int countStartSymbols; private int countEndSymbols; + private char lastCharacter = INVALID_CHAR; public RepeatableSymbolContainerIdentifier(RepeatableSymbolContainerPattern nodeBreakLinePattern) { super(nodeBreakLinePattern); @@ -28,10 +29,17 @@ public void checkByCharacter(String text, char character, int index, NodePattern } else { countStartSymbols++; } - } else if (endIndex == INVALID_INDEX && symbolMatch - && index - startIndex - countStartSymbols - 1 > nodeBreakLinePattern.getMinSize()) { + } else if (isCharacterAfterStartSymbolsSpace(character, index)) { + reset(); + } else if (endIndex == INVALID_INDEX + && symbolMatch + && isContentGreaterThanMinSize(index)) { if (countEndSymbols == patternSizeLessOne) { - endIndex = index + 1; + if (isLastCharacterBeforeCloseSymbolSpace(text, index)) { + reset(); + } else { + endIndex = index + 1; + } } else { countEndSymbols++; } @@ -43,6 +51,18 @@ public void checkByCharacter(String text, char character, int index, NodePattern } } + private boolean isLastCharacterBeforeCloseSymbolSpace(String text, int index) { + return text.charAt(index - patternSizeLessOne - 1) == ' '; + } + + private boolean isContentGreaterThanMinSize(int index) { + return index - startIndex - countStartSymbols - 1 > nodeBreakLinePattern.getMinSize(); + } + + private boolean isCharacterAfterStartSymbolsSpace(char character, int index) { + return index == startIndex + patternSizeLessOne + 1 && character == ' '; + } + private void resetCounterIfSymbolDoesNotMatch(boolean symbolMatch) { if (countStartSymbols > 0 && !symbolMatch) { countStartSymbols = 0; @@ -57,5 +77,6 @@ protected void reset() { super.reset(); countStartSymbols = 0; countEndSymbols = 0; + lastCharacter = INVALID_CHAR; } } diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java index 3ae0c2072..ccbe4f9cf 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java @@ -5,7 +5,7 @@ public class SingleSymbolContainerIdentifier extends TextIdentifier { private final SingleSymbolContainerPattern nodeBreakLinePattern; - private char lastCharacter = '\0'; + private char lastCharacter = INVALID_CHAR; public SingleSymbolContainerIdentifier(SingleSymbolContainerPattern nodeBreakLinePattern) { super(nodeBreakLinePattern); diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java index 0a2d59e15..266d996cc 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java @@ -29,9 +29,9 @@ public int priority() { public NodeCreator nodeCreator() { return new NodeCreator() { @Override - public Node build(String found, InternalBlocks[] internalBlocks) { + public Node build(String textFound, InternalBlocks[] internalBlocks) { Emphasis emphasis = new Emphasis("*"); - emphasis.appendChild(new Text(SingleSymbolContainerExtractor.from(found))); + emphasis.appendChild(new Text(SingleSymbolContainerExtractor.from(textFound))); return emphasis; } }; diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java index 8bd0c046f..427a78f54 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java @@ -32,8 +32,8 @@ public int priority() { public NodeCreator nodeCreator() { return new NodeCreator() { @Override - public Node build(String found, InternalBlocks[] internalBlocks) { - String[] content = BracketContainerExtractor.from(found, LOGO_IMAGE_PATTERN, internalBlocks); + public Node build(String textFound, InternalBlocks[] internalBlocks) { + String[] content = BracketContainerExtractor.from(textFound, LOGO_IMAGE_PATTERN, internalBlocks); return new Image(content[1], content[0]); } }; diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java index 18f289138..a051122c3 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java @@ -5,7 +5,6 @@ import org.junit.Test; import static org.commonmark.experimental.identifier.TextIdentifierBaseTest.readLine; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -66,6 +65,27 @@ public void shouldNotIdentifyIfEmptyTextBetweenCharacters() { verifyNoInteractions(nodePatternIdentifier); } + @Test + public void shouldNotIdentifyIfSpaceAfterStartSymbols() { + readLine("~~ i~~", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldNotIdentifyIfSpaceBeforeEndSymbols() { + readLine("~~i ~~", textIdentifier, nodePatternIdentifier); + + verifyNoInteractions(nodePatternIdentifier); + } + + @Test + public void shouldIdentifyAfterFirstSymbolWithSpaces() { + readLine("~~ ~~i~~", textIdentifier, nodePatternIdentifier); + + verify(nodePatternIdentifier).found(3, 8, null); + } + @Test public void shouldIdentifyMultipleCharacterMultipleTimes() { readLine("some ~~image.gif~~ and ~~second.jpg~~ text", textIdentifier, nodePatternIdentifier); @@ -104,6 +124,6 @@ public void supportMultipleSymbolsAsSetup() { verify(nodePatternIdentifier).found(5, 22, null); verify(nodePatternIdentifier, times(1)) - .found(anyInt(), anyInt(), eq((NodePatternIdentifier.InternalBlocks[])null)); + .found(anyInt(), anyInt(), eq((NodePatternIdentifier.InternalBlocks[]) null)); } } From d2b6afcbdc86b207dd85dc70035331d632f2428f Mon Sep 17 00:00:00 2001 From: samukce Date: Tue, 7 Jul 2020 22:58:36 -0700 Subject: [PATCH 07/13] Add strong emphasis node setup --- .../InlineParserNodeSetupFactory.java | 2 + .../experimental/TextNodeIdentifierSetup.java | 6 +++ .../RepeatableSymbolContainerExtractor.java | 17 +++++++ .../RepeatableSymbolContainerPattern.java | 20 +++++++- .../experimental/setup/EmphasisNodeSetup.java | 2 +- .../setup/ImageLogoNodeSetup.java | 2 +- .../setup/StrongEmphasisNodeSetup.java | 41 ++++++++++++++++ ...nlineParserImplMultipleNodesSetupTest.java | 2 +- .../InlineParserNodeSetupFactoryTest.java | 25 ++++++---- ...epeatableSymbolContainerExtractorTest.java | 47 +++++++++++++++++++ ...peatableSymbolContainerIdentifierTest.java | 4 +- 11 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/experimental/extractor/RepeatableSymbolContainerExtractor.java create mode 100644 commonmark/src/main/java/org/commonmark/experimental/setup/StrongEmphasisNodeSetup.java create mode 100644 commonmark/src/test/java/org/commonmark/experimental/extractor/RepeatableSymbolContainerExtractorTest.java diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java index d37ba722c..0bee84cc6 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java @@ -2,6 +2,7 @@ import org.commonmark.experimental.setup.EmphasisNodeSetup; import org.commonmark.experimental.setup.ImageLogoNodeSetup; +import org.commonmark.experimental.setup.StrongEmphasisNodeSetup; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; @@ -10,6 +11,7 @@ public class InlineParserNodeSetupFactory implements org.commonmark.parser.Inlin public InlineParser create(InlineParserContext inlineParserContext) { return InlineParserImpl.builder() .nodeSetup(new ImageLogoNodeSetup()) + .nodeSetup(new StrongEmphasisNodeSetup()) .nodeSetup(new EmphasisNodeSetup()) .build(); } diff --git a/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java b/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java index 0a4c8b708..e9c62430c 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java @@ -4,4 +4,10 @@ public interface TextNodeIdentifierSetup extends NodeSetup { TextIdentifier textIdentifier(); int priority(); + + class DefaultPriority { + public static int DEFAULT = 0; + public static int REPEATABLE_SYMBOL = 10; + public static int BRACKET_SYMBOL = 20; + } } diff --git a/commonmark/src/main/java/org/commonmark/experimental/extractor/RepeatableSymbolContainerExtractor.java b/commonmark/src/main/java/org/commonmark/experimental/extractor/RepeatableSymbolContainerExtractor.java new file mode 100644 index 000000000..48f8fe468 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/extractor/RepeatableSymbolContainerExtractor.java @@ -0,0 +1,17 @@ +package org.commonmark.experimental.extractor; + +import org.commonmark.experimental.identifier.RepeatableSymbolContainerPattern; + +public class RepeatableSymbolContainerExtractor { + private RepeatableSymbolContainerExtractor() { + } + + public static String from(String text, RepeatableSymbolContainerPattern repeatableSymbolContainerPattern) { + if (text == null || text.length() < repeatableSymbolContainerPattern.getSize() * 2) { + return ""; + } + + return text.substring(repeatableSymbolContainerPattern.getSize(), + text.length() - repeatableSymbolContainerPattern.getSize()); + } +} diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerPattern.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerPattern.java index 2e1a5da0d..4dbf78c3e 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerPattern.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerPattern.java @@ -6,11 +6,25 @@ public class RepeatableSymbolContainerPattern implements NodeBreakLinePattern { private final char symbol; private final int size; private final int minSize; + private final String literalSymbolContainer; - public RepeatableSymbolContainerPattern(char symbol, int size) { + private RepeatableSymbolContainerPattern(char symbol, int size) { this.symbol = symbol; this.size = size; this.minSize = 1; + this.literalSymbolContainer = buildLiteralText(); + } + + public static RepeatableSymbolContainerPattern of(char symbol, int size) { + return new RepeatableSymbolContainerPattern(symbol, size); + } + + private String buildLiteralText() { + char[] repeatedCharacters = new char[size]; + for (int i = 0; i < size; i++) { + repeatedCharacters[i] = symbol; + } + return new String(repeatedCharacters); } @Override @@ -25,4 +39,8 @@ public int getSize() { public int getMinSize() { return minSize; } + + public String literalSymbolContainer() { + return literalSymbolContainer; + } } diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java index 266d996cc..028a869db 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/EmphasisNodeSetup.java @@ -22,7 +22,7 @@ public TextIdentifier textIdentifier() { @Override public int priority() { - return 0; + return DefaultPriority.DEFAULT; } @Override diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java index 427a78f54..fba23cd27 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java @@ -25,7 +25,7 @@ public TextIdentifier textIdentifier() { @Override public int priority() { - return 0; + return DefaultPriority.BRACKET_SYMBOL; } @Override diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/StrongEmphasisNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/StrongEmphasisNodeSetup.java new file mode 100644 index 000000000..dd48d2e29 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/StrongEmphasisNodeSetup.java @@ -0,0 +1,41 @@ +package org.commonmark.experimental.setup; + +import org.commonmark.experimental.NodeCreator; +import org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; +import org.commonmark.experimental.TextIdentifier; +import org.commonmark.experimental.TextNodeIdentifierSetup; +import org.commonmark.experimental.extractor.RepeatableSymbolContainerExtractor; +import org.commonmark.experimental.identifier.RepeatableSymbolContainerIdentifier; +import org.commonmark.experimental.identifier.RepeatableSymbolContainerPattern; +import org.commonmark.node.Node; +import org.commonmark.node.StrongEmphasis; +import org.commonmark.node.Text; + +public class StrongEmphasisNodeSetup implements TextNodeIdentifierSetup { + + private static final RepeatableSymbolContainerPattern STRONG_EMPHASIS_PATTERN = + RepeatableSymbolContainerPattern.of('*', 2); + + @Override + public TextIdentifier textIdentifier() { + return new RepeatableSymbolContainerIdentifier(STRONG_EMPHASIS_PATTERN); + } + + @Override + public int priority() { + return DefaultPriority.REPEATABLE_SYMBOL; + } + + @Override + public NodeCreator nodeCreator() { + return new NodeCreator() { + @Override + public Node build(String textFound, InternalBlocks[] internalBlocks) { + StrongEmphasis emphasis = new StrongEmphasis(STRONG_EMPHASIS_PATTERN.literalSymbolContainer()); + emphasis.appendChild( + new Text(RepeatableSymbolContainerExtractor.from(textFound, STRONG_EMPHASIS_PATTERN))); + return emphasis; + } + }; + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java index 059fecf40..604e7798e 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserImplMultipleNodesSetupTest.java @@ -57,7 +57,7 @@ public void shouldConsiderTwoNodeSetupWithSameStartSymbolAndSamePriorityTheFirst @Test public void shouldConsiderTwoNodeSetupWithSameStartSymbolWinningTheHighPriority() { setupParser(0, new SingleSymbolContainerIdentifier(SingleSymbolContainerPattern.of('~')), - 10, new RepeatableSymbolContainerIdentifier(new RepeatableSymbolContainerPattern('~', 2))) + 10, new RepeatableSymbolContainerIdentifier(RepeatableSymbolContainerPattern.of('~', 2))) .parse("~image.gif~ ~~second.jpg~~", nodeParent); verify(nodeCreatorOne).build(eq("~image.gif~"), eq((InternalBlocks[]) null)); diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java index 84920d931..4140aa733 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java @@ -4,14 +4,12 @@ import org.commonmark.node.Emphasis; import org.commonmark.node.Image; import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.node.Node; import org.commonmark.node.Paragraph; +import org.commonmark.node.StrongEmphasis; import org.commonmark.node.Text; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; -import org.commonmark.parser.Parser; import org.commonmark.parser.delimiter.DelimiterProcessor; -import org.commonmark.renderer.html.HtmlRenderer; import org.junit.Before; import org.junit.Test; @@ -20,7 +18,6 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; public class InlineParserNodeSetupFactoryTest { private InlineParser inlineParser; @@ -48,7 +45,7 @@ public void shouldTakeLiteralTextInParagraph() { inlineParser.parse("easy test", paragraph); assertThat(paragraph.getFirstChild(), instanceOf(Text.class)); - assertThat(((Text)paragraph.getFirstChild()).getLiteral(), is("easy test")); + assertThat(((Text) paragraph.getFirstChild()).getLiteral(), is("easy test")); } @Test @@ -56,8 +53,8 @@ public void shouldNoticeImageLogoNode() { inlineParser.parse("![foo](/train.jpg)", paragraph); assertThat(paragraph.getFirstChild(), instanceOf(Image.class)); - assertThat(((Image)paragraph.getFirstChild()).getTitle(), is("foo")); - assertThat(((Image)paragraph.getFirstChild()).getDestination(), is("/train.jpg")); + assertThat(((Image) paragraph.getFirstChild()).getTitle(), is("foo")); + assertThat(((Image) paragraph.getFirstChild()).getDestination(), is("/train.jpg")); } @Test @@ -65,8 +62,18 @@ public void shouldNoticeEmphasisText() { inlineParser.parse("This is *Sparta*", paragraph); assertThat(paragraph.getFirstChild(), instanceOf(Text.class)); - assertThat(((Text)paragraph.getFirstChild()).getLiteral(), is("This is")); + assertThat(((Text) paragraph.getFirstChild()).getLiteral(), is("This is")); assertThat(paragraph.getFirstChild().getNext(), instanceOf(Emphasis.class)); - assertThat(((Emphasis)paragraph.getFirstChild().getNext()).getOpeningDelimiter(), is("*")); + assertThat(((Emphasis) paragraph.getFirstChild().getNext()).getOpeningDelimiter(), is("*")); + } + + @Test + public void shouldNoticeStrongEmphasisText() { + inlineParser.parse("This is **Strong**", paragraph); + + assertThat(paragraph.getFirstChild(), instanceOf(Text.class)); + assertThat(((Text) paragraph.getFirstChild()).getLiteral(), is("This is")); + assertThat(paragraph.getFirstChild().getNext(), instanceOf(StrongEmphasis.class)); + assertThat(((StrongEmphasis) paragraph.getFirstChild().getNext()).getOpeningDelimiter(), is("**")); } } diff --git a/commonmark/src/test/java/org/commonmark/experimental/extractor/RepeatableSymbolContainerExtractorTest.java b/commonmark/src/test/java/org/commonmark/experimental/extractor/RepeatableSymbolContainerExtractorTest.java new file mode 100644 index 000000000..6fb609a2f --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/extractor/RepeatableSymbolContainerExtractorTest.java @@ -0,0 +1,47 @@ +package org.commonmark.experimental.extractor; + +import org.commonmark.experimental.identifier.RepeatableSymbolContainerPattern; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class RepeatableSymbolContainerExtractorTest { + @Test + public void shouldExtractJustTextSkippingTheFirstCharacter() { + assertThat(RepeatableSymbolContainerExtractor + .from("~~some~~", RepeatableSymbolContainerPattern.of('~', 2)), + is("some")); + } + + @Test + public void shouldReturnEmptyIfNotNullText() { + assertThat(RepeatableSymbolContainerExtractor + .from(null, RepeatableSymbolContainerPattern.of('~', 2)), + is("")); + } + + @Test + public void shouldReturnEmptyIfEmptyText() { + assertThat(RepeatableSymbolContainerExtractor + .from("", RepeatableSymbolContainerPattern.of('~', 2)), + is("")); + } + + @Test + public void shouldReturnEmptyIfNoContent() { + assertThat(RepeatableSymbolContainerExtractor + .from("~~~~", RepeatableSymbolContainerPattern.of('~', 2)), + is("")); + } + + @Test + public void shouldReturnEmptyIfLessThenMinSizeCharacter() { + assertThat(RepeatableSymbolContainerExtractor + .from("~~~", RepeatableSymbolContainerPattern.of('~', 2)), + is("")); + assertThat(RepeatableSymbolContainerExtractor + .from("~~", RepeatableSymbolContainerPattern.of('~', 2)), + is("")); + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java b/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java index a051122c3..29362a18d 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifierTest.java @@ -20,7 +20,7 @@ public class RepeatableSymbolContainerIdentifierTest { public void setUp() { nodePatternIdentifier = mock(NodePatternIdentifier.class); textIdentifier = new RepeatableSymbolContainerIdentifier( - new RepeatableSymbolContainerPattern('~', 2)); + RepeatableSymbolContainerPattern.of('~', 2)); } @Test @@ -118,7 +118,7 @@ public void shouldIdentifyMultipleCharacterBetweenWords() { @Test public void supportMultipleSymbolsAsSetup() { textIdentifier = new RepeatableSymbolContainerIdentifier( - new RepeatableSymbolContainerPattern('*', 4)); + RepeatableSymbolContainerPattern.of('*', 4)); readLine("some ****image.gif**** thing **foo**", textIdentifier, nodePatternIdentifier); From bb51464da0659896a2c45ead0fde9036e521f31e Mon Sep 17 00:00:00 2001 From: samukce Date: Tue, 7 Jul 2020 23:22:09 -0700 Subject: [PATCH 08/13] Add link node setup --- .../InlineParserNodeSetupFactory.java | 2 + .../experimental/TextNodeIdentifierSetup.java | 1 + .../setup/ImageLogoNodeSetup.java | 2 +- .../experimental/setup/LinkNodeSetup.java | 40 +++++++++++++++++++ .../InlineParserNodeSetupFactoryTest.java | 11 +++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 commonmark/src/main/java/org/commonmark/experimental/setup/LinkNodeSetup.java diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java index 0bee84cc6..3bc754658 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java @@ -2,6 +2,7 @@ import org.commonmark.experimental.setup.EmphasisNodeSetup; import org.commonmark.experimental.setup.ImageLogoNodeSetup; +import org.commonmark.experimental.setup.LinkNodeSetup; import org.commonmark.experimental.setup.StrongEmphasisNodeSetup; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; @@ -11,6 +12,7 @@ public class InlineParserNodeSetupFactory implements org.commonmark.parser.Inlin public InlineParser create(InlineParserContext inlineParserContext) { return InlineParserImpl.builder() .nodeSetup(new ImageLogoNodeSetup()) + .nodeSetup(new LinkNodeSetup()) .nodeSetup(new StrongEmphasisNodeSetup()) .nodeSetup(new EmphasisNodeSetup()) .build(); diff --git a/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java b/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java index e9c62430c..8d63d2e79 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java @@ -9,5 +9,6 @@ class DefaultPriority { public static int DEFAULT = 0; public static int REPEATABLE_SYMBOL = 10; public static int BRACKET_SYMBOL = 20; + public static int BRACKET_PLUS_START_SYMBOL = 30; } } diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java index fba23cd27..5165a731b 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/ImageLogoNodeSetup.java @@ -25,7 +25,7 @@ public TextIdentifier textIdentifier() { @Override public int priority() { - return DefaultPriority.BRACKET_SYMBOL; + return DefaultPriority.BRACKET_PLUS_START_SYMBOL; } @Override diff --git a/commonmark/src/main/java/org/commonmark/experimental/setup/LinkNodeSetup.java b/commonmark/src/main/java/org/commonmark/experimental/setup/LinkNodeSetup.java new file mode 100644 index 000000000..53cebdea1 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/experimental/setup/LinkNodeSetup.java @@ -0,0 +1,40 @@ +package org.commonmark.experimental.setup; + +import org.commonmark.experimental.NodeCreator; +import org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; +import org.commonmark.experimental.TextIdentifier; +import org.commonmark.experimental.TextNodeIdentifierSetup; +import org.commonmark.experimental.extractor.BracketContainerExtractor; +import org.commonmark.experimental.identifier.BracketContainerIdentifier; +import org.commonmark.experimental.identifier.BracketContainerPattern; +import org.commonmark.experimental.identifier.BracketContainerPattern.OpenClose; +import org.commonmark.node.Link; +import org.commonmark.node.Node; + +public class LinkNodeSetup implements TextNodeIdentifierSetup { + + private static final BracketContainerPattern LINK_PATTERN = BracketContainerPattern.of( + OpenClose.of('[', ']'), + OpenClose.of('(', ')')); + + @Override + public TextIdentifier textIdentifier() { + return new BracketContainerIdentifier(LINK_PATTERN); + } + + @Override + public int priority() { + return DefaultPriority.BRACKET_SYMBOL; + } + + @Override + public NodeCreator nodeCreator() { + return new NodeCreator() { + @Override + public Node build(String textFound, InternalBlocks[] internalBlocks) { + String[] content = BracketContainerExtractor.from(textFound, LINK_PATTERN, internalBlocks); + return new Link(content[1], content[0]); + } + }; + } +} diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java index 4140aa733..4ea0245d1 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java @@ -3,6 +3,7 @@ import org.commonmark.node.Emphasis; import org.commonmark.node.Image; +import org.commonmark.node.Link; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.node.Paragraph; import org.commonmark.node.StrongEmphasis; @@ -18,6 +19,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; public class InlineParserNodeSetupFactoryTest { private InlineParser inlineParser; @@ -57,6 +59,15 @@ public void shouldNoticeImageLogoNode() { assertThat(((Image) paragraph.getFirstChild()).getDestination(), is("/train.jpg")); } + @Test + public void shouldNoticeLink() { + inlineParser.parse("[foo](a.com)", paragraph); + + assertThat(paragraph.getFirstChild(), instanceOf(Link.class)); + assertThat(((Link) paragraph.getFirstChild()).getTitle(), is("foo")); + assertThat(((Link) paragraph.getFirstChild()).getDestination(), is("a.com")); + } + @Test public void shouldNoticeEmphasisText() { inlineParser.parse("This is *Sparta*", paragraph); From cbe50fb00392bac7863501636d07144cac5025d3 Mon Sep 17 00:00:00 2001 From: samukce Date: Tue, 7 Jul 2020 23:48:20 -0700 Subject: [PATCH 09/13] Add benchmark to compare new fashion vs current one --- .../InlineParserNodeSetupFactory.java | 14 +++--- .../experimental/ParseBenchmark.java | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/experimental/ParseBenchmark.java diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java index 3bc754658..c367c08f5 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java @@ -8,13 +8,15 @@ import org.commonmark.parser.InlineParserContext; public class InlineParserNodeSetupFactory implements org.commonmark.parser.InlineParserFactory { + private static final InlineParserImpl inlineParser = InlineParserImpl.builder() + .nodeSetup(new ImageLogoNodeSetup()) + .nodeSetup(new LinkNodeSetup()) + .nodeSetup(new StrongEmphasisNodeSetup()) + .nodeSetup(new EmphasisNodeSetup()) + .build(); + @Override public InlineParser create(InlineParserContext inlineParserContext) { - return InlineParserImpl.builder() - .nodeSetup(new ImageLogoNodeSetup()) - .nodeSetup(new LinkNodeSetup()) - .nodeSetup(new StrongEmphasisNodeSetup()) - .nodeSetup(new EmphasisNodeSetup()) - .build(); + return inlineParser; } } diff --git a/commonmark/src/test/java/org/commonmark/experimental/ParseBenchmark.java b/commonmark/src/test/java/org/commonmark/experimental/ParseBenchmark.java new file mode 100644 index 000000000..da7efa60c --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/experimental/ParseBenchmark.java @@ -0,0 +1,48 @@ +package org.commonmark.experimental; + +import org.commonmark.node.Node; +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.concurrent.TimeUnit; + +@BenchmarkMode(Mode.SingleShotTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Measurement(batchSize = 10000, iterations = 5) +@Warmup(batchSize = 10000, iterations = 5) +public class ParseBenchmark { + public static final String lineToBeParsed = "Some text ![text](/url.png) *another* **point** [link](a.com)"; + + private static final Parser regularParser = Parser.builder().build(); + private static final Parser nodeSetupFashionParser = Parser.builder() + .inlineParserFactory(new InlineParserNodeSetupFactory()) + .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 currentRegularParser() { + return regularParser.parse(lineToBeParsed); + } + + @Benchmark + public Node newFashionParser() { + return nodeSetupFashionParser.parse(lineToBeParsed); + } + +} From 57a2268f40b7afadd63b68db4678886f122c7a60 Mon Sep 17 00:00:00 2001 From: samukce Date: Wed, 8 Jul 2020 21:28:48 -0700 Subject: [PATCH 10/13] Refactor text identifier --- .../BracketContainerIdentifier.java | 39 +++++++++---------- .../RepeatableSymbolContainerIdentifier.java | 18 +++------ .../SingleSymbolContainerIdentifier.java | 12 +++--- 3 files changed, 30 insertions(+), 39 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerIdentifier.java index 2a97d4003..5b1214063 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/BracketContainerIdentifier.java @@ -1,6 +1,7 @@ package org.commonmark.experimental.identifier; import org.commonmark.experimental.NodePatternIdentifier; +import org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; import org.commonmark.experimental.TextIdentifier; import java.util.List; @@ -8,7 +9,7 @@ public class BracketContainerIdentifier extends TextIdentifier { private final List openCloses; private final BracketContainerPattern nodeBreakLinePattern; - private NodePatternIdentifier.InternalBlocks[] internalBlocks; + private InternalBlocks[] internalBlocks; private int countBrackets = 0; private int indexOpenClose = 0; private boolean nextCharacterOpenExpected = false; @@ -20,7 +21,7 @@ public BracketContainerIdentifier(BracketContainerPattern nodeBreakLinePattern) this.openCloses = nodeBreakLinePattern.getOpenCloses(); this.nodeBreakLinePattern = nodeBreakLinePattern; this.currentOpenClose = openCloses.get(indexOpenClose); - this.internalBlocks = new NodePatternIdentifier.InternalBlocks[openCloses.size()]; + this.internalBlocks = new InternalBlocks[openCloses.size()]; } @Override @@ -48,25 +49,23 @@ public void checkByCharacter(String text, char character, int index, NodePattern countedAny = true; } - if (countedAny && countBrackets == 0) { - int startIndexGroup = indexOpenClose == 0 - ? 0 - : internalBlocks[indexOpenClose - 1].getRelativeEndIndex(); - - internalBlocks[indexOpenClose] = new NodePatternIdentifier.InternalBlocks( - startIndexGroup, - index - startIndex + 1); - - if (indexOpenClose < openCloses.size() - 1) { - indexOpenClose++; - currentOpenClose = openCloses.get(indexOpenClose); - lastGroupClosed = true; - } else { - endIndex = index + 1; - } + if (!countedAny || countBrackets != 0) { + return; } - if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { + int startIndexGroup = indexOpenClose == 0 + ? 0 + : internalBlocks[indexOpenClose - 1].getRelativeEndIndex(); + + internalBlocks[indexOpenClose] = new InternalBlocks(startIndexGroup, index - startIndex + 1); + + if (indexOpenClose < openCloses.size() - 1) { + indexOpenClose++; + currentOpenClose = openCloses.get(indexOpenClose); + lastGroupClosed = true; + } else { + endIndex = index + 1; + nodePatternIdentifier.found(startIndex, endIndex, internalBlocks); reset(); } @@ -97,6 +96,6 @@ protected void reset() { this.currentOpenClose = openCloses.get(indexOpenClose); this.lastGroupClosed = false; this.nextCharacterOpenExpected = false; - this.internalBlocks = new NodePatternIdentifier.InternalBlocks[openCloses.size()]; + this.internalBlocks = new InternalBlocks[openCloses.size()]; } } diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java index b12021f86..c5b525b4a 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/RepeatableSymbolContainerIdentifier.java @@ -8,7 +8,6 @@ public class RepeatableSymbolContainerIdentifier extends TextIdentifier { private final int patternSizeLessOne; private int countStartSymbols; private int countEndSymbols; - private char lastCharacter = INVALID_CHAR; public RepeatableSymbolContainerIdentifier(RepeatableSymbolContainerPattern nodeBreakLinePattern) { super(nodeBreakLinePattern); @@ -31,24 +30,18 @@ public void checkByCharacter(String text, char character, int index, NodePattern } } else if (isCharacterAfterStartSymbolsSpace(character, index)) { reset(); - } else if (endIndex == INVALID_INDEX - && symbolMatch - && isContentGreaterThanMinSize(index)) { + } else if (symbolMatch && isContentGreaterThanMinSize(index)) { if (countEndSymbols == patternSizeLessOne) { - if (isLastCharacterBeforeCloseSymbolSpace(text, index)) { - reset(); - } else { + if (!isLastCharacterBeforeCloseSymbolSpace(text, index)) { endIndex = index + 1; + + nodePatternIdentifier.found(startIndex, endIndex, null); } + reset(); } else { countEndSymbols++; } } - - if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { - nodePatternIdentifier.found(startIndex, endIndex, null); - reset(); - } } private boolean isLastCharacterBeforeCloseSymbolSpace(String text, int index) { @@ -77,6 +70,5 @@ protected void reset() { super.reset(); countStartSymbols = 0; countEndSymbols = 0; - lastCharacter = INVALID_CHAR; } } diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java index ccbe4f9cf..78a6884d9 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/SingleSymbolContainerIdentifier.java @@ -17,24 +17,24 @@ public void checkByCharacter(String text, char character, int index, NodePattern if (character == nodeBreakLinePattern.characterTrigger()) { if (startIndex == INVALID_INDEX) { startIndex = index; - } else if (endIndex == INVALID_INDEX) { + } else { if (lastCharacter == ' ' || index - startIndex <= nodeBreakLinePattern.getMinSize()) { reset(); startIndex = index; } else { endIndex = index + 1; } + + if (endIndex != INVALID_INDEX) { + nodePatternIdentifier.found(startIndex, endIndex, null); + reset(); + } } } else if (index == startIndex + 1 && character == ' ') { reset(); } lastCharacter = character; - - if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { - nodePatternIdentifier.found(startIndex, endIndex, null); - reset(); - } } @Override From 4aacdecc8d0630ed9a7838b74ecc9837bf51e518 Mon Sep 17 00:00:00 2001 From: samukce Date: Wed, 8 Jul 2020 21:56:46 -0700 Subject: [PATCH 11/13] Add entrypoint for external nodeSetup --- .../experimental/InlineParserImpl.java | 5 ++ .../InlineParserNodeSetupFactory.java | 24 ++++++--- .../commonmark/experimental/NodeCreator.java | 3 +- .../experimental/TextNodeIdentifierSetup.java | 11 ++-- .../InlineParserNodeSetupFactoryTest.java | 50 ++++++++++++++++++- 5 files changed, 81 insertions(+), 12 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java index 79858b61e..d1a6624ca 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java @@ -108,6 +108,11 @@ public Builder nodeSetup(TextNodeIdentifierSetup... extensions) { return this; } + public Builder nodeSetup(List extensions) { + nodeSetups.addAll(extensions); + return this; + } + public InlineParserImpl build() { return new InlineParserImpl(literalNodeSetupSingle(literalNodeSetup), bootParse(nodeSetups)); } diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java index c367c08f5..8dd4a0aee 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserNodeSetupFactory.java @@ -7,16 +7,28 @@ import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; +import java.util.ArrayList; +import java.util.List; + public class InlineParserNodeSetupFactory implements org.commonmark.parser.InlineParserFactory { - private static final InlineParserImpl inlineParser = InlineParserImpl.builder() - .nodeSetup(new ImageLogoNodeSetup()) - .nodeSetup(new LinkNodeSetup()) - .nodeSetup(new StrongEmphasisNodeSetup()) - .nodeSetup(new EmphasisNodeSetup()) - .build(); + private final InlineParserImpl inlineParser; @Override public InlineParser create(InlineParserContext inlineParserContext) { return inlineParser; } + + public InlineParserNodeSetupFactory() { + this(new ArrayList()); + } + + public InlineParserNodeSetupFactory(List nodeSetupExtensions) { + inlineParser = InlineParserImpl.builder() + .nodeSetup(new ImageLogoNodeSetup()) + .nodeSetup(new LinkNodeSetup()) + .nodeSetup(new StrongEmphasisNodeSetup()) + .nodeSetup(new EmphasisNodeSetup()) + .nodeSetup(nodeSetupExtensions) + .build(); + } } diff --git a/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java b/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java index a07d9020c..9a73f06fa 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java +++ b/commonmark/src/main/java/org/commonmark/experimental/NodeCreator.java @@ -1,7 +1,8 @@ package org.commonmark.experimental; +import org.commonmark.experimental.NodePatternIdentifier.InternalBlocks; import org.commonmark.node.Node; public interface NodeCreator { - Node build(String textFound, NodePatternIdentifier.InternalBlocks[] internalBlocks); + Node build(String textFound, InternalBlocks[] internalBlocks); } diff --git a/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java b/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java index 8d63d2e79..d14ad21c7 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java +++ b/commonmark/src/main/java/org/commonmark/experimental/TextNodeIdentifierSetup.java @@ -6,9 +6,12 @@ public interface TextNodeIdentifierSetup extends NodeSetup { int priority(); class DefaultPriority { - public static int DEFAULT = 0; - public static int REPEATABLE_SYMBOL = 10; - public static int BRACKET_SYMBOL = 20; - public static int BRACKET_PLUS_START_SYMBOL = 30; + public static final int DEFAULT = 0; + public static final int REPEATABLE_SYMBOL = 10; + public static final int BRACKET_SYMBOL = 20; + public static final int BRACKET_PLUS_START_SYMBOL = 30; + + private DefaultPriority() { + } } } diff --git a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java index 4ea0245d1..e53a8f51f 100644 --- a/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java +++ b/commonmark/src/test/java/org/commonmark/experimental/InlineParserNodeSetupFactoryTest.java @@ -5,6 +5,7 @@ import org.commonmark.node.Image; import org.commonmark.node.Link; import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.Node; import org.commonmark.node.Paragraph; import org.commonmark.node.StrongEmphasis; import org.commonmark.node.Text; @@ -14,12 +15,13 @@ import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; import java.util.List; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; public class InlineParserNodeSetupFactoryTest { private InlineParser inlineParser; @@ -87,4 +89,50 @@ public void shouldNoticeStrongEmphasisText() { assertThat(paragraph.getFirstChild().getNext(), instanceOf(StrongEmphasis.class)); assertThat(((StrongEmphasis) paragraph.getFirstChild().getNext()).getOpeningDelimiter(), is("**")); } + + @Test + public void shouldAddExtraNodeSetupConfiguration() { + final Node nodeToCheck = mock(Node.class); + TextNodeIdentifierSetup textNodeIdentifierSetup = new TextNodeIdentifierSetup() { + @Override + public NodeCreator nodeCreator() { + return new NodeCreator() { + @Override + public Node build(String textFound, NodePatternIdentifier.InternalBlocks[] internalBlocks) { + return nodeToCheck; + } + }; + } + + @Override + public TextIdentifier textIdentifier() { + return new TextIdentifier(new NodeBreakLinePattern() { + @Override + public char characterTrigger() { + return 'a'; + } + }) { + @Override + public void checkByCharacter(String text, char character, int index, NodePatternIdentifier nodePatternIdentifier) { + nodePatternIdentifier.found(0, 3, null); + } + }; + } + + @Override + public int priority() { + return -10000; + } + }; + + List nodeSetupExtensions = new ArrayList<>(); + nodeSetupExtensions.add(textNodeIdentifierSetup); + InlineParserNodeSetupFactory inlineParserNodeSetupFactory = + new InlineParserNodeSetupFactory(nodeSetupExtensions); + + InlineParser inlineParser = inlineParserNodeSetupFactory.create(null); + inlineParser.parse("abc", paragraph); + + assertThat(paragraph.getFirstChild(), is(nodeToCheck)); + } } From b81430da49d2b160b492bfa444c357a60796a2b6 Mon Sep 17 00:00:00 2001 From: samukce Date: Wed, 8 Jul 2020 22:07:02 -0700 Subject: [PATCH 12/13] Code refactor in start symbol logic --- .../experimental/identifier/StartSymbolIdentifier.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolIdentifier.java b/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolIdentifier.java index 930e9567d..2305de2fe 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolIdentifier.java +++ b/commonmark/src/main/java/org/commonmark/experimental/identifier/StartSymbolIdentifier.java @@ -23,9 +23,7 @@ && isFoundStart(text, index) && endIndex == INVALID_INDEX && isFoundEnd(text, index)) { endIndex = index + 1; - } - if (startIndex != INVALID_INDEX && endIndex != INVALID_INDEX) { nodePatternIdentifier.found(startIndex, endIndex, null); reset(); } From 85fff28fabaf12ed5e53551a0a9e1a4155208dae Mon Sep 17 00:00:00 2001 From: samukce Date: Wed, 8 Jul 2020 22:16:38 -0700 Subject: [PATCH 13/13] Code refactors rename things --- .../org/commonmark/experimental/InlineParserImpl.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java index d1a6624ca..155f3698e 100644 --- a/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/experimental/InlineParserImpl.java @@ -124,14 +124,14 @@ private NodeSetupSingleInstance literalNodeSetupSingle(NodeSetup literalNodeSetu private Map bootParse(List nodeSetups) { orderHighestPriorityFirst(nodeSetups); - Map extensionByStartBy = new HashMap<>(nodeSetups.size()); + Map nodeSetupsByStartCharacter = new HashMap<>(nodeSetups.size()); for (TextNodeIdentifierSetup nodeSetup : nodeSetups) { TextIdentifier textIdentifier = nodeSetup.textIdentifier(); NodeBreakLinePattern preProcessLine = textIdentifier.getNodeBreakLinePattern(); - NodeGroupHandler nodeGroupHandler = extensionByStartBy.get(preProcessLine.characterTrigger()); + NodeGroupHandler nodeGroupHandler = nodeSetupsByStartCharacter.get(preProcessLine.characterTrigger()); if (nodeGroupHandler == null) { nodeGroupHandler = new NodeGroupHandler(); } @@ -140,9 +140,9 @@ private Map bootParse(List nodeSetup.nodeCreator(), nodeSetup.priority())); - extensionByStartBy.put(preProcessLine.characterTrigger(), nodeGroupHandler); + nodeSetupsByStartCharacter.put(preProcessLine.characterTrigger(), nodeGroupHandler); } - return extensionByStartBy; + return nodeSetupsByStartCharacter; } private void orderHighestPriorityFirst(List nodeSetups) { @@ -152,8 +152,7 @@ public int compare(TextNodeIdentifierSetup o1, TextNodeIdentifierSetup o2) { return Integer.compare(o1.priority(), o2.priority()); } }; - Comparator nodeSetupComparator1 = Collections.reverseOrder(nodeSetupComparator); - Collections.sort(nodeSetups, nodeSetupComparator1); + Collections.sort(nodeSetups, Collections.reverseOrder(nodeSetupComparator)); } public Builder literalNodeSetup(NodeSetup literalNodeSetup) {