diff --git a/CHANGELOG.md b/CHANGELOG.md index c07bf665c..65cc40e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ 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 +### Changed +- Pass original instead of normalized label to `InlineParserContext` for lookup (#204). + This allows custom contexts to change the lookup logic and have access to the original + label content. + In case you have a custom implementation of `InlineParserContext`, you might need to adjust + it to do normalization. + ## [0.17.1] - 2021-02-03 ### Fixed - Fix emphasis surrounded by non-BMP punctuation/whitespace characters diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 179ad1140..ed8ae7412 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -68,7 +68,7 @@ public class DocumentParser implements ParserState { private final List delimiterProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; - private final Map definitions = new LinkedHashMap<>(); + private final LinkReferenceDefinitions definitions = new LinkReferenceDefinitions(); private final List openBlockParsers = new ArrayList<>(); private final List allBlockParsers = new ArrayList<>(); @@ -460,11 +460,7 @@ private void addDefinitionsFrom(ParagraphParser paragraphParser) { // Add nodes into document before paragraph. paragraphParser.getBlock().insertBefore(definition); - String label = definition.getLabel(); - // spec: When there are multiple matching link reference definitions, the first is used - if (!definitions.containsKey(label)) { - definitions.put(label, definition); - } + definitions.add(definition); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index bff085ad8..f485614d5 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -10,10 +10,10 @@ public class InlineParserContextImpl implements InlineParserContext { private final List delimiterProcessors; - private final Map linkReferenceDefinitions; + private final LinkReferenceDefinitions linkReferenceDefinitions; public InlineParserContextImpl(List delimiterProcessors, - Map linkReferenceDefinitions) { + LinkReferenceDefinitions linkReferenceDefinitions) { this.delimiterProcessors = delimiterProcessors; this.linkReferenceDefinitions = linkReferenceDefinitions; } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index c90e61fb2..c14b9e885 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -323,8 +323,7 @@ private Node parseCloseBracket() { } if (ref != null) { - String label = Escaping.normalizeLabelContent(ref); - LinkReferenceDefinition definition = context.getLinkReferenceDefinition(label); + LinkReferenceDefinition definition = context.getLinkReferenceDefinition(ref); if (definition != null) { dest = definition.getDestination(); title = definition.getTitle(); diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java new file mode 100644 index 000000000..8fbdb982a --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java @@ -0,0 +1,27 @@ +package org.commonmark.internal; + +import org.commonmark.internal.util.Escaping; +import org.commonmark.node.LinkReferenceDefinition; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class LinkReferenceDefinitions { + + // LinkedHashMap for determinism and to preserve document order + private final Map definitions = new LinkedHashMap<>(); + + public void add(LinkReferenceDefinition definition) { + String normalizedLabel = Escaping.normalizeLabelContent(definition.getLabel()); + + // spec: When there are multiple matching link reference definitions, the first is used + if (!definitions.containsKey(normalizedLabel)) { + definitions.put(normalizedLabel, definition); + } + } + + public LinkReferenceDefinition get(String label) { + String normalizedLabel = Escaping.normalizeLabelContent(label); + return definitions.get(normalizedLabel); + } +} diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 467742e2c..dae96e2c8 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -17,6 +17,8 @@ public interface InlineParserContext { /** * Look up a {@link LinkReferenceDefinition} for a given label. + *

+ * Note that the label is not normalized yet; implementations are responsible for normalizing before lookup. * * @param label the link label to look up * @return the definition if one exists, {@code null} otherwise diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index ae1f700b8..63cebb2eb 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -4,23 +4,14 @@ import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.node.Block; -import org.commonmark.node.BlockQuote; -import org.commonmark.node.FencedCodeBlock; -import org.commonmark.node.Heading; -import org.commonmark.node.HtmlBlock; -import org.commonmark.node.IndentedCodeBlock; -import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.node.ListBlock; -import org.commonmark.node.Node; -import org.commonmark.node.ThematicBreak; +import org.commonmark.internal.LinkReferenceDefinitions; +import org.commonmark.node.*; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -51,8 +42,7 @@ private Parser(Builder builder) { // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. - this.inlineParserFactory.create(new InlineParserContextImpl(delimiterProcessors, - Collections.emptyMap())); + this.inlineParserFactory.create(new InlineParserContextImpl(delimiterProcessors, new LinkReferenceDefinitions())); } /** @@ -179,7 +169,7 @@ public Builder extensions(Iterable extensions) { * * * @param enabledBlockTypes A list of block nodes the parser will parse. - * If this list is empty, the parser will not recognize any CommonMark core features. + * If this list is empty, the parser will not recognize any CommonMark core features. * @return {@code this} */ public Builder enabledBlockTypes(Set> enabledBlockTypes) { diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java new file mode 100644 index 000000000..b7d083df3 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -0,0 +1,59 @@ +package org.commonmark.test; + +import org.commonmark.internal.InlineParserImpl; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.InlineParserFactory; +import org.commonmark.parser.Parser; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.renderer.html.HtmlRenderer; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class InlineParserContextTest { + + @Test + public void labelShouldBeOriginalNotNormalized() { + CapturingInlineParserFactory inlineParserFactory = new CapturingInlineParserFactory(); + + Parser parser = Parser.builder().inlineParserFactory(inlineParserFactory).build(); + String input = "[link with special label][FooBarBaz]\n\n[foobarbaz]: /url"; + + String rendered = HtmlRenderer.builder().build().render(parser.parse(input)); + + // Lookup should pass original label to context + assertEquals(Collections.singletonList("FooBarBaz"), inlineParserFactory.lookups); + + // Context should normalize label for finding reference + assertEquals("

link with special label

\n", rendered); + } + + static class CapturingInlineParserFactory implements InlineParserFactory { + + private List lookups = new ArrayList<>(); + + @Override + public InlineParser create(final InlineParserContext inlineParserContext) { + InlineParserContext wrappedContext = new InlineParserContext() { + @Override + public List getCustomDelimiterProcessors() { + return inlineParserContext.getCustomDelimiterProcessors(); + } + + @Override + public LinkReferenceDefinition getLinkReferenceDefinition(String label) { + lookups.add(label); + return inlineParserContext.getLinkReferenceDefinition(label); + } + }; + + return new InlineParserImpl(wrappedContext); + } + } +}