diff --git a/make/autoconf/spec.gmk.in b/make/autoconf/spec.gmk.in index 9448cb9b7e885..9fe930bc824a3 100644 --- a/make/autoconf/spec.gmk.in +++ b/make/autoconf/spec.gmk.in @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -680,7 +680,7 @@ BUILD_JAR=@FIXPATH@ $(BUILD_JDK)/bin/jar DOCS_REFERENCE_JAVADOC := @DOCS_REFERENCE_JAVADOC@ # Interim langtools modules and arguments -INTERIM_LANGTOOLS_BASE_MODULES := java.compiler jdk.compiler jdk.javadoc +INTERIM_LANGTOOLS_BASE_MODULES := java.compiler jdk.compiler jdk.javadoc jdk.internal.md INTERIM_LANGTOOLS_MODULES := $(addsuffix .interim, $(INTERIM_LANGTOOLS_BASE_MODULES)) INTERIM_LANGTOOLS_ADD_EXPORTS := \ --add-exports java.base/sun.reflect.annotation=jdk.compiler.interim \ diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java index 9d457a739da51..35f8554078019 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -153,6 +153,14 @@ enum Kind { */ LITERAL("literal"), + /** + * Used for instances of {@link MarkdownTree} + * representing a fragment of Markdown content. + * + * @since 21 + */ + MARKDOWN, + /** * Used for instances of {@link ParamTree} * representing an {@code @param} tag. diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java index 7bfaa7acd4626..5b6acac4c1855 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -207,6 +207,21 @@ default R visitIndex(IndexTree node, P p) { */ R visitLiteral(LiteralTree node, P p); + /** + * Visits a {@code MarkdownTree} node. + * @param node the node being visited + * @param p a parameter value + * @return a result value + * + * @implSpec Visits the provided {@code MarkdownTree} node + * by calling {@code visitOther(node, p)}. + * + * @since 21 + */ + default R visitMarkdown(MarkdownTree node, P p) { + return visitOther(node, p); + } + /** * Visits a {@code ParamTree} node. * @param node the node being visited diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/InheritDocTree.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/InheritDocTree.java index 304bde3908f9b..a9dd9d84a997f 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/doctree/InheritDocTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/InheritDocTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,11 @@ /** * A tree node for an {@code @inheritDoc} inline tag. * + * @apiNote + * There is no requirement that the comment containing the tag and the comment + * containing the inherited documentation should either be both Markdown comments + * or both normal (not Markdown) comments. + * *
  *    {@inheritDoc}
  * 
diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/MarkdownTree.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/MarkdownTree.java new file mode 100644 index 0000000000000..36b8e450ad20a --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/MarkdownTree.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.doctree; + +/** + * A tree node for a fragment of Markdown content. + * + *

+ * The content may contain plain text, entities and HTML elements, + * all represented directly in the text of the content, + * but not {@linkplain InlineTagTree inline tags}. + * + * @apiNote + * {@code MarkdownTree} nodes will typically exist in a list of + * {@code DocTree} nodes, along with other kinds of {@code DocTree} + * nodes, such as for inline tags. When processing any such list, + * any non-Markdown nodes will be processed recursively first, and then + * treated as opaque objects within the remaining stream of Markdown nodes. + * Thus, the content of any non-Markdown nodes will not affect how the + * Markdown nodes will be processed. + * + * Note: malformed entities and HTML elements will typically be + * processed as literal text, with no warnings or errors being + * reported. + * + * @since 21 + */ +public interface MarkdownTree extends DocTree { + /** + * {@return the content} + */ + String getContent(); +} diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/package-info.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/package-info.java index 30832c062ff7e..410f1ef022550 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/doctree/package-info.java +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,17 @@ * Provides interfaces to represent documentation comments as abstract syntax * trees (AST). * + *

Markdown

