diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java index 33a7c321a..76165b2c0 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java @@ -61,6 +61,11 @@ private static Node insertNode(Node node, Node insertAfterNode) { private class AutolinkVisitor extends AbstractVisitor { int inLink = 0; + @Override + public void visit(AutoLink link) { + visit((Link)link); + } + @Override public void visit(Link link) { inLink++; diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java index b4ecfce2f..1d89594de 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java @@ -1,9 +1,29 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.node.CustomNode; +import org.commonmark.node.Delimited; /** * A strikethrough node containing text and other inline nodes nodes as children. */ -public class Strikethrough extends CustomNode { +public class Strikethrough extends CustomNode implements Delimited { + + private final char delimiterChar; + private final int delimiterCount; + + public Strikethrough(char delimiterChar, int delimiterCount) { + this.delimiterChar = delimiterChar; + this.delimiterCount = delimiterCount; + } + + @Override + public char getDelimiterChar() { + return delimiterChar; + } + + @Override + public int getDelimiterCount() { + return delimiterCount; + } + } diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java index 14a847e2d..3e9b71efc 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java @@ -39,7 +39,7 @@ public void process(Text opener, Text closer, int delimiterCount) { } // Normal case, wrap nodes between delimiters in strikethrough. - Node strikethrough = new Strikethrough(); + Node strikethrough = new Strikethrough(getDelimiterChar(), delimiterCount); Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index a18928640..352c354f1 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -271,6 +271,11 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { renderCodeBlock(indentedCodeBlock.getLiteral(), getAttrs(indentedCodeBlock)); } + @Override + public void visit(AutoLink link) { + visit((Link)link); + } + @Override public void visit(Link link) { Map attrs = new LinkedHashMap<>(); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index ea145c595..07d7c25f4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -237,6 +237,7 @@ public int parseReference(String s) { if (!referenceMap.containsKey(normalizedLabel)) { Link link = new Link(dest, title); + link.setLabel(normalizedLabel); referenceMap.put(normalizedLabel, link); } return this.pos - startPos; @@ -565,6 +566,11 @@ private boolean parseCloseBracket() { String title = null; boolean isLinkOrImage = false; + // If we are parsing a collapsed or shortcut link/image, this + // definition reference will be populated from the cached + // referenceMap. + Reference definition = null; + // Inline link? if (this.peek() == '(') { this.pos++; @@ -603,6 +609,7 @@ private boolean parseCloseBracket() { if (ref != null) { Link link = referenceMap.get(Escaping.normalizeReference(ref)); if (link != null) { + definition = link; dest = link.getDestination(); title = link.getTitle(); isLinkOrImage = true; @@ -613,7 +620,11 @@ private boolean parseCloseBracket() { if (isLinkOrImage) { // If we got here, open is a potential opener boolean isImage = opener.delimiterChar == '!'; - Node linkOrImage = isImage ? new Image(dest, title) : new Link(dest, title); + Reference linkOrImage = isImage ? new Image(dest, title) : new Link(dest, title); + if (definition != null) { + linkOrImage.setLabel(definition.getLabel()); + linkOrImage.setDefinition(definition); + } // Flush text now. We don't need to worry about combining it with adjacent text nodes, as we'll wrap it in a // link or image node. @@ -705,13 +716,13 @@ private boolean parseAutolink() { String m; if ((m = this.match(EMAIL_AUTOLINK)) != null) { String dest = m.substring(1, m.length() - 1); - Link node = new Link("mailto:" + dest, null); + Link node = new AutoLink("mailto:" + dest, null); node.appendChild(new Text(dest)); appendNode(node); return true; } else if ((m = this.match(AUTOLINK)) != null) { String dest = m.substring(1, m.length() - 1); - Link node = new Link(dest, null); + Link node = new AutoLink(dest, null); node.appendChild(new Text(dest)); appendNode(node); return true; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 3e83dd7a9..5532c816a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -1,6 +1,7 @@ package org.commonmark.internal.inline; import org.commonmark.parser.DelimiterProcessor; +import org.commonmark.node.Delimited; import org.commonmark.node.Emphasis; import org.commonmark.node.Node; import org.commonmark.node.StrongEmphasis; @@ -26,7 +27,9 @@ public int getDelimiterUse(int openerCount, int closerCount) { @Override public void process(Text opener, Text closer, int delimiterUse) { - Node emphasis = delimiterUse == 1 ? new Emphasis() : new StrongEmphasis(); + Node emphasis = delimiterUse == 1 + ? new Emphasis(getDelimiterChar(), delimiterUse) + : new StrongEmphasis(getDelimiterChar(), delimiterUse); Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Debugging.java b/commonmark/src/main/java/org/commonmark/internal/util/Debugging.java new file mode 100644 index 000000000..d1e863d5b --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/util/Debugging.java @@ -0,0 +1,58 @@ +package org.commonmark.internal.util; + +import java.util.Stack; +import org.commonmark.node.Node; + +public class Debugging { + + /** + * Print a tree-representation of the given node and its children. + */ + public static String toStringTree(Node node) { + StringBuilder b = new StringBuilder(); + Stack stack = new Stack(); + + visit(b, stack, node); + + return b.toString(); + } + + private static void visit(StringBuilder b, Stack stack, Node node) { + + int sz = stack.size(); + for (int i = 0; i < sz; i++) { + b.append('.'); + } + b.append(node.toString()); + b.append('\n'); + + Node current = node.getFirstChild(); + while (current != null) { + stack.push(current); + visit(b, stack, stack.peek()); + stack.pop(); + current = current.getNext(); + } + + } + + /** + * Log a simple message. This is not a substitute for a logging + * framework. + */ + public static void log(String msg) { + System.out.println("commonmark-java: " + msg); + } + + /** + * Print a stacktrace with the given message. + */ + public static void stacktrace(String msg) { + try { + throw new Exception(msg); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + +} diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index c7ecbe150..de7f8d467 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -78,6 +78,11 @@ public void visit(Link link) { visitChildren(link); } + @Override + public void visit(AutoLink link) { + visitChildren(link); + } + @Override public void visit(ListItem listItem) { visitChildren(listItem); diff --git a/commonmark/src/main/java/org/commonmark/node/AutoLink.java b/commonmark/src/main/java/org/commonmark/node/AutoLink.java new file mode 100644 index 000000000..521661d64 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/AutoLink.java @@ -0,0 +1,17 @@ +package org.commonmark.node; + +public class AutoLink extends Link { + + public AutoLink() { + super(); + } + + public AutoLink(String destination, String title) { + super(destination, title); + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/commonmark/src/main/java/org/commonmark/node/Delimited.java b/commonmark/src/main/java/org/commonmark/node/Delimited.java new file mode 100644 index 000000000..dc65a772a --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/Delimited.java @@ -0,0 +1,9 @@ +package org.commonmark.node; + +public interface Delimited { + + char getDelimiterChar(); + + int getDelimiterCount(); + +} diff --git a/commonmark/src/main/java/org/commonmark/node/Emphasis.java b/commonmark/src/main/java/org/commonmark/node/Emphasis.java index 3f7c86051..39069de04 100644 --- a/commonmark/src/main/java/org/commonmark/node/Emphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/Emphasis.java @@ -1,6 +1,24 @@ package org.commonmark.node; -public class Emphasis extends Node { +public class Emphasis extends Node implements Delimited { + + private final char delimiterChar; + private final int delimiterCount; + + public Emphasis(char delimiterChar, int delimiterCount) { + this.delimiterChar = delimiterChar; + this.delimiterCount = delimiterCount; + } + + @Override + public char getDelimiterChar() { + return delimiterChar; + } + + @Override + public int getDelimiterCount() { + return delimiterCount; + } @Override public void accept(Visitor visitor) { diff --git a/commonmark/src/main/java/org/commonmark/node/Image.java b/commonmark/src/main/java/org/commonmark/node/Image.java index 63481773a..b332a301c 100644 --- a/commonmark/src/main/java/org/commonmark/node/Image.java +++ b/commonmark/src/main/java/org/commonmark/node/Image.java @@ -1,41 +1,16 @@ package org.commonmark.node; -public class Image extends Node { - - private String destination; - private String title; +public class Image extends Reference { public Image() { } public Image(String destination, String title) { - this.destination = destination; - this.title = title; + super(destination, title); } @Override public void accept(Visitor visitor) { visitor.visit(this); } - - public String getDestination() { - return destination; - } - - public void setDestination(String destination) { - this.destination = destination; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - @Override - protected String toStringAttributes() { - return "destination=" + destination + ", title=" + title; - } } diff --git a/commonmark/src/main/java/org/commonmark/node/Link.java b/commonmark/src/main/java/org/commonmark/node/Link.java index b4794253e..87f83942a 100644 --- a/commonmark/src/main/java/org/commonmark/node/Link.java +++ b/commonmark/src/main/java/org/commonmark/node/Link.java @@ -1,41 +1,16 @@ package org.commonmark.node; -public class Link extends Node { - - private String destination; - private String title; +public class Link extends Reference { public Link() { } public Link(String destination, String title) { - this.destination = destination; - this.title = title; + super(destination, title); } @Override public void accept(Visitor visitor) { visitor.visit(this); } - - public String getDestination() { - return destination; - } - - public void setDestination(String destination) { - this.destination = destination; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - @Override - protected String toStringAttributes() { - return "destination=" + destination + ", title=" + title; - } } diff --git a/commonmark/src/main/java/org/commonmark/node/Reference.java b/commonmark/src/main/java/org/commonmark/node/Reference.java new file mode 100644 index 000000000..05438bd6b --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/Reference.java @@ -0,0 +1,63 @@ +package org.commonmark.node; + +public abstract class Reference extends Node { + + protected Reference definition; + protected String destination; + protected String title; + protected String label; + + public Reference() { + } + + public Reference(String destination, String title) { + this.destination = destination; + this.title = title; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + /** + * If this entity represents a collapsed or shortcut link, the + * getReference method should return the link + * reference definition that the label matches to. + */ + public Reference getDefinition() { + return definition; + } + + public void setDefinition(Reference definition) { + this.definition = definition; + } + + @Override + protected String toStringAttributes() { + return "destination=" + destination + + ", title=" + title + + ", label=" + label + + ", definition=" + definition; + } + +} diff --git a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java index 26ab4fbf5..eeb9e9343 100644 --- a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java @@ -1,9 +1,28 @@ package org.commonmark.node; -public class StrongEmphasis extends Node { +public class StrongEmphasis extends Node implements Delimited { + + private char delimiterChar; + private int delimiterCount; + + public StrongEmphasis(char delimiterChar, int delimiterCount) { + this.delimiterChar = delimiterChar; + this.delimiterCount = delimiterCount; + } + + @Override + public char getDelimiterChar() { + return delimiterChar; + } + + @Override + public int getDelimiterCount() { + return delimiterCount; + } @Override public void accept(Visitor visitor) { visitor.visit(this); } + } diff --git a/commonmark/src/main/java/org/commonmark/node/Visitor.java b/commonmark/src/main/java/org/commonmark/node/Visitor.java index 3f0b4fb6c..59591270e 100644 --- a/commonmark/src/main/java/org/commonmark/node/Visitor.java +++ b/commonmark/src/main/java/org/commonmark/node/Visitor.java @@ -35,6 +35,8 @@ public interface Visitor { void visit(Link link); + void visit(AutoLink link); + void visit(ListItem listItem); void visit(OrderedList orderedList); diff --git a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java new file mode 100644 index 000000000..813192bdc --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java @@ -0,0 +1,69 @@ +package org.commonmark.test; + +import org.commonmark.node.Node; +import org.commonmark.node.Delimited; +import org.commonmark.node.Emphasis; +import org.commonmark.node.StrongEmphasis; +import org.commonmark.node.Visitor; +import org.commonmark.node.AbstractVisitor; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.commonmark.internal.util.Debugging.log; +import static org.commonmark.internal.util.Debugging.toStringTree; + +public class DelimitedTest { + + @Test + public void one() { + + final Parser.Builder builder = Parser.builder(); + final Parser parser = builder.build(); + final Node document = parser.parse(getText()); + final java.util.List list = new java.util.ArrayList(); + + final Visitor visitor = new AbstractVisitor() { + @Override + public void visit(Emphasis node) { + list.add(node); + } + + @Override + public void visit(StrongEmphasis node) { + list.add(node); + } + }; + + //log(toStringTree(document)); + document.accept(visitor); + + assertEquals(4, list.size()); + + Delimited emphasis = list.get(0); + Delimited strong = list.get(1); + Delimited important = list.get(2); + Delimited critical = list.get(3); + + assertEquals('*', emphasis.getDelimiterChar()); + assertEquals('*', strong.getDelimiterChar()); + assertEquals('_', important.getDelimiterChar()); + assertEquals('_', critical.getDelimiterChar()); + + assertEquals(1, emphasis.getDelimiterCount()); + assertEquals(2, strong.getDelimiterCount()); + assertEquals(1, important.getDelimiterCount()); + assertEquals(2, critical.getDelimiterCount()); + + } + + String getText() { + String s = ""; + s += "* *emphasis* \n"; + s += "* **strong** \n"; + s += "* _important_ \n"; + s += "* __CRITICAL__ \n"; + return s; + } + +} diff --git a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionTest.java b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionTest.java new file mode 100644 index 000000000..ac55eed57 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionTest.java @@ -0,0 +1,64 @@ +package org.commonmark.test; + +import org.commonmark.node.Node; +import org.commonmark.node.Link; +import org.commonmark.node.AutoLink; +import org.commonmark.node.Visitor; +import org.commonmark.node.AbstractVisitor; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.commonmark.internal.util.Debugging.log; +import static org.commonmark.internal.util.Debugging.toStringTree; + +public class LinkReferenceDefinitionTest { + + @Test + public void one() { + + final Parser.Builder builder = Parser.builder(); + final Parser parser = builder.build(); + final Node document = parser.parse(getText()); + final java.util.List links = new java.util.ArrayList(); + + final Visitor visitor = new AbstractVisitor() { + @Override + public void visit(Link node) { + links.add(node); + } + + @Override + public void visit(AutoLink node) { + links.add(node); + } + }; + + //log(toStringTree(document)); + document.accept(visitor); + + assertEquals(3, links.size()); + + Link one = links.get(0); + Link two = links.get(1); + Link auto = links.get(2); + + assertEquals("1", one.getDefinition().getLabel()); + assertEquals("2", two.getDefinition().getLabel()); + assertEquals(AutoLink.class, auto.getClass()); + } + + String getText() { + String s = ""; + s += "* this is collapsed link: [one][1]\n"; + s += "* this is shortcut link: [2]\n"; + s += "* this is an AutoLink: ...\n"; + s += "* not to be confused with the *Autolink plugin*,"; + s += " that is not picked up in this test: http://foo.com\n"; + s += "\n"; + s += "[1]: http://1.com\n"; + s += "[2]: http://2.com\n"; + return s; + } + +}