Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html),
with the exception that 0.x versions can break between minor versions.

## Unreleased
### Added
- Autolink extension: Now supports configuration of different link types that
should be recognized and converted to links. See `AutolinkExtension#builder`

| Type | Default? | Description |
|---------|----------|--------------------------------------------------------|
| `URL` | Yes | URL with a protocol such as `https://example.com` |
| `EMAIL` | Yes | Email address such as `foo@example.com` |
| `WWW` | Yes | Address beginning with `www` such as `www.example.com` |

Note that this changes the behavior of `AutolinkExtension.create()` to now also
include `WWW` links by default. To re-enable the previous behavior, use:

```java
AutolinkExtension.builder().linkTypes(AutolinkType.URL, AutolinkType.EMAIL).build();
```

## [0.26.0] - 2025-09-13
### Changed
- A `LinkProcessor` using `replaceWith` now also stops outer links from being
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.commonmark.ext.autolink;

import java.util.EnumSet;
import java.util.Set;

import org.commonmark.Extension;
import org.commonmark.ext.autolink.internal.AutolinkPostProcessor;
import org.commonmark.parser.Parser;
Expand All @@ -18,16 +21,71 @@
*/
public class AutolinkExtension implements Parser.ParserExtension {

private AutolinkExtension() {
private final Set<AutolinkType> linkTypes;

private AutolinkExtension(Builder builder) {
this.linkTypes = builder.linkTypes;
}

/**
* @return the extension with default options
*/
public static Extension create() {
return new AutolinkExtension();
return builder().build();
}

/**
* @return a builder to configure the behavior of the extension.
*/
public static Builder builder() {
return new Builder();
}

@Override
public void extend(Parser.Builder parserBuilder) {
parserBuilder.postProcessor(new AutolinkPostProcessor());
parserBuilder.postProcessor(new AutolinkPostProcessor(linkTypes));
}

public static class Builder {

private Set<AutolinkType> linkTypes = EnumSet.allOf(AutolinkType.class);

/**
* @param linkTypes the link types that should be converted. By default,
* all {@link AutolinkType}s are converted.
* @return {@code this}
*/
public Builder linkTypes(AutolinkType... linkTypes) {
if (linkTypes == null) {
throw new NullPointerException("linkTypes must not be null");
}

return this.linkTypes(Set.of(linkTypes));
}

/**
* @param linkTypes the link types that should be converted. By default,
* all {@link AutolinkType}s are converted.
* @return {@code this}
*/
public Builder linkTypes(Set<AutolinkType> linkTypes) {
if (linkTypes == null) {
throw new NullPointerException("linkTypes must not be null");
}

if (linkTypes.isEmpty()) {
throw new IllegalArgumentException("linkTypes must not be empty");
}

this.linkTypes = EnumSet.copyOf(linkTypes);
return this;
}

/**
* @return a configured extension
*/
public Extension build() {
return new AutolinkExtension(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.commonmark.ext.autolink;

/**
* The types of strings that can be automatically turned into links.
*/
public enum AutolinkType {
/**
* URL such as {@code http://example.com}
*/
URL,
/**
* Email address such as {@code foo@example.com}
*/
EMAIL,
/**
* URL such as {@code www.example.com}
*/
WWW
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.commonmark.ext.autolink.internal;

import org.commonmark.ext.autolink.AutolinkType;
import org.commonmark.node.*;
import org.commonmark.parser.PostProcessor;
import org.nibor.autolink.LinkExtractor;
Expand All @@ -11,9 +12,36 @@

public class AutolinkPostProcessor implements PostProcessor {

private LinkExtractor linkExtractor = LinkExtractor.builder()
.linkTypes(EnumSet.of(LinkType.URL, LinkType.EMAIL))
.build();
private final LinkExtractor linkExtractor;

public AutolinkPostProcessor(Set<AutolinkType> linkTypes) {
if (linkTypes == null) {
throw new NullPointerException("linkTypes must not be null");
}

if (linkTypes.isEmpty()) {
throw new IllegalArgumentException("linkTypes must not be empty");
}

var types = EnumSet.noneOf(LinkType.class);
for (AutolinkType linkType : linkTypes) {
switch (linkType) {
case URL:
types.add(LinkType.URL);
break;
case EMAIL:
types.add(LinkType.EMAIL);
break;
case WWW:
types.add(LinkType.WWW);
break;
}
}

this.linkExtractor = LinkExtractor.builder()
.linkTypes(types)
.build();
}

@Override
public Node process(Node node) {
Expand Down Expand Up @@ -67,8 +95,13 @@ private static Text createTextNode(String literal, Span span, SourceSpan sourceS
}

private static String getDestination(LinkSpan linkSpan, String linkText) {
if (linkSpan.getType() == LinkType.EMAIL) {
var type = linkSpan.getType();

if (type == LinkType.EMAIL) {
return "mailto:" + linkText;
} else if (type == LinkType.WWW) {
// Use http instead of https (see https://github.github.com/gfm/#extended-www-autolink)
return "http://" + linkText;
} else {
return linkText;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ public class AutolinkTest extends RenderingTestCase {
private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build();
private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build();

private static final Set<Extension> NO_WWW_EXTENSIONS = Set.of(AutolinkExtension.builder()
.linkTypes(AutolinkType.URL, AutolinkType.EMAIL)
.build());
private static final Parser NO_WWW_PARSER = Parser.builder().extensions(NO_WWW_EXTENSIONS).build();
private static final HtmlRenderer NO_WWW_RENDERER = HtmlRenderer.builder().extensions(NO_WWW_EXTENSIONS).build();

@Test
public void oneTextNode() {
assertRendering("foo http://one.org/ bar http://two.org/",
Expand Down Expand Up @@ -57,6 +63,18 @@ public void dontLinkTextWithinLinks() {
"<p><a href=\"http://example.com\">http://example.com</a></p>\n");
}

@Test
public void wwwLinks() {
assertRendering("www.example.com",
"<p><a href=\"http://www.example.com\">www.example.com</a></p>\n");
}

@Test
public void noWwwLinks() {
String html = NO_WWW_RENDERER.render(NO_WWW_PARSER.parse("www.example.com"));
assertThat(html).isEqualTo("<p>www.example.com</p>\n");
}

@Test
public void sourceSpans() {
Parser parser = Parser.builder()
Expand Down