+ * + * Various classes defined in this package contain a list of {@link DocTree} nodes, + * which may represent {@linkplain TextTree plain text}, {@linkplain EntityTree entities}, + * {@linkplain InlineTagTree inline} and {@linkplain BlockTagTree block} tags, + * {@linkplain StartElementTree start} and {@linkplain EndElementTree end} HTML elements, + * and {@linkplain MarkdownTree Markdown content}. In such lists, if there is at least + * one Markdown node, the entire list will be treated as Markdown, although all + * non-Markdown nodes will be treated as opaque objects and will not be parsed as + * part of the Markdown content. + * * @author Jonathan Gibbons * @since 1.8 * diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java index 78e973d23d45f..8d330589b555f 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,6 +49,7 @@ import com.sun.source.doctree.InheritDocTree; import com.sun.source.doctree.LinkTree; import com.sun.source.doctree.LiteralTree; +import com.sun.source.doctree.MarkdownTree; import com.sun.source.doctree.ParamTree; import com.sun.source.doctree.ProvidesTree; import com.sun.source.doctree.ReferenceTree; @@ -235,6 +236,15 @@ DocCommentTree newDocCommentTree(List fullBody, */ LiteralTree newLiteralTree(TextTree text); + /** + * Creates a new {@code MarkdownTree} object, to represent a fragment of Markdown content. + * @param code the code + * @return a {@code MarkdownTree} object + * + * @since 21 + */ + MarkdownTree newMarkdownTree(String code); + /** * Creates a new {@code ParamTree} object, to represent a {@code @param} tag. * @param isTypeParameter {@code true} if this is a type parameter, and {@code false} otherwise diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java index bd5a64324caef..f0172f18617a0 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -365,6 +365,22 @@ public R visitLiteral(LiteralTree node, P p) { return scan(node.getBody(), p); } + /** + * {@inheritDoc} + * + * @implSpec This implementation returns {@code null}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of scanning + * + * @since 21 + */ + @Override + public R visitMarkdown(MarkdownTree node, P p) { + return null; + } + /** * {@inheritDoc} * diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java index b3a27f9e6b424..c0f0a3cf612ae 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -320,6 +320,22 @@ public R visitLiteral(LiteralTree node, P p) { return defaultAction(node, p); } + /** + * {@inheritDoc} + * + * @implSpec This implementation calls {@code defaultAction}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of {@code defaultAction} + * + * @since 21 + */ + @Override + public R visitMarkdown(MarkdownTree node, P p) { + return defaultAction(node, p); + } + /** * {@inheritDoc} * diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java index 80dde7fe8a6ca..a2ec714371e7e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -83,6 +83,7 @@ private enum Phase { PREAMBLE, BODY, POSTAMBLE } private final DocTreeMaker m; private final Names names; private final boolean isFileContent; + private final boolean isMarkdown; /** The input buffer, index of most recent character read, * index of one past last character in buffer. @@ -101,6 +102,8 @@ private enum Phase { PREAMBLE, BODY, POSTAMBLE } private final Map tagParsers; + private static final String MARKDOWN_PREFIX = "md"; + public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment, boolean isFileContent) { this.fac = fac; @@ -109,6 +112,8 @@ public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, this.comment = comment; names = fac.names; this.isFileContent = isFileContent; + String t = comment.getText(); + this.isMarkdown = t.startsWith(MARKDOWN_PREFIX) && Character.isWhitespace(t.charAt(MARKDOWN_PREFIX.length())); m = fac.docTreeMaker; tagParsers = createTagParsers(); } @@ -127,15 +132,16 @@ public DCDocComment parse() { c.getChars(0, c.length(), buf, 0); buf[buf.length - 1] = EOI; buflen = buf.length - 1; - bp = -1; + bp = -1 + (isMarkdown ? MARKDOWN_PREFIX.length() : 0); nextChar(); - List preamble = isFileContent ? blockContent(Phase.PREAMBLE) : List.nil(); + List preamble = isFileContent && !isMarkdown ? blockContent(Phase.PREAMBLE) : List.nil(); List body = blockContent(Phase.BODY); List tags = blockTags(); List postamble = isFileContent ? blockContent(Phase.POSTAMBLE) : List.nil(); - int pos = !preamble.isEmpty() ? preamble.head.pos + int pos = isMarkdown ? 0 + : !preamble.isEmpty() ? preamble.head.pos : !body.isEmpty() ? body.head.pos : !tags.isEmpty() ? tags.head.pos : !postamble.isEmpty() ? postamble.head.pos @@ -157,9 +163,13 @@ protected List blockContent() { } /** - * Read block content, consisting of text, html and inline tags. + * Read block content, consisting of text, and Markdown code or html and inline tags. * Terminated by the end of input, or the beginning of the next block tag: * that is, @ as the first non-whitespace character on a line. + * + * In Markdown mode, {@code &} (introducing an entity) and {@code <} (introducing + * an HTML element) are treated as "plain" characters, to be analyzed eventually + * by the Markdown processor. */ @SuppressWarnings("fallthrough") protected List blockContent(Phase phase) { @@ -178,44 +188,52 @@ protected List blockContent(Phase phase) { break; case '&': - entity(trees); + if (isMarkdown) { + defaultBlockCharacter(); + } else { + entity(trees); + } break; case '<': - newline = false; - if (isFileContent) { - switch (phase) { - case PREAMBLE: - if (isEndPreamble()) { - trees.add(html()); - if (textStart == -1) { - textStart = bp; - lastNonWhite = -1; + if (isMarkdown) { + defaultBlockCharacter(); + } else { + newline = false; + if (isFileContent) { + switch (phase) { + case PREAMBLE: + if (isEndPreamble()) { + trees.add(html()); + if (textStart == -1) { + textStart = bp; + lastNonWhite = -1; + } + // mark this as the start, for processing purposes + newline = true; + break loop; } - // mark this as the start, for processing purposes - newline = true; - break loop; - } - break; - case BODY: - if (isEndBody()) { - addPendingText(trees, lastNonWhite); - break loop; - } - break; - default: - // fallthrough + break; + case BODY: + if (isEndBody()) { + addPendingText(trees, lastNonWhite); + break loop; + } + break; + default: + // fallthrough + } } - } - addPendingText(trees, bp - 1); - trees.add(html()); + addPendingText(trees, bp - 1); + trees.add(html()); - if (phase == Phase.PREAMBLE || phase == Phase.POSTAMBLE) { - break; // Ignore newlines after html tags, in the meta content - } - if (textStart == -1) { - textStart = bp; - lastNonWhite = -1; + if (phase == Phase.PREAMBLE || phase == Phase.POSTAMBLE) { + break; // Ignore newlines after html tags, in the meta content + } + if (textStart == -1) { + textStart = bp; + lastNonWhite = -1; + } } break; @@ -231,11 +249,7 @@ protected List blockContent(Phase phase) { // fallthrough default: - newline = false; - if (textStart == -1) - textStart = bp; - lastNonWhite = bp; - nextChar(); + defaultBlockCharacter(); } } @@ -245,6 +259,14 @@ protected List blockContent(Phase phase) { return trees.toList(); } + private void defaultBlockCharacter() { + newline = false; + if (textStart == -1) + textStart = bp; + lastNonWhite = bp; + nextChar(); + } + /** * Read a series of block tags, including their content. * Standard tags parse their content appropriately. @@ -561,9 +583,13 @@ protected DCText inlineWord() { } /** - * Reads general text content of an inline tag, including HTML entities and elements. + * Reads general text content of an inline tag, including Markdown or HTML entities and elements. * Matching pairs of { } are skipped; the text is terminated by the first * unmatched }. It is an error if the beginning of the next tag is detected. + * + * In Markdown mode, {@code &} (introducing an entity) and {@code <} (introducing + * an HTML element) are treated as "plain" characters, to be analyzed eventually + * by the Markdown processor. */ @SuppressWarnings("fallthrough") private List inlineContent() { @@ -587,15 +613,23 @@ private List inlineContent() { break; case '&': - entity(trees); + if (isMarkdown) { + defaultInlineCharacter(); + } else { + entity(trees); + } break; case '<': - newline = false; - addPendingText(trees, bp - 1); - trees.add(html()); - textStart = bp; - lastNonWhite = -1; + if (isMarkdown) { + defaultInlineCharacter(); + } else { + newline = false; + addPendingText(trees, bp - 1); + trees.add(html()); + textStart = bp; + lastNonWhite = -1; + } break; case '{': @@ -629,9 +663,7 @@ private List inlineContent() { // fallthrough default: - if (textStart == -1) - textStart = bp; - nextChar(); + defaultInlineCharacter(); break; } } @@ -639,6 +671,12 @@ private List inlineContent() { return List.of(erroneous("dc.unterminated.inline.tag", pos)); } + private void defaultInlineCharacter() { + if (textStart == -1) + textStart = bp; + nextChar(); + } + protected void entity(ListBuffer list) { newline = false; addPendingText(list, bp - 1); @@ -942,7 +980,7 @@ protected List htmlAttrs() { } attrValueChar(v); } - addPendingText(v, bp - 1); + addPendingText(v, bp - 1, false); nextChar(); } else { vkind = ValueKind.UNQUOTED; @@ -950,7 +988,7 @@ protected List htmlAttrs() { while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) { attrValueChar(v); } - addPendingText(v, bp - 1); + addPendingText(v, bp - 1, false); } skipWhitespace(); value = v.toList(); @@ -978,9 +1016,17 @@ protected void attrValueChar(ListBuffer list) { } protected void addPendingText(ListBuffer list, int textEnd) { + addPendingText(list, textEnd, isMarkdown); + } + + protected void addPendingText(ListBuffer list, int textEnd, boolean useMarkdown) { if (textStart != -1) { if (textStart <= textEnd) { - list.add(m.at(textStart).newTextTree(newString(textStart, textEnd + 1))); + if (useMarkdown) { + list.add(m.at(textStart).newMarkdownTree(newString(textStart, textEnd + 1))); + } else { + list.add(m.at(textStart).newTextTree(newString(textStart, textEnd + 1))); + } } textStart = -1; } @@ -1524,7 +1570,7 @@ private List tagAttrs() { while (bp < buflen && ch != quote) { nextChar(); } - addPendingText(v, bp - 1); + addPendingText(v, bp - 1, false); nextChar(); } else { vkind = ValueKind.UNQUOTED; @@ -1533,7 +1579,7 @@ private List tagAttrs() { while (bp < buflen && (ch != '}' && ch != ':' && !isUnquotedAttrValueTerminator(ch))) { nextChar(); } - addPendingText(v, bp - 1); + addPendingText(v, bp - 1, false); } skipWhitespace(); value = v.toList(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java index 95cb6e0fa3c14..b28aaacbed94f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -158,6 +158,11 @@ public int getEndPosition() { } switch (getKind()) { + case MARKDOWN -> { + DCMarkdown markdown = (DCMarkdown) this; + return markdown.pos + markdown.code.length(); + } + case TEXT -> { DCText text = (DCText) this; return text.pos + text.text.length(); @@ -791,6 +796,34 @@ public DCText getBody() { } } + public static class DCMarkdown extends DCTree implements MarkdownTree { + public final String code; + + DCMarkdown(String code) { + this.code = code; + } + + @Override + public boolean isBlank() { + return code.isBlank(); + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Kind getKind() { + return Kind.MARKDOWN; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public R accept(DocTreeVisitor v, D d) { + return v.visitMarkdown(this, d); + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public String getContent() { + return code; + } + } + public static class DCParam extends DCBlockTag implements ParamTree { public final boolean isTypeParameter; public final DCIdentifier name; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java index 37f0fe0e9c0fa..c001291bdb0bd 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ import com.sun.source.doctree.*; import com.sun.source.doctree.AttributeTree.ValueKind; +import com.sun.source.util.DocTreeScanner; import com.sun.tools.javac.util.Convert; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; @@ -188,6 +189,9 @@ public Void visitDeprecated(DeprecatedTree node, Void p) { @Override @DefinedBy(Api.COMPILER_TREE) public Void visitDocComment(DocCommentTree node, Void p) { try { + if (isMarkdown(node)) { + print("md\n"); + } List b = node.getFullBody(); List t = node.getBlockTags(); print(b); @@ -200,6 +204,36 @@ public Void visitDocComment(DocCommentTree node, Void p) { return null; } + private static final DocTreeVisitor isMarkdownVisitor = new DocTreeScanner() { + @Override + public Boolean scan(Iterable nodes, Void ignore) { + if (nodes != null) { + boolean first = true; + for (DocTree node : nodes) { + Boolean b = scan(node, ignore); + if (b == Boolean.TRUE) { + return b; + } + } + } + return false; + } + + @Override + public Boolean scan(DocTree node, Void ignore) { + return node != null && node.getKind() == DocTree.Kind.MARKDOWN ? Boolean.TRUE : super.scan(node, ignore); + } + + @Override + public Boolean reduce(Boolean r1, Boolean r2) { + return r1 == Boolean.TRUE || r2 == Boolean.TRUE; + } + }; + + private boolean isMarkdown(DocCommentTree node) { + return isMarkdownVisitor.visitDocComment(node, null); + } + @Override @DefinedBy(Api.COMPILER_TREE) public Void visitDocRoot(DocRootTree node, Void p) { try { @@ -345,6 +379,16 @@ public Void visitLiteral(LiteralTree node, Void p) { return null; } + @Override @DefinedBy(Api.COMPILER_TREE) + public Void visitMarkdown(MarkdownTree node, Void p) { + try { + print(node.getContent()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return null; + } + @Override @DefinedBy(Api.COMPILER_TREE) public Void visitParam(ParamTree node, Void p) { try { @@ -616,8 +660,12 @@ public Void visitUnknownInlineTag(UnknownInlineTagTree node, Void p) { print("{"); print("@"); print(node.getTagName()); - print(" "); - print(node.getContent()); + var content = node.getContent(); + boolean isEmpty = content.stream().allMatch(n -> (n instanceof TextTree t) && t.getBody().isEmpty()); + if (!isEmpty) { + print(" "); + print(content); + } print("}"); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java index cd6fdad70e67d..3000b960d85c8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,6 +66,7 @@ import com.sun.tools.javac.tree.DCTree.DCInheritDoc; import com.sun.tools.javac.tree.DCTree.DCLink; import com.sun.tools.javac.tree.DCTree.DCLiteral; +import com.sun.tools.javac.tree.DCTree.DCMarkdown; import com.sun.tools.javac.tree.DCTree.DCParam; import com.sun.tools.javac.tree.DCTree.DCProvides; import com.sun.tools.javac.tree.DCTree.DCReference; @@ -333,6 +334,13 @@ public DCLiteral newLiteralTree(TextTree text) { return tree; } + @Override @DefinedBy(Api.COMPILER_TREE) + public DCMarkdown newMarkdownTree(String text) { + DCMarkdown tree = new DCMarkdown(text); + tree.pos = pos; + return tree; + } + @Override @DefinedBy(Api.COMPILER_TREE) public DCParam newParamTree(boolean isTypeParameter, IdentifierTree name, List description) { DCParam tree = new DCParam(isTypeParameter, (DCIdentifier) name, cast(description)); @@ -538,11 +546,46 @@ private Pair, List> splitBody(Collection continue; } switch (dt.getKind()) { - case RETURN: - case SUMMARY: + case RETURN, + SUMMARY -> foundFirstSentence = true; - break; - case TEXT: + + // TODO: merge MARKDOWN and TEXT code, perhaps with generic method + case MARKDOWN -> { + DCMarkdown mt = (DCMarkdown) dt; + String s = mt.getContent(); + DocTree peekedNext = itr.hasNext() + ? alist.get(itr.nextIndex()) + : null; + int sbreak = getSentenceBreak(s, peekedNext); + if (sbreak > 0) { + s = s.substring(0, sbreak).stripTrailing(); + DCMarkdown text = this.at(spos).newMarkdownTree(s); + fs.add(text); + foundFirstSentence = true; + int nwPos = skipWhiteSpace(mt.getContent(), sbreak); + if (nwPos > 0) { + DCMarkdown text2 = this.at(spos + nwPos).newMarkdownTree(mt.getContent().substring(nwPos)); + body.add(text2); + } + continue; + } else if (itr.hasNext()) { + // if the next doctree is a break, remove trailing spaces + peekedNext = alist.get(itr.nextIndex()); + boolean sbrk = isSentenceBreak(peekedNext, false); + if (sbrk) { + DocTree next = itr.next(); + s = s.stripTrailing(); + DCMarkdown text = this.at(spos).newMarkdownTree(s); + fs.add(text); + body.add((DCMarkdown) next); // TODO: why the cast? + foundFirstSentence = true; + continue; + } + } + } + + case TEXT -> { DCText tt = (DCText) dt; String s = tt.getBody(); DocTree peekedNext = itr.hasNext() @@ -574,14 +617,15 @@ private Pair, List> splitBody(Collection continue; } } - break; - default: + } + + default -> { if (isSentenceBreak(dt, isFirst)) { body.add((DCTree) dt); foundFirstSentence = true; continue; } - break; + } } fs.add((DCTree) dt); } diff --git a/src/jdk.compiler/share/classes/module-info.java b/src/jdk.compiler/share/classes/module-info.java index 4610474318272..a513f3c258e0a 100644 --- a/src/jdk.compiler/share/classes/module-info.java +++ b/src/jdk.compiler/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -257,8 +257,6 @@ jdk.jdeps, jdk.javadoc, jdk.jshell; - exports jdk.internal.shellsupport.doc to - jdk.jshell; uses javax.annotation.processing.Processor; uses com.sun.source.util.Plugin; diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/Extension.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/Extension.java new file mode 100644 index 0000000000000..b0f1ed378f69b --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/Extension.java @@ -0,0 +1,43 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark; + +/** + * Base interface for a parser/renderer extension. + *

+ * Doesn't have any methods itself, but has specific sub interfaces to + * configure parser/renderer. This base interface is for convenience, so that a list of extensions can be built and then + * used for configuring both the parser and renderer in the same way. + */ +public interface Extension { +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockContent.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockContent.java new file mode 100644 index 0000000000000..64e9e5f7bf193 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockContent.java @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +class BlockContent { + + private final StringBuilder sb; + + private int lineCount = 0; + + public BlockContent() { + sb = new StringBuilder(); + } + + public BlockContent(String content) { + sb = new StringBuilder(content); + } + + public void add(CharSequence line) { + if (lineCount != 0) { + sb.append('\n'); + } + sb.append(line); + lineCount++; + } + + public String getString() { + return sb.toString(); + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockContinueImpl.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockContinueImpl.java new file mode 100644 index 0000000000000..47697018e406d --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockContinueImpl.java @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.parser.block.BlockContinue; + +public class BlockContinueImpl extends BlockContinue { + + private final int newIndex; + private final int newColumn; + private final boolean finalize; + + public BlockContinueImpl(int newIndex, int newColumn, boolean finalize) { + this.newIndex = newIndex; + this.newColumn = newColumn; + this.finalize = finalize; + } + + public int getNewIndex() { + return newIndex; + } + + public int getNewColumn() { + return newColumn; + } + + public boolean isFinalize() { + return finalize; + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockQuoteParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockQuoteParser.java new file mode 100644 index 0000000000000..c7f30af09d730 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockQuoteParser.java @@ -0,0 +1,94 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.BlockQuote; +import jdk.internal.org.commonmark.parser.block.*; + +public class BlockQuoteParser extends AbstractBlockParser { + + private final BlockQuote block = new BlockQuote(); + + @Override + public boolean isContainer() { + return true; + } + + @Override + public boolean canContain(Block block) { + return true; + } + + @Override + public BlockQuote getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + int nextNonSpace = state.getNextNonSpaceIndex(); + if (isMarker(state, nextNonSpace)) { + int newColumn = state.getColumn() + state.getIndent() + 1; + // optional following space or tab + if (Parsing.isSpaceOrTab(state.getLine().getContent(), nextNonSpace + 1)) { + newColumn++; + } + return BlockContinue.atColumn(newColumn); + } else { + return BlockContinue.none(); + } + } + + private static boolean isMarker(ParserState state, int index) { + CharSequence line = state.getLine().getContent(); + return state.getIndent() < Parsing.CODE_BLOCK_INDENT && index < line.length() && line.charAt(index) == '>'; + } + + public static class Factory extends AbstractBlockParserFactory { + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + int nextNonSpace = state.getNextNonSpaceIndex(); + if (isMarker(state, nextNonSpace)) { + int newColumn = state.getColumn() + state.getIndent() + 1; + // optional following space or tab + if (Parsing.isSpaceOrTab(state.getLine().getContent(), nextNonSpace + 1)) { + newColumn++; + } + return BlockStart.of(new BlockQuoteParser()).atColumn(newColumn); + } else { + return BlockStart.none(); + } + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockStartImpl.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockStartImpl.java new file mode 100644 index 0000000000000..bde28136b82b1 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockStartImpl.java @@ -0,0 +1,83 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.parser.block.BlockParser; +import jdk.internal.org.commonmark.parser.block.BlockStart; + +public class BlockStartImpl extends BlockStart { + + private final BlockParser[] blockParsers; + private int newIndex = -1; + private int newColumn = -1; + private boolean replaceActiveBlockParser = false; + + public BlockStartImpl(BlockParser... blockParsers) { + this.blockParsers = blockParsers; + } + + public BlockParser[] getBlockParsers() { + return blockParsers; + } + + public int getNewIndex() { + return newIndex; + } + + public int getNewColumn() { + return newColumn; + } + + public boolean isReplaceActiveBlockParser() { + return replaceActiveBlockParser; + } + + @Override + public BlockStart atIndex(int newIndex) { + this.newIndex = newIndex; + return this; + } + + @Override + public BlockStart atColumn(int newColumn) { + this.newColumn = newColumn; + return this; + } + + @Override + public BlockStart replaceActiveBlockParser() { + this.replaceActiveBlockParser = true; + return this; + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/Bracket.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/Bracket.java new file mode 100644 index 0000000000000..b3473bb666f5b --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/Bracket.java @@ -0,0 +1,96 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.inline.Position; +import jdk.internal.org.commonmark.node.Text; + +/** + * Opening bracket for links ([) or images (![). + */ +public class Bracket { + + public final Text node; + + /** + * The position of the marker for the bracket ([ or ![) + */ + public final Position markerPosition; + + /** + * The position of the content (after the opening bracket) + */ + public final Position contentPosition; + + /** + * Whether this is an image or link. + */ + public final boolean image; + + /** + * Previous bracket. + */ + public final Bracket previous; + + /** + * Previous delimiter (emphasis, etc) before this bracket. + */ + public final Delimiter previousDelimiter; + + /** + * Whether this bracket is allowed to form a link/image (also known as "active"). + */ + public boolean allowed = true; + + /** + * Whether there is an unescaped bracket (opening or closing) anywhere after this opening bracket. + */ + public boolean bracketAfter = false; + + static public Bracket link(Text node, Position markerPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(node, markerPosition, contentPosition, previous, previousDelimiter, false); + } + + static public Bracket image(Text node, Position markerPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(node, markerPosition, contentPosition, previous, previousDelimiter, true); + } + + private Bracket(Text node, Position markerPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter, boolean image) { + this.node = node; + this.markerPosition = markerPosition; + this.contentPosition = contentPosition; + this.image = image; + this.previous = previous; + this.previousDelimiter = previousDelimiter; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/Delimiter.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/Delimiter.java new file mode 100644 index 0000000000000..8437594dd6da3 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/Delimiter.java @@ -0,0 +1,114 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.node.Text; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterRun; + +import java.util.List; + +/** + * Delimiter (emphasis, strong emphasis or custom emphasis). + */ +public class Delimiter implements DelimiterRun { + + public final List characters; + public final char delimiterChar; + private final int originalLength; + + // Can open emphasis, see spec. + private final boolean canOpen; + + // Can close emphasis, see spec. + private final boolean canClose; + + public Delimiter previous; + public Delimiter next; + + public Delimiter(List characters, char delimiterChar, boolean canOpen, boolean canClose, Delimiter previous) { + this.characters = characters; + this.delimiterChar = delimiterChar; + this.canOpen = canOpen; + this.canClose = canClose; + this.previous = previous; + this.originalLength = characters.size(); + } + + @Override + public boolean canOpen() { + return canOpen; + } + + @Override + public boolean canClose() { + return canClose; + } + + @Override + public int length() { + return characters.size(); + } + + @Override + public int originalLength() { + return originalLength; + } + + @Override + public Text getOpener() { + return characters.get(characters.size() - 1); + } + + @Override + public Text getCloser() { + return characters.get(0); + } + + @Override + public Iterable getOpeners(int length) { + if (!(length >= 1 && length <= length())) { + throw new IllegalArgumentException("length must be between 1 and " + length() + ", was " + length); + } + + return characters.subList(characters.size() - length, characters.size()); + } + + @Override + public Iterable getClosers(int length) { + if (!(length >= 1 && length <= length())) { + throw new IllegalArgumentException("length must be between 1 and " + length() + ", was " + length); + } + + return characters.subList(0, length); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/DocumentBlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/DocumentBlockParser.java new file mode 100644 index 0000000000000..8f18feed1a0a3 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/DocumentBlockParser.java @@ -0,0 +1,70 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.Document; +import jdk.internal.org.commonmark.parser.SourceLine; +import jdk.internal.org.commonmark.parser.block.AbstractBlockParser; +import jdk.internal.org.commonmark.parser.block.BlockContinue; +import jdk.internal.org.commonmark.parser.block.ParserState; + +public class DocumentBlockParser extends AbstractBlockParser { + + private final Document document = new Document(); + + @Override + public boolean isContainer() { + return true; + } + + @Override + public boolean canContain(Block block) { + return true; + } + + @Override + public Document getBlock() { + return document; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + return BlockContinue.atIndex(state.getIndex()); + } + + @Override + public void addLine(SourceLine line) { + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/DocumentParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/DocumentParser.java new file mode 100644 index 0000000000000..714c5ce7ef8bb --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/DocumentParser.java @@ -0,0 +1,609 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.*; +import jdk.internal.org.commonmark.parser.*; +import jdk.internal.org.commonmark.parser.block.*; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.*; + +public class DocumentParser implements ParserState { + + private static final Set> CORE_FACTORY_TYPES = new LinkedHashSet<>(Arrays.asList( + BlockQuote.class, + Heading.class, + FencedCodeBlock.class, + HtmlBlock.class, + ThematicBreak.class, + ListBlock.class, + IndentedCodeBlock.class)); + + private static final Map, BlockParserFactory> NODES_TO_CORE_FACTORIES; + + static { + Map, BlockParserFactory> map = new HashMap<>(); + map.put(BlockQuote.class, new BlockQuoteParser.Factory()); + map.put(Heading.class, new HeadingParser.Factory()); + map.put(FencedCodeBlock.class, new FencedCodeBlockParser.Factory()); + map.put(HtmlBlock.class, new HtmlBlockParser.Factory()); + map.put(ThematicBreak.class, new ThematicBreakParser.Factory()); + map.put(ListBlock.class, new ListBlockParser.Factory()); + map.put(IndentedCodeBlock.class, new IndentedCodeBlockParser.Factory()); + NODES_TO_CORE_FACTORIES = Collections.unmodifiableMap(map); + } + + private SourceLine line; + + /** + * Line index (0-based) + */ + private int lineIndex = -1; + + /** + * current index (offset) in input line (0-based) + */ + private int index = 0; + + /** + * current column of input line (tab causes column to go to next 4-space tab stop) (0-based) + */ + private int column = 0; + + /** + * if the current column is within a tab character (partially consumed tab) + */ + private boolean columnIsInTab; + + private int nextNonSpace = 0; + private int nextNonSpaceColumn = 0; + private int indent = 0; + private boolean blank; + + private final List blockParserFactories; + private final InlineParserFactory inlineParserFactory; + private final List delimiterProcessors; + private final IncludeSourceSpans includeSourceSpans; + private final DocumentBlockParser documentBlockParser; + private final LinkReferenceDefinitions definitions = new LinkReferenceDefinitions(); + + private final List openBlockParsers = new ArrayList<>(); + private final List allBlockParsers = new ArrayList<>(); + + public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, + List delimiterProcessors, IncludeSourceSpans includeSourceSpans) { + this.blockParserFactories = blockParserFactories; + this.inlineParserFactory = inlineParserFactory; + this.delimiterProcessors = delimiterProcessors; + this.includeSourceSpans = includeSourceSpans; + + this.documentBlockParser = new DocumentBlockParser(); + activateBlockParser(new OpenBlockParser(documentBlockParser, 0)); + } + + public static Set> getDefaultBlockParserTypes() { + return CORE_FACTORY_TYPES; + } + + public static List calculateBlockParserFactories(List customBlockParserFactories, Set> enabledBlockTypes) { + List list = new ArrayList<>(); + // By having the custom factories come first, extensions are able to change behavior of core syntax. + list.addAll(customBlockParserFactories); + for (Class blockType : enabledBlockTypes) { + list.add(NODES_TO_CORE_FACTORIES.get(blockType)); + } + return list; + } + + public static void checkEnabledBlockTypes(Set> enabledBlockTypes) { + for (Class enabledBlockType : enabledBlockTypes) { + if (!NODES_TO_CORE_FACTORIES.containsKey(enabledBlockType)) { + throw new IllegalArgumentException("Can't enable block type " + enabledBlockType + ", possible options are: " + NODES_TO_CORE_FACTORIES.keySet()); + } + } + } + + /** + * The main parsing function. Returns a parsed document AST. + */ + public Document parse(String input) { + int lineStart = 0; + int lineBreak; + while ((lineBreak = Parsing.findLineBreak(input, lineStart)) != -1) { + String line = input.substring(lineStart, lineBreak); + parseLine(line); + if (lineBreak + 1 < input.length() && input.charAt(lineBreak) == '\r' && input.charAt(lineBreak + 1) == '\n') { + lineStart = lineBreak + 2; + } else { + lineStart = lineBreak + 1; + } + } + if (input.length() > 0 && (lineStart == 0 || lineStart < input.length())) { + String line = input.substring(lineStart); + parseLine(line); + } + + return finalizeAndProcess(); + } + + public Document parse(Reader input) throws IOException { + BufferedReader bufferedReader; + if (input instanceof BufferedReader) { + bufferedReader = (BufferedReader) input; + } else { + bufferedReader = new BufferedReader(input); + } + + String line; + while ((line = bufferedReader.readLine()) != null) { + parseLine(line); + } + + return finalizeAndProcess(); + } + + @Override + public SourceLine getLine() { + return line; + } + + @Override + public int getIndex() { + return index; + } + + @Override + public int getNextNonSpaceIndex() { + return nextNonSpace; + } + + @Override + public int getColumn() { + return column; + } + + @Override + public int getIndent() { + return indent; + } + + @Override + public boolean isBlank() { + return blank; + } + + @Override + public BlockParser getActiveBlockParser() { + return openBlockParsers.get(openBlockParsers.size() - 1).blockParser; + } + + /** + * Analyze a line of text and update the document appropriately. We parse markdown text by calling this on each + * line of input, then finalizing the document. + */ + private void parseLine(CharSequence ln) { + setLine(ln); + + // For each containing block, try to parse the associated line start. + // The document will always match, so we can skip the first block parser and start at 1 matches + int matches = 1; + for (int i = 1; i < openBlockParsers.size(); i++) { + OpenBlockParser openBlockParser = openBlockParsers.get(i); + BlockParser blockParser = openBlockParser.blockParser; + findNextNonSpace(); + + BlockContinue result = blockParser.tryContinue(this); + if (result instanceof BlockContinueImpl) { + BlockContinueImpl blockContinue = (BlockContinueImpl) result; + openBlockParser.sourceIndex = getIndex(); + if (blockContinue.isFinalize()) { + addSourceSpans(); + closeBlockParsers(openBlockParsers.size() - i); + return; + } else { + if (blockContinue.getNewIndex() != -1) { + setNewIndex(blockContinue.getNewIndex()); + } else if (blockContinue.getNewColumn() != -1) { + setNewColumn(blockContinue.getNewColumn()); + } + matches++; + } + } else { + break; + } + } + + int unmatchedBlocks = openBlockParsers.size() - matches; + BlockParser blockParser = openBlockParsers.get(matches - 1).blockParser; + boolean startedNewBlock = false; + + int lastIndex = index; + + // Unless last matched container is a code block, try new container starts, + // adding children to the last matched container: + boolean tryBlockStarts = blockParser.getBlock() instanceof Paragraph || blockParser.isContainer(); + while (tryBlockStarts) { + lastIndex = index; + findNextNonSpace(); + + // this is a little performance optimization: + if (isBlank() || (indent < Parsing.CODE_BLOCK_INDENT && Parsing.isLetter(this.line.getContent(), nextNonSpace))) { + setNewIndex(nextNonSpace); + break; + } + + BlockStartImpl blockStart = findBlockStart(blockParser); + if (blockStart == null) { + setNewIndex(nextNonSpace); + break; + } + + startedNewBlock = true; + int sourceIndex = getIndex(); + + // We're starting a new block. If we have any previous blocks that need to be closed, we need to do it now. + if (unmatchedBlocks > 0) { + closeBlockParsers(unmatchedBlocks); + unmatchedBlocks = 0; + } + + if (blockStart.getNewIndex() != -1) { + setNewIndex(blockStart.getNewIndex()); + } else if (blockStart.getNewColumn() != -1) { + setNewColumn(blockStart.getNewColumn()); + } + + List replacedSourceSpans = null; + if (blockStart.isReplaceActiveBlockParser()) { + Block replacedBlock = prepareActiveBlockParserForReplacement(); + replacedSourceSpans = replacedBlock.getSourceSpans(); + } + + for (BlockParser newBlockParser : blockStart.getBlockParsers()) { + addChild(new OpenBlockParser(newBlockParser, sourceIndex)); + if (replacedSourceSpans != null) { + newBlockParser.getBlock().setSourceSpans(replacedSourceSpans); + } + blockParser = newBlockParser; + tryBlockStarts = newBlockParser.isContainer(); + } + } + + // What remains at the offset is a text line. Add the text to the + // appropriate block. + + // First check for a lazy paragraph continuation: + if (!startedNewBlock && !isBlank() && + getActiveBlockParser().canHaveLazyContinuationLines()) { + openBlockParsers.get(openBlockParsers.size() - 1).sourceIndex = lastIndex; + // lazy paragraph continuation + addLine(); + + } else { + + // finalize any blocks not matched + if (unmatchedBlocks > 0) { + closeBlockParsers(unmatchedBlocks); + } + + if (!blockParser.isContainer()) { + addLine(); + } else if (!isBlank()) { + // create paragraph container for line + ParagraphParser paragraphParser = new ParagraphParser(); + addChild(new OpenBlockParser(paragraphParser, lastIndex)); + addLine(); + } else { + // This can happen for a list item like this: + // ``` + // * + // list item + // ``` + // + // The first line does not start a paragraph yet, but we still want to record source positions. + addSourceSpans(); + } + } + } + + private void setLine(CharSequence ln) { + lineIndex++; + index = 0; + column = 0; + columnIsInTab = false; + + CharSequence lineContent = Parsing.prepareLine(ln); + SourceSpan sourceSpan = null; + if (includeSourceSpans != IncludeSourceSpans.NONE) { + sourceSpan = SourceSpan.of(lineIndex, 0, lineContent.length()); + } + this.line = SourceLine.of(lineContent, sourceSpan); + } + + private void findNextNonSpace() { + int i = index; + int cols = column; + + blank = true; + int length = line.getContent().length(); + while (i < length) { + char c = line.getContent().charAt(i); + switch (c) { + case ' ': + i++; + cols++; + continue; + case '\t': + i++; + cols += (4 - (cols % 4)); + continue; + } + blank = false; + break; + } + + nextNonSpace = i; + nextNonSpaceColumn = cols; + indent = nextNonSpaceColumn - column; + } + + private void setNewIndex(int newIndex) { + if (newIndex >= nextNonSpace) { + // We can start from here, no need to calculate tab stops again + index = nextNonSpace; + column = nextNonSpaceColumn; + } + int length = line.getContent().length(); + while (index < newIndex && index != length) { + advance(); + } + // If we're going to an index as opposed to a column, we're never within a tab + columnIsInTab = false; + } + + private void setNewColumn(int newColumn) { + if (newColumn >= nextNonSpaceColumn) { + // We can start from here, no need to calculate tab stops again + index = nextNonSpace; + column = nextNonSpaceColumn; + } + int length = line.getContent().length(); + while (column < newColumn && index != length) { + advance(); + } + if (column > newColumn) { + // Last character was a tab and we overshot our target + index--; + column = newColumn; + columnIsInTab = true; + } else { + columnIsInTab = false; + } + } + + private void advance() { + char c = line.getContent().charAt(index); + index++; + if (c == '\t') { + column += Parsing.columnsToNextTabStop(column); + } else { + column++; + } + } + + /** + * Add line content to the active block parser. We assume it can accept lines -- that check should be done before + * calling this. + */ + private void addLine() { + CharSequence content; + if (columnIsInTab) { + // Our column is in a partially consumed tab. Expand the remaining columns (to the next tab stop) to spaces. + int afterTab = index + 1; + CharSequence rest = line.getContent().subSequence(afterTab, line.getContent().length()); + int spaces = Parsing.columnsToNextTabStop(column); + StringBuilder sb = new StringBuilder(spaces + rest.length()); + for (int i = 0; i < spaces; i++) { + sb.append(' '); + } + sb.append(rest); + content = sb.toString(); + } else if (index == 0) { + content = line.getContent(); + } else { + content = line.getContent().subSequence(index, line.getContent().length()); + } + SourceSpan sourceSpan = null; + if (includeSourceSpans == IncludeSourceSpans.BLOCKS_AND_INLINES) { + // Note that if we're in a partially-consumed tab, the length here corresponds to the content but not to the + // actual source length. That sounds like a problem, but I haven't found a test case where it matters (yet). + sourceSpan = SourceSpan.of(lineIndex, index, content.length()); + } + getActiveBlockParser().addLine(SourceLine.of(content, sourceSpan)); + addSourceSpans(); + } + + private void addSourceSpans() { + if (includeSourceSpans != IncludeSourceSpans.NONE) { + // Don't add source spans for Document itself (it would get the whole source text) + for (int i = 1; i < openBlockParsers.size(); i++) { + OpenBlockParser openBlockParser = openBlockParsers.get(i); + int blockIndex = openBlockParser.sourceIndex; + int length = line.getContent().length() - blockIndex; + if (length != 0) { + openBlockParser.blockParser.addSourceSpan(SourceSpan.of(lineIndex, blockIndex, length)); + } + } + } + } + + private BlockStartImpl findBlockStart(BlockParser blockParser) { + MatchedBlockParser matchedBlockParser = new MatchedBlockParserImpl(blockParser); + for (BlockParserFactory blockParserFactory : blockParserFactories) { + BlockStart result = blockParserFactory.tryStart(this, matchedBlockParser); + if (result instanceof BlockStartImpl) { + return (BlockStartImpl) result; + } + } + return null; + } + + /** + * Finalize a block. Close it and do any necessary postprocessing, e.g. setting the content of blocks and + * collecting link reference definitions from paragraphs. + */ + private void finalize(BlockParser blockParser) { + if (blockParser instanceof ParagraphParser) { + addDefinitionsFrom((ParagraphParser) blockParser); + } + + blockParser.closeBlock(); + } + + private void addDefinitionsFrom(ParagraphParser paragraphParser) { + for (LinkReferenceDefinition definition : paragraphParser.getDefinitions()) { + // Add nodes into document before paragraph. + paragraphParser.getBlock().insertBefore(definition); + + definitions.add(definition); + } + } + + /** + * Walk through a block & children recursively, parsing string content into inline content where appropriate. + */ + private void processInlines() { + InlineParserContextImpl context = new InlineParserContextImpl(delimiterProcessors, definitions); + InlineParser inlineParser = inlineParserFactory.create(context); + + for (BlockParser blockParser : allBlockParsers) { + blockParser.parseInlines(inlineParser); + } + } + + /** + * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try + * its parent, and so on until we find a block that can accept children. + */ + private void addChild(OpenBlockParser openBlockParser) { + while (!getActiveBlockParser().canContain(openBlockParser.blockParser.getBlock())) { + closeBlockParsers(1); + } + + getActiveBlockParser().getBlock().appendChild(openBlockParser.blockParser.getBlock()); + activateBlockParser(openBlockParser); + } + + private void activateBlockParser(OpenBlockParser openBlockParser) { + openBlockParsers.add(openBlockParser); + } + + private OpenBlockParser deactivateBlockParser() { + return openBlockParsers.remove(openBlockParsers.size() - 1); + } + + private Block prepareActiveBlockParserForReplacement() { + // Note that we don't want to parse inlines, as it's getting replaced. + BlockParser old = deactivateBlockParser().blockParser; + + if (old instanceof ParagraphParser) { + ParagraphParser paragraphParser = (ParagraphParser) old; + // Collect any link reference definitions. Note that replacing the active block parser is done after a + // block parser got the current paragraph content using MatchedBlockParser#getContentString. In case the + // paragraph started with link reference definitions, we parse and strip them before the block parser gets + // the content. We want to keep them. + // If no replacement happens, we collect the definitions as part of finalizing paragraph blocks. + addDefinitionsFrom(paragraphParser); + } + + // Do this so that source positions are calculated, which we will carry over to the replacing block. + old.closeBlock(); + old.getBlock().unlink(); + return old.getBlock(); + } + + private Document finalizeAndProcess() { + closeBlockParsers(openBlockParsers.size()); + processInlines(); + return documentBlockParser.getBlock(); + } + + private void closeBlockParsers(int count) { + for (int i = 0; i < count; i++) { + BlockParser blockParser = deactivateBlockParser().blockParser; + finalize(blockParser); + // Remember for inline parsing. Note that a lot of blocks don't need inline parsing. We could have a + // separate interface (e.g. BlockParserWithInlines) so that we only have to remember those that actually + // have inlines to parse. + allBlockParsers.add(blockParser); + } + } + + private static class MatchedBlockParserImpl implements MatchedBlockParser { + + private final BlockParser matchedBlockParser; + + public MatchedBlockParserImpl(BlockParser matchedBlockParser) { + this.matchedBlockParser = matchedBlockParser; + } + + @Override + public BlockParser getMatchedBlockParser() { + return matchedBlockParser; + } + + @Override + public SourceLines getParagraphLines() { + if (matchedBlockParser instanceof ParagraphParser) { + ParagraphParser paragraphParser = (ParagraphParser) matchedBlockParser; + return paragraphParser.getParagraphLines(); + } + return SourceLines.empty(); + } + } + + private static class OpenBlockParser { + private final BlockParser blockParser; + private int sourceIndex; + + OpenBlockParser(BlockParser blockParser, int sourceIndex) { + this.blockParser = blockParser; + this.sourceIndex = sourceIndex; + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/FencedCodeBlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/FencedCodeBlockParser.java new file mode 100644 index 0000000000000..92de6f34bf952 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/FencedCodeBlockParser.java @@ -0,0 +1,164 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.FencedCodeBlock; +import jdk.internal.org.commonmark.parser.SourceLine; +import jdk.internal.org.commonmark.parser.block.*; + +import static jdk.internal.org.commonmark.internal.util.Escaping.unescapeString; + +public class FencedCodeBlockParser extends AbstractBlockParser { + + private final FencedCodeBlock block = new FencedCodeBlock(); + + private String firstLine; + private StringBuilder otherLines = new StringBuilder(); + + public FencedCodeBlockParser(char fenceChar, int fenceLength, int fenceIndent) { + block.setFenceChar(fenceChar); + block.setFenceLength(fenceLength); + block.setFenceIndent(fenceIndent); + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + int nextNonSpace = state.getNextNonSpaceIndex(); + int newIndex = state.getIndex(); + CharSequence line = state.getLine().getContent(); + if (state.getIndent() < Parsing.CODE_BLOCK_INDENT && nextNonSpace < line.length() && line.charAt(nextNonSpace) == block.getFenceChar() && isClosing(line, nextNonSpace)) { + // closing fence - we're at end of line, so we can finalize now + return BlockContinue.finished(); + } else { + // skip optional spaces of fence indent + int i = block.getFenceIndent(); + int length = line.length(); + while (i > 0 && newIndex < length && line.charAt(newIndex) == ' ') { + newIndex++; + i--; + } + } + return BlockContinue.atIndex(newIndex); + } + + @Override + public void addLine(SourceLine line) { + if (firstLine == null) { + firstLine = line.getContent().toString(); + } else { + otherLines.append(line.getContent()); + otherLines.append('\n'); + } + } + + @Override + public void closeBlock() { + // first line becomes info string + block.setInfo(unescapeString(firstLine.trim())); + block.setLiteral(otherLines.toString()); + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + int indent = state.getIndent(); + if (indent >= Parsing.CODE_BLOCK_INDENT) { + return BlockStart.none(); + } + + int nextNonSpace = state.getNextNonSpaceIndex(); + FencedCodeBlockParser blockParser = checkOpener(state.getLine().getContent(), nextNonSpace, indent); + if (blockParser != null) { + return BlockStart.of(blockParser).atIndex(nextNonSpace + blockParser.block.getFenceLength()); + } else { + return BlockStart.none(); + } + } + } + + // spec: A code fence is a sequence of at least three consecutive backtick characters (`) or tildes (~). (Tildes and + // backticks cannot be mixed.) + private static FencedCodeBlockParser checkOpener(CharSequence line, int index, int indent) { + int backticks = 0; + int tildes = 0; + int length = line.length(); + loop: + for (int i = index; i < length; i++) { + switch (line.charAt(i)) { + case '`': + backticks++; + break; + case '~': + tildes++; + break; + default: + break loop; + } + } + if (backticks >= 3 && tildes == 0) { + // spec: If the info string comes after a backtick fence, it may not contain any backtick characters. + if (Parsing.find('`', line, index + backticks) != -1) { + return null; + } + return new FencedCodeBlockParser('`', backticks, indent); + } else if (tildes >= 3 && backticks == 0) { + // spec: Info strings for tilde code blocks can contain backticks and tildes + return new FencedCodeBlockParser('~', tildes, indent); + } else { + return null; + } + } + + // spec: The content of the code block consists of all subsequent lines, until a closing code fence of the same type + // as the code block began with (backticks or tildes), and with at least as many backticks or tildes as the opening + // code fence. + private boolean isClosing(CharSequence line, int index) { + char fenceChar = block.getFenceChar(); + int fenceLength = block.getFenceLength(); + int fences = Parsing.skip(fenceChar, line, index, line.length()) - index; + if (fences < fenceLength) { + return false; + } + // spec: The closing code fence [...] may be followed only by spaces, which are ignored. + int after = Parsing.skipSpaceTab(line, index + fences, line.length()); + return after == line.length(); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/HeadingParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/HeadingParser.java new file mode 100644 index 0000000000000..7568803760c45 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/HeadingParser.java @@ -0,0 +1,187 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.inline.Position; +import jdk.internal.org.commonmark.internal.inline.Scanner; +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.Heading; +import jdk.internal.org.commonmark.parser.InlineParser; +import jdk.internal.org.commonmark.parser.SourceLine; +import jdk.internal.org.commonmark.parser.SourceLines; +import jdk.internal.org.commonmark.parser.block.*; + +public class HeadingParser extends AbstractBlockParser { + + private final Heading block = new Heading(); + private final SourceLines content; + + public HeadingParser(int level, SourceLines content) { + block.setLevel(level); + this.content = content; + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + // In both ATX and Setext headings, once we have the heading markup, there's nothing more to parse. + return BlockContinue.none(); + } + + @Override + public void parseInlines(InlineParser inlineParser) { + inlineParser.parse(content, block); + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT) { + return BlockStart.none(); + } + + SourceLine line = state.getLine(); + int nextNonSpace = state.getNextNonSpaceIndex(); + if (line.getContent().charAt(nextNonSpace) == '#') { + HeadingParser atxHeading = getAtxHeading(line.substring(nextNonSpace, line.getContent().length())); + if (atxHeading != null) { + return BlockStart.of(atxHeading).atIndex(line.getContent().length()); + } + } + + int setextHeadingLevel = getSetextHeadingLevel(line.getContent(), nextNonSpace); + if (setextHeadingLevel > 0) { + SourceLines paragraph = matchedBlockParser.getParagraphLines(); + if (!paragraph.isEmpty()) { + return BlockStart.of(new HeadingParser(setextHeadingLevel, paragraph)) + .atIndex(line.getContent().length()) + .replaceActiveBlockParser(); + } + } + + return BlockStart.none(); + } + } + + // spec: An ATX heading consists of a string of characters, parsed as inline content, between an opening sequence of + // 1\u20136 unescaped # characters and an optional closing sequence of any number of unescaped # characters. The opening + // sequence of # characters must be followed by a space or by the end of line. The optional closing sequence of #s + // must be preceded by a space and may be followed by spaces only. + private static HeadingParser getAtxHeading(SourceLine line) { + Scanner scanner = Scanner.of(SourceLines.of(line)); + int level = scanner.matchMultiple('#'); + + if (level == 0 || level > 6) { + return null; + } + + if (!scanner.hasNext()) { + // End of line after markers is an empty heading + return new HeadingParser(level, SourceLines.empty()); + } + + char next = scanner.peek(); + if (!(next == ' ' || next == '\t')) { + return null; + } + + scanner.whitespace(); + Position start = scanner.position(); + Position end = start; + boolean hashCanEnd = true; + + while (scanner.hasNext()) { + char c = scanner.peek(); + switch (c) { + case '#': + if (hashCanEnd) { + scanner.matchMultiple('#'); + int whitespace = scanner.whitespace(); + // If there's other characters, the hashes and spaces were part of the heading + if (scanner.hasNext()) { + end = scanner.position(); + } + hashCanEnd = whitespace > 0; + } else { + scanner.next(); + end = scanner.position(); + } + break; + case ' ': + case '\t': + hashCanEnd = true; + scanner.next(); + break; + default: + hashCanEnd = false; + scanner.next(); + end = scanner.position(); + } + } + + SourceLines source = scanner.getSource(start, end); + String content = source.getContent(); + if (content.isEmpty()) { + return new HeadingParser(level, SourceLines.empty()); + } + return new HeadingParser(level, source); + } + + // spec: A setext heading underline is a sequence of = characters or a sequence of - characters, with no more than + // 3 spaces indentation and any number of trailing spaces. + @SuppressWarnings("fallthrough") private static int getSetextHeadingLevel(CharSequence line, int index) { + switch (line.charAt(index)) { + case '=': + if (isSetextHeadingRest(line, index + 1, '=')) { + return 1; + } + case '-': + if (isSetextHeadingRest(line, index + 1, '-')) { + return 2; + } + } + return 0; + } + + private static boolean isSetextHeadingRest(CharSequence line, int index, char marker) { + int afterMarker = Parsing.skip(marker, line, index, line.length()); + int afterSpace = Parsing.skipSpaceTab(line, afterMarker, line.length()); + return afterSpace >= line.length(); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/HtmlBlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/HtmlBlockParser.java new file mode 100644 index 0000000000000..fe467f825e125 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/HtmlBlockParser.java @@ -0,0 +1,164 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.HtmlBlock; +import jdk.internal.org.commonmark.node.Paragraph; +import jdk.internal.org.commonmark.parser.SourceLine; +import jdk.internal.org.commonmark.parser.block.*; + +import java.util.regex.Pattern; + +public class HtmlBlockParser extends AbstractBlockParser { + + private static final Pattern[][] BLOCK_PATTERNS = new Pattern[][]{ + {null, null}, // not used (no type 0) + { + Pattern.compile("^<(?:script|pre|style|textarea)(?:\\s|>|$)", Pattern.CASE_INSENSITIVE), + Pattern.compile("", Pattern.CASE_INSENSITIVE) + }, + { + Pattern.compile("^") + }, + { + Pattern.compile("^<[?]"), + Pattern.compile("\\?>") + }, + { + Pattern.compile("^") + }, + { + Pattern.compile("^") + }, + { + Pattern.compile("^]|$)", Pattern.CASE_INSENSITIVE), + null // terminated by blank line + }, + { + Pattern.compile("^(?:" + Parsing.OPENTAG + '|' + Parsing.CLOSETAG + ")\\s*$", Pattern.CASE_INSENSITIVE), + null // terminated by blank line + } + }; + + private final HtmlBlock block = new HtmlBlock(); + private final Pattern closingPattern; + + private boolean finished = false; + private BlockContent content = new BlockContent(); + + private HtmlBlockParser(Pattern closingPattern) { + this.closingPattern = closingPattern; + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + if (finished) { + return BlockContinue.none(); + } + + // Blank line ends type 6 and type 7 blocks + if (state.isBlank() && closingPattern == null) { + return BlockContinue.none(); + } else { + return BlockContinue.atIndex(state.getIndex()); + } + } + + @Override + public void addLine(SourceLine line) { + content.add(line.getContent()); + + if (closingPattern != null && closingPattern.matcher(line.getContent()).find()) { + finished = true; + } + } + + @Override + public void closeBlock() { + block.setLiteral(content.getString()); + content = null; + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + int nextNonSpace = state.getNextNonSpaceIndex(); + CharSequence line = state.getLine().getContent(); + + if (state.getIndent() < 4 && line.charAt(nextNonSpace) == '<') { + for (int blockType = 1; blockType <= 7; blockType++) { + // Type 7 can not interrupt a paragraph (not even a lazy one) + if (blockType == 7 && ( + matchedBlockParser.getMatchedBlockParser().getBlock() instanceof Paragraph || + state.getActiveBlockParser().canHaveLazyContinuationLines())) { + continue; + } + Pattern opener = BLOCK_PATTERNS[blockType][0]; + Pattern closer = BLOCK_PATTERNS[blockType][1]; + boolean matches = opener.matcher(line.subSequence(nextNonSpace, line.length())).find(); + if (matches) { + return BlockStart.of(new HtmlBlockParser(closer)).atIndex(state.getIndex()); + } + } + } + return BlockStart.none(); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/IndentedCodeBlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/IndentedCodeBlockParser.java new file mode 100644 index 0000000000000..ffbd6a24d8c17 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/IndentedCodeBlockParser.java @@ -0,0 +1,104 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.IndentedCodeBlock; +import jdk.internal.org.commonmark.node.Paragraph; +import jdk.internal.org.commonmark.parser.SourceLine; +import jdk.internal.org.commonmark.parser.block.*; + +import java.util.ArrayList; +import java.util.List; + +public class IndentedCodeBlockParser extends AbstractBlockParser { + + private final IndentedCodeBlock block = new IndentedCodeBlock(); + private final List lines = new ArrayList<>(); + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT) { + return BlockContinue.atColumn(state.getColumn() + Parsing.CODE_BLOCK_INDENT); + } else if (state.isBlank()) { + return BlockContinue.atIndex(state.getNextNonSpaceIndex()); + } else { + return BlockContinue.none(); + } + } + + @Override + public void addLine(SourceLine line) { + lines.add(line.getContent()); + } + + @Override + public void closeBlock() { + int lastNonBlank = lines.size() - 1; + while (lastNonBlank >= 0) { + if (!Parsing.isBlank(lines.get(lastNonBlank))) { + break; + } + lastNonBlank--; + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < lastNonBlank + 1; i++) { + sb.append(lines.get(i)); + sb.append('\n'); + } + + String literal = sb.toString(); + block.setLiteral(literal); + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + // An indented code block cannot interrupt a paragraph. + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT && !state.isBlank() && !(state.getActiveBlockParser().getBlock() instanceof Paragraph)) { + return BlockStart.of(new IndentedCodeBlockParser()).atColumn(state.getColumn() + Parsing.CODE_BLOCK_INDENT); + } else { + return BlockStart.none(); + } + } + } +} + diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/InlineParserContextImpl.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/InlineParserContextImpl.java new file mode 100644 index 0000000000000..dab02aeedd16a --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/InlineParserContextImpl.java @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.node.LinkReferenceDefinition; +import jdk.internal.org.commonmark.parser.InlineParserContext; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.util.List; +import java.util.Map; + +public class InlineParserContextImpl implements InlineParserContext { + + private final List delimiterProcessors; + private final LinkReferenceDefinitions linkReferenceDefinitions; + + public InlineParserContextImpl(List delimiterProcessors, + LinkReferenceDefinitions linkReferenceDefinitions) { + this.delimiterProcessors = delimiterProcessors; + this.linkReferenceDefinitions = linkReferenceDefinitions; + } + + @Override + public List getCustomDelimiterProcessors() { + return delimiterProcessors; + } + + @Override + public LinkReferenceDefinition getLinkReferenceDefinition(String label) { + return linkReferenceDefinitions.get(label); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/InlineParserImpl.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/InlineParserImpl.java new file mode 100644 index 0000000000000..14ccbd5ef61d5 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/InlineParserImpl.java @@ -0,0 +1,786 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.inline.Scanner; +import jdk.internal.org.commonmark.internal.inline.*; +import jdk.internal.org.commonmark.internal.util.Escaping; +import jdk.internal.org.commonmark.internal.util.LinkScanner; +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.*; +import jdk.internal.org.commonmark.parser.InlineParser; +import jdk.internal.org.commonmark.parser.InlineParserContext; +import jdk.internal.org.commonmark.parser.SourceLines; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.util.*; + +public class InlineParserImpl implements InlineParser, InlineParserState { + + private final BitSet specialCharacters; + private final Map delimiterProcessors; + private final InlineParserContext context; + private final Map> inlineParsers; + + private Scanner scanner; + private boolean includeSourceSpans; + private int trailingSpaces; + + /** + * Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different + * from the algorithm described in the spec.) + */ + private Delimiter lastDelimiter; + + /** + * Top opening bracket ([ or ![)). + */ + private Bracket lastBracket; + + public InlineParserImpl(InlineParserContext inlineParserContext) { + this.delimiterProcessors = calculateDelimiterProcessors(inlineParserContext.getCustomDelimiterProcessors()); + + this.context = inlineParserContext; + this.inlineParsers = new HashMap<>(); + this.inlineParsers.put('\\', Collections.singletonList(new BackslashInlineParser())); + this.inlineParsers.put('`', Collections.singletonList(new BackticksInlineParser())); + this.inlineParsers.put('&', Collections.singletonList(new EntityInlineParser())); + this.inlineParsers.put('<', Arrays.asList(new AutolinkInlineParser(), new HtmlInlineParser())); + + this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), inlineParsers.keySet()); + } + + public static BitSet calculateSpecialCharacters(Set delimiterCharacters, Set characters) { + BitSet bitSet = new BitSet(); + for (Character c : delimiterCharacters) { + bitSet.set(c); + } + for (Character c : characters) { + bitSet.set(c); + } + bitSet.set('['); + bitSet.set(']'); + bitSet.set('!'); + bitSet.set('\n'); + return bitSet; + } + + public static Map calculateDelimiterProcessors(List delimiterProcessors) { + Map map = new HashMap<>(); + addDelimiterProcessors(Arrays.asList(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map); + addDelimiterProcessors(delimiterProcessors, map); + return map; + } + + @Override + public Scanner scanner() { + return scanner; + } + + private static void addDelimiterProcessors(Iterable delimiterProcessors, Map map) { + for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { + char opening = delimiterProcessor.getOpeningCharacter(); + char closing = delimiterProcessor.getClosingCharacter(); + if (opening == closing) { + DelimiterProcessor old = map.get(opening); + if (old != null && old.getOpeningCharacter() == old.getClosingCharacter()) { + StaggeredDelimiterProcessor s; + if (old instanceof StaggeredDelimiterProcessor) { + s = (StaggeredDelimiterProcessor) old; + } else { + s = new StaggeredDelimiterProcessor(opening); + s.add(old); + } + s.add(delimiterProcessor); + map.put(opening, s); + } else { + addDelimiterProcessorForChar(opening, delimiterProcessor, map); + } + } else { + addDelimiterProcessorForChar(opening, delimiterProcessor, map); + addDelimiterProcessorForChar(closing, delimiterProcessor, map); + } + } + } + + private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterProcessor toAdd, Map delimiterProcessors) { + DelimiterProcessor existing = delimiterProcessors.put(delimiterChar, toAdd); + if (existing != null) { + throw new IllegalArgumentException("Delimiter processor conflict with delimiter char '" + delimiterChar + "'"); + } + } + + /** + * Parse content in block into inline children, appending them to the block node. + */ + @Override + public void parse(SourceLines lines, Node block) { + reset(lines); + + while (true) { + List nodes = parseInline(); + if (nodes != null) { + for (Node node : nodes) { + block.appendChild(node); + } + } else { + break; + } + } + + processDelimiters(null); + mergeChildTextNodes(block); + } + + void reset(SourceLines lines) { + this.scanner = Scanner.of(lines); + this.includeSourceSpans = !lines.getSourceSpans().isEmpty(); + this.trailingSpaces = 0; + this.lastDelimiter = null; + this.lastBracket = null; + } + + private Text text(SourceLines sourceLines) { + Text text = new Text(sourceLines.getContent()); + text.setSourceSpans(sourceLines.getSourceSpans()); + return text; + } + + /** + * Parse the next inline element in subject, advancing our position. + * On success, return the new inline node. + * On failure, return null. + */ + private List parseInline() { + char c = scanner.peek(); + + switch (c) { + case '[': + return Collections.singletonList(parseOpenBracket()); + case '!': + return Collections.singletonList(parseBang()); + case ']': + return Collections.singletonList(parseCloseBracket()); + case '\n': + return Collections.singletonList(parseLineBreak()); + case Scanner.END: + return null; + } + + // No inline parser, delimiter or other special handling. + if (!specialCharacters.get(c)) { + return Collections.singletonList(parseText()); + } + + List inlineParsers = this.inlineParsers.get(c); + if (inlineParsers != null) { + Position position = scanner.position(); + for (InlineContentParser inlineParser : inlineParsers) { + ParsedInline parsedInline = inlineParser.tryParse(this); + if (parsedInline instanceof ParsedInlineImpl) { + ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; + Node node = parsedInlineImpl.getNode(); + scanner.setPosition(parsedInlineImpl.getPosition()); + if (includeSourceSpans && node.getSourceSpans().isEmpty()) { + node.setSourceSpans(scanner.getSource(position, scanner.position()).getSourceSpans()); + } + return Collections.singletonList(node); + } else { + // Reset position + scanner.setPosition(position); + } + } + } + + DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c); + if (delimiterProcessor != null) { + List nodes = parseDelimiters(delimiterProcessor, c); + if (nodes != null) { + return nodes; + } + } + + // If we get here, even for a special/delimiter character, we will just treat it as text. + return Collections.singletonList(parseText()); + } + + /** + * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. + */ + private List parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar); + if (res == null) { + return null; + } + + List characters = res.characters; + + // Add entry to stack for this opener + lastDelimiter = new Delimiter(characters, delimiterChar, res.canOpen, res.canClose, lastDelimiter); + if (lastDelimiter.previous != null) { + lastDelimiter.previous.next = lastDelimiter; + } + + return characters; + } + + /** + * Add open bracket to delimiter stack and add a text node to block's children. + */ + private Node parseOpenBracket() { + Position start = scanner.position(); + scanner.next(); + Position contentPosition = scanner.position(); + + Text node = text(scanner.getSource(start, contentPosition)); + + // Add entry to stack for this opener + addBracket(Bracket.link(node, start, contentPosition, lastBracket, lastDelimiter)); + + return node; + } + + /** + * If next character is [, and ! delimiter to delimiter stack and add a text node to block's children. + * Otherwise just add a text node. + */ + private Node parseBang() { + Position start = scanner.position(); + scanner.next(); + if (scanner.next('[')) { + Position contentPosition = scanner.position(); + Text node = text(scanner.getSource(start, contentPosition)); + + // Add entry to stack for this opener + addBracket(Bracket.image(node, start, contentPosition, lastBracket, lastDelimiter)); + return node; + } else { + return text(scanner.getSource(start, scanner.position())); + } + } + + /** + * Try to match close bracket against an opening in the delimiter stack. Return either a link or image, or a + * plain [ character. If there is a matching delimiter, remove it from the delimiter stack. + */ + private Node parseCloseBracket() { + Position beforeClose = scanner.position(); + scanner.next(); + Position afterClose = scanner.position(); + + // Get previous `[` or `![` + Bracket opener = lastBracket; + if (opener == null) { + // No matching opener, just return a literal. + return text(scanner.getSource(beforeClose, afterClose)); + } + + if (!opener.allowed) { + // Matching opener but it's not allowed, just return a literal. + removeLastBracket(); + return text(scanner.getSource(beforeClose, afterClose)); + } + + // Check to see if we have a link/image + String dest = null; + String title = null; + + // Maybe a inline link like `[foo](/uri "title")` + if (scanner.next('(')) { + scanner.whitespace(); + dest = parseLinkDestination(scanner); + if (dest == null) { + scanner.setPosition(afterClose); + } else { + int whitespace = scanner.whitespace(); + // title needs a whitespace before + if (whitespace >= 1) { + title = parseLinkTitle(scanner); + scanner.whitespace(); + } + if (!scanner.next(')')) { + // Don't have a closing `)`, so it's not a destination and title -> reset. + // Note that something like `[foo](` could be valid, `(` will just be text. + scanner.setPosition(afterClose); + dest = null; + title = null; + } + } + } + + // Maybe a reference link like `[foo][bar]`, `[foo][]` or `[foo]`. + // Note that even `[foo](` could be a valid link if there's a reference, which is why this is not just an `else` + // here. + if (dest == null) { + // See if there's a link label like `[bar]` or `[]` + String ref = parseLinkLabel(scanner); + if (ref == null) { + scanner.setPosition(afterClose); + } + if ((ref == null || ref.isEmpty()) && !opener.bracketAfter) { + // If the second label is empty `[foo][]` or missing `[foo]`, then the first label is the reference. + // But it can only be a reference when there's no (unescaped) bracket in it. + // If there is, we don't even need to try to look up the reference. This is an optimization. + ref = scanner.getSource(opener.contentPosition, beforeClose).getContent(); + } + + if (ref != null) { + LinkReferenceDefinition definition = context.getLinkReferenceDefinition(ref); + if (definition != null) { + dest = definition.getDestination(); + title = definition.getTitle(); + } + } + } + + if (dest != null) { + // If we got here, we have a link or image + Node linkOrImage = opener.image ? new Image(dest, title) : new Link(dest, title); + + // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link + Node node = opener.node.getNext(); + while (node != null) { + Node next = node.getNext(); + linkOrImage.appendChild(node); + node = next; + } + + if (includeSourceSpans) { + linkOrImage.setSourceSpans(scanner.getSource(opener.markerPosition, scanner.position()).getSourceSpans()); + } + + // Process delimiters such as emphasis inside link/image + processDelimiters(opener.previousDelimiter); + mergeChildTextNodes(linkOrImage); + // We don't need the corresponding text node anymore, we turned it into a link/image node + opener.node.unlink(); + removeLastBracket(); + + // Links within links are not allowed. We found this link, so there can be no other link around it. + if (!opener.image) { + Bracket bracket = lastBracket; + while (bracket != null) { + if (!bracket.image) { + // Disallow link opener. It will still get matched, but will not result in a link. + bracket.allowed = false; + } + bracket = bracket.previous; + } + } + + return linkOrImage; + + } else { + // No link or image, parse just the bracket as text and continue + removeLastBracket(); + + scanner.setPosition(afterClose); + return text(scanner.getSource(beforeClose, afterClose)); + } + } + + private void addBracket(Bracket bracket) { + if (lastBracket != null) { + lastBracket.bracketAfter = true; + } + lastBracket = bracket; + } + + private void removeLastBracket() { + lastBracket = lastBracket.previous; + } + + /** + * Attempt to parse link destination, returning the string or null if no match. + */ + private String parseLinkDestination(Scanner scanner) { + char delimiter = scanner.peek(); + Position start = scanner.position(); + if (!LinkScanner.scanLinkDestination(scanner)) { + return null; + } + + String dest; + if (delimiter == '<') { + // chop off surrounding <..>: + String rawDestination = scanner.getSource(start, scanner.position()).getContent(); + dest = rawDestination.substring(1, rawDestination.length() - 1); + } else { + dest = scanner.getSource(start, scanner.position()).getContent(); + } + + return Escaping.unescapeString(dest); + } + + /** + * Attempt to parse link title (sans quotes), returning the string or null if no match. + */ + private String parseLinkTitle(Scanner scanner) { + Position start = scanner.position(); + if (!LinkScanner.scanLinkTitle(scanner)) { + return null; + } + + // chop off ', " or parens + String rawTitle = scanner.getSource(start, scanner.position()).getContent(); + String title = rawTitle.substring(1, rawTitle.length() - 1); + return Escaping.unescapeString(title); + } + + /** + * Attempt to parse a link label, returning the label between the brackets or null. + */ + String parseLinkLabel(Scanner scanner) { + if (!scanner.next('[')) { + return null; + } + + Position start = scanner.position(); + if (!LinkScanner.scanLinkLabelContent(scanner)) { + return null; + } + Position end = scanner.position(); + + if (!scanner.next(']')) { + return null; + } + + String content = scanner.getSource(start, end).getContent(); + // spec: A link label can have at most 999 characters inside the square brackets. + if (content.length() > 999) { + return null; + } + + return content; + } + + private Node parseLineBreak() { + scanner.next(); + + if (trailingSpaces >= 2) { + return new HardLineBreak(); + } else { + return new SoftLineBreak(); + } + } + + /** + * Parse the next character as plain text, and possibly more if the following characters are non-special. + */ + private Node parseText() { + Position start = scanner.position(); + scanner.next(); + char c; + while (true) { + c = scanner.peek(); + if (c == Scanner.END || specialCharacters.get(c)) { + break; + } + scanner.next(); + } + + SourceLines source = scanner.getSource(start, scanner.position()); + String content = source.getContent(); + + if (c == '\n') { + // We parsed until the end of the line. Trim any trailing spaces and remember them (for hard line breaks). + int end = Parsing.skipBackwards(' ', content, content.length() - 1, 0) + 1; + trailingSpaces = content.length() - end; + content = content.substring(0, end); + } else if (c == Scanner.END) { + // For the last line, both tabs and spaces are trimmed for some reason (checked with commonmark.js). + int end = Parsing.skipSpaceTabBackwards(content, content.length() - 1, 0) + 1; + content = content.substring(0, end); + } + + Text text = new Text(content); + text.setSourceSpans(source.getSourceSpans()); + return text; + } + + /** + * Scan a sequence of characters with code delimiterChar, and return information about the number of delimiters + * and whether they are positioned such that they can open and/or close emphasis or strong emphasis. + * + * @return information about delimiter run, or {@code null} + */ + private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + int before = scanner.peekPreviousCodePoint(); + Position start = scanner.position(); + + // Quick check to see if we have enough delimiters. + int delimiterCount = scanner.matchMultiple(delimiterChar); + if (delimiterCount < delimiterProcessor.getMinLength()) { + scanner.setPosition(start); + return null; + } + + // We do have enough, extract a text node for each delimiter character. + List delimiters = new ArrayList<>(); + scanner.setPosition(start); + Position positionBefore = start; + while (scanner.next(delimiterChar)) { + delimiters.add(text(scanner.getSource(positionBefore, scanner.position()))); + positionBefore = scanner.position(); + } + + int after = scanner.peekCodePoint(); + + // We could be more lazy here, in most cases we don't need to do every match case. + boolean beforeIsPunctuation = before == Scanner.END || Parsing.isPunctuationCodePoint(before); + boolean beforeIsWhitespace = before == Scanner.END || Parsing.isWhitespaceCodePoint(before); + boolean afterIsPunctuation = after == Scanner.END || Parsing.isPunctuationCodePoint(after); + boolean afterIsWhitespace = after == Scanner.END || Parsing.isWhitespaceCodePoint(after); + + boolean leftFlanking = !afterIsWhitespace && + (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation); + boolean rightFlanking = !beforeIsWhitespace && + (!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation); + boolean canOpen; + boolean canClose; + if (delimiterChar == '_') { + canOpen = leftFlanking && (!rightFlanking || beforeIsPunctuation); + canClose = rightFlanking && (!leftFlanking || afterIsPunctuation); + } else { + canOpen = leftFlanking && delimiterChar == delimiterProcessor.getOpeningCharacter(); + canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingCharacter(); + } + + return new DelimiterData(delimiters, canOpen, canClose); + } + + private void processDelimiters(Delimiter stackBottom) { + + Map openersBottom = new HashMap<>(); + + // find first closer above stackBottom: + Delimiter closer = lastDelimiter; + while (closer != null && closer.previous != stackBottom) { + closer = closer.previous; + } + // move forward, looking for closers, and handling each + while (closer != null) { + char delimiterChar = closer.delimiterChar; + + DelimiterProcessor delimiterProcessor = delimiterProcessors.get(delimiterChar); + if (!closer.canClose() || delimiterProcessor == null) { + closer = closer.next; + continue; + } + + char openingDelimiterChar = delimiterProcessor.getOpeningCharacter(); + + // Found delimiter closer. Now look back for first matching opener. + int usedDelims = 0; + boolean openerFound = false; + boolean potentialOpenerFound = false; + Delimiter opener = closer.previous; + while (opener != null && opener != stackBottom && opener != openersBottom.get(delimiterChar)) { + if (opener.canOpen() && opener.delimiterChar == openingDelimiterChar) { + potentialOpenerFound = true; + usedDelims = delimiterProcessor.process(opener, closer); + if (usedDelims > 0) { + openerFound = true; + break; + } + } + opener = opener.previous; + } + + if (!openerFound) { + if (!potentialOpenerFound) { + // Set lower bound for future searches for openers. + // Only do this when we didn't even have a potential + // opener (one that matches the character and can open). + // If an opener was rejected because of the number of + // delimiters (e.g. because of the "multiple of 3" rule), + // we want to consider it next time because the number + // of delimiters can change as we continue processing. + openersBottom.put(delimiterChar, closer.previous); + if (!closer.canOpen()) { + // We can remove a closer that can't be an opener, + // once we've seen there's no matching opener: + removeDelimiterKeepNode(closer); + } + } + closer = closer.next; + continue; + } + + // Remove number of used delimiters nodes. + for (int i = 0; i < usedDelims; i++) { + Text delimiter = opener.characters.remove(opener.characters.size() - 1); + delimiter.unlink(); + } + for (int i = 0; i < usedDelims; i++) { + Text delimiter = closer.characters.remove(0); + delimiter.unlink(); + } + + removeDelimitersBetween(opener, closer); + + // No delimiter characters left to process, so we can remove delimiter and the now empty node. + if (opener.length() == 0) { + removeDelimiterAndNodes(opener); + } + + if (closer.length() == 0) { + Delimiter next = closer.next; + removeDelimiterAndNodes(closer); + closer = next; + } + } + + // remove all delimiters + while (lastDelimiter != null && lastDelimiter != stackBottom) { + removeDelimiterKeepNode(lastDelimiter); + } + } + + private void removeDelimitersBetween(Delimiter opener, Delimiter closer) { + Delimiter delimiter = closer.previous; + while (delimiter != null && delimiter != opener) { + Delimiter previousDelimiter = delimiter.previous; + removeDelimiterKeepNode(delimiter); + delimiter = previousDelimiter; + } + } + + /** + * Remove the delimiter and the corresponding text node. For used delimiters, e.g. `*` in `*foo*`. + */ + private void removeDelimiterAndNodes(Delimiter delim) { + removeDelimiter(delim); + } + + /** + * Remove the delimiter but keep the corresponding node as text. For unused delimiters such as `_` in `foo_bar`. + */ + private void removeDelimiterKeepNode(Delimiter delim) { + removeDelimiter(delim); + } + + private void removeDelimiter(Delimiter delim) { + if (delim.previous != null) { + delim.previous.next = delim.next; + } + if (delim.next == null) { + // top of stack + lastDelimiter = delim.previous; + } else { + delim.next.previous = delim.previous; + } + } + + private void mergeChildTextNodes(Node node) { + // No children, no need for merging + if (node.getFirstChild() == null) { + return; + } + + mergeTextNodesInclusive(node.getFirstChild(), node.getLastChild()); + } + + private void mergeTextNodesInclusive(Node fromNode, Node toNode) { + Text first = null; + Text last = null; + int length = 0; + + Node node = fromNode; + while (node != null) { + if (node instanceof Text) { + Text text = (Text) node; + if (first == null) { + first = text; + } + length += text.getLiteral().length(); + last = text; + } else { + mergeIfNeeded(first, last, length); + first = null; + last = null; + length = 0; + + mergeChildTextNodes(node); + } + if (node == toNode) { + break; + } + node = node.getNext(); + } + + mergeIfNeeded(first, last, length); + } + + private void mergeIfNeeded(Text first, Text last, int textLength) { + if (first != null && last != null && first != last) { + StringBuilder sb = new StringBuilder(textLength); + sb.append(first.getLiteral()); + SourceSpans sourceSpans = null; + if (includeSourceSpans) { + sourceSpans = new SourceSpans(); + sourceSpans.addAll(first.getSourceSpans()); + } + Node node = first.getNext(); + Node stop = last.getNext(); + while (node != stop) { + sb.append(((Text) node).getLiteral()); + if (sourceSpans != null) { + sourceSpans.addAll(node.getSourceSpans()); + } + + Node unlink = node; + node = node.getNext(); + unlink.unlink(); + } + String literal = sb.toString(); + first.setLiteral(literal); + if (sourceSpans != null) { + first.setSourceSpans(sourceSpans.getSourceSpans()); + } + } + } + + private static class DelimiterData { + + final List characters; + final boolean canClose; + final boolean canOpen; + + DelimiterData(List characters, boolean canOpen, boolean canClose) { + this.characters = characters; + this.canOpen = canOpen; + this.canClose = canClose; + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/LinkReferenceDefinitionParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/LinkReferenceDefinitionParser.java new file mode 100644 index 0000000000000..af03efca7de40 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -0,0 +1,311 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.inline.Position; +import jdk.internal.org.commonmark.internal.inline.Scanner; +import jdk.internal.org.commonmark.internal.util.Escaping; +import jdk.internal.org.commonmark.internal.util.LinkScanner; +import jdk.internal.org.commonmark.node.LinkReferenceDefinition; +import jdk.internal.org.commonmark.node.SourceSpan; +import jdk.internal.org.commonmark.parser.SourceLine; +import jdk.internal.org.commonmark.parser.SourceLines; + +import java.util.ArrayList; +import java.util.List; + +/** + * Parser for link reference definitions at the beginning of a paragraph. + * + * @see Link reference definitions + */ +public class LinkReferenceDefinitionParser { + + private State state = State.START_DEFINITION; + + private final List paragraphLines = new ArrayList<>(); + private final List definitions = new ArrayList<>(); + private final List sourceSpans = new ArrayList<>(); + + private StringBuilder label; + private String destination; + private char titleDelimiter; + private StringBuilder title; + private boolean referenceValid = false; + + public void parse(SourceLine line) { + paragraphLines.add(line); + if (state == State.PARAGRAPH) { + // We're in a paragraph now. Link reference definitions can only appear at the beginning, so once + // we're in a paragraph, there's no going back. + return; + } + + Scanner scanner = Scanner.of(SourceLines.of(line)); + while (scanner.hasNext()) { + boolean success; + switch (state) { + case START_DEFINITION: { + success = startDefinition(scanner); + break; + } + case LABEL: { + success = label(scanner); + break; + } + case DESTINATION: { + success = destination(scanner); + break; + } + case START_TITLE: { + success = startTitle(scanner); + break; + } + case TITLE: { + success = title(scanner); + break; + } + default: { + throw new IllegalStateException("Unknown parsing state: " + state); + } + } + // Parsing failed, which means we fall back to treating text as a paragraph. + if (!success) { + state = State.PARAGRAPH; + return; + } + } + } + + public void addSourceSpan(SourceSpan sourceSpan) { + sourceSpans.add(sourceSpan); + } + + /** + * @return the lines that are normal paragraph content, without newlines + */ + SourceLines getParagraphLines() { + return SourceLines.of(paragraphLines); + } + + List getParagraphSourceSpans() { + return sourceSpans; + } + + List getDefinitions() { + finishReference(); + return definitions; + } + + State getState() { + return state; + } + + private boolean startDefinition(Scanner scanner) { + scanner.whitespace(); + if (!scanner.next('[')) { + return false; + } + + state = State.LABEL; + label = new StringBuilder(); + + if (!scanner.hasNext()) { + label.append('\n'); + } + return true; + } + + private boolean label(Scanner scanner) { + Position start = scanner.position(); + if (!LinkScanner.scanLinkLabelContent(scanner)) { + return false; + } + + label.append(scanner.getSource(start, scanner.position()).getContent()); + + if (!scanner.hasNext()) { + // label might continue on next line + label.append('\n'); + return true; + } else if (scanner.next(']')) { + // end of label + if (!scanner.next(':')) { + return false; + } + + // spec: A link label can have at most 999 characters inside the square brackets. + if (label.length() > 999) { + return false; + } + + String normalizedLabel = Escaping.normalizeLabelContent(label.toString()); + if (normalizedLabel.isEmpty()) { + return false; + } + + state = State.DESTINATION; + + scanner.whitespace(); + return true; + } else { + return false; + } + } + + private boolean destination(Scanner scanner) { + scanner.whitespace(); + Position start = scanner.position(); + if (!LinkScanner.scanLinkDestination(scanner)) { + return false; + } + + String rawDestination = scanner.getSource(start, scanner.position()).getContent(); + destination = rawDestination.startsWith("<") ? + rawDestination.substring(1, rawDestination.length() - 1) : + rawDestination; + + int whitespace = scanner.whitespace(); + if (!scanner.hasNext()) { + // Destination was at end of line, so this is a valid reference for sure (and maybe a title). + // If not at end of line, wait for title to be valid first. + referenceValid = true; + paragraphLines.clear(); + } else if (whitespace == 0) { + // spec: The title must be separated from the link destination by whitespace + return false; + } + + state = State.START_TITLE; + return true; + } + + private boolean startTitle(Scanner scanner) { + scanner.whitespace(); + if (!scanner.hasNext()) { + state = State.START_DEFINITION; + return true; + } + + titleDelimiter = '\0'; + char c = scanner.peek(); + switch (c) { + case '"': + case '\'': + titleDelimiter = c; + break; + case '(': + titleDelimiter = ')'; + break; + } + + if (titleDelimiter != '\0') { + state = State.TITLE; + title = new StringBuilder(); + scanner.next(); + if (!scanner.hasNext()) { + title.append('\n'); + } + } else { + finishReference(); + // There might be another reference instead, try that for the same character. + state = State.START_DEFINITION; + } + return true; + } + + private boolean title(Scanner scanner) { + Position start = scanner.position(); + if (!LinkScanner.scanLinkTitleContent(scanner, titleDelimiter)) { + // Invalid title, stop + return false; + } + + title.append(scanner.getSource(start, scanner.position()).getContent()); + + if (!scanner.hasNext()) { + // Title ran until the end of line, so continue on next line (until we find the delimiter) + title.append('\n'); + return true; + } + + // Skip delimiter character + scanner.next(); + scanner.whitespace(); + if (scanner.hasNext()) { + // spec: No further non-whitespace characters may occur on the line. + return false; + } + referenceValid = true; + finishReference(); + paragraphLines.clear(); + + // See if there's another definition. + state = State.START_DEFINITION; + return true; + } + + private void finishReference() { + if (!referenceValid) { + return; + } + + String d = Escaping.unescapeString(destination); + String t = title != null ? Escaping.unescapeString(title.toString()) : null; + LinkReferenceDefinition definition = new LinkReferenceDefinition(label.toString(), d, t); + definition.setSourceSpans(sourceSpans); + sourceSpans.clear(); + definitions.add(definition); + + label = null; + referenceValid = false; + destination = null; + title = null; + } + + enum State { + // Looking for the start of a definition, i.e. `[` + START_DEFINITION, + // Parsing the label, i.e. `foo` within `[foo]` + LABEL, + // Parsing the destination, i.e. `/url` in `[foo]: /url` + DESTINATION, + // Looking for the start of a title, i.e. the first `"` in `[foo]: /url "title"` + START_TITLE, + // Parsing the content of the title, i.e. `title` in `[foo]: /url "title"` + TITLE, + + // End state, no matter what kind of lines we add, they won't be references + PARAGRAPH, + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/LinkReferenceDefinitions.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/LinkReferenceDefinitions.java new file mode 100644 index 0000000000000..e1def1de98e9b --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/LinkReferenceDefinitions.java @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.util.Escaping; +import jdk.internal.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/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ListBlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ListBlockParser.java new file mode 100644 index 0000000000000..7635c3d587957 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ListBlockParser.java @@ -0,0 +1,288 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.*; +import jdk.internal.org.commonmark.parser.block.*; + +public class ListBlockParser extends AbstractBlockParser { + + private final ListBlock block; + + private boolean hadBlankLine; + private int linesAfterBlank; + + public ListBlockParser(ListBlock block) { + this.block = block; + } + + @Override + public boolean isContainer() { + return true; + } + + @Override + public boolean canContain(Block childBlock) { + if (childBlock instanceof ListItem) { + // Another list item is added to this list block. If the previous line was blank, that means this list block + // is "loose" (not tight). + // + // spec: A list is loose if any of its constituent list items are separated by blank lines + if (hadBlankLine && linesAfterBlank == 1) { + block.setTight(false); + hadBlankLine = false; + } + return true; + } else { + return false; + } + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + if (state.isBlank()) { + hadBlankLine = true; + linesAfterBlank = 0; + } else if (hadBlankLine) { + linesAfterBlank++; + } + // List blocks themselves don't have any markers, only list items. So try to stay in the list. + // If there is a block start other than list item, canContain makes sure that this list is closed. + return BlockContinue.atIndex(state.getIndex()); + } + + /** + * Parse a list marker and return data on the marker or null. + */ + private static ListData parseList(CharSequence line, final int markerIndex, final int markerColumn, + final boolean inParagraph) { + ListMarkerData listMarker = parseListMarker(line, markerIndex); + if (listMarker == null) { + return null; + } + ListBlock listBlock = listMarker.listBlock; + + int indexAfterMarker = listMarker.indexAfterMarker; + int markerLength = indexAfterMarker - markerIndex; + // marker doesn't include tabs, so counting them as columns directly is ok + int columnAfterMarker = markerColumn + markerLength; + // the column within the line where the content starts + int contentColumn = columnAfterMarker; + + // See at which column the content starts if there is content + boolean hasContent = false; + int length = line.length(); + for (int i = indexAfterMarker; i < length; i++) { + char c = line.charAt(i); + if (c == '\t') { + contentColumn += Parsing.columnsToNextTabStop(contentColumn); + } else if (c == ' ') { + contentColumn++; + } else { + hasContent = true; + break; + } + } + + if (inParagraph) { + // If the list item is ordered, the start number must be 1 to interrupt a paragraph. + if (listBlock instanceof OrderedList && ((OrderedList) listBlock).getStartNumber() != 1) { + return null; + } + // Empty list item can not interrupt a paragraph. + if (!hasContent) { + return null; + } + } + + if (!hasContent || (contentColumn - columnAfterMarker) > Parsing.CODE_BLOCK_INDENT) { + // If this line is blank or has a code block, default to 1 space after marker + contentColumn = columnAfterMarker + 1; + } + + return new ListData(listBlock, contentColumn); + } + + private static ListMarkerData parseListMarker(CharSequence line, int index) { + char c = line.charAt(index); + switch (c) { + // spec: A bullet list marker is a -, +, or * character. + case '-': + case '+': + case '*': + if (isSpaceTabOrEnd(line, index + 1)) { + BulletList bulletList = new BulletList(); + bulletList.setBulletMarker(c); + return new ListMarkerData(bulletList, index + 1); + } else { + return null; + } + default: + return parseOrderedList(line, index); + } + } + + // spec: An ordered list marker is a sequence of 1\u20139 arabic digits (0-9), followed by either a `.` character or a + // `)` character. + private static ListMarkerData parseOrderedList(CharSequence line, int index) { + int digits = 0; + int length = line.length(); + for (int i = index; i < length; i++) { + char c = line.charAt(i); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + digits++; + if (digits > 9) { + return null; + } + break; + case '.': + case ')': + if (digits >= 1 && isSpaceTabOrEnd(line, i + 1)) { + String number = line.subSequence(index, i).toString(); + OrderedList orderedList = new OrderedList(); + orderedList.setStartNumber(Integer.parseInt(number)); + orderedList.setDelimiter(c); + return new ListMarkerData(orderedList, i + 1); + } else { + return null; + } + default: + return null; + } + } + return null; + } + + private static boolean isSpaceTabOrEnd(CharSequence line, int index) { + if (index < line.length()) { + switch (line.charAt(index)) { + case ' ': + case '\t': + return true; + default: + return false; + } + } else { + return true; + } + } + + /** + * Returns true if the two list items are of the same type, + * with the same delimiter and bullet character. This is used + * in agglomerating list items into lists. + */ + private static boolean listsMatch(ListBlock a, ListBlock b) { + if (a instanceof BulletList && b instanceof BulletList) { + return equals(((BulletList) a).getBulletMarker(), ((BulletList) b).getBulletMarker()); + } else if (a instanceof OrderedList && b instanceof OrderedList) { + return equals(((OrderedList) a).getDelimiter(), ((OrderedList) b).getDelimiter()); + } + return false; + } + + private static boolean equals(Object a, Object b) { + return (a == null) ? (b == null) : a.equals(b); + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + BlockParser matched = matchedBlockParser.getMatchedBlockParser(); + + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT) { + return BlockStart.none(); + } + int markerIndex = state.getNextNonSpaceIndex(); + int markerColumn = state.getColumn() + state.getIndent(); + boolean inParagraph = !matchedBlockParser.getParagraphLines().isEmpty(); + ListData listData = parseList(state.getLine().getContent(), markerIndex, markerColumn, inParagraph); + if (listData == null) { + return BlockStart.none(); + } + + int newColumn = listData.contentColumn; + ListItemParser listItemParser = new ListItemParser(newColumn - state.getColumn()); + + // prepend the list block if needed + if (!(matched instanceof ListBlockParser) || + !(listsMatch((ListBlock) matched.getBlock(), listData.listBlock))) { + + ListBlockParser listBlockParser = new ListBlockParser(listData.listBlock); + // We start out with assuming a list is tight. If we find a blank line, we set it to loose later. + listData.listBlock.setTight(true); + + return BlockStart.of(listBlockParser, listItemParser).atColumn(newColumn); + } else { + return BlockStart.of(listItemParser).atColumn(newColumn); + } + } + } + + private static class ListData { + final ListBlock listBlock; + final int contentColumn; + + ListData(ListBlock listBlock, int contentColumn) { + this.listBlock = listBlock; + this.contentColumn = contentColumn; + } + } + + private static class ListMarkerData { + final ListBlock listBlock; + final int indexAfterMarker; + + ListMarkerData(ListBlock listBlock, int indexAfterMarker) { + this.listBlock = listBlock; + this.indexAfterMarker = indexAfterMarker; + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ListItemParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ListItemParser.java new file mode 100644 index 0000000000000..4f4a5a24fc804 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ListItemParser.java @@ -0,0 +1,105 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.ListBlock; +import jdk.internal.org.commonmark.node.ListItem; +import jdk.internal.org.commonmark.node.Paragraph; +import jdk.internal.org.commonmark.parser.block.AbstractBlockParser; +import jdk.internal.org.commonmark.parser.block.BlockContinue; +import jdk.internal.org.commonmark.parser.block.ParserState; + +public class ListItemParser extends AbstractBlockParser { + + private final ListItem block = new ListItem(); + + /** + * Minimum number of columns that the content has to be indented (relative to the containing block) to be part of + * this list item. + */ + private int contentIndent; + + private boolean hadBlankLine; + + public ListItemParser(int contentIndent) { + this.contentIndent = contentIndent; + } + + @Override + public boolean isContainer() { + return true; + } + + @Override + public boolean canContain(Block childBlock) { + if (hadBlankLine) { + // We saw a blank line in this list item, that means the list block is loose. + // + // spec: if any of its constituent list items directly contain two block-level elements with a blank line + // between them + Block parent = block.getParent(); + if (parent instanceof ListBlock) { + ((ListBlock) parent).setTight(false); + } + } + return true; + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + if (state.isBlank()) { + if (block.getFirstChild() == null) { + // Blank line after empty list item + return BlockContinue.none(); + } else { + Block activeBlock = state.getActiveBlockParser().getBlock(); + // If the active block is a code block, blank lines in it should not affect if the list is tight. + hadBlankLine = activeBlock instanceof Paragraph || activeBlock instanceof ListItem; + return BlockContinue.atIndex(state.getNextNonSpaceIndex()); + } + } + + if (state.getIndent() >= contentIndent) { + return BlockContinue.atColumn(state.getColumn() + contentIndent); + } else { + // Note: We'll hit this case for lazy continuation lines, they will get added later. + return BlockContinue.none(); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ParagraphParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ParagraphParser.java new file mode 100644 index 0000000000000..7cbc28e2cb228 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ParagraphParser.java @@ -0,0 +1,108 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.LinkReferenceDefinition; +import jdk.internal.org.commonmark.node.Paragraph; +import jdk.internal.org.commonmark.node.SourceSpan; +import jdk.internal.org.commonmark.parser.InlineParser; +import jdk.internal.org.commonmark.parser.SourceLine; +import jdk.internal.org.commonmark.parser.SourceLines; +import jdk.internal.org.commonmark.parser.block.AbstractBlockParser; +import jdk.internal.org.commonmark.parser.block.BlockContinue; +import jdk.internal.org.commonmark.parser.block.ParserState; + +import java.util.List; + +public class ParagraphParser extends AbstractBlockParser { + + private final Paragraph block = new Paragraph(); + private final LinkReferenceDefinitionParser linkReferenceDefinitionParser = new LinkReferenceDefinitionParser(); + + @Override + public boolean canHaveLazyContinuationLines() { + return true; + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + if (!state.isBlank()) { + return BlockContinue.atIndex(state.getIndex()); + } else { + return BlockContinue.none(); + } + } + + @Override + public void addLine(SourceLine line) { + linkReferenceDefinitionParser.parse(line); + } + + @Override + public void addSourceSpan(SourceSpan sourceSpan) { + // Some source spans might belong to link reference definitions, others to the paragraph. + // The parser will handle that. + linkReferenceDefinitionParser.addSourceSpan(sourceSpan); + } + + @Override + public void closeBlock() { + if (linkReferenceDefinitionParser.getParagraphLines().isEmpty()) { + block.unlink(); + } else { + block.setSourceSpans(linkReferenceDefinitionParser.getParagraphSourceSpans()); + } + } + + @Override + public void parseInlines(InlineParser inlineParser) { + SourceLines lines = linkReferenceDefinitionParser.getParagraphLines(); + if (!lines.isEmpty()) { + inlineParser.parse(lines, block); + } + } + + public SourceLines getParagraphLines() { + return linkReferenceDefinitionParser.getParagraphLines(); + } + + public List getDefinitions() { + return linkReferenceDefinitionParser.getDefinitions(); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/StaggeredDelimiterProcessor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/StaggeredDelimiterProcessor.java new file mode 100644 index 0000000000000..995f9eac27026 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/StaggeredDelimiterProcessor.java @@ -0,0 +1,108 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterRun; + +import java.util.LinkedList; +import java.util.ListIterator; + +/** + * An implementation of DelimiterProcessor that dispatches all calls to two or more other DelimiterProcessors + * depending on the length of the delimiter run. All child DelimiterProcessors must have different minimum + * lengths. A given delimiter run is dispatched to the child with the largest acceptable minimum length. If no + * child is applicable, the one with the largest minimum length is chosen. + */ +class StaggeredDelimiterProcessor implements DelimiterProcessor { + + private final char delim; + private int minLength = 0; + private LinkedList processors = new LinkedList<>(); // in reverse getMinLength order + + StaggeredDelimiterProcessor(char delim) { + this.delim = delim; + } + + + @Override + public char getOpeningCharacter() { + return delim; + } + + @Override + public char getClosingCharacter() { + return delim; + } + + @Override + public int getMinLength() { + return minLength; + } + + void add(DelimiterProcessor dp) { + final int len = dp.getMinLength(); + ListIterator it = processors.listIterator(); + boolean added = false; + while (it.hasNext()) { + DelimiterProcessor p = it.next(); + int pLen = p.getMinLength(); + if (len > pLen) { + it.previous(); + it.add(dp); + added = true; + break; + } else if (len == pLen) { + throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len + "; conflicting processors: " + p + ", " + dp); + } + } + if (!added) { + processors.add(dp); + this.minLength = len; + } + } + + private DelimiterProcessor findProcessor(int len) { + for (DelimiterProcessor p : processors) { + if (p.getMinLength() <= len) { + return p; + } + } + return processors.getFirst(); + } + + @Override + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + return findProcessor(openingRun.length()).process(openingRun, closingRun); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ThematicBreakParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ThematicBreakParser.java new file mode 100644 index 0000000000000..36d32244b8bba --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ThematicBreakParser.java @@ -0,0 +1,102 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal; + +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.ThematicBreak; +import jdk.internal.org.commonmark.parser.block.*; + +public class ThematicBreakParser extends AbstractBlockParser { + + private final ThematicBreak block = new ThematicBreak(); + + @Override + public Block getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + // a horizontal rule can never container > 1 line, so fail to match + return BlockContinue.none(); + } + + public static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + if (state.getIndent() >= 4) { + return BlockStart.none(); + } + int nextNonSpace = state.getNextNonSpaceIndex(); + CharSequence line = state.getLine().getContent(); + if (isThematicBreak(line, nextNonSpace)) { + return BlockStart.of(new ThematicBreakParser()).atIndex(line.length()); + } else { + return BlockStart.none(); + } + } + } + + // spec: A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching -, _, or * + // characters, each followed optionally by any number of spaces, forms a thematic break. + private static boolean isThematicBreak(CharSequence line, int index) { + int dashes = 0; + int underscores = 0; + int asterisks = 0; + int length = line.length(); + for (int i = index; i < length; i++) { + switch (line.charAt(i)) { + case '-': + dashes++; + break; + case '_': + underscores++; + break; + case '*': + asterisks++; + break; + case ' ': + case '\t': + // Allowed, even between markers + break; + default: + return false; + } + } + + return ((dashes >= 3 && underscores == 0 && asterisks == 0) || + (underscores >= 3 && dashes == 0 && asterisks == 0) || + (asterisks >= 3 && dashes == 0 && underscores == 0)); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/AsteriskDelimiterProcessor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/AsteriskDelimiterProcessor.java new file mode 100644 index 0000000000000..ff205e8bd2204 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/AsteriskDelimiterProcessor.java @@ -0,0 +1,40 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +public class AsteriskDelimiterProcessor extends EmphasisDelimiterProcessor { + + public AsteriskDelimiterProcessor() { + super('*'); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/AutolinkInlineParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/AutolinkInlineParser.java new file mode 100644 index 0000000000000..919392b4614ce --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -0,0 +1,79 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.node.Link; +import jdk.internal.org.commonmark.node.Text; +import jdk.internal.org.commonmark.parser.SourceLines; + +import java.util.regex.Pattern; + +/** + * Attempt to parse an autolink (URL or email in pointy brackets). + */ +public class AutolinkInlineParser implements InlineContentParser { + + private static final Pattern URI = Pattern + .compile("^[a-zA-Z][a-zA-Z0-9.+-]{1,31}:[^<>\u0000-\u0020]*$"); + + private static final Pattern EMAIL = Pattern + .compile("^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$"); + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + Scanner scanner = inlineParserState.scanner(); + scanner.next(); + Position textStart = scanner.position(); + if (scanner.find('>') > 0) { + SourceLines textSource = scanner.getSource(textStart, scanner.position()); + String content = textSource.getContent(); + scanner.next(); + + String destination = null; + if (URI.matcher(content).matches()) { + destination = content; + } else if (EMAIL.matcher(content).matches()) { + destination = "mailto:" + content; + } + + if (destination != null) { + Link link = new Link(destination, null); + Text text = new Text(content); + text.setSourceSpans(textSource.getSourceSpans()); + link.appendChild(text); + return ParsedInline.of(link, scanner.position()); + } + } + return ParsedInline.none(); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/BackslashInlineParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/BackslashInlineParser.java new file mode 100644 index 0000000000000..296f5e7e8d610 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/BackslashInlineParser.java @@ -0,0 +1,67 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.internal.util.Escaping; +import jdk.internal.org.commonmark.node.HardLineBreak; +import jdk.internal.org.commonmark.node.Node; +import jdk.internal.org.commonmark.node.Text; + +import java.util.regex.Pattern; + +/** + * Parse a backslash-escaped special character, adding either the escaped character, a hard line break + * (if the backslash is followed by a newline), or a literal backslash to the block's children. + */ +public class BackslashInlineParser implements InlineContentParser { + + private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + Scanner scanner = inlineParserState.scanner(); + // Backslash + scanner.next(); + + char next = scanner.peek(); + if (next == '\n') { + scanner.next(); + return ParsedInline.of(new HardLineBreak(), scanner.position()); + } else if (ESCAPABLE.matcher(String.valueOf(next)).matches()) { + scanner.next(); + return ParsedInline.of(new Text(String.valueOf(next)), scanner.position()); + } else { + return ParsedInline.of(new Text("\\"), scanner.position()); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/BackticksInlineParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/BackticksInlineParser.java new file mode 100644 index 0000000000000..b868046acab90 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/BackticksInlineParser.java @@ -0,0 +1,80 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.internal.util.Parsing; +import jdk.internal.org.commonmark.node.Code; +import jdk.internal.org.commonmark.node.Text; +import jdk.internal.org.commonmark.parser.SourceLines; + +/** + * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. + */ +public class BackticksInlineParser implements InlineContentParser { + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + Scanner scanner = inlineParserState.scanner(); + Position start = scanner.position(); + int openingTicks = scanner.matchMultiple('`'); + Position afterOpening = scanner.position(); + + while (scanner.find('`') > 0) { + Position beforeClosing = scanner.position(); + int count = scanner.matchMultiple('`'); + if (count == openingTicks) { + Code node = new Code(); + + String content = scanner.getSource(afterOpening, beforeClosing).getContent(); + content = content.replace('\n', ' '); + + // spec: If the resulting string both begins and ends with a space character, but does not consist + // entirely of space characters, a single space character is removed from the front and back. + if (content.length() >= 3 && + content.charAt(0) == ' ' && + content.charAt(content.length() - 1) == ' ' && + Parsing.hasNonSpace(content)) { + content = content.substring(1, content.length() - 1); + } + + node.setLiteral(content); + return ParsedInline.of(node, scanner.position()); + } + } + + // If we got here, we didn't find a matching closing backtick sequence. + SourceLines source = scanner.getSource(start, afterOpening); + Text text = new Text(source.getContent()); + return ParsedInline.of(text, afterOpening); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java new file mode 100644 index 0000000000000..0a4c3c4514054 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -0,0 +1,98 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.node.*; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterRun; + +public abstract class EmphasisDelimiterProcessor implements DelimiterProcessor { + + private final char delimiterChar; + + protected EmphasisDelimiterProcessor(char delimiterChar) { + this.delimiterChar = delimiterChar; + } + + @Override + public char getOpeningCharacter() { + return delimiterChar; + } + + @Override + public char getClosingCharacter() { + return delimiterChar; + } + + @Override + public int getMinLength() { + return 1; + } + + @Override + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + // "multiple of 3" rule for internal delimiter runs + if ((openingRun.canClose() || closingRun.canOpen()) && + closingRun.originalLength() % 3 != 0 && + (openingRun.originalLength() + closingRun.originalLength()) % 3 == 0) { + return 0; + } + + int usedDelimiters; + Node emphasis; + // calculate actual number of delimiters used from this closer + if (openingRun.length() >= 2 && closingRun.length() >= 2) { + usedDelimiters = 2; + emphasis = new StrongEmphasis(String.valueOf(delimiterChar) + delimiterChar); + } else { + usedDelimiters = 1; + emphasis = new Emphasis(String.valueOf(delimiterChar)); + } + + SourceSpans sourceSpans = SourceSpans.empty(); + sourceSpans.addAllFrom(openingRun.getOpeners(usedDelimiters)); + + Text opener = openingRun.getOpener(); + for (Node node : Nodes.between(opener, closingRun.getCloser())) { + emphasis.appendChild(node); + sourceSpans.addAll(node.getSourceSpans()); + } + + sourceSpans.addAllFrom(closingRun.getClosers(usedDelimiters)); + + emphasis.setSourceSpans(sourceSpans.getSourceSpans()); + opener.insertAfter(emphasis); + + return usedDelimiters; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/EntityInlineParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/EntityInlineParser.java new file mode 100644 index 0000000000000..c6c0fb7e17e4e --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/EntityInlineParser.java @@ -0,0 +1,85 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.internal.util.AsciiMatcher; +import jdk.internal.org.commonmark.internal.util.Html5Entities; +import jdk.internal.org.commonmark.node.Text; + +/** + * Attempts to parse a HTML entity or numeric character reference. + */ +public class EntityInlineParser implements InlineContentParser { + + private static final AsciiMatcher hex = AsciiMatcher.builder().range('0', '9').range('A', 'F').range('a', 'f').build(); + private static final AsciiMatcher dec = AsciiMatcher.builder().range('0', '9').build(); + private static final AsciiMatcher entityStart = AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); + private static final AsciiMatcher entityContinue = entityStart.newBuilder().range('0', '9').build(); + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + Scanner scanner = inlineParserState.scanner(); + Position start = scanner.position(); + // Skip `&` + scanner.next(); + + char c = scanner.peek(); + if (c == '#') { + // Numeric + scanner.next(); + if (scanner.next('x') || scanner.next('X')) { + int digits = scanner.match(hex); + if (1 <= digits && digits <= 6 && scanner.next(';')) { + return entity(scanner, start); + } + } else { + int digits = scanner.match(dec); + if (1 <= digits && digits <= 7 && scanner.next(';')) { + return entity(scanner, start); + } + } + } else if (entityStart.matches(c)) { + scanner.match(entityContinue); + if (scanner.next(';')) { + return entity(scanner, start); + } + } + + return ParsedInline.none(); + } + + private ParsedInline entity(Scanner scanner, Position start) { + String text = scanner.getSource(start, scanner.position()).getContent(); + return ParsedInline.of(new Text(Html5Entities.entityToString(text)), scanner.position()); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/HtmlInlineParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/HtmlInlineParser.java new file mode 100644 index 0000000000000..5414f40eb75f6 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/HtmlInlineParser.java @@ -0,0 +1,232 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.internal.util.AsciiMatcher; +import jdk.internal.org.commonmark.node.HtmlInline; + +/** + * Attempt to parse inline HTML. + */ +public class HtmlInlineParser implements InlineContentParser { + + private static final AsciiMatcher asciiLetter = AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); + + // spec: A tag name consists of an ASCII letter followed by zero or more ASCII letters, digits, or hyphens (-). + private static final AsciiMatcher tagNameStart = asciiLetter; + private static final AsciiMatcher tagNameContinue = tagNameStart.newBuilder().range('0', '9').c('-').build(); + + // spec: An attribute name consists of an ASCII letter, _, or :, followed by zero or more ASCII letters, digits, + // _, ., :, or -. (Note: This is the XML specification restricted to ASCII. HTML5 is laxer.) + private static final AsciiMatcher attributeStart = asciiLetter.newBuilder().c('_').c(':').build(); + private static final AsciiMatcher attributeContinue = attributeStart.newBuilder().range('0', '9').c('.').c('-').build(); + // spec: An unquoted attribute value is a nonempty string of characters not including whitespace, ", ', =, <, >, or `. + private static final AsciiMatcher attributeValueEnd = AsciiMatcher.builder() + .c(' ').c('\t').c('\n').c('\u000B').c('\f').c('\r') + .c('"').c('\'').c('=').c('<').c('>').c('`') + .build(); + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + Scanner scanner = inlineParserState.scanner(); + Position start = scanner.position(); + // Skip over `<` + scanner.next(); + + char c = scanner.peek(); + if (tagNameStart.matches(c)) { + if (tryOpenTag(scanner)) { + return htmlInline(start, scanner); + } + } else if (c == '/') { + if (tryClosingTag(scanner)) { + return htmlInline(start, scanner); + } + } else if (c == '?') { + if (tryProcessingInstruction(scanner)) { + return htmlInline(start, scanner); + } + } else if (c == '!') { + // comment, declaration or CDATA + scanner.next(); + c = scanner.peek(); + if (c == '-') { + if (tryComment(scanner)) { + return htmlInline(start, scanner); + } + } else if (c == '[') { + if (tryCdata(scanner)) { + return htmlInline(start, scanner); + } + } else if (asciiLetter.matches(c)) { + if (tryDeclaration(scanner)) { + return htmlInline(start, scanner); + } + } + } + + return ParsedInline.none(); + } + + private static ParsedInline htmlInline(Position start, Scanner scanner) { + String text = scanner.getSource(start, scanner.position()).getContent(); + HtmlInline node = new HtmlInline(); + node.setLiteral(text); + return ParsedInline.of(node, scanner.position()); + } + + private static boolean tryOpenTag(Scanner scanner) { + // spec: An open tag consists of a < character, a tag name, zero or more attributes, optional whitespace, + // an optional / character, and a > character. + scanner.next(); + scanner.match(tagNameContinue); + boolean whitespace = scanner.whitespace() >= 1; + // spec: An attribute consists of whitespace, an attribute name, and an optional attribute value specification. + while (whitespace && scanner.match(attributeStart) >= 1) { + scanner.match(attributeContinue); + // spec: An attribute value specification consists of optional whitespace, a = character, + // optional whitespace, and an attribute value. + whitespace = scanner.whitespace() >= 1; + if (scanner.next('=')) { + scanner.whitespace(); + char valueStart = scanner.peek(); + if (valueStart == '\'') { + scanner.next(); + if (scanner.find('\'') < 0) { + return false; + } + scanner.next(); + } else if (valueStart == '"') { + scanner.next(); + if (scanner.find('"') < 0) { + return false; + } + scanner.next(); + } else { + if (scanner.find(attributeValueEnd) <= 0) { + return false; + } + } + + // Whitespace is required between attributes + whitespace = scanner.whitespace() >= 1; + } + } + + scanner.next('/'); + return scanner.next('>'); + } + + private static boolean tryClosingTag(Scanner scanner) { + // spec: A closing tag consists of the string . + scanner.next(); + if (scanner.match(tagNameStart) >= 1) { + scanner.match(tagNameContinue); + scanner.whitespace(); + return scanner.next('>'); + } + return false; + } + + private static boolean tryProcessingInstruction(Scanner scanner) { + // spec: A processing instruction consists of the string , + // and the string ?>. + scanner.next(); + while (scanner.find('?') > 0) { + scanner.next(); + if (scanner.next('>')) { + return true; + } + } + return false; + } + + private static boolean tryComment(Scanner scanner) { + // spec: An HTML comment consists of , where text does not start with > or ->, does not end + // with -, and does not contain --. (See the HTML5 spec.) + + // Skip first `-` + scanner.next(); + if (!scanner.next('-')) { + return false; + } + + if (scanner.next('>') || scanner.next("->")) { + return false; + } + + while (scanner.find('-') >= 0) { + if (scanner.next("--")) { + return scanner.next('>'); + } else { + scanner.next(); + } + } + + return false; + } + + private static boolean tryCdata(Scanner scanner) { + // spec: A CDATA section consists of the string , + // and the string ]]>. + + // Skip `[` + scanner.next(); + + if (scanner.next("CDATA[")) { + while (scanner.find(']') >= 0) { + if (scanner.next("]]>")) { + return true; + } else { + scanner.next(); + } + } + } + + return false; + } + + private static boolean tryDeclaration(Scanner scanner) { + // spec: A declaration consists of the string , and the character >. + scanner.match(asciiLetter); + if (scanner.whitespace() <= 0) { + return false; + } + if (scanner.find('>') >= 0) { + scanner.next(); + return true; + } + return false; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/InlineContentParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/InlineContentParser.java new file mode 100644 index 0000000000000..66feff21784db --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/InlineContentParser.java @@ -0,0 +1,38 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +public interface InlineContentParser { + + ParsedInline tryParse(InlineParserState inlineParserState); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/InlineParserState.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/InlineParserState.java new file mode 100644 index 0000000000000..3e970b0c26a47 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/InlineParserState.java @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +public interface InlineParserState { + + /** + * Return a scanner for the input for the current position (on the character that the inline parser registered + * interest for). + *

+ * Note that this always returns the same instance, if you want to backtrack you need to use + * {@link Scanner#position()} and {@link Scanner#setPosition(Position)}. + */ + Scanner scanner(); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/ParsedInline.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/ParsedInline.java new file mode 100644 index 0000000000000..e23b020d674f7 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/ParsedInline.java @@ -0,0 +1,55 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.node.Node; + +public abstract class ParsedInline { + + protected ParsedInline() { + } + + public static ParsedInline none() { + return null; + } + + public static ParsedInline of(Node node, Position position) { + if (node == null) { + throw new NullPointerException("node must not be null"); + } + if (position == null) { + throw new NullPointerException("position must not be null"); + } + return new ParsedInlineImpl(node, position); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/ParsedInlineImpl.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/ParsedInlineImpl.java new file mode 100644 index 0000000000000..236df3ad52a13 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/ParsedInlineImpl.java @@ -0,0 +1,53 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.node.Node; + +public class ParsedInlineImpl extends ParsedInline { + private final Node node; + private final Position position; + + ParsedInlineImpl(Node node, Position position) { + this.node = node; + this.position = position; + } + + public Node getNode() { + return node; + } + + public Position getPosition() { + return position; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/Position.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/Position.java new file mode 100644 index 0000000000000..fa3767083e37e --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/Position.java @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +/** + * Position within a {@link Scanner}. This is intentionally kept opaque so as not to expose the internal structure of + * the Scanner. + */ +public class Position { + + final int lineIndex; + final int index; + + Position(int lineIndex, int index) { + this.lineIndex = lineIndex; + this.index = index; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/Scanner.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/Scanner.java new file mode 100644 index 0000000000000..a32d0564f7d49 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/Scanner.java @@ -0,0 +1,313 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +import jdk.internal.org.commonmark.internal.util.CharMatcher; +import jdk.internal.org.commonmark.node.SourceSpan; +import jdk.internal.org.commonmark.parser.SourceLine; +import jdk.internal.org.commonmark.parser.SourceLines; + +import java.util.List; + +public class Scanner { + + /** + * Character representing the end of input source (or outside of the text in case of the "previous" methods). + *

+ * Note that we can use NULL to represent this because CommonMark does not allow those in the input (we replace them + * in the beginning of parsing). + */ + public static final char END = '\0'; + + // Lines without newlines at the end. The scanner will yield `\n` between lines because they're significant for + // parsing and the final output. There is no `\n` after the last line. + private final List lines; + // Which line we're at. + private int lineIndex; + // The index within the line. If index == length(), we pretend that there's a `\n` and only advance after we yield + // that. + private int index; + + // Current line or "" if at the end of the lines (using "" instead of null saves a null check) + private SourceLine line = SourceLine.of("", null); + private int lineLength = 0; + + Scanner(List lines, int lineIndex, int index) { + this.lines = lines; + this.lineIndex = lineIndex; + this.index = index; + if (!lines.isEmpty()) { + checkPosition(lineIndex, index); + setLine(lines.get(lineIndex)); + } + } + + public static Scanner of(SourceLines lines) { + return new Scanner(lines.getLines(), 0, 0); + } + + public char peek() { + if (index < lineLength) { + return line.getContent().charAt(index); + } else { + if (lineIndex < lines.size() - 1) { + return '\n'; + } else { + // Don't return newline for end of last line + return END; + } + } + } + + public int peekCodePoint() { + if (index < lineLength) { + char c = line.getContent().charAt(index); + if (Character.isHighSurrogate(c) && index + 1 < lineLength) { + char low = line.getContent().charAt(index + 1); + if (Character.isLowSurrogate(low)) { + return Character.toCodePoint(c, low); + } + } + return c; + } else { + if (lineIndex < lines.size() - 1) { + return '\n'; + } else { + // Don't return newline for end of last line + return END; + } + } + } + + public int peekPreviousCodePoint() { + if (index > 0) { + int prev = index - 1; + char c = line.getContent().charAt(prev); + if (Character.isLowSurrogate(c) && prev > 0) { + char high = line.getContent().charAt(prev - 1); + if (Character.isHighSurrogate(high)) { + return Character.toCodePoint(high, c); + } + } + return c; + } else { + if (lineIndex > 0) { + return '\n'; + } else { + return END; + } + } + } + + public boolean hasNext() { + if (index < lineLength) { + return true; + } else { + // No newline at end of last line + return lineIndex < lines.size() - 1; + } + } + + public void next() { + index++; + if (index > lineLength) { + lineIndex++; + if (lineIndex < lines.size()) { + setLine(lines.get(lineIndex)); + } else { + setLine(SourceLine.of("", null)); + } + index = 0; + } + } + + /** + * Check if the specified char is next and advance the position. + * + * @param c the char to check (including newline characters) + * @return true if matched and position was advanced, false otherwise + */ + public boolean next(char c) { + if (peek() == c) { + next(); + return true; + } else { + return false; + } + } + + /** + * Check if we have the specified content on the line and advanced the position. Note that if you want to match + * newline characters, use {@link #next(char)}. + * + * @param content the text content to match on a single line (excluding newline characters) + * @return true if matched and position was advanced, false otherwise + */ + public boolean next(String content) { + if (index < lineLength && index + content.length() <= lineLength) { + // Can't use startsWith because it's not available on CharSequence + for (int i = 0; i < content.length(); i++) { + if (line.getContent().charAt(index + i) != content.charAt(i)) { + return false; + } + } + index += content.length(); + return true; + } else { + return false; + } + } + + public int matchMultiple(char c) { + int count = 0; + while (peek() == c) { + count++; + next(); + } + return count; + } + + public int match(CharMatcher matcher) { + int count = 0; + while (matcher.matches(peek())) { + count++; + next(); + } + return count; + } + + public int whitespace() { + int count = 0; + while (true) { + switch (peek()) { + case ' ': + case '\t': + case '\n': + case '\u000B': + case '\f': + case '\r': + count++; + next(); + break; + default: + return count; + } + } + } + + public int find(char c) { + int count = 0; + while (true) { + char cur = peek(); + if (cur == Scanner.END) { + return -1; + } else if (cur == c) { + return count; + } + count++; + next(); + } + } + + public int find(CharMatcher matcher) { + int count = 0; + while (true) { + char c = peek(); + if (c == END) { + return -1; + } else if (matcher.matches(c)) { + return count; + } + count++; + next(); + } + } + + // Don't expose the int index, because it would be good if we could switch input to a List of lines later + // instead of one contiguous String. + public Position position() { + return new Position(lineIndex, index); + } + + public void setPosition(Position position) { + checkPosition(position.lineIndex, position.index); + this.lineIndex = position.lineIndex; + this.index = position.index; + setLine(lines.get(this.lineIndex)); + } + + // For cases where the caller appends the result to a StringBuilder, we could offer another method to avoid some + // unnecessary copying. + public SourceLines getSource(Position begin, Position end) { + if (begin.lineIndex == end.lineIndex) { + // Shortcut for common case of text from a single line + SourceLine line = lines.get(begin.lineIndex); + CharSequence newContent = line.getContent().subSequence(begin.index, end.index); + SourceSpan newSourceSpan = null; + SourceSpan sourceSpan = line.getSourceSpan(); + if (sourceSpan != null) { + newSourceSpan = SourceSpan.of(sourceSpan.getLineIndex(), sourceSpan.getColumnIndex() + begin.index, newContent.length()); + } + return SourceLines.of(SourceLine.of(newContent, newSourceSpan)); + } else { + SourceLines sourceLines = SourceLines.empty(); + + SourceLine firstLine = lines.get(begin.lineIndex); + sourceLines.addLine(firstLine.substring(begin.index, firstLine.getContent().length())); + + // Lines between begin and end (we are appending the full line) + for (int line = begin.lineIndex + 1; line < end.lineIndex; line++) { + sourceLines.addLine(lines.get(line)); + } + + SourceLine lastLine = lines.get(end.lineIndex); + sourceLines.addLine(lastLine.substring(0, end.index)); + return sourceLines; + } + } + + private void setLine(SourceLine line) { + this.line = line; + this.lineLength = line.getContent().length(); + } + + private void checkPosition(int lineIndex, int index) { + if (lineIndex < 0 || lineIndex >= lines.size()) { + throw new IllegalArgumentException("Line index " + lineIndex + " out of range, number of lines: " + lines.size()); + } + SourceLine line = lines.get(lineIndex); + if (index < 0 || index > line.getContent().length()) { + throw new IllegalArgumentException("Index " + index + " out of range, line length: " + line.getContent().length()); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/UnderscoreDelimiterProcessor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/UnderscoreDelimiterProcessor.java new file mode 100644 index 0000000000000..be297441d974c --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/UnderscoreDelimiterProcessor.java @@ -0,0 +1,40 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.inline; + +public class UnderscoreDelimiterProcessor extends EmphasisDelimiterProcessor { + + public UnderscoreDelimiterProcessor() { + super('_'); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/NodeRendererMap.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/NodeRendererMap.java new file mode 100644 index 0000000000000..523609f21de00 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/NodeRendererMap.java @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.renderer; + +import jdk.internal.org.commonmark.node.Node; +import jdk.internal.org.commonmark.renderer.NodeRenderer; + +import java.util.HashMap; +import java.util.Map; + +public class NodeRendererMap { + + private final Map, NodeRenderer> renderers = new HashMap<>(32); + + public void add(NodeRenderer nodeRenderer) { + for (Class nodeType : nodeRenderer.getNodeTypes()) { + // Overwrite existing renderer + renderers.put(nodeType, nodeRenderer); + } + } + + public void render(Node node) { + NodeRenderer nodeRenderer = renderers.get(node.getClass()); + if (nodeRenderer != null) { + nodeRenderer.render(node); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/BulletListHolder.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/BulletListHolder.java new file mode 100644 index 0000000000000..f6faad207901e --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/BulletListHolder.java @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.renderer.text; + +import jdk.internal.org.commonmark.node.BulletList; + +public class BulletListHolder extends ListHolder { + private final char marker; + + public BulletListHolder(ListHolder parent, BulletList list) { + super(parent); + marker = list.getBulletMarker(); + } + + public char getMarker() { + return marker; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/ListHolder.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/ListHolder.java new file mode 100644 index 0000000000000..6334f4cffadc0 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/ListHolder.java @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.renderer.text; + +public abstract class ListHolder { + private static final String INDENT_DEFAULT = " "; + private static final String INDENT_EMPTY = ""; + + private final ListHolder parent; + private final String indent; + + ListHolder(ListHolder parent) { + this.parent = parent; + + if (parent != null) { + indent = parent.indent + INDENT_DEFAULT; + } else { + indent = INDENT_EMPTY; + } + } + + public ListHolder getParent() { + return parent; + } + + public String getIndent() { + return indent; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/OrderedListHolder.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/OrderedListHolder.java new file mode 100644 index 0000000000000..d7b77a05ebe85 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/renderer/text/OrderedListHolder.java @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.renderer.text; + +import jdk.internal.org.commonmark.node.OrderedList; + +public class OrderedListHolder extends ListHolder { + private final char delimiter; + private int counter; + + public OrderedListHolder(ListHolder parent, OrderedList list) { + super(parent); + delimiter = list.getDelimiter(); + counter = list.getStartNumber(); + } + + public char getDelimiter() { + return delimiter; + } + + public int getCounter() { + return counter; + } + + public void increaseCounter() { + counter++; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/AsciiMatcher.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/AsciiMatcher.java new file mode 100644 index 0000000000000..915c3078b7ab4 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/AsciiMatcher.java @@ -0,0 +1,83 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.util; + +import java.util.BitSet; + +public class AsciiMatcher implements CharMatcher { + private final BitSet set; + + private AsciiMatcher(Builder builder) { + this.set = builder.set; + } + + @Override + public boolean matches(char c) { + return set.get(c); + } + + public Builder newBuilder() { + return new Builder((BitSet) set.clone()); + } + + public static Builder builder() { + return new Builder(new BitSet()); + } + + public static class Builder { + private final BitSet set; + + private Builder(BitSet set) { + this.set = set; + } + + public Builder c(char c) { + if (c > 127) { + throw new IllegalArgumentException("Can only match ASCII characters"); + } + set.set(c); + return this; + } + + public Builder range(char from, char toInclusive) { + for (char c = from; c <= toInclusive; c++) { + c(c); + } + return this; + } + + public AsciiMatcher build() { + return new AsciiMatcher(this); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/CharMatcher.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/CharMatcher.java new file mode 100644 index 0000000000000..e74cb314ff3c2 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/CharMatcher.java @@ -0,0 +1,38 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.util; + +public interface CharMatcher { + + boolean matches(char c); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Escaping.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Escaping.java new file mode 100644 index 0000000000000..939a4c69860c5 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Escaping.java @@ -0,0 +1,183 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.util; + +import java.nio.charset.Charset; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Escaping { + + public static final String ESCAPABLE = "[!\"#$%&\'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~-]"; + + public static final String ENTITY = "&(?:#x[a-f0-9]{1,6}|#[0-9]{1,7}|[a-z][a-z0-9]{1,31});"; + + private static final Pattern BACKSLASH_OR_AMP = Pattern.compile("[\\\\&]"); + + private static final Pattern ENTITY_OR_ESCAPED_CHAR = + Pattern.compile("\\\\" + ESCAPABLE + '|' + ENTITY, Pattern.CASE_INSENSITIVE); + + // From RFC 3986 (see "reserved", "unreserved") except don't escape '[' or ']' to be compatible with JS encodeURI + private static final Pattern ESCAPE_IN_URI = + Pattern.compile("(%[a-fA-F0-9]{0,2}|[^:/?#@!$&'()*+,;=a-zA-Z0-9\\-._~])"); + + private static final char[] HEX_DIGITS = + new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private static final Pattern WHITESPACE = Pattern.compile("[ \t\r\n]+"); + + private static final Replacer UNESCAPE_REPLACER = new Replacer() { + @Override + public void replace(String input, StringBuilder sb) { + if (input.charAt(0) == '\\') { + sb.append(input, 1, input.length()); + } else { + sb.append(Html5Entities.entityToString(input)); + } + } + }; + + private static final Replacer URI_REPLACER = new Replacer() { + @Override + public void replace(String input, StringBuilder sb) { + if (input.startsWith("%")) { + if (input.length() == 3) { + // Already percent-encoded, preserve + sb.append(input); + } else { + // %25 is the percent-encoding for % + sb.append("%25"); + sb.append(input, 1, input.length()); + } + } else { + byte[] bytes = input.getBytes(Charset.forName("UTF-8")); + for (byte b : bytes) { + sb.append('%'); + sb.append(HEX_DIGITS[(b >> 4) & 0xF]); + sb.append(HEX_DIGITS[b & 0xF]); + } + } + } + }; + + public static String escapeHtml(String input) { + // Avoid building a new string in the majority of cases (nothing to escape) + StringBuilder sb = null; + + loop: + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + String replacement; + switch (c) { + case '&': + replacement = "&"; + break; + case '<': + replacement = "<"; + break; + case '>': + replacement = ">"; + break; + case '\"': + replacement = """; + break; + default: + if (sb != null) { + sb.append(c); + } + continue loop; + } + if (sb == null) { + sb = new StringBuilder(); + sb.append(input, 0, i); + } + sb.append(replacement); + } + + return sb != null ? sb.toString() : input; + } + + /** + * Replace entities and backslash escapes with literal characters. + */ + public static String unescapeString(String s) { + if (BACKSLASH_OR_AMP.matcher(s).find()) { + return replaceAll(ENTITY_OR_ESCAPED_CHAR, s, UNESCAPE_REPLACER); + } else { + return s; + } + } + + public static String percentEncodeUrl(String s) { + return replaceAll(ESCAPE_IN_URI, s, URI_REPLACER); + } + + public static String normalizeLabelContent(String input) { + String trimmed = input.trim(); + + // This is necessary to correctly case fold "\u1e9e" to "SS": + // "\u1e9e".toLowerCase(Locale.ROOT) -> "\u00df" + // "\u00df".toUpperCase(Locale.ROOT) -> "SS" + // Note that doing upper first (or only upper without lower) wouldn't work because: + // "\u1e9e".toUpperCase(Locale.ROOT) -> "\u1e9e" + String caseFolded = trimmed.toLowerCase(Locale.ROOT).toUpperCase(Locale.ROOT); + + return WHITESPACE.matcher(caseFolded).replaceAll(" "); + } + + private static String replaceAll(Pattern p, String s, Replacer replacer) { + Matcher matcher = p.matcher(s); + + if (!matcher.find()) { + return s; + } + + StringBuilder sb = new StringBuilder(s.length() + 16); + int lastEnd = 0; + do { + sb.append(s, lastEnd, matcher.start()); + replacer.replace(matcher.group(), sb); + lastEnd = matcher.end(); + } while (matcher.find()); + + if (lastEnd != s.length()) { + sb.append(s, lastEnd, s.length()); + } + return sb.toString(); + } + + private interface Replacer { + void replace(String input, StringBuilder sb); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Html5Entities.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Html5Entities.java new file mode 100644 index 0000000000000..a300cd0438ac3 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Html5Entities.java @@ -0,0 +1,103 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class Html5Entities { + + private static final Map NAMED_CHARACTER_REFERENCES = readEntities(); + private static final String ENTITY_PATH = "/org/commonmark/internal/util/entities.txt"; + + public static String entityToString(String input) { + if (!input.startsWith("&") || !input.endsWith(";")) { + return input; + } + + String value = input.substring(1, input.length() - 1); + if (value.startsWith("#")) { + value = value.substring(1); + int base = 10; + if (value.startsWith("x") || value.startsWith("X")) { + value = value.substring(1); + base = 16; + } + + try { + int codePoint = Integer.parseInt(value, base); + if (codePoint == 0) { + return "\uFFFD"; + } + return new String(Character.toChars(codePoint)); + } catch (IllegalArgumentException e) { + return "\uFFFD"; + } + } else { + String s = NAMED_CHARACTER_REFERENCES.get(value); + if (s != null) { + return s; + } else { + return input; + } + } + } + + private static Map readEntities() { + Map entities = new HashMap<>(); + InputStream stream = Html5Entities.class.getResourceAsStream(ENTITY_PATH); + Charset charset = StandardCharsets.UTF_8; + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, charset))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + if (line.length() == 0) { + continue; + } + int equal = line.indexOf("="); + String key = line.substring(0, equal); + String value = line.substring(equal + 1); + entities.put(key, value); + } + } catch (IOException e) { + throw new IllegalStateException("Failed reading data for HTML named character references", e); + } + entities.put("NewLine", "\n"); + return entities; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/LinkScanner.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/LinkScanner.java new file mode 100644 index 0000000000000..494947abe2092 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/LinkScanner.java @@ -0,0 +1,195 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.util; + +import jdk.internal.org.commonmark.internal.inline.Scanner; + +public class LinkScanner { + + /** + * Attempt to scan the contents of a link label (inside the brackets), stopping after the content or returning false. + * The stopped position can bei either the closing {@code ]}, or the end of the line if the label continues on + * the next line. + */ + public static boolean scanLinkLabelContent(Scanner scanner) { + while (scanner.hasNext()) { + switch (scanner.peek()) { + case '\\': + scanner.next(); + if (Parsing.isEscapable(scanner.peek())) { + scanner.next(); + } + break; + case ']': + return true; + case '[': + // spec: Unescaped square bracket characters are not allowed inside the opening and closing + // square brackets of link labels. + return false; + default: + scanner.next(); + } + } + return true; + } + + /** + * Attempt to scan a link destination, stopping after the destination or returning false. + */ + public static boolean scanLinkDestination(Scanner scanner) { + if (!scanner.hasNext()) { + return false; + } + + if (scanner.next('<')) { + while (scanner.hasNext()) { + switch (scanner.peek()) { + case '\\': + scanner.next(); + if (Parsing.isEscapable(scanner.peek())) { + scanner.next(); + } + break; + case '\n': + case '<': + return false; + case '>': + scanner.next(); + return true; + default: + scanner.next(); + } + } + return false; + } else { + return scanLinkDestinationWithBalancedParens(scanner); + } + } + + public static boolean scanLinkTitle(Scanner scanner) { + if (!scanner.hasNext()) { + return false; + } + + char endDelimiter; + switch (scanner.peek()) { + case '"': + endDelimiter = '"'; + break; + case '\'': + endDelimiter = '\''; + break; + case '(': + endDelimiter = ')'; + break; + default: + return false; + } + scanner.next(); + + if (!scanLinkTitleContent(scanner, endDelimiter)) { + return false; + } + if (!scanner.hasNext()) { + return false; + } + scanner.next(); + return true; + } + + public static boolean scanLinkTitleContent(Scanner scanner, char endDelimiter) { + while (scanner.hasNext()) { + char c = scanner.peek(); + if (c == '\\') { + scanner.next(); + if (Parsing.isEscapable(scanner.peek())) { + scanner.next(); + } + } else if (c == endDelimiter) { + return true; + } else if (endDelimiter == ')' && c == '(') { + // unescaped '(' in title within parens is invalid + return false; + } else { + scanner.next(); + } + } + return true; + } + + // spec: a nonempty sequence of characters that does not start with <, does not include ASCII space or control + // characters, and includes parentheses only if (a) they are backslash-escaped or (b) they are part of a balanced + // pair of unescaped parentheses + private static boolean scanLinkDestinationWithBalancedParens(Scanner scanner) { + int parens = 0; + boolean empty = true; + while (scanner.hasNext()) { + char c = scanner.peek(); + switch (c) { + case ' ': + return !empty; + case '\\': + scanner.next(); + if (Parsing.isEscapable(scanner.peek())) { + scanner.next(); + } + break; + case '(': + parens++; + // Limit to 32 nested parens for pathological cases + if (parens > 32) { + return false; + } + scanner.next(); + break; + case ')': + if (parens == 0) { + return true; + } else { + parens--; + } + scanner.next(); + break; + default: + // or control character + if (Character.isISOControl(c)) { + return !empty; + } + scanner.next(); + break; + } + empty = false; + } + return true; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Parsing.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Parsing.java new file mode 100644 index 0000000000000..afc6a119ee061 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Parsing.java @@ -0,0 +1,278 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.internal.util; + +public class Parsing { + + private static final String TAGNAME = "[A-Za-z][A-Za-z0-9-]*"; + private static final String ATTRIBUTENAME = "[a-zA-Z_:][a-zA-Z0-9:._-]*"; + private static final String UNQUOTEDVALUE = "[^\"'=<>`\\x00-\\x20]+"; + private static final String SINGLEQUOTEDVALUE = "'[^']*'"; + private static final String DOUBLEQUOTEDVALUE = "\"[^\"]*\""; + private static final String ATTRIBUTEVALUE = "(?:" + UNQUOTEDVALUE + "|" + SINGLEQUOTEDVALUE + + "|" + DOUBLEQUOTEDVALUE + ")"; + private static final String ATTRIBUTEVALUESPEC = "(?:" + "\\s*=" + "\\s*" + ATTRIBUTEVALUE + + ")"; + private static final String ATTRIBUTE = "(?:" + "\\s+" + ATTRIBUTENAME + ATTRIBUTEVALUESPEC + + "?)"; + + public static final String OPENTAG = "<" + TAGNAME + ATTRIBUTE + "*" + "\\s*/?>"; + public static final String CLOSETAG = "]"; + + public static int CODE_BLOCK_INDENT = 4; + + public static int columnsToNextTabStop(int column) { + // Tab stop is 4 + return 4 - (column % 4); + } + + public static int find(char c, CharSequence s, int startIndex) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { + if (s.charAt(i) == c) { + return i; + } + } + return -1; + } + + public static int findLineBreak(CharSequence s, int startIndex) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { + switch (s.charAt(i)) { + case '\n': + case '\r': + return i; + } + } + return -1; + } + + public static boolean isBlank(CharSequence s) { + return findNonSpace(s, 0) == -1; + } + + public static boolean hasNonSpace(CharSequence s) { + int length = s.length(); + int skipped = skip(' ', s, 0, length); + return skipped != length; + } + + public static boolean isLetter(CharSequence s, int index) { + int codePoint = Character.codePointAt(s, index); + return Character.isLetter(codePoint); + } + + public static boolean isSpaceOrTab(CharSequence s, int index) { + if (index < s.length()) { + switch (s.charAt(index)) { + case ' ': + case '\t': + return true; + } + } + return false; + } + + public static boolean isEscapable(char c) { + switch (c) { + case '!': + case '"': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case '\\': + case ']': + case '^': + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + return true; + } + return false; + } + + // See https://spec.commonmark.org/0.29/#punctuation-character + public static boolean isPunctuationCodePoint(int codePoint) { + switch (Character.getType(codePoint)) { + case Character.CONNECTOR_PUNCTUATION: + case Character.DASH_PUNCTUATION: + case Character.END_PUNCTUATION: + case Character.FINAL_QUOTE_PUNCTUATION: + case Character.INITIAL_QUOTE_PUNCTUATION: + case Character.OTHER_PUNCTUATION: + case Character.START_PUNCTUATION: + return true; + default: + switch (codePoint) { + case '$': + case '+': + case '<': + case '=': + case '>': + case '^': + case '`': + case '|': + case '~': + return true; + default: + return false; + } + } + } + + public static boolean isWhitespaceCodePoint(int codePoint) { + switch (codePoint) { + case ' ': + case '\t': + case '\r': + case '\n': + case '\f': + return true; + default: + return Character.getType(codePoint) == Character.SPACE_SEPARATOR; + } + } + + /** + * Prepares the input line replacing {@code \0} + */ + public static CharSequence prepareLine(CharSequence line) { + // Avoid building a new string in the majority of cases (no \0) + StringBuilder sb = null; + int length = line.length(); + for (int i = 0; i < length; i++) { + char c = line.charAt(i); + if (c == '\0') { + if (sb == null) { + sb = new StringBuilder(length); + sb.append(line, 0, i); + } + sb.append('\uFFFD'); + } else { + if (sb != null) { + sb.append(c); + } + } + } + + if (sb != null) { + return sb.toString(); + } else { + return line; + } + } + + public static int skip(char skip, CharSequence s, int startIndex, int endIndex) { + for (int i = startIndex; i < endIndex; i++) { + if (s.charAt(i) != skip) { + return i; + } + } + return endIndex; + } + + public static int skipBackwards(char skip, CharSequence s, int startIndex, int lastIndex) { + for (int i = startIndex; i >= lastIndex; i--) { + if (s.charAt(i) != skip) { + return i; + } + } + return lastIndex - 1; + } + + public static int skipSpaceTab(CharSequence s, int startIndex, int endIndex) { + for (int i = startIndex; i < endIndex; i++) { + switch (s.charAt(i)) { + case ' ': + case '\t': + break; + default: + return i; + } + } + return endIndex; + } + + public static int skipSpaceTabBackwards(CharSequence s, int startIndex, int lastIndex) { + for (int i = startIndex; i >= lastIndex; i--) { + switch (s.charAt(i)) { + case ' ': + case '\t': + break; + default: + return i; + } + } + return lastIndex - 1; + } + + private static int findNonSpace(CharSequence s, int startIndex) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { + switch (s.charAt(i)) { + case ' ': + case '\t': + case '\n': + case '\u000B': + case '\f': + case '\r': + break; + default: + return i; + } + } + return -1; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/entities.txt b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/entities.txt new file mode 100644 index 0000000000000..5b126923ba881 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/entities.txt @@ -0,0 +1,2126 @@ +Aacute=Á +aacute=á +Abreve=Ă +abreve=ă +ac=∾ +acd=∿ +acE=∾̳ +Acirc= +acirc=â +acute=´ +Acy=А +acy=а +AElig=Æ +aelig=æ +af=⁡ +Afr=𝔄 +afr=𝔞 +Agrave=À +agrave=à +alefsym=ℵ +aleph=ℵ +Alpha=Α +alpha=α +Amacr=Ā +amacr=ā +amalg=⨿ +amp=& +AMP=& +andand=⩕ +And=⩓ +and=∧ +andd=⩜ +andslope=⩘ +andv=⩚ +ang=∠ +ange=⦤ +angle=∠ +angmsdaa=⦨ +angmsdab=⦩ +angmsdac=⦪ +angmsdad=⦫ +angmsdae=⦬ +angmsdaf=⦭ +angmsdag=⦮ +angmsdah=⦯ +angmsd=∡ +angrt=∟ +angrtvb=⊾ +angrtvbd=⦝ +angsph=∢ +angst=Å +angzarr=⍼ +Aogon=Ą +aogon=ą +Aopf=𝔸 +aopf=𝕒 +apacir=⩯ +ap=≈ +apE=⩰ +ape=≊ +apid=≋ +apos=' +ApplyFunction=⁡ +approx=≈ +approxeq=≊ +Aring=Å +aring=å +Ascr=𝒜 +ascr=𝒶 +Assign=≔ +ast=* +asymp=≈ +asympeq=≍ +Atilde=à +atilde=ã +Auml=Ä +auml=ä +awconint=∳ +awint=⨑ +backcong=≌ +backepsilon=϶ +backprime=‵ +backsim=∽ +backsimeq=⋍ +Backslash=∖ +Barv=⫧ +barvee=⊽ +barwed=⌅ +Barwed=⌆ +barwedge=⌅ +bbrk=⎵ +bbrktbrk=⎶ +bcong=≌ +Bcy=Б +bcy=б +bdquo=„ +becaus=∵ +because=∵ +Because=∵ +bemptyv=⦰ +bepsi=϶ +bernou=ℬ +Bernoullis=ℬ +Beta=Β +beta=β +beth=ℶ +between=≬ +Bfr=𝔅 +bfr=𝔟 +bigcap=⋂ +bigcirc=◯ +bigcup=⋃ +bigodot=⨀ +bigoplus=⨁ +bigotimes=⨂ +bigsqcup=⨆ +bigstar=★ +bigtriangledown=▽ +bigtriangleup=△ +biguplus=⨄ +bigvee=⋁ +bigwedge=⋀ +bkarow=⤍ +blacklozenge=⧫ +blacksquare=▪ +blacktriangle=▴ +blacktriangledown=▾ +blacktriangleleft=◂ +blacktriangleright=▸ +blank=␣ +blk12=▒ +blk14=░ +blk34=▓ +block=█ +bne==⃥ +bnequiv=≡⃥ +bNot=⫭ +bnot=⌐ +Bopf=𝔹 +bopf=𝕓 +bot=⊥ +bottom=⊥ +bowtie=⋈ +boxbox=⧉ +boxdl=┐ +boxdL=╕ +boxDl=╖ +boxDL=╗ +boxdr=┌ +boxdR=╒ +boxDr=╓ +boxDR=╔ +boxh=─ +boxH=═ +boxhd=┬ +boxHd=╤ +boxhD=╥ +boxHD=╦ +boxhu=┴ +boxHu=╧ +boxhU=╨ +boxHU=╩ +boxminus=⊟ +boxplus=⊞ +boxtimes=⊠ +boxul=┘ +boxuL=╛ +boxUl=╜ +boxUL=╝ +boxur=└ +boxuR=╘ +boxUr=╙ +boxUR=╚ +boxv=│ +boxV=║ +boxvh=┼ +boxvH=╪ +boxVh=╫ +boxVH=╬ +boxvl=┤ +boxvL=╡ +boxVl=╢ +boxVL=╣ +boxvr=├ +boxvR=╞ +boxVr=╟ +boxVR=╠ +bprime=‵ +breve=˘ +Breve=˘ +brvbar=¦ +bscr=𝒷 +Bscr=ℬ +bsemi=⁏ +bsim=∽ +bsime=⋍ +bsolb=⧅ +bsol=\ +bsolhsub=⟈ +bull=• +bullet=• +bump=≎ +bumpE=⪮ +bumpe=≏ +Bumpeq=≎ +bumpeq=≏ +Cacute=Ć +cacute=ć +capand=⩄ +capbrcup=⩉ +capcap=⩋ +cap=∩ +Cap=⋒ +capcup=⩇ +capdot=⩀ +CapitalDifferentialD=ⅅ +caps=∩︀ +caret=⁁ +caron=ˇ +Cayleys=ℭ +ccaps=⩍ +Ccaron=Č +ccaron=č +Ccedil=Ç +ccedil=ç +Ccirc=Ĉ +ccirc=ĉ +Cconint=∰ +ccups=⩌ +ccupssm=⩐ +Cdot=Ċ +cdot=ċ +cedil=¸ +Cedilla=¸ +cemptyv=⦲ +cent=¢ +centerdot=· +CenterDot=· +cfr=𝔠 +Cfr=ℭ +CHcy=Ч +chcy=ч +check=✓ +checkmark=✓ +Chi=Χ +chi=χ +circ=ˆ +circeq=≗ +circlearrowleft=↺ +circlearrowright=↻ +circledast=⊛ +circledcirc=⊚ +circleddash=⊝ +CircleDot=⊙ +circledR=® +circledS=Ⓢ +CircleMinus=⊖ +CirclePlus=⊕ +CircleTimes=⊗ +cir=○ +cirE=⧃ +cire=≗ +cirfnint=⨐ +cirmid=⫯ +cirscir=⧂ +ClockwiseContourIntegral=∲ +CloseCurlyDoubleQuote=” +CloseCurlyQuote=’ +clubs=♣ +clubsuit=♣ +colon=: +Colon=∷ +Colone=⩴ +colone=≔ +coloneq=≔ +comma=, +commat=@ +comp=∁ +compfn=∘ +complement=∁ +complexes=ℂ +cong=≅ +congdot=⩭ +Congruent=≡ +conint=∮ +Conint=∯ +ContourIntegral=∮ +copf=𝕔 +Copf=ℂ +coprod=∐ +Coproduct=∐ +copy=© +COPY=© +copysr=℗ +CounterClockwiseContourIntegral=∳ +crarr=↵ +cross=✗ +Cross=⨯ +Cscr=𝒞 +cscr=𝒸 +csub=⫏ +csube=⫑ +csup=⫐ +csupe=⫒ +ctdot=⋯ +cudarrl=⤸ +cudarrr=⤵ +cuepr=⋞ +cuesc=⋟ +cularr=↶ +cularrp=⤽ +cupbrcap=⩈ +cupcap=⩆ +CupCap=≍ +cup=∪ +Cup=⋓ +cupcup=⩊ +cupdot=⊍ +cupor=⩅ +cups=∪︀ +curarr=↷ +curarrm=⤼ +curlyeqprec=⋞ +curlyeqsucc=⋟ +curlyvee=⋎ +curlywedge=⋏ +curren=¤ +curvearrowleft=↶ +curvearrowright=↷ +cuvee=⋎ +cuwed=⋏ +cwconint=∲ +cwint=∱ +cylcty=⌭ +dagger=† +Dagger=‡ +daleth=ℸ +darr=↓ +Darr=↡ +dArr=⇓ +dash=‐ +Dashv=⫤ +dashv=⊣ +dbkarow=⤏ +dblac=˝ +Dcaron=Ď +dcaron=ď +Dcy=Д +dcy=д +ddagger=‡ +ddarr=⇊ +DD=ⅅ +dd=ⅆ +DDotrahd=⤑ +ddotseq=⩷ +deg=° +Del=∇ +Delta=Δ +delta=δ +demptyv=⦱ +dfisht=⥿ +Dfr=𝔇 +dfr=𝔡 +dHar=⥥ +dharl=⇃ +dharr=⇂ +DiacriticalAcute=´ +DiacriticalDot=˙ +DiacriticalDoubleAcute=˝ +DiacriticalGrave=` +DiacriticalTilde=˜ +diam=⋄ +diamond=⋄ +Diamond=⋄ +diamondsuit=♦ +diams=♦ +die=¨ +DifferentialD=ⅆ +digamma=ϝ +disin=⋲ +div=÷ +divide=÷ +divideontimes=⋇ +divonx=⋇ +DJcy=Ђ +djcy=ђ +dlcorn=⌞ +dlcrop=⌍ +dollar=$ +Dopf=𝔻 +dopf=𝕕 +Dot=¨ +dot=˙ +DotDot=⃜ +doteq=≐ +doteqdot=≑ +DotEqual=≐ +dotminus=∸ +dotplus=∔ +dotsquare=⊡ +doublebarwedge=⌆ +DoubleContourIntegral=∯ +DoubleDot=¨ +DoubleDownArrow=⇓ +DoubleLeftArrow=⇐ +DoubleLeftRightArrow=⇔ +DoubleLeftTee=⫤ +DoubleLongLeftArrow=⟸ +DoubleLongLeftRightArrow=⟺ +DoubleLongRightArrow=⟹ +DoubleRightArrow=⇒ +DoubleRightTee=⊨ +DoubleUpArrow=⇑ +DoubleUpDownArrow=⇕ +DoubleVerticalBar=∥ +DownArrowBar=⤓ +downarrow=↓ +DownArrow=↓ +Downarrow=⇓ +DownArrowUpArrow=⇵ +DownBreve=̑ +downdownarrows=⇊ +downharpoonleft=⇃ +downharpoonright=⇂ +DownLeftRightVector=⥐ +DownLeftTeeVector=⥞ +DownLeftVectorBar=⥖ +DownLeftVector=↽ +DownRightTeeVector=⥟ +DownRightVectorBar=⥗ +DownRightVector=⇁ +DownTeeArrow=↧ +DownTee=⊤ +drbkarow=⤐ +drcorn=⌟ +drcrop=⌌ +Dscr=𝒟 +dscr=𝒹 +DScy=Ѕ +dscy=ѕ +dsol=⧶ +Dstrok=Đ +dstrok=đ +dtdot=⋱ +dtri=▿ +dtrif=▾ +duarr=⇵ +duhar=⥯ +dwangle=⦦ +DZcy=Џ +dzcy=џ +dzigrarr=⟿ +Eacute=É +eacute=é +easter=⩮ +Ecaron=Ě +ecaron=ě +Ecirc=Ê +ecirc=ê +ecir=≖ +ecolon=≕ +Ecy=Э +ecy=э +eDDot=⩷ +Edot=Ė +edot=ė +eDot=≑ +ee=ⅇ +efDot=≒ +Efr=𝔈 +efr=𝔢 +eg=⪚ +Egrave=È +egrave=è +egs=⪖ +egsdot=⪘ +el=⪙ +Element=∈ +elinters=⏧ +ell=ℓ +els=⪕ +elsdot=⪗ +Emacr=Ē +emacr=ē +empty=∅ +emptyset=∅ +EmptySmallSquare=◻ +emptyv=∅ +EmptyVerySmallSquare=▫ +emsp13=  +emsp14=  +emsp=  +ENG=Ŋ +eng=ŋ +ensp=  +Eogon=Ę +eogon=ę +Eopf=𝔼 +eopf=𝕖 +epar=⋕ +eparsl=⧣ +eplus=⩱ +epsi=ε +Epsilon=Ε +epsilon=ε +epsiv=ϵ +eqcirc=≖ +eqcolon=≕ +eqsim=≂ +eqslantgtr=⪖ +eqslantless=⪕ +Equal=⩵ +equals== +EqualTilde=≂ +equest=≟ +Equilibrium=⇌ +equiv=≡ +equivDD=⩸ +eqvparsl=⧥ +erarr=⥱ +erDot=≓ +escr=ℯ +Escr=ℰ +esdot=≐ +Esim=⩳ +esim=≂ +Eta=Η +eta=η +ETH=Ð +eth=ð +Euml=Ë +euml=ë +euro=€ +excl=! +exist=∃ +Exists=∃ +expectation=ℰ +exponentiale=ⅇ +ExponentialE=ⅇ +fallingdotseq=≒ +Fcy=Ф +fcy=ф +female=♀ +ffilig=ffi +fflig=ff +ffllig=ffl +Ffr=𝔉 +ffr=𝔣 +filig=fi +FilledSmallSquare=◼ +FilledVerySmallSquare=▪ +fjlig=fj +flat=♭ +fllig=fl +fltns=▱ +fnof=ƒ +Fopf=𝔽 +fopf=𝕗 +forall=∀ +ForAll=∀ +fork=⋔ +forkv=⫙ +Fouriertrf=ℱ +fpartint=⨍ +frac12=½ +frac13=⅓ +frac14=¼ +frac15=⅕ +frac16=⅙ +frac18=⅛ +frac23=⅔ +frac25=⅖ +frac34=¾ +frac35=⅗ +frac38=⅜ +frac45=⅘ +frac56=⅚ +frac58=⅝ +frac78=⅞ +frasl=⁄ +frown=⌢ +fscr=𝒻 +Fscr=ℱ +gacute=ǵ +Gamma=Γ +gamma=γ +Gammad=Ϝ +gammad=ϝ +gap=⪆ +Gbreve=Ğ +gbreve=ğ +Gcedil=Ģ +Gcirc=Ĝ +gcirc=ĝ +Gcy=Г +gcy=г +Gdot=Ġ +gdot=ġ +ge=≥ +gE=≧ +gEl=⪌ +gel=⋛ +geq=≥ +geqq=≧ +geqslant=⩾ +gescc=⪩ +ges=⩾ +gesdot=⪀ +gesdoto=⪂ +gesdotol=⪄ +gesl=⋛︀ +gesles=⪔ +Gfr=𝔊 +gfr=𝔤 +gg=≫ +Gg=⋙ +ggg=⋙ +gimel=ℷ +GJcy=Ѓ +gjcy=ѓ +gla=⪥ +gl=≷ +glE=⪒ +glj=⪤ +gnap=⪊ +gnapprox=⪊ +gne=⪈ +gnE=≩ +gneq=⪈ +gneqq=≩ +gnsim=⋧ +Gopf=𝔾 +gopf=𝕘 +grave=` +GreaterEqual=≥ +GreaterEqualLess=⋛ +GreaterFullEqual=≧ +GreaterGreater=⪢ +GreaterLess=≷ +GreaterSlantEqual=⩾ +GreaterTilde=≳ +Gscr=𝒢 +gscr=ℊ +gsim=≳ +gsime=⪎ +gsiml=⪐ +gtcc=⪧ +gtcir=⩺ +gt=> +GT=> +Gt=≫ +gtdot=⋗ +gtlPar=⦕ +gtquest=⩼ +gtrapprox=⪆ +gtrarr=⥸ +gtrdot=⋗ +gtreqless=⋛ +gtreqqless=⪌ +gtrless=≷ +gtrsim=≳ +gvertneqq=≩︀ +gvnE=≩︀ +Hacek=ˇ +hairsp=  +half=½ +hamilt=ℋ +HARDcy=Ъ +hardcy=ъ +harrcir=⥈ +harr=↔ +hArr=⇔ +harrw=↭ +Hat=^ +hbar=ℏ +Hcirc=Ĥ +hcirc=ĥ +hearts=♥ +heartsuit=♥ +hellip=… +hercon=⊹ +hfr=𝔥 +Hfr=ℌ +HilbertSpace=ℋ +hksearow=⤥ +hkswarow=⤦ +hoarr=⇿ +homtht=∻ +hookleftarrow=↩ +hookrightarrow=↪ +hopf=𝕙 +Hopf=ℍ +horbar=― +HorizontalLine=─ +hscr=𝒽 +Hscr=ℋ +hslash=ℏ +Hstrok=Ħ +hstrok=ħ +HumpDownHump=≎ +HumpEqual=≏ +hybull=⁃ +hyphen=‐ +Iacute=Í +iacute=í +ic=⁣ +Icirc=Î +icirc=î +Icy=И +icy=и +Idot=İ +IEcy=Е +iecy=е +iexcl=¡ +iff=⇔ +ifr=𝔦 +Ifr=ℑ +Igrave=Ì +igrave=ì +ii=ⅈ +iiiint=⨌ +iiint=∭ +iinfin=⧜ +iiota=℩ +IJlig=IJ +ijlig=ij +Imacr=Ī +imacr=ī +image=ℑ +ImaginaryI=ⅈ +imagline=ℐ +imagpart=ℑ +imath=ı +Im=ℑ +imof=⊷ +imped=Ƶ +Implies=⇒ +incare=℅ +in=∈ +infin=∞ +infintie=⧝ +inodot=ı +intcal=⊺ +int=∫ +Int=∬ +integers=ℤ +Integral=∫ +intercal=⊺ +Intersection=⋂ +intlarhk=⨗ +intprod=⨼ +InvisibleComma=⁣ +InvisibleTimes=⁢ +IOcy=Ё +iocy=ё +Iogon=Į +iogon=į +Iopf=𝕀 +iopf=𝕚 +Iota=Ι +iota=ι +iprod=⨼ +iquest=¿ +iscr=𝒾 +Iscr=ℐ +isin=∈ +isindot=⋵ +isinE=⋹ +isins=⋴ +isinsv=⋳ +isinv=∈ +it=⁢ +Itilde=Ĩ +itilde=ĩ +Iukcy=І +iukcy=і +Iuml=Ï +iuml=ï +Jcirc=Ĵ +jcirc=ĵ +Jcy=Й +jcy=й +Jfr=𝔍 +jfr=𝔧 +jmath=ȷ +Jopf=𝕁 +jopf=𝕛 +Jscr=𝒥 +jscr=𝒿 +Jsercy=Ј +jsercy=ј +Jukcy=Є +jukcy=є +Kappa=Κ +kappa=κ +kappav=ϰ +Kcedil=Ķ +kcedil=ķ +Kcy=К +kcy=к +Kfr=𝔎 +kfr=𝔨 +kgreen=ĸ +KHcy=Х +khcy=х +KJcy=Ќ +kjcy=ќ +Kopf=𝕂 +kopf=𝕜 +Kscr=𝒦 +kscr=𝓀 +lAarr=⇚ +Lacute=Ĺ +lacute=ĺ +laemptyv=⦴ +lagran=ℒ +Lambda=Λ +lambda=λ +lang=⟨ +Lang=⟪ +langd=⦑ +langle=⟨ +lap=⪅ +Laplacetrf=ℒ +laquo=« +larrb=⇤ +larrbfs=⤟ +larr=← +Larr=↞ +lArr=⇐ +larrfs=⤝ +larrhk=↩ +larrlp=↫ +larrpl=⤹ +larrsim=⥳ +larrtl=↢ +latail=⤙ +lAtail=⤛ +lat=⪫ +late=⪭ +lates=⪭︀ +lbarr=⤌ +lBarr=⤎ +lbbrk=❲ +lbrace={ +lbrack=[ +lbrke=⦋ +lbrksld=⦏ +lbrkslu=⦍ +Lcaron=Ľ +lcaron=ľ +Lcedil=Ļ +lcedil=ļ +lceil=⌈ +lcub={ +Lcy=Л +lcy=л +ldca=⤶ +ldquo=“ +ldquor=„ +ldrdhar=⥧ +ldrushar=⥋ +ldsh=↲ +le=≤ +lE=≦ +LeftAngleBracket=⟨ +LeftArrowBar=⇤ +leftarrow=← +LeftArrow=← +Leftarrow=⇐ +LeftArrowRightArrow=⇆ +leftarrowtail=↢ +LeftCeiling=⌈ +LeftDoubleBracket=⟦ +LeftDownTeeVector=⥡ +LeftDownVectorBar=⥙ +LeftDownVector=⇃ +LeftFloor=⌊ +leftharpoondown=↽ +leftharpoonup=↼ +leftleftarrows=⇇ +leftrightarrow=↔ +LeftRightArrow=↔ +Leftrightarrow=⇔ +leftrightarrows=⇆ +leftrightharpoons=⇋ +leftrightsquigarrow=↭ +LeftRightVector=⥎ +LeftTeeArrow=↤ +LeftTee=⊣ +LeftTeeVector=⥚ +leftthreetimes=⋋ +LeftTriangleBar=⧏ +LeftTriangle=⊲ +LeftTriangleEqual=⊴ +LeftUpDownVector=⥑ +LeftUpTeeVector=⥠ +LeftUpVectorBar=⥘ +LeftUpVector=↿ +LeftVectorBar=⥒ +LeftVector=↼ +lEg=⪋ +leg=⋚ +leq=≤ +leqq=≦ +leqslant=⩽ +lescc=⪨ +les=⩽ +lesdot=⩿ +lesdoto=⪁ +lesdotor=⪃ +lesg=⋚︀ +lesges=⪓ +lessapprox=⪅ +lessdot=⋖ +lesseqgtr=⋚ +lesseqqgtr=⪋ +LessEqualGreater=⋚ +LessFullEqual=≦ +LessGreater=≶ +lessgtr=≶ +LessLess=⪡ +lesssim=≲ +LessSlantEqual=⩽ +LessTilde=≲ +lfisht=⥼ +lfloor=⌊ +Lfr=𝔏 +lfr=𝔩 +lg=≶ +lgE=⪑ +lHar=⥢ +lhard=↽ +lharu=↼ +lharul=⥪ +lhblk=▄ +LJcy=Љ +ljcy=љ +llarr=⇇ +ll=≪ +Ll=⋘ +llcorner=⌞ +Lleftarrow=⇚ +llhard=⥫ +lltri=◺ +Lmidot=Ŀ +lmidot=ŀ +lmoustache=⎰ +lmoust=⎰ +lnap=⪉ +lnapprox=⪉ +lne=⪇ +lnE=≨ +lneq=⪇ +lneqq=≨ +lnsim=⋦ +loang=⟬ +loarr=⇽ +lobrk=⟦ +longleftarrow=⟵ +LongLeftArrow=⟵ +Longleftarrow=⟸ +longleftrightarrow=⟷ +LongLeftRightArrow=⟷ +Longleftrightarrow=⟺ +longmapsto=⟼ +longrightarrow=⟶ +LongRightArrow=⟶ +Longrightarrow=⟹ +looparrowleft=↫ +looparrowright=↬ +lopar=⦅ +Lopf=𝕃 +lopf=𝕝 +loplus=⨭ +lotimes=⨴ +lowast=∗ +lowbar=_ +LowerLeftArrow=↙ +LowerRightArrow=↘ +loz=◊ +lozenge=◊ +lozf=⧫ +lpar=( +lparlt=⦓ +lrarr=⇆ +lrcorner=⌟ +lrhar=⇋ +lrhard=⥭ +lrm=‎ +lrtri=⊿ +lsaquo=‹ +lscr=𝓁 +Lscr=ℒ +lsh=↰ +Lsh=↰ +lsim=≲ +lsime=⪍ +lsimg=⪏ +lsqb=[ +lsquo=‘ +lsquor=‚ +Lstrok=Ł +lstrok=ł +ltcc=⪦ +ltcir=⩹ +lt=< +LT=< +Lt=≪ +ltdot=⋖ +lthree=⋋ +ltimes=⋉ +ltlarr=⥶ +ltquest=⩻ +ltri=◃ +ltrie=⊴ +ltrif=◂ +ltrPar=⦖ +lurdshar=⥊ +luruhar=⥦ +lvertneqq=≨︀ +lvnE=≨︀ +macr=¯ +male=♂ +malt=✠ +maltese=✠ +Map=⤅ +map=↦ +mapsto=↦ +mapstodown=↧ +mapstoleft=↤ +mapstoup=↥ +marker=▮ +mcomma=⨩ +Mcy=М +mcy=м +mdash=— +mDDot=∺ +measuredangle=∡ +MediumSpace=  +Mellintrf=ℳ +Mfr=𝔐 +mfr=𝔪 +mho=℧ +micro=µ +midast=* +midcir=⫰ +mid=∣ +middot=· +minusb=⊟ +minus=− +minusd=∸ +minusdu=⨪ +MinusPlus=∓ +mlcp=⫛ +mldr=… +mnplus=∓ +models=⊧ +Mopf=𝕄 +mopf=𝕞 +mp=∓ +mscr=𝓂 +Mscr=ℳ +mstpos=∾ +Mu=Μ +mu=μ +multimap=⊸ +mumap=⊸ +nabla=∇ +Nacute=Ń +nacute=ń +nang=∠⃒ +nap=≉ +napE=⩰̸ +napid=≋̸ +napos=ʼn +napprox=≉ +natural=♮ +naturals=ℕ +natur=♮ +nbsp=  +nbump=≎̸ +nbumpe=≏̸ +ncap=⩃ +Ncaron=Ň +ncaron=ň +Ncedil=Ņ +ncedil=ņ +ncong=≇ +ncongdot=⩭̸ +ncup=⩂ +Ncy=Н +ncy=н +ndash=– +nearhk=⤤ +nearr=↗ +neArr=⇗ +nearrow=↗ +ne=≠ +nedot=≐̸ +NegativeMediumSpace=​ +NegativeThickSpace=​ +NegativeThinSpace=​ +NegativeVeryThinSpace=​ +nequiv=≢ +nesear=⤨ +nesim=≂̸ +NestedGreaterGreater=≫ +NestedLessLess=≪ +NewLine= + +nexist=∄ +nexists=∄ +Nfr=𝔑 +nfr=𝔫 +ngE=≧̸ +nge=≱ +ngeq=≱ +ngeqq=≧̸ +ngeqslant=⩾̸ +nges=⩾̸ +nGg=⋙̸ +ngsim=≵ +nGt=≫⃒ +ngt=≯ +ngtr=≯ +nGtv=≫̸ +nharr=↮ +nhArr=⇎ +nhpar=⫲ +ni=∋ +nis=⋼ +nisd=⋺ +niv=∋ +NJcy=Њ +njcy=њ +nlarr=↚ +nlArr=⇍ +nldr=‥ +nlE=≦̸ +nle=≰ +nleftarrow=↚ +nLeftarrow=⇍ +nleftrightarrow=↮ +nLeftrightarrow=⇎ +nleq=≰ +nleqq=≦̸ +nleqslant=⩽̸ +nles=⩽̸ +nless=≮ +nLl=⋘̸ +nlsim=≴ +nLt=≪⃒ +nlt=≮ +nltri=⋪ +nltrie=⋬ +nLtv=≪̸ +nmid=∤ +NoBreak=⁠ +NonBreakingSpace=  +nopf=𝕟 +Nopf=ℕ +Not=⫬ +not=¬ +NotCongruent=≢ +NotCupCap=≭ +NotDoubleVerticalBar=∦ +NotElement=∉ +NotEqual=≠ +NotEqualTilde=≂̸ +NotExists=∄ +NotGreater=≯ +NotGreaterEqual=≱ +NotGreaterFullEqual=≧̸ +NotGreaterGreater=≫̸ +NotGreaterLess=≹ +NotGreaterSlantEqual=⩾̸ +NotGreaterTilde=≵ +NotHumpDownHump=≎̸ +NotHumpEqual=≏̸ +notin=∉ +notindot=⋵̸ +notinE=⋹̸ +notinva=∉ +notinvb=⋷ +notinvc=⋶ +NotLeftTriangleBar=⧏̸ +NotLeftTriangle=⋪ +NotLeftTriangleEqual=⋬ +NotLess=≮ +NotLessEqual=≰ +NotLessGreater=≸ +NotLessLess=≪̸ +NotLessSlantEqual=⩽̸ +NotLessTilde=≴ +NotNestedGreaterGreater=⪢̸ +NotNestedLessLess=⪡̸ +notni=∌ +notniva=∌ +notnivb=⋾ +notnivc=⋽ +NotPrecedes=⊀ +NotPrecedesEqual=⪯̸ +NotPrecedesSlantEqual=⋠ +NotReverseElement=∌ +NotRightTriangleBar=⧐̸ +NotRightTriangle=⋫ +NotRightTriangleEqual=⋭ +NotSquareSubset=⊏̸ +NotSquareSubsetEqual=⋢ +NotSquareSuperset=⊐̸ +NotSquareSupersetEqual=⋣ +NotSubset=⊂⃒ +NotSubsetEqual=⊈ +NotSucceeds=⊁ +NotSucceedsEqual=⪰̸ +NotSucceedsSlantEqual=⋡ +NotSucceedsTilde=≿̸ +NotSuperset=⊃⃒ +NotSupersetEqual=⊉ +NotTilde=≁ +NotTildeEqual=≄ +NotTildeFullEqual=≇ +NotTildeTilde=≉ +NotVerticalBar=∤ +nparallel=∦ +npar=∦ +nparsl=⫽⃥ +npart=∂̸ +npolint=⨔ +npr=⊀ +nprcue=⋠ +nprec=⊀ +npreceq=⪯̸ +npre=⪯̸ +nrarrc=⤳̸ +nrarr=↛ +nrArr=⇏ +nrarrw=↝̸ +nrightarrow=↛ +nRightarrow=⇏ +nrtri=⋫ +nrtrie=⋭ +nsc=⊁ +nsccue=⋡ +nsce=⪰̸ +Nscr=𝒩 +nscr=𝓃 +nshortmid=∤ +nshortparallel=∦ +nsim=≁ +nsime=≄ +nsimeq=≄ +nsmid=∤ +nspar=∦ +nsqsube=⋢ +nsqsupe=⋣ +nsub=⊄ +nsubE=⫅̸ +nsube=⊈ +nsubset=⊂⃒ +nsubseteq=⊈ +nsubseteqq=⫅̸ +nsucc=⊁ +nsucceq=⪰̸ +nsup=⊅ +nsupE=⫆̸ +nsupe=⊉ +nsupset=⊃⃒ +nsupseteq=⊉ +nsupseteqq=⫆̸ +ntgl=≹ +Ntilde=Ñ +ntilde=ñ +ntlg=≸ +ntriangleleft=⋪ +ntrianglelefteq=⋬ +ntriangleright=⋫ +ntrianglerighteq=⋭ +Nu=Ν +nu=ν +num=# +numero=№ +numsp=  +nvap=≍⃒ +nvdash=⊬ +nvDash=⊭ +nVdash=⊮ +nVDash=⊯ +nvge=≥⃒ +nvgt=>⃒ +nvHarr=⤄ +nvinfin=⧞ +nvlArr=⤂ +nvle=≤⃒ +nvlt=<⃒ +nvltrie=⊴⃒ +nvrArr=⤃ +nvrtrie=⊵⃒ +nvsim=∼⃒ +nwarhk=⤣ +nwarr=↖ +nwArr=⇖ +nwarrow=↖ +nwnear=⤧ +Oacute=Ó +oacute=ó +oast=⊛ +Ocirc=Ô +ocirc=ô +ocir=⊚ +Ocy=О +ocy=о +odash=⊝ +Odblac=Ő +odblac=ő +odiv=⨸ +odot=⊙ +odsold=⦼ +OElig=Œ +oelig=œ +ofcir=⦿ +Ofr=𝔒 +ofr=𝔬 +ogon=˛ +Ograve=Ò +ograve=ò +ogt=⧁ +ohbar=⦵ +ohm=Ω +oint=∮ +olarr=↺ +olcir=⦾ +olcross=⦻ +oline=‾ +olt=⧀ +Omacr=Ō +omacr=ō +Omega=Ω +omega=ω +Omicron=Ο +omicron=ο +omid=⦶ +ominus=⊖ +Oopf=𝕆 +oopf=𝕠 +opar=⦷ +OpenCurlyDoubleQuote=“ +OpenCurlyQuote=‘ +operp=⦹ +oplus=⊕ +orarr=↻ +Or=⩔ +or=∨ +ord=⩝ +order=ℴ +orderof=ℴ +ordf=ª +ordm=º +origof=⊶ +oror=⩖ +orslope=⩗ +orv=⩛ +oS=Ⓢ +Oscr=𝒪 +oscr=ℴ +Oslash=Ø +oslash=ø +osol=⊘ +Otilde=Õ +otilde=õ +otimesas=⨶ +Otimes=⨷ +otimes=⊗ +Ouml=Ö +ouml=ö +ovbar=⌽ +OverBar=‾ +OverBrace=⏞ +OverBracket=⎴ +OverParenthesis=⏜ +para=¶ +parallel=∥ +par=∥ +parsim=⫳ +parsl=⫽ +part=∂ +PartialD=∂ +Pcy=П +pcy=п +percnt=% +period=. +permil=‰ +perp=⊥ +pertenk=‱ +Pfr=𝔓 +pfr=𝔭 +Phi=Φ +phi=φ +phiv=ϕ +phmmat=ℳ +phone=☎ +Pi=Π +pi=π +pitchfork=⋔ +piv=ϖ +planck=ℏ +planckh=ℎ +plankv=ℏ +plusacir=⨣ +plusb=⊞ +pluscir=⨢ +plus=+ +plusdo=∔ +plusdu=⨥ +pluse=⩲ +PlusMinus=± +plusmn=± +plussim=⨦ +plustwo=⨧ +pm=± +Poincareplane=ℌ +pointint=⨕ +popf=𝕡 +Popf=ℙ +pound=£ +prap=⪷ +Pr=⪻ +pr=≺ +prcue=≼ +precapprox=⪷ +prec=≺ +preccurlyeq=≼ +Precedes=≺ +PrecedesEqual=⪯ +PrecedesSlantEqual=≼ +PrecedesTilde=≾ +preceq=⪯ +precnapprox=⪹ +precneqq=⪵ +precnsim=⋨ +pre=⪯ +prE=⪳ +precsim=≾ +prime=′ +Prime=″ +primes=ℙ +prnap=⪹ +prnE=⪵ +prnsim=⋨ +prod=∏ +Product=∏ +profalar=⌮ +profline=⌒ +profsurf=⌓ +prop=∝ +Proportional=∝ +Proportion=∷ +propto=∝ +prsim=≾ +prurel=⊰ +Pscr=𝒫 +pscr=𝓅 +Psi=Ψ +psi=ψ +puncsp=  +Qfr=𝔔 +qfr=𝔮 +qint=⨌ +qopf=𝕢 +Qopf=ℚ +qprime=⁗ +Qscr=𝒬 +qscr=𝓆 +quaternions=ℍ +quatint=⨖ +quest=? +questeq=≟ +quot=" +QUOT=" +rAarr=⇛ +race=∽̱ +Racute=Ŕ +racute=ŕ +radic=√ +raemptyv=⦳ +rang=⟩ +Rang=⟫ +rangd=⦒ +range=⦥ +rangle=⟩ +raquo=» +rarrap=⥵ +rarrb=⇥ +rarrbfs=⤠ +rarrc=⤳ +rarr=→ +Rarr=↠ +rArr=⇒ +rarrfs=⤞ +rarrhk=↪ +rarrlp=↬ +rarrpl=⥅ +rarrsim=⥴ +Rarrtl=⤖ +rarrtl=↣ +rarrw=↝ +ratail=⤚ +rAtail=⤜ +ratio=∶ +rationals=ℚ +rbarr=⤍ +rBarr=⤏ +RBarr=⤐ +rbbrk=❳ +rbrace=} +rbrack=] +rbrke=⦌ +rbrksld=⦎ +rbrkslu=⦐ +Rcaron=Ř +rcaron=ř +Rcedil=Ŗ +rcedil=ŗ +rceil=⌉ +rcub=} +Rcy=Р +rcy=р +rdca=⤷ +rdldhar=⥩ +rdquo=” +rdquor=” +rdsh=↳ +real=ℜ +realine=ℛ +realpart=ℜ +reals=ℝ +Re=ℜ +rect=▭ +reg=® +REG=® +ReverseElement=∋ +ReverseEquilibrium=⇋ +ReverseUpEquilibrium=⥯ +rfisht=⥽ +rfloor=⌋ +rfr=𝔯 +Rfr=ℜ +rHar=⥤ +rhard=⇁ +rharu=⇀ +rharul=⥬ +Rho=Ρ +rho=ρ +rhov=ϱ +RightAngleBracket=⟩ +RightArrowBar=⇥ +rightarrow=→ +RightArrow=→ +Rightarrow=⇒ +RightArrowLeftArrow=⇄ +rightarrowtail=↣ +RightCeiling=⌉ +RightDoubleBracket=⟧ +RightDownTeeVector=⥝ +RightDownVectorBar=⥕ +RightDownVector=⇂ +RightFloor=⌋ +rightharpoondown=⇁ +rightharpoonup=⇀ +rightleftarrows=⇄ +rightleftharpoons=⇌ +rightrightarrows=⇉ +rightsquigarrow=↝ +RightTeeArrow=↦ +RightTee=⊢ +RightTeeVector=⥛ +rightthreetimes=⋌ +RightTriangleBar=⧐ +RightTriangle=⊳ +RightTriangleEqual=⊵ +RightUpDownVector=⥏ +RightUpTeeVector=⥜ +RightUpVectorBar=⥔ +RightUpVector=↾ +RightVectorBar=⥓ +RightVector=⇀ +ring=˚ +risingdotseq=≓ +rlarr=⇄ +rlhar=⇌ +rlm=‏ +rmoustache=⎱ +rmoust=⎱ +rnmid=⫮ +roang=⟭ +roarr=⇾ +robrk=⟧ +ropar=⦆ +ropf=𝕣 +Ropf=ℝ +roplus=⨮ +rotimes=⨵ +RoundImplies=⥰ +rpar=) +rpargt=⦔ +rppolint=⨒ +rrarr=⇉ +Rrightarrow=⇛ +rsaquo=› +rscr=𝓇 +Rscr=ℛ +rsh=↱ +Rsh=↱ +rsqb=] +rsquo=’ +rsquor=’ +rthree=⋌ +rtimes=⋊ +rtri=▹ +rtrie=⊵ +rtrif=▸ +rtriltri=⧎ +RuleDelayed=⧴ +ruluhar=⥨ +rx=℞ +Sacute=Ś +sacute=ś +sbquo=‚ +scap=⪸ +Scaron=Š +scaron=š +Sc=⪼ +sc=≻ +sccue=≽ +sce=⪰ +scE=⪴ +Scedil=Ş +scedil=ş +Scirc=Ŝ +scirc=ŝ +scnap=⪺ +scnE=⪶ +scnsim=⋩ +scpolint=⨓ +scsim=≿ +Scy=С +scy=с +sdotb=⊡ +sdot=⋅ +sdote=⩦ +searhk=⤥ +searr=↘ +seArr=⇘ +searrow=↘ +sect=§ +semi=; +seswar=⤩ +setminus=∖ +setmn=∖ +sext=✶ +Sfr=𝔖 +sfr=𝔰 +sfrown=⌢ +sharp=♯ +SHCHcy=Щ +shchcy=щ +SHcy=Ш +shcy=ш +ShortDownArrow=↓ +ShortLeftArrow=← +shortmid=∣ +shortparallel=∥ +ShortRightArrow=→ +ShortUpArrow=↑ +shy=­ +Sigma=Σ +sigma=σ +sigmaf=ς +sigmav=ς +sim=∼ +simdot=⩪ +sime=≃ +simeq=≃ +simg=⪞ +simgE=⪠ +siml=⪝ +simlE=⪟ +simne=≆ +simplus=⨤ +simrarr=⥲ +slarr=← +SmallCircle=∘ +smallsetminus=∖ +smashp=⨳ +smeparsl=⧤ +smid=∣ +smile=⌣ +smt=⪪ +smte=⪬ +smtes=⪬︀ +SOFTcy=Ь +softcy=ь +solbar=⌿ +solb=⧄ +sol=/ +Sopf=𝕊 +sopf=𝕤 +spades=♠ +spadesuit=♠ +spar=∥ +sqcap=⊓ +sqcaps=⊓︀ +sqcup=⊔ +sqcups=⊔︀ +Sqrt=√ +sqsub=⊏ +sqsube=⊑ +sqsubset=⊏ +sqsubseteq=⊑ +sqsup=⊐ +sqsupe=⊒ +sqsupset=⊐ +sqsupseteq=⊒ +square=□ +Square=□ +SquareIntersection=⊓ +SquareSubset=⊏ +SquareSubsetEqual=⊑ +SquareSuperset=⊐ +SquareSupersetEqual=⊒ +SquareUnion=⊔ +squarf=▪ +squ=□ +squf=▪ +srarr=→ +Sscr=𝒮 +sscr=𝓈 +ssetmn=∖ +ssmile=⌣ +sstarf=⋆ +Star=⋆ +star=☆ +starf=★ +straightepsilon=ϵ +straightphi=ϕ +strns=¯ +sub=⊂ +Sub=⋐ +subdot=⪽ +subE=⫅ +sube=⊆ +subedot=⫃ +submult=⫁ +subnE=⫋ +subne=⊊ +subplus=⪿ +subrarr=⥹ +subset=⊂ +Subset=⋐ +subseteq=⊆ +subseteqq=⫅ +SubsetEqual=⊆ +subsetneq=⊊ +subsetneqq=⫋ +subsim=⫇ +subsub=⫕ +subsup=⫓ +succapprox=⪸ +succ=≻ +succcurlyeq=≽ +Succeeds=≻ +SucceedsEqual=⪰ +SucceedsSlantEqual=≽ +SucceedsTilde=≿ +succeq=⪰ +succnapprox=⪺ +succneqq=⪶ +succnsim=⋩ +succsim=≿ +SuchThat=∋ +sum=∑ +Sum=∑ +sung=♪ +sup1=¹ +sup2=² +sup3=³ +sup=⊃ +Sup=⋑ +supdot=⪾ +supdsub=⫘ +supE=⫆ +supe=⊇ +supedot=⫄ +Superset=⊃ +SupersetEqual=⊇ +suphsol=⟉ +suphsub=⫗ +suplarr=⥻ +supmult=⫂ +supnE=⫌ +supne=⊋ +supplus=⫀ +supset=⊃ +Supset=⋑ +supseteq=⊇ +supseteqq=⫆ +supsetneq=⊋ +supsetneqq=⫌ +supsim=⫈ +supsub=⫔ +supsup=⫖ +swarhk=⤦ +swarr=↙ +swArr=⇙ +swarrow=↙ +swnwar=⤪ +szlig=ß +Tab= +target=⌖ +Tau=Τ +tau=τ +tbrk=⎴ +Tcaron=Ť +tcaron=ť +Tcedil=Ţ +tcedil=ţ +Tcy=Т +tcy=т +tdot=⃛ +telrec=⌕ +Tfr=𝔗 +tfr=𝔱 +there4=∴ +therefore=∴ +Therefore=∴ +Theta=Θ +theta=θ +thetasym=ϑ +thetav=ϑ +thickapprox=≈ +thicksim=∼ +ThickSpace=   +ThinSpace=  +thinsp=  +thkap=≈ +thksim=∼ +THORN=Þ +thorn=þ +tilde=˜ +Tilde=∼ +TildeEqual=≃ +TildeFullEqual=≅ +TildeTilde=≈ +timesbar=⨱ +timesb=⊠ +times=× +timesd=⨰ +tint=∭ +toea=⤨ +topbot=⌶ +topcir=⫱ +top=⊤ +Topf=𝕋 +topf=𝕥 +topfork=⫚ +tosa=⤩ +tprime=‴ +trade=™ +TRADE=™ +triangle=▵ +triangledown=▿ +triangleleft=◃ +trianglelefteq=⊴ +triangleq=≜ +triangleright=▹ +trianglerighteq=⊵ +tridot=◬ +trie=≜ +triminus=⨺ +TripleDot=⃛ +triplus=⨹ +trisb=⧍ +tritime=⨻ +trpezium=⏢ +Tscr=𝒯 +tscr=𝓉 +TScy=Ц +tscy=ц +TSHcy=Ћ +tshcy=ћ +Tstrok=Ŧ +tstrok=ŧ +twixt=≬ +twoheadleftarrow=↞ +twoheadrightarrow=↠ +Uacute=Ú +uacute=ú +uarr=↑ +Uarr=↟ +uArr=⇑ +Uarrocir=⥉ +Ubrcy=Ў +ubrcy=ў +Ubreve=Ŭ +ubreve=ŭ +Ucirc=Û +ucirc=û +Ucy=У +ucy=у +udarr=⇅ +Udblac=Ű +udblac=ű +udhar=⥮ +ufisht=⥾ +Ufr=𝔘 +ufr=𝔲 +Ugrave=Ù +ugrave=ù +uHar=⥣ +uharl=↿ +uharr=↾ +uhblk=▀ +ulcorn=⌜ +ulcorner=⌜ +ulcrop=⌏ +ultri=◸ +Umacr=Ū +umacr=ū +uml=¨ +UnderBar=_ +UnderBrace=⏟ +UnderBracket=⎵ +UnderParenthesis=⏝ +Union=⋃ +UnionPlus=⊎ +Uogon=Ų +uogon=ų +Uopf=𝕌 +uopf=𝕦 +UpArrowBar=⤒ +uparrow=↑ +UpArrow=↑ +Uparrow=⇑ +UpArrowDownArrow=⇅ +updownarrow=↕ +UpDownArrow=↕ +Updownarrow=⇕ +UpEquilibrium=⥮ +upharpoonleft=↿ +upharpoonright=↾ +uplus=⊎ +UpperLeftArrow=↖ +UpperRightArrow=↗ +upsi=υ +Upsi=ϒ +upsih=ϒ +Upsilon=Υ +upsilon=υ +UpTeeArrow=↥ +UpTee=⊥ +upuparrows=⇈ +urcorn=⌝ +urcorner=⌝ +urcrop=⌎ +Uring=Ů +uring=ů +urtri=◹ +Uscr=𝒰 +uscr=𝓊 +utdot=⋰ +Utilde=Ũ +utilde=ũ +utri=▵ +utrif=▴ +uuarr=⇈ +Uuml=Ü +uuml=ü +uwangle=⦧ +vangrt=⦜ +varepsilon=ϵ +varkappa=ϰ +varnothing=∅ +varphi=ϕ +varpi=ϖ +varpropto=∝ +varr=↕ +vArr=⇕ +varrho=ϱ +varsigma=ς +varsubsetneq=⊊︀ +varsubsetneqq=⫋︀ +varsupsetneq=⊋︀ +varsupsetneqq=⫌︀ +vartheta=ϑ +vartriangleleft=⊲ +vartriangleright=⊳ +vBar=⫨ +Vbar=⫫ +vBarv=⫩ +Vcy=В +vcy=в +vdash=⊢ +vDash=⊨ +Vdash=⊩ +VDash=⊫ +Vdashl=⫦ +veebar=⊻ +vee=∨ +Vee=⋁ +veeeq=≚ +vellip=⋮ +verbar=| +Verbar=‖ +vert=| +Vert=‖ +VerticalBar=∣ +VerticalLine=| +VerticalSeparator=❘ +VerticalTilde=≀ +VeryThinSpace=  +Vfr=𝔙 +vfr=𝔳 +vltri=⊲ +vnsub=⊂⃒ +vnsup=⊃⃒ +Vopf=𝕍 +vopf=𝕧 +vprop=∝ +vrtri=⊳ +Vscr=𝒱 +vscr=𝓋 +vsubnE=⫋︀ +vsubne=⊊︀ +vsupnE=⫌︀ +vsupne=⊋︀ +Vvdash=⊪ +vzigzag=⦚ +Wcirc=Ŵ +wcirc=ŵ +wedbar=⩟ +wedge=∧ +Wedge=⋀ +wedgeq=≙ +weierp=℘ +Wfr=𝔚 +wfr=𝔴 +Wopf=𝕎 +wopf=𝕨 +wp=℘ +wr=≀ +wreath=≀ +Wscr=𝒲 +wscr=𝓌 +xcap=⋂ +xcirc=◯ +xcup=⋃ +xdtri=▽ +Xfr=𝔛 +xfr=𝔵 +xharr=⟷ +xhArr=⟺ +Xi=Ξ +xi=ξ +xlarr=⟵ +xlArr=⟸ +xmap=⟼ +xnis=⋻ +xodot=⨀ +Xopf=𝕏 +xopf=𝕩 +xoplus=⨁ +xotime=⨂ +xrarr=⟶ +xrArr=⟹ +Xscr=𝒳 +xscr=𝓍 +xsqcup=⨆ +xuplus=⨄ +xutri=△ +xvee=⋁ +xwedge=⋀ +Yacute=Ý +yacute=ý +YAcy=Я +yacy=я +Ycirc=Ŷ +ycirc=ŷ +Ycy=Ы +ycy=ы +yen=¥ +Yfr=𝔜 +yfr=𝔶 +YIcy=Ї +yicy=ї +Yopf=𝕐 +yopf=𝕪 +Yscr=𝒴 +yscr=𝓎 +YUcy=Ю +yucy=ю +yuml=ÿ +Yuml=Ÿ +Zacute=Ź +zacute=ź +Zcaron=Ž +zcaron=ž +Zcy=З +zcy=з +Zdot=Ż +zdot=ż +zeetrf=ℨ +ZeroWidthSpace=​ +Zeta=Ζ +zeta=ζ +zfr=𝔷 +Zfr=ℨ +ZHcy=Ж +zhcy=ж +zigrarr=⇝ +zopf=𝕫 +Zopf=ℤ +Zscr=𝒵 +zscr=𝓏 +zwj=‍ +zwnj=‌ diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/AbstractVisitor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/AbstractVisitor.java new file mode 100644 index 0000000000000..982a6b565503c --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/AbstractVisitor.java @@ -0,0 +1,173 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * Abstract visitor that visits all children by default. + *

+ * Can be used to only process certain nodes. If you override a method and want visiting to descend into children, + * call {@link #visitChildren}. + */ +public abstract class AbstractVisitor implements Visitor { + + @Override + public void visit(BlockQuote blockQuote) { + visitChildren(blockQuote); + } + + @Override + public void visit(BulletList bulletList) { + visitChildren(bulletList); + } + + @Override + public void visit(Code code) { + visitChildren(code); + } + + @Override + public void visit(Document document) { + visitChildren(document); + } + + @Override + public void visit(Emphasis emphasis) { + visitChildren(emphasis); + } + + @Override + public void visit(FencedCodeBlock fencedCodeBlock) { + visitChildren(fencedCodeBlock); + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + visitChildren(hardLineBreak); + } + + @Override + public void visit(Heading heading) { + visitChildren(heading); + } + + @Override + public void visit(ThematicBreak thematicBreak) { + visitChildren(thematicBreak); + } + + @Override + public void visit(HtmlInline htmlInline) { + visitChildren(htmlInline); + } + + @Override + public void visit(HtmlBlock htmlBlock) { + visitChildren(htmlBlock); + } + + @Override + public void visit(Image image) { + visitChildren(image); + } + + @Override + public void visit(IndentedCodeBlock indentedCodeBlock) { + visitChildren(indentedCodeBlock); + } + + @Override + public void visit(Link link) { + visitChildren(link); + } + + @Override + public void visit(ListItem listItem) { + visitChildren(listItem); + } + + @Override + public void visit(OrderedList orderedList) { + visitChildren(orderedList); + } + + @Override + public void visit(Paragraph paragraph) { + visitChildren(paragraph); + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + visitChildren(softLineBreak); + } + + @Override + public void visit(StrongEmphasis strongEmphasis) { + visitChildren(strongEmphasis); + } + + @Override + public void visit(Text text) { + visitChildren(text); + } + + @Override + public void visit(LinkReferenceDefinition linkReferenceDefinition) { + visitChildren(linkReferenceDefinition); + } + + @Override + public void visit(CustomBlock customBlock) { + visitChildren(customBlock); + } + + @Override + public void visit(CustomNode customNode) { + visitChildren(customNode); + } + + /** + * Visit the child nodes. + * + * @param parent the parent node whose children should be visited + */ + protected void visitChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + // A subclass of this visitor might modify the node, resulting in getNext returning a different node or no + // node after visiting it. So get the next node before visiting. + Node next = node.getNext(); + node.accept(this); + node = next; + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Block.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Block.java new file mode 100644 index 0000000000000..57603fa5fddb5 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Block.java @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * Block nodes such as paragraphs, list blocks, code blocks etc. + */ +public abstract class Block extends Node { + + public Block getParent() { + return (Block) super.getParent(); + } + + @Override + protected void setParent(Node parent) { + if (!(parent instanceof Block)) { + throw new IllegalArgumentException("Parent of block must also be block (can not be inline)"); + } + super.setParent(parent); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/BlockQuote.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/BlockQuote.java new file mode 100644 index 0000000000000..8a5f1d20c0781 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/BlockQuote.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class BlockQuote extends Block { + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/BulletList.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/BulletList.java new file mode 100644 index 0000000000000..7512d56673a14 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/BulletList.java @@ -0,0 +1,52 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class BulletList extends ListBlock { + + private char bulletMarker; + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public char getBulletMarker() { + return bulletMarker; + } + + public void setBulletMarker(char bulletMarker) { + this.bulletMarker = bulletMarker; + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Code.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Code.java new file mode 100644 index 0000000000000..b64ddefa1f1e5 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Code.java @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class Code extends Node { + + private String literal; + + public Code() { + } + + public Code(String literal) { + this.literal = literal; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public String getLiteral() { + return literal; + } + + public void setLiteral(String literal) { + this.literal = literal; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/CustomBlock.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/CustomBlock.java new file mode 100644 index 0000000000000..08038848adab8 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/CustomBlock.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public abstract class CustomBlock extends Block { + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/CustomNode.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/CustomNode.java new file mode 100644 index 0000000000000..733df320afcf8 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/CustomNode.java @@ -0,0 +1,40 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public abstract class CustomNode extends Node { + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Delimited.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Delimited.java new file mode 100644 index 0000000000000..b887aa3f75a26 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Delimited.java @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * A node that uses delimiters in the source form (e.g. *bold*). + */ +public interface Delimited { + + /** + * @return the opening (beginning) delimiter, e.g. * + */ + String getOpeningDelimiter(); + + /** + * @return the closing (ending) delimiter, e.g. * + */ + String getClosingDelimiter(); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Document.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Document.java new file mode 100644 index 0000000000000..390728318eca5 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Document.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class Document extends Block { + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Emphasis.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Emphasis.java new file mode 100644 index 0000000000000..86b09f4f2f6dd --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Emphasis.java @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class Emphasis extends Node implements Delimited { + + private String delimiter; + + public Emphasis() { + } + + public Emphasis(String delimiter) { + this.delimiter = delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + @Override + public String getOpeningDelimiter() { + return delimiter; + } + + @Override + public String getClosingDelimiter() { + return delimiter; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/FencedCodeBlock.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/FencedCodeBlock.java new file mode 100644 index 0000000000000..43fd68aa06f07 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/FencedCodeBlock.java @@ -0,0 +1,91 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class FencedCodeBlock extends Block { + + private char fenceChar; + private int fenceLength; + private int fenceIndent; + + private String info; + private String literal; + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public char getFenceChar() { + return fenceChar; + } + + public void setFenceChar(char fenceChar) { + this.fenceChar = fenceChar; + } + + public int getFenceLength() { + return fenceLength; + } + + public void setFenceLength(int fenceLength) { + this.fenceLength = fenceLength; + } + + public int getFenceIndent() { + return fenceIndent; + } + + public void setFenceIndent(int fenceIndent) { + this.fenceIndent = fenceIndent; + } + + /** + * @see CommonMark spec + */ + public String getInfo() { + return info; + } + + public void setInfo(String info) { + this.info = info; + } + + public String getLiteral() { + return literal; + } + + public void setLiteral(String literal) { + this.literal = literal; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HardLineBreak.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HardLineBreak.java new file mode 100644 index 0000000000000..5c85504a8fbc7 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HardLineBreak.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class HardLineBreak extends Node { + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Heading.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Heading.java new file mode 100644 index 0000000000000..49a8225fca1fa --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Heading.java @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class Heading extends Block { + + private int level; + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HtmlBlock.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HtmlBlock.java new file mode 100644 index 0000000000000..94380c084e12b --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HtmlBlock.java @@ -0,0 +1,56 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * HTML block + * + * @see CommonMark Spec + */ +public class HtmlBlock extends Block { + + private String literal; + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public String getLiteral() { + return literal; + } + + public void setLiteral(String literal) { + this.literal = literal; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HtmlInline.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HtmlInline.java new file mode 100644 index 0000000000000..7402606ac83ec --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/HtmlInline.java @@ -0,0 +1,56 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * Inline HTML element. + * + * @see CommonMark Spec + */ +public class HtmlInline extends Node { + + private String literal; + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public String getLiteral() { + return literal; + } + + public void setLiteral(String literal) { + this.literal = literal; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Image.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Image.java new file mode 100644 index 0000000000000..36a3f2f72212f --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Image.java @@ -0,0 +1,73 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class Image extends Node { + + private String destination; + private String title; + + public Image() { + } + + public Image(String destination, String title) { + this.destination = destination; + this.title = 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/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/IndentedCodeBlock.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/IndentedCodeBlock.java new file mode 100644 index 0000000000000..d3b4cec41d8a4 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/IndentedCodeBlock.java @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class IndentedCodeBlock extends Block { + + private String literal; + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public String getLiteral() { + return literal; + } + + public void setLiteral(String literal) { + this.literal = literal; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Link.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Link.java new file mode 100644 index 0000000000000..448c0b063db19 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Link.java @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * A link with a destination and an optional title; the link text is in child nodes. + *

+ * Example for an inline link in a CommonMark document: + *


+ * [link](/uri "title")
+ * 
+ *

+ * The corresponding Link node would look like this: + *

    + *
  • {@link #getDestination()} returns {@code "/uri"} + *
  • {@link #getTitle()} returns {@code "title"} + *
  • A {@link Text} child node with {@link Text#getLiteral() getLiteral} that returns {@code "link"}
  • + *
+ *

+ * Note that the text in the link can contain inline formatting, so it could also contain an {@link Image} or + * {@link Emphasis}, etc. + * + * @see CommonMark Spec for links + */ +public class Link extends Node { + + private String destination; + private String title; + + public Link() { + } + + public Link(String destination, String title) { + this.destination = destination; + this.title = 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/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/LinkReferenceDefinition.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/LinkReferenceDefinition.java new file mode 100644 index 0000000000000..9f3e7107e789a --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/LinkReferenceDefinition.java @@ -0,0 +1,89 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * A link reference definition, e.g.: + *


+ * [foo]: /url "title"
+ * 
+ *

+ * They can be referenced anywhere else in the document to produce a link using [foo]. The definitions + * themselves are usually not rendered in the final output. + * + * @see Link reference definitions + */ +public class LinkReferenceDefinition extends Node { + + private String label; + private String destination; + private String title; + + public LinkReferenceDefinition() { + } + + public LinkReferenceDefinition(String label, String destination, String title) { + this.label = label; + 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; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ListBlock.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ListBlock.java new file mode 100644 index 0000000000000..67cccde093a3e --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ListBlock.java @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public abstract class ListBlock extends Block { + + private boolean tight; + + /** + * @return whether this list is tight or loose + * @see CommonMark Spec for tight lists + */ + public boolean isTight() { + return tight; + } + + public void setTight(boolean tight) { + this.tight = tight; + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ListItem.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ListItem.java new file mode 100644 index 0000000000000..aeb99e7828c1f --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ListItem.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class ListItem extends Block { + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Node.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Node.java new file mode 100644 index 0000000000000..1607ec2ef5f3d --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Node.java @@ -0,0 +1,193 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * The base class of all CommonMark AST nodes ({@link Block} and inlines). + *

+ * A node can have multiple children, and a parent (except for the root node). + */ +public abstract class Node { + + private Node parent = null; + private Node firstChild = null; + private Node lastChild = null; + private Node prev = null; + private Node next = null; + private List sourceSpans = null; + + public abstract void accept(Visitor visitor); + + public Node getNext() { + return next; + } + + public Node getPrevious() { + return prev; + } + + public Node getFirstChild() { + return firstChild; + } + + public Node getLastChild() { + return lastChild; + } + + public Node getParent() { + return parent; + } + + protected void setParent(Node parent) { + this.parent = parent; + } + + public void appendChild(Node child) { + child.unlink(); + child.setParent(this); + if (this.lastChild != null) { + this.lastChild.next = child; + child.prev = this.lastChild; + this.lastChild = child; + } else { + this.firstChild = child; + this.lastChild = child; + } + } + + public void prependChild(Node child) { + child.unlink(); + child.setParent(this); + if (this.firstChild != null) { + this.firstChild.prev = child; + child.next = this.firstChild; + this.firstChild = child; + } else { + this.firstChild = child; + this.lastChild = child; + } + } + + public void unlink() { + if (this.prev != null) { + this.prev.next = this.next; + } else if (this.parent != null) { + this.parent.firstChild = this.next; + } + if (this.next != null) { + this.next.prev = this.prev; + } else if (this.parent != null) { + this.parent.lastChild = this.prev; + } + this.parent = null; + this.next = null; + this.prev = null; + } + + public void insertAfter(Node sibling) { + sibling.unlink(); + sibling.next = this.next; + if (sibling.next != null) { + sibling.next.prev = sibling; + } + sibling.prev = this; + this.next = sibling; + sibling.parent = this.parent; + if (sibling.next == null) { + sibling.parent.lastChild = sibling; + } + } + + public void insertBefore(Node sibling) { + sibling.unlink(); + sibling.prev = this.prev; + if (sibling.prev != null) { + sibling.prev.next = sibling; + } + sibling.next = this; + this.prev = sibling; + sibling.parent = this.parent; + if (sibling.prev == null) { + sibling.parent.firstChild = sibling; + } + } + + + /** + * @return the source spans of this node if included by the parser, an empty list otherwise + * @since 0.16.0 + */ + public List getSourceSpans() { + return sourceSpans != null ? Collections.unmodifiableList(sourceSpans) : Collections.emptyList(); + } + + /** + * Replace the current source spans with the provided list. + * + * @param sourceSpans the new source spans to set + * @since 0.16.0 + */ + public void setSourceSpans(List sourceSpans) { + if (sourceSpans.isEmpty()) { + this.sourceSpans = null; + } else { + this.sourceSpans = new ArrayList<>(sourceSpans); + } + } + + /** + * Add a source span to the end of the list. + * + * @param sourceSpan the source span to add + * @since 0.16.0 + */ + public void addSourceSpan(SourceSpan sourceSpan) { + if (sourceSpans == null) { + this.sourceSpans = new ArrayList<>(); + } + this.sourceSpans.add(sourceSpan); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + toStringAttributes() + "}"; + } + + protected String toStringAttributes() { + return ""; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Nodes.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Nodes.java new file mode 100644 index 0000000000000..5be07004284ec --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Nodes.java @@ -0,0 +1,98 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +import java.util.Iterator; + +/** + * Utility class for working with multiple {@link Node}s. + * + * @since 0.16.0 + */ +public class Nodes { + + private Nodes() { + } + + /** + * The nodes between (not including) start and end. + */ + public static Iterable between(Node start, Node end) { + return new NodeIterable(start.getNext(), end); + } + + private static class NodeIterable implements Iterable { + + private final Node first; + private final Node end; + + private NodeIterable(Node first, Node end) { + this.first = first; + this.end = end; + } + + @Override + public Iterator iterator() { + return new NodeIterator(first, end); + } + } + + private static class NodeIterator implements Iterator { + + private Node node; + private final Node end; + + private NodeIterator(Node first, Node end) { + node = first; + this.end = end; + } + + @Override + public boolean hasNext() { + return node != null && node != end; + } + + @Override + public Node next() { + Node result = node; + node = node.getNext(); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + } +} + diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/OrderedList.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/OrderedList.java new file mode 100644 index 0000000000000..5fa309dd1265c --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/OrderedList.java @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class OrderedList extends ListBlock { + + private int startNumber; + private char delimiter; + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public int getStartNumber() { + return startNumber; + } + + public void setStartNumber(int startNumber) { + this.startNumber = startNumber; + } + + public char getDelimiter() { + return delimiter; + } + + public void setDelimiter(char delimiter) { + this.delimiter = delimiter; + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Paragraph.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Paragraph.java new file mode 100644 index 0000000000000..32168c0251e11 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Paragraph.java @@ -0,0 +1,44 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * A paragraph block, contains inline nodes such as {@link Text} + */ +public class Paragraph extends Block { + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SoftLineBreak.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SoftLineBreak.java new file mode 100644 index 0000000000000..1fedc5c899b22 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SoftLineBreak.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class SoftLineBreak extends Node { + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SourceSpan.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SourceSpan.java new file mode 100644 index 0000000000000..b816a8bf437f0 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SourceSpan.java @@ -0,0 +1,122 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +import java.util.Objects; + +/** + * A source span references a snippet of text from the source input. + *

+ * It has a starting position (line and column index) and a length of how many characters it spans. + *

+ * For example, this CommonMark source text: + *


+ * > foo
+ * 
+ * The {@link BlockQuote} node would have this source span: line 0, column 0, length 5. + *

+ * The {@link Paragraph} node inside it would have: line 0, column 2, length 3. + *

+ * If a block has multiple lines, it will have a source span for each line. + *

+ * Note that the column index and length are measured in Java characters (UTF-16 code units). If you're outputting them + * to be consumed by another programming language, e.g. one that uses UTF-8 strings, you will need to translate them, + * otherwise characters such as emojis will result in incorrect positions. + * + * @since 0.16.0 + */ +public class SourceSpan { + + private final int lineIndex; + private final int columnIndex; + private final int length; + + public static SourceSpan of(int lineIndex, int columnIndex, int length) { + return new SourceSpan(lineIndex, columnIndex, length); + } + + private SourceSpan(int lineIndex, int columnIndex, int length) { + this.lineIndex = lineIndex; + this.columnIndex = columnIndex; + this.length = length; + } + + /** + * @return 0-based index of line in source + */ + public int getLineIndex() { + return lineIndex; + } + + /** + * @return 0-based index of column (character on line) in source + */ + public int getColumnIndex() { + return columnIndex; + } + + /** + * @return length of the span in characters + */ + public int getLength() { + return length; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SourceSpan that = (SourceSpan) o; + return lineIndex == that.lineIndex && + columnIndex == that.columnIndex && + length == that.length; + } + + @Override + public int hashCode() { + return Objects.hash(lineIndex, columnIndex, length); + } + + @Override + public String toString() { + return "SourceSpan{" + + "line=" + lineIndex + + ", column=" + columnIndex + + ", length=" + length + + "}"; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SourceSpans.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SourceSpans.java new file mode 100644 index 0000000000000..4a35a89840d19 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/SourceSpans.java @@ -0,0 +1,85 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A list of source spans that can be added to. Takes care of merging adjacent source spans. + * + * @since 0.16.0 + */ +public class SourceSpans { + + private List sourceSpans; + + public static SourceSpans empty() { + return new SourceSpans(); + } + + public List getSourceSpans() { + return sourceSpans != null ? sourceSpans : Collections.emptyList(); + } + + public void addAllFrom(Iterable nodes) { + for (Node node : nodes) { + addAll(node.getSourceSpans()); + } + } + + public void addAll(List other) { + if (other.isEmpty()) { + return; + } + + if (sourceSpans == null) { + sourceSpans = new ArrayList<>(); + } + + if (sourceSpans.isEmpty()) { + sourceSpans.addAll(other); + } else { + int lastIndex = sourceSpans.size() - 1; + SourceSpan a = sourceSpans.get(lastIndex); + SourceSpan b = other.get(0); + if (a.getLineIndex() == b.getLineIndex() && a.getColumnIndex() + a.getLength() == b.getColumnIndex()) { + sourceSpans.set(lastIndex, SourceSpan.of(a.getLineIndex(), a.getColumnIndex(), a.getLength() + b.getLength())); + sourceSpans.addAll(other.subList(1, other.size())); + } else { + sourceSpans.addAll(other); + } + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/StrongEmphasis.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/StrongEmphasis.java new file mode 100644 index 0000000000000..1efed6d99294b --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/StrongEmphasis.java @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class StrongEmphasis extends Node implements Delimited { + + private String delimiter; + + public StrongEmphasis() { + } + + public StrongEmphasis(String delimiter) { + this.delimiter = delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + @Override + public String getOpeningDelimiter() { + return delimiter; + } + + @Override + public String getClosingDelimiter() { + return delimiter; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Text.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Text.java new file mode 100644 index 0000000000000..976134d1dd015 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Text.java @@ -0,0 +1,63 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class Text extends Node { + + private String literal; + + public Text() { + } + + public Text(String literal) { + this.literal = literal; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + public String getLiteral() { + return literal; + } + + public void setLiteral(String literal) { + this.literal = literal; + } + + @Override + protected String toStringAttributes() { + return "literal=" + literal; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ThematicBreak.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ThematicBreak.java new file mode 100644 index 0000000000000..e337dc9e71176 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ThematicBreak.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +public class ThematicBreak extends Block { + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Visitor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Visitor.java new file mode 100644 index 0000000000000..460a6876c28d7 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Visitor.java @@ -0,0 +1,87 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.node; + +/** + * Node visitor. + *

+ * Implementations should subclass {@link AbstractVisitor} instead of implementing this directly. + */ +public interface Visitor { + + void visit(BlockQuote blockQuote); + + void visit(BulletList bulletList); + + void visit(Code code); + + void visit(Document document); + + void visit(Emphasis emphasis); + + void visit(FencedCodeBlock fencedCodeBlock); + + void visit(HardLineBreak hardLineBreak); + + void visit(Heading heading); + + void visit(ThematicBreak thematicBreak); + + void visit(HtmlInline htmlInline); + + void visit(HtmlBlock htmlBlock); + + void visit(Image image); + + void visit(IndentedCodeBlock indentedCodeBlock); + + void visit(Link link); + + void visit(ListItem listItem); + + void visit(OrderedList orderedList); + + void visit(Paragraph paragraph); + + void visit(SoftLineBreak softLineBreak); + + void visit(StrongEmphasis strongEmphasis); + + void visit(Text text); + + void visit(LinkReferenceDefinition linkReferenceDefinition); + + void visit(CustomBlock customBlock); + + void visit(CustomNode customNode); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/package-info.java new file mode 100644 index 0000000000000..e957ac5b7725f --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/package-info.java @@ -0,0 +1,36 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +/** + * AST node types (see {@link org.commonmark.node.Node}) and visitors (see {@link org.commonmark.node.AbstractVisitor}) + */ +package jdk.internal.org.commonmark.node; diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/package-info.java new file mode 100644 index 0000000000000..99e6b39ca43ec --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/package-info.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +/** + * Root package of commonmark-java + *

    + *
  • {@link org.commonmark.parser} for parsing input text to AST nodes
  • + *
  • {@link org.commonmark.node} for AST node types and visitors
  • + *
  • {@link org.commonmark.renderer.html} for HTML rendering
  • + *
+ */ +package jdk.internal.org.commonmark; diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/IncludeSourceSpans.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/IncludeSourceSpans.java new file mode 100644 index 0000000000000..27d91f96e7644 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/IncludeSourceSpans.java @@ -0,0 +1,54 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser; + +/** + * Whether to include {@link org.commonmark.node.SourceSpan} or not while parsing, + * see {@link Parser.Builder#includeSourceSpans(IncludeSourceSpans)}. + * + * @since 0.16.0 + */ +public enum IncludeSourceSpans { + /** + * Do not include source spans. + */ + NONE, + /** + * Include source spans on {@link org.commonmark.node.Block} nodes. + */ + BLOCKS, + /** + * Include source spans on block nodes and inline nodes. + */ + BLOCKS_AND_INLINES, +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParser.java new file mode 100644 index 0000000000000..e0e4246eb095a --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParser.java @@ -0,0 +1,47 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser; + +import jdk.internal.org.commonmark.node.Node; + +/** + * Parser for inline content (text, links, emphasized text, etc). + */ +public interface InlineParser { + + /** + * @param lines the source content to parse as inline + * @param node the node to append resulting nodes to (as children) + */ + void parse(SourceLines lines, Node node); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParserContext.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParserContext.java new file mode 100644 index 0000000000000..b28faff22be5d --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParserContext.java @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser; + +import jdk.internal.org.commonmark.node.LinkReferenceDefinition; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.util.List; + +/** + * Context for inline parsing. + */ +public interface InlineParserContext { + + /** + * @return custom delimiter processors that have been configured with {@link Parser.Builder#customDelimiterProcessor(DelimiterProcessor)} + */ + List getCustomDelimiterProcessors(); + + /** + * 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 + */ + LinkReferenceDefinition getLinkReferenceDefinition(String label); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParserFactory.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParserFactory.java new file mode 100644 index 0000000000000..6fb0860f48e1a --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/InlineParserFactory.java @@ -0,0 +1,40 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser; + +/** + * Factory for custom inline parser. + */ +public interface InlineParserFactory { + InlineParser create(InlineParserContext inlineParserContext); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/Parser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/Parser.java new file mode 100644 index 0000000000000..40712b9787379 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/Parser.java @@ -0,0 +1,316 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser; + +import jdk.internal.org.commonmark.Extension; +import jdk.internal.org.commonmark.internal.DocumentParser; +import jdk.internal.org.commonmark.internal.InlineParserContextImpl; +import jdk.internal.org.commonmark.internal.InlineParserImpl; +import jdk.internal.org.commonmark.internal.LinkReferenceDefinitions; +import jdk.internal.org.commonmark.node.*; +import jdk.internal.org.commonmark.parser.block.BlockParserFactory; +import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + + +/** + * Parses input text to a tree of nodes. + *

+ * Start with the {@link #builder} method, configure the parser and build it. Example: + *


+ * Parser parser = Parser.builder().build();
+ * Node document = parser.parse("input text");
+ * 
+ */ +public class Parser { + + private final List blockParserFactories; + private final List delimiterProcessors; + private final InlineParserFactory inlineParserFactory; + private final List postProcessors; + private final IncludeSourceSpans includeSourceSpans; + + private Parser(Builder builder) { + this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); + this.inlineParserFactory = builder.getInlineParserFactory(); + this.postProcessors = builder.postProcessors; + this.delimiterProcessors = builder.delimiterProcessors; + this.includeSourceSpans = builder.includeSourceSpans; + + // 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, new LinkReferenceDefinitions())); + } + + /** + * Create a new builder for configuring a {@link Parser}. + * + * @return a builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Parse the specified input text into a tree of nodes. + *

+ * This method is thread-safe (a new parser state is used for each invocation). + * + * @param input the text to parse - must not be null + * @return the root node + */ + public Node parse(String input) { + if (input == null) { + throw new NullPointerException("input must not be null"); + } + DocumentParser documentParser = createDocumentParser(); + Node document = documentParser.parse(input); + return postProcess(document); + } + + /** + * Parse the specified reader into a tree of nodes. The caller is responsible for closing the reader. + *


+     * Parser parser = Parser.builder().build();
+     * try (InputStreamReader reader = new InputStreamReader(new FileInputStream("file.md"), StandardCharsets.UTF_8)) {
+     *     Node document = parser.parseReader(reader);
+     *     // ...
+     * }
+     * 
+ * Note that if you have a file with a byte order mark (BOM), you need to skip it before handing the reader to this + * library. There's existing classes that do that, e.g. see {@code BOMInputStream} in Commons IO. + *

+ * This method is thread-safe (a new parser state is used for each invocation). + * + * @param input the reader to parse - must not be null + * @return the root node + * @throws IOException when reading throws an exception + */ + public Node parseReader(Reader input) throws IOException { + if (input == null) { + throw new NullPointerException("input must not be null"); + } + + DocumentParser documentParser = createDocumentParser(); + Node document = documentParser.parse(input); + return postProcess(document); + } + + private DocumentParser createDocumentParser() { + return new DocumentParser(blockParserFactories, inlineParserFactory, delimiterProcessors, includeSourceSpans); + } + + private Node postProcess(Node document) { + for (PostProcessor postProcessor : postProcessors) { + document = postProcessor.process(document); + } + return document; + } + + /** + * Builder for configuring a {@link Parser}. + */ + public static class Builder { + private final List blockParserFactories = new ArrayList<>(); + private final List delimiterProcessors = new ArrayList<>(); + private final List postProcessors = new ArrayList<>(); + private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); + private InlineParserFactory inlineParserFactory; + private IncludeSourceSpans includeSourceSpans = IncludeSourceSpans.NONE; + + /** + * @return the configured {@link Parser} + */ + public Parser build() { + return new Parser(this); + } + + /** + * @param extensions extensions to use on this parser + * @return {@code this} + */ + public Builder extensions(Iterable extensions) { + if (extensions == null) { + throw new NullPointerException("extensions must not be null"); + } + for (Extension extension : extensions) { + if (extension instanceof ParserExtension) { + ParserExtension parserExtension = (ParserExtension) extension; + parserExtension.extend(this); + } + } + return this; + } + + /** + * Describe the list of markdown features the parser will recognize and parse. + *

+ * By default, CommonMark will recognize and parse the following set of "block" elements: + *

    + *
  • {@link Heading} ({@code #}) + *
  • {@link HtmlBlock} ({@code }) + *
  • {@link ThematicBreak} (Horizontal Rule) ({@code ---}) + *
  • {@link FencedCodeBlock} ({@code ```}) + *
  • {@link IndentedCodeBlock} + *
  • {@link BlockQuote} ({@code >}) + *
  • {@link ListBlock} (Ordered / Unordered List) ({@code 1. / *}) + *
+ *

+ * To parse only a subset of the features listed above, pass a list of each feature's associated {@link Block} class. + *

+ * E.g., to only parse headings and lists: + *

+         *     {@code
+         *     Parser.builder().enabledBlockTypes(new HashSet<>(Arrays.asList(Heading.class, ListBlock.class)));
+         *     }
+         * 
+ * + * @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. + * @return {@code this} + */ + public Builder enabledBlockTypes(Set> enabledBlockTypes) { + if (enabledBlockTypes == null) { + throw new NullPointerException("enabledBlockTypes must not be null"); + } + DocumentParser.checkEnabledBlockTypes(enabledBlockTypes); + this.enabledBlockTypes = enabledBlockTypes; + return this; + } + + /** + * Whether to calculate {@link org.commonmark.node.SourceSpan} for {@link Node}. + *

+ * By default, source spans are disabled. + * + * @param includeSourceSpans which kind of source spans should be included + * @return {@code this} + * @since 0.16.0 + */ + public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { + this.includeSourceSpans = includeSourceSpans; + return this; + } + + /** + * Adds a custom block parser factory. + *

+ * Note that custom factories are applied before the built-in factories. This is so that + * extensions can change how some syntax is parsed that would otherwise be handled by built-in factories. + * "With great power comes great responsibility." + * + * @param blockParserFactory a block parser factory implementation + * @return {@code this} + */ + public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { + if (blockParserFactory == null) { + throw new NullPointerException("blockParserFactory must not be null"); + } + blockParserFactories.add(blockParserFactory); + return this; + } + + /** + * Adds a custom delimiter processor. + *

+ * Note that multiple delimiter processors with the same characters can be added, as long as they have a + * different minimum length. In that case, the processor with the shortest matching length is used. Adding more + * than one delimiter processor with the same character and minimum length is invalid. + * + * @param delimiterProcessor a delimiter processor implementation + * @return {@code this} + */ + public Builder customDelimiterProcessor(DelimiterProcessor delimiterProcessor) { + if (delimiterProcessor == null) { + throw new NullPointerException("delimiterProcessor must not be null"); + } + delimiterProcessors.add(delimiterProcessor); + return this; + } + + public Builder postProcessor(PostProcessor postProcessor) { + if (postProcessor == null) { + throw new NullPointerException("postProcessor must not be null"); + } + postProcessors.add(postProcessor); + return this; + } + + /** + * Overrides the parser used for inline markdown processing. + *

+ * Provide an implementation of InlineParserFactory which provides a custom inline parser + * to modify how the following are parsed: + * bold (**) + * italic (*) + * strikethrough (~~) + * backtick quote (`) + * link ([title](http://)) + * image (![alt](http://)) + *

+ * Note that if this method is not called or the inline parser factory is set to null, then the default + * implementation will be used. + * + * @param inlineParserFactory an inline parser factory implementation + * @return {@code this} + */ + public Builder inlineParserFactory(InlineParserFactory inlineParserFactory) { + this.inlineParserFactory = inlineParserFactory; + return this; + } + + private InlineParserFactory getInlineParserFactory() { + if (inlineParserFactory != null) { + return inlineParserFactory; + } + return new InlineParserFactory() { + @Override + public InlineParser create(InlineParserContext inlineParserContext) { + return new InlineParserImpl(inlineParserContext); + } + }; + } + } + + /** + * Extension for {@link Parser}. + */ + public interface ParserExtension extends Extension { + void extend(Builder parserBuilder); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/PostProcessor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/PostProcessor.java new file mode 100644 index 0000000000000..5cf4f480ae0ca --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/PostProcessor.java @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser; + +import jdk.internal.org.commonmark.node.Node; + +public interface PostProcessor { + + /** + * @param node the node to post-process + * @return the result of post-processing, may be a modified {@code node} argument + */ + Node process(Node node); + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/SourceLine.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/SourceLine.java new file mode 100644 index 0000000000000..4008e6bece098 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/SourceLine.java @@ -0,0 +1,79 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser; + +import jdk.internal.org.commonmark.node.SourceSpan; + +/** + * A line or part of a line from the input source. + * + * @since 0.16.0 + */ +public class SourceLine { + + private final CharSequence content; + private final SourceSpan sourceSpan; + + public static SourceLine of(CharSequence content, SourceSpan sourceSpan) { + return new SourceLine(content, sourceSpan); + } + + private SourceLine(CharSequence content, SourceSpan sourceSpan) { + if (content == null) { + throw new NullPointerException("content must not be null"); + } + this.content = content; + this.sourceSpan = sourceSpan; + } + + public CharSequence getContent() { + return content; + } + + public SourceSpan getSourceSpan() { + return sourceSpan; + } + + public SourceLine substring(int beginIndex, int endIndex) { + CharSequence newContent = content.subSequence(beginIndex, endIndex); + SourceSpan newSourceSpan = null; + if (sourceSpan != null) { + int columnIndex = sourceSpan.getColumnIndex() + beginIndex; + int length = endIndex - beginIndex; + if (length != 0) { + newSourceSpan = SourceSpan.of(sourceSpan.getLineIndex(), columnIndex, length); + } + } + return SourceLine.of(newContent, newSourceSpan); + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/SourceLines.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/SourceLines.java new file mode 100644 index 0000000000000..59bed5c5bf614 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/SourceLines.java @@ -0,0 +1,98 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser; + +import jdk.internal.org.commonmark.node.SourceSpan; + +import java.util.ArrayList; +import java.util.List; + +/** + * A set of lines ({@link SourceLine}) from the input source. + * + * @since 0.16.0 + */ +public class SourceLines { + + private final List lines = new ArrayList<>(); + + public static SourceLines empty() { + return new SourceLines(); + } + + public static SourceLines of(SourceLine sourceLine) { + SourceLines sourceLines = new SourceLines(); + sourceLines.addLine(sourceLine); + return sourceLines; + } + + public static SourceLines of(List sourceLines) { + SourceLines result = new SourceLines(); + result.lines.addAll(sourceLines); + return result; + } + + public void addLine(SourceLine sourceLine) { + lines.add(sourceLine); + } + + public List getLines() { + return lines; + } + + public boolean isEmpty() { + return lines.isEmpty(); + } + + public String getContent() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < lines.size(); i++) { + if (i != 0) { + sb.append('\n'); + } + sb.append(lines.get(i).getContent()); + } + return sb.toString(); + } + + public List getSourceSpans() { + List sourceSpans = new ArrayList<>(); + for (SourceLine line : lines) { + SourceSpan sourceSpan = line.getSourceSpan(); + if (sourceSpan != null) { + sourceSpans.add(sourceSpan); + } + } + return sourceSpans; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/AbstractBlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/AbstractBlockParser.java new file mode 100644 index 0000000000000..26cd6220daf1c --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/AbstractBlockParser.java @@ -0,0 +1,74 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.block; + +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.SourceSpan; +import jdk.internal.org.commonmark.parser.InlineParser; +import jdk.internal.org.commonmark.parser.SourceLine; + +public abstract class AbstractBlockParser implements BlockParser { + + @Override + public boolean isContainer() { + return false; + } + + @Override + public boolean canHaveLazyContinuationLines() { + return false; + } + + @Override + public boolean canContain(Block childBlock) { + return false; + } + + @Override + public void addLine(SourceLine line) { + } + + @Override + public void addSourceSpan(SourceSpan sourceSpan) { + getBlock().addSourceSpan(sourceSpan); + } + + @Override + public void closeBlock() { + } + + @Override + public void parseInlines(InlineParser inlineParser) { + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/AbstractBlockParserFactory.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/AbstractBlockParserFactory.java new file mode 100644 index 0000000000000..40c2bf91b6666 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/AbstractBlockParserFactory.java @@ -0,0 +1,36 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.block; + +public abstract class AbstractBlockParserFactory implements BlockParserFactory { +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockContinue.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockContinue.java new file mode 100644 index 0000000000000..5fc4e028d2200 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockContinue.java @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.block; + +import jdk.internal.org.commonmark.internal.BlockContinueImpl; + +/** + * Result object for continuing parsing of a block, see static methods for constructors. + */ +public class BlockContinue { + + protected BlockContinue() { + } + + public static BlockContinue none() { + return null; + } + + public static BlockContinue atIndex(int newIndex) { + return new BlockContinueImpl(newIndex, -1, false); + } + + public static BlockContinue atColumn(int newColumn) { + return new BlockContinueImpl(-1, newColumn, false); + } + + public static BlockContinue finished() { + return new BlockContinueImpl(-1, -1, true); + } + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockParser.java new file mode 100644 index 0000000000000..76888922abcb3 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockParser.java @@ -0,0 +1,84 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.block; + +import jdk.internal.org.commonmark.node.Block; +import jdk.internal.org.commonmark.node.SourceSpan; +import jdk.internal.org.commonmark.parser.InlineParser; +import jdk.internal.org.commonmark.parser.SourceLine; + +/** + * Parser for a specific block node. + *

+ * Implementations should subclass {@link AbstractBlockParser} instead of implementing this directly. + */ +public interface BlockParser { + + /** + * Return true if the block that is parsed is a container (contains other blocks), or false if it's a leaf. + */ + boolean isContainer(); + + /** + * Return true if the block can have lazy continuation lines. + *

+ * Lazy continuation lines are lines that were rejected by this {@link #tryContinue(ParserState)} but didn't match + * any other block parsers either. + *

+ * If true is returned here, those lines will get added via {@link #addLine(SourceLine)}. For false, the block is + * closed instead. + */ + boolean canHaveLazyContinuationLines(); + + boolean canContain(Block childBlock); + + Block getBlock(); + + BlockContinue tryContinue(ParserState parserState); + + void addLine(SourceLine line); + + /** + * Add a source span of the currently parsed block. The default implementation in {@link AbstractBlockParser} adds + * it to the block. Unless you have some complicated parsing where you need to check source positions, you don't + * need to override this. + * + * @since 0.16.0 + */ + void addSourceSpan(SourceSpan sourceSpan); + + void closeBlock(); + + void parseInlines(InlineParser inlineParser); + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockParserFactory.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockParserFactory.java new file mode 100644 index 0000000000000..53cc8d35ca705 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockParserFactory.java @@ -0,0 +1,44 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.block; + +/** + * Parser factory for a block node for determining when a block starts. + *

+ * Implementations should subclass {@link AbstractBlockParserFactory} instead of implementing this directly. + */ +public interface BlockParserFactory { + + BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser); + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockStart.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockStart.java new file mode 100644 index 0000000000000..a927abe968b28 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockStart.java @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.block; + +import jdk.internal.org.commonmark.internal.BlockStartImpl; + +/** + * Result object for starting parsing of a block, see static methods for constructors. + */ +public abstract class BlockStart { + + protected BlockStart() { + } + + public static BlockStart none() { + return null; + } + + public static BlockStart of(BlockParser... blockParsers) { + return new BlockStartImpl(blockParsers); + } + + public abstract BlockStart atIndex(int newIndex); + + public abstract BlockStart atColumn(int newColumn); + + public abstract BlockStart replaceActiveBlockParser(); + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/MatchedBlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/MatchedBlockParser.java new file mode 100644 index 0000000000000..76a9669d069b5 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/MatchedBlockParser.java @@ -0,0 +1,53 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.block; + +import jdk.internal.org.commonmark.parser.SourceLines; + +/** + * Open block parser that was last matched during the continue phase. This is different from the currently active + * block parser, as an unmatched block is only closed when a new block is started. + *

This interface is not intended to be implemented by clients.

+ */ +public interface MatchedBlockParser { + + BlockParser getMatchedBlockParser(); + + /** + * Returns the current paragraph lines if the matched block is a paragraph. + * + * @return paragraph content or an empty list + */ + SourceLines getParagraphLines(); + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/ParserState.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/ParserState.java new file mode 100644 index 0000000000000..7bda0620a8a7d --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/ParserState.java @@ -0,0 +1,82 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.block; + +import jdk.internal.org.commonmark.parser.SourceLine; + +/** + * State of the parser that is used in block parsers. + *

This interface is not intended to be implemented by clients.

+ */ +public interface ParserState { + + /** + * @return the current source line being parsed (full line) + */ + SourceLine getLine(); + + /** + * @return the current index within the line (0-based) + */ + int getIndex(); + + /** + * @return the index of the next non-space character starting from {@link #getIndex()} (may be the same) (0-based) + */ + int getNextNonSpaceIndex(); + + /** + * The column is the position within the line after tab characters have been processed as 4-space tab stops. + * If the line doesn't contain any tabs, it's the same as the {@link #getIndex()}. If the line starts with a tab, + * followed by text, then the column for the first character of the text is 4 (the index is 1). + * + * @return the current column within the line (0-based) + */ + int getColumn(); + + /** + * @return the indentation in columns (either by spaces or tab stop of 4), starting from {@link #getColumn()} + */ + int getIndent(); + + /** + * @return true if the current line is blank starting from the index + */ + boolean isBlank(); + + /** + * @return the deepest open block parser + */ + BlockParser getActiveBlockParser(); + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/package-info.java new file mode 100644 index 0000000000000..7bcb61e8a8691 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/package-info.java @@ -0,0 +1,36 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +/** + * Types for extending block parsing + */ +package jdk.internal.org.commonmark.parser.block; diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/delimiter/DelimiterProcessor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/delimiter/DelimiterProcessor.java new file mode 100644 index 0000000000000..592f6a5e4335f --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/delimiter/DelimiterProcessor.java @@ -0,0 +1,76 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.delimiter; + +import jdk.internal.org.commonmark.node.Text; + +/** + * Custom delimiter processor for additional delimiters besides {@code _} and {@code *}. + *

+ * Note that implementations of this need to be thread-safe, the same instance may be used by multiple parsers. + */ +public interface DelimiterProcessor { + + /** + * @return the character that marks the beginning of a delimited node, must not clash with any built-in special + * characters + */ + char getOpeningCharacter(); + + /** + * @return the character that marks the the ending of a delimited node, must not clash with any built-in special + * characters. Note that for a symmetric delimiter such as "*", this is the same as the opening. + */ + char getClosingCharacter(); + + /** + * Minimum number of delimiter characters that are needed to activate this. Must be at least 1. + */ + int getMinLength(); + + /** + * Process the delimiter runs. + *

+ * The processor can examine the runs and the nodes and decide if it wants to process or not. If not, it should not + * change any nodes and return 0. If yes, it should do the processing (wrapping nodes, etc) and then return how many + * delimiters were used. + *

+ * Note that removal (unlinking) of the used delimiter {@link Text} nodes is done by the caller. + * + * @param openingRun the opening delimiter run + * @param closingRun the closing delimiter run + * @return how many delimiters were used; must not be greater than length of either opener or closer + */ + int process(DelimiterRun openingRun, DelimiterRun closingRun); + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/delimiter/DelimiterRun.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/delimiter/DelimiterRun.java new file mode 100644 index 0000000000000..9672c1906cfe0 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/delimiter/DelimiterRun.java @@ -0,0 +1,90 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.parser.delimiter; + +import jdk.internal.org.commonmark.node.Text; + +/** + * A delimiter run is one or more of the same delimiter character, e.g. {@code ***}. + */ +public interface DelimiterRun { + + /** + * @return whether this can open a delimiter + */ + boolean canOpen(); + + /** + * @return whether this can close a delimiter + */ + boolean canClose(); + + /** + * @return the number of characters in this delimiter run (that are left for processing) + */ + int length(); + + /** + * @return the number of characters originally in this delimiter run; at the start of processing, this is the same + * as {{@link #length()}} + */ + int originalLength(); + + /** + * @return the innermost opening delimiter, e.g. for {@code ***} this is the last {@code *} + */ + Text getOpener(); + + /** + * @return the innermost closing delimiter, e.g. for {@code ***} this is the first {@code *} + */ + Text getCloser(); + + /** + * Get the opening delimiter nodes for the specified length of delimiters. Length must be between 1 and + * {@link #length()}. + *

+ * For example, for a delimiter run {@code ***}, calling this with 1 would return the last {@code *}. + * Calling it with 2 would return the second last {@code *} and the last {@code *}. + */ + Iterable getOpeners(int length); + + /** + * Get the closing delimiter nodes for the specified length of delimiters. Length must be between 1 and + * {@link #length()}. + *

+ * For example, for a delimiter run {@code ***}, calling this with 1 would return the first {@code *}. + * Calling it with 2 would return the first {@code *} and the second {@code *}. + */ + Iterable getClosers(int length); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/package-info.java new file mode 100644 index 0000000000000..b7bc957cc06fb --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/package-info.java @@ -0,0 +1,36 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +/** + * Parsing input text to AST nodes (see {@link org.commonmark.parser.Parser}) + */ +package jdk.internal.org.commonmark.parser; diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/NodeRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/NodeRenderer.java new file mode 100644 index 0000000000000..00d6c185920f3 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/NodeRenderer.java @@ -0,0 +1,55 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer; + +import jdk.internal.org.commonmark.node.Node; + +import java.util.Set; + +/** + * A renderer for a set of node types. + */ +public interface NodeRenderer { + + /** + * @return the types of nodes that this renderer handles + */ + Set> getNodeTypes(); + + /** + * Render the specified node. + * + * @param node the node to render, will be an instance of one of {@link #getNodeTypes()} + */ + void render(Node node); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/Renderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/Renderer.java new file mode 100644 index 0000000000000..9cee90af7d9de --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/Renderer.java @@ -0,0 +1,54 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer; + +import jdk.internal.org.commonmark.node.Node; + +public interface Renderer { + + /** + * Render the tree of nodes to output. + * + * @param node the root node + * @param output output for rendering + */ + void render(Node node, Appendable output); + + /** + * Render the tree of nodes to string. + * + * @param node the root node + * @return the rendered string + */ + String render(Node node); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProvider.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProvider.java new file mode 100644 index 0000000000000..4c9b33adb80a9 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProvider.java @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +import jdk.internal.org.commonmark.node.Node; + +import java.util.Map; + +/** + * Extension point for adding/changing attributes on HTML tags for a node. + */ +public interface AttributeProvider { + + /** + * Set the attributes for a HTML tag of the specified node by modifying the provided map. + *

+ * This allows to change or even remove default attributes. With great power comes great responsibility. + *

+ * The attribute key and values will be escaped (preserving character entities), so don't escape them here, + * otherwise they will be double-escaped. + *

+ * This method may be called multiple times for the same node, if the node is rendered using multiple nested + * tags (e.g. code blocks). + * + * @param node the node to set attributes for + * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, {@code code}). + * @param attributes the attributes, with any default attributes already set in the map + */ + void setAttributes(Node node, String tagName, Map attributes); + +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProviderContext.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProviderContext.java new file mode 100644 index 0000000000000..92ca0383b748c --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProviderContext.java @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +/** + * The context for attribute providers. + *

Note: There are currently no methods here, this is for future extensibility.

+ *

This interface is not intended to be implemented by clients.

+ */ +public interface AttributeProviderContext { +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProviderFactory.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProviderFactory.java new file mode 100644 index 0000000000000..e4503daffaba2 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/AttributeProviderFactory.java @@ -0,0 +1,47 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +/** + * Factory for instantiating new attribute providers when rendering is done. + */ +public interface AttributeProviderFactory { + + /** + * Create a new attribute provider. + * + * @param context for this attribute provider + * @return an AttributeProvider + */ + AttributeProvider create(AttributeProviderContext context); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java new file mode 100644 index 0000000000000..05b34a485fd35 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -0,0 +1,352 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +import jdk.internal.org.commonmark.node.*; +import jdk.internal.org.commonmark.renderer.NodeRenderer; + +import java.util.*; + +/** + * The node renderer that renders all the core nodes (comes last in the order of node renderers). + */ +public class CoreHtmlNodeRenderer extends AbstractVisitor implements NodeRenderer { + + protected final HtmlNodeRendererContext context; + private final HtmlWriter html; + + public CoreHtmlNodeRenderer(HtmlNodeRendererContext context) { + this.context = context; + this.html = context.getWriter(); + } + + @Override + public Set> getNodeTypes() { + return new HashSet<>(Arrays.asList( + Document.class, + Heading.class, + Paragraph.class, + BlockQuote.class, + BulletList.class, + FencedCodeBlock.class, + HtmlBlock.class, + ThematicBreak.class, + IndentedCodeBlock.class, + Link.class, + ListItem.class, + OrderedList.class, + Image.class, + Emphasis.class, + StrongEmphasis.class, + Text.class, + Code.class, + HtmlInline.class, + SoftLineBreak.class, + HardLineBreak.class + )); + } + + @Override + public void render(Node node) { + node.accept(this); + } + + @Override + public void visit(Document document) { + // No rendering itself + visitChildren(document); + } + + @Override + public void visit(Heading heading) { + String htag = "h" + heading.getLevel(); + html.line(); + html.tag(htag, getAttrs(heading, htag)); + visitChildren(heading); + html.tag('/' + htag); + html.line(); + } + + @Override + public void visit(Paragraph paragraph) { + boolean inTightList = isInTightList(paragraph); + if (!inTightList) { + html.line(); + html.tag("p", getAttrs(paragraph, "p")); + } + visitChildren(paragraph); + if (!inTightList) { + html.tag("/p"); + html.line(); + } + } + + @Override + public void visit(BlockQuote blockQuote) { + html.line(); + html.tag("blockquote", getAttrs(blockQuote, "blockquote")); + html.line(); + visitChildren(blockQuote); + html.line(); + html.tag("/blockquote"); + html.line(); + } + + @Override + public void visit(BulletList bulletList) { + renderListBlock(bulletList, "ul", getAttrs(bulletList, "ul")); + } + + @Override + public void visit(FencedCodeBlock fencedCodeBlock) { + String literal = fencedCodeBlock.getLiteral(); + Map attributes = new LinkedHashMap<>(); + String info = fencedCodeBlock.getInfo(); + if (info != null && !info.isEmpty()) { + int space = info.indexOf(" "); + String language; + if (space == -1) { + language = info; + } else { + language = info.substring(0, space); + } + attributes.put("class", "language-" + language); + } + renderCodeBlock(literal, fencedCodeBlock, attributes); + } + + @Override + public void visit(HtmlBlock htmlBlock) { + html.line(); + if (context.shouldEscapeHtml()) { + html.tag("p", getAttrs(htmlBlock, "p")); + html.text(htmlBlock.getLiteral()); + html.tag("/p"); + } else { + html.raw(htmlBlock.getLiteral()); + } + html.line(); + } + + @Override + public void visit(ThematicBreak thematicBreak) { + html.line(); + html.tag("hr", getAttrs(thematicBreak, "hr"), true); + html.line(); + } + + @Override + public void visit(IndentedCodeBlock indentedCodeBlock) { + renderCodeBlock(indentedCodeBlock.getLiteral(), indentedCodeBlock, Collections.emptyMap()); + } + + @Override + public void visit(Link link) { + Map attrs = new LinkedHashMap<>(); + String url = link.getDestination(); + + if (context.shouldSanitizeUrls()) { + url = context.urlSanitizer().sanitizeLinkUrl(url); + attrs.put("rel", "nofollow"); + } + + url = context.encodeUrl(url); + attrs.put("href", url); + if (link.getTitle() != null) { + attrs.put("title", link.getTitle()); + } + html.tag("a", getAttrs(link, "a", attrs)); + visitChildren(link); + html.tag("/a"); + } + + @Override + public void visit(ListItem listItem) { + html.tag("li", getAttrs(listItem, "li")); + visitChildren(listItem); + html.tag("/li"); + html.line(); + } + + @Override + public void visit(OrderedList orderedList) { + int start = orderedList.getStartNumber(); + Map attrs = new LinkedHashMap<>(); + if (start != 1) { + attrs.put("start", String.valueOf(start)); + } + renderListBlock(orderedList, "ol", getAttrs(orderedList, "ol", attrs)); + } + + @Override + public void visit(Image image) { + String url = image.getDestination(); + + AltTextVisitor altTextVisitor = new AltTextVisitor(); + image.accept(altTextVisitor); + String altText = altTextVisitor.getAltText(); + + Map attrs = new LinkedHashMap<>(); + if (context.shouldSanitizeUrls()) { + url = context.urlSanitizer().sanitizeImageUrl(url); + } + + attrs.put("src", context.encodeUrl(url)); + attrs.put("alt", altText); + if (image.getTitle() != null) { + attrs.put("title", image.getTitle()); + } + + html.tag("img", getAttrs(image, "img", attrs), true); + } + + @Override + public void visit(Emphasis emphasis) { + html.tag("em", getAttrs(emphasis, "em")); + visitChildren(emphasis); + html.tag("/em"); + } + + @Override + public void visit(StrongEmphasis strongEmphasis) { + html.tag("strong", getAttrs(strongEmphasis, "strong")); + visitChildren(strongEmphasis); + html.tag("/strong"); + } + + @Override + public void visit(Text text) { + html.text(text.getLiteral()); + } + + @Override + public void visit(Code code) { + html.tag("code", getAttrs(code, "code")); + html.text(code.getLiteral()); + html.tag("/code"); + } + + @Override + public void visit(HtmlInline htmlInline) { + if (context.shouldEscapeHtml()) { + html.text(htmlInline.getLiteral()); + } else { + html.raw(htmlInline.getLiteral()); + } + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + html.raw(context.getSoftbreak()); + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + html.tag("br", getAttrs(hardLineBreak, "br"), true); + html.line(); + } + + @Override + protected void visitChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } + + private void renderCodeBlock(String literal, Node node, Map attributes) { + html.line(); + html.tag("pre", getAttrs(node, "pre")); + html.tag("code", getAttrs(node, "code", attributes)); + html.text(literal); + html.tag("/code"); + html.tag("/pre"); + html.line(); + } + + private void renderListBlock(ListBlock listBlock, String tagName, Map attributes) { + html.line(); + html.tag(tagName, attributes); + html.line(); + visitChildren(listBlock); + html.line(); + html.tag('/' + tagName); + html.line(); + } + + private boolean isInTightList(Paragraph paragraph) { + Node parent = paragraph.getParent(); + if (parent != null) { + Node gramps = parent.getParent(); + if (gramps instanceof ListBlock) { + ListBlock list = (ListBlock) gramps; + return list.isTight(); + } + } + return false; + } + + private Map getAttrs(Node node, String tagName) { + return getAttrs(node, tagName, Collections.emptyMap()); + } + + private Map getAttrs(Node node, String tagName, Map defaultAttributes) { + return context.extendAttributes(node, tagName, defaultAttributes); + } + + private static class AltTextVisitor extends AbstractVisitor { + + private final StringBuilder sb = new StringBuilder(); + + String getAltText() { + return sb.toString(); + } + + @Override + public void visit(Text text) { + sb.append(text.getLiteral()); + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + sb.append('\n'); + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + sb.append('\n'); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/DefaultUrlSanitizer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/DefaultUrlSanitizer.java new file mode 100644 index 0000000000000..16e094afe16cd --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/DefaultUrlSanitizer.java @@ -0,0 +1,115 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * + * Allows http, https and mailto protocols for url. + * Also allows protocol relative urls, and relative urls. + * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java + */ +public class DefaultUrlSanitizer implements UrlSanitizer { + private Set protocols; + + public DefaultUrlSanitizer() { + this(Arrays.asList("http", "https", "mailto")); + } + + public DefaultUrlSanitizer(Collection protocols) { + this.protocols = new HashSet<>(protocols); + } + + @Override + public String sanitizeLinkUrl(String url) { + url = stripHtmlSpaces(url); + protocol_loop: + for (int i = 0, n = url.length(); i < n; ++i) { + switch (url.charAt(i)) { + case '/': + case '#': + case '?': // No protocol. + break protocol_loop; + case ':': + String protocol = url.substring(0, i).toLowerCase(); + if (!protocols.contains(protocol)) { + return ""; + } + break protocol_loop; + } + } + return url; + } + + + @Override + public String sanitizeImageUrl(String url) { + return sanitizeLinkUrl(url); + } + + private String stripHtmlSpaces(String s) { + int i = 0, n = s.length(); + for (; n > i; --n) { + if (!isHtmlSpace(s.charAt(n - 1))) { + break; + } + } + for (; i < n; ++i) { + if (!isHtmlSpace(s.charAt(i))) { + break; + } + } + if (i == 0 && n == s.length()) { + return s; + } + return s.substring(i, n); + } + + private boolean isHtmlSpace(int ch) { + switch (ch) { + case ' ': + case '\t': + case '\n': + case '\u000c': + case '\r': + return true; + default: + return false; + + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlNodeRendererContext.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlNodeRendererContext.java new file mode 100644 index 0000000000000..78dc295cb21e0 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlNodeRendererContext.java @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +import jdk.internal.org.commonmark.node.Image; +import jdk.internal.org.commonmark.node.Link; +import jdk.internal.org.commonmark.node.Node; + +import java.util.Map; + +public interface HtmlNodeRendererContext { + + /** + * @param url to be encoded + * @return an encoded URL (depending on the configuration) + */ + String encodeUrl(String url); + + /** + * Let extensions modify the HTML tag attributes. + * + * @param node the node for which the attributes are applied + * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, {@code code}). + * @param attributes the attributes that were calculated by the renderer + * @return the extended attributes with added/updated/removed entries + */ + Map extendAttributes(Node node, String tagName, Map attributes); + + /** + * @return the HTML writer to use + */ + HtmlWriter getWriter(); + + /** + * @return HTML that should be rendered for a soft line break + */ + String getSoftbreak(); + + /** + * Render the specified node and its children using the configured renderers. This should be used to render child + * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * + * @param node the node to render + */ + void render(Node node); + + /** + * @return whether HTML blocks and tags should be escaped or not + */ + boolean shouldEscapeHtml(); + + /** + * @return true if the {@link UrlSanitizer} should be used. + * @since 0.14.0 + */ + boolean shouldSanitizeUrls(); + + /** + * @return Sanitizer to use for securing {@link Link} href and {@link Image} src if {@link #shouldSanitizeUrls()} is true. + * @since 0.14.0 + */ + UrlSanitizer urlSanitizer(); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlNodeRendererFactory.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlNodeRendererFactory.java new file mode 100644 index 0000000000000..c86d976ae0bf1 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlNodeRendererFactory.java @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +import jdk.internal.org.commonmark.renderer.NodeRenderer; + +/** + * Factory for instantiating new node renderers when rendering is done. + */ +public interface HtmlNodeRendererFactory { + + /** + * Create a new node renderer for the specified rendering context. + * + * @param context the context for rendering (normally passed on to the node renderer) + * @return a node renderer + */ + NodeRenderer create(HtmlNodeRendererContext context); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlRenderer.java new file mode 100644 index 0000000000000..d4b5214211af6 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlRenderer.java @@ -0,0 +1,337 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +import jdk.internal.org.commonmark.Extension; +import jdk.internal.org.commonmark.internal.renderer.NodeRendererMap; +import jdk.internal.org.commonmark.internal.util.Escaping; +import jdk.internal.org.commonmark.node.*; +import jdk.internal.org.commonmark.renderer.NodeRenderer; +import jdk.internal.org.commonmark.renderer.Renderer; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Renders a tree of nodes to HTML. + *

+ * Start with the {@link #builder} method to configure the renderer. Example: + *


+ * HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build();
+ * renderer.render(node);
+ * 
+ */ +public class HtmlRenderer implements Renderer { + + private final String softbreak; + private final boolean escapeHtml; + private final boolean sanitizeUrls; + private final UrlSanitizer urlSanitizer; + private final boolean percentEncodeUrls; + private final List attributeProviderFactories; + private final List nodeRendererFactories; + + private HtmlRenderer(Builder builder) { + this.softbreak = builder.softbreak; + this.escapeHtml = builder.escapeHtml; + this.sanitizeUrls = builder.sanitizeUrls; + this.percentEncodeUrls = builder.percentEncodeUrls; + this.urlSanitizer = builder.urlSanitizer; + this.attributeProviderFactories = new ArrayList<>(builder.attributeProviderFactories); + + this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); + this.nodeRendererFactories.addAll(builder.nodeRendererFactories); + // Add as last. This means clients can override the rendering of core nodes if they want. + this.nodeRendererFactories.add(new HtmlNodeRendererFactory() { + @Override + public NodeRenderer create(HtmlNodeRendererContext context) { + return new CoreHtmlNodeRenderer(context); + } + }); + } + + /** + * Create a new builder for configuring an {@link HtmlRenderer}. + * + * @return a builder + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public void render(Node node, Appendable output) { + if (node == null) { + throw new NullPointerException("node must not be null"); + } + RendererContext context = new RendererContext(new HtmlWriter(output)); + context.render(node); + } + + @Override + public String render(Node node) { + if (node == null) { + throw new NullPointerException("node must not be null"); + } + StringBuilder sb = new StringBuilder(); + render(node, sb); + return sb.toString(); + } + + /** + * Builder for configuring an {@link HtmlRenderer}. See methods for default configuration. + */ + public static class Builder { + + private String softbreak = "\n"; + private boolean escapeHtml = false; + private boolean sanitizeUrls = false; + private UrlSanitizer urlSanitizer = new DefaultUrlSanitizer(); + private boolean percentEncodeUrls = false; + private List attributeProviderFactories = new ArrayList<>(); + private List nodeRendererFactories = new ArrayList<>(); + + /** + * @return the configured {@link HtmlRenderer} + */ + public HtmlRenderer build() { + return new HtmlRenderer(this); + } + + /** + * The HTML to use for rendering a softbreak, defaults to {@code "\n"} (meaning the rendered result doesn't have + * a line break). + *

+ * Set it to {@code "
"} (or {@code "
"} to make them hard breaks. + *

+ * Set it to {@code " "} to ignore line wrapping in the source. + * + * @param softbreak HTML for softbreak + * @return {@code this} + */ + public Builder softbreak(String softbreak) { + this.softbreak = softbreak; + return this; + } + + /** + * Whether {@link HtmlInline} and {@link HtmlBlock} should be escaped, defaults to {@code false}. + *

+ * Note that {@link HtmlInline} is only a tag itself, not the text between an opening tag and a closing tag. So + * markup in the text will be parsed as normal and is not affected by this option. + * + * @param escapeHtml true for escaping, false for preserving raw HTML + * @return {@code this} + */ + public Builder escapeHtml(boolean escapeHtml) { + this.escapeHtml = escapeHtml; + return this; + } + + /** + * Whether {@link Image} src and {@link Link} href should be sanitized, defaults to {@code false}. + * + * @param sanitizeUrls true for sanitization, false for preserving raw attribute + * @return {@code this} + * @since 0.14.0 + */ + public Builder sanitizeUrls(boolean sanitizeUrls) { + this.sanitizeUrls = sanitizeUrls; + return this; + } + + /** + * {@link UrlSanitizer} used to filter URL's if {@link #sanitizeUrls} is true. + * + * @param urlSanitizer Filterer used to filter {@link Image} src and {@link Link}. + * @return {@code this} + * @since 0.14.0 + */ + public Builder urlSanitizer(UrlSanitizer urlSanitizer) { + this.urlSanitizer = urlSanitizer; + return this; + } + + /** + * Whether URLs of link or images should be percent-encoded, defaults to {@code false}. + *

+ * If enabled, the following is done: + *

    + *
  • Existing percent-encoded parts are preserved (e.g. "%20" is kept as "%20")
  • + *
  • Reserved characters such as "/" are preserved, except for "[" and "]" (see encodeURI in JS)
  • + *
  • Unreserved characters such as "a" are preserved
  • + *
  • Other characters such umlauts are percent-encoded
  • + *
+ * + * @param percentEncodeUrls true to percent-encode, false for leaving as-is + * @return {@code this} + */ + public Builder percentEncodeUrls(boolean percentEncodeUrls) { + this.percentEncodeUrls = percentEncodeUrls; + return this; + } + + /** + * Add a factory for an attribute provider for adding/changing HTML attributes to the rendered tags. + * + * @param attributeProviderFactory the attribute provider factory to add + * @return {@code this} + */ + public Builder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) { + if (attributeProviderFactory == null) { + throw new NullPointerException("attributeProviderFactory must not be null"); + } + this.attributeProviderFactories.add(attributeProviderFactory); + return this; + } + + /** + * Add a factory for instantiating a node renderer (done when rendering). This allows to override the rendering + * of node types or define rendering for custom node types. + *

+ * If multiple node renderers for the same node type are created, the one from the factory that was added first + * "wins". (This is how the rendering for core node types can be overridden; the default rendering comes last.) + * + * @param nodeRendererFactory the factory for creating a node renderer + * @return {@code this} + */ + public Builder nodeRendererFactory(HtmlNodeRendererFactory nodeRendererFactory) { + if (nodeRendererFactory == null) { + throw new NullPointerException("nodeRendererFactory must not be null"); + } + this.nodeRendererFactories.add(nodeRendererFactory); + return this; + } + + /** + * @param extensions extensions to use on this HTML renderer + * @return {@code this} + */ + public Builder extensions(Iterable extensions) { + if (extensions == null) { + throw new NullPointerException("extensions must not be null"); + } + for (Extension extension : extensions) { + if (extension instanceof HtmlRendererExtension) { + HtmlRendererExtension htmlRendererExtension = (HtmlRendererExtension) extension; + htmlRendererExtension.extend(this); + } + } + return this; + } + } + + /** + * Extension for {@link HtmlRenderer}. + */ + public interface HtmlRendererExtension extends Extension { + void extend(Builder rendererBuilder); + } + + private class RendererContext implements HtmlNodeRendererContext, AttributeProviderContext { + + private final HtmlWriter htmlWriter; + private final List attributeProviders; + private final NodeRendererMap nodeRendererMap = new NodeRendererMap(); + + private RendererContext(HtmlWriter htmlWriter) { + this.htmlWriter = htmlWriter; + + attributeProviders = new ArrayList<>(attributeProviderFactories.size()); + for (AttributeProviderFactory attributeProviderFactory : attributeProviderFactories) { + attributeProviders.add(attributeProviderFactory.create(this)); + } + + // The first node renderer for a node type "wins". + for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { + HtmlNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); + NodeRenderer nodeRenderer = nodeRendererFactory.create(this); + nodeRendererMap.add(nodeRenderer); + } + } + + @Override + public boolean shouldEscapeHtml() { + return escapeHtml; + } + + @Override + public boolean shouldSanitizeUrls() { + return sanitizeUrls; + } + + @Override + public UrlSanitizer urlSanitizer() { + return urlSanitizer; + } + + @Override + public String encodeUrl(String url) { + if (percentEncodeUrls) { + return Escaping.percentEncodeUrl(url); + } else { + return url; + } + } + + @Override + public Map extendAttributes(Node node, String tagName, Map attributes) { + Map attrs = new LinkedHashMap<>(attributes); + setCustomAttributes(node, tagName, attrs); + return attrs; + } + + @Override + public HtmlWriter getWriter() { + return htmlWriter; + } + + @Override + public String getSoftbreak() { + return softbreak; + } + + @Override + public void render(Node node) { + nodeRendererMap.render(node); + } + + private void setCustomAttributes(Node node, String tagName, Map attrs) { + for (AttributeProvider attributeProvider : attributeProviders) { + attributeProvider.setAttributes(node, tagName, attrs); + } + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlWriter.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlWriter.java new file mode 100644 index 0000000000000..341c428a3ee1d --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/HtmlWriter.java @@ -0,0 +1,107 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +import jdk.internal.org.commonmark.internal.util.Escaping; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +public class HtmlWriter { + + private static final Map NO_ATTRIBUTES = Collections.emptyMap(); + + private final Appendable buffer; + private char lastChar = 0; + + public HtmlWriter(Appendable out) { + if (out == null) { + throw new NullPointerException("out must not be null"); + } + this.buffer = out; + } + + public void raw(String s) { + append(s); + } + + public void text(String text) { + append(Escaping.escapeHtml(text)); + } + + public void tag(String name) { + tag(name, NO_ATTRIBUTES); + } + + public void tag(String name, Map attrs) { + tag(name, attrs, false); + } + + public void tag(String name, Map attrs, boolean voidElement) { + append("<"); + append(name); + if (attrs != null && !attrs.isEmpty()) { + for (Map.Entry attrib : attrs.entrySet()) { + append(" "); + append(Escaping.escapeHtml(attrib.getKey())); + append("=\""); + append(Escaping.escapeHtml(attrib.getValue())); + append("\""); + } + } + if (voidElement) { + append(" /"); + } + + append(">"); + } + + public void line() { + if (lastChar != 0 && lastChar != '\n') { + append("\n"); + } + } + + protected void append(String s) { + try { + buffer.append(s); + } catch (IOException e) { + throw new RuntimeException(e); + } + int length = s.length(); + if (length != 0) { + lastChar = s.charAt(length - 1); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/UrlSanitizer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/UrlSanitizer.java new file mode 100644 index 0000000000000..12c69a7382154 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/UrlSanitizer.java @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.html; + +import jdk.internal.org.commonmark.node.Image; +import jdk.internal.org.commonmark.node.Link; + +/** + * Sanitizes urls for img and a elements by whitelisting protocols. + * This is intended to prevent XSS payloads like [Click this totally safe url](javascript:document.xss=true;) + *

+ * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java + * + * @since 0.14.0 + */ +public interface UrlSanitizer { + /** + * Sanitize a url for use in the href attribute of a {@link Link}. + * + * @param url Link to sanitize + * @return Sanitized link + */ + String sanitizeLinkUrl(String url); + + /** + * Sanitize a url for use in the src attribute of a {@link Image}. + * + * @param url Link to sanitize + * @return Sanitized link {@link Image} + */ + String sanitizeImageUrl(String url); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/package-info.java new file mode 100644 index 0000000000000..41dd2c6c6cc9d --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/html/package-info.java @@ -0,0 +1,36 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +/** + * HTML rendering (see {@link org.commonmark.renderer.html.HtmlRenderer}) + */ +package jdk.internal.org.commonmark.renderer.html; diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java new file mode 100644 index 0000000000000..1321d5ad78502 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -0,0 +1,312 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.text; + +import jdk.internal.org.commonmark.node.*; +import jdk.internal.org.commonmark.renderer.NodeRenderer; +import jdk.internal.org.commonmark.internal.renderer.text.BulletListHolder; +import jdk.internal.org.commonmark.internal.renderer.text.ListHolder; +import jdk.internal.org.commonmark.internal.renderer.text.OrderedListHolder; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * The node renderer that renders all the core nodes (comes last in the order of node renderers). + */ +public class CoreTextContentNodeRenderer extends AbstractVisitor implements NodeRenderer { + + protected final TextContentNodeRendererContext context; + private final TextContentWriter textContent; + + private ListHolder listHolder; + + public CoreTextContentNodeRenderer(TextContentNodeRendererContext context) { + this.context = context; + this.textContent = context.getWriter(); + } + + @Override + public Set> getNodeTypes() { + return new HashSet<>(Arrays.asList( + Document.class, + Heading.class, + Paragraph.class, + BlockQuote.class, + BulletList.class, + FencedCodeBlock.class, + HtmlBlock.class, + ThematicBreak.class, + IndentedCodeBlock.class, + Link.class, + ListItem.class, + OrderedList.class, + Image.class, + Emphasis.class, + StrongEmphasis.class, + Text.class, + Code.class, + HtmlInline.class, + SoftLineBreak.class, + HardLineBreak.class + )); + } + + @Override + public void render(Node node) { + node.accept(this); + } + + @Override + public void visit(Document document) { + // No rendering itself + visitChildren(document); + } + + @Override + public void visit(BlockQuote blockQuote) { + textContent.write('\u00ab'); + visitChildren(blockQuote); + textContent.write('\u00bb'); + + writeEndOfLineIfNeeded(blockQuote, null); + } + + @Override + public void visit(BulletList bulletList) { + if (listHolder != null) { + writeEndOfLine(); + } + listHolder = new BulletListHolder(listHolder, bulletList); + visitChildren(bulletList); + writeEndOfLineIfNeeded(bulletList, null); + if (listHolder.getParent() != null) { + listHolder = listHolder.getParent(); + } else { + listHolder = null; + } + } + + @Override + public void visit(Code code) { + textContent.write('\"'); + textContent.write(code.getLiteral()); + textContent.write('\"'); + } + + @Override + public void visit(FencedCodeBlock fencedCodeBlock) { + if (context.stripNewlines()) { + textContent.writeStripped(fencedCodeBlock.getLiteral()); + writeEndOfLineIfNeeded(fencedCodeBlock, null); + } else { + textContent.write(fencedCodeBlock.getLiteral()); + } + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + writeEndOfLineIfNeeded(hardLineBreak, null); + } + + @Override + public void visit(Heading heading) { + visitChildren(heading); + writeEndOfLineIfNeeded(heading, ':'); + } + + @Override + public void visit(ThematicBreak thematicBreak) { + if (!context.stripNewlines()) { + textContent.write("***"); + } + writeEndOfLineIfNeeded(thematicBreak, null); + } + + @Override + public void visit(HtmlInline htmlInline) { + writeText(htmlInline.getLiteral()); + } + + @Override + public void visit(HtmlBlock htmlBlock) { + writeText(htmlBlock.getLiteral()); + } + + @Override + public void visit(Image image) { + writeLink(image, image.getTitle(), image.getDestination()); + } + + @Override + public void visit(IndentedCodeBlock indentedCodeBlock) { + if (context.stripNewlines()) { + textContent.writeStripped(indentedCodeBlock.getLiteral()); + writeEndOfLineIfNeeded(indentedCodeBlock, null); + } else { + textContent.write(indentedCodeBlock.getLiteral()); + } + } + + @Override + public void visit(Link link) { + writeLink(link, link.getTitle(), link.getDestination()); + } + + @Override + public void visit(ListItem listItem) { + if (listHolder != null && listHolder instanceof OrderedListHolder) { + OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; + String indent = context.stripNewlines() ? "" : orderedListHolder.getIndent(); + textContent.write(indent + orderedListHolder.getCounter() + orderedListHolder.getDelimiter() + " "); + visitChildren(listItem); + writeEndOfLineIfNeeded(listItem, null); + orderedListHolder.increaseCounter(); + } else if (listHolder != null && listHolder instanceof BulletListHolder) { + BulletListHolder bulletListHolder = (BulletListHolder) listHolder; + if (!context.stripNewlines()) { + textContent.write(bulletListHolder.getIndent() + bulletListHolder.getMarker() + " "); + } + visitChildren(listItem); + writeEndOfLineIfNeeded(listItem, null); + } + } + + @Override + public void visit(OrderedList orderedList) { + if (listHolder != null) { + writeEndOfLine(); + } + listHolder = new OrderedListHolder(listHolder, orderedList); + visitChildren(orderedList); + writeEndOfLineIfNeeded(orderedList, null); + if (listHolder.getParent() != null) { + listHolder = listHolder.getParent(); + } else { + listHolder = null; + } + } + + @Override + public void visit(Paragraph paragraph) { + visitChildren(paragraph); + // Add "end of line" only if its "root paragraph. + if (paragraph.getParent() == null || paragraph.getParent() instanceof Document) { + writeEndOfLineIfNeeded(paragraph, null); + } + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + writeEndOfLineIfNeeded(softLineBreak, null); + } + + @Override + public void visit(Text text) { + writeText(text.getLiteral()); + } + + @Override + protected void visitChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } + + private void writeText(String text) { + if (context.stripNewlines()) { + textContent.writeStripped(text); + } else { + textContent.write(text); + } + } + + private void writeLink(Node node, String title, String destination) { + boolean hasChild = node.getFirstChild() != null; + boolean hasTitle = title != null && !title.equals(destination); + boolean hasDestination = destination != null && !destination.equals(""); + + if (hasChild) { + textContent.write('"'); + visitChildren(node); + textContent.write('"'); + if (hasTitle || hasDestination) { + textContent.whitespace(); + textContent.write('('); + } + } + + if (hasTitle) { + textContent.write(title); + if (hasDestination) { + textContent.colon(); + textContent.whitespace(); + } + } + + if (hasDestination) { + textContent.write(destination); + } + + if (hasChild && (hasTitle || hasDestination)) { + textContent.write(')'); + } + } + + private void writeEndOfLineIfNeeded(Node node, Character c) { + if (context.stripNewlines()) { + if (c != null) { + textContent.write(c); + } + if (node.getNext() != null) { + textContent.whitespace(); + } + } else { + if (node.getNext() != null) { + textContent.line(); + } + } + } + + private void writeEndOfLine() { + if (context.stripNewlines()) { + textContent.whitespace(); + } else { + textContent.line(); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentNodeRendererContext.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentNodeRendererContext.java new file mode 100644 index 0000000000000..c00be3cca416e --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentNodeRendererContext.java @@ -0,0 +1,57 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.text; + +import jdk.internal.org.commonmark.node.Node; + +public interface TextContentNodeRendererContext { + + /** + * @return true for stripping new lines and render text as "single line", + * false for keeping all line breaks. + */ + boolean stripNewlines(); + + /** + * @return the writer to use + */ + TextContentWriter getWriter(); + + /** + * Render the specified node and its children using the configured renderers. This should be used to render child + * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * + * @param node the node to render + */ + void render(Node node); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentNodeRendererFactory.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentNodeRendererFactory.java new file mode 100644 index 0000000000000..5c110ba4d2947 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentNodeRendererFactory.java @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.text; + +import jdk.internal.org.commonmark.renderer.NodeRenderer; + +/** + * Factory for instantiating new node renderers when rendering is done. + */ +public interface TextContentNodeRendererFactory { + + /** + * Create a new node renderer for the specified rendering context. + * + * @param context the context for rendering (normally passed on to the node renderer) + * @return a node renderer + */ + NodeRenderer create(TextContentNodeRendererContext context); +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentRenderer.java new file mode 100644 index 0000000000000..3dc36fabd3025 --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentRenderer.java @@ -0,0 +1,181 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.text; + +import jdk.internal.org.commonmark.Extension; +import jdk.internal.org.commonmark.internal.renderer.NodeRendererMap; +import jdk.internal.org.commonmark.node.Node; +import jdk.internal.org.commonmark.renderer.NodeRenderer; +import jdk.internal.org.commonmark.renderer.Renderer; + +import java.util.ArrayList; +import java.util.List; + +public class TextContentRenderer implements Renderer { + + private final boolean stripNewlines; + + private final List nodeRendererFactories; + + private TextContentRenderer(Builder builder) { + this.stripNewlines = builder.stripNewlines; + + this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); + this.nodeRendererFactories.addAll(builder.nodeRendererFactories); + // Add as last. This means clients can override the rendering of core nodes if they want. + this.nodeRendererFactories.add(new TextContentNodeRendererFactory() { + @Override + public NodeRenderer create(TextContentNodeRendererContext context) { + return new CoreTextContentNodeRenderer(context); + } + }); + } + + /** + * Create a new builder for configuring an {@link TextContentRenderer}. + * + * @return a builder + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public void render(Node node, Appendable output) { + RendererContext context = new RendererContext(new TextContentWriter(output)); + context.render(node); + } + + @Override + public String render(Node node) { + StringBuilder sb = new StringBuilder(); + render(node, sb); + return sb.toString(); + } + + /** + * Builder for configuring an {@link TextContentRenderer}. See methods for default configuration. + */ + public static class Builder { + + private boolean stripNewlines = false; + private List nodeRendererFactories = new ArrayList<>(); + + /** + * @return the configured {@link TextContentRenderer} + */ + public TextContentRenderer build() { + return new TextContentRenderer(this); + } + + /** + * Set the value of flag for stripping new lines. + * + * @param stripNewlines true for stripping new lines and render text as "single line", + * false for keeping all line breaks + * @return {@code this} + */ + public Builder stripNewlines(boolean stripNewlines) { + this.stripNewlines = stripNewlines; + return this; + } + + /** + * Add a factory for instantiating a node renderer (done when rendering). This allows to override the rendering + * of node types or define rendering for custom node types. + *

+ * If multiple node renderers for the same node type are created, the one from the factory that was added first + * "wins". (This is how the rendering for core node types can be overridden; the default rendering comes last.) + * + * @param nodeRendererFactory the factory for creating a node renderer + * @return {@code this} + */ + public Builder nodeRendererFactory(TextContentNodeRendererFactory nodeRendererFactory) { + this.nodeRendererFactories.add(nodeRendererFactory); + return this; + } + + /** + * @param extensions extensions to use on this text content renderer + * @return {@code this} + */ + public Builder extensions(Iterable extensions) { + for (Extension extension : extensions) { + if (extension instanceof TextContentRenderer.TextContentRendererExtension) { + TextContentRenderer.TextContentRendererExtension textContentRendererExtension = + (TextContentRenderer.TextContentRendererExtension) extension; + textContentRendererExtension.extend(this); + } + } + return this; + } + } + + /** + * Extension for {@link TextContentRenderer}. + */ + public interface TextContentRendererExtension extends Extension { + void extend(TextContentRenderer.Builder rendererBuilder); + } + + private class RendererContext implements TextContentNodeRendererContext { + private final TextContentWriter textContentWriter; + private final NodeRendererMap nodeRendererMap = new NodeRendererMap(); + + private RendererContext(TextContentWriter textContentWriter) { + this.textContentWriter = textContentWriter; + + // The first node renderer for a node type "wins". + for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { + TextContentNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); + NodeRenderer nodeRenderer = nodeRendererFactory.create(this); + nodeRendererMap.add(nodeRenderer); + } + } + + @Override + public boolean stripNewlines() { + return stripNewlines; + } + + @Override + public TextContentWriter getWriter() { + return textContentWriter; + } + + @Override + public void render(Node node) { + nodeRendererMap.render(node); + } + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentWriter.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentWriter.java new file mode 100644 index 0000000000000..edbb7ae2839eb --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentWriter.java @@ -0,0 +1,99 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +package jdk.internal.org.commonmark.renderer.text; + +import java.io.IOException; + +public class TextContentWriter { + + private final Appendable buffer; + + private char lastChar; + + public TextContentWriter(Appendable out) { + buffer = out; + } + + public void whitespace() { + if (lastChar != 0 && lastChar != ' ') { + append(' '); + } + } + + public void colon() { + if (lastChar != 0 && lastChar != ':') { + append(':'); + } + } + + public void line() { + if (lastChar != 0 && lastChar != '\n') { + append('\n'); + } + } + + public void writeStripped(String s) { + append(s.replaceAll("[\\r\\n\\s]+", " ")); + } + + public void write(String s) { + append(s); + } + + public void write(char c) { + append(c); + } + + private void append(String s) { + try { + buffer.append(s); + } catch (IOException e) { + throw new RuntimeException(e); + } + + int length = s.length(); + if (length != 0) { + lastChar = s.charAt(length - 1); + } + } + + private void append(char c) { + try { + buffer.append(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + lastChar = c; + } +} diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/package-info.java new file mode 100644 index 0000000000000..e861e2457c77e --- /dev/null +++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/package-info.java @@ -0,0 +1,36 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, a notice that is now available elsewhere in this distribution + * accompanied the original version of this file, and, per its terms, + * should not be removed. + */ + +/** + * Text content rendering (see {@link org.commonmark.renderer.text.TextContentRenderer}) + */ +package jdk.internal.org.commonmark.renderer.text; diff --git a/src/jdk.internal.md/share/classes/module-info.java b/src/jdk.internal.md/share/classes/module-info.java new file mode 100644 index 0000000000000..6103521c20d98 --- /dev/null +++ b/src/jdk.internal.md/share/classes/module-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Internal support for Markdown. + * + * @since 21 + */ +module jdk.internal.md { + exports jdk.internal.org.commonmark.node to jdk.javadoc, jdk.jshell; + exports jdk.internal.org.commonmark.parser to jdk.javadoc, jdk.jshell; + exports jdk.internal.org.commonmark.renderer.html to jdk.javadoc, jdk.jshell; +} diff --git a/src/jdk.internal.md/share/legal/commonmark.md b/src/jdk.internal.md/share/legal/commonmark.md new file mode 100644 index 0000000000000..7fb0ee0c090a8 --- /dev/null +++ b/src/jdk.internal.md/share/legal/commonmark.md @@ -0,0 +1,29 @@ +## CommonMark 0.21.0 + +### CommonMark License +``` +Copyright (c) 2015, Atlassian Pty Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +``` diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java index 7fd4f7d14d181..1c16c4a348ab4 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ import java.util.ListIterator; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; @@ -44,7 +43,6 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ModuleElement; import javax.lang.model.element.Name; @@ -70,9 +68,10 @@ import com.sun.source.doctree.ErroneousTree; import com.sun.source.doctree.IndexTree; import com.sun.source.doctree.InheritDocTree; +import com.sun.source.doctree.InlineTagTree; import com.sun.source.doctree.LinkTree; import com.sun.source.doctree.LiteralTree; -import com.sun.source.doctree.SeeTree; +import com.sun.source.doctree.MarkdownTree; import com.sun.source.doctree.StartElementTree; import com.sun.source.doctree.SummaryTree; import com.sun.source.doctree.SystemPropertyTree; @@ -80,6 +79,9 @@ import com.sun.source.util.DocTreePath; import com.sun.source.util.SimpleDocTreeVisitor; +import jdk.internal.org.commonmark.node.Node; +import jdk.internal.org.commonmark.parser.Parser; +import jdk.internal.org.commonmark.renderer.html.HtmlRenderer; import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; import jdk.javadoc.internal.doclets.formats.html.markup.Entity; import jdk.javadoc.internal.doclets.formats.html.markup.Head; @@ -1199,10 +1201,13 @@ public ContentBuilder add(CharSequence text) { configuration.tagletManager.checkTags(element, trees); commentRemoved = false; - for (ListIterator iterator = trees.listIterator(); iterator.hasNext();) { + var useMarkdown = trees.stream().anyMatch(t -> t.getKind() == Kind.MARKDOWN); + var markdownHandler = useMarkdown ? new MarkdownHandler() : null; + + for (ListIterator iterator = trees.listIterator(); iterator.hasNext(); ) { boolean isFirstNode = !iterator.hasPrevious(); DocTree tag = iterator.next(); - boolean isLastNode = !iterator.hasNext(); + boolean isLastNode = !iterator.hasNext(); if (context.isFirstSentence) { // Ignore block tags @@ -1222,242 +1227,357 @@ public ContentBuilder add(CharSequence text) { } } - var docTreeVisitor = new SimpleDocTreeVisitor() { + var docTreeVisitor = new InlineVisitor(element, tag, isLastNode, context, ch, trees); - private boolean inAnAtag() { - return (tag instanceof StartElementTree st) && equalsIgnoreCase(st.getName(), "a"); - } + boolean allDone = useMarkdown + ? markdownHandler.handle(tag, docTreeVisitor) + : docTreeVisitor.visit(tag, result); + commentRemoved = false; - @Override - public Boolean visitAttribute(AttributeTree node, Content content) { - if (!content.isEmpty()) { - content.add(" "); - } - content.add(node.getName()); - if (node.getValueKind() == ValueKind.EMPTY) { - return false; - } - content.add("="); - String quote = switch (node.getValueKind()) { - case DOUBLE -> "\""; - case SINGLE -> "'"; - default -> ""; - }; - content.add(quote); - - /* In the following code for an attribute value: - * 1. {@docRoot} followed by text beginning "/.." is replaced by the value - * of the docrootParent option, followed by the remainder of the text - * 2. in the value of an "href" attribute in a tag, an initial text - * value will have a relative link redirected. - * Note that, realistically, it only makes sense to ever use {@docRoot} - * at the beginning of a URL in an attribute value, but this is not - * required or enforced. - */ - boolean isHRef = inAnAtag() && equalsIgnoreCase(node.getName(), "href"); - boolean first = true; - DocRootTree pendingDocRoot = null; - for (DocTree dt : node.getValue()) { - if (pendingDocRoot != null) { - if (dt instanceof TextTree tt) { - String text = tt.getBody(); - if (text.startsWith("/..") && !options.docrootParent().isEmpty()) { - content.add(options.docrootParent()); - content.add(textCleanup(text.substring(3), isLastNode)); - pendingDocRoot = null; - continue; - } - } - pendingDocRoot.accept(this, content); - pendingDocRoot = null; - } + if (allDone) + break; + } - if (dt instanceof TextTree tt) { - String text = tt.getBody(); - if (first && isHRef) { - text = redirectRelativeLinks(element, tt); - } - content.add(textCleanup(text, isLastNode)); - } else if (dt instanceof DocRootTree drt) { - // defer until we see what, if anything, follows this node - pendingDocRoot = drt; - } else { - dt.accept(this, content); - } - first = false; - } - if (pendingDocRoot != null) { - pendingDocRoot.accept(this, content); - } + if (useMarkdown) { + markdownHandler.addContent(result); + } - content.add(quote); - return false; - } + return result; + } - @Override - public Boolean visitComment(CommentTree node, Content content) { - content.add(RawHtml.comment(node.getBody())); - return false; + private class MarkdownHandler { + private static final char PLACEHOLDER = '\uFFFC'; // Unicode Object Replacement Character + StringBuilder markdownInput = new StringBuilder() ; + ArrayList fffcObjects = new ArrayList<>(); + + Parser parser = Parser.builder().build(); + HtmlRenderer renderer = HtmlRenderer.builder().build(); + + boolean handle(DocTree tree, InlineVisitor visitor) { + boolean allDone; + if (tree instanceof MarkdownTree t) { + String code = t.getContent(); + // handle the (unlikely) case of FFFC characters existing in the code + int start = 0; + int pos; + while ((pos = code.indexOf(PLACEHOLDER, start)) != -1) { + markdownInput.append(code.substring(start, pos)); + markdownInput.append(PLACEHOLDER); + fffcObjects.add(Text.of(String.valueOf(PLACEHOLDER))); + start = pos + 1; } + markdownInput.append(code.substring(start)); + allDone = false; + } else { + Content embeddedContent = new ContentBuilder(); + allDone = visitor.visit(tree, embeddedContent); + fffcObjects.add(embeddedContent); + markdownInput.append(PLACEHOLDER); + } + return allDone; + } - @Override - public Boolean visitDocRoot(DocRootTree node, Content content) { - content.add(getInlineTagOutput(element, node, context)); - return false; - } + void addContent(Content result) { + Node document = parser.parse(markdownInput.toString()); + String markdownOutput = unwrap(renderer.render(document)); + + int start = 0; + int pos; + int fffcObjectIndex = 0; + while ((pos = markdownOutput.indexOf(PLACEHOLDER, start)) != -1) { + result.add(RawHtml.markdown(markdownOutput.substring(start, pos))); + result.add(fffcObjects.get(fffcObjectIndex++)); + start = pos + 1; + } + if (start < markdownOutput.length()) { + result.add(RawHtml.of(markdownOutput.substring(start))); + } + } + } - @Override - public Boolean visitEndElement(EndElementTree node, Content content) { - content.add(RawHtml.endElement(node.getName())); - return false; + /* + * If a string contains a simple HTML paragraph, beginning with

+ * and ending with

and optional whitespace, return the content + * of the paragraph between the tags. + * Otherwise, return the string unmodified. + */ + private static String unwrap(String s) { + var prefix = "

"; + if (s.startsWith(prefix)) { + var suffix = "

"; + var suffixPos = s.indexOf(suffix); + if (suffixPos > 0) { + var endSuffixPos = suffixPos + suffix.length(); + if (isBlank(s, endSuffixPos, s.length())) { + return s.substring(prefix.length(), suffixPos); } + } + } + return s; + } - @Override - public Boolean visitEntity(EntityTree node, Content content) { - content.add(Entity.of(node.getName())); - return false; - } + /* + * Returns whether a substring of a string is blank. + * Avoid creating a substring or using regular expressions. + */ + private static boolean isBlank(String s, int start, int end) { + for (int i = start; i < end; i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } + + private class InlineVisitor extends SimpleDocTreeVisitor { + private final Element element; + private final DocTree tag; + private final boolean isLastNode; + private final TagletWriterImpl.Context context; + private final CommentHelper ch; + private final List trees; + + InlineVisitor(Element element, + DocTree tag, + boolean isLastNode, + TagletWriterImpl.Context context, + CommentHelper ch, + List trees) { + + this.element = element; + this.tag = tag; + this.isLastNode = isLastNode; + this.context = context; + this.ch = ch; + this.trees = trees; + } - @Override - public Boolean visitErroneous(ErroneousTree node, Content content) { - DocTreePath dtp = ch.getDocTreePath(node); - if (dtp != null) { - String body = node.getBody(); - Matcher m = Pattern.compile("(?i)\\{@([a-z]+).*").matcher(body); - String tagName = m.matches() ? m.group(1) : null; - if (tagName == null) { - if (!configuration.isDocLintSyntaxGroupEnabled()) { - messages.warning(dtp, "doclet.tag.invalid_input", body); - } - content.add(invalidTagOutput(resources.getText("doclet.tag.invalid_input", body), - Optional.empty())); - } else { - messages.warning(dtp, "doclet.tag.invalid_usage", body); - content.add(invalidTagOutput(resources.getText("doclet.tag.invalid", tagName), - Optional.of(Text.of(body)))); + private boolean inAnAtag() { + return (tag instanceof StartElementTree st) && equalsIgnoreCase(st.getName(), "a"); + } + + @Override + public Boolean visitAttribute(AttributeTree node, Content content) { + if (!content.isEmpty()) { + content.add(" "); + } + content.add(node.getName()); + if (node.getValueKind() == ValueKind.EMPTY) { + return false; + } + content.add("="); + String quote = switch (node.getValueKind()) { + case DOUBLE -> "\""; + case SINGLE -> "'"; + default -> ""; + }; + content.add(quote); + + /* In the following code for an attribute value: + * 1. {@docRoot} followed by text beginning "/.." is replaced by the value + * of the docrootParent option, followed by the remainder of the text + * 2. in the value of an "href" attribute in a
tag, an initial text + * value will have a relative link redirected. + * Note that, realistically, it only makes sense to ever use {@docRoot} + * at the beginning of a URL in an attribute value, but this is not + * required or enforced. + */ + boolean isHRef = inAnAtag() && equalsIgnoreCase(node.getName(), "href"); + boolean first = true; + DocRootTree pendingDocRoot = null; + for (DocTree dt : node.getValue()) { + if (pendingDocRoot != null) { + if (dt instanceof TextTree tt) { + String text = tt.getBody(); + if (text.startsWith("/..") && !options.docrootParent().isEmpty()) { + content.add(options.docrootParent()); + content.add(textCleanup(text.substring(3), isLastNode)); + pendingDocRoot = null; + continue; } } - return false; + pendingDocRoot.accept(this, content); + pendingDocRoot = null; } - @Override - public Boolean visitInheritDoc(InheritDocTree node, Content content) { - Content output = getInlineTagOutput(element, node, context); - content.add(output); - // if we obtained the first sentence successfully, nothing more to do - return (context.isFirstSentence && !output.isEmpty()); - } - - @Override - public Boolean visitIndex(IndexTree node, Content content) { - Content output = getInlineTagOutput(element, node, context); - if (output != null) { - content.add(output); + if (dt instanceof TextTree tt) { + String text = tt.getBody(); + if (first && isHRef) { + text = redirectRelativeLinks(element, tt); } - return false; + content.add(textCleanup(text, isLastNode)); + } else if (dt instanceof DocRootTree drt) { + // defer until we see what, if anything, follows this node + pendingDocRoot = drt; + } else { + dt.accept(this, content); } + first = false; + } + if (pendingDocRoot != null) { + pendingDocRoot.accept(this, content); + } - @Override - public Boolean visitLink(LinkTree node, Content content) { - var inTags = context.inTags; - if (inTags.contains(LINK) || inTags.contains(LINK_PLAIN) || inTags.contains(SEE)) { - DocTreePath dtp = ch.getDocTreePath(node); - if (dtp != null) { - messages.warning(dtp, "doclet.see.nested_link", "{@" + node.getTagName() + "}"); - } - Content label = commentTagsToContent(element, node.getLabel(), context); - if (label.isEmpty()) { - label = Text.of(node.getReference().getSignature()); - } - content.add(label); - } else { - TagletWriterImpl t = getTagletWriterInstance(context.within(node)); - content.add(t.linkTagOutput(element, node)); - } - return false; - } + content.add(quote); + return false; + } - @Override - public Boolean visitLiteral(LiteralTree node, Content content) { - String s = node.getBody().getBody(); - Content t = Text.of(Text.normalizeNewlines(s)); - content.add(node.getKind() == CODE ? HtmlTree.CODE(t) : t); - return false; - } + @Override + public Boolean visitComment(CommentTree node, Content content) { + content.add(RawHtml.comment(node.getBody())); + return false; + } - @Override - public Boolean visitStartElement(StartElementTree node, Content content) { - Content attrs = new ContentBuilder(); - if (node.getName().toString().matches("(?i)h[1-6]") && !hasIdAttribute(node)) { - generateHeadingId(node, trees, attrs); - } - for (DocTree dt : node.getAttributes()) { - dt.accept(this, attrs); - } - content.add(RawHtml.startElement(node.getName(), attrs, node.isSelfClosing())); - return false; - } + @Override + public Boolean visitDocRoot(DocRootTree node, Content content) { + content.add(getInlineTagOutput(element, node, context)); + return false; + } - @Override - public Boolean visitSummary(SummaryTree node, Content content) { - Content output = getInlineTagOutput(element, node, context); - content.add(output); - return false; - } + @Override + public Boolean visitEndElement(EndElementTree node, Content content) { + content.add(RawHtml.endElement(node.getName())); + return false; + } - @Override - public Boolean visitSystemProperty(SystemPropertyTree node, Content content) { - Content output = getInlineTagOutput(element, node, context); - if (output != null) { - content.add(output); + @Override + public Boolean visitEntity(EntityTree node, Content content) { + content.add(Entity.of(node.getName())); + return false; + } + + @Override + public Boolean visitErroneous(ErroneousTree node, Content content) { + DocTreePath dtp = ch.getDocTreePath(node); + if (dtp != null) { + String body = node.getBody(); + Matcher m = Pattern.compile("(?i)\\{@([a-z]+).*").matcher(body); + String tagName = m.matches() ? m.group(1) : null; + if (tagName == null) { + if (!configuration.isDocLintSyntaxGroupEnabled()) { + messages.warning(dtp, "doclet.tag.invalid_input", body); } - return false; + content.add(invalidTagOutput(resources.getText("doclet.tag.invalid_input", body), + Optional.empty())); + } else { + messages.warning(dtp, "doclet.tag.invalid_usage", body); + content.add(invalidTagOutput(resources.getText("doclet.tag.invalid", tagName), + Optional.of(Text.of(body)))); } + } + return false; + } - private CharSequence textCleanup(String text, boolean isLast) { - return textCleanup(text, isLast, false); - } + @Override + public Boolean visitInheritDoc(InheritDocTree node, Content content) { + Content output = getInlineTagOutput(element, node, context); + content.add(output); + // if we obtained the first sentence successfully, nothing more to do + return (context.isFirstSentence && !output.isEmpty()); + } - private CharSequence textCleanup(String text, boolean isLast, boolean stripLeading) { - boolean stripTrailing = context.isFirstSentence && isLast; - if (stripLeading && stripTrailing) { - text = text.strip(); - } else if (stripLeading) { - text = text.stripLeading(); - } else if (stripTrailing) { - text = text.stripTrailing(); - } - text = utils.replaceTabs(text); - return Text.normalizeNewlines(text); - } + @Override + public Boolean visitIndex(IndexTree node, Content content) { + Content output = getInlineTagOutput(element, node, context); + if (output != null) { + content.add(output); + } + return false; + } - @Override - public Boolean visitText(TextTree node, Content content) { - String text = node.getBody(); - result.add(text.startsWith(" { + case TEXT, START_ELEMENT, MARKDOWN -> { // @see "Reference" // @see ... return htmlWriter.commentTagsToContent(element, ref, false, false); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/RawHtml.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/RawHtml.java index 02e8aff13baad..39c98f93d4f46 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/RawHtml.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/RawHtml.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,6 +55,16 @@ public int charCount() { }; } + /** + * Creates HTML for a fragment of Markdown output. + * + * @param markdownOutput the fragment + * @return the HTML + */ + public static RawHtml markdown(CharSequence markdownOutput) { + return of(markdownOutput); + } + /** * Creates HTML for the start of an element. * diff --git a/src/jdk.javadoc/share/classes/module-info.java b/src/jdk.javadoc/share/classes/module-info.java index b666517e3f09c..18cce43f8c33b 100644 --- a/src/jdk.javadoc/share/classes/module-info.java +++ b/src/jdk.javadoc/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,6 +59,7 @@ */ module jdk.javadoc { requires java.xml; + requires jdk.internal.md; requires transitive java.compiler; requires transitive jdk.compiler; diff --git a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java similarity index 84% rename from src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java rename to src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java index afc10d0c9883c..6e67a7d00123e 100644 --- a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java +++ b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,10 +33,13 @@ import java.util.HashMap; import java.util.IdentityHashMap; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.Stack; +import java.util.regex.Pattern; +import java.util.stream.StreamSupport; import javax.lang.model.element.Name; import javax.tools.JavaFileObject.Kind; @@ -51,9 +54,12 @@ import com.sun.source.doctree.InlineTagTree; import com.sun.source.doctree.LinkTree; import com.sun.source.doctree.LiteralTree; +import com.sun.source.doctree.MarkdownTree; import com.sun.source.doctree.ParamTree; import com.sun.source.doctree.ReturnTree; +import com.sun.source.doctree.SnippetTree; import com.sun.source.doctree.StartElementTree; +import com.sun.source.doctree.SystemPropertyTree; import com.sun.source.doctree.TextTree; import com.sun.source.doctree.ThrowsTree; import com.sun.source.util.DocTreeScanner; @@ -62,6 +68,9 @@ import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.StringUtils; +import jdk.internal.org.commonmark.node.Node; +import jdk.internal.org.commonmark.parser.Parser; +import jdk.internal.org.commonmark.renderer.html.HtmlRenderer; /**A javadoc to plain text formatter. * @@ -110,7 +119,8 @@ public String formatJavadoc(String header, String javadoc) { DocCommentTree docComment = trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), Kind.HTML) { @Override @DefinedBy(Api.COMPILER) public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { - return "" + javadoc + ""; + boolean isMarkDown = javadoc.startsWith("md") && Character.isWhitespace(javadoc.charAt(2)); + return isMarkDown ? javadoc : "" + javadoc + ""; } }); @@ -146,6 +156,9 @@ public static HtmlTag get(Name tagName) { } private class FormatJavadocScanner extends DocTreeScanner { + private static final char FFFC = '\uFFFC'; // Unicode Object Replacement Character + private static final Pattern FFFC_PATTERN = Pattern.compile(Pattern.quote("" + FFFC)); + private final StringBuilder result; private final JavacTask task; private final DocTrees trees; @@ -214,7 +227,29 @@ public Object visitText(TextTree node, Object p) { } else { text = text.replaceAll("\n", "\n" + indentString(indent)); } - result.append(text); + boolean first = true; + for (String part : FFFC_PATTERN.split(text, -1)) { + if (!first) { + Object nested = injects.remove(0); + if (nested instanceof DocTree nestedTree) { + scan(nestedTree, null); + } else { + result.append(nested); + } + while (part.length() > 0 && Character.isWhitespace(part.charAt(0)) && !pre) { + part = part.substring(1); + } + } else { + first = false; + } + //remove space right before dot or comma: + if (!result.isEmpty() && !part.isEmpty() && + result.charAt(result.length() - 1) == ' ' && + (part.charAt(0) == '.' || part.charAt(0) == ',')) { + result.delete(result.length() - 1, result.length()); + } + result.append(part); + } return null; } @@ -573,6 +608,89 @@ public Object visitEntity(EntityTree node, Object p) { } + @Override + public Object visitSystemProperty(SystemPropertyTree node, Object p) { + result.append(node.getPropertyName()); + return null; + } + + @Override + public Object visitSnippet(SnippetTree node, Object p) { + boolean prevPre = pre; + try { + reflowTillNow(); + pre = true; + return scan(node.getBody(), p); + } finally { + reflownTo = result.length(); + pre = prevPre; + } + } + + private List injects = null; + + @Override + public Object scan(Iterable nodes, Object p) { + boolean hasMarkDown = StreamSupport.stream(nodes.spliterator(), false) + .anyMatch(t -> t.getKind() == DocTree.Kind.MARKDOWN); + if (!hasMarkDown) { + return super.scan(nodes, p); + } + + List prevInjects = injects; + Map prevTableColumns = tableColumns; + try { + StringBuilder realMarkDownContent = new StringBuilder(); + + injects = new LinkedList<>(); + + for (DocTree node : nodes) { + if (node.getKind() == DocTree.Kind.MARKDOWN) { + String code = ((MarkdownTree) node).getContent(); + boolean first = true; + + for (String part : FFFC_PATTERN.split(code, -1)) { + if (first) { + first = false; + } else { + realMarkDownContent.append(FFFC); + injects.add(FFFC); + } + realMarkDownContent.append(part); + } + } else { + realMarkDownContent.append(FFFC); + injects.add(node); + } + } + + Parser parser = Parser.builder().build(); + Node document = parser.parse(realMarkDownContent.toString()); + HtmlRenderer renderer = HtmlRenderer.builder().build(); + String markdownOutput = renderer.render(document); + DocCommentTree docComment = trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), Kind.HTML) { + @Override @DefinedBy(Api.COMPILER) + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return "" + markdownOutput + ""; + } + }); + + tableColumns = countTableColumns(docComment); + scan(docComment.getFullBody(), p); + } catch (URISyntaxException ex) { + throw new InternalError(ex); + } finally { + tableColumns = prevTableColumns; + injects = prevInjects; + } + return null; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Object visitMarkdown(MarkdownTree node, Object p) { + throw new IllegalStateException("Should not get here."); + } + private DocTree lastNode; @Override @DefinedBy(Api.COMPILER_TREE) diff --git a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java similarity index 100% rename from src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java rename to src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java diff --git a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties similarity index 100% rename from src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties rename to src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties diff --git a/src/jdk.jshell/share/classes/module-info.java b/src/jdk.jshell/share/classes/module-info.java index 3f70ef55dbcf3..9e4f223b1a2ef 100644 --- a/src/jdk.jshell/share/classes/module-info.java +++ b/src/jdk.jshell/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -68,6 +68,7 @@ requires jdk.compiler; requires jdk.internal.ed; requires jdk.internal.le; + requires jdk.internal.md; requires jdk.internal.opt; requires transitive java.compiler; diff --git a/test/langtools/jdk/internal/shellsupport/doc/JavadocFormatterTest.java b/test/langtools/jdk/internal/shellsupport/doc/JavadocFormatterTest.java index 27ce31fa5b6c2..67c6a4454015f 100644 --- a/test/langtools/jdk/internal/shellsupport/doc/JavadocFormatterTest.java +++ b/test/langtools/jdk/internal/shellsupport/doc/JavadocFormatterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ * @bug 8131019 8169561 8261450 * @summary Test JavadocFormatter * @library /tools/lib - * @modules jdk.compiler/jdk.internal.shellsupport.doc + * @modules jdk.jshell/jdk.internal.shellsupport.doc * @run testng JavadocFormatterTest */ @@ -413,4 +413,69 @@ public void testSpaceAtEndOfLine() { new JavadocFormatter(60, true).formatJavadoc(header, javadoc); } + public void testMarkDown() { + String header = "test"; + String javadoc = """ + md + + MarkDown test. + + 1. first list item + 1. testing {@code code} inside a list + 1. another \uFFFClist item + """; + + String actual; + String expected; + + actual = new JavadocFormatter(60, false).formatJavadoc(header, javadoc); + expected = """ + test + MarkDown test. + 1. first list item + 2. testing code inside a list + 3. another \uFFFClist item + """; + + if (!Objects.equals(actual, expected)) { + throw new AssertionError("Incorrect output: " + actual); + } + } + + public void testNewTags() { + String header = "test"; + String javadoc = """ + {@summary New tags test.} + +

Property: {@systemProperty someProperty1}and {@systemProperty someProperty2}. +

Another + {@snippet : + class Test { + public static void main(String... args) { // @highlight substring="main" + System.out.println("Hello World!"); + } + } + } + """; + + String actual; + String expected; + + actual = new JavadocFormatter(60, false).formatJavadoc(header, javadoc); + expected = """ + test + New tags test. + Property: someProperty1 and someProperty2. + Anotherclass Test { + public static void main(String... args) { // @highlight substring="main" + System.out.println("Hello World!"); + } + } + """; + + if (!Objects.equals(actual, expected)) { + throw new AssertionError("Incorrect output: " + actual); + } + } + } diff --git a/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java b/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java index 48106face35aa..b640c5ea35c4e 100644 --- a/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java +++ b/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -298,6 +298,19 @@ public void testShortComment() throws Exception { "@return value\n"); } + public void testMarkDown() throws Exception { + doTestJavadoc(" /**md Test.*/\n", + getSubTest, + "md Test." + + "@param p1 param1\n" + + "@param p2 param2\n" + + "@param p3 param3\n" + + "@throws java.lang.IllegalStateException exc1\n" + + "@throws java.lang.IllegalArgumentException exc2\n" + + "@throws java.lang.IllegalAccessException exc3\n" + + "@return value\n"); + } + private void doTestJavadoc(String origJavadoc, Function getElement, String expectedJavadoc) throws Exception { doTestJavadoc(origJavadoc, " /**\n" + diff --git a/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdown.java b/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdown.java new file mode 100644 index 0000000000000..105bc58489c4e --- /dev/null +++ b/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdown.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8298405 + * @summary Markdown support in the standard doclet + * @library /tools/lib ../../lib + * @modules jdk.javadoc/jdk.javadoc.internal.tool + * @build toolbox.ToolBox javadoc.tester.* + * @run main TestMarkdown + */ + +import javadoc.tester.JavadocTester; +import toolbox.ToolBox; + +import java.nio.file.Path; + +public class TestMarkdown extends JavadocTester { + + public static void main(String... args) throws Exception { + var tester = new TestMarkdown(); + tester.runTests(); + } + + ToolBox tb = new ToolBox(); + + @Test + public void testMinimal(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + /**md + * Hello, _Markdown_ world! + */ + public class C { } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOutput("p/C.html", true, + """ +

Hello, Markdown world!
+ """); + } + + @Test + public void testFirstSentence(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + public class C { + /**md + * This is the _first_ sentence. + * This is the _second_ sentence. + */ + public void m() { } + } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOrder("p/C.html", + """ +
""", + """ +
This is the first sentence.
""", + """ +
""", + """ +
This is the first sentence. + This is the second sentence.
"""); + } + + @Test + public void testMarkdownList(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + /**md + * Before list. + * + * * item 1 + * * item 2 + * * item 3 + * + * After list. + */ + public class C { } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOrder("p/C.html", + """ +

Before list.

+
    +
  • item 1
  • +
  • item 2
  • +
  • item 3
  • +
+

After list.

+ """); + } + + @Test + public void testMarkdownList2(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + /**md + Before list. + + - item 1 + - item 2 + - item 3 + + After list. + */ + public class C { } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOrder("p/C.html", + """ +

Before list.

+
    +
  • item 1
  • +
  • item 2
  • +
  • item 3
  • +
+

After list.

+ """); + } + + @Test + public void testFont(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + /**md + * Regular, `Monospace`, _italic_, and **bold** font. + */ + public class C { } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOutput("p/C.html", true, + """ + Regular, Monospace, italic, and bold font."""); + } + + @Test + public void testInherit_md_md(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + public class Base { + /**md + * Markdown comment. + * @throws Exception Base _Markdown_ + */ + public void m() throws Exception { } + }""", + """ + package p; + public class Derived extends Base { + /**md + * Markdown comment. + * @throws {@inheritDoc} + */ + public void m() throws Exception { } + } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--no-platform-links", + "--source-path", src.toString(), + "p"); + + checkOutput("p/Derived.html", true, + """ +
Throws:
+
java.lang.Exception - Base Markdown
+ """); + } + + @Test + public void testInherit_md_plain(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + public class Base { + /**md + * Markdown comment. + * @throws Exception Base _Markdown_ + */ + public void m() throws Exception { } + }""", + """ + package p; + public class Derived extends Base { + /** + * Plain comment. + * @throws {@inheritDoc} + */ + public void m() throws Exception { } + } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--no-platform-links", + "--source-path", src.toString(), + "p"); + + checkOutput("p/Derived.html", true, + """ +
Throws:
+
java.lang.Exception - Base Markdown
+ """); + } + + @Test + public void testInherit_plain_md(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + public class Base { + /** + * Plain comment. + * @throws Exception Base _Not Markdown_ + */ + public void m() throws Exception { } + }""", + """ + package p; + public class Derived extends Base { + /**md + * Markdown comment. + * @throws {@inheritDoc} + */ + public void m() throws Exception { } + } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--no-platform-links", + "--source-path", src.toString(), + "p"); + + checkOutput("p/Derived.html", true, + """ +
Throws:
+
java.lang.Exception - Base _Not Markdown_
+ """); + } + + @Test + public void testSimpleLink(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + public class C { + /**md + * Method m1. + * This is different from {@link #m2()}. + */ + public void m1() { } + /**md + * Method m2. + * This is different from {@link #m1()}. + */ + public void m2() { } + } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOutput("p/C.html", true, + """ + Method m1. + This is different from m2()."""); + + } + + @Test + public void testLinkWithDescription(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + public class C { + /**md + * Method m1. + * This is different from {@linkplain #m2() _Markdown_ m2}. + */ + public void m1() { } + /**md + * Method m2. + * This is different from {@linkplain #m1() _Markdown_ m1}. + */ + public void m2() { } + } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOutput("p/C.html", true, + """ + Method m1. + This is different from Markdown m2"""); + + } + + @Test + public void testSee(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + /**md + * First sentence. + * @see "A reference" + * @see Example + * @see D a _Markdown_ description + */ + public class C { } + """, + """ + package p; + public class D { }"""); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOutput("p/C.html", true, + """ +
See Also:
+
+ +
"""); + + } + + @Test + public void testFFFC(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package p; + /**md + * First sentence. 1{@code 1}1 \ufffc 2{@code 2}2 + */ + public class C { } + """); + javadoc("-d", base.resolve("api").toString(), + "-Xdoclint:none", + "--source-path", src.toString(), + "p"); + + checkOutput("p/C.html", true, + """ +
First sentence. 111 \ufffc 222
+ """); + } +} diff --git a/test/langtools/tools/javac/doctree/DocCommentTester.java b/test/langtools/tools/javac/doctree/DocCommentTester.java index 671c21a165da5..87049660c4255 100644 --- a/test/langtools/tools/javac/doctree/DocCommentTester.java +++ b/test/langtools/tools/javac/doctree/DocCommentTester.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -510,6 +510,11 @@ public Void visitLiteral(LiteralTree node, Void p) { return null; } + public Void visitMarkdown(MarkdownTree node, Void p) { + header(node, compress(node.getContent())); + return null; + } + public Void visitParam(ParamTree node, Void p) { header(node); indent(+1); @@ -917,7 +922,9 @@ void check(TreePath path, Name name) throws Exception { * Maintain contents of at-code and at-literal inline tags. */ String normalize(String s) { - String s2 = s.trim().replaceFirst("\\.\\s*\\n *@", ".\n@"); + String s2 = s.trim() + .replaceFirst("^md\\s+", "md\n") // Markdown prefix + .replaceFirst("\\.\\s*\\n *@", ".\n@"); // Between block tags StringBuilder sb = new StringBuilder(); Pattern p = Pattern.compile("\\{@(code|literal)( )?"); Matcher m = p.matcher(s2); diff --git a/test/langtools/tools/javac/doctree/ElementTest.java b/test/langtools/tools/javac/doctree/ElementTest.java index 911814c5880f3..354b630389edc 100644 --- a/test/langtools/tools/javac/doctree/ElementTest.java +++ b/test/langtools/tools/javac/doctree/ElementTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 7021614 8078320 8247788 8273244 + * @bug 7021614 8078320 8247788 8273244 8298405 * @summary extend com.sun.source API to support parsing javadoc comments * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.file @@ -236,4 +236,18 @@ void comment() { } ] */ +// In Markdown mode, < does not introduce an entity + /**md + * abc < def + */ + public void markdown() { } +/* +DocComment[DOC_COMMENT, pos:0 + firstSentence: 1 + Markdown[MARKDOWN, pos:5, abc_<_def] + body: empty + block tags: empty +] +*/ + } diff --git a/test/langtools/tools/javac/doctree/EntityTest.java b/test/langtools/tools/javac/doctree/EntityTest.java index 57226746d2e25..b08dffa66d3ec 100644 --- a/test/langtools/tools/javac/doctree/EntityTest.java +++ b/test/langtools/tools/javac/doctree/EntityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 7021614 8273244 8284908 + * @bug 7021614 8273244 8284908 8298405 * @summary extend com.sun.source API to support parsing javadoc comments * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.file @@ -166,4 +166,18 @@ public void bad_entity_hex_value() { } ] */ +// In Markdown mode, & does not introduce an entity + /**md + * abc & def + */ + public void markdown() { } +/* +DocComment[DOC_COMMENT, pos:0 + firstSentence: 1 + Markdown[MARKDOWN, pos:5, abc_&_def] + body: empty + block tags: empty +] +*/ + } diff --git a/test/langtools/tools/javac/doctree/MarkdownTest.java b/test/langtools/tools/javac/doctree/MarkdownTest.java new file mode 100644 index 0000000000000..a6d7a905d16d1 --- /dev/null +++ b/test/langtools/tools/javac/doctree/MarkdownTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8298405 + * @summary Markdown support in the standard doclet + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.tree + * jdk.compiler/com.sun.tools.javac.util + * @build DocCommentTester + * @run main DocCommentTester MarkdownTest.java + */ + +class MarkdownTest { + /**md + * abc < def & ghi {@code 123} jkl {@unknown} mno. + */ + void descriptionMix() { } +/* +DocComment[DOC_COMMENT, pos:0 + firstSentence: 5 + Markdown[MARKDOWN, pos:4, abc_<_def_&_ghi_] + Literal[CODE, pos:20, 123] + Markdown[MARKDOWN, pos:31, _jkl_] + UnknownInlineTag[UNKNOWN_INLINE_TAG, pos:36 + tag:unknown + content: 1 + Text[TEXT, pos:45] + ] + Markdown[MARKDOWN, pos:46, _mno.] + body: empty + block tags: empty +] +*/ + + /**md + * @since abc < def & ghi {@code 123} jkl {@unknown} mno. + */ + void blockTagMix() { } +/* +DocComment[DOC_COMMENT, pos:0 + firstSentence: empty + body: empty + block tags: 1 + Since[SINCE, pos:4 + body: 5 + Markdown[MARKDOWN, pos:11, abc_<_def_&_ghi_] + Literal[CODE, pos:27, 123] + Markdown[MARKDOWN, pos:38, _jkl_] + UnknownInlineTag[UNKNOWN_INLINE_TAG, pos:43 + tag:unknown + content: 1 + Text[TEXT, pos:52] + ] + Markdown[MARKDOWN, pos:53, _mno.] + ] +] +*/ + + /**md + * 123 {@link Object abc < def & ghi {@code 123} jkl {@unknown} mno} 456. + */ + void inlineTagMix() { } +/* +DocComment[DOC_COMMENT, pos:0 + firstSentence: 3 + Markdown[MARKDOWN, pos:4, 123_] + Link[LINK, pos:8 + reference: + Reference[REFERENCE, pos:15, Object] + body: 5 + Markdown[MARKDOWN, pos:22, abc_<_def_&_ghi_] + Literal[CODE, pos:38, 123] + Markdown[MARKDOWN, pos:49, _jkl_] + UnknownInlineTag[UNKNOWN_INLINE_TAG, pos:54 + tag:unknown + content: 1 + Text[TEXT, pos:63] + ] + Markdown[MARKDOWN, pos:64, _mno] + ] + Markdown[MARKDOWN, pos:69, _456.] + body: empty + block tags: empty +] +*/ + + + +} diff --git a/test/langtools/tools/javac/lib/DPrinter.java b/test/langtools/tools/javac/lib/DPrinter.java index c32f1135b73a6..2a1100dd766ba 100644 --- a/test/langtools/tools/javac/lib/DPrinter.java +++ b/test/langtools/tools/javac/lib/DPrinter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1091,6 +1091,11 @@ public Void visitLiteral(LiteralTree node, Void p) { return visitInlineTag(node, null); } + public Void visitMarkdown(MarkdownTree node, Void p) { + printLimitedEscapedString("content", node.getContent()); + return visitTree(node, null); + } + public Void visitParam(ParamTree node, Void p) { printString("isTypeParameter", String.valueOf(node.isTypeParameter())); printString("kind", node.getKind().name());