Skip to content

Commit 863f1b4

Browse files
committed
Move new emphasis rule into DelimiterProcessor (extension api break)
The "multiple of 3" rule was breaking the strikethrough extension logic. By passing more information into getDelimiterUse and allowing the implementation to return 0, we can move the "multiple of 3" rule into the delimiter processor. For the strikethrough delimiter processor, we can have different rules and simplify the implementation. It's now also closer to GFM. Also clean up the other method names a bit while we are breaking the API: * getOpeningDelimiterChar -> getOpeningCharacter * getClosingDelimiterChar -> getClosingCharacter * getMinDelimiterCount -> getMinLength
1 parent 5afb621 commit 863f1b4

10 files changed

Lines changed: 174 additions & 87 deletions

File tree

commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,39 @@
33
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
44
import org.commonmark.node.Node;
55
import org.commonmark.node.Text;
6-
import org.commonmark.parser.DelimiterProcessor;
6+
import org.commonmark.parser.delimiter.DelimiterProcessor;
7+
import org.commonmark.parser.delimiter.DelimiterRun;
78

89
public class StrikethroughDelimiterProcessor implements DelimiterProcessor {
910

1011
@Override
11-
public char getOpeningDelimiterChar() {
12+
public char getOpeningCharacter() {
1213
return '~';
1314
}
1415

1516
@Override
16-
public char getClosingDelimiterChar() {
17+
public char getClosingCharacter() {
1718
return '~';
1819
}
1920

2021
@Override
21-
public int getMinDelimiterCount() {
22+
public int getMinLength() {
2223
return 2;
2324
}
2425

2526
@Override
26-
public int getDelimiterUse(int openerCount, int closerCount) {
27-
if (openerCount >= 2 && closerCount >= 2) {
27+
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
28+
if (opener.length() >= 2 && closer.length() >= 2) {
29+
// Use exactly two delimiters even if we have more, and don't care about internal openers/closers.
2830
return 2;
2931
} else {
30-
// Can happen if a run had 3 delimiters before, and we removed 2 of them in an earlier processing step.
31-
// So just use 1 of them, see corresponding handling in process method.
32-
return 1;
32+
return 0;
3333
}
3434
}
3535

3636
@Override
3737
public void process(Text opener, Text closer, int delimiterCount) {
38-
// Can happen if a run had 3 or more delimiters, so 1 is left over. Don't turn that into strikethrough, but
39-
// preserve original character.
40-
if (delimiterCount == 1) {
41-
opener.insertAfter(new Text("~"));
42-
closer.insertBefore(new Text("~"));
43-
return;
44-
}
45-
46-
// Normal case, wrap nodes between delimiters in strikethrough.
38+
// Wrap nodes between delimiters in strikethrough.
4739
Node strikethrough = new Strikethrough();
4840

4941
Node tmp = opener.getNext();

commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,14 @@ public void twoInnerThree() {
5050
}
5151

5252
@Test
53-
public void twoStrikethroughsWithoutSpacing() {
53+
public void tildesInside() {
54+
assertRendering("~~foo~bar~~", "<p><del>foo~bar</del></p>\n");
55+
assertRendering("~~foo~~bar~~", "<p><del>foo</del>bar~~</p>\n");
56+
assertRendering("~~foo~~~bar~~", "<p><del>foo</del>~bar~~</p>\n");
5457
assertRendering("~~foo~~~~bar~~", "<p><del>foo</del><del>bar</del></p>\n");
58+
assertRendering("~~foo~~~~~bar~~", "<p><del>foo</del>~<del>bar</del></p>\n");
59+
assertRendering("~~foo~~~~~~bar~~", "<p><del>foo</del>~~<del>bar</del></p>\n");
60+
assertRendering("~~foo~~~~~~~bar~~", "<p><del>foo</del>~~~<del>bar</del></p>\n");
5561
}
5662

5763
@Test

commonmark/src/main/java/org/commonmark/internal/Delimiter.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package org.commonmark.internal;
22

33
import org.commonmark.node.Text;
4+
import org.commonmark.parser.delimiter.DelimiterRun;
45

56
/**
67
* Delimiter (emphasis, strong emphasis or custom emphasis).
78
*/
8-
class Delimiter {
9+
class Delimiter implements DelimiterRun {
910

1011
final Text node;
1112
final char delimiterChar;
@@ -32,4 +33,19 @@ class Delimiter {
3233
this.canClose = canClose;
3334
this.previous = previous;
3435
}
36+
37+
@Override
38+
public boolean canOpen() {
39+
return canOpen;
40+
}
41+
42+
@Override
43+
public boolean canClose() {
44+
return canClose;
45+
}
46+
47+
@Override
48+
public int length() {
49+
return numDelims;
50+
}
3551
}

commonmark/src/main/java/org/commonmark/internal/DelimiterRun.java

Lines changed: 0 additions & 15 deletions
This file was deleted.

commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import org.commonmark.internal.util.Html5Entities;
77
import org.commonmark.internal.util.Parsing;
88
import org.commonmark.node.*;
9-
import org.commonmark.parser.DelimiterProcessor;
9+
import org.commonmark.parser.delimiter.DelimiterProcessor;
1010
import org.commonmark.parser.InlineParser;
1111

1212
import java.util.*;
@@ -134,9 +134,9 @@ public static Map<Character, DelimiterProcessor> calculateDelimiterProcessors(Li
134134

135135
private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> map) {
136136
for (DelimiterProcessor delimiterProcessor : delimiterProcessors) {
137-
char opening = delimiterProcessor.getOpeningDelimiterChar();
137+
char opening = delimiterProcessor.getOpeningCharacter();
138138
addDelimiterProcessorForChar(opening, delimiterProcessor, map);
139-
char closing = delimiterProcessor.getClosingDelimiterChar();
139+
char closing = delimiterProcessor.getClosingCharacter();
140140
if (opening != closing) {
141141
addDelimiterProcessorForChar(closing, delimiterProcessor, map);
142142
}
@@ -430,7 +430,7 @@ private boolean parseBackticks() {
430430
* Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters.
431431
*/
432432
private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) {
433-
DelimiterRun res = scanDelimiters(delimiterProcessor, delimiterChar);
433+
DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar);
434434
if (res == null) {
435435
return false;
436436
}
@@ -727,7 +727,7 @@ private boolean parseString() {
727727
*
728728
* @return information about delimiter run, or {@code null}
729729
*/
730-
private DelimiterRun scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) {
730+
private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) {
731731
int startIndex = index;
732732

733733
int delimiterCount = 0;
@@ -736,7 +736,7 @@ private DelimiterRun scanDelimiters(DelimiterProcessor delimiterProcessor, char
736736
index++;
737737
}
738738

739-
if (delimiterCount < delimiterProcessor.getMinDelimiterCount()) {
739+
if (delimiterCount < delimiterProcessor.getMinLength()) {
740740
index = startIndex;
741741
return null;
742742
}
@@ -764,12 +764,12 @@ private DelimiterRun scanDelimiters(DelimiterProcessor delimiterProcessor, char
764764
canOpen = leftFlanking && (!rightFlanking || beforeIsPunctuation);
765765
canClose = rightFlanking && (!leftFlanking || afterIsPunctuation);
766766
} else {
767-
canOpen = leftFlanking && delimiterChar == delimiterProcessor.getOpeningDelimiterChar();
768-
canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingDelimiterChar();
767+
canOpen = leftFlanking && delimiterChar == delimiterProcessor.getOpeningCharacter();
768+
canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingCharacter();
769769
}
770770

771771
index = startIndex;
772-
return new DelimiterRun(delimiterCount, canOpen, canClose);
772+
return new DelimiterData(delimiterCount, canOpen, canClose);
773773
}
774774

775775
private void processDelimiters(Delimiter stackBottom) {
@@ -791,17 +791,18 @@ private void processDelimiters(Delimiter stackBottom) {
791791
continue;
792792
}
793793

794-
char openingDelimiterChar = delimiterProcessor.getOpeningDelimiterChar();
794+
char openingDelimiterChar = delimiterProcessor.getOpeningCharacter();
795795

796796
// Found delimiter closer. Now look back for first matching opener.
797+
int useDelims = 0;
797798
boolean openerFound = false;
798-
boolean oddMatch = false;
799+
boolean potentialOpenerFound = false;
799800
Delimiter opener = closer.previous;
800801
while (opener != null && opener != stackBottom && opener != openersBottom.get(delimiterChar)) {
801-
if (opener.delimiterChar == openingDelimiterChar) {
802-
oddMatch = (closer.canOpen || opener.canClose) &&
803-
(opener.numDelims + closer.numDelims) % 3 == 0;
804-
if (opener.canOpen && !oddMatch) {
802+
if (opener.canOpen && opener.delimiterChar == openingDelimiterChar) {
803+
potentialOpenerFound = true;
804+
useDelims = delimiterProcessor.getDelimiterUse(opener, closer);
805+
if (useDelims > 0) {
805806
openerFound = true;
806807
break;
807808
}
@@ -810,12 +811,14 @@ private void processDelimiters(Delimiter stackBottom) {
810811
}
811812

812813
if (!openerFound) {
813-
if (!oddMatch) {
814+
if (!potentialOpenerFound) {
814815
// Set lower bound for future searches for openers.
815-
// We don't do this with odd matches because a **
816-
// that doesn't match an earlier * might turn into
817-
// an opener, and the * might be matched by something
818-
// else.
816+
// Only do this when we didn't even have a potential
817+
// opener (one that matches the character and can open).
818+
// If an opener was rejected because of the number of
819+
// delimiters (e.g. because of the "multiple of 3" rule),
820+
// we want to consider it next time because the number
821+
// of delimiters can change as we continue processing.
819822
openersBottom.put(delimiterChar, closer.previous);
820823
if (!closer.canOpen) {
821824
// We can remove a closer that can't be an opener,
@@ -827,16 +830,10 @@ private void processDelimiters(Delimiter stackBottom) {
827830
continue;
828831
}
829832

830-
int useDelims = delimiterProcessor.getDelimiterUse(opener.numDelims, closer.numDelims);
831-
if (useDelims <= 0) {
832-
// nope
833-
useDelims = 1;
834-
}
835-
836833
Text openerNode = opener.node;
837834
Text closerNode = closer.node;
838835

839-
// remove used delimiters from stack elts and inlines
836+
// Remove number of used delimiters from stack and inline nodes.
840837
opener.numDelims -= useDelims;
841838
closer.numDelims -= useDelims;
842839
openerNode.setLiteral(
@@ -952,4 +949,17 @@ private void mergeIfNeeded(Text first, Text last, int textLength) {
952949
first.setLiteral(literal);
953950
}
954951
}
952+
953+
private static class DelimiterData {
954+
955+
final int count;
956+
final boolean canClose;
957+
final boolean canOpen;
958+
959+
DelimiterData(int count, boolean canOpen, boolean canClose) {
960+
this.count = count;
961+
this.canOpen = canOpen;
962+
this.canClose = canClose;
963+
}
964+
}
955965
}

commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import org.commonmark.node.Node;
55
import org.commonmark.node.StrongEmphasis;
66
import org.commonmark.node.Text;
7-
import org.commonmark.parser.DelimiterProcessor;
7+
import org.commonmark.parser.delimiter.DelimiterProcessor;
8+
import org.commonmark.parser.delimiter.DelimiterRun;
89

910
public abstract class EmphasisDelimiterProcessor implements DelimiterProcessor {
1011

@@ -15,34 +16,38 @@ protected EmphasisDelimiterProcessor(char delimiterChar) {
1516
}
1617

1718
@Override
18-
public char getOpeningDelimiterChar() {
19+
public char getOpeningCharacter() {
1920
return delimiterChar;
2021
}
2122

2223
@Override
23-
public char getClosingDelimiterChar() {
24+
public char getClosingCharacter() {
2425
return delimiterChar;
2526
}
2627

2728
@Override
28-
public int getMinDelimiterCount() {
29+
public int getMinLength() {
2930
return 1;
3031
}
3132

3233
@Override
33-
public int getDelimiterUse(int openerCount, int closerCount) {
34+
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
35+
// "multiple of 3" rule for internal delimiter runs
36+
if ((opener.canClose() || closer.canOpen()) && (opener.length() + closer.length()) % 3 == 0) {
37+
return 0;
38+
}
3439
// calculate actual number of delimiters used from this closer
35-
if (closerCount < 3 || openerCount < 3) {
36-
return closerCount <= openerCount ?
37-
closerCount : openerCount;
40+
if (opener.length() < 3 || closer.length() < 3) {
41+
return closer.length() <= opener.length() ?
42+
closer.length() : opener.length();
3843
} else {
39-
return closerCount % 2 == 0 ? 2 : 1;
44+
return closer.length() % 2 == 0 ? 2 : 1;
4045
}
4146
}
4247

4348
@Override
4449
public void process(Text opener, Text closer, int delimiterUse) {
45-
String singleDelimiter = String.valueOf(getOpeningDelimiterChar());
50+
String singleDelimiter = String.valueOf(getOpeningCharacter());
4651
Node emphasis = delimiterUse == 1
4752
? new Emphasis(singleDelimiter)
4853
: new StrongEmphasis(singleDelimiter + singleDelimiter);

commonmark/src/main/java/org/commonmark/parser/Parser.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.commonmark.internal.InlineParserImpl;
88
import org.commonmark.node.Node;
99
import org.commonmark.parser.block.BlockParserFactory;
10+
import org.commonmark.parser.delimiter.DelimiterProcessor;
1011

1112
import java.util.ArrayList;
1213
import java.util.BitSet;

commonmark/src/main/java/org/commonmark/parser/DelimiterProcessor.java renamed to commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.commonmark.parser;
1+
package org.commonmark.parser.delimiter;
22

33
import org.commonmark.node.Text;
44

@@ -13,28 +13,30 @@ public interface DelimiterProcessor {
1313
* @return the character that marks the beginning of a delimited node, must not clash with any built-in special
1414
* characters
1515
*/
16-
char getOpeningDelimiterChar();
16+
char getOpeningCharacter();
1717

1818
/**
1919
* @return the character that marks the the ending of a delimited node, must not clash with any built-in special
2020
* characters. Note that for a symmetric delimiter such as "*", this is the same as the opening.
2121
*/
22-
char getClosingDelimiterChar();
22+
char getClosingCharacter();
2323

2424
/**
2525
* Minimum number of delimiter characters that are needed to activate this. Must be at least 1.
2626
*/
27-
int getMinDelimiterCount();
27+
int getMinLength();
2828

2929
/**
30-
* Determine how many of the delimiters should be used. Useful in case the same character with a different count
31-
* should have a different meaning (e.g. with "*" for emphasis and "**" for strong emphasis).
30+
* Determine how many (if any) of the delimiter characters should be used.
31+
* <p>
32+
* This allows implementations to decide how many characters to use based on the properties of the delimiter runs.
33+
* An implementation can also return 0 when it doesn't want to allow this particular combination of delimiter runs.
3234
*
33-
* @param openerCount the delimiter count of the opening delimiter, at least 1
34-
* @param closerCount the delimiter count of the closing delimiter, at least 1
35-
* @return how many delimiters should be used; cannot be 0; must not be greater than either openerCount or closerCount
35+
* @param opener the opening delimiter run
36+
* @param closer the closing delimiter run
37+
* @return how many delimiters should be used; must not be greater than length of either opener or closer
3638
*/
37-
int getDelimiterUse(int openerCount, int closerCount);
39+
int getDelimiterUse(DelimiterRun opener, DelimiterRun closer);
3840

3941
/**
4042
* Process the matched delimiters, e.g. by wrapping the nodes between opener and closer in a new node, or appending

0 commit comments

Comments
 (0)