Skip to content

Commit f490b74

Browse files
authored
Merge pull request #270 from commonmark/issue-209-autolink-sourcespans
Fix autolink extension to handle source spans correctly (fixes #209)
2 parents 0f3f4f3 + 2e03eb5 commit f490b74

File tree

3 files changed

+96
-15
lines changed

3 files changed

+96
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ with the exception that 0.x versions can break between minor versions.
1414
- GitHub strikethrough: A single tilde now also works, and more than two
1515
tildes are not accepted anymore. This brings us in line with what
1616
GitHub actually does, which is a bit underspecified (#267)
17+
- The autolink extension now handles source spans correctly (#209)
1718

1819
## [0.19.0] - 2022-06-02
1920
### Added

commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
package org.commonmark.ext.autolink.internal;
22

3-
import org.commonmark.node.AbstractVisitor;
4-
import org.commonmark.node.Link;
5-
import org.commonmark.node.Node;
6-
import org.commonmark.node.Text;
3+
import org.commonmark.node.*;
74
import org.commonmark.parser.PostProcessor;
85
import org.nibor.autolink.LinkExtractor;
96
import org.nibor.autolink.LinkSpan;
107
import org.nibor.autolink.LinkType;
118
import org.nibor.autolink.Span;
129

13-
import java.util.EnumSet;
10+
import java.util.*;
1411

1512
public class AutolinkPostProcessor implements PostProcessor {
1613

@@ -25,26 +22,49 @@ public Node process(Node node) {
2522
return node;
2623
}
2724

28-
private void linkify(Text textNode) {
29-
String literal = textNode.getLiteral();
25+
private void linkify(Text originalTextNode) {
26+
String literal = originalTextNode.getLiteral();
3027

31-
Node lastNode = textNode;
28+
Node lastNode = originalTextNode;
29+
List<SourceSpan> sourceSpans = originalTextNode.getSourceSpans();
30+
SourceSpan sourceSpan = sourceSpans.size() == 1 ? sourceSpans.get(0) : null;
3231

33-
for (Span span : linkExtractor.extractSpans(literal)) {
34-
String text = literal.substring(span.getBeginIndex(), span.getEndIndex());
32+
Iterator<Span> spans = linkExtractor.extractSpans(literal).iterator();
33+
while (spans.hasNext()) {
34+
Span span = spans.next();
35+
36+
if (lastNode == originalTextNode && !spans.hasNext() && !(span instanceof LinkSpan)) {
37+
// Didn't find any links, don't bother changing existing node.
38+
return;
39+
}
40+
41+
Text textNode = createTextNode(literal, span, sourceSpan);
3542
if (span instanceof LinkSpan) {
36-
String destination = getDestination((LinkSpan) span, text);
37-
Text contentNode = new Text(text);
43+
String destination = getDestination((LinkSpan) span, textNode.getLiteral());
44+
3845
Link linkNode = new Link(destination, null);
39-
linkNode.appendChild(contentNode);
46+
linkNode.appendChild(textNode);
47+
linkNode.setSourceSpans(textNode.getSourceSpans());
4048
lastNode = insertNode(linkNode, lastNode);
4149
} else {
42-
lastNode = insertNode(new Text(text), lastNode);
50+
lastNode = insertNode(textNode, lastNode);
4351
}
4452
}
4553

4654
// Original node no longer needed
47-
textNode.unlink();
55+
originalTextNode.unlink();
56+
}
57+
58+
private static Text createTextNode(String literal, Span span, SourceSpan sourceSpan) {
59+
int beginIndex = span.getBeginIndex();
60+
int endIndex = span.getEndIndex();
61+
String text = literal.substring(beginIndex, endIndex);
62+
Text textNode = new Text(text);
63+
if (sourceSpan != null) {
64+
int length = endIndex - beginIndex;
65+
textNode.addSourceSpan(SourceSpan.of(sourceSpan.getLineIndex(), beginIndex, length));
66+
}
67+
return textNode;
4868
}
4969

5070
private static String getDestination(LinkSpan linkSpan, String linkText) {

commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package org.commonmark.ext.autolink;
22

33
import org.commonmark.Extension;
4+
import org.commonmark.node.*;
5+
import org.commonmark.parser.IncludeSourceSpans;
46
import org.commonmark.parser.Parser;
57
import org.commonmark.renderer.html.HtmlRenderer;
68
import org.commonmark.testutil.RenderingTestCase;
79
import org.junit.Test;
810

11+
import java.util.Arrays;
912
import java.util.Collections;
1013
import java.util.Set;
1114

15+
import static org.hamcrest.CoreMatchers.instanceOf;
16+
import static org.junit.Assert.assertEquals;
17+
import static org.junit.Assert.assertTrue;
18+
1219
public class AutolinkTest extends RenderingTestCase {
1320

1421
private static final Set<Extension> EXTENSIONS = Collections.singleton(AutolinkExtension.create());
@@ -53,6 +60,59 @@ public void dontLinkTextWithinLinks() {
5360
"<p><a href=\"http://example.com\">http://example.com</a></p>\n");
5461
}
5562

63+
@Test
64+
public void sourceSpans() {
65+
Parser parser = Parser.builder()
66+
.extensions(EXTENSIONS)
67+
.includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES)
68+
.build();
69+
Node document = parser.parse("abc\n" +
70+
"http://example.com/one\n" +
71+
"def http://example.com/two\n" +
72+
"ghi http://example.com/three jkl");
73+
74+
Paragraph paragraph = (Paragraph) document.getFirstChild();
75+
Text abc = (Text) paragraph.getFirstChild();
76+
assertEquals(Arrays.asList(SourceSpan.of(0, 0, 3)),
77+
abc.getSourceSpans());
78+
79+
assertTrue(abc.getNext() instanceof SoftLineBreak);
80+
81+
Link one = (Link) abc.getNext().getNext();
82+
assertEquals("http://example.com/one", one.getDestination());
83+
assertEquals(Arrays.asList(SourceSpan.of(1, 0, 22)),
84+
one.getSourceSpans());
85+
86+
assertTrue(one.getNext() instanceof SoftLineBreak);
87+
88+
Text def = (Text) one.getNext().getNext();
89+
assertEquals("def ", def.getLiteral());
90+
assertEquals(Arrays.asList(SourceSpan.of(2, 0, 4)),
91+
def.getSourceSpans());
92+
93+
Link two = (Link) def.getNext();
94+
assertEquals("http://example.com/two", two.getDestination());
95+
assertEquals(Arrays.asList(SourceSpan.of(2, 4, 22)),
96+
two.getSourceSpans());
97+
98+
assertTrue(two.getNext() instanceof SoftLineBreak);
99+
100+
Text ghi = (Text) two.getNext().getNext();
101+
assertEquals("ghi ", ghi.getLiteral());
102+
assertEquals(Arrays.asList(SourceSpan.of(3, 0, 4)),
103+
ghi.getSourceSpans());
104+
105+
Link three = (Link) ghi.getNext();
106+
assertEquals("http://example.com/three", three.getDestination());
107+
assertEquals(Arrays.asList(SourceSpan.of(3, 4, 24)),
108+
three.getSourceSpans());
109+
110+
Text jkl = (Text) three.getNext();
111+
assertEquals(" jkl", jkl.getLiteral());
112+
assertEquals(Arrays.asList(SourceSpan.of(3, 28, 4)),
113+
jkl.getSourceSpans());
114+
}
115+
56116
@Override
57117
protected String render(String source) {
58118
return RENDERER.render(PARSER.parse(source));

0 commit comments

Comments
 (0)