diff --git a/make/CompileDemos.gmk b/make/CompileDemos.gmk
index 599ce9842e4..6c751552e50 100644
--- a/make/CompileDemos.gmk
+++ b/make/CompileDemos.gmk
@@ -183,7 +183,7 @@ $(eval $(call SetupBuildDemo, SwingSet2, \
))
$(eval $(call SetupBuildDemo, Font2DTest, \
- DISABLED_WARNINGS := rawtypes deprecation unchecked serial cast this-escape, \
+ DISABLED_WARNINGS := rawtypes deprecation unchecked serial cast this-escape dangling-doc-comments, \
DEMO_SUBDIR := jfc, \
))
diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template
index 616999f2736..7ce4038f641 100644
--- a/make/autoconf/spec.gmk.template
+++ b/make/autoconf/spec.gmk.template
@@ -675,7 +675,7 @@ DOCS_REFERENCE_JAVADOC := @DOCS_REFERENCE_JAVADOC@
SOURCE_REVISION_TRACKER := $(SUPPORT_OUTPUTDIR)/src-rev/source-revision-tracker
# Interim langtools modules and arguments
-INTERIM_LANGTOOLS_BASE_MODULES := java.compiler jdk.compiler jdk.javadoc
+INTERIM_LANGTOOLS_BASE_MODULES := java.compiler jdk.compiler jdk.internal.md jdk.javadoc
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/make/jdk/src/classes/build/tools/taglet/JSpec.java b/make/jdk/src/classes/build/tools/taglet/JSpec.java
index ff8abc46ffd..fda372c8b01 100644
--- a/make/jdk/src/classes/build/tools/taglet/JSpec.java
+++ b/make/jdk/src/classes/build/tools/taglet/JSpec.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2024, 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
@@ -30,6 +30,8 @@ import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
import javax.lang.model.element.Element;
import com.sun.source.doctree.DocTree;
@@ -157,7 +159,10 @@ public class JSpec implements Taglet {
continue;
}
- String tagText = contents.toString().trim();
+ String tagText = contents.stream()
+ .map(Object::toString)
+ .collect(Collectors.joining())
+ .trim();
Matcher m = TAG_PATTERN.matcher(tagText);
if (m.find()) {
String chapter = m.group("chapter");
diff --git a/make/jdk/src/classes/build/tools/taglet/ToolGuide.java b/make/jdk/src/classes/build/tools/taglet/ToolGuide.java
index f3ce80cf241..af38e0a765c 100644
--- a/make/jdk/src/classes/build/tools/taglet/ToolGuide.java
+++ b/make/jdk/src/classes/build/tools/taglet/ToolGuide.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2024, 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
@@ -30,6 +30,8 @@ import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
@@ -105,8 +107,11 @@ public class ToolGuide implements Taglet {
continue;
}
- UnknownBlockTagTree blockTag = (UnknownBlockTagTree)tag;
- String tagText = blockTag.getContent().toString().trim();
+ UnknownBlockTagTree blockTag = (UnknownBlockTagTree) tag;
+ String tagText = blockTag.getContent().stream()
+ .map(Object::toString)
+ .collect(Collectors.joining())
+ .trim();
Matcher m = TAG_PATTERN.matcher(tagText);
if (m.matches()) {
String name = m.group("name");
diff --git a/make/modules/java.base/Java.gmk b/make/modules/java.base/Java.gmk
index 5820c280fe9..cbe6eecf187 100644
--- a/make/modules/java.base/Java.gmk
+++ b/make/modules/java.base/Java.gmk
@@ -27,7 +27,7 @@
# new warning is added to javac, it can be temporarily added to the
# disabled warnings list.
#
-# DISABLED_WARNINGS_java +=
+DISABLED_WARNINGS_java += dangling-doc-comments
DOCLINT += -Xdoclint:all/protected \
'-Xdoclint/package:java.*,javax.*'
diff --git a/make/modules/jdk.incubator.vector/Java.gmk b/make/modules/jdk.incubator.vector/Java.gmk
index a2013179ace..6cd5301428f 100644
--- a/make/modules/jdk.incubator.vector/Java.gmk
+++ b/make/modules/jdk.incubator.vector/Java.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2020, 2024, 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,4 +23,6 @@
# questions.
#
+DISABLED_WARNINGS_java += dangling-doc-comments
+
DOCLINT += -Xdoclint:all/protected
diff --git a/make/modules/jdk.jpackage/Java.gmk b/make/modules/jdk.jpackage/Java.gmk
index acb8529f9ef..9d31e5417e9 100644
--- a/make/modules/jdk.jpackage/Java.gmk
+++ b/make/modules/jdk.jpackage/Java.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2020, 2024, 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,6 +23,8 @@
# questions.
#
+DISABLED_WARNINGS_java += dangling-doc-comments
+
COPY += .gif .png .txt .spec .script .prerm .preinst \
.postrm .postinst .list .sh .desktop .copyright .control .plist .template \
.icns .scpt .wxs .wxl .wxi .ico .bmp .tiff .service
diff --git a/make/modules/jdk.unsupported/Java.gmk b/make/modules/jdk.unsupported/Java.gmk
new file mode 100644
index 00000000000..fc8b2f832d7
--- /dev/null
+++ b/make/modules/jdk.unsupported/Java.gmk
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2024, 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.
+#
+
+DISABLED_WARNINGS_java += dangling-doc-comments
diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk
index 3d6ec9950d7..4a6232afbe0 100644
--- a/make/test/BuildMicrobenchmark.gmk
+++ b/make/test/BuildMicrobenchmark.gmk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2018, 2024, 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
@@ -95,7 +95,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \
SMALL_JAVA := false, \
CLASSPATH := $(JMH_COMPILE_JARS), \
DISABLED_WARNINGS := restricted this-escape processing rawtypes cast \
- serial preview, \
+ serial preview dangling-doc-comments, \
SRC := $(MICROBENCHMARK_SRC), \
BIN := $(MICROBENCHMARK_CLASSES), \
JAVAC_FLAGS := \
diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java
index 5dd556215d3..f0804b58c1c 100644
--- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java
+++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2024, 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
@@ -46,7 +46,7 @@ final class JrtFileAttributes implements BasicFileAttributes {
this.node = node;
}
- ///////// basic attributes ///////////
+ //-------- basic attributes --------
@Override
public FileTime creationTime() {
return node.creationTime();
@@ -92,7 +92,7 @@ final class JrtFileAttributes implements BasicFileAttributes {
return node.resolveLink(true);
}
- ///////// jrtfs specific attributes ///////////
+ //-------- jrtfs specific attributes --------
/**
* Compressed resource file. If not available or not applicable, 0L is
* returned.
diff --git a/src/java.compiler/share/classes/javax/lang/model/util/Elements.java b/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
index 56c86fc3ce4..2faf3f5ec32 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
@@ -277,19 +277,26 @@ public interface Elements {
getElementValuesWithDefaults(AnnotationMirror a);
/**
- * Returns the text of the documentation ("Javadoc")
+ * Returns the text of the documentation ("JavaDoc")
* comment of an element.
*
- *
A documentation comment of an element is a comment that
- * begins with "{@code /**}", ends with a separate
- * "*/", and immediately precedes the element,
- * ignoring white space, annotations, end-of-line-comments ({@code
- * "//"} comments), and intermediate traditional comments
- * ("/* ... */" comments) that are not doc comments.
- * Therefore, a documentation comment
- * contains at least three "{@code *}" characters. The text
+ *
A documentation comment of an element is a particular kind
+ * of comment that immediately precedes the element, ignoring
+ * white space, annotations and any other comments that are
+ * not themselves documentation comments.
+ *
+ *
A {@linkplain DocCommentKind#TRADITIONAL traditional
+ * documentation comment} is a traditional comment that begins
+ * with "{@code /**}", and ends with a separate "*/".
+ * (Therefore, such a comment contains at least three "{@code *}"
+ * characters.)
+ * The lines of such a comment are processed as follows:
*
An {@linkplain DocCommentKind#END_OF_LINE end-of-line
+ * documentation comment} is a series of adjacent end-of-line
+ * comments, each on a line by itself, ignoring any whitespace
+ * characters at the beginning of the line, and each beginning
+ * with "{@code ///}".
+ * The lines of such a comment are processed as follows:
+ *
{
return visitOther(node, p);
}
+ /**
+ * Visits a {@code RawTextTree} node.
+ *
+ * @implSpec Visits the provided {@code RawTextTree} node
+ * by calling {@code visitOther(node, p)}.
+ *
+ * @param node the node being visited
+ * @param p a parameter value
+ * @return a result value
+ *
+ * @since 23
+ */
+ default R visitRawText(RawTextTree node, P p) {
+ return visitOther(node, p);
+ }
+
/**
* Visits a {@code ReferenceTree} 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 7d3624413ed..0df2c0db028 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, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2024, 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,6 +33,11 @@ package com.sun.source.doctree;
* {@inheritDoc supertype}
*
*
+ * @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 traditional (not Markdown) comments.
+ *
* @since 1.8
*/
public interface InheritDocTree extends InlineTagTree {
diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/RawTextTree.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/RawTextTree.java
new file mode 100644
index 00000000000..e2a02b42cb9
--- /dev/null
+++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/RawTextTree.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022, 2024, 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 uninterpreted raw text content.
+ *
+ *
+ * The content may contain any text except that for
+ * {@linkplain InlineTagTree inline tags}.
+ *
+ *
The format of the content is indicated by the {@linkplain #getKind() kind}
+ * of the tree node.
+ *
+ * @apiNote
+ * This class may be used to represent tree nodes containing
+ * {@linkplain DocTree.Kind#MARKDOWN Markdown} text.
+ * Such 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.
+ *
+ * @since 23
+ */
+public interface RawTextTree 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 e4794e7be94..22be135e696 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, 2024, 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,7 +28,6 @@
* trees (AST).
*
* @spec javadoc/doc-comment-spec.html Documentation Comment Specification for the Standard Doclet
- * @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 391acfdfb6e..610c685f848 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, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2024, 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
@@ -52,6 +52,7 @@ import com.sun.source.doctree.IndexTree;
import com.sun.source.doctree.InheritDocTree;
import com.sun.source.doctree.LinkTree;
import com.sun.source.doctree.LiteralTree;
+import com.sun.source.doctree.RawTextTree;
import com.sun.source.doctree.ParamTree;
import com.sun.source.doctree.ProvidesTree;
import com.sun.source.doctree.ReferenceTree;
@@ -289,6 +290,18 @@ public interface DocTreeFactory {
*/
ProvidesTree newProvidesTree(ReferenceTree name, List extends DocTree> description);
+ /**
+ * Creates a new {@code RawTextTree} object, to represent a fragment of uninterpreted raw text.
+ *
+ * @param kind the kind of text
+ * @param code the code
+ * @return a {@code RawTextTree} object
+ * @throws IllegalArgumentException if the kind is not a recognized kind for raw text
+ *
+ * @since 23
+ */
+ RawTextTree newRawTextTree(DocTree.Kind kind, String code);
+
/**
* Creates a new {@code ReferenceTree} object, to represent a reference to an API element.
*
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 cde78ea467c..2dd83d85a5d 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, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2024, 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
@@ -413,6 +413,22 @@ public class DocTreeScanner implements DocTreeVisitor {
return r;
}
+ /**
+ * {@inheritDoc}
+ *
+ * @implSpec This implementation returns {@code null}.
+ *
+ * @param node {@inheritDoc}
+ * @param p {@inheritDoc}
+ * @return the result of scanning
+ *
+ * @since 23
+ */
+ @Override
+ public R visitRawText(RawTextTree node, P p) {
+ return null;
+ }
+
/**
* {@inheritDoc}
*
diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java b/src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java
index ecad91f76c9..6d2e60e58a3 100644
--- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java
+++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2024, 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,10 +28,12 @@ package com.sun.source.util;
import java.io.IOException;
import java.text.BreakIterator;
import java.util.List;
+
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements.DocCommentKind;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.JavaCompiler.CompilationTask;
@@ -84,6 +86,16 @@ public abstract class DocTrees extends Trees {
*/
public abstract BreakIterator getBreakIterator();
+ /**
+ * {@return the style of the documentation comment associated with a tree node}
+ *
+ * @param path the path for the tree node
+ *
+ * @see Trees#getPath(Element)
+ * @since 23
+ */
+ public abstract DocCommentKind getDocCommentKind(TreePath path);
+
/**
* Returns the doc comment tree, if any, for the Tree node identified by a given TreePath.
* Returns {@code null} if no doc comment was found.
@@ -113,10 +125,14 @@ public abstract class DocTrees extends Trees {
public abstract DocCommentTree getDocCommentTree(Element e);
/**
- * Returns the doc comment tree of the given file. The file must be
- * an HTML file, in which case the doc comment tree represents the
- * entire contents of the file.
- * Returns {@code null} if no doc comment was found.
+ * Returns the doc comment tree of the given file, which must
+ * be of one of the supported file types.
+ *
+ * The supported file types are:
+ *
+ * - HTML files, identified by a file name ending in {@code .html},
+ *
- Markdown files, identified by a file name ending in {@code .md}.
+ *
* Future releases may support additional file types.
*
* @implNote The default implementation of this method returns a
@@ -124,16 +140,22 @@ public abstract class DocTrees extends Trees {
*
* @param fileObject the content container
* @return the doc comment tree
+ * @throws IllegalArgumentException if the file type is not supported
+ *
* @since 9
*/
public abstract DocCommentTree getDocCommentTree(FileObject fileObject);
/**
- * Returns the doc comment tree of the given file whose path is
- * specified relative to the given element. The file must be an HTML
- * file, in which case the doc comment tree represents the contents
- * of the <body> tag, and any enclosing tags are ignored.
- * Returns {@code null} if no doc comment was found.
+ * Returns the doc comment tree of the given file, which must
+ * be of one of the supported file types, and whose path is
+ * specified relative to the given element.
+ *
+ * The supported file types are:
+ *
+ * - HTML files, identified by a file name ending in {@code .html},
+ *
- Markdown files, identified by a file name ending in {@code .md}.
+ *
* Future releases may support additional file types.
*
* @implNote The default implementation of this method returns a
@@ -142,16 +164,20 @@ public abstract class DocTrees extends Trees {
* @param e an element whose path is used as a reference
* @param relativePath the relative path from the Element
* @return the doc comment tree
- * @throws java.io.IOException if an exception occurs
+ * @throws IOException if an exception occurs
+ * @throws IllegalArgumentException if the file type is not supported
*
* @since 9
*/
public abstract DocCommentTree getDocCommentTree(Element e, String relativePath) throws IOException;
/**
- * Returns a doc tree path containing the doc comment tree of the given file.
- * The file must be an HTML file, in which case the doc comment tree represents
- * the contents of the {@code } tag, and any enclosing tags are ignored.
+ * Returns a doc tree path containing the doc comment tree of the given file,
+ * which must be of one of the supported file types.
+ *
+ * Supported file types are HTML files and Markdown files.
+ * Future releases may support additional file types.
+ *
* Any references to source code elements contained in {@code @see} and
* {@code {@link}} tags in the doc comment tree will be evaluated in the
* context of the given package element.
@@ -161,7 +187,7 @@ public abstract class DocTrees extends Trees {
* @param packageElement a package element to associate with the given file object
* representing a legacy package.html, null otherwise
* @return a doc tree path containing the doc comment parsed from the given file
- * @throws IllegalArgumentException if the fileObject is not an HTML file
+ * @throws IllegalArgumentException if the file type is not supported
*
* @since 9
*/
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 bdd6225c869..77ecb5535a1 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, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2024, 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
@@ -366,6 +366,22 @@ public class SimpleDocTreeVisitor implements DocTreeVisitor {
return defaultAction(node, p);
}
+ /**
+ * {@inheritDoc}
+ *
+ * @implSpec This implementation calls {@code defaultAction}.
+ *
+ * @param node {@inheritDoc}
+ * @param p {@inheritDoc}
+ * @return the result of {@code defaultAction}
+ *
+ * @since 23
+ */
+ @Override
+ public R visitRawText(RawTextTree node, P p) {
+ return defaultAction(node, p);
+ }
+
/**
* {@inheritDoc}
*
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java
index 45c76071f8b..8f84f5d7fbd 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java
@@ -32,6 +32,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.ServiceLoader;
import java.util.Set;
import java.util.WeakHashMap;
@@ -48,6 +49,7 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements.DocCommentKind;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.ForwardingFileObject;
@@ -67,7 +69,6 @@ import com.sun.source.tree.Scope;
import com.sun.source.tree.Tree;
import com.sun.source.util.DocSourcePositions;
import com.sun.source.util.DocTreePath;
-import com.sun.source.util.DocTreeScanner;
import com.sun.source.util.DocTrees;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TreePath;
@@ -100,6 +101,7 @@ import com.sun.tools.javac.file.BaseFileManager;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.parser.DocCommentParser;
import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.parser.ReferenceParser;
import com.sun.tools.javac.parser.Tokens.Comment;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
@@ -169,10 +171,12 @@ public class JavacTrees extends DocTrees {
private final Types types;
private final DocTreeMaker docTreeMaker;
private final JavaFileManager fileManager;
- private final ParserFactory parser;
private final Symtab syms;
private BreakIterator breakIterator;
+ private final ParserFactory parserFactory;
+
+ private DocCommentTreeTransformer docCommentTreeTransformer;
private final Map extraType2OriginalMap = new WeakHashMap<>();
@@ -214,7 +218,7 @@ public class JavacTrees extends DocTrees {
names = Names.instance(context);
types = Types.instance(context);
docTreeMaker = DocTreeMaker.instance(context);
- parser = ParserFactory.instance(context);
+ parserFactory = ParserFactory.instance(context);
syms = Symtab.instance(context);
fileManager = context.get(JavaFileManager.class);
var task = context.get(JavacTask.class);
@@ -261,20 +265,6 @@ public class JavacTrees extends DocTrees {
return docTreeMaker;
}
- private DocTree getLastChild(DocTree tree) {
- final DocTree[] last = new DocTree[] {null};
-
- tree.accept(new DocTreeScanner() {
- @Override @DefinedBy(Api.COMPILER_TREE)
- public Void scan(DocTree node, Void p) {
- if (node != null) last[0] = node;
- return null;
- }
- }, null);
-
- return last[0];
- }
-
@Override @DefinedBy(Api.COMPILER_TREE)
public JCClassDecl getTree(TypeElement element) {
return (JCClassDecl) getTree((Element) element);
@@ -723,8 +713,8 @@ public class JavacTrees extends DocTrees {
@Override @DefinedBy(Api.COMPILER_TREE)
public TypeMirror getTypeMirror(TreePath path) {
- Tree t = path.getLeaf();
- Type ty = ((JCTree)t).type;
+ Tree leaf = path.getLeaf();
+ Type ty = ((JCTree) leaf).type;
return ty == null ? null : ty.stripMetadataIfNeeded();
}
@@ -733,26 +723,40 @@ public class JavacTrees extends DocTrees {
return JavacScope.create(getAttrContext(path));
}
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public DocCommentKind getDocCommentKind(TreePath path) {
+ var compUnit = path.getCompilationUnit();
+ var leaf = path.getLeaf();
+ if (compUnit instanceof JCTree.JCCompilationUnit cu && leaf instanceof JCTree l
+ && cu.docComments != null) {
+ Comment c = cu.docComments.getComment(l);
+ return (c == null) ? null : switch (c.getStyle()) {
+ case JAVADOC_BLOCK -> DocCommentKind.TRADITIONAL;
+ case JAVADOC_LINE -> DocCommentKind.END_OF_LINE;
+ default -> null;
+ };
+ }
+ return null;
+ }
+
@Override @DefinedBy(Api.COMPILER_TREE)
public String getDocComment(TreePath path) {
- CompilationUnitTree t = path.getCompilationUnit();
- Tree leaf = path.getLeaf();
- if (t instanceof JCTree.JCCompilationUnit compilationUnit && leaf instanceof JCTree tree) {
- if (compilationUnit.docComments != null) {
- return compilationUnit.docComments.getCommentText(tree);
- }
+ var compUnit = path.getCompilationUnit();
+ var leaf = path.getLeaf();
+ if (compUnit instanceof JCTree.JCCompilationUnit cu && leaf instanceof JCTree l
+ && cu.docComments != null) {
+ return cu.docComments.getCommentText(l);
}
return null;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public DocCommentTree getDocCommentTree(TreePath path) {
- CompilationUnitTree t = path.getCompilationUnit();
- Tree leaf = path.getLeaf();
- if (t instanceof JCTree.JCCompilationUnit compilationUnit && leaf instanceof JCTree tree) {
- if (compilationUnit.docComments != null) {
- return compilationUnit.docComments.getCommentTree(tree);
- }
+ var compUnit = path.getCompilationUnit();
+ var leaf = path.getLeaf();
+ if (compUnit instanceof JCTree.JCCompilationUnit cu && leaf instanceof JCTree l
+ && cu.docComments != null) {
+ return cu.docComments.getCommentTree(l);
}
return null;
}
@@ -990,33 +994,40 @@ public class JavacTrees extends DocTrees {
return flatNameForClass;
}
- static JavaFileObject asJavaFileObject(FileObject fileObject) {
- JavaFileObject jfo = null;
+ private static boolean isHtmlFile(FileObject fo) {
+ return fo.getName().endsWith(".html");
+ }
- if (fileObject instanceof JavaFileObject javaFileObject) {
- checkHtmlKind(fileObject, Kind.HTML);
- return javaFileObject;
+ private static boolean isMarkdownFile(FileObject fo) {
+ return fo.getName().endsWith(".md");
+ }
+
+
+ static JavaFileObject asDocFileObject(FileObject fo) {
+ if (fo instanceof JavaFileObject jfo) {
+ switch (jfo.getKind()) {
+ case HTML -> {
+ return jfo;
+ }
+ case OTHER -> {
+ if (isMarkdownFile(jfo)) {
+ return jfo;
+ }
+ }
+ }
+ } else {
+ if (isHtmlFile(fo) || isMarkdownFile(fo)) {
+ return new DocFileObject(fo);
+ }
}
- checkHtmlKind(fileObject);
- jfo = new HtmlFileObject(fileObject);
- return jfo;
+ throw new IllegalArgumentException(("Not a documentation file: " + fo.getName()));
}
- private static void checkHtmlKind(FileObject fileObject) {
- checkHtmlKind(fileObject, BaseFileManager.getKind(fileObject.getName()));
- }
-
- private static void checkHtmlKind(FileObject fileObject, JavaFileObject.Kind kind) {
- if (kind != JavaFileObject.Kind.HTML) {
- throw new IllegalArgumentException("HTML file expected:" + fileObject.getName());
- }
- }
-
- private static class HtmlFileObject extends ForwardingFileObject
+ private static class DocFileObject extends ForwardingFileObject
implements JavaFileObject {
- public HtmlFileObject(FileObject fileObject) {
+ public DocFileObject(FileObject fileObject) {
super(fileObject);
}
@@ -1043,7 +1054,7 @@ public class JavacTrees extends DocTrees {
@Override @DefinedBy(Api.COMPILER_TREE)
public DocCommentTree getDocCommentTree(FileObject fileObject) {
- JavaFileObject jfo = asJavaFileObject(fileObject);
+ JavaFileObject jfo = asDocFileObject(fileObject);
DiagnosticSource diagSource = new DiagnosticSource(jfo, log);
final Comment comment = new Comment() {
@@ -1071,21 +1082,26 @@ public class JavacTrees extends DocTrees {
@Override
public CommentStyle getStyle() {
- throw new UnsupportedOperationException();
+ return isHtmlFile(fileObject) ? CommentStyle.JAVADOC_BLOCK
+ : isMarkdownFile(fileObject) ? CommentStyle.JAVADOC_LINE
+ : null;
}
@Override
public boolean isDeprecated() {
- throw new UnsupportedOperationException();
+ return false;
}
};
- return new DocCommentParser(parser, diagSource, comment, true).parse();
+ boolean isHtmlFile = jfo.getKind() == Kind.HTML;
+
+ var dct = new DocCommentParser(parserFactory, diagSource, comment, isHtmlFile).parse();
+ return transform(dct);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public DocTreePath getDocTreePath(FileObject fileObject, PackageElement packageElement) {
- JavaFileObject jfo = asJavaFileObject(fileObject);
+ JavaFileObject jfo = asDocFileObject(fileObject);
DocCommentTree docCommentTree = getDocCommentTree(jfo);
if (docCommentTree == null)
return null;
@@ -1103,6 +1119,103 @@ public class JavacTrees extends DocTrees {
return Entity.getCharacters(tree);
}
+ /**
+ * {@return the doc comment tree for a given comment}
+ *
+ * @param diagSource the source containing the comment, used when displaying any diagnostics
+ * @param c the comment
+ */
+ public DocCommentTree getDocCommentTree(DiagnosticSource diagSource, Comment c) {
+ var dct = new DocCommentParser(parserFactory, diagSource, c).parse();
+ return transform(dct);
+ }
+
+ /**
+ * An interface for transforming a {@code DocCommentTree}.
+ * It is primarily used as the service-provider interface for an implementation
+ * that embodies the JDK extensions to CommonMark, such as reference links to
+ * program elements.
+ */
+ public interface DocCommentTreeTransformer {
+ /**
+ * The name used by the implementation that embodies the JDK extensions to CommonMark.
+ */
+ public final String STANDARD = "standard";
+
+ /**
+ * {@return the name of this transformer}
+ */
+ String name();
+
+ /**
+ * Transforms a documentation comment tree.
+ *
+ * @param trees an instance of the {@link DocTrees} utility interface.
+ * @param tree the tree to be transformed
+ * @return the transformed tree
+ */
+ DocCommentTree transform(DocTrees trees, DocCommentTree tree);
+ }
+
+ /**
+ * A class that provides the identity transform on instances of {@code DocCommentTree}.
+ */
+ public static class IdentityTransformer implements DocCommentTreeTransformer {
+ @Override
+ public String name() {
+ return "identity";
+ }
+
+ @Override
+ public DocCommentTree transform(DocTrees trees, DocCommentTree tree) {
+ return tree;
+ }
+ }
+
+ public DocCommentTreeTransformer getDocCommentTreeTransformer() {
+ return docCommentTreeTransformer;
+ }
+
+ public void setDocCommentTreeTransformer(DocCommentTreeTransformer transformer) {
+ docCommentTreeTransformer = transformer;
+ }
+
+ /**
+ * Initialize {@link #docCommentTreeTransformer} if it is {@code null},
+ * using a service provider to look up an implementation with the name "standard".
+ * If none is found, an identity transformer is used, with the name "identity".
+ */
+ public void initDocCommentTreeTransformer() {
+ if (docCommentTreeTransformer == null) {
+ var sl = ServiceLoader.load(DocCommentTreeTransformer.class);
+ docCommentTreeTransformer = sl.stream()
+ .map(ServiceLoader.Provider::get)
+ .filter(t -> t.name().equals(DocCommentTreeTransformer.STANDARD))
+ .findFirst()
+ .orElseGet(() -> new IdentityTransformer());
+ }
+ }
+
+ /**
+ * Transforms the given tree using the current {@linkplain #getDocCommentTreeTransformer() transformer},
+ * after ensuring it has been {@linkplain #initDocCommentTreeTransformer() initialized}.
+ *
+ * @param tree the tree
+ * @return the transformed tree
+ */
+ private DocCommentTree transform(DocCommentTree tree) {
+ initDocCommentTreeTransformer();
+ return docCommentTreeTransformer.transform(this, tree);
+ }
+
+ /**
+ * {@return the {@linkplain ParserFactory} parser factory}
+ * The factory can be used to create a {@link ReferenceParser}, to parse link references.
+ */
+ public ParserFactory getParserFactory() {
+ return parserFactory;
+ }
+
/**
* Makes a copy of a tree, noting the value resulting from copying a particular leaf.
**/
@@ -1206,20 +1319,10 @@ public class JavacTrees extends DocTrees {
try {
switch (kind) {
- case ERROR:
- log.error(DiagnosticFlag.API, pos, Errors.ProcMessager(msg.toString()));
- break;
-
- case WARNING:
- log.warning(pos, Warnings.ProcMessager(msg.toString()));
- break;
-
- case MANDATORY_WARNING:
- log.mandatoryWarning(pos, Warnings.ProcMessager(msg.toString()));
- break;
-
- default:
- log.note(pos, Notes.ProcMessager(msg.toString()));
+ case ERROR -> log.error(DiagnosticFlag.API, pos, Errors.ProcMessager(msg.toString()));
+ case WARNING -> log.warning(pos, Warnings.ProcMessager(msg.toString()));
+ case MANDATORY_WARNING -> log.mandatoryWarning(pos, Warnings.ProcMessager(msg.toString()));
+ default -> log.note(pos, Notes.ProcMessager(msg.toString()));
}
} finally {
if (oldSource != null)
@@ -1272,6 +1375,11 @@ public class JavacTrees extends DocTrees {
throw new UnsupportedOperationException();
}
+ @Override
+ public DocCommentKind getCommentKind(JCTree tree) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public String getCommentText(JCTree tree) {
throw new UnsupportedOperationException();
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java
index e49ce45240a..cec23aea0b7 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2006, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 2024, 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
@@ -381,6 +381,8 @@ public enum Option {
PREVIEW("--enable-preview", "opt.preview", STANDARD, BASIC),
+ DISABLE_LINE_DOC_COMMENTS("--disable-line-doc-comments", "opt.lineDocComments", EXTENDED, BASIC),
+
PROFILE("-profile", "opt.arg.profile", "opt.profile", STANDARD, BASIC) {
@Override
public void process(OptionHelper helper, String option, String operand) throws InvalidValueException {
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
index bdd25c066e5..7cf419f3ca8 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2024, 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,6 +33,7 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.BiFunction;
import java.util.stream.Collectors;
import javax.lang.model.AnnotatedConstruct;
@@ -61,6 +62,7 @@ import com.sun.tools.javac.comp.Enter;
import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.processing.PrintingProcessor;
+import com.sun.tools.javac.tree.DocCommentTable;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.tree.TreeInfo;
@@ -74,7 +76,6 @@ import static com.sun.tools.javac.code.TypeTag.CLASS;
import com.sun.tools.javac.comp.Attr;
import com.sun.tools.javac.comp.Modules;
import com.sun.tools.javac.comp.Resolve;
-import com.sun.tools.javac.comp.Resolve.RecoveryLoadClass;
import com.sun.tools.javac.resources.CompilerProperties.Notes;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
@@ -433,6 +434,15 @@ public class JavacElements implements Elements {
@DefinedBy(Api.LANGUAGE_MODEL)
public String getDocComment(Element e) {
+ return getDocCommentItem(e, ((docCommentTable, tree) -> docCommentTable.getCommentText(tree)));
+ }
+
+ @DefinedBy(Api.LANGUAGE_MODEL)
+ public DocCommentKind getDocCommentKind(Element e) {
+ return getDocCommentItem(e, ((docCommentTable, tree) -> docCommentTable.getCommentKind(tree)));
+ }
+
+ private R getDocCommentItem(Element e, BiFunction f) {
// Our doc comment is contained in a map in our toplevel,
// indexed by our tree. Find our enter environment, which gives
// us our toplevel. It also gives us a tree that contains our
@@ -444,7 +454,7 @@ public class JavacElements implements Elements {
JCCompilationUnit toplevel = treeTop.snd;
if (toplevel.docComments == null)
return null;
- return toplevel.docComments.getCommentText(tree);
+ return f.apply(toplevel.docComments, tree);
}
@DefinedBy(Api.LANGUAGE_MODEL)
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 603e7c42807..acd79c69bd4 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, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024, 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,10 +26,13 @@
package com.sun.tools.javac.parser;
import java.io.Serial;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import java.util.regex.Pattern;
import com.sun.source.doctree.AttributeTree.ValueKind;
+import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.ErroneousTree;
import com.sun.source.doctree.UnknownBlockTagTree;
import com.sun.source.doctree.UnknownInlineTagTree;
@@ -76,7 +79,27 @@ public class DocCommentParser {
}
}
- private enum Phase { PREAMBLE, BODY, POSTAMBLE }
+ /**
+ * An indication of the part of documentation comment or file being read.
+ * A documentation comment and a Markdown file just have a {@code BODY}.
+ * An HTML file has a {@code PREAMBLE}, {@code BODY} and {@code POSTAMBLE}.
+ * While parsing a {@code BODY}, text within {@code {@tag ... }} may be
+ * read as {@code INLINE}.
+ * One of the primary characteristics of each "phase" is the termination
+ * condition, indicating when the current phase is complete, and either
+ * the previous one resumed (in the case of {@code INLINE}) or the
+ * next phase started.
+ */
+ private enum Phase {
+ /** The initial part of an HTML file up to and including the {@code body} and possible {@code } tag. */
+ PREAMBLE,
+ /** The initial part of a doc comment, or the rich-text content of a block tag. */
+ BODY,
+ /** The end of an HTML file, from and including the {@code } or {@code } tag. */
+ POSTAMBLE,
+ /** The rich-text content of an inline documentation comment tag. */
+ INLINE
+ }
private final ParserFactory fac;
private final JCDiagnostic.Factory diags;
@@ -84,7 +107,9 @@ public class DocCommentParser {
private final Comment comment;
private final DocTreeMaker m;
private final Names names;
- private final boolean isFileContent;
+ private final boolean isHtmlFile;
+ private final DocTree.Kind textKind;
+ private final Markdown markdown;
/** The input buffer, index of most recent character read,
* index of one past last character in buffer.
@@ -103,22 +128,79 @@ public class DocCommentParser {
private final Map tagParsers;
+ /**
+ * Creates a parser for a documentation comment.
+ *
+ * @param fac a parser factory, for a doc-tree maker and for reference parsers
+ * @param diagSource the source in which the comment was found
+ * @param comment the comment
+ */
+ public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) {
+ this(fac, diagSource, comment, false);
+ }
+
+ /**
+ * Creates a parser for a documentation comment.
+ *
+ * If the comment is the content of a standalone HTML file, it will be parsed
+ * in three parts: a preamble (up to and including the {@code } tag,
+ * or {@code } tag if there is no {@code } tag, then the main content
+ * of the file, and then finally the end part of the file starting at the
+ * end tag matching the tag that ended the preamble.
+ *
+ * @param fac a parser factory, for a doc-tree maker and for reference parsers
+ * @param diagSource the source in which the comment was found
+ * @param comment the comment
+ * @param isHtmlFile whether the comment is the entire content of an HTML file
+ */
public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource,
- Comment comment, boolean isFileContent) {
+ Comment comment, boolean isHtmlFile) {
this.fac = fac;
this.diags = fac.log.diags;
this.diagSource = diagSource;
this.comment = comment;
names = fac.names;
- this.isFileContent = isFileContent;
+ this.isHtmlFile = isHtmlFile;
+ textKind = isHtmlFile ? DocTree.Kind.TEXT : getTextKind(comment);
m = fac.docTreeMaker;
tagParsers = createTagParsers();
+ markdown = textKind == DocTree.Kind.MARKDOWN ? new Markdown() : null;
}
- public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) {
- this(fac, diagSource, comment, false);
+ private static DocTree.Kind getTextKind(Comment c) {
+ return switch (c.getStyle()) {
+ case JAVADOC_BLOCK -> DocTree.Kind.TEXT;
+ case JAVADOC_LINE -> DocTree.Kind.MARKDOWN;
+ default -> throw new IllegalArgumentException(c.getStyle().name());
+ };
}
+ /**
+ * {@return the result of parsing the content given to the constructor}
+ *
+ * The parsing rules for "traditional" documentation comments are generally incompatible
+ * with the rules for parsing CommonMark Markdown: traditional comments are parsed
+ * with a simple recursive-descent parser, while CommonMark is parsed in two "phases":
+ * first identifying blocks, and then subsequently parsing the content of those blocks.
+ * The conflict shows up most with the rules for inline tags, some of which may contain
+ * almost arbitrary content, including blank lines in particular.
+ *
+ * We do not want to build and maintain a fully-compliant Markdown parser, nor
+ * can we leverage an existing Markdown parser, such as in commonmark-java,
+ * which cannot handle arbitrary content in "inline" constructs, or the recursive
+ * nature of some inline tags.
+ *
+ * The solution is a compromise. First, we identify inline and block tags,
+ * so that they may be treated as opaque objects when subsequently using a
+ * library (such as commonmark-java) to process the enclosing parts of the
+ * comment. The catch is that while we do not need to provide a full Markdown
+ * parser, we do need to parse it enough to recognize character sequences of
+ * literal code (that is, code spans and code blocks), which should not be
+ * scanned for inline and block tags. Most of this work is handled by the
+ * nested {@link Markdown} class.
+ *
+ * @see Blocks and Inlines
+ */
public DCDocComment parse() {
String c = comment.getText();
buf = new char[c.length() + 1];
@@ -128,12 +210,13 @@ public class DocCommentParser {
bp = -1;
nextChar();
- List preamble = isFileContent ? blockContent(Phase.PREAMBLE) : List.nil();
- List body = blockContent(Phase.BODY);
+ List preamble = isHtmlFile ? content(Phase.PREAMBLE) : List.nil();
+ List body = content(Phase.BODY);
List tags = blockTags();
- List postamble = isFileContent ? blockContent(Phase.POSTAMBLE) : List.nil();
+ List postamble = isHtmlFile ? content(Phase.POSTAMBLE) : List.nil();
- int pos = !preamble.isEmpty() ? preamble.head.pos
+ int pos = textKind == DocTree.Kind.MARKDOWN ? 0
+ : !preamble.isEmpty() ? preamble.head.pos
: !body.isEmpty() ? body.head.pos
: !tags.isEmpty() ? tags.head.pos
: !postamble.isEmpty() ? postamble.head.pos
@@ -145,7 +228,17 @@ public class DocCommentParser {
void nextChar() {
ch = buf[bp < buflen ? ++bp : buflen];
switch (ch) {
- case '\f', '\n', '\r' -> newline = true;
+ case '\n' -> {
+ newline = true;
+ }
+
+ case '\r' -> {
+ if (bp + 1 < buflen && buf[bp + 1] == '\n') {
+ bp++;
+ ch = '\n';
+ }
+ newline = true;
+ }
}
}
@@ -154,74 +247,146 @@ public class DocCommentParser {
}
protected List blockContent() {
- return blockContent(Phase.BODY);
+ // The whitespace after a tag name is typically problematic:
+ // should it be included in the content or not?
+ // The problem is made worse by Markdown, in which leading
+ // whitespace in Markdown content may be significant.
+ // While generally in traditional comments, whitespace
+ // after a tag name is skipped, to allow for Markdown we just
+ // skip horizontal whitespace.
+ while (isHorizontalWhitespace(ch) && bp < buflen) {
+ nextChar();
+ }
+ return content(Phase.BODY);
}
/**
- * Read block content, consisting of text, 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.
+ * Reads "rich text" content, consisting of text, html and inline tags,
+ * according to the given {@code phase}.
+ *
+ * Inline tags are only recognized in {@code BODY} and {@code INLINE}
+ * phases, and not in {@code PREAMBLE} and {@code POSTAMBLE} phases.
+ *
+ * The end of the content is dependent on the phase:
+ *
+ *
+ * - {@code PREAMBLE}: the appearance of {@code } (or {@code }),
+ * as determined by {@link #isEndPreamble()}
+ *
- {@code BODY}: the beginning of a block tag, or when reading from
+ * an HTML file, the appearance of {@code
} (or {@code },
+ * as determined by {@link #isEndBody()}
+ * - {@code INLINE}: '}', after skipping any matching {@code { }}
+ *
- {@code PREAMBLE}: end of file
+ *
*/
- @SuppressWarnings("fallthrough")
- protected List blockContent(Phase phase) {
+ protected List content(Phase phase) {
ListBuffer trees = new ListBuffer<>();
textStart = -1;
+ int depth = 1; // only used when phase is INLINE
+ int pos = bp; // only used when phase is INLINE
+
loop:
while (bp < buflen) {
switch (ch) {
- case '\n': case '\r': case '\f':
- case ' ': case '\t':
+ case '\n', '\r' -> {
nextChar();
- break;
-
- case '&':
- entity(trees);
- break;
-
- case '<':
- 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;
- }
- }
- case BODY -> {
- if (isEndBody()) {
- addPendingText(trees, lastNonWhite);
- break loop;
- }
- }
-
- default -> { }
+ if (textKind == DocTree.Kind.MARKDOWN) {
+ markdown.update();
+ if (markdown.isIndentedCodeBlock()) {
+ markdown.skipLine();
}
}
- 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) {
+ case ' ', '\t' -> {
+ if (textKind == DocTree.Kind.MARKDOWN && textStart == -1) {
textStart = bp;
- lastNonWhite = -1;
}
- break;
+ nextChar();
+ }
- case '{':
- inlineTag(trees);
- break;
- case '@':
+ case '&' -> {
+ switch (textKind) {
+ case MARKDOWN -> defaultContentCharacter();
+ case TEXT -> entity(trees);
+ default -> throw unknownTextKind(textKind);
+ }
+ }
+
+ case '<' -> {
+ switch (textKind) {
+ case MARKDOWN -> {
+ defaultContentCharacter();
+ }
+ case TEXT -> {
+ newline = false;
+ if (isHtmlFile) {
+ 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;
+ }
+ }
+ case BODY -> {
+ if (isEndBody()) {
+ addPendingText(trees, lastNonWhite);
+ break loop;
+ }
+ }
+ default -> { }
+ }
+ }
+ 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;
+ }
+ }
+ default -> throw unknownTextKind(textKind);
+ }
+ }
+
+ case '{' -> {
+ switch (phase) {
+ case PREAMBLE, POSTAMBLE -> defaultContentCharacter();
+ case BODY -> inlineTag(trees);
+ case INLINE -> {
+ if (!inlineTag(trees)) {
+ depth++;
+ }
+ }
+ }
+ }
+
+ case '}' -> {
+ if (phase == Phase.INLINE) {
+ newline = false;
+ if (--depth == 0) {
+ addPendingText(trees, bp - 1);
+ nextChar();
+ return trees.toList();
+ }
+ nextChar();
+ } else {
+ defaultContentCharacter();
+ }
+ }
+
+ case '@' -> {
// check for context-sensitive escape sequences:
// newline whitespace @@
// newline whitespace @*
@@ -236,7 +401,7 @@ public class DocCommentParser {
nextChar();
textStart = bp;
break;
- } else {
+ } else if (phase == Phase.BODY) {
addPendingText(trees, lastNonWhite);
break loop;
}
@@ -249,21 +414,82 @@ public class DocCommentParser {
textStart = bp;
break;
}
- // fallthrough
+ defaultContentCharacter();
+ }
- default:
- newline = false;
- if (textStart == -1)
- textStart = bp;
- lastNonWhite = bp;
- nextChar();
+ case '\\' -> {
+ switch (textKind) {
+ case MARKDOWN -> {
+ defaultContentCharacter();
+ nextChar();
+ defaultContentCharacter();
+ }
+ case TEXT -> {
+ defaultContentCharacter();
+ }
+ }
+ }
+
+ case '`', '~' -> {
+ switch (textKind) {
+ case MARKDOWN -> {
+ newline = false;
+ if (textStart == -1) {
+ textStart = bp;
+ }
+ lastNonWhite = bp;
+ var saveNewline = newline;
+ var isCodeFence = markdown.isCodeFence();
+ if (ch == '`' || ch == '~' && isCodeFence) {
+ int end = markdown.skipCode();
+ if (end == -1) {
+ // if a match for the opening sequence of characters was not found:
+ // - if the characters were a fence, the code block extends to the end of file
+ // - if the characters were inline, the opening characters are treated literally
+ if (isCodeFence) {
+ lastNonWhite = buflen - 1;
+ } else {
+ bp = lastNonWhite;
+ newline = saveNewline;
+ nextChar();
+ }
+ } else {
+ lastNonWhite = end - 1;
+ }
+ } else {
+ nextChar();
+ }
+ }
+ case TEXT -> {
+ defaultContentCharacter();
+ }
+ }
+ }
+
+ default -> {
+ defaultContentCharacter();
+ }
}
}
if (lastNonWhite != -1)
addPendingText(trees, lastNonWhite);
- return trees.toList();
+ return (phase == Phase.INLINE)
+ ? List.of(erroneous("dc.unterminated.inline.tag", pos))
+ : trees.toList();
+ }
+
+ void defaultContentCharacter() {
+ newline = false;
+ if (textStart == -1)
+ textStart = bp;
+ lastNonWhite = bp;
+ nextChar();
+ }
+
+ private IllegalStateException unknownTextKind(DocTree.Kind textKind) {
+ return new IllegalStateException(textKind.toString());
}
/**
@@ -312,7 +538,40 @@ public class DocCommentParser {
}
}
- protected void inlineTag(ListBuffer list) {
+ // unused, but useful when debugging
+ private String showPos(int p) {
+ var sb = new StringBuilder();
+ sb.append("[").append(p).append("] ");
+ if (p >= 0) {
+ for (int i = Math.max(p - 10, 0); i < Math.min(p + 10, buflen); i++) {
+ if (i == p) sb.append("[");
+ var c = buf[i];
+ sb.append(switch (c) {
+ case '\n' -> '|';
+ case ' ' -> '_';
+ default -> c;
+ });
+ if (i == p) sb.append("]");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Reads a possible inline tag, after finding an opening brace { character.
+ *
+ * If the next character is {@code @}, an opening tag is read and added to the
+ * given {@code list}, and the result is {@code true}.
+ *
+ * Otherwise, the {@code list} is updated with the characters that have been read,
+ * and the result is {@code false}. The result also indicates that a single
+ * opening brace was read, and that a corresponding closing brace should eventually
+ * be read.
+ *
+ * @param list the list of trees being accumulated
+ * @return {@code true} if an inline tag was read, and {@code false} otherwise
+ */
+ protected boolean inlineTag(ListBuffer list) {
newline = false;
nextChar();
if (ch == '@') {
@@ -333,12 +592,14 @@ public class DocCommentParser {
list.add(inlineTag());
textStart = bp;
lastNonWhite = -1;
+ return true;
}
} else {
if (textStart == -1)
textStart = bp - 1;
lastNonWhite = bp;
}
+ return false;
}
/**
@@ -586,97 +847,9 @@ public class DocCommentParser {
* 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.
*/
- @SuppressWarnings("fallthrough")
private List inlineContent() {
- ListBuffer trees = new ListBuffer<>();
-
skipWhitespace();
- int pos = bp;
- int depth = 1;
- textStart = -1;
-
- loop:
- while (bp < buflen) {
-
- switch (ch) {
- case '\n': case '\r': case '\f':
- case ' ': case '\t':
- nextChar();
- break;
-
- case '&':
- entity(trees);
- break;
-
- case '<':
- newline = false;
- addPendingText(trees, bp - 1);
- trees.add(html());
- textStart = bp;
- lastNonWhite = -1;
- break;
-
- case '{':
- if (textStart == -1)
- textStart = bp;
- newline = false;
- nextChar();
- if (ch == '@') {
- addPendingText(trees, bp - 2);
- trees.add(inlineTag());
- textStart = bp;
- lastNonWhite = -1;
- } else {
- depth++;
- }
- break;
-
- case '}':
- newline = false;
- if (--depth == 0) {
- addPendingText(trees, bp - 1);
- nextChar();
- return trees.toList();
- }
- nextChar();
- break;
-
- case '@':
- // check for context-sensitive escape sequences:
- // newline whitespace @@
- // newline whitespace @*
- // *@/
- if (newline) {
- char peek = peekChar();
- if (peek == '@' || peek == '*') {
- addPendingText(trees, bp - 1);
- nextChar();
- trees.add(m.at(bp - 1).newEscapeTree(ch));
- newline = false;
- nextChar();
- textStart = bp;
- break;
- }
- } else if (textStart != -1 && buf[bp - 1] == '*' && peekChar() == '/') {
- addPendingText(trees, bp - 1);
- nextChar();
- trees.add(m.at(bp - 1).newEscapeTree('/'));
- newline = false;
- nextChar();
- textStart = bp;
- break;
- }
- // fallthrough
-
- default:
- if (textStart == -1)
- textStart = bp;
- nextChar();
- break;
- }
- }
-
- return List.of(erroneous("dc.unterminated.inline.tag", pos));
+ return content(Phase.INLINE);
}
protected void entity(ListBuffer list) {
@@ -845,8 +1018,15 @@ public class DocCommentParser {
}
/**
- * Read the start or end of an HTML tag, or an HTML comment
- * {@literal } or {@literal }
+ * Reads an HTML construct, beginning with {@code <}.
+ *
+ *
+ * - start element: {@code }
+ *
- end element: {@code
}
+ * - comment: {@code }
+ *
- doctype: {@code }
+ *
- cdata: {@code }
+ *
*/
private DCTree html() {
int p = bp;
@@ -983,7 +1163,7 @@ public class DocCommentParser {
}
attrValueChar(v);
}
- addPendingText(v, bp - 1);
+ addPendingText(v, bp - 1, DocTree.Kind.TEXT);
nextChar();
} else {
vkind = ValueKind.UNQUOTED;
@@ -991,7 +1171,7 @@ public class DocCommentParser {
while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
attrValueChar(v);
}
- addPendingText(v, bp - 1);
+ addPendingText(v, bp - 1, DocTree.Kind.TEXT);
}
skipWhitespace();
value = v.toList();
@@ -1012,9 +1192,20 @@ public class DocCommentParser {
}
protected void addPendingText(ListBuffer list, int textEnd) {
+ addPendingText(list, textEnd, textKind);
+ }
+
+ protected void addPendingText(ListBuffer list, int textEnd, DocTree.Kind kind) {
if (textStart != -1) {
if (textStart <= textEnd) {
- list.add(m.at(textStart).newTextTree(newString(textStart, textEnd + 1)));
+ switch (kind) {
+ case TEXT ->
+ list.add(m.at(textStart).newTextTree(newString(textStart, textEnd + 1)));
+ case MARKDOWN ->
+ list.add(m.at(textStart).newRawTextTree(DocTree.Kind.MARKDOWN, newString(textStart, textEnd + 1)));
+ default ->
+ throw new IllegalArgumentException(kind.toString());
+ }
}
textStart = -1;
}
@@ -1124,6 +1315,456 @@ public class DocCommentParser {
return names.fromChars(buf, pos, bp - pos);
}
+ /**
+ * A class to encapsulate the work to parse Markdown enough to
+ * detect the boundaries of character sequences of literal text,
+ * and to skip over such sequences, without analyzing the content.
+ *
+ * The primary factors are:
+ * - the content of the current line, including its indentation
+ * - the currently open set of container blocks, and any leaf block
+ * - a serial number that is incremented when a line is encountered
+ * that is not a continuation of the previous block.
+ *
+ * There are some limitations when using code blocks containing
+ * strings that resemble tags, which should be treated as literal text.
+ * In particular, list items are parsed individually and not as
+ * part of an overall group of list items. This means that the
+ * indentation is determined _for each item_, and not for the group
+ * as a whole. This in turn implies that a list item cannot begin
+ * with an indented code block. The workaround is to use a fenced code
+ * block. It is also recommended, as a matter of style, that all
+ * list items should use the same relative indentation for their content.
+ *
+ * The restrictions only apply when indented code blocks contain
+ * strings resembling tags.
+ *
+ * @see CommonMark spec
+ */
+ class Markdown {
+ // Container blocks may nested contained blocks and leaf blocks.
+ // Note, the code does not model lists, which would require
+ // arbitrary multi-line lookahead.
+ // https://spec.commonmark.org/0.30/#container-blocks
+ private enum ContainerBlockKind {
+ LIST_ITEM, QUOTE
+ }
+
+ /**
+ * Details about a currently open container block.
+ * @param blockKind the kind of block
+ * @param indent the indentation for the block's content, for list items
+ */
+ private record BlockInfo(ContainerBlockKind blockKind, int indent) { }
+
+ /**
+ * A list of the currently open container blocks.
+ */
+ private final java.util.List containers = new ArrayList<>();
+
+
+ // Except for NONE, leaf blocks contain "textual" content.
+ // Single-line leaf blocks, like ATX headings and thematic breaks are all represented by NONE.
+ // See https://spec.commonmark.org/0.30/#leaf-blocks
+ private enum LeafBlockKind {
+ NONE, PARAGRAPH, FENCED_CODE, INDENTED_CODE
+ }
+
+ private LeafBlockKind leafKind = LeafBlockKind.NONE;
+
+ /**
+ * A serial number for the current "block".
+ * It is updated whenever a line is encountered that indicates
+ * the beginning of a new block, such that any potential code span
+ * should be terminated as "not a span".
+ */
+ private int blockId = 0;
+
+ /**
+ * Updates the state after a newline has been read.
+ * There are two primary goals.
+ *
+ * 1. Determine any change to the current list of container blocks
+ * 2. Determine if the current line is part of a code block
+ */
+ void update() {
+ var prevLeafKind = leafKind;
+ int indent = readIndent(0);
+
+ final var line = peekLine();
+ var peekIndex = 0; // index into `line`; note it does not include `indent`
+ var blockIndex = 0; // index into `blocks`
+
+ // Iterate examining the beginning of the line, left to right,
+ // comparing indications of containers (indentation or markers)
+ // against the list of currently open container blocks.
+ // Side effects may open nested blocks or close existing ones.
+ while (true) {
+ if (blockIndex == containers.size()) {
+ // check for an open code block
+ if (prevLeafKind == LeafBlockKind.FENCED_CODE) {
+ return;
+ } else {
+ var codeIndent = (containers.isEmpty() ? 0 : containers.getLast().indent) + 4;
+ if (indent >= codeIndent && prevLeafKind != LeafBlockKind.PARAGRAPH) {
+ leafKind = LeafBlockKind.INDENTED_CODE;
+ if (leafKind != prevLeafKind) {
+ blockId++;
+ }
+ return;
+ }
+ }
+ // examine the remaining part of the lne
+ var peekLineKind = getLineKind(line.substring(peekIndex));
+ switch (peekLineKind) {
+ case BULLETED_LIST_ITEM, ORDERED_LIST_ITEM -> {
+ var count = indent;
+ // skip over the list marker
+ while (ch != ' ' && ch != '\t') {
+ count++;
+ nextChar();
+ }
+ var listItemIndent = readIndent(count);
+ containers.add(new BlockInfo(ContainerBlockKind.LIST_ITEM, listItemIndent));
+ blockIndex++;
+ peekIndex = listItemIndent - indent;
+ blockId++;
+ }
+
+ case BLOCK_QUOTE -> {
+ containers.add(new BlockInfo(ContainerBlockKind.QUOTE, indent + 1));
+ blockIndex++;
+ peekIndex += 1;
+ blockId++;
+ }
+
+ case OTHER -> {
+ leafKind = LeafBlockKind.PARAGRAPH;
+ return;
+ }
+
+ case ATX_HEADER, SETEXT_UNDERLINE, THEMATIC_BREAK, BLANK -> {
+ leafKind = LeafBlockKind.NONE;
+ blockId++;
+ return;
+ }
+
+ case CODE_FENCE -> {
+ leafKind = LeafBlockKind.FENCED_CODE;
+ blockId++;
+ return;
+ }
+ }
+ } else {
+ var block = containers.get(blockIndex);
+ var blockKind = block.blockKind;
+ switch (blockKind) {
+ case LIST_ITEM -> {
+ if (indent >= block.indent) {
+ blockIndex++;
+ } else {
+ var peekLineKind = getLineKind(line.substring(peekIndex));
+ if (peekLineKind == LineKind.BLANK) {
+ // blank lines are considered to be part of a list item
+ blockIndex++;
+ } else if (peekLineKind == LineKind.OTHER && prevLeafKind == LeafBlockKind.PARAGRAPH) {
+ // lazy continuation line: leaf kind and id are unchanged
+ return;
+ } else {
+ closeContainer(blockIndex);
+ blockId++;
+ }
+ }
+ }
+
+ case QUOTE -> {
+ var peekLineKind = getLineKind(line.substring(peekIndex));
+ if (peekLineKind == LineKind.BLOCK_QUOTE) {
+ blockIndex++;
+ peekIndex++;
+ } else if (peekLineKind == LineKind.OTHER && prevLeafKind == LeafBlockKind.PARAGRAPH) {
+ // lazy continuation line: leaf kind and id are unchanged
+ return;
+ } else {
+ closeContainer(blockIndex);
+ blockId++;
+ }
+ }
+
+ default -> throw new IllegalStateException(blockKind.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * {@return {@code true} if the current line is part of an indented code block,
+ * and {@code false} otherwise}
+ * Indented code blocks should be treated as literal text and not scanned for tags.
+ */
+ boolean isIndentedCodeBlock() {
+ return leafKind == LeafBlockKind.INDENTED_CODE;
+ }
+
+ /**
+ * {@return {@code true} if the current line is part of a fenced code block,
+ * and {@code false} otherwise}
+ * Code fences are used to surround content that should be treated as literal text
+ * and not scanned for tags.
+ */
+ boolean isCodeFence() {
+ return leafKind == LeafBlockKind.FENCED_CODE;
+ }
+
+ /**
+ * Closes a given container block, and any open nested blocks.
+ *
+ * @param index the index of the block to be closed
+ */
+ private void closeContainer(int index) {
+ containers.subList(index, containers.size()).clear();
+ }
+
+ /**
+ * Skips the content of the current line.
+ */
+ void skipLine() {
+ while (bp < buflen) {
+ if (ch == '\n' || ch == '\r') {
+ return;
+ }
+ nextChar();
+ }
+ }
+
+ /**
+ * Skips literal content.
+ *
+ * The ending delimiter is a function of the repetition count of the current
+ * character, and whether this is fenced content or not.
+ *
+ * In fenced content, the ending delimiter must appear at the start of a line;
+ * the code block may also be terminated implicitly by the end of a containing block.
+ *
+ * In other content, the scan may be terminated if the blockId changes,
+ * meaning the ending delimiter was not found before the block ended.
+ *
+ * @return the current position, or {@code -1} to indicate that the ending
+ * delimiter was not found
+ */
+ int skipCode() {
+ char term = ch;
+ var count = count(term);
+ var initialLeafKind = leafKind;
+ var isFenced = (leafKind == LeafBlockKind.FENCED_CODE);
+ var initialBlockId = blockId;
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n', '\r' -> {
+ nextChar();
+ update();
+ if (isFenced) {
+ if (leafKind == LeafBlockKind.FENCED_CODE && ch == term && count(ch) >= count
+ || blockId != initialBlockId) {
+ leafKind = LeafBlockKind.NONE;
+ return bp;
+ }
+ } else {
+ if (blockId != initialBlockId) {
+ return -1;
+ }
+ }
+ }
+
+ default -> {
+ if (ch == term && initialLeafKind != LeafBlockKind.FENCED_CODE ) {
+ if (count(ch) == count) {
+ return bp;
+ }
+ }
+ nextChar();
+ }
+ }
+ }
+ // found end of input
+ return -1;
+ }
+
+ /**
+ * Reads spaces and tabs, expanding tabs as if they were replaced by spaces
+ * with a tab stop of 4 characters.
+ *
+ * @param initialCount the initial indentation
+ * @return the indentation after skipping spaces and tabs
+ */
+ private int readIndent(int initialCount) {
+ final int TABSTOP = 4;
+ int indent = initialCount;
+ while (bp < buflen) {
+ switch (ch) {
+ case ' ' -> indent++;
+ case '\t' -> indent += TABSTOP - indent % TABSTOP;
+ default -> {
+ return indent;
+ }
+ }
+ nextChar();
+ }
+ return indent;
+ }
+
+ /**
+ * Matches a string against an ordered series of regular expressions,
+ * to determine the "kind" of the string.
+ *
+ * @return the "kind" of the line
+ */
+ private LineKind getLineKind(String s) {
+ if (s.isBlank()) {
+ return LineKind.BLANK;
+ }
+
+ switch (s.charAt(0)) {
+ case '#', '=', '-', '+', '*', '_', '`', '~', '>',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> {
+ for (LineKind lk : LineKind.values()) {
+ if (lk.pattern.matcher(s).matches()) {
+ return lk;
+ }
+ }
+ }
+ }
+
+ return LineKind.OTHER;
+ }
+
+ /**
+ * {@return a string obtained by peeking ahead at the current line}
+ * The current position is unchanged.
+ */
+ private String peekLine() {
+ int p = bp;
+ while (p < buflen) {
+ switch (buf[p]) {
+ case '\n', '\r' -> {
+ return newString(bp, p);
+ }
+ default -> p++;
+ }
+ }
+ return newString(bp, buflen);
+ }
+
+ /**
+ * {@return the number of consecutive occurrences of the given character}
+ *
+ * @param c the character
+ */
+ private int count(char c) {
+ int n = 1;
+ nextChar();
+ while (bp < buflen && ch == c) {
+ n++;
+ nextChar();
+ }
+ return n;
+ }
+
+ // unused, but useful when debugging
+ @Override
+ public String toString() {
+ return getClass().getSimpleName()
+ + "[containers:" + containers
+ + ", leafKind:" + leafKind
+ + ", blockId:" + blockId
+ + "]";
+ }
+ }
+
+ /**
+ * The kinds of line, used when reading a Markdown documentation comment.
+ * The values are loosely ordered by a combination of the specificity of
+ * the associated pattern, and the likelihood of use in a doc comment.
+ *
+ * Note that some of the patterns overlap, such as SETEXT_UNDERLINE and
+ * THEMATIC_BREAK. The CommonMark spec resolves the ambiguities with
+ * priority rules, such as the following:
+ *
+ * * If a line of dashes that meets the above conditions for being a
+ * thematic break could also be interpreted as the underline of a
+ * setext heading, the interpretation as a setext heading takes precedence.
+ *
+ * * When both a thematic break and a list item are possible interpretations
+ * of a line, the thematic break takes precedence.
+ *
+ * Here in this context, the primary purpose of these kinds is to help
+ * determine when a block boundary terminates literal text, such as a
+ * code span, and so the exact nature of the line kind does not matter.
+ */
+ // This class is only used within the Markdown class, and could be
+ // a private nested class there.
+ enum LineKind {
+ BLANK(Pattern.compile("[ \t]*")),
+
+ /**
+ * ATX header: starts with 1 to 6 # characters, followed by space or tab or end of line.
+ * @see ATX Headings
+ */
+ ATX_HEADER(Pattern.compile("#{1,6}([ \t].*|$)")),
+
+ /**
+ * Setext header: underline is sequence of = or - followed by optional spaces and tabs.
+ * @see Setext Headings
+ */
+ SETEXT_UNDERLINE(Pattern.compile("(=+|-+)[ \t]*")),
+
+ /**
+ * Thematic break: a line of * - _ interspersed with optional spaces and tabs
+ * @see Thematic Break
+ */
+ THEMATIC_BREAK(Pattern.compile("((\\*[ \t]*+){3,})|((-[ \t]*+){3,})|((_[ \t]*+){3,})")),
+
+ /**
+ * Code fence: 3 or more back ticks or tildes; back tick fence cannot have back ticks
+ * in the info string.
+ * Note potential conflict with strikeout for similar reasons if strikeout is supported.
+ * @see Code Fence
+ */
+ CODE_FENCE(Pattern.compile("(`{3,}[^`]*+)|(~{3,}.*+)")),
+
+ /**
+ * Bullet list item: * + - followed by at least one space or tab
+ * @see List items
+ */
+ BULLETED_LIST_ITEM(Pattern.compile("[-+*][ \t].*")),
+
+ /**
+ * Ordered list item: a sequence of 1-9 arabic digits (0-9), followed by
+ * either a . character or a ), followed by at least one space or tab
+ * @see List items
+ */
+ ORDERED_LIST_ITEM(Pattern.compile("[0-9]{1,9}[.)][ \t].*")),
+
+ /**
+ * Block quote: >
+ * @see Block quotes
+ */
+ BLOCK_QUOTE(Pattern.compile(">.*")),
+
+ /**
+ * Everything else...
+ *
+ * This entry must come last, since it matches "none of the above".
+ */
+ OTHER(Pattern.compile(".*"));
+
+ LineKind(Pattern p) {
+ this.pattern = p;
+ }
+
+ final Pattern pattern;
+ }
+
protected boolean isDecimalDigit(char ch) {
return ('0' <= ch && ch <= '9');
}
@@ -1559,7 +2200,7 @@ public class DocCommentParser {
while (bp < buflen && ch != quote) {
nextChar();
}
- addPendingText(v, bp - 1);
+ addPendingText(v, bp - 1, DocTree.Kind.TEXT);
nextChar();
} else {
vkind = ValueKind.UNQUOTED;
@@ -1568,7 +2209,7 @@ public class DocCommentParser {
while (bp < buflen && (ch != '}' && ch != ':' && !isUnquotedAttrValueTerminator(ch))) {
nextChar();
}
- addPendingText(v, bp - 1);
+ addPendingText(v, bp - 1, DocTree.Kind.TEXT);
}
skipWhitespace();
value = v.toList();
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java
index 403cb30921a..a38658a315e 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java
@@ -76,6 +76,11 @@ public class JavaTokenizer extends UnicodeReader {
*/
private final Preview preview;
+ /**
+ * Whether "///" comments are recognized as documentation comments.
+ */
+ protected final boolean enableLineDocComments;
+
/**
* The log to be used for error reporting. Copied from scanner factory.
*/
@@ -163,6 +168,7 @@ public class JavaTokenizer extends UnicodeReader {
this.tokens = fac.tokens;
this.source = fac.source;
this.preview = fac.preview;
+ this.enableLineDocComments = fac.enableLineDocComments;
this.lint = fac.lint;
this.sb = new StringBuilder(256);
}
@@ -918,10 +924,22 @@ public class JavaTokenizer extends UnicodeReader {
next();
if (accept('/')) { // (Spec. 3.7)
- skipToEOLN();
+ if (enableLineDocComments && accept('/')) { // JavaDoc line comment
+ int endPos;
+ do {
+ skipToEOLN();
+ endPos = position();
+ skipLineTerminator();
+ skipWhitespace();
+ } while (accept("///"));
- if (isAvailable()) {
- comments = appendComment(comments, processComment(pos, position(), CommentStyle.LINE));
+ comments = appendComment(comments, processComment(pos, endPos, CommentStyle.JAVADOC_LINE));
+ } else {
+ skipToEOLN();
+
+ if (isAvailable()) {
+ comments = appendComment(comments, processComment(pos, position(), CommentStyle.LINE));
+ }
}
break;
} else if (accept('*')) { // (Spec. 3.7)
@@ -929,7 +947,7 @@ public class JavaTokenizer extends UnicodeReader {
CommentStyle style;
if (accept('*')) {
- style = CommentStyle.JAVADOC;
+ style = CommentStyle.JAVADOC_BLOCK;
if (is('/')) {
isEmpty = true;
@@ -1207,7 +1225,7 @@ public class JavaTokenizer extends UnicodeReader {
/**
* Style of comment
*/
- CommentStyle cs;
+ final CommentStyle cs;
DiagnosticPosition pos;
@@ -1312,7 +1330,7 @@ public class JavaTokenizer extends UnicodeReader {
}
/**
- * Trim the first part of the JavaDoc comment.
+ * Trim the first part of the JavaDoc block comment.
*
* @param line line reader
*
@@ -1334,6 +1352,49 @@ public class JavaTokenizer extends UnicodeReader {
return line;
}
+ /**
+ * Determine how much indent to remove from a JavaDoc line comment.
+ *
+ * @return minimum indent to remove
+ */
+ int getJavadocLineCommentIndent() {
+ int result = Integer.MAX_VALUE;
+ UnicodeReader fullReader = lineReader(position(), position() + length());
+
+ while (fullReader.isAvailable()) {
+ UnicodeReader line = fullReader.lineReader();
+ line.skipWhitespace();
+ line.accept("///");
+ int pos = line.position();
+ line.skipWhitespace();
+
+ if (line.isAvailable()) {
+ result = Integer.min(result, line.position() - pos);
+ }
+ }
+
+ return result == Integer.MAX_VALUE ? 0 : result;
+ }
+
+ /**
+ * Trim the first part of a JavaDoc line comment.
+ *
+ * @param indent how much indentation to remove
+ * @param line line reader
+ *
+ * @return modified line reader
+ */
+ UnicodeReader trimJavadocLineComment(UnicodeReader line, int indent) {
+ line.skipWhitespace();
+ line.accept("///");
+
+ for (int i = 0; line.isAvailable() && i < indent; i++) {
+ line.next();
+ }
+
+ return line;
+ }
+
/**
* Put the line into the buffer.
*
@@ -1350,39 +1411,52 @@ public class JavaTokenizer extends UnicodeReader {
if (!scanned) {
deprecatedFlag = false;
scanned = true;
+ CommentStyle style;
+ int indent = 0;
+ int start = position();
- if (!accept("/**")) {
+ if (accept("/**")) {
+ style = CommentStyle.JAVADOC_BLOCK;
+ if (skip('*') != 0 && is('/')) {
+ return ;
+ }
+
+ skipWhitespace();
+
+ if (isEOLN()) {
+ accept('\r');
+ accept('\n');
+ }
+ } else if (accept("///")) {
+ style = CommentStyle.JAVADOC_LINE;
+ reset(start);
+ indent = getJavadocLineCommentIndent();
+ } else {
return;
}
- if (skip('*') != 0 && is('/')) {
- return ;
- }
-
- skipWhitespace();
-
- if (isEOLN()) {
- accept('\r');
- accept('\n');
- }
-
while (isAvailable()) {
UnicodeReader line = lineReader();
- line = trimJavadocComment(line);
+ line = (style == CommentStyle.JAVADOC_LINE)
+ ? trimJavadocLineComment(line, indent)
+ : trimJavadocComment(line);
- // If standalone @deprecated tag
- int pos = line.position();
- line.skipWhitespace();
+ if (cs == CommentStyle.JAVADOC_BLOCK) {
+ // If standalone @deprecated tag
+ int pos = line.position();
+ line.skipWhitespace();
- if (line.accept("@deprecated") &&
- (!line.isAvailable() ||
- line.isWhitespace() ||
- line.isEOLN() ||
- line.get() == EOI)) {
- deprecatedFlag = true;
+ if (line.accept("@deprecated") &&
+ (!line.isAvailable() ||
+ line.isWhitespace() ||
+ line.isEOLN() ||
+ line.get() == EOI)) {
+ deprecatedFlag = true;
+ }
+
+ line.reset(pos);
}
- line.reset(pos);
putLine(line);
}
}
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/LazyDocCommentTable.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/LazyDocCommentTable.java
index 23966c7bd9a..7fcd46d38a5 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/LazyDocCommentTable.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/LazyDocCommentTable.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2024, 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,8 +28,11 @@ package com.sun.tools.javac.parser;
import java.util.HashMap;
import java.util.Map;
+import javax.lang.model.util.Elements.DocCommentKind;
+
+import com.sun.source.doctree.DocCommentTree;
+
import com.sun.tools.javac.parser.Tokens.Comment;
-import com.sun.tools.javac.tree.DCTree.DCDocComment;
import com.sun.tools.javac.tree.DocCommentTable;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.DiagnosticSource;
@@ -44,7 +47,7 @@ import com.sun.tools.javac.util.DiagnosticSource;
public class LazyDocCommentTable implements DocCommentTable {
private static class Entry {
final Comment comment;
- DCDocComment tree;
+ DocCommentTree tree;
Entry(Comment c) {
comment = c;
@@ -72,6 +75,16 @@ public class LazyDocCommentTable implements DocCommentTable {
return (e == null) ? null : e.comment;
}
+ @Override
+ public DocCommentKind getCommentKind(JCTree tree) {
+ Comment c = getComment(tree);
+ return (c == null) ? null : switch (c.getStyle()) {
+ case JAVADOC_BLOCK -> DocCommentKind.TRADITIONAL;
+ case JAVADOC_LINE -> DocCommentKind.END_OF_LINE;
+ default -> throw new IllegalStateException(c.getStyle().toString());
+ };
+ }
+
@Override
public String getCommentText(JCTree tree) {
Comment c = getComment(tree);
@@ -79,12 +92,14 @@ public class LazyDocCommentTable implements DocCommentTable {
}
@Override
- public DCDocComment getCommentTree(JCTree tree) {
+ public DocCommentTree getCommentTree(JCTree tree) {
Entry e = table.get(tree);
- if (e == null)
+ if (e == null) {
return null;
- if (e.tree == null)
- e.tree = new DocCommentParser(fac, diagSource, e.comment).parse();
+ }
+ if (e.tree == null) {
+ e.tree = fac.getTrees().getDocCommentTree(diagSource, e.comment);
+ }
return e.tree;
}
@@ -92,5 +107,4 @@ public class LazyDocCommentTable implements DocCommentTable {
public void putComment(JCTree tree, Comment c) {
table.put(tree, new Entry(c));
}
-
}
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ParserFactory.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ParserFactory.java
index 3c07c382aa3..c873c6f31b7 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ParserFactory.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ParserFactory.java
@@ -27,6 +27,7 @@ package com.sun.tools.javac.parser;
import java.util.Locale;
+import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.DeferredLintHandler;
import com.sun.tools.javac.code.Lint;
import com.sun.tools.javac.code.Preview;
@@ -34,7 +35,6 @@ import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.tree.DocTreeMaker;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
-import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Names;
import com.sun.tools.javac.util.Options;
@@ -72,6 +72,8 @@ public class ParserFactory {
final Locale locale;
final DeferredLintHandler deferredLintHandler;
+ private final JavacTrees trees;
+
@SuppressWarnings("this-escape")
protected ParserFactory(Context context) {
super();
@@ -87,6 +89,7 @@ public class ParserFactory {
this.scannerFactory = ScannerFactory.instance(context);
this.locale = context.get(Locale.class);
this.deferredLintHandler = DeferredLintHandler.instance(context);
+ this.trees = JavacTrees.instance(context);
}
public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap) {
@@ -97,4 +100,8 @@ public class ParserFactory {
Lexer lexer = scannerFactory.newScanner(input, keepDocComments);
return new JavacParser(this, lexer, keepDocComments, keepLineMap, keepEndPos, parseModuleInfo);
}
+
+ public JavacTrees getTrees() {
+ return trees;
+ }
}
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Scanner.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Scanner.java
index a182c74b07b..a24e73a4141 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Scanner.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Scanner.java
@@ -125,7 +125,7 @@ public class Scanner implements Lexer {
if (token.comments != null) {
for (var c : token.comments) {
switch (c.getStyle()) {
- case JAVADOC -> {
+ case JAVADOC_BLOCK, JAVADOC_LINE -> {
docComments.add(c);
}
}
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java
index 90188eaafea..188fe838b18 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2024, 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
@@ -30,9 +30,11 @@ import java.nio.CharBuffer;
import com.sun.tools.javac.code.Lint;
import com.sun.tools.javac.code.Preview;
import com.sun.tools.javac.code.Source;
+import com.sun.tools.javac.main.Option;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Names;
+import com.sun.tools.javac.util.Options;
/**
@@ -61,6 +63,7 @@ public class ScannerFactory {
final Preview preview;
final Tokens tokens;
final Lint lint;
+ final boolean enableLineDocComments;
/** Create a new scanner factory. */
@SuppressWarnings("this-escape")
@@ -72,6 +75,8 @@ public class ScannerFactory {
this.preview = Preview.instance(context);
this.tokens = Tokens.instance(context);
this.lint = Lint.instance(context);
+ var options = Options.instance(context);
+ this.enableLineDocComments = !options.isSet(Option.DISABLE_LINE_DOC_COMMENTS);
}
public Scanner newScanner(CharSequence input, boolean keepDocComments) {
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java
index a05d48d320b..963b545b8b3 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java
@@ -274,9 +274,10 @@ public class Tokens {
public interface Comment {
enum CommentStyle {
- LINE, // Starting with //
- BLOCK, // starting with /*
- JAVADOC, // starting with /**
+ LINE, // starting with // -- also known in JLS as "end-of-line comment"
+ BLOCK, // starting with /* -- also known in JLS as "traditional comment"
+ JAVADOC_LINE, // starting with ///
+ JAVADOC_BLOCK // starting with /**
}
String getText();
@@ -360,7 +361,7 @@ public class Tokens {
* the last one is returned
*/
public Comment docComment() {
- List comments = getComments(Comment.CommentStyle.JAVADOC);
+ List comments = getDocComments();
return comments.isEmpty() ?
null :
comments.head;
@@ -371,7 +372,7 @@ public class Tokens {
* javadoc comment attached to this token contains the '@deprecated' string
*/
public boolean deprecatedFlag() {
- for (Comment c : getComments(Comment.CommentStyle.JAVADOC)) {
+ for (Comment c : getDocComments()) {
if (c.isDeprecated()) {
return true;
}
@@ -379,14 +380,15 @@ public class Tokens {
return false;
}
- private List getComments(Comment.CommentStyle style) {
+ private List getDocComments() {
if (comments == null) {
return List.nil();
} else {
ListBuffer buf = new ListBuffer<>();
for (Comment c : comments) {
- if (c.getStyle() == style) {
- buf.add(c);
+ Comment.CommentStyle style = c.getStyle();
+ switch (style) {
+ case JAVADOC_BLOCK, JAVADOC_LINE -> buf.add(c);
}
}
return buf.toList();
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties
index 0566272ca1b..2b48676e6b8 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties
@@ -381,6 +381,8 @@ javac.opt.inherit_runtime_environment=\
javac.opt.default.module.for.created.files=\
Fallback target module for files created by annotation processors,\n\
if none specified or inferred.
+javac.opt.lineDocComments=\
+ Disable support for documentation comments with lines beginning '///'
## messages
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 b1f9802354d..a999786f119 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, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2024, 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 abstract class DCTree implements DocTree {
}
switch (getKind()) {
+ case MARKDOWN -> {
+ DCRawText markdown = (DCRawText) this;
+ return markdown.pos + markdown.code.length();
+ }
+
case TEXT -> {
DCText text = (DCText) this;
return text.pos + text.text.length();
@@ -896,6 +901,39 @@ public abstract class DCTree implements DocTree {
}
}
+ public static class DCRawText extends DCTree implements RawTextTree {
+ public final Kind kind;
+ public final String code;
+
+ DCRawText(Kind kind, String code) {
+ if (kind != Kind.MARKDOWN) {
+ throw new IllegalArgumentException(String.valueOf(kind));
+ }
+ this.kind = kind;
+ 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.visitRawText(this, d);
+ }
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public String getContent() {
+ return code;
+ }
+ }
+
public static class DCReference extends DCEndPosTree implements ReferenceTree {
public final String signature;
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocCommentTable.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocCommentTable.java
index c5445624d40..9582aa213f9 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocCommentTable.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocCommentTable.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024, 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
@@ -25,9 +25,12 @@
package com.sun.tools.javac.tree;
+import javax.lang.model.util.Elements.DocCommentKind;
+
+import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.ErroneousTree;
+
import com.sun.tools.javac.parser.Tokens.Comment;
-import com.sun.tools.javac.tree.DCTree.DCDocComment;
/**
* A table giving the doc comment, if any, for any tree node.
@@ -39,29 +42,34 @@ import com.sun.tools.javac.tree.DCTree.DCDocComment;
*/
public interface DocCommentTable {
/**
- * Check if a tree node has a corresponding doc comment.
+ * Checks if a tree node has a corresponding doc comment.
*/
boolean hasComment(JCTree tree);
/**
- * Get the Comment token containing the doc comment, if any, for a tree node.
+ * Returns the Comment token containing the doc comment, if any, for a tree node.
*/
Comment getComment(JCTree tree);
/**
- * Get the plain text of the doc comment, if any, for a tree node.
+ * Returns the kind of the doc comment, if any, for a tree node.
+ */
+ DocCommentKind getCommentKind(JCTree tree);
+
+ /**
+ * Returns the plain text of the doc comment, if any, for a tree node.
*/
String getCommentText(JCTree tree);
/**
- * Get the parsed form of the doc comment as a DocTree. If any errors
+ * Returns the parsed form of the doc comment as a DocTree. If any errors
* are detected during parsing, they will be reported via
- * {@link ErroneousTree ErroneousTree} nodes within the resulting tree.
+ * {@link ErroneousTree} nodes within the resulting tree.
*/
- DCDocComment getCommentTree(JCTree tree);
+ DocCommentTree getCommentTree(JCTree tree);
/**
- * Set the Comment to be associated with a tree node.
+ * Sets the Comment to be associated with a tree node.
*/
void putComment(JCTree tree, Comment c);
}
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 e4f7cf096eb..aac2647db3c 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, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2024, 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
@@ -390,6 +390,16 @@ public class DocPretty implements DocTreeVisitor {
return null;
}
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitRawText(RawTextTree node, Void p) {
+ try {
+ print(node.getContent());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return null;
+ }
+
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitReference(ReferenceTree node, Void p) {
try {
@@ -627,8 +637,12 @@ public class DocPretty implements DocTreeVisitor {
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 c729b6ff832..77f6eefc073 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
@@ -30,6 +30,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.lang.model.element.Name;
import javax.tools.Diagnostic;
@@ -68,6 +70,7 @@ import com.sun.tools.javac.tree.DCTree.DCLiteral;
import com.sun.tools.javac.tree.DCTree.DCParam;
import com.sun.tools.javac.tree.DCTree.DCProvides;
import com.sun.tools.javac.tree.DCTree.DCReference;
+import com.sun.tools.javac.tree.DCTree.DCRawText;
import com.sun.tools.javac.tree.DCTree.DCReturn;
import com.sun.tools.javac.tree.DCTree.DCSee;
import com.sun.tools.javac.tree.DCTree.DCSerial;
@@ -211,12 +214,12 @@ public class DocTreeMaker implements DocTreeFactory {
lb.addAll(cast(fullBody));
List fBody = lb.toList();
- // A dummy comment to keep the diagnostics logic happy.
+ // A dummy comment that returns Position.NOPOS for any source position.
+ // A different solution would be to replace the Comment field
+ // in DCDocComment with a narrower type equivalent to Function
+ // so that here in this code we can just supply a lambda as follows:
+ // i -> Position.NOPOS
Comment c = new Comment() {
- @Override
- public String getText() {
- return null;
- }
@Override
public JCDiagnostic.DiagnosticPosition getPos() {
@@ -228,14 +231,19 @@ public class DocTreeMaker implements DocTreeFactory {
return Position.NOPOS;
}
+ @Override
+ public String getText() {
+ throw new UnsupportedOperationException(getClass() + ".getText");
+ }
+
@Override
public CommentStyle getStyle() {
- return CommentStyle.JAVADOC;
+ throw new UnsupportedOperationException(getClass() + ".getStyle");
}
@Override
public boolean isDeprecated() {
- return false;
+ throw new UnsupportedOperationException(getClass() + ".isDeprecated");
}
};
Pair, List> pair = splitBody(fullBody);
@@ -347,6 +355,13 @@ public class DocTreeMaker implements DocTreeFactory {
return tree;
}
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public DCRawText newRawTextTree(DocTree.Kind kind, String text) {
+ DCTree.DCRawText tree = new DCRawText(kind, text);
+ tree.pos = pos;
+ return tree;
+ }
+
@Override @DefinedBy(Api.COMPILER_TREE)
public DCParam newParamTree(boolean isTypeParameter, IdentifierTree name, List extends DocTree> description) {
DCParam tree = new DCParam(isTypeParameter, (DCIdentifier) name, cast(description));
@@ -574,34 +589,29 @@ public class DocTreeMaker implements DocTreeFactory {
foundFirstSentence = true;
}
- case TEXT -> {
- var dtPos = dt.pos;
- var s = ((DCText) dt).getBody();
- var peekedNext = iter.hasNext()
- ? alist.get(iter.nextIndex())
- : null;
- int sbreak = getSentenceBreak(s, peekedNext);
- if (sbreak > 0) {
- var fsPart = m.at(dtPos).newTextTree(s.substring(0, sbreak).stripTrailing());
+ case TEXT, MARKDOWN -> {
+ var peekedNext = iter.hasNext() ? alist.get(iter.nextIndex()) : null;
+ var content = getContent(dt);
+ int breakOffset = getSentenceBreak(dt.getKind(), content, peekedNext);
+ if (breakOffset > 0) {
+ // the end of sentence is within the current node;
+ // split it, skipping whitespace in between the two parts
+ var fsPart = newNode(dt.getKind(), dt.pos, content.substring(0, breakOffset).stripTrailing());
fs.add(fsPart);
- int offsetPos = skipWhiteSpace(s, sbreak);
- if (offsetPos > 0) {
- DCText bodyPart = m.at(dtPos + offsetPos).newTextTree(s.substring(offsetPos));
+ int wsOffset = skipWhiteSpace(content, breakOffset);
+ if (wsOffset > 0) {
+ var bodyPart = newNode(dt.getKind(), dt.pos + wsOffset, content.substring(wsOffset));
body.add(bodyPart);
}
foundFirstSentence = true;
- } else if (peekedNext != null) {
- // if the next doctree is a break, remove trailing spaces
- if (isSentenceBreak(peekedNext, false)) {
- DCTree next = iter.next();
- DCText fsPart = m.at(dtPos).newTextTree(s.stripTrailing());
- fs.add(fsPart);
- body.add(next);
- foundFirstSentence = true;
- } else {
- fs.add(dt);
- }
+ } else if (peekedNext != null && isSentenceBreak(peekedNext, false)) {
+ // the next node is a sentence break, so this is the end of the first sentence;
+ // remove trailing spaces
+ var fsPart = newNode(dt.getKind(), dt.pos, content.stripTrailing());
+ fs.add(fsPart);
+ foundFirstSentence = true;
} else {
+ // no sentence break found; keep scanning
fs.add(dt);
}
}
@@ -633,34 +643,20 @@ public class DocTreeMaker implements DocTreeFactory {
}
}
- /*
- * Computes the first sentence break, a simple dot-space algorithm.
- */
- private int defaultSentenceBreak(String s) {
- // scan for period followed by whitespace
- int period = -1;
- for (int i = 0; i < s.length(); i++) {
- switch (s.charAt(i)) {
- case '.':
- period = i;
- break;
+ private String getContent(DCTree dt) {
+ return switch (dt.getKind()) {
+ case TEXT -> ((DCText) dt).text;
+ case MARKDOWN -> ((DCRawText) dt).code;
+ default -> throw new IllegalArgumentException(dt.getKind().toString());
+ };
+ }
- case ' ':
- case '\f':
- case '\n':
- case '\r':
- case '\t':
- if (period >= 0) {
- return i;
- }
- break;
-
- default:
- period = -1;
- break;
- }
- }
- return -1;
+ private DCTree newNode(DocTree.Kind kind, int pos, String text) {
+ return switch (kind) {
+ case TEXT -> m.at(pos).newTextTree(text);
+ case MARKDOWN -> m.at(pos).newRawTextTree(kind, text);
+ default -> throw new IllegalArgumentException(kind.toString());
+ };
}
/*
@@ -683,36 +679,53 @@ public class DocTreeMaker implements DocTreeFactory {
* Therefore, we have to probe further to determine whether
* there really is a sentence break or not at the end of this run of text.
*/
- private int getSentenceBreak(String s, DCTree nextTree) {
+ private int getSentenceBreak(DocTree.Kind kind, String s, DCTree nextTree) {
BreakIterator breakIterator = m.trees.getBreakIterator();
if (breakIterator == null) {
- return defaultSentenceBreak(s);
+ return defaultSentenceBreak(kind, s);
}
- breakIterator.setText(s);
+
+ // If there is a paragraph break in a run of Markdown text, restrict the
+ // search to the first paragraph, to avoid beginning-of-line Markdown constructs
+ // confusing the sentence breaker.
+ String s2 = normalize(kind, kind == Kind.MARKDOWN ? firstParaText(s) : s);
+ breakIterator.setText(s2);
final int sbrk = breakIterator.next();
- // This is the last doctree, found the droid we are looking for
- if (nextTree == null) {
+
+ switch (kind) {
+ case MARKDOWN -> {
+ int endParaPos = endParaPos(s2);
+ if (endParaPos != -1) {
+ return Math.min(sbrk, endParaPos);
+ }
+ }
+ }
+
+ // If this is the last doctree, or if there was a paragraph break in a run
+ // of Markdown text, then we found the droid we are looking for
+ if (nextTree == null || kind == Kind.MARKDOWN && s2.length() < s.length()) {
return sbrk;
}
- // If the break is well within the span of the string ie. not
+ // If the break is well within the span of the string i.e. not
// at EOL, then we have a clear break.
if (sbrk < s.length() - 1) {
return sbrk;
}
- if (nextTree.getKind() == Kind.TEXT) {
- // Two adjacent text trees, a corner case, perhaps
- // produced by a tool synthesizing a doctree. In
- // this case, does the break lie within the first span,
- // then we have the droid, otherwise allow the callers
- // logic to handle the break in the adjacent doctree.
- TextTree ttnext = (TextTree) nextTree;
- String combined = s + ttnext.getBody();
- breakIterator.setText(combined);
- int sbrk2 = breakIterator.next();
- if (sbrk < sbrk2) {
- return sbrk;
+ switch (nextTree.getKind()) {
+ case TEXT, MARKDOWN -> {
+ // Two adjacent text trees, a corner case, perhaps
+ // produced by a tool synthesizing a doctree. In
+ // this case, does the break lie within the first span,
+ // then we have the droid, otherwise allow the callers
+ // logic to handle the break in the adjacent doctree.
+ String combined = s2 + normalize(nextTree.getKind(), getContent(nextTree));
+ breakIterator.setText(combined);
+ int sbrk2 = breakIterator.next();
+ if (sbrk < sbrk2) {
+ return sbrk;
+ }
}
}
@@ -733,6 +746,69 @@ public class DocTreeMaker implements DocTreeFactory {
return -1; // indeterminate at this time
}
+ /*
+ * Computes the first sentence break, a simple dot-space algorithm.
+ */
+ private int defaultSentenceBreak(DocTree.Kind kind, String s) {
+ String s2 = normalize(kind, s);
+
+ // scan for period followed by whitespace
+ int period = -1;
+ for (int i = 0; i < s2.length(); i++) {
+ switch (s2.charAt(i)) {
+ case '.':
+ period = i;
+ break;
+
+ case ' ':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ if (period >= 0) {
+ switch (kind) {
+ case MARKDOWN -> {
+ int endParaPos = endParaPos(s2);
+ return endParaPos == -1 || i < endParaPos ? i : endParaPos;
+ }
+ case TEXT -> {
+ return i;
+ }
+ default -> throw new IllegalArgumentException(kind.toString());
+ }
+ }
+ break;
+
+ default:
+ period = -1;
+ break;
+ }
+ }
+
+ return switch (kind) {
+ case MARKDOWN -> endParaPos(s2); // may be -1
+ case TEXT -> -1;
+ default -> throw new IllegalArgumentException(kind.toString());
+ };
+ }
+
+ // End of paragraph is newline, followed by a blank line or the beginning of the next block.
+ // - + * are list markers
+ // # = - are for headings
+ // - _ * are for thematic breaks
+ // > is for block quotes
+ private static final Pattern endPara = Pattern.compile("\n(([ \t]*\n)|( {0,3}[-+*#=_>]))");
+
+ private static int endParaPos(String s) {
+ Matcher m = endPara.matcher(s);
+ return m.find() ? m.start() : -1;
+ }
+
+ private static String firstParaText(String s) {
+ int endParaPos = endParaPos(s);
+ return endParaPos == -1 ? s : s.substring(0, endParaPos);
+ }
+
private boolean isSentenceBreak(DCTree dt, boolean isFirstDocTree) {
switch (dt.getKind()) {
case START_ELEMENT:
@@ -762,6 +838,75 @@ public class DocTreeMaker implements DocTreeFactory {
}
return -1;
}
+
+ private String normalize(DocTree.Kind kind, String s) {
+ return switch (kind) {
+ case TEXT -> s;
+ case MARKDOWN -> normalizeMarkdown(s);
+ default -> throw new IllegalArgumentException(kind.toString());
+ };
+ }
+
+ // Returns a string in which any periods that should not be considered
+ // as ending a sentence are replaced by dashes. This specifically
+ // includes periods in code spans and links.
+ private String normalizeMarkdown(String s) {
+ StringBuilder sb = new StringBuilder();
+ int slen = s.length();
+ int i = 0;
+ while (i < slen) {
+ char ch = s.charAt(i);
+ switch (ch) {
+ case '\\' -> {
+ sb.append(ch);
+ i++;
+ if (i < slen) {
+ sb.append(s.charAt(i));
+ i++;
+ }
+ }
+
+ case '<' -> i = skip(sb, s, i, ch, '>');
+ case '[' -> i = skip(sb, s, i, ch, ']');
+ case '(' -> i = skip(sb, s, i, ch, ')');
+
+ case '`' -> {
+ int start = i;
+ i++;
+ while (i < slen && s.charAt(i) == '`') {
+ i++;
+ }
+ String prefix = s.substring(start, i);
+ sb.append(prefix);
+ int j = s.indexOf(prefix, i);
+ if (j > i) {
+ sb.append(s.substring(i, j).replace('.', '-'));
+ sb.append(prefix);
+ i = j + prefix.length();
+ }
+ }
+
+ default -> {
+ sb.append(ch);
+ i++;
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private int skip(StringBuilder sb, String s, int i, char ch, char term) {
+ sb.append(ch);
+ i++;
+ int j = s.indexOf(term, i);
+ if (j != -1) {
+ sb.append(s.substring(i, j).replace('.', '-'));
+ return j;
+ } else {
+ return i;
+ }
+ }
}
}
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java
index 42cfd9ac675..54917c90dc2 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java
@@ -478,13 +478,6 @@ public class TreeInfo {
return (docComments == null) ? null : docComments.getCommentText(tree);
}
- public static DCTree.DCDocComment getCommentTree(Env> env, JCTree tree) {
- DocCommentTable docComments = (tree.hasTag(JCTree.Tag.TOPLEVEL))
- ? ((JCCompilationUnit) tree).docComments
- : env.toplevel.docComments;
- return (docComments == null) ? null : docComments.getCommentTree(tree);
- }
-
/** The position of the first statement in a block, or the position of
* the block itself if it is empty.
*/
diff --git a/src/jdk.compiler/share/classes/module-info.java b/src/jdk.compiler/share/classes/module-info.java
index 9b12171ef6d..63b5aa63e4c 100644
--- a/src/jdk.compiler/share/classes/module-info.java
+++ b/src/jdk.compiler/share/classes/module-info.java
@@ -233,7 +233,8 @@ module jdk.compiler {
jdk.javadoc;
exports com.sun.tools.javac.api to
jdk.javadoc,
- jdk.jshell;
+ jdk.jshell,
+ jdk.internal.md;
exports com.sun.tools.javac.resources to
jdk.jshell;
exports com.sun.tools.javac.code to
@@ -253,17 +254,20 @@ module jdk.compiler {
exports com.sun.tools.javac.model to
jdk.javadoc;
exports com.sun.tools.javac.parser to
- jdk.jshell;
+ jdk.jshell,
+ jdk.internal.md;
exports com.sun.tools.javac.platform to
jdk.jdeps,
jdk.javadoc;
exports com.sun.tools.javac.tree to
jdk.javadoc,
- jdk.jshell;
+ jdk.jshell,
+ jdk.internal.md;
exports com.sun.tools.javac.util to
jdk.jdeps,
jdk.javadoc,
- jdk.jshell;
+ jdk.jshell,
+ jdk.internal.md;
exports jdk.internal.shellsupport.doc to
jdk.jshell;
@@ -271,6 +275,7 @@ module jdk.compiler {
uses com.sun.source.util.Plugin;
uses com.sun.tools.doclint.DocLint;
uses com.sun.tools.javac.platform.PlatformProvider;
+ uses com.sun.tools.javac.api.JavacTrees.DocCommentTreeTransformer;
provides java.util.spi.ToolProvider with
com.sun.tools.javac.main.JavacToolProvider;
diff --git a/src/jdk.compiler/share/man/javac.1 b/src/jdk.compiler/share/man/javac.1
index 6586bcaf83f..de374e7d0c7 100644
--- a/src/jdk.compiler/share/man/javac.1
+++ b/src/jdk.compiler/share/man/javac.1
@@ -1,4 +1,4 @@
-.\" Copyright (c) 1994, 2023, Oracle and/or its affiliates. All rights reserved.
+.\" Copyright (c) 1994, 2024, 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
@@ -563,6 +563,9 @@ module.
Specifies the fallback target module for files created by annotation
processors, if none is specified or inferred.
.TP
+\f[V]--disable-line-doc-comments\f[R]
+Disables support for documentation comments with lines beginning ///.
+.TP
\f[V]-Djava.endorsed.dirs=\f[R]\f[I]dirs\f[R]
Overrides the location of the endorsed standards path.
.RS
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/markdown/MarkdownTransformer.java b/src/jdk.internal.md/share/classes/jdk/internal/markdown/MarkdownTransformer.java
new file mode 100644
index 00000000000..bd2521e4efa
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/markdown/MarkdownTransformer.java
@@ -0,0 +1,976 @@
+/*
+ * Copyright (c) 2023, 2024, 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 jdk.internal.markdown;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.MatchResult;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.DocTreeVisitor;
+import com.sun.source.doctree.RawTextTree;
+import com.sun.source.util.DocTreeScanner;
+import com.sun.source.util.DocTrees;
+
+import com.sun.tools.javac.api.JavacTrees;
+import com.sun.tools.javac.parser.ReferenceParser;
+import com.sun.tools.javac.tree.DCTree;
+import com.sun.tools.javac.tree.DocTreeMaker;
+import com.sun.tools.javac.util.DefinedBy;
+
+import jdk.internal.org.commonmark.ext.gfm.tables.TablesExtension;
+import jdk.internal.org.commonmark.internal.InlineParserImpl;
+import jdk.internal.org.commonmark.node.AbstractVisitor;
+import jdk.internal.org.commonmark.node.Link;
+import jdk.internal.org.commonmark.node.LinkReferenceDefinition;
+import jdk.internal.org.commonmark.node.Node;
+import jdk.internal.org.commonmark.node.Text;
+import jdk.internal.org.commonmark.parser.IncludeSourceSpans;
+import jdk.internal.org.commonmark.parser.InlineParser;
+import jdk.internal.org.commonmark.parser.InlineParserContext;
+import jdk.internal.org.commonmark.parser.InlineParserFactory;
+import jdk.internal.org.commonmark.parser.Parser;
+import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor;
+
+import static com.sun.tools.javac.util.Position.NOPOS;
+
+/**
+ * A class to transform a {@code DocTree} node into a similar one with
+ * doc-comment "extensions" translated into equivalent standard {@code DocTree} nodes.
+ *
+ * The primary extension is to allow references to program elements to be
+ * translated to {@code {@link ...}} or {@code {@linkplain ...}} tags.
+ */
+public class MarkdownTransformer implements JavacTrees.DocCommentTreeTransformer {
+
+ /**
+ * Public no-args constructor, suitable for use with {@link java.util.ServiceLoader}.
+ */
+ public MarkdownTransformer() { }
+
+ public String name() {
+ return "standard";
+ }
+
+ @Override @DefinedBy(DefinedBy.Api.COMPILER_TREE)
+ public DocCommentTree transform(DocTrees trees, DocCommentTree tree) {
+ if (!(trees instanceof JavacTrees t)) {
+ throw new IllegalArgumentException("class not supported: " + trees.getClass());
+ }
+ if (!(tree instanceof DCTree.DCDocComment dc)) {
+ throw new IllegalArgumentException("class not supported: " + tree.getClass());
+ }
+
+ return isMarkdown(dc) ? new DCTransformer(t).transform(dc) : dc;
+ }
+
+ private boolean isMarkdown(DocCommentTree node) {
+ return isMarkdownVisitor.visitDocComment(node, null);
+ }
+
+ /**
+ * A fast scanner for detecting Markdown nodes in documentation comment nodes.
+ * The scanner returns as soon as any Markdown node is found.
+ */
+ private static final DocTreeVisitor isMarkdownVisitor = new DocTreeScanner<>() {
+ @Override
+ public Boolean scan(Iterable extends DocTree> nodes, Void ignore) {
+ if (nodes != null) {
+ 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 static final char PLACEHOLDER = '\uFFFC'; // Unicode Object Replacement Character
+
+ private static class DCTransformer {
+ private final DocTreeMaker m;
+ private final ReferenceParser refParser;
+
+ // a dynamically generated scheme for the URLs of automatically generated references;
+ // to allow user-generated code URLs, change this to just "code:"
+ private final String autorefScheme = "code-" + Integer.toHexString(hashCode()) + ":";
+
+ DCTransformer(JavacTrees t) {
+ m = t.getDocTreeFactory();
+ refParser = new ReferenceParser(t.getParserFactory());
+ }
+
+ /**
+ * Transforms a doc tree node.
+ * This node dispatches to a more specific overload, based on the kind of the node.
+ * The result may be the same as the argument if no transformations were made.
+ *
+ * @param tree the tree node
+ * @return a tree with any "extensions" converted into tags
+ */
+ public DCTree transform(DCTree tree) {
+ // The following switch statement could eventually be converted to a
+ // pattern switch. It is intended to be a total switch, with a default
+ // to catch any omissions.
+ return switch (tree.getKind()) {
+ // The following cannot contain Markdown and so always transform to themselves.
+ case ATTRIBUTE,
+ CODE, COMMENT,
+ DOC_ROOT, DOC_TYPE,
+ END_ELEMENT, ENTITY, ERRONEOUS, ESCAPE,
+ IDENTIFIER, INHERIT_DOC,
+ LITERAL,
+ REFERENCE,
+ SNIPPET, START_ELEMENT, SYSTEM_PROPERTY,
+ TEXT,
+ VALUE -> tree;
+
+ // The following may contain Markdown in at least one of their fields.
+ case AUTHOR -> transform((DCTree.DCAuthor) tree);
+ case DEPRECATED -> transform((DCTree.DCDeprecated) tree);
+ case DOC_COMMENT -> transform((DCTree.DCDocComment) tree);
+ case EXCEPTION, THROWS -> transform((DCTree.DCThrows) tree);
+ case HIDDEN -> transform((DCTree.DCHidden) tree);
+ case INDEX -> transform((DCTree.DCIndex) tree);
+ case LINK, LINK_PLAIN -> transform((DCTree.DCLink) tree);
+ case PARAM -> transform((DCTree.DCParam) tree);
+ case PROVIDES -> transform((DCTree.DCProvides) tree);
+ case RETURN -> transform((DCTree.DCReturn) tree);
+ case SEE -> transform((DCTree.DCSee) tree);
+ case SERIAL -> transform((DCTree.DCSerial) tree);
+ case SERIAL_DATA -> transform((DCTree.DCSerialData) tree);
+ case SERIAL_FIELD -> transform((DCTree.DCSerialField) tree);
+ case SINCE -> transform((DCTree.DCSince) tree);
+ case SPEC -> transform((DCTree.DCSpec) tree);
+ case SUMMARY -> transform((DCTree.DCSummary) tree);
+ case UNKNOWN_BLOCK_TAG -> transform((DCTree.DCUnknownBlockTag) tree);
+ case UNKNOWN_INLINE_TAG -> transform((DCTree.DCUnknownInlineTag) tree);
+ case USES -> transform((DCTree.DCUses) tree);
+ case VERSION -> transform((DCTree.DCVersion) tree);
+
+ // This should never be handled directly; instead it should be handled as part of
+ // transform(List extends DocTree>);
+ // because a Markdown node has the potential to be split into multiple nodes.
+ case MARKDOWN -> throw new IllegalArgumentException(tree.getKind().toString());
+
+ // Catch in case new kinds are added
+ default -> throw new IllegalArgumentException(tree.getKind().toString());
+ };
+ }
+
+ /**
+ * Transforms a list of doc tree nodes.
+ * If any of the nodes contain Markdown, the Markdown source is parsed
+ * and analyzed for any transformations.
+ * Any non-Markdown nodes are individually transformed.
+ *
+ * @param trees the list of tree nodes to be transformed
+ * @return the transformed list
+ */
+ private List extends DCTree> transform(List extends DCTree> trees) {
+ boolean hasMarkdown = trees.stream().anyMatch(t -> t.getKind() == DocTree.Kind.MARKDOWN);
+ if (hasMarkdown) {
+ var sourceBuilder = new StringBuilder();
+ var replacements = new ArrayList<>();
+
+ /*
+ * Step 1: Convert the trees into a string containing Markdown text,
+ * using Unicode Object Replacement characters to mark the positions
+ * of non-Markdown content.
+ */
+ for (DCTree tree : trees) {
+ if (tree instanceof RawTextTree t) {
+ if (t.getKind() != DocTree.Kind.MARKDOWN) {
+ throw new IllegalStateException(t.getKind().toString());
+ }
+ String code = t.getContent();
+ // handle the (unlikely) case of any U+FFFC characters existing in the code
+ int start = 0;
+ int pos;
+ while ((pos = code.indexOf(PLACEHOLDER, start)) != -1) {
+ replacements.add(PLACEHOLDER);
+ start = pos + 1;
+ }
+ sourceBuilder.append(code);
+ } else {
+ replacements.add(transform(tree));
+ sourceBuilder.append(PLACEHOLDER);
+ }
+ }
+
+ /*
+ * Step 2: Build a parser, and configure it to accept additional syntactic constructs,
+ * such as reference-style links to program elements.
+ */
+ String source = sourceBuilder.toString();
+ Parser parser = Parser.builder()
+ .extensions(List.of(TablesExtension.create()))
+ .inlineParserFactory(new AutoRefInlineParserFactory(refParser, autorefScheme))
+ .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES)
+ .build();
+ Node document = parser.parse(source);
+
+ /*
+ * Step 3: Analyze the parsed tree, converting it back to a list of DocTree nodes,
+ * consisting of Markdown text interspersed with any pre-existing
+ * DocTree nodes, as well as any new nodes created by converting
+ * parts of the Markdown tree into nodes for old-style javadoc tags.
+ */
+ var firstTreePos = trees.getFirst().getStartPosition();
+ Lower v = new Lower(m, document, source, firstTreePos, replacements, autorefScheme);
+ document.accept(v);
+
+ return v.getTrees();
+
+ } else {
+ var list2 = trees.stream()
+ .map(this::transform)
+ .toList();
+ return equal(list2, trees) ? trees : list2;
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ //
+ // The following {@code transform} methods invoke {@code transform} on
+ // any children that may contain Markdown. If the transformations on
+ // the children are all identity transformations (that is the result
+ // of the transformations are the same as the originals) then the
+ // result of the overall transform is the original object. But if
+ // any transformation on the children is not an identity transformation
+ // then the result is a new node containing the transformed values.
+ //
+ // Thus, we only duplicate the parts of the tree that have changed,
+ // and we do not duplicate the parts of the tree that have not changed.
+
+ private DCTree.DCAuthor transform(DCTree.DCAuthor tree) {
+ var name2 = transform(tree.name);
+ return (equal(name2, tree.name))
+ ? tree
+ : m.at(tree.pos).newAuthorTree(name2);
+ }
+
+ private DCTree.DCDeprecated transform(DCTree.DCDeprecated tree) {
+ var body2 = transform(tree.body);
+ return (equal(body2, tree.body))
+ ? tree
+ : m.at(tree.pos).newDeprecatedTree(body2);
+ }
+
+ public DCTree.DCDocComment transform(DCTree.DCDocComment tree) {
+ var fullBody2 = transform(tree.fullBody);
+ var tags2 = transform(tree.tags);
+ // Note: preamble and postamble only appear in HTML files, so should always be
+ // null or empty for doc comments and/or Markdown files
+ var pre2 = transform(tree.preamble);
+ var post2 = transform(tree.postamble);
+ return (equal(fullBody2, tree.fullBody) && equal(tags2, tree.tags)
+ && equal(pre2, tree.preamble) && equal(post2, tree.postamble))
+ ? tree
+ : m.at(tree.pos).newDocCommentTree(tree.comment, fullBody2, tags2, pre2, post2);
+ }
+
+ private DCTree.DCHidden transform(DCTree.DCHidden tree) {
+ var body2 = transform(tree.body);
+ return (equal(body2, tree.body))
+ ? tree
+ : m.at(tree.pos).newHiddenTree(body2);
+ }
+
+ private DCTree.DCIndex transform(DCTree.DCIndex tree) {
+ // The public API permits a DocTree, although in the implementation, it is always a TextTree.
+ var term2 = transform(tree.term);
+ var desc2 = transform(tree.description);
+ return (equal(term2, tree.term) && equal(desc2, tree.description))
+ ? tree
+ : m.at(tree.pos).newIndexTree(term2, desc2).setEndPos(tree.getEndPos());
+ }
+
+ private DCTree.DCLink transform(DCTree.DCLink tree) {
+ var label2 = transform(tree.label);
+ return (equal(label2, tree.label))
+ ? tree
+ : switch (tree.getKind()) {
+ case LINK -> m.at(tree.pos).newLinkTree(tree.ref, label2).setEndPos(tree.getEndPos());
+ case LINK_PLAIN -> m.at(tree.pos).newLinkPlainTree(tree.ref, label2).setEndPos(tree.getEndPos());
+ default -> throw new IllegalArgumentException(tree.getKind().toString());
+ };
+ }
+
+ private DCTree.DCParam transform(DCTree.DCParam tree) {
+ var desc2 = transform(tree.description);
+ return (equal(desc2, tree.description))
+ ? tree
+ : m.at(tree.pos).newParamTree(tree.isTypeParameter, tree.name, desc2);
+ }
+
+ private DCTree.DCProvides transform(DCTree.DCProvides tree) {
+ var desc2 = transform(tree.description);
+ return (equal(desc2, tree.description))
+ ? tree
+ : m.at(tree.pos).newProvidesTree(tree.serviceType, desc2);
+ }
+
+ private DCTree.DCReturn transform(DCTree.DCReturn tree) {
+ var desc2 = transform(tree.description);
+ return (equal(desc2, tree.description))
+ ? tree
+ : m.at(tree.pos).newReturnTree(tree.inline, desc2).setEndPos(tree.getEndPos());
+ }
+
+ private DCTree.DCSee transform(DCTree.DCSee tree) {
+ List extends DocTree> ref2 = transform(tree.reference);
+ return (equal(ref2, tree.getReference()))
+ ? tree
+ : m.at(tree.pos).newSeeTree(ref2);
+ }
+
+ private DCTree.DCSerial transform(DCTree.DCSerial tree) {
+ var desc2 = transform(tree.description);
+ return (equal(desc2, tree.description))
+ ? tree
+ : m.at(tree.pos).newSerialTree(desc2);
+ }
+
+ private DCTree.DCSerialData transform(DCTree.DCSerialData tree) {
+ var desc2 = transform(tree.description);
+ return (equal(desc2, tree.description))
+ ? tree
+ : m.at(tree.pos).newSerialDataTree(desc2);
+ }
+
+ private DCTree.DCSerialField transform(DCTree.DCSerialField tree) {
+ var desc2 = transform(tree.description);
+ return (equal(desc2, tree.description))
+ ? tree
+ : m.at(tree.pos).newSerialFieldTree(tree.name, tree.type, desc2);
+ }
+
+ DCTree.DCSince transform(DCTree.DCSince tree) {
+ var body2 = transform(tree.body);
+ return (equal(body2, tree.body))
+ ? tree
+ : m.at(tree.pos).newSinceTree(body2);
+ }
+
+ private DCTree.DCSpec transform(DCTree.DCSpec tree) {
+ var title2 = transform(tree.title);
+ return (equal(title2, tree.title))
+ ? tree
+ : m.at(tree.pos).newSpecTree(tree.uri, title2);
+ }
+
+ private DCTree.DCSummary transform(DCTree.DCSummary tree) {
+ var summ2 = transform(tree.summary);
+ return (equal(summ2, tree.summary))
+ ? tree
+ : m.at(tree.pos).newSummaryTree(summ2).setEndPos(tree.getEndPos());
+ }
+
+ private DCTree.DCThrows transform(DCTree.DCThrows tree) {
+ var desc2 = transform(tree.description);
+ return (equal(desc2, tree.description))
+ ? tree
+ : switch (tree.getKind()) {
+ case EXCEPTION -> m.at(tree.pos).newExceptionTree(tree.name, desc2);
+ case THROWS -> m.at(tree.pos).newThrowsTree(tree.name, desc2);
+ default -> throw new IllegalArgumentException(tree.getKind().toString());
+ };
+ }
+
+ private DCTree.DCUnknownBlockTag transform(DCTree.DCUnknownBlockTag tree) {
+ var cont2 = transform(tree.content);
+ return (equal(cont2, tree.content))
+ ? tree
+ : m.at(tree.pos).newUnknownBlockTagTree(tree.name, cont2);
+ }
+
+ private DCTree.DCUnknownInlineTag transform(DCTree.DCUnknownInlineTag tree) {
+ var cont2 = transform(tree.content);
+ return (equal(cont2, tree.content))
+ ? tree
+ : m.at(tree.pos).newUnknownInlineTagTree(tree.name, cont2).setEndPos(tree.getEndPos());
+ }
+
+ private DCTree.DCUses transform(DCTree.DCUses tree) {
+ var desc2 = transform(tree.description);
+ return (equal(desc2, tree.description))
+ ? tree
+ : m.at(tree.pos).newUsesTree(tree.serviceType, desc2);
+ }
+
+ private DCTree.DCVersion transform(DCTree.DCVersion tree) {
+ var body2 = transform(tree.body);
+ return (equal(body2, tree.body))
+ ? tree
+ : m.at(tree.pos).newVersionTree(body2);
+ }
+
+ /**
+ * Shallow "equals" for two doc tree nodes.
+ *
+ * @param the type of the items
+ * @param item1 the first item
+ * @param item2 the second item
+ * @return {@code true} if the items are reference-equal, and {@code false} otherwise
+ */
+ private static boolean equal(T item1, T item2) {
+ return item1 == item2;
+ }
+
+ /**
+ * Shallow "equals" for two lists of doc tree nodes.
+ *
+ * @param the type of the items
+ * @param list1 the first item
+ * @param list2 the second item
+ * @return {@code true} if the items are reference-equal, and {@code false} otherwise
+ */
+ private static boolean equal(List extends T> list1, List extends T> list2) {
+ if (list1 == null || list2 == null) {
+ return (list1 == list2);
+ }
+
+ if (list1.size() != list2.size()) {
+ return false;
+ }
+
+ var iter1 = list1.iterator();
+ var iter2 = list2.iterator();
+ while (iter1.hasNext()) {
+ var item1 = iter1.next();
+ var item2 = iter2.next();
+ if (item1 != item2) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * An {@code InlineParserFactory} that checks any unresolved link references in
+ * reference links. If an unresolved link reference appears to be a reference to a
+ * program element, such as may be used in {@code @see ...} or {@code {@link ...}}
+ * tags, it generates a synthetic {@code LinkReferenceDefinition}.
+ *
+ * The reference is not validated to ensure it refers to any existing element.
+ *
+ * The primary function of this {@code InlineParserFactory} is to provide
+ * a custom implementation of an {@code InlineParserContext} via the
+ * {@code create(InlineParserContext inlineParserContext)} method.
+ * It is this {@code InlineParserContext} that actually manages the set
+ * of link reference definitions.
+ *
+ * This {@code InlineParserFactory} is intended to be registered with a
+ * {@code Parser.Builder} using {@code setInlineParserFactory}.
+ */
+ private static class AutoRefInlineParserFactory implements InlineParserFactory {
+ private final ReferenceParser refParser;
+ private final String autorefScheme;
+
+ AutoRefInlineParserFactory(ReferenceParser refParser,
+ String autorefScheme) {
+ this.refParser = refParser;
+ this.autorefScheme = autorefScheme;
+ }
+
+ /**
+ * Creates a parser with a modified {@code InlineParserContext} that
+ * delegates to the standard {@code InlineParserContext} and also
+ * checks unresolved link references.
+ *
+ * @param inlineParserContext the standard {@code InlineParserContext}
+ * @return the {@code InlineParser}
+ */
+ @Override
+ public InlineParser create(InlineParserContext inlineParserContext) {
+ return new InlineParserImpl(new InlineParserContext() {
+
+ @Override
+ public List getCustomDelimiterProcessors() {
+ return inlineParserContext.getCustomDelimiterProcessors();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * If the given label does not match any explicitly defined
+ * link reference definition, but does match a reference
+ * to a program element, a synthetic link reference definition
+ * is returned, with the label prefixed by the {@code autorefScheme}.
+ *
+ * @param label the link label to look up
+ * @return the link reference definition for the label, or {@code null}
+ */
+ @Override
+ public LinkReferenceDefinition getLinkReferenceDefinition(String label) {
+ // In CommonMark, square brackets characters need to be escaped within a link label.
+ // See https://spec.commonmark.org/0.30/#link-label
+ // Unescaped square bracket characters are not allowed inside the opening
+ // and closing square brackets of link labels.
+ // The escape characters are still present here in the label,
+ // so we remove them before creating the autoref URL.
+ // Note that the characters always appear together as a pair in API references.
+ var l = label.replace("\\[\\]", "[]");
+ var d = inlineParserContext.getLinkReferenceDefinition(l);
+ return d == null && isReference(l)
+ ? new LinkReferenceDefinition("", autorefScheme + l, "")
+ : d;
+ }
+ });
+ }
+
+ /**
+ * {@return whether a string appears to be a reference to a program element}
+ *
+ * @param s the string
+ */
+ private boolean isReference(String s) {
+ try {
+ refParser.parse(s, ReferenceParser.Mode.MEMBER_OPTIONAL);
+ return true;
+ } catch (ReferenceParser.ParseException e) {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * A visitor to scan a Markdown document looking for any constructs that
+ * should be mapped to doc comment nodes.
+ *
+ * The intended use is as follows:
+ * {@snippet
+ * Lower v = new Lower(document, source, replacements);
+ * document.accept(v);
+ * var result = v.getTrees();
+ * }
+ *
+ * On coordinates ...
+ *
+ * There are various position/coordinate systems in play in this code:
+ *
+ * 1. Tree positions, which correspond to positions in the original comment,
+ * represented as a 0-based character offset within the content of the comment.
+ * 2. Positions in the source string given to the Markdown parser,
+ * represented as a 0-based character offset within the source string.
+ * These differ from positions in the original comment if the source string
+ * contains U+FFFC characters representing embedded tree nodes.
+ * 3. Positions in the source string given to the Markdown parser,
+ * represented as (line, column) values within SourceSpan nodes.
+ * Both line and column are 0-based.
+ * Note: SourceSpan objects never include newline characters.
+ *
+ * See {@link Lower#toSourcePos(int, int)} to convert from (line, column)
+ * coordinates to a source position.
+ * See {@link Lower#sourcePosToTreePos(int)} to convert from a source
+ * position to a position in the original comment, for use in tree nodes.
+ */
+
+ // Future opportunity:
+ //
+ // It would be possible to override {@code visit(Heading)} and
+ // check for certain recognized headings, and for those headings,
+ // check the content that follows up to but not including
+ // the next heading at the same or higher level, or end of text.
+ // When a match occurs, the heading and the content could be translated
+ // into one or more block tags. Note that any such block tags should
+ // probably be stored separately from {@code trees}, and handled
+ // appropriately by the caller of this method, either by accepting
+ // such tags, or by reporting an error if they are not applicable.
+ //
+ // A variant would be to pass in a boolean parameter to indicate
+ // whether such tags would be acceptable if found, and to ignore
+ // the heading and content if the block tag would not be acceptable.
+ //
+ // If a heading and any following content is converted to a block tag,
+ // the content should probably be removed from the tree, so that
+ // it is not handled by the call of {@code visitChildren} that
+ // invoked {@code visit(Heading)}.
+ //
+ // Overall, this would allow Markdown constructs such as the following:
+ //
+ // /// Method description.
+ // ///
+ // /// # Parameters
+ // /// * args
+ // ///
+ // /// # Returns
+ // /// a result
+ // ///
+ // /// # Throws
+ // /// * IOException if an error occurs
+ //
+ // That being said, is it so much better than using standard block tags?
+ // It is somewhat more concise for any repeatable block tag, if we
+ // leverage the idea of a heading followed by a list.
+ private static class Lower extends AbstractVisitor {
+ private final DocTreeMaker m;
+ private final String autorefScheme;
+
+ /**
+ * The Markdown document being processed.
+ */
+ private final Node document;
+
+ /**
+ * The source for the Markdown document being processed.
+ * The document has "source spans" that indirectly point into this string
+ * using {@code (line, column)} coordinates.
+ * @see #toSourcePos(int, int)
+ */
+ private final String source;
+
+ /**
+ * An array giving the position of the first character after each newline
+ * in the source.
+ * Used to convert {@code (line, column)} coordinates to a character offset
+ * in {@code source}.
+ */
+ private final int[] sourceLineOffsets;
+
+ /**
+ * An iterator containing the values to be substituted into the generated
+ * tree when U+FFFC characters are encountered in the document.
+ * There is a 1-1 correspondence between the values returned by the
+ * iterator and the U+FFFC characters in the document.
+ * The replacement objects may be either {@code DCTree} objects or
+ * U+FFFC characters that were found in the original document.
+ */
+ private final Iterator> replaceIter;
+
+ /**
+ * The list of trees being built.
+ * It may be temporarily replaced while visiting the children of
+ * {@code Link} codes.
+ */
+ private List trees;
+
+ /**
+ * The source text being accumulated, prior to being placed in a
+ * Markdown source node ({@code RawTextTree} with kind {@code MARKDOWN}).
+ */
+ private final StringBuilder text;
+
+ /**
+ * The initial position in the enclosing comment of the source
+ * being transformed.
+ */
+ private int mainStartPos;
+
+ /**
+ * The start of source text to be copied literally when required.
+ */
+ private int copyStartPos;
+
+ /**
+ * The current adjustment from positions in {@code source} to positions
+ * in the original comment, as used in doc tree nodes.
+ * The difference arises because of the use of U+FFFC characters for
+ * embedded objects. As the document (and source) are scanned,
+ * this offset is updated when U+FFFC characters are encountered.
+ */
+ private int replaceAdjustPos;
+
+ /**
+ * The pattern for a line break.
+ * This is equivalent to the detection in the Markdown parser, so that
+ * {@code (line, column)} coordinates can be accurately converted back
+ * into source positions.
+ *
+ * @see jdk.internal.org.commonmark.internal.DocumentParser#parse(String)
+ * @see jdk.internal.org.commonmark.internal.util.Parsing#findLineBreak(CharSequence, int)
+ */
+ private static final Pattern lineBreak = Pattern.compile("\n|\r\n?");
+
+ /**
+ * Creates an instance of the visitor, given a document,
+ * the source text for the document and objects to be substituted
+ * when U+FFFC characters are encountered.
+ *
+ * Note the document does not itself contain any source text:
+ * it just contains {@code SourceSpan} objects that point into
+ * the source text using line and column indices.
+ *
+ * @param document the document to be converted
+ * @param source the source of the document to be converted
+ * @param sourcePos the position in the enclosing comment of the source to be converted
+ * @param replacements the objects to be substituted when U+FFFC is encountered
+ * @param autorefScheme the scheme used for auto-generated references
+ */
+ public Lower(DocTreeMaker docTreeMaker,
+ Node document,
+ String source, int sourcePos,
+ List> replacements,
+ String autorefScheme) {
+ this.m = docTreeMaker;
+ this.document = document;
+ this.source = source;
+ this.autorefScheme = autorefScheme;
+
+ sourceLineOffsets = Stream.concat(
+ Stream.of(0),
+ lineBreak.matcher(source).results().map(MatchResult::end))
+ .mapToInt(Integer::intValue)
+ .toArray();
+
+ replaceIter = replacements.iterator();
+
+ trees = new ArrayList<>();
+ text = new StringBuilder();
+ mainStartPos = sourcePos;
+ copyStartPos = 0;
+ replaceAdjustPos = 0;
+ }
+
+ /**
+ * {@return the trees that were built after using the visitor}
+ */
+ public List getTrees() {
+ return trees;
+ }
+
+ /**
+ * Visits a CommonMark {@code Link} node.
+ *
+ * If the destination for the link begins with the {@code autorefScheme}
+ * convert it to {@code {@link ...}} or {@code {@linkplain ...}} DocTree node.
+ * {@code {@link ...}} will be used if the content (label) for
+ * the link is the same as the reference found after the {@code code:};
+ * otherwise, {@code {@linkplain ...}} will be used.
+ *
+ * The label will be left blank for {@code {@link ...}} nodes,
+ * implying that a default label should be used, based on the
+ * program element that was referenced.
+ *
+ * @param link the link node
+ */
+ @Override
+ public void visit(Link link) {
+ String dest = link.getDestination();
+ if (dest.startsWith(autorefScheme)) {
+ // copy the source text up to the start of the node
+ copyTo(getStartPos(link));
+ // push temporary value for {@code trees} while handling the content of the node
+ var saveTrees = trees;
+ trees = new ArrayList<>();
+ try {
+ copyStartPos = getStartPos(link.getFirstChild());
+ visitChildren(link);
+ copyTo(getEndPos(link.getLastChild()));
+
+ // determine whether to use {@link ... } or {@linkplain ...}
+ // based on whether the "link text" is the same as the "link destination"
+ String ref = dest.substring(autorefScheme.length());
+ int refPos = sourcePosToTreePos(getRefPos(ref, link));
+ var newRefTree = m.at(refPos).newReferenceTree(ref).setEndPos(refPos + ref.length());
+
+ Node child = link.getFirstChild();
+ DocTree.Kind linkKind = child.getNext() == null
+ && child instanceof Text t
+ && t.getLiteral().equals(ref) ? DocTree.Kind.LINK : DocTree.Kind.LINK_PLAIN;
+
+ DCTree newLinkTree = linkKind == DocTree.Kind.LINK
+ ? m.at(NOPOS).newLinkTree(newRefTree, List.of()) // ignore the child trees
+ : m.at(NOPOS).newLinkPlainTree(newRefTree, trees);
+
+ saveTrees.add(newLinkTree);
+ } finally {
+ // start any subsequent copy after the end of the link node
+ copyStartPos = getEndPos(link);
+ trees = saveTrees;
+ }
+ } else {
+ visitChildren(link);
+ }
+ }
+
+ /**
+ * {@return the position in the source for the reference in a link}
+ * Many syntactic forms may yield a {@code Link} object, so scan the
+ * source looking for a match. Since the reference typically comes
+ * after any text (when they are different), scan the source backwards.
+ *
+ * @param ref the reference to find
+ * @param link the link containing the reference
+ */
+ private int getRefPos(String ref, Link link) {
+ var spans = link.getSourceSpans();
+ var revSpanIter = spans.listIterator(spans.size());
+ while (revSpanIter.hasPrevious()) {
+ var span = revSpanIter.previous();
+ var start = toSourcePos(span.getLineIndex(), span.getColumnIndex());
+ var end = toSourcePos(span.getLineIndex(), span.getColumnIndex() + span.getLength());
+ var s = source.substring(start, end);
+ var index = s.lastIndexOf(ref);
+ if (index != -1) {
+ return start + index;
+ }
+ }
+ return NOPOS;
+ }
+
+ /**
+ * {@return the position in the original comment for a position in {@code source},
+ * using {@link #replaceAdjustPos}}
+ *
+ * @param pos the position in {@code source}
+ */
+ private int sourcePosToTreePos(int pos) {
+ return pos == NOPOS ? NOPOS : mainStartPos + pos + replaceAdjustPos;
+ }
+
+ /**
+ * Processes a node and any children.
+ *
+ * If the node has children, the children are each visited by
+ * calling their {@code accept} method, and then finally, if this
+ * is the top-level {@code document} node, any pending text is
+ * flushed.
+ *
+ * If the node does not have children, the source spans for
+ * the node are processed instead.
+ *
+ * Note that unlike the default implementation of {@code visitChildren},
+ * the next child is not accessed until after the current child
+ * has been visited. This allows a child to peek at and possibly remove
+ * any child nodes that may follow it.
+ *
+ * @param node the node whose children should be visited
+ */
+ @Override
+ protected void visitChildren(Node node) {
+ Node child = node.getFirstChild();
+ if (child != null) {
+ while (child != null) {
+ // defer getting the next child until after this node has
+ // been processed, in case any following nodes were handled
+ // by this node, and removed from the document
+ child.accept(this);
+ child = child.getNext();
+ }
+ if (node == document) {
+ // the top level document has no spans of its own, so use the last child
+ copyTo(getEndPos(document.getLastChild()));
+ }
+ }
+ }
+
+ /**
+ * {@return the start of this node, from the start of the first span}
+ *
+ * @param node the node
+ */
+ private int getStartPos(Node node) {
+ var spans = node.getSourceSpans();
+ var firstSpan = spans.getFirst();
+ return toSourcePos(firstSpan.getLineIndex(), firstSpan.getColumnIndex());
+ }
+
+ /**
+ * {@return the end of this node, from the end of the last span}
+ * The end points to the first character not included in the span.
+ *
+ * @param node the node
+ */
+ private int getEndPos(Node node) {
+ var spans = node.getSourceSpans();
+ var lastSpan = spans.getLast();
+ return toSourcePos(lastSpan.getLineIndex(), lastSpan.getColumnIndex() + lastSpan.getLength());
+ }
+
+ /**
+ * {@return the position in the {@code source} string for a given {@code (line, column}}
+ *
+ * @param lineIndex the line index
+ * @param columnIndex the column index
+ */
+ private int toSourcePos(int lineIndex, int columnIndex) {
+ return sourceLineOffsets[lineIndex] + columnIndex;
+ }
+
+ /**
+ * Copies source text from the saved copy-start position to the given end position
+ * using the saved {@code source}, to the list of {@code trees}.
+ *
+ * @param endPos the end position
+ */
+ private void copyTo(int endPos) {
+ int startPos = copyStartPos;
+ int rawTextStartPos = copyStartPos;
+ int pos;
+ while ((pos = source.indexOf(PLACEHOLDER, startPos)) != -1 && pos < endPos) {
+ text.append(source, startPos, pos);
+ assert replaceIter.hasNext();
+ Object r = replaceIter.next();
+ if (r instanceof DCTree t) {
+ flushText(rawTextStartPos);
+ trees.add(t);
+ replaceAdjustPos += t.getEndPosition() - t.getStartPosition() - 1;
+ rawTextStartPos = pos + 1;
+ } else if (r.equals(PLACEHOLDER)) {
+ text.append(PLACEHOLDER);
+ } else {
+ throw new IllegalStateException(r.getClass().toString());
+ }
+ startPos = pos + 1;
+ }
+ if (startPos < endPos) {
+ text.append(source, startPos, endPos);
+ }
+ flushText(rawTextStartPos);
+ }
+
+ /**
+ * Flushes any text in the {@code text} buffer, by creating a new
+ * Markdown source text node and adding it to the list of trees.
+ */
+ private void flushText(int pos) {
+ if (!text.isEmpty()) {
+ trees.add(m.at(sourcePosToTreePos(pos)).newRawTextTree(DocTree.Kind.MARKDOWN, text.toString()));
+ text.setLength(0);
+ }
+ }
+ }
+}
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 00000000000..b0f1ed378f6
--- /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/ext/gfm/tables/TableBlock.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableBlock.java
new file mode 100644
index 00000000000..95e6676b137
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableBlock.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.ext.gfm.tables;
+
+import jdk.internal.org.commonmark.node.CustomBlock;
+
+/**
+ * Table block containing a {@link TableHead} and optionally a {@link TableBody}.
+ */
+public class TableBlock extends CustomBlock {
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableBody.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableBody.java
new file mode 100644
index 00000000000..7f0361467d3
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableBody.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.ext.gfm.tables;
+
+import jdk.internal.org.commonmark.node.CustomNode;
+
+/**
+ * Body part of a {@link TableBlock} containing {@link TableRow TableRows}.
+ */
+public class TableBody extends CustomNode {
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableCell.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableCell.java
new file mode 100644
index 00000000000..3613fc2aea1
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableCell.java
@@ -0,0 +1,86 @@
+/*
+ * 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.ext.gfm.tables;
+
+import jdk.internal.org.commonmark.node.CustomNode;
+
+/**
+ * Table cell of a {@link TableRow} containing inline nodes.
+ */
+public class TableCell extends CustomNode {
+
+ private boolean header;
+ private Alignment alignment;
+ private int width;
+
+ /**
+ * @return whether the cell is a header or not
+ */
+ public boolean isHeader() {
+ return header;
+ }
+
+ public void setHeader(boolean header) {
+ this.header = header;
+ }
+
+ /**
+ * @return the cell alignment or {@code null} if no specific alignment
+ */
+ public Alignment getAlignment() {
+ return alignment;
+ }
+
+ public void setAlignment(Alignment alignment) {
+ this.alignment = alignment;
+ }
+
+ /**
+ * @return the cell width (the number of dash and colon characters in the delimiter row of the table for this column)
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ /**
+ * How the cell is aligned horizontally.
+ */
+ public enum Alignment {
+ LEFT, CENTER, RIGHT
+ }
+
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableHead.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableHead.java
new file mode 100644
index 00000000000..3cff03071f8
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableHead.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.ext.gfm.tables;
+
+import jdk.internal.org.commonmark.node.CustomNode;
+
+/**
+ * Head part of a {@link TableBlock} containing {@link TableRow TableRows}.
+ */
+public class TableHead extends CustomNode {
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableRow.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableRow.java
new file mode 100644
index 00000000000..b50d6cc0c38
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TableRow.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.ext.gfm.tables;
+
+import jdk.internal.org.commonmark.node.CustomNode;
+
+/**
+ * Table row of a {@link TableHead} or {@link TableBody} containing {@link TableCell TableCells}.
+ */
+public class TableRow extends CustomNode {
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TablesExtension.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TablesExtension.java
new file mode 100644
index 00000000000..63cd4f8e2e5
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/TablesExtension.java
@@ -0,0 +1,117 @@
+/*
+ * 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.ext.gfm.tables;
+
+import jdk.internal.org.commonmark.Extension;
+import jdk.internal.org.commonmark.ext.gfm.tables.internal.TableBlockParser;
+import jdk.internal.org.commonmark.ext.gfm.tables.internal.TableHtmlNodeRenderer;
+import jdk.internal.org.commonmark.ext.gfm.tables.internal.TableMarkdownNodeRenderer;
+import jdk.internal.org.commonmark.ext.gfm.tables.internal.TableTextContentNodeRenderer;
+import jdk.internal.org.commonmark.parser.Parser;
+import jdk.internal.org.commonmark.renderer.NodeRenderer;
+import jdk.internal.org.commonmark.renderer.html.HtmlNodeRendererContext;
+import jdk.internal.org.commonmark.renderer.html.HtmlNodeRendererFactory;
+import jdk.internal.org.commonmark.renderer.html.HtmlRenderer;
+import jdk.internal.org.commonmark.renderer.markdown.MarkdownNodeRendererContext;
+import jdk.internal.org.commonmark.renderer.markdown.MarkdownNodeRendererFactory;
+import jdk.internal.org.commonmark.renderer.markdown.MarkdownRenderer;
+import jdk.internal.org.commonmark.renderer.text.TextContentNodeRendererContext;
+import jdk.internal.org.commonmark.renderer.text.TextContentNodeRendererFactory;
+import jdk.internal.org.commonmark.renderer.text.TextContentRenderer;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown).
+ *
+ * Create it with {@link #create()} and then configure it on the builders
+ * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)},
+ * {@link HtmlRenderer.Builder#extensions(Iterable)}).
+ *
+ *
+ * The parsed tables are turned into {@link TableBlock} blocks.
+ *
+ *
+ * @see Tables (extension) in GitHub Flavored Markdown Spec
+ */
+public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension,
+ TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension {
+
+ private TablesExtension() {
+ }
+
+ public static Extension create() {
+ return new TablesExtension();
+ }
+
+ @Override
+ public void extend(Parser.Builder parserBuilder) {
+ parserBuilder.customBlockParserFactory(new TableBlockParser.Factory());
+ }
+
+ @Override
+ public void extend(HtmlRenderer.Builder rendererBuilder) {
+ rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() {
+ @Override
+ public NodeRenderer create(HtmlNodeRendererContext context) {
+ return new TableHtmlNodeRenderer(context);
+ }
+ });
+ }
+
+ @Override
+ public void extend(TextContentRenderer.Builder rendererBuilder) {
+ rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() {
+ @Override
+ public NodeRenderer create(TextContentNodeRendererContext context) {
+ return new TableTextContentNodeRenderer(context);
+ }
+ });
+ }
+
+ @Override
+ public void extend(MarkdownRenderer.Builder rendererBuilder) {
+ rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() {
+ @Override
+ public NodeRenderer create(MarkdownNodeRendererContext context) {
+ return new TableMarkdownNodeRenderer(context);
+ }
+
+ @Override
+ public Set getSpecialCharacters() {
+ return Collections.singleton('|');
+ }
+ });
+ }
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java
new file mode 100644
index 00000000000..aca4db8fc1c
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java
@@ -0,0 +1,344 @@
+/*
+ * 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.ext.gfm.tables.internal;
+
+import jdk.internal.org.commonmark.ext.gfm.tables.*;
+import jdk.internal.org.commonmark.node.Block;
+import jdk.internal.org.commonmark.node.Node;
+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.*;
+import jdk.internal.org.commonmark.text.Characters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TableBlockParser extends AbstractBlockParser {
+
+ private final TableBlock block = new TableBlock();
+ private final List rowLines = new ArrayList<>();
+ private final List columns;
+
+ private boolean canHaveLazyContinuationLines = true;
+
+ private TableBlockParser(List columns, SourceLine headerLine) {
+ this.columns = columns;
+ this.rowLines.add(headerLine);
+ }
+
+ @Override
+ public boolean canHaveLazyContinuationLines() {
+ return canHaveLazyContinuationLines;
+ }
+
+ @Override
+ public Block getBlock() {
+ return block;
+ }
+
+ @Override
+ public BlockContinue tryContinue(ParserState state) {
+ CharSequence content = state.getLine().getContent();
+ int pipe = Characters.find('|', content, state.getNextNonSpaceIndex());
+ if (pipe != -1) {
+ if (pipe == state.getNextNonSpaceIndex()) {
+ // If we *only* have a pipe character (and whitespace), that is not a valid table row and ends the table.
+ if (Characters.skipSpaceTab(content, pipe + 1, content.length()) == content.length()) {
+ // We also don't want the pipe to be added via lazy continuation.
+ canHaveLazyContinuationLines = false;
+ return BlockContinue.none();
+ }
+ }
+ return BlockContinue.atIndex(state.getIndex());
+ } else {
+ return BlockContinue.none();
+ }
+ }
+
+ @Override
+ public void addLine(SourceLine line) {
+ rowLines.add(line);
+ }
+
+ @Override
+ public void parseInlines(InlineParser inlineParser) {
+ List sourceSpans = block.getSourceSpans();
+
+ SourceSpan headerSourceSpan = !sourceSpans.isEmpty() ? sourceSpans.get(0) : null;
+ Node head = new TableHead();
+ if (headerSourceSpan != null) {
+ head.addSourceSpan(headerSourceSpan);
+ }
+ block.appendChild(head);
+
+ TableRow headerRow = new TableRow();
+ headerRow.setSourceSpans(head.getSourceSpans());
+ head.appendChild(headerRow);
+
+ List headerCells = split(rowLines.get(0));
+ int headerColumns = headerCells.size();
+ for (int i = 0; i < headerColumns; i++) {
+ SourceLine cell = headerCells.get(i);
+ TableCell tableCell = parseCell(cell, i, inlineParser);
+ tableCell.setHeader(true);
+ headerRow.appendChild(tableCell);
+ }
+
+ TableBody body = null;
+ // Body starts at index 2. 0 is header, 1 is separator.
+ for (int rowIndex = 2; rowIndex < rowLines.size(); rowIndex++) {
+ SourceLine rowLine = rowLines.get(rowIndex);
+ SourceSpan sourceSpan = rowIndex < sourceSpans.size() ? sourceSpans.get(rowIndex) : null;
+ List cells = split(rowLine);
+ TableRow row = new TableRow();
+ if (sourceSpan != null) {
+ row.addSourceSpan(sourceSpan);
+ }
+
+ // Body can not have more columns than head
+ for (int i = 0; i < headerColumns; i++) {
+ SourceLine cell = i < cells.size() ? cells.get(i) : SourceLine.of("", null);
+ TableCell tableCell = parseCell(cell, i, inlineParser);
+ row.appendChild(tableCell);
+ }
+
+ if (body == null) {
+ // It's valid to have a table without body. In that case, don't add an empty TableBody node.
+ body = new TableBody();
+ block.appendChild(body);
+ }
+ body.appendChild(row);
+ body.addSourceSpan(sourceSpan);
+ }
+ }
+
+ private TableCell parseCell(SourceLine cell, int column, InlineParser inlineParser) {
+ TableCell tableCell = new TableCell();
+ SourceSpan sourceSpan = cell.getSourceSpan();
+ if (sourceSpan != null) {
+ tableCell.addSourceSpan(sourceSpan);
+ }
+
+ if (column < columns.size()) {
+ TableCellInfo cellInfo = columns.get(column);
+ tableCell.setAlignment(cellInfo.getAlignment());
+ tableCell.setWidth(cellInfo.getWidth());
+ }
+
+ CharSequence content = cell.getContent();
+ int start = Characters.skipSpaceTab(content, 0, content.length());
+ int end = Characters.skipSpaceTabBackwards(content, content.length() - 1, start);
+ inlineParser.parse(SourceLines.of(cell.substring(start, end + 1)), tableCell);
+
+ return tableCell;
+ }
+
+ private static List split(SourceLine line) {
+ CharSequence row = line.getContent();
+ int nonSpace = Characters.skipSpaceTab(row, 0, row.length());
+ int cellStart = nonSpace;
+ int cellEnd = row.length();
+ if (row.charAt(nonSpace) == '|') {
+ // This row has leading/trailing pipes - skip the leading pipe
+ cellStart = nonSpace + 1;
+ // Strip whitespace from the end but not the pipe or we could miss an empty ("||") cell
+ int nonSpaceEnd = Characters.skipSpaceTabBackwards(row, row.length() - 1, cellStart);
+ cellEnd = nonSpaceEnd + 1;
+ }
+ List cells = new ArrayList<>();
+ StringBuilder sb = new StringBuilder();
+ for (int i = cellStart; i < cellEnd; i++) {
+ char c = row.charAt(i);
+ switch (c) {
+ case '\\':
+ if (i + 1 < cellEnd && row.charAt(i + 1) == '|') {
+ // Pipe is special for table parsing. An escaped pipe doesn't result in a new cell, but is
+ // passed down to inline parsing as an unescaped pipe. Note that that applies even for the `\|`
+ // in an input like `\\|` - in other words, table parsing doesn't support escaping backslashes.
+ sb.append('|');
+ i++;
+ } else {
+ // Preserve backslash before other characters or at end of line.
+ sb.append('\\');
+ }
+ break;
+ case '|':
+ String content = sb.toString();
+
+ cells.add(SourceLine.of(content, line.substring(cellStart, i).getSourceSpan()));
+ sb.setLength(0);
+ // + 1 to skip the pipe itself for the next cell's span
+ cellStart = i + 1;
+ break;
+ default:
+ sb.append(c);
+ }
+ }
+ if (sb.length() > 0) {
+ String content = sb.toString();
+ cells.add(SourceLine.of(content, line.substring(cellStart, line.getContent().length()).getSourceSpan()));
+ }
+ return cells;
+ }
+
+ // Examples of valid separators:
+ //
+ // |-
+ // -|
+ // |-|
+ // -|-
+ // |-|-|
+ // --- | ---
+ private static List parseSeparator(CharSequence s) {
+ List columns = new ArrayList<>();
+ int pipes = 0;
+ boolean valid = false;
+ int i = 0;
+ int width = 0;
+ while (i < s.length()) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '|':
+ i++;
+ pipes++;
+ if (pipes > 1) {
+ // More than one adjacent pipe not allowed
+ return null;
+ }
+ // Need at lest one pipe, even for a one column table
+ valid = true;
+ break;
+ case '-':
+ case ':':
+ if (pipes == 0 && !columns.isEmpty()) {
+ // Need a pipe after the first column (first column doesn't need to start with one)
+ return null;
+ }
+ boolean left = false;
+ boolean right = false;
+ if (c == ':') {
+ left = true;
+ i++;
+ width++;
+ }
+ boolean haveDash = false;
+ while (i < s.length() && s.charAt(i) == '-') {
+ i++;
+ width++;
+ haveDash = true;
+ }
+ if (!haveDash) {
+ // Need at least one dash
+ return null;
+ }
+ if (i < s.length() && s.charAt(i) == ':') {
+ right = true;
+ i++;
+ width++;
+ }
+ columns.add(new TableCellInfo(getAlignment(left, right), width));
+ width = 0;
+ // Next, need another pipe
+ pipes = 0;
+ break;
+ case ' ':
+ case '\t':
+ // White space is allowed between pipes and columns
+ i++;
+ break;
+ default:
+ // Any other character is invalid
+ return null;
+ }
+ }
+ if (!valid) {
+ return null;
+ }
+ return columns;
+ }
+
+ private static TableCell.Alignment getAlignment(boolean left, boolean right) {
+ if (left && right) {
+ return TableCell.Alignment.CENTER;
+ } else if (left) {
+ return TableCell.Alignment.LEFT;
+ } else if (right) {
+ return TableCell.Alignment.RIGHT;
+ } else {
+ return null;
+ }
+ }
+
+ public static class Factory extends AbstractBlockParserFactory {
+
+ @Override
+ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
+ List paragraphLines = matchedBlockParser.getParagraphLines().getLines();
+ if (paragraphLines.size() == 1 && Characters.find('|', paragraphLines.get(0).getContent(), 0) != -1) {
+ SourceLine line = state.getLine();
+ SourceLine separatorLine = line.substring(state.getIndex(), line.getContent().length());
+ List columns = parseSeparator(separatorLine.getContent());
+ if (columns != null && !columns.isEmpty()) {
+ SourceLine paragraph = paragraphLines.get(0);
+ List headerCells = split(paragraph);
+ if (columns.size() >= headerCells.size()) {
+ return BlockStart.of(new TableBlockParser(columns, paragraph))
+ .atIndex(state.getIndex())
+ .replaceActiveBlockParser();
+ }
+ }
+ }
+ return BlockStart.none();
+ }
+ }
+
+ private static class TableCellInfo {
+ private final TableCell.Alignment alignment;
+ private final int width;
+
+ public TableCell.Alignment getAlignment() {
+ return alignment;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public TableCellInfo(TableCell.Alignment alignment, int width) {
+ this.alignment = alignment;
+ this.width = width;
+ }
+ }
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java
new file mode 100644
index 00000000000..7c9f74fec43
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java
@@ -0,0 +1,126 @@
+/*
+ * 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.ext.gfm.tables.internal;
+
+import jdk.internal.org.commonmark.ext.gfm.tables.*;
+import jdk.internal.org.commonmark.node.Node;
+import jdk.internal.org.commonmark.renderer.html.HtmlNodeRendererContext;
+import jdk.internal.org.commonmark.renderer.html.HtmlWriter;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class TableHtmlNodeRenderer extends TableNodeRenderer {
+
+ private final HtmlWriter htmlWriter;
+ private final HtmlNodeRendererContext context;
+
+ public TableHtmlNodeRenderer(HtmlNodeRendererContext context) {
+ this.htmlWriter = context.getWriter();
+ this.context = context;
+ }
+
+ protected void renderBlock(TableBlock tableBlock) {
+ htmlWriter.line();
+ htmlWriter.tag("table", getAttributes(tableBlock, "table"));
+ renderChildren(tableBlock);
+ htmlWriter.tag("/table");
+ htmlWriter.line();
+ }
+
+ protected void renderHead(TableHead tableHead) {
+ htmlWriter.line();
+ htmlWriter.tag("thead", getAttributes(tableHead, "thead"));
+ renderChildren(tableHead);
+ htmlWriter.tag("/thead");
+ htmlWriter.line();
+ }
+
+ protected void renderBody(TableBody tableBody) {
+ htmlWriter.line();
+ htmlWriter.tag("tbody", getAttributes(tableBody, "tbody"));
+ renderChildren(tableBody);
+ htmlWriter.tag("/tbody");
+ htmlWriter.line();
+ }
+
+ protected void renderRow(TableRow tableRow) {
+ htmlWriter.line();
+ htmlWriter.tag("tr", getAttributes(tableRow, "tr"));
+ renderChildren(tableRow);
+ htmlWriter.tag("/tr");
+ htmlWriter.line();
+ }
+
+ protected void renderCell(TableCell tableCell) {
+ String tagName = tableCell.isHeader() ? "th" : "td";
+ htmlWriter.line();
+ htmlWriter.tag(tagName, getCellAttributes(tableCell, tagName));
+ renderChildren(tableCell);
+ htmlWriter.tag("/" + tagName);
+ htmlWriter.line();
+ }
+
+ private Map getAttributes(Node node, String tagName) {
+ return context.extendAttributes(node, tagName, Collections.emptyMap());
+ }
+
+ private Map getCellAttributes(TableCell tableCell, String tagName) {
+ if (tableCell.getAlignment() != null) {
+ return context.extendAttributes(tableCell, tagName, Collections.singletonMap("align", getAlignValue(tableCell.getAlignment())));
+ } else {
+ return context.extendAttributes(tableCell, tagName, Collections.emptyMap());
+ }
+ }
+
+ private static String getAlignValue(TableCell.Alignment alignment) {
+ switch (alignment) {
+ case LEFT:
+ return "left";
+ case CENTER:
+ return "center";
+ case RIGHT:
+ return "right";
+ }
+ throw new IllegalStateException("Unknown alignment: " + alignment);
+ }
+
+ private void renderChildren(Node parent) {
+ Node node = parent.getFirstChild();
+ while (node != null) {
+ Node next = node.getNext();
+ context.render(node);
+ node = next;
+ }
+ }
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java
new file mode 100644
index 00000000000..00712579a5b
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ext.gfm.tables.internal;
+
+import jdk.internal.org.commonmark.ext.gfm.tables.*;
+import jdk.internal.org.commonmark.node.Node;
+import jdk.internal.org.commonmark.renderer.NodeRenderer;
+import jdk.internal.org.commonmark.renderer.markdown.MarkdownNodeRendererContext;
+import jdk.internal.org.commonmark.renderer.markdown.MarkdownWriter;
+import jdk.internal.org.commonmark.text.AsciiMatcher;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text content.
+ */
+public class TableMarkdownNodeRenderer extends TableNodeRenderer implements NodeRenderer {
+ private final MarkdownWriter writer;
+ private final MarkdownNodeRendererContext context;
+
+ private final AsciiMatcher pipe = AsciiMatcher.builder().c('|').build();
+
+ private final List columns = new ArrayList<>();
+
+ public TableMarkdownNodeRenderer(MarkdownNodeRendererContext context) {
+ this.writer = context.getWriter();
+ this.context = context;
+ }
+
+ @Override
+ protected void renderBlock(TableBlock node) {
+ columns.clear();
+ writer.pushTight(true);
+ renderChildren(node);
+ writer.popTight();
+ writer.block();
+ }
+
+ @Override
+ protected void renderHead(TableHead node) {
+ renderChildren(node);
+ for (TableCell.Alignment columnAlignment : columns) {
+ writer.raw('|');
+ if (columnAlignment == TableCell.Alignment.LEFT) {
+ writer.raw(":---");
+ } else if (columnAlignment == TableCell.Alignment.RIGHT) {
+ writer.raw("---:");
+ } else if (columnAlignment == TableCell.Alignment.CENTER) {
+ writer.raw(":---:");
+ } else {
+ writer.raw("---");
+ }
+ }
+ writer.raw("|");
+ writer.block();
+ }
+
+ @Override
+ protected void renderBody(TableBody node) {
+ renderChildren(node);
+ }
+
+ @Override
+ protected void renderRow(TableRow node) {
+ renderChildren(node);
+ // Trailing | at the end of the line
+ writer.raw("|");
+ writer.block();
+ }
+
+ @Override
+ protected void renderCell(TableCell node) {
+ if (node.getParent() != null && node.getParent().getParent() instanceof TableHead) {
+ columns.add(node.getAlignment());
+ }
+ writer.raw("|");
+ writer.pushRawEscape(pipe);
+ renderChildren(node);
+ writer.popRawEscape();
+ }
+
+ private void renderChildren(Node parent) {
+ Node node = parent.getFirstChild();
+ while (node != null) {
+ Node next = node.getNext();
+ context.render(node);
+ node = next;
+ }
+ }
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java
new file mode 100644
index 00000000000..c7b6beabb0d
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.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.ext.gfm.tables.internal;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import jdk.internal.org.commonmark.ext.gfm.tables.TableBlock;
+import jdk.internal.org.commonmark.ext.gfm.tables.TableBody;
+import jdk.internal.org.commonmark.ext.gfm.tables.TableCell;
+import jdk.internal.org.commonmark.ext.gfm.tables.TableHead;
+import jdk.internal.org.commonmark.ext.gfm.tables.TableRow;
+import jdk.internal.org.commonmark.node.Node;
+import jdk.internal.org.commonmark.renderer.NodeRenderer;
+
+abstract class TableNodeRenderer implements NodeRenderer {
+
+ @Override
+ public Set> getNodeTypes() {
+ return new HashSet<>(Arrays.asList(
+ TableBlock.class,
+ TableHead.class,
+ TableBody.class,
+ TableRow.class,
+ TableCell.class
+ ));
+ }
+
+ @Override
+ public void render(Node node) {
+ if (node instanceof TableBlock) {
+ renderBlock((TableBlock) node);
+ } else if (node instanceof TableHead) {
+ renderHead((TableHead) node);
+ } else if (node instanceof TableBody) {
+ renderBody((TableBody) node);
+ } else if (node instanceof TableRow) {
+ renderRow((TableRow) node);
+ } else if (node instanceof TableCell) {
+ renderCell((TableCell) node);
+ }
+ }
+
+ protected abstract void renderBlock(TableBlock node);
+
+ protected abstract void renderHead(TableHead node);
+
+ protected abstract void renderBody(TableBody node);
+
+ protected abstract void renderRow(TableRow node);
+
+ protected abstract void renderCell(TableCell node);
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java
new file mode 100644
index 00000000000..3b618474bce
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.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.ext.gfm.tables.internal;
+
+import jdk.internal.org.commonmark.ext.gfm.tables.TableBlock;
+import jdk.internal.org.commonmark.ext.gfm.tables.TableBody;
+import jdk.internal.org.commonmark.ext.gfm.tables.TableCell;
+import jdk.internal.org.commonmark.ext.gfm.tables.TableHead;
+import jdk.internal.org.commonmark.ext.gfm.tables.TableRow;
+import jdk.internal.org.commonmark.node.Node;
+import jdk.internal.org.commonmark.renderer.text.TextContentNodeRendererContext;
+import jdk.internal.org.commonmark.renderer.text.TextContentWriter;
+
+/**
+ * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text content.
+ */
+public class TableTextContentNodeRenderer extends TableNodeRenderer {
+
+ private final TextContentWriter textContentWriter;
+ private final TextContentNodeRendererContext context;
+
+ public TableTextContentNodeRenderer(TextContentNodeRendererContext context) {
+ this.textContentWriter = context.getWriter();
+ this.context = context;
+ }
+
+ protected void renderBlock(TableBlock tableBlock) {
+ renderChildren(tableBlock);
+ if (tableBlock.getNext() != null) {
+ textContentWriter.write("\n");
+ }
+ }
+
+ protected void renderHead(TableHead tableHead) {
+ renderChildren(tableHead);
+ }
+
+ protected void renderBody(TableBody tableBody) {
+ renderChildren(tableBody);
+ }
+
+ protected void renderRow(TableRow tableRow) {
+ textContentWriter.line();
+ renderChildren(tableRow);
+ textContentWriter.line();
+ }
+
+ protected void renderCell(TableCell tableCell) {
+ renderChildren(tableCell);
+ textContentWriter.write('|');
+ textContentWriter.whitespace();
+ }
+
+ private void renderLastCell(TableCell tableCell) {
+ renderChildren(tableCell);
+ }
+
+ private void renderChildren(Node parent) {
+ Node node = parent.getFirstChild();
+ while (node != null) {
+ Node next = node.getNext();
+
+ // For last cell in row, we dont render the delimiter.
+ if (node instanceof TableCell && next == null) {
+ renderLastCell((TableCell) node);
+ } else {
+ context.render(node);
+ }
+
+ node = next;
+ }
+ }
+}
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 00000000000..64e9e5f7bf1
--- /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 00000000000..47697018e40
--- /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 00000000000..db6f81976b4
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/BlockQuoteParser.java
@@ -0,0 +1,95 @@
+/*
+ * 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.*;
+import jdk.internal.org.commonmark.text.Characters;
+
+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 (Characters.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 (Characters.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 00000000000..bde28136b82
--- /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 00000000000..373d3ac1663
--- /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.node.Text;
+import jdk.internal.org.commonmark.parser.beta.Position;
+
+/**
+ * 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 00000000000..8437594dd6d
--- /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 00000000000..8f18feed1a0
--- /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 00000000000..34ac293d891
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/DocumentParser.java
@@ -0,0 +1,639 @@
+/*
+ * 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 jdk.internal.org.commonmark.text.Characters;
+
+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 extends Block> blockType : enabledBlockTypes) {
+ list.add(NODES_TO_CORE_FACTORIES.get(blockType));
+ }
+ return list;
+ }
+
+ public static void checkEnabledBlockTypes(Set> enabledBlockTypes) {
+ for (Class extends Block> 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 = Characters.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 && Characters.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 = 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);
+ }
+ }
+
+ /**
+ * Prepares the input line replacing {@code \0}
+ */
+ private 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;
+ }
+ }
+
+ 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 00000000000..10a962a45c5
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/FencedCodeBlockParser.java
@@ -0,0 +1,171 @@
+/*
+ * 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 jdk.internal.org.commonmark.text.Characters;
+
+import static jdk.internal.org.commonmark.internal.util.Escaping.unescapeString;
+
+public class FencedCodeBlockParser extends AbstractBlockParser {
+
+ private final FencedCodeBlock block = new FencedCodeBlock();
+ private final char fenceChar;
+ private final int openingFenceLength;
+
+ private String firstLine;
+ private StringBuilder otherLines = new StringBuilder();
+
+ public FencedCodeBlockParser(char fenceChar, int fenceLength, int fenceIndent) {
+ this.fenceChar = fenceChar;
+ this.openingFenceLength = fenceLength;
+ block.setFenceCharacter(String.valueOf(fenceChar));
+ block.setOpeningFenceLength(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() && tryClosing(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.getOpeningFenceLength());
+ } 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 (Characters.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 tryClosing(CharSequence line, int index) {
+ int fences = Characters.skip(fenceChar, line, index, line.length()) - index;
+ if (fences < openingFenceLength) {
+ return false;
+ }
+ // spec: The closing code fence [...] may be followed only by spaces, which are ignored.
+ int after = Characters.skipSpaceTab(line, index + fences, line.length());
+ if (after == line.length()) {
+ block.setClosingFenceLength(fences);
+ return true;
+ }
+ return false;
+ }
+}
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 00000000000..cea8cb10366
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/HeadingParser.java
@@ -0,0 +1,188 @@
+/*
+ * 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.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.beta.Position;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+import jdk.internal.org.commonmark.parser.block.*;
+import jdk.internal.org.commonmark.text.Characters;
+
+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 = Characters.skip(marker, line, index, line.length());
+ int afterSpace = Characters.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 00000000000..4fb7eb33da1
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/HtmlBlockParser.java
@@ -0,0 +1,178 @@
+/*
+ * 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.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 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
+ + "?)";
+
+ private static final String OPENTAG = "<" + TAGNAME + ATTRIBUTE + "*" + "\\s*/?>";
+ private static final String CLOSETAG = "" + TAGNAME + "\\s*[>]";
+
+ 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("(?:script|pre|style|textarea)>", Pattern.CASE_INSENSITIVE)
+ },
+ {
+ Pattern.compile("^")
+ },
+ {
+ Pattern.compile("^<[?]"),
+ Pattern.compile("\\?>")
+ },
+ {
+ Pattern.compile("^")
+ },
+ {
+ Pattern.compile("^")
+ },
+ {
+ Pattern.compile("^?(?:" +
+ "address|article|aside|" +
+ "base|basefont|blockquote|body|" +
+ "caption|center|col|colgroup|" +
+ "dd|details|dialog|dir|div|dl|dt|" +
+ "fieldset|figcaption|figure|footer|form|frame|frameset|" +
+ "h1|h2|h3|h4|h5|h6|head|header|hr|html|" +
+ "iframe|" +
+ "legend|li|link|" +
+ "main|menu|menuitem|" +
+ "nav|noframes|" +
+ "ol|optgroup|option|" +
+ "p|param|" +
+ "search|section|summary|" +
+ "table|tbody|td|tfoot|th|thead|title|tr|track|" +
+ "ul" +
+ ")(?:\\s|[/]?[>]|$)", Pattern.CASE_INSENSITIVE),
+ null // terminated by blank line
+ },
+ {
+ Pattern.compile("^(?:" + OPENTAG + '|' + 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 00000000000..0b988407c0d
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/IndentedCodeBlockParser.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.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 jdk.internal.org.commonmark.text.Characters;
+
+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 (!Characters.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 00000000000..dab02aeedd1
--- /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 00000000000..3c0913a3206
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/InlineParserImpl.java
@@ -0,0 +1,787 @@
+/*
+ * 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.*;
+import jdk.internal.org.commonmark.internal.util.Escaping;
+import jdk.internal.org.commonmark.internal.util.LinkScanner;
+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.beta.Position;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+import jdk.internal.org.commonmark.parser.delimiter.DelimiterProcessor;
+import jdk.internal.org.commonmark.text.Characters;
+
+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 extends Node> 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 extends Node> 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 extends Node> 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 extends Node> 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 ``
+ 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 = Characters.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 = Characters.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 || Characters.isPunctuationCodePoint(before);
+ boolean beforeIsWhitespace = before == Scanner.END || Characters.isWhitespaceCodePoint(before);
+ boolean afterIsPunctuation = after == Scanner.END || Characters.isPunctuationCodePoint(after);
+ boolean afterIsWhitespace = after == Scanner.END || Characters.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 00000000000..35db88cc2ba
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/LinkReferenceDefinitionParser.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;
+
+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 jdk.internal.org.commonmark.parser.beta.Position;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+
+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) {
+ // Finish any outstanding references now. We don't do this earlier because we need addSourceSpan to have been
+ // called before we do it.
+ finishReference();
+
+ 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 {
+ // 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;
+ 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 00000000000..e1def1de98e
--- /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 00000000000..89a3c2332cb
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ListBlockParser.java
@@ -0,0 +1,286 @@
+/*
+ * 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.*;
+
+import java.util.Objects;
+
+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).getMarkerStartNumber() != 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.setMarker(String.valueOf(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.setMarkerStartNumber(Integer.parseInt(number));
+ orderedList.setMarkerDelimiter(String.valueOf(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 Objects.equals(((BulletList) a).getMarker(), ((BulletList) b).getMarker());
+ } else if (a instanceof OrderedList && b instanceof OrderedList) {
+ return Objects.equals(((OrderedList) a).getMarkerDelimiter(), ((OrderedList) b).getMarkerDelimiter());
+ }
+ return false;
+ }
+
+ 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(state.getIndent(), 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 00000000000..e6401deed9f
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ListItemParser.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.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 markerIndent, int contentIndent) {
+ this.contentIndent = contentIndent;
+ block.setMarkerIndent(markerIndent);
+ block.setContentIndent(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 00000000000..7cbc28e2cb2
--- /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 00000000000..995f9eac270
--- /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 00000000000..ae0abed6d71
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/ThematicBreakParser.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.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();
+
+ public ThematicBreakParser(String literal) {
+ block.setLiteral(literal);
+ }
+
+ @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)) {
+ var literal = String.valueOf(line.subSequence(state.getIndex(), line.length()));
+ return BlockStart.of(new ThematicBreakParser(literal)).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 00000000000..ff205e8bd22
--- /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 00000000000..28ce45d9c4c
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/AutolinkInlineParser.java
@@ -0,0 +1,81 @@
+/*
+ * 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 jdk.internal.org.commonmark.parser.beta.Position;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+
+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 00000000000..e18d337ad8a
--- /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.Text;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+
+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 00000000000..06b80ab3ebd
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/BackticksInlineParser.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.internal.inline;
+
+import jdk.internal.org.commonmark.node.Code;
+import jdk.internal.org.commonmark.node.Text;
+import jdk.internal.org.commonmark.parser.SourceLines;
+import jdk.internal.org.commonmark.parser.beta.Position;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+import jdk.internal.org.commonmark.text.Characters;
+
+/**
+ * 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) == ' ' &&
+ Characters.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 00000000000..0a4c3c45140
--- /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 00000000000..cf4e3d2004b
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/EntityInlineParser.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.internal.inline;
+
+import jdk.internal.org.commonmark.text.AsciiMatcher;
+import jdk.internal.org.commonmark.internal.util.Html5Entities;
+import jdk.internal.org.commonmark.node.Text;
+import jdk.internal.org.commonmark.parser.beta.Position;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+
+/**
+ * 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 00000000000..88f4baa2275
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/HtmlInlineParser.java
@@ -0,0 +1,235 @@
+/*
+ * 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.text.AsciiMatcher;
+import jdk.internal.org.commonmark.node.HtmlInline;
+import jdk.internal.org.commonmark.parser.beta.Position;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+
+/**
+ * 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 , a tag name, optional whitespace, and the character >.
+ 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 , a string of characters not including 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 ``, ``, or ``, and `-->` (see the
+ // [HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)).
+
+ // Skip first `-`
+ scanner.next();
+ if (!scanner.next('-')) {
+ return false;
+ }
+
+ if (scanner.next('>') || scanner.next("->")) {
+ return true;
+ }
+
+ while (scanner.find('-') >= 0) {
+ if (scanner.next("-->")) {
+ return true;
+ } 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 00000000000..66feff21784
--- /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 00000000000..6890364fca0
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/InlineParserState.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;
+
+import jdk.internal.org.commonmark.parser.beta.Position;
+import jdk.internal.org.commonmark.parser.beta.Scanner;
+
+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 00000000000..2aed7db83d0
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/ParsedInline.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.internal.inline;
+
+import jdk.internal.org.commonmark.node.Node;
+import jdk.internal.org.commonmark.parser.beta.Position;
+
+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 00000000000..973149a83b4
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/inline/ParsedInlineImpl.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.internal.inline;
+
+import jdk.internal.org.commonmark.node.Node;
+import jdk.internal.org.commonmark.parser.beta.Position;
+
+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 00000000000..fa3767083e3
--- /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 00000000000..a32d0564f7d
--- /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 00000000000..be297441d97
--- /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 00000000000..523609f21de
--- /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 extends Node> 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 00000000000..154bad58278
--- /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 String marker;
+
+ public BulletListHolder(ListHolder parent, BulletList list) {
+ super(parent);
+ marker = list.getMarker();
+ }
+
+ public String 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 00000000000..6334f4cffad
--- /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 00000000000..114c4dfeb29
--- /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 String delimiter;
+ private int counter;
+
+ public OrderedListHolder(ListHolder parent, OrderedList list) {
+ super(parent);
+ delimiter = list.getMarkerDelimiter() != null ? list.getMarkerDelimiter() : ".";
+ counter = list.getMarkerStartNumber() != null ? list.getMarkerStartNumber() : 1;
+ }
+
+ public String 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 00000000000..915c3078b7a
--- /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 00000000000..e74cb314ff3
--- /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 00000000000..939a4c69860
--- /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 00000000000..a300cd0438a
--- /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 00000000000..b3f80c173d1
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/LinkScanner.java
@@ -0,0 +1,234 @@
+/*
+ * 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.parser.beta.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 (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 (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 (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 (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;
+ }
+
+ private 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;
+ }
+}
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 00000000000..34c8cf89ac6
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/internal/util/Parsing.java
@@ -0,0 +1,42 @@
+/*
+ * 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 {
+ public static int CODE_BLOCK_INDENT = 4;
+
+ public static int columnsToNextTabStop(int column) {
+ // Tab stop is 4
+ return 4 - (column % 4);
+ }
+}
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 00000000000..5b126923ba8
--- /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 00000000000..982a6b56550
--- /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 00000000000..57603fa5fdd
--- /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 00000000000..8a5f1d20c07
--- /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 00000000000..75ab053da3f
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/BulletList.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.node;
+
+public class BulletList extends ListBlock {
+
+ private String marker;
+
+ @Override
+ public void accept(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ /**
+ * @return the bullet list marker that was used, e.g. {@code -}, {@code *} or {@code +}, if available, or null otherwise
+ */
+ public String getMarker() {
+ return marker;
+ }
+
+ public void setMarker(String marker) {
+ this.marker = marker;
+ }
+
+ /**
+ * @deprecated use {@link #getMarker()} instead
+ */
+ @Deprecated
+ public char getBulletMarker() {
+ return marker != null && !marker.isEmpty() ? marker.charAt(0) : '\0';
+ }
+
+ /**
+ * @deprecated use {@link #getMarker()} instead
+ */
+ @Deprecated
+ public void setBulletMarker(char bulletMarker) {
+ this.marker = bulletMarker != '\0' ? String.valueOf(bulletMarker) : null;
+ }
+}
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 00000000000..b64ddefa1f1
--- /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 00000000000..08038848ada
--- /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 00000000000..733df320afc
--- /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 00000000000..b887aa3f75a
--- /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 00000000000..390728318ec
--- /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 00000000000..86b09f4f2f6
--- /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 00000000000..d78b3e4292a
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/FencedCodeBlock.java
@@ -0,0 +1,159 @@
+/*
+ * 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 String fenceCharacter;
+ private Integer openingFenceLength;
+ private Integer closingFenceLength;
+ private int fenceIndent;
+
+ private String info;
+ private String literal;
+
+ @Override
+ public void accept(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ /**
+ * @return the fence character that was used, e.g. {@code `} or {@code ~}, if available, or null otherwise
+ */
+ public String getFenceCharacter() {
+ return fenceCharacter;
+ }
+
+ public void setFenceCharacter(String fenceCharacter) {
+ this.fenceCharacter = fenceCharacter;
+ }
+
+ /**
+ * @return the length of the opening fence (how many of {{@link #getFenceCharacter()}} were used to start the code
+ * block) if available, or null otherwise
+ */
+ public Integer getOpeningFenceLength() {
+ return openingFenceLength;
+ }
+
+ public void setOpeningFenceLength(Integer openingFenceLength) {
+ if (openingFenceLength != null && openingFenceLength < 3) {
+ throw new IllegalArgumentException("openingFenceLength needs to be >= 3");
+ }
+ checkFenceLengths(openingFenceLength, closingFenceLength);
+ this.openingFenceLength = openingFenceLength;
+ }
+
+ /**
+ * @return the length of the closing fence (how many of {@link #getFenceCharacter()} were used to end the code
+ * block) if available, or null otherwise
+ */
+ public Integer getClosingFenceLength() {
+ return closingFenceLength;
+ }
+
+ public void setClosingFenceLength(Integer closingFenceLength) {
+ if (closingFenceLength != null && closingFenceLength < 3) {
+ throw new IllegalArgumentException("closingFenceLength needs to be >= 3");
+ }
+ checkFenceLengths(openingFenceLength, closingFenceLength);
+ this.closingFenceLength = closingFenceLength;
+ }
+
+ 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;
+ }
+
+ /**
+ * @deprecated use {@link #getFenceCharacter()} instead
+ */
+ @Deprecated
+ public char getFenceChar() {
+ return fenceCharacter != null && !fenceCharacter.isEmpty() ? fenceCharacter.charAt(0) : '\0';
+ }
+
+ /**
+ * @deprecated use {@link #setFenceCharacter} instead
+ */
+ @Deprecated
+ public void setFenceChar(char fenceChar) {
+ this.fenceCharacter = fenceChar != '\0' ? String.valueOf(fenceChar) : null;
+ }
+
+ /**
+ * @deprecated use {@link #getOpeningFenceLength} instead
+ */
+ @Deprecated
+ public int getFenceLength() {
+ return openingFenceLength != null ? openingFenceLength : 0;
+ }
+
+ /**
+ * @deprecated use {@link #setOpeningFenceLength} instead
+ */
+ @Deprecated
+ public void setFenceLength(int fenceLength) {
+ this.openingFenceLength = fenceLength != 0 ? fenceLength : null;
+ }
+
+ private static void checkFenceLengths(Integer openingFenceLength, Integer closingFenceLength) {
+ if (openingFenceLength != null && closingFenceLength != null) {
+ if (closingFenceLength < openingFenceLength) {
+ throw new IllegalArgumentException("fence lengths required to be: closingFenceLength >= openingFenceLength");
+ }
+ }
+ }
+}
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 00000000000..5c85504a8fb
--- /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 00000000000..49a8225fca1
--- /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 00000000000..94380c084e1
--- /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 00000000000..7402606ac83
--- /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 00000000000..36a3f2f7221
--- /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 00000000000..d3b4cec41d8
--- /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 00000000000..448c0b063db
--- /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 00000000000..9f3e7107e78
--- /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 00000000000..67cccde093a
--- /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 00000000000..6e1a5d15fb3
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ListItem.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;
+
+public class ListItem extends Block {
+
+ private Integer markerIndent;
+ private Integer contentIndent;
+
+ @Override
+ public void accept(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ /**
+ * Returns the indent of the marker such as "-" or "1." in columns (spaces or tab stop of 4) if available, or null
+ * otherwise.
+ *
+ * Some examples and their marker indent:
+ *
- Foo
+ * Marker indent: 0
+ * - Foo
+ * Marker indent: 1
+ * 1. Foo
+ * Marker indent: 2
+ */
+ public Integer getMarkerIndent() {
+ return markerIndent;
+ }
+
+ public void setMarkerIndent(Integer markerIndent) {
+ this.markerIndent = markerIndent;
+ }
+
+ /**
+ * Returns the indent of the content in columns (spaces or tab stop of 4) if available, or null otherwise.
+ * The content indent is counted from the beginning of the line and includes the marker on the first line.
+ *
+ * Some examples and their content indent:
+ *
- Foo
+ * Content indent: 2
+ * - Foo
+ * Content indent: 3
+ * 1. Foo
+ * Content indent: 5
+ *
+ * Note that subsequent lines in the same list item need to be indented by at least the content indent to be counted
+ * as part of the list item.
+ */
+ public Integer getContentIndent() {
+ return contentIndent;
+ }
+
+ public void setContentIndent(Integer contentIndent) {
+ this.contentIndent = contentIndent;
+ }
+}
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 00000000000..1607ec2ef5f
--- /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 00000000000..5be07004284
--- /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 00000000000..ebee8422f08
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/OrderedList.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;
+
+public class OrderedList extends ListBlock {
+
+ private String markerDelimiter;
+ private Integer markerStartNumber;
+
+ @Override
+ public void accept(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ /**
+ * @return the start number used in the marker, e.g. {@code 1}, if available, or null otherwise
+ */
+ public Integer getMarkerStartNumber() {
+ return markerStartNumber;
+ }
+
+ public void setMarkerStartNumber(Integer markerStartNumber) {
+ this.markerStartNumber = markerStartNumber;
+ }
+
+ /**
+ * @return the delimiter used in the marker, e.g. {@code .} or {@code )}, if available, or null otherwise
+ */
+ public String getMarkerDelimiter() {
+ return markerDelimiter;
+ }
+
+ public void setMarkerDelimiter(String markerDelimiter) {
+ this.markerDelimiter = markerDelimiter;
+ }
+
+ /**
+ * @deprecated use {@link #getMarkerStartNumber()} instead
+ */
+ @Deprecated
+ public int getStartNumber() {
+ return markerStartNumber != null ? markerStartNumber : 0;
+ }
+
+ /**
+ * @deprecated use {@link #setMarkerStartNumber} instead
+ */
+ @Deprecated
+ public void setStartNumber(int startNumber) {
+ this.markerStartNumber = startNumber != 0 ? startNumber : null;
+ }
+
+ /**
+ * @deprecated use {@link #getMarkerDelimiter()} instead
+ */
+ @Deprecated
+ public char getDelimiter() {
+ return markerDelimiter != null && !markerDelimiter.isEmpty() ? markerDelimiter.charAt(0) : '\0';
+ }
+
+ /**
+ * @deprecated use {@link #setMarkerDelimiter} instead
+ */
+ @Deprecated
+ public void setDelimiter(char delimiter) {
+ this.markerDelimiter = delimiter != '\0' ? String.valueOf(delimiter) : null;
+ }
+}
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 00000000000..32168c0251e
--- /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 00000000000..1fedc5c899b
--- /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 00000000000..b816a8bf437
--- /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 00000000000..4a35a89840d
--- /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 extends Node> 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 00000000000..1efed6d9929
--- /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 00000000000..976134d1dd0
--- /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 00000000000..8be13266a33
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/ThematicBreak.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.node;
+
+public class ThematicBreak extends Block {
+
+ private String literal;
+
+ @Override
+ public void accept(Visitor visitor) {
+ visitor.visit(this);
+ }
+
+ /**
+ * @return the source literal that represents this node, if available
+ */
+ 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/Visitor.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/node/Visitor.java
new file mode 100644
index 00000000000..460a6876c28
--- /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 00000000000..e957ac5b772
--- /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 00000000000..f3dcba6dc10
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/package-info.java
@@ -0,0 +1,42 @@
+/*
+ * 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
+ * - {@link org.commonmark.renderer.markdown} for Markdown 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 00000000000..27d91f96e76
--- /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 00000000000..e0e4246eb09
--- /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 00000000000..b28faff22be
--- /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 00000000000..6fb0860f48e
--- /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 00000000000..40712b97873
--- /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 extends Extension> 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 ()
+ *
+ * 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 00000000000..5cf4f480ae0
--- /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 00000000000..4008e6bece0
--- /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 00000000000..59bed5c5bf6
--- /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/beta/Position.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/beta/Position.java
new file mode 100644
index 00000000000..754e409001e
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/beta/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.parser.beta;
+
+/**
+ * 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/parser/beta/Scanner.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/beta/Scanner.java
new file mode 100644
index 00000000000..e6b1dfaa347
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/beta/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.parser.beta;
+
+import jdk.internal.org.commonmark.node.SourceSpan;
+import jdk.internal.org.commonmark.parser.SourceLine;
+import jdk.internal.org.commonmark.parser.SourceLines;
+import jdk.internal.org.commonmark.text.CharMatcher;
+
+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/parser/beta/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/beta/package-info.java
new file mode 100644
index 00000000000..549b5073e19
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/beta/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.
+ */
+
+/**
+ * Experimental APIs to use for extensions. APIs are subject to change if necessary.
+ */
+package jdk.internal.org.commonmark.parser.beta;
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 00000000000..26cd6220daf
--- /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 00000000000..40c2bf91b66
--- /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 00000000000..5fc4e028d22
--- /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 00000000000..e893e70b0ef
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/parser/block/BlockParser.java
@@ -0,0 +1,88 @@
+/*
+ * 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);
+
+ /**
+ * Add the part of a line that belongs to this block parser to parse (i.e. without any container block markers).
+ * Note that the line will only include a {@link SourceLine#getSourceSpan()} if source spans are enabled for inlines.
+ */
+ 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 00000000000..53cc8d35ca7
--- /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 00000000000..a927abe968b
--- /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 00000000000..76a9669d069
--- /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 00000000000..7bda0620a8a
--- /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 00000000000..7bcb61e8a86
--- /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 00000000000..592f6a5e433
--- /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 00000000000..9672c1906cf
--- /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 00000000000..b7bc957cc06
--- /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 00000000000..00d6c185920
--- /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 00000000000..9cee90af7d9
--- /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 00000000000..4c9b33adb80
--- /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 00000000000..92ca0383b74
--- /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 00000000000..e4503daffab
--- /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 00000000000..646cc7413ac
--- /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.getMarkerStartNumber() != null ? orderedList.getMarkerStartNumber() : 1;
+ 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 00000000000..16e094afe16
--- /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 00000000000..78dc295cb21
--- /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 00000000000..c86d976ae0b
--- /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 00000000000..d4b5214211a
--- /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 extends Extension> 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 00000000000..341c428a3ee
--- /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 00000000000..12c69a73821
--- /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 00000000000..41dd2c6c6cc
--- /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/markdown/CoreMarkdownNodeRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java
new file mode 100644
index 00000000000..4d543ea1174
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java
@@ -0,0 +1,582 @@
+/*
+ * 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.markdown;
+
+import jdk.internal.org.commonmark.text.AsciiMatcher;
+import jdk.internal.org.commonmark.node.*;
+import jdk.internal.org.commonmark.text.CharMatcher;
+import jdk.internal.org.commonmark.renderer.NodeRenderer;
+import jdk.internal.org.commonmark.text.Characters;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The node renderer that renders all the core nodes (comes last in the order of node renderers).
+ *
+ * Note that while sometimes it would be easier to record what kind of syntax was used on parsing (e.g. ATX vs Setext
+ * heading), this renderer is intended to also work for documents that were created by directly creating
+ * {@link Node Nodes} instead. So in order to support that, it sometimes needs to do a bit more work.
+ */
+public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer {
+
+ private final AsciiMatcher textEscape;
+ private final CharMatcher textEscapeInHeading;
+ private final CharMatcher linkDestinationNeedsAngleBrackets =
+ AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\n').c('\\').build();
+ private final CharMatcher linkDestinationEscapeInAngleBrackets =
+ AsciiMatcher.builder().c('<').c('>').c('\n').c('\\').build();
+ private final CharMatcher linkTitleEscapeInQuotes =
+ AsciiMatcher.builder().c('"').c('\n').c('\\').build();
+
+ private final Pattern orderedListMarkerPattern = Pattern.compile("^([0-9]{1,9})([.)])");
+
+ protected final MarkdownNodeRendererContext context;
+ private final MarkdownWriter writer;
+ /**
+ * If we're currently within a {@link BulletList} or {@link OrderedList}, this keeps the context of that list.
+ * It has a parent field so that it can represent a stack (for nested lists).
+ */
+ private ListHolder listHolder;
+
+ public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) {
+ this.context = context;
+ this.writer = context.getWriter();
+
+ textEscape = AsciiMatcher.builder().anyOf("[]<>`*_&\n\\").anyOf(context.getSpecialCharacters()).build();
+ textEscapeInHeading = AsciiMatcher.builder(textEscape).anyOf("#").build();
+ }
+
+ @Override
+ public Set> getNodeTypes() {
+ return new HashSet<>(Arrays.asList(
+ BlockQuote.class,
+ BulletList.class,
+ Code.class,
+ Document.class,
+ Emphasis.class,
+ FencedCodeBlock.class,
+ HardLineBreak.class,
+ Heading.class,
+ HtmlBlock.class,
+ HtmlInline.class,
+ Image.class,
+ IndentedCodeBlock.class,
+ Link.class,
+ ListItem.class,
+ OrderedList.class,
+ Paragraph.class,
+ SoftLineBreak.class,
+ StrongEmphasis.class,
+ Text.class,
+ ThematicBreak.class
+ ));
+ }
+
+ @Override
+ public void render(Node node) {
+ node.accept(this);
+ }
+
+ @Override
+ public void visit(Document document) {
+ // No rendering itself
+ visitChildren(document);
+ writer.line();
+ }
+
+ @Override
+ public void visit(ThematicBreak thematicBreak) {
+ // Let's use ___ as it doesn't introduce ambiguity with * or - list item markers
+ writer.raw("___");
+ writer.block();
+ }
+
+ @Override
+ public void visit(Heading heading) {
+ if (heading.getLevel() <= 2) {
+ LineBreakVisitor lineBreakVisitor = new LineBreakVisitor();
+ heading.accept(lineBreakVisitor);
+ boolean isMultipleLines = lineBreakVisitor.hasLineBreak();
+
+ if (isMultipleLines) {
+ // Setext headings: Can have multiple lines, but only level 1 or 2
+ visitChildren(heading);
+ writer.line();
+ if (heading.getLevel() == 1) {
+ // Note that it would be nice to match the length of the contents instead of just using 3, but that's
+ // not easy.
+ writer.raw("===");
+ } else {
+ writer.raw("---");
+ }
+ writer.block();
+ return;
+ }
+ }
+
+ // ATX headings: Can't have multiple lines, but up to level 6.
+ for (int i = 0; i < heading.getLevel(); i++) {
+ writer.raw('#');
+ }
+ writer.raw(' ');
+ visitChildren(heading);
+
+ writer.block();
+ }
+
+ @Override
+ public void visit(IndentedCodeBlock indentedCodeBlock) {
+ String literal = indentedCodeBlock.getLiteral();
+ // We need to respect line prefixes which is why we need to write it line by line (e.g. an indented code block
+ // within a block quote)
+ writer.writePrefix(" ");
+ writer.pushPrefix(" ");
+ List lines = getLines(literal);
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ writer.raw(line);
+ if (i != lines.size() - 1) {
+ writer.line();
+ }
+ }
+ writer.popPrefix();
+ writer.block();
+ }
+
+ @Override
+ public void visit(FencedCodeBlock codeBlock) {
+ String literal = codeBlock.getLiteral();
+ String fenceChar = codeBlock.getFenceCharacter() != null ? codeBlock.getFenceCharacter() : "`";
+ int openingFenceLength;
+ if (codeBlock.getOpeningFenceLength() != null) {
+ // If we have a known fence length, use it
+ openingFenceLength = codeBlock.getOpeningFenceLength();
+ } else {
+ // Otherwise, calculate the closing fence length pessimistically, e.g. if the code block itself contains a
+ // line with ```, we need to use a fence of length 4. If ``` occurs with non-whitespace characters on a
+ // line, we technically don't need a longer fence, but it's not incorrect to do so.
+ int fenceCharsInLiteral = findMaxRunLength(fenceChar, literal);
+ openingFenceLength = Math.max(fenceCharsInLiteral + 1, 3);
+ }
+ int closingFenceLength = codeBlock.getClosingFenceLength() != null ? codeBlock.getClosingFenceLength() : openingFenceLength;
+
+ String openingFence = repeat(fenceChar, openingFenceLength);
+ String closingFence = repeat(fenceChar, closingFenceLength);
+ int indent = codeBlock.getFenceIndent();
+
+ if (indent > 0) {
+ String indentPrefix = repeat(" ", indent);
+ writer.writePrefix(indentPrefix);
+ writer.pushPrefix(indentPrefix);
+ }
+
+ writer.raw(openingFence);
+ if (codeBlock.getInfo() != null) {
+ writer.raw(codeBlock.getInfo());
+ }
+ writer.line();
+ if (!literal.isEmpty()) {
+ List lines = getLines(literal);
+ for (String line : lines) {
+ writer.raw(line);
+ writer.line();
+ }
+ }
+ writer.raw(closingFence);
+ if (indent > 0) {
+ writer.popPrefix();
+ }
+ writer.block();
+ }
+
+ @Override
+ public void visit(HtmlBlock htmlBlock) {
+ List lines = getLines(htmlBlock.getLiteral());
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ writer.raw(line);
+ if (i != lines.size() - 1) {
+ writer.line();
+ }
+ }
+ writer.block();
+ }
+
+ @Override
+ public void visit(Paragraph paragraph) {
+ visitChildren(paragraph);
+ writer.block();
+ }
+
+ @Override
+ public void visit(BlockQuote blockQuote) {
+ writer.writePrefix("> ");
+ writer.pushPrefix("> ");
+ visitChildren(blockQuote);
+ writer.popPrefix();
+ writer.block();
+ }
+
+ @Override
+ public void visit(BulletList bulletList) {
+ writer.pushTight(bulletList.isTight());
+ listHolder = new BulletListHolder(listHolder, bulletList);
+ visitChildren(bulletList);
+ listHolder = listHolder.parent;
+ writer.popTight();
+ writer.block();
+ }
+
+ @Override
+ public void visit(OrderedList orderedList) {
+ writer.pushTight(orderedList.isTight());
+ listHolder = new OrderedListHolder(listHolder, orderedList);
+ visitChildren(orderedList);
+ listHolder = listHolder.parent;
+ writer.popTight();
+ writer.block();
+ }
+
+ @Override
+ public void visit(ListItem listItem) {
+ int markerIndent = listItem.getMarkerIndent() != null ? listItem.getMarkerIndent() : 0;
+ String marker;
+ if (listHolder instanceof BulletListHolder) {
+ BulletListHolder bulletListHolder = (BulletListHolder) listHolder;
+ marker = repeat(" ", markerIndent) + bulletListHolder.marker;
+ } else if (listHolder instanceof OrderedListHolder) {
+ OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder;
+ marker = repeat(" ", markerIndent) + orderedListHolder.number + orderedListHolder.delimiter;
+ orderedListHolder.number++;
+ } else {
+ throw new IllegalStateException("Unknown list holder type: " + listHolder);
+ }
+ Integer contentIndent = listItem.getContentIndent();
+ String spaces = contentIndent != null ? repeat(" ", contentIndent - marker.length()) : " ";
+ writer.writePrefix(marker);
+ writer.writePrefix(spaces);
+ writer.pushPrefix(repeat(" ", marker.length() + spaces.length()));
+
+ if (listItem.getFirstChild() == null) {
+ // Empty list item
+ writer.block();
+ } else {
+ visitChildren(listItem);
+ }
+
+ writer.popPrefix();
+ }
+
+ @Override
+ public void visit(Code code) {
+ String literal = code.getLiteral();
+ // If the literal includes backticks, we can surround them by using one more backtick.
+ int backticks = findMaxRunLength("`", literal);
+ for (int i = 0; i < backticks + 1; i++) {
+ writer.raw('`');
+ }
+ // If the literal starts or ends with a backtick, surround it with a single space.
+ // If it starts and ends with a space (but is not only spaces), add an additional space (otherwise they would
+ // get removed on parsing).
+ boolean addSpace = literal.startsWith("`") || literal.endsWith("`") ||
+ (literal.startsWith(" ") && literal.endsWith(" ") && Characters.hasNonSpace(literal));
+ if (addSpace) {
+ writer.raw(' ');
+ }
+ writer.raw(literal);
+ if (addSpace) {
+ writer.raw(' ');
+ }
+ for (int i = 0; i < backticks + 1; i++) {
+ writer.raw('`');
+ }
+ }
+
+ @Override
+ public void visit(Emphasis emphasis) {
+ String delimiter = emphasis.getOpeningDelimiter();
+ // Use delimiter that was parsed if available
+ if (delimiter == null) {
+ // When emphasis is nested, a different delimiter needs to be used
+ delimiter = writer.getLastChar() == '*' ? "_" : "*";
+ }
+ writer.raw(delimiter);
+ super.visit(emphasis);
+ writer.raw(delimiter);
+ }
+
+ @Override
+ public void visit(StrongEmphasis strongEmphasis) {
+ writer.raw("**");
+ super.visit(strongEmphasis);
+ writer.raw("**");
+ }
+
+ @Override
+ public void visit(Link link) {
+ writeLinkLike(link.getTitle(), link.getDestination(), link, "[");
+ }
+
+ @Override
+ public void visit(Image image) {
+ writeLinkLike(image.getTitle(), image.getDestination(), image, "![");
+ }
+
+ @Override
+ public void visit(HtmlInline htmlInline) {
+ writer.raw(htmlInline.getLiteral());
+ }
+
+ @Override
+ public void visit(HardLineBreak hardLineBreak) {
+ writer.raw(" ");
+ writer.line();
+ }
+
+ @Override
+ public void visit(SoftLineBreak softLineBreak) {
+ writer.line();
+ }
+
+ @Override
+ public void visit(Text text) {
+ // Text is tricky. In Markdown special characters (`-`, `#` etc.) can be escaped (`\-`, `\#` etc.) so that
+ // they're parsed as plain text. Currently, whether a character was escaped or not is not recorded in the Node,
+ // so here we don't know. If we just wrote out those characters unescaped, the resulting Markdown would change
+ // meaning (turn into a list item, heading, etc.).
+ // You might say "Why not store that in the Node when parsing", but that wouldn't work for the use case where
+ // nodes are constructed directly instead of via parsing. This renderer needs to work for that too.
+ // So currently, when in doubt, we escape. For special characters only occurring at the beginning of a line,
+ // we only escape them then (we wouldn't want to escape every `.` for example).
+ String literal = text.getLiteral();
+ if (writer.isAtLineStart() && !literal.isEmpty()) {
+ char c = literal.charAt(0);
+ switch (c) {
+ case '-': {
+ // Would be ambiguous with a bullet list marker, escape
+ writer.raw("\\-");
+ literal = literal.substring(1);
+ break;
+ }
+ case '#': {
+ // Would be ambiguous with an ATX heading, escape
+ writer.raw("\\#");
+ literal = literal.substring(1);
+ break;
+ }
+ case '=': {
+ // Would be ambiguous with a Setext heading, escape
+ writer.raw("\\=");
+ literal = literal.substring(1);
+ break;
+ }
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9': {
+ // Check for ordered list marker
+ Matcher m = orderedListMarkerPattern.matcher(literal);
+ if (m.find()) {
+ writer.raw(m.group(1));
+ writer.raw("\\" + m.group(2));
+ literal = literal.substring(m.end());
+ }
+ break;
+ }
+ case '\t': {
+ writer.raw(" ");
+ literal = literal.substring(1);
+ break;
+ }
+ case ' ': {
+ writer.raw(" ");
+ literal = literal.substring(1);
+ break;
+ }
+ }
+ }
+
+ CharMatcher escape = text.getParent() instanceof Heading ? textEscapeInHeading : textEscape;
+
+ if (literal.endsWith("!") && text.getNext() instanceof Link) {
+ // If we wrote the `!` unescaped, it would turn the link into an image instead.
+ writer.text(literal.substring(0, literal.length() - 1), escape);
+ writer.raw("\\!");
+ } else {
+ writer.text(literal, escape);
+ }
+ }
+
+ @Override
+ protected void visitChildren(Node parent) {
+ Node node = parent.getFirstChild();
+ while (node != null) {
+ Node next = node.getNext();
+ context.render(node);
+ node = next;
+ }
+ }
+
+ private static int findMaxRunLength(String needle, String s) {
+ int maxRunLength = 0;
+ int pos = 0;
+ while (pos < s.length()) {
+ pos = s.indexOf(needle, pos);
+ if (pos == -1) {
+ return maxRunLength;
+ }
+ int runLength = 0;
+ do {
+ pos += needle.length();
+ runLength++;
+ } while (s.startsWith(needle, pos));
+ maxRunLength = Math.max(runLength, maxRunLength);
+ }
+ return maxRunLength;
+ }
+
+ private static boolean contains(String s, CharMatcher charMatcher) {
+ for (int i = 0; i < s.length(); i++) {
+ if (charMatcher.matches(s.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Keep for Android compat (String.repeat only available on Android 12 and later)
+ private static String repeat(String s, int count) {
+ StringBuilder sb = new StringBuilder(s.length() * count);
+ for (int i = 0; i < count; i++) {
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+
+ private static List getLines(String literal) {
+ // Without -1, split would discard all trailing empty strings, which is not what we want, e.g. it would
+ // return the same result for "abc", "abc\n" and "abc\n\n".
+ // With -1, it returns ["abc"], ["abc", ""] and ["abc", "", ""].
+ String[] parts = literal.split("\n", -1);
+ if (parts[parts.length - 1].isEmpty()) {
+ // But we don't want the last empty string, as "\n" is used as a line terminator (not a separator),
+ // so return without the last element.
+ return Arrays.asList(parts).subList(0, parts.length - 1);
+ } else {
+ return Arrays.asList(parts);
+ }
+ }
+
+ private void writeLinkLike(String title, String destination, Node node, String opener) {
+ writer.raw(opener);
+ visitChildren(node);
+ writer.raw(']');
+ writer.raw('(');
+ if (contains(destination, linkDestinationNeedsAngleBrackets)) {
+ writer.raw('<');
+ writer.text(destination, linkDestinationEscapeInAngleBrackets);
+ writer.raw('>');
+ } else {
+ writer.raw(destination);
+ }
+ if (title != null) {
+ writer.raw(' ');
+ writer.raw('"');
+ writer.text(title, linkTitleEscapeInQuotes);
+ writer.raw('"');
+ }
+ writer.raw(')');
+ }
+
+ private static class ListHolder {
+ final ListHolder parent;
+
+ protected ListHolder(ListHolder parent) {
+ this.parent = parent;
+ }
+ }
+
+ private static class BulletListHolder extends ListHolder {
+ final String marker;
+
+ public BulletListHolder(ListHolder parent, BulletList bulletList) {
+ super(parent);
+ this.marker = bulletList.getMarker() != null ? bulletList.getMarker() : "-";
+ }
+ }
+
+ private static class OrderedListHolder extends ListHolder {
+ final String delimiter;
+ private int number;
+
+ protected OrderedListHolder(ListHolder parent, OrderedList orderedList) {
+ super(parent);
+ delimiter = orderedList.getMarkerDelimiter() != null ? orderedList.getMarkerDelimiter() : ".";
+ number = orderedList.getMarkerStartNumber() != null ? orderedList.getMarkerStartNumber() : 1;
+ }
+ }
+
+ /**
+ * Visits nodes to check if there are any soft or hard line breaks.
+ */
+ private static class LineBreakVisitor extends AbstractVisitor {
+ private boolean lineBreak = false;
+
+ public boolean hasLineBreak() {
+ return lineBreak;
+ }
+
+ @Override
+ public void visit(SoftLineBreak softLineBreak) {
+ super.visit(softLineBreak);
+ lineBreak = true;
+ }
+
+ @Override
+ public void visit(HardLineBreak hardLineBreak) {
+ super.visit(hardLineBreak);
+ lineBreak = true;
+ }
+ }
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java
new file mode 100644
index 00000000000..e3f9dadb753
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.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.markdown;
+
+import jdk.internal.org.commonmark.node.Node;
+
+import java.util.Set;
+
+/**
+ * Context that is passed to custom node renderers, see {@link MarkdownNodeRendererFactory#create}.
+ */
+public interface MarkdownNodeRendererContext {
+
+ /**
+ * @return the writer to use
+ */
+ MarkdownWriter 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);
+
+ /**
+ * @return additional special characters that need to be escaped if they occur in normal text; currently only ASCII
+ * characters are allowed
+ */
+ Set getSpecialCharacters();
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java
new file mode 100644
index 00000000000..c908398b7de
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.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.markdown;
+
+import jdk.internal.org.commonmark.renderer.NodeRenderer;
+
+import java.util.Set;
+
+/**
+ * Factory for instantiating new node renderers for rendering custom nodes.
+ */
+public interface MarkdownNodeRendererFactory {
+
+ /**
+ * 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(MarkdownNodeRendererContext context);
+
+ /**
+ * @return the additional special characters that this factory would like to have escaped in normal text; currently
+ * only ASCII characters are allowed
+ */
+ Set getSpecialCharacters();
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownRenderer.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownRenderer.java
new file mode 100644
index 00000000000..344a176d0d5
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownRenderer.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.renderer.markdown;
+
+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.*;
+
+/**
+ * Renders nodes to Markdown (CommonMark syntax); use {@link #builder()} to create a renderer.
+ *
+ * Note that it doesn't currently preserve the exact syntax of the original input Markdown (if any):
+ *
+ * - Headings are output as ATX headings if possible (multi-line headings need Setext headings)
+ * - Links are always rendered as inline links (no support for reference links yet)
+ * - Escaping might be over-eager, e.g. a plain {@code *} might be escaped
+ * even though it doesn't need to be in that particular context
+ * - Leading whitespace in paragraphs is not preserved
+ *
+ * However, it should produce Markdown that is semantically equivalent to the input, i.e. if the Markdown was parsed
+ * again and compared against the original AST, it should be the same (minus bugs).
+ */
+public class MarkdownRenderer implements Renderer {
+
+ private final List nodeRendererFactories;
+
+ private MarkdownRenderer(Builder builder) {
+ 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 MarkdownNodeRendererFactory() {
+ @Override
+ public NodeRenderer create(MarkdownNodeRendererContext context) {
+ return new CoreMarkdownNodeRenderer(context);
+ }
+
+ @Override
+ public Set getSpecialCharacters() {
+ return Collections.emptySet();
+ }
+ });
+ }
+
+ /**
+ * Create a new builder for configuring a {@link MarkdownRenderer}.
+ *
+ * @return a builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public void render(Node node, Appendable output) {
+ RendererContext context = new RendererContext(new MarkdownWriter(output));
+ context.render(node);
+ }
+
+ @Override
+ public String render(Node node) {
+ StringBuilder sb = new StringBuilder();
+ render(node, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Builder for configuring a {@link MarkdownRenderer}. See methods for default configuration.
+ */
+ public static class Builder {
+
+ private final List nodeRendererFactories = new ArrayList<>();
+
+ /**
+ * @return the configured {@link MarkdownRenderer}
+ */
+ public MarkdownRenderer build() {
+ return new MarkdownRenderer(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(MarkdownNodeRendererFactory nodeRendererFactory) {
+ this.nodeRendererFactories.add(nodeRendererFactory);
+ return this;
+ }
+
+ /**
+ * @param extensions extensions to use on this renderer
+ * @return {@code this}
+ */
+ public Builder extensions(Iterable extends Extension> extensions) {
+ for (Extension extension : extensions) {
+ if (extension instanceof MarkdownRendererExtension) {
+ MarkdownRendererExtension markdownRendererExtension = (MarkdownRendererExtension) extension;
+ markdownRendererExtension.extend(this);
+ }
+ }
+ return this;
+ }
+ }
+
+ /**
+ * Extension for {@link MarkdownRenderer} for rendering custom nodes.
+ */
+ public interface MarkdownRendererExtension extends Extension {
+
+ /**
+ * Extend Markdown rendering, usually by registering custom node renderers using {@link Builder#nodeRendererFactory}.
+ *
+ * @param rendererBuilder the renderer builder to extend
+ */
+ void extend(Builder rendererBuilder);
+ }
+
+ private class RendererContext implements MarkdownNodeRendererContext {
+ private final MarkdownWriter writer;
+ private final NodeRendererMap nodeRendererMap = new NodeRendererMap();
+ private final Set additionalTextEscapes;
+
+ private RendererContext(MarkdownWriter writer) {
+ // Set fields that are used by interface
+ this.writer = writer;
+ Set escapes = new HashSet();
+ for (MarkdownNodeRendererFactory factory : nodeRendererFactories) {
+ escapes.addAll(factory.getSpecialCharacters());
+ }
+ additionalTextEscapes = Collections.unmodifiableSet(escapes);
+
+ // The first node renderer for a node type "wins".
+ for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) {
+ MarkdownNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i);
+ // Pass in this as context here, which uses the fields set above
+ NodeRenderer nodeRenderer = nodeRendererFactory.create(this);
+ nodeRendererMap.add(nodeRenderer);
+ }
+ }
+
+ @Override
+ public MarkdownWriter getWriter() {
+ return writer;
+ }
+
+ @Override
+ public void render(Node node) {
+ nodeRendererMap.render(node);
+ }
+
+ @Override
+ public Set getSpecialCharacters() {
+ return additionalTextEscapes;
+ }
+ }
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownWriter.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownWriter.java
new file mode 100644
index 00000000000..b91fdd6b1b5
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/MarkdownWriter.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.renderer.markdown;
+
+import jdk.internal.org.commonmark.text.CharMatcher;
+
+import java.io.IOException;
+import java.util.LinkedList;
+
+/**
+ * Writer for Markdown (CommonMark) text.
+ */
+public class MarkdownWriter {
+
+ private final Appendable buffer;
+
+ private int blockSeparator = 0;
+ private char lastChar;
+ private boolean atLineStart = true;
+
+ // Stacks of settings that affect various rendering behaviors. The common pattern here is that callers use "push" to
+ // change a setting, render some nodes, and then "pop" the setting off the stack again to restore previous state.
+ private final LinkedList prefixes = new LinkedList<>();
+ private final LinkedList tight = new LinkedList<>();
+ private final LinkedList rawEscapes = new LinkedList<>();
+
+ public MarkdownWriter(Appendable out) {
+ buffer = out;
+ }
+
+ /**
+ * Write the supplied string (raw/unescaped except if {@link #pushRawEscape} was used).
+ */
+ public void raw(String s) {
+ flushBlockSeparator();
+ write(s, null);
+ }
+
+ /**
+ * Write the supplied character (raw/unescaped except if {@link #pushRawEscape} was used).
+ */
+ public void raw(char c) {
+ flushBlockSeparator();
+ write(c);
+ }
+
+ /**
+ * Write the supplied string with escaping.
+ *
+ * @param s the string to write
+ * @param escape which characters to escape
+ */
+ public void text(String s, CharMatcher escape) {
+ if (s.isEmpty()) {
+ return;
+ }
+ flushBlockSeparator();
+ write(s, escape);
+
+ lastChar = s.charAt(s.length() - 1);
+ atLineStart = false;
+ }
+
+ /**
+ * Write a newline (line terminator).
+ */
+ public void line() {
+ write('\n');
+ writePrefixes();
+ atLineStart = true;
+ }
+
+ /**
+ * Enqueue a block separator to be written before the next text is written. Block separators are not written
+ * straight away because if there are no more blocks to write we don't want a separator (at the end of the document).
+ */
+ public void block() {
+ // Remember whether this should be a tight or loose separator now because tight could get changed in between
+ // this and the next flush.
+ blockSeparator = isTight() ? 1 : 2;
+ atLineStart = true;
+ }
+
+ /**
+ * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each line, until the
+ * prefix is popped again.
+ *
+ * @param prefix the raw prefix string
+ */
+ public void pushPrefix(String prefix) {
+ prefixes.addLast(prefix);
+ }
+
+ /**
+ * Write a prefix.
+ *
+ * @param prefix the raw prefix string to write
+ */
+ public void writePrefix(String prefix) {
+ boolean tmp = atLineStart;
+ raw(prefix);
+ atLineStart = tmp;
+ }
+
+ /**
+ * Remove the last prefix from the top of the stack.
+ */
+ public void popPrefix() {
+ prefixes.removeLast();
+ }
+
+ /**
+ * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight
+ * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines
+ * within the list.
+ *
+ * Note that changing this does not affect block separators that have already been enqueued with {@link #block()},
+ * only future ones.
+ */
+ public void pushTight(boolean tight) {
+ this.tight.addLast(tight);
+ }
+
+ /**
+ * Remove the last "tight" setting from the top of the stack.
+ */
+ public void popTight() {
+ this.tight.removeLast();
+ }
+
+ /**
+ * Escape the characters matching the supplied matcher, in all text (text and raw). This might be useful to
+ * extensions that add another layer of syntax, e.g. the tables extension that uses `|` to separate cells and needs
+ * all `|` characters to be escaped (even in code spans).
+ *
+ * @param rawEscape the characters to escape in raw text
+ */
+ public void pushRawEscape(CharMatcher rawEscape) {
+ rawEscapes.add(rawEscape);
+ }
+
+ /**
+ * Remove the last raw escape from the top of the stack.
+ */
+ public void popRawEscape() {
+ rawEscapes.removeLast();
+ }
+
+ /**
+ * @return the last character that was written
+ */
+ public char getLastChar() {
+ return lastChar;
+ }
+
+ /**
+ * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}.
+ */
+ public boolean isAtLineStart() {
+ return atLineStart;
+ }
+
+ private void write(String s, CharMatcher escape) {
+ try {
+ if (rawEscapes.isEmpty() && escape == null) {
+ // Normal fast path
+ buffer.append(s);
+ } else {
+ for (int i = 0; i < s.length(); i++) {
+ append(s.charAt(i), escape);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ int length = s.length();
+ if (length != 0) {
+ lastChar = s.charAt(length - 1);
+ }
+ atLineStart = false;
+ }
+
+ private void write(char c) {
+ try {
+ append(c, null);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ lastChar = c;
+ atLineStart = false;
+ }
+
+ private void writePrefixes() {
+ if (!prefixes.isEmpty()) {
+ for (String prefix : prefixes) {
+ write(prefix, null);
+ }
+ }
+ }
+
+ /**
+ * If a block separator has been enqueued with {@link #block()} but not yet written, write it now.
+ */
+ private void flushBlockSeparator() {
+ if (blockSeparator != 0) {
+ write('\n');
+ writePrefixes();
+ if (blockSeparator > 1) {
+ write('\n');
+ writePrefixes();
+ }
+ blockSeparator = 0;
+ }
+ }
+
+ private void append(char c, CharMatcher escape) throws IOException {
+ if (needsEscaping(c, escape)) {
+ if (c == '\n') {
+ // Can't escape this with \, use numeric character reference
+ buffer.append("
");
+ } else {
+ buffer.append('\\');
+ buffer.append(c);
+ }
+ } else {
+ buffer.append(c);
+ }
+ }
+
+ private boolean isTight() {
+ return !tight.isEmpty() && tight.getLast();
+ }
+
+ private boolean needsEscaping(char c, CharMatcher escape) {
+ return (escape != null && escape.matches(c)) || rawNeedsEscaping(c);
+ }
+
+ private boolean rawNeedsEscaping(char c) {
+ for (CharMatcher rawEscape : rawEscapes) {
+ if (rawEscape.matches(c)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/package-info.java
new file mode 100644
index 00000000000..2db58aca257
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/markdown/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.
+ */
+
+/**
+ * Markdown rendering (see {@link org.commonmark.renderer.markdown.MarkdownRenderer})
+ */
+package jdk.internal.org.commonmark.renderer.markdown;
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 00000000000..1321d5ad785
--- /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 00000000000..c00be3cca41
--- /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 00000000000..5c110ba4d29
--- /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 00000000000..6ac282fd4c7
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/renderer/text/TextContentRenderer.java
@@ -0,0 +1,184 @@
+/*
+ * 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;
+
+/**
+ * Renders nodes to plain text content with minimal markup-like additions.
+ */
+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 a {@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 a {@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 extends Extension> 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 00000000000..edbb7ae2839
--- /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 00000000000..c84c2c33755
--- /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.
+ */
+
+/**
+ * Plain text rendering with minimal markup (see {@link org.commonmark.renderer.text.TextContentRenderer})
+ */
+package jdk.internal.org.commonmark.renderer.text;
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/AsciiMatcher.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/AsciiMatcher.java
new file mode 100644
index 00000000000..76b90c9b6fd
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/AsciiMatcher.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.text;
+
+import java.util.BitSet;
+import java.util.Set;
+
+/**
+ * Char matcher that can match ASCII characters efficiently.
+ */
+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 Builder builder(AsciiMatcher matcher) {
+ return new Builder((BitSet) matcher.set.clone());
+ }
+
+ 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 anyOf(String s) {
+ for (int i = 0; i < s.length(); i++) {
+ c(s.charAt(i));
+ }
+ return this;
+ }
+
+ public Builder anyOf(Set characters) {
+ for (Character c : characters) {
+ c(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/text/CharMatcher.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/CharMatcher.java
new file mode 100644
index 00000000000..b4ac2ed1c67
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/CharMatcher.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.text;
+
+/**
+ * Matcher interface for {@code char} values.
+ *
+ * Note that because this matches on {@code char} values only (as opposed to {@code int} code points),
+ * this only operates on the level of code units and doesn't support supplementary characters
+ * (see {@link Character#isSupplementaryCodePoint(int)}).
+ */
+public interface CharMatcher {
+
+ boolean matches(char c);
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/Characters.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/Characters.java
new file mode 100644
index 00000000000..d69e1251874
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/Characters.java
@@ -0,0 +1,189 @@
+/*
+ * 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.text;
+
+/**
+ * Functions for finding characters in strings or checking characters.
+ */
+public class Characters {
+
+ 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;
+ }
+
+ /**
+ * @see blank line
+ */
+ public static boolean isBlank(CharSequence s) {
+ return skipSpaceTab(s, 0, s.length()) == s.length();
+ }
+
+ 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;
+ }
+
+ /**
+ * @see Unicode punctuation character
+ */
+ public static boolean isPunctuationCodePoint(int codePoint) {
+ switch (Character.getType(codePoint)) {
+ // General category "P" (punctuation)
+ case Character.DASH_PUNCTUATION:
+ case Character.START_PUNCTUATION:
+ case Character.END_PUNCTUATION:
+ case Character.CONNECTOR_PUNCTUATION:
+ case Character.OTHER_PUNCTUATION:
+ case Character.INITIAL_QUOTE_PUNCTUATION:
+ case Character.FINAL_QUOTE_PUNCTUATION:
+ // General category "S" (symbol)
+ case Character.MATH_SYMBOL:
+ case Character.CURRENCY_SYMBOL:
+ case Character.MODIFIER_SYMBOL:
+ case Character.OTHER_SYMBOL:
+ return true;
+ default:
+ switch (codePoint) {
+ case '$':
+ case '+':
+ case '<':
+ case '=':
+ case '>':
+ case '^':
+ case '`':
+ case '|':
+ case '~':
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Check whether the provided code point is a Unicode whitespace character as defined in the spec.
+ *
+ * @see Unicode whitespace character
+ */
+ public static boolean isWhitespaceCodePoint(int codePoint) {
+ switch (codePoint) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ return true;
+ default:
+ return Character.getType(codePoint) == Character.SPACE_SEPARATOR;
+ }
+ }
+
+ 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;
+ }
+}
diff --git a/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/package-info.java b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/text/package-info.java
new file mode 100644
index 00000000000..14c6328b517
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark/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 processing utilities for parsing and rendering, exported for use by extensions
+ */
+package jdk.internal.org.commonmark.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 00000000000..a9e3e5e2a03
--- /dev/null
+++ b/src/jdk.internal.md/share/classes/module-info.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2023, 2024, 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.
+ */
+
+// This module is primarily an import from a recent tagged version of
+// https://github.com/commonmark/commonmark-java
+//
+// The following parts are imported:
+//
+// * commonmark/src/main/java
+// * commonmark/src/main/resources
+// * commonmark-ext-gfm-tables/src/main/java
+// * commonmark-ext-gfm-tables/src/main/resources
+//
+// For source and resource files, the following transformations are applied:
+//
+// * legal headers are added
+// * package and import statements are updated
+// * characters outside the ASCII range are converted to Unicode escapes
+// * @SuppressWarnings("fallthrough") is added to getSetextHeadingLevel
+// * the value for ENTITY_PATH is updated with the modified package
+// * the file `entities.properties` is renamed to `entities.txt`
+
+/**
+ * Internal support for Markdown.
+ *
+ * @since 23
+ */
+module jdk.internal.md {
+ requires jdk.compiler;
+
+ exports jdk.internal.markdown to
+ jdk.javadoc,
+ jdk.jshell;
+ exports jdk.internal.org.commonmark to
+ jdk.javadoc,
+ jdk.jshell;
+ exports jdk.internal.org.commonmark.ext.gfm.tables to
+ jdk.javadoc,
+ jdk.jshell;
+ 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 to
+ jdk.javadoc,
+ jdk.jshell;
+ exports jdk.internal.org.commonmark.renderer.html to
+ jdk.javadoc,
+ jdk.jshell;
+
+ provides com.sun.tools.javac.api.JavacTrees.DocCommentTreeTransformer
+ with jdk.internal.markdown.MarkdownTransformer;
+}
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 00000000000..1fd2d7eb01b
--- /dev/null
+++ b/src/jdk.internal.md/share/legal/commonmark.md
@@ -0,0 +1,29 @@
+## CommonMark 0.22.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/Content.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Content.java
index 597747bdb4c..13a2bb4c1bf 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Content.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Content.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024, 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
@@ -141,4 +141,15 @@ public abstract class Content {
public int charCount() {
return 0;
}
+
+ /**
+ * {@return true if the content is "phrasing content"}
+ *
+ * Phrasing content is content that may appear in a paragraph, such as text, or certain HTML elements.
+ *
+ * @see Phrasing content
+ */
+ public boolean isPhrasingContent() {
+ return false;
+ }
}
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DocFilesHandler.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DocFilesHandler.java
index 47b2b6b41fe..71e576b4408 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DocFilesHandler.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DocFilesHandler.java
@@ -158,8 +158,9 @@ public class DocFilesHandler {
configuration.messages.warning("doclet.Copy_Overwrite_warning",
srcfile.getPath(), dstdir.getPath());
} else {
- if (Utils.toLowerCase(srcfile.getPath()).endsWith(".html")) {
- handleHtmlFile(srcfile, dstDocPath);
+ var path = Utils.toLowerCase(srcfile.getPath());
+ if (path.endsWith(".html") || path.endsWith(".md")) {
+ handleDocFile(srcfile, dstDocPath);
} else {
configuration.messages.notice("doclet.Copying_File_0_To_Dir_1",
srcfile.getPath(), dstdir.getPath());
@@ -186,10 +187,10 @@ public class DocFilesHandler {
}
}
- private void handleHtmlFile(DocFile srcFile, DocPath dstPath) throws DocletException {
+ private void handleDocFile(DocFile srcFile, DocPath dstPath) throws DocletException {
var fileObject = srcFile.getFileObject();
var dfElement = new DocFileElement(utils, element, fileObject);
- var path = dstPath.resolve(srcFile.getName());
+ var path = dstPath.resolve(srcFile.getName().replaceAll("\\.[a-z]+$", ".html"));
writerFactory.newDocFileWriter(path, dfElement).buildPage();
}
@@ -246,8 +247,8 @@ public class DocFilesHandler {
printHtmlDocument(List.of(), null, localTagsContent, List.of(), htmlContent);
}
- private String getWindowTitle(HtmlDocletWriter docletWriter, Element element) {
- String t = configuration.utils.getHTMLTitle(element);
+ private String getWindowTitle(HtmlDocletWriter docletWriter, DocFileElement element) {
+ var t = docletWriter.getFileTitle(element);
return docletWriter.getWindowTitle(t);
}
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ExternalSpecsWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ExternalSpecsWriter.java
index a57bc9544b6..cd66876997c 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ExternalSpecsWriter.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ExternalSpecsWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2024, 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
@@ -53,7 +53,7 @@ import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
import jdk.javadoc.internal.doclets.formats.html.markup.Text;
-import jdk.javadoc.internal.doclets.toolkit.DocletElement;
+import jdk.javadoc.internal.doclets.toolkit.DocFileElement;
import jdk.javadoc.internal.doclets.toolkit.OverviewElement;
import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
@@ -70,7 +70,7 @@ public class ExternalSpecsWriter extends HtmlDocletWriter {
/**
* Cached contents of {@code
...} tags of the HTML pages.
*/
- final Map titles = new WeakHashMap<>();
+ final Map titles = new WeakHashMap<>();
/**
* Constructs ExternalSpecsWriter object.
@@ -274,29 +274,27 @@ public class ExternalSpecsWriter extends HtmlDocletWriter {
private Content createLink(IndexItem i) {
assert i.getDocTree().getKind() == DocTree.Kind.SPEC : i;
- Element element = i.getElement();
+ var element = i.getElement();
if (element instanceof OverviewElement) {
return links.createLink(pathToRoot.resolve(i.getUrl()),
resources.getText("doclet.Overview"));
- } else if (element instanceof DocletElement e) {
- // Implementations of DocletElement do not override equals and
- // hashCode; putting instances of DocletElement in a map is not
- // incorrect, but might well be inefficient
- String t = titles.computeIfAbsent(element, utils::getHTMLTitle);
+ } else if (element instanceof DocFileElement e) {
+ var fo = e.getFileObject();
+ var t = titles.computeIfAbsent(e, this::getFileTitle);
if (t.isBlank()) {
// The user should probably be notified (a warning?) that this
// file does not have a title
- Path p = Path.of(e.getFileObject().toUri());
+ Path p = Path.of(fo.toUri());
t = p.getFileName().toString();
}
- ContentBuilder b = new ContentBuilder();
- b.add(HtmlTree.CODE(Text.of(i.getHolder() + ": ")));
- // non-program elements should be displayed using a normal font
- b.add(t);
+ var b = new ContentBuilder()
+ .add(HtmlTree.CODE(Text.of(i.getHolder() + ": ")))
+ // non-program elements should be displayed using a normal font
+ .add(t);
return links.createLink(pathToRoot.resolve(i.getUrl()), b);
} else {
// program elements should be displayed using a code font
- Content link = links.createLink(pathToRoot.resolve(i.getUrl()), i.getHolder());
+ var link = links.createLink(pathToRoot.resolve(i.getUrl()), i.getHolder());
return HtmlTree.CODE(link);
}
}
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 3e461dc3e74..7cc4a409759 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
@@ -45,6 +45,7 @@ import java.util.regex.Pattern;
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;
@@ -75,11 +76,25 @@ 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.RawTextTree;
import com.sun.source.doctree.StartElementTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.SimpleDocTreeVisitor;
+import jdk.internal.org.commonmark.Extension;
+import jdk.internal.org.commonmark.ext.gfm.tables.TablesExtension;
+import jdk.internal.org.commonmark.node.AbstractVisitor;
+import jdk.internal.org.commonmark.node.Code;
+import jdk.internal.org.commonmark.node.Heading;
+import jdk.internal.org.commonmark.node.Node;
+import jdk.internal.org.commonmark.parser.Parser;
+import jdk.internal.org.commonmark.renderer.NodeRenderer;
+import jdk.internal.org.commonmark.renderer.html.HtmlNodeRendererContext;
+import jdk.internal.org.commonmark.renderer.html.HtmlNodeRendererFactory;
+import jdk.internal.org.commonmark.renderer.html.HtmlRenderer;
+import jdk.internal.org.commonmark.renderer.html.HtmlWriter;
+
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;
@@ -95,6 +110,7 @@ import jdk.javadoc.internal.doclets.formats.html.markup.Text;
import jdk.javadoc.internal.doclets.formats.html.markup.TextBuilder;
import jdk.javadoc.internal.doclets.formats.html.taglets.Taglet;
import jdk.javadoc.internal.doclets.formats.html.taglets.TagletWriter;
+import jdk.javadoc.internal.doclets.toolkit.DocFileElement;
import jdk.javadoc.internal.doclets.toolkit.DocletException;
import jdk.javadoc.internal.doclets.toolkit.Messages;
import jdk.javadoc.internal.doclets.toolkit.Resources;
@@ -205,7 +221,7 @@ public abstract class HtmlDocletWriter {
*
* @param configuration the configuration for this doclet
* @param path the file to be generated.
- * @param generating whether to write a "Geneterating ..." message to the console
+ * @param generating whether to write a "Generating ..." message to the console
*/
protected HtmlDocletWriter(HtmlConfiguration configuration, DocPath path, boolean generating) {
this.configuration = configuration;
@@ -503,7 +519,10 @@ public abstract class HtmlDocletWriter {
}
/**
- * Get the window title.
+ * Returns the window title.
+ *
+ * The window title is the composition of the given title and
+ * any value set by the window-title option.
*
* @param title the title string to construct the complete window title
* @return the window title string
@@ -515,6 +534,105 @@ public abstract class HtmlDocletWriter {
return title;
}
+ /**
+ * {@return the title for a doc-file element}
+ *
+ * For an HTML file, the title is as given in the {@code } element,
+ * as found in the preamble of the doc-comment tree.
+ *
+ * For a Markdown file, there is no direct representation of the page title,
+ * and so the content of the {@code } heading is used.
+ *
+ * @param element the doc-file element
+ */
+ public String getFileTitle(DocFileElement element) {
+ var fileName = element.getFileObject().getName();
+ if (fileName.endsWith(".html")) {
+ return getTextContent(utils.getPreamble(element), "title");
+ } else if (fileName.endsWith(".md")) {
+ var c = commentTagsToContent(element, utils.getBody(element), false);
+ return getHeadingText(c);
+ } else {
+ throw new IllegalArgumentException(fileName);
+ }
+ }
+
+ /**
+ * {@return the plain-text content of a named HTML element in a list of content}
+ *
+ * @param trees the list of content
+ * @param name the name og the HTML element
+ */
+ private String getTextContent(List extends DocTree> trees, String name) {
+ var sb = new StringBuilder();
+ var collectText = false;
+ loop:
+ for (DocTree dt : trees) {
+ switch (dt.getKind()) {
+ case START_ELEMENT -> {
+ var nodeStart = (StartElementTree) dt;
+ if (nodeStart.getName().toString().equalsIgnoreCase(name)) {
+ collectText = true;
+ }
+ }
+ case END_ELEMENT -> {
+ var nodeEnd = (EndElementTree) dt;
+ if (nodeEnd.getName().toString().equalsIgnoreCase(name)) {
+ break loop;
+ }
+ }
+ case TEXT -> {
+ var nodeText = (TextTree) dt;
+ if (collectText)
+ sb.append(nodeText.getBody());
+ }
+ default -> {
+ }
+ // do nothing
+ }
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * {@return the content of the {@code } heading in the given content,
+ * or an empty string if there is no such heading}
+ *
+ * The heading must be at the beginning of the content.
+ * It may be represented in either an HTML tree with tag name {@code h1}
+ * or in a raw HTML node.
+ *
+ * @param c the content
+ */
+ private String getHeadingText(Content c) {
+ var sb = new StringBuilder();
+ if (c instanceof ContentBuilder cb) {
+ var contents = cb.getContents();
+ if (!contents.isEmpty()) {
+ var first = contents.get(0);
+ if (first instanceof HtmlTree htmlTree && htmlTree.tagName.equals(TagName.H1)) {
+ for (var c2 : htmlTree.getContents()) {
+ if (c2 instanceof Text t) {
+ sb.append(t.toString());
+ } else if (c2 instanceof TextBuilder tb) {
+ sb.append(tb.toString());
+ }
+ }
+ return sb.toString();
+ } else if (first instanceof RawHtml rawHtml) {
+ Pattern h1 = Pattern.compile("]*>(.*)
");
+ Matcher m = h1.matcher(rawHtml.toString());
+ if (m.lookingAt()) {
+ var heading = m.group(1);
+ var headingText = heading.replaceAll("?[^>]+>", "");
+ return headingText;
+ }
+ }
+ }
+ }
+ return "";
+ }
+
/**
* Returns a {@code } element, containing the user "top" text, if any,
* and the main navigation bar.
@@ -1267,10 +1385,13 @@ public abstract class HtmlDocletWriter {
commentRemoved = false;
List openTags = new ArrayList<>();
- for (ListIterator extends DocTree> iterator = trees.listIterator(); iterator.hasNext();) {
+ var useMarkdown = trees.stream().anyMatch(t -> t.getKind() == Kind.MARKDOWN);
+ var markdownHandler = useMarkdown ? new MarkdownHandler(element) : null;
+
+ for (ListIterator extends DocTree> 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
@@ -1293,13 +1414,19 @@ public abstract class HtmlDocletWriter {
}
var docTreeVisitor = new InlineVisitor(element, tag, isLastNode, context, ch, trees);
-
- boolean allDone = docTreeVisitor.visit(tag, result);
+ boolean allDone = useMarkdown
+ ? markdownHandler.handle(tag, docTreeVisitor)
+ : docTreeVisitor.visit(tag, result);
commentRemoved = false;
if (allDone)
break;
}
+
+ if (useMarkdown) {
+ markdownHandler.addContent(result);
+ }
+
// Close any open inline tags
while (!openTags.isEmpty()) {
result.add(RawHtml.endElement(openTags.removeLast()));
@@ -1307,6 +1434,219 @@ public abstract class HtmlDocletWriter {
return result;
}
+ private class MarkdownHandler {
+ /**
+ * Placeholder for non-HTML nodes that are phrasing content.
+ * The character (\uFFFC) is the Unicode Object Replacement Character, {@code U+FFFC}.
+ */
+ private static final char PLACEHOLDER_CHAR = '\uFFFC';
+ /**
+ * Placeholder for non-HTML nodes that are not phrasing content.
+ * The placeholder is an HTML block, according to CommonMark 4.6 rule 2,
+ * and so will not be wrapped into with {@code ...
} tags.
+ * @see HTML blocks
+ */
+ private static final String PLACEHOLDER_BLOCK = "";
+ private static final Pattern PLACEHOLDERS = Pattern.compile(PLACEHOLDER_CHAR + "|" + PLACEHOLDER_BLOCK);
+
+ private final StringBuilder markdownInput = new StringBuilder() ;
+ private final ArrayList fffcObjects = new ArrayList<>();
+
+ private final Extension tablesExtn = TablesExtension.create();
+ private final HtmlNodeRendererFactory headingRendererFactory = HeadingNodeRenderer::new;
+
+ private final Element element;
+
+ private final Parser parser = Parser.builder()
+ .extensions(List.of(tablesExtn))
+ .build();
+ private final HtmlRenderer renderer = HtmlRenderer.builder()
+ .nodeRendererFactory(headingRendererFactory)
+ .extensions(List.of(tablesExtn))
+ .build();
+
+ MarkdownHandler(Element element) {
+ this.element = element;
+ }
+
+ boolean handle(DocTree tree, InlineVisitor visitor) {
+ boolean allDone;
+ if (tree instanceof RawTextTree t) {
+ if (t.getKind() != Kind.MARKDOWN) {
+ throw new IllegalStateException(t.getKind().toString());
+ }
+ 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_CHAR, start)) != -1) {
+ markdownInput.append(code.substring(start, pos));
+ markdownInput.append(PLACEHOLDER_CHAR);
+ fffcObjects.add(Text.of(String.valueOf(PLACEHOLDER_CHAR)));
+ start = pos + 1;
+ }
+ markdownInput.append(code.substring(start));
+ allDone = false;
+ } else {
+ Content embeddedContent = new ContentBuilder();
+ allDone = visitor.visit(tree, embeddedContent);
+ fffcObjects.add(embeddedContent);
+ if (embeddedContent.isPhrasingContent()) {
+ markdownInput.append(PLACEHOLDER_CHAR);
+ } else {
+ if (!markdownInput.isEmpty() && markdownInput.charAt(markdownInput.length() - 1) != '\n') {
+ markdownInput.append('\n');
+ }
+ markdownInput.append(PLACEHOLDER_BLOCK);
+ }
+ }
+ return allDone;
+ }
+
+ void addContent(Content result) {
+ Node document = parser.parse(markdownInput.toString());
+ String markdownOutput = unwrap(renderer.render(document));
+ Matcher m = PLACEHOLDERS.matcher(markdownOutput);
+ int start = 0;
+ int pos;
+ int fffcObjectIndex = 0;
+ while (m.find()) {
+ result.add(RawHtml.markdown(markdownOutput.substring(start, m.start())));
+ result.add(fffcObjects.get(fffcObjectIndex++));
+ start = m.end();
+ }
+ if (start < markdownOutput.length()) {
+ result.add(RawHtml.of(markdownOutput.substring(start)));
+ }
+ }
+
+ /*
+ * 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;
+ }
+
+ /**
+ * A renderer for Markdown {@code Heading} nodes, which represent
+ * both ATX headings (using {@code ####}) and Setext (using underlines).
+ * The mapping to HTML takes into account the context within the overall
+ * generated page, and automatically includes an id, to allow the heading
+ * to be referenced from elsewhere.
+ */
+ private class HeadingNodeRenderer extends AbstractVisitor implements NodeRenderer {
+ private final HtmlWriter htmlWriter;
+ private final HtmlNodeRendererContext context;
+
+ HeadingNodeRenderer(HtmlNodeRendererContext context) {
+ this.htmlWriter = context.getWriter();
+ this.context = context;
+ }
+
+ @Override
+ public Set> getNodeTypes() {
+ return Set.of(Heading.class);
+ }
+
+ @Override
+ public void render(Node node) {
+ node.accept(this);
+ }
+
+ @Override
+ public void visit(Heading heading) {
+ var htag = getTag(heading);
+ var id = getId(heading);
+
+ htmlWriter.line();
+ htmlWriter.tag(htag, Map.of("id", id.name()));
+ visitChildren(heading);
+ htmlWriter.tag('/' + htag);
+ htmlWriter.line();
+
+ if (includeHeadingInTableOfContents(htag)) {
+ StringBuilder headingContent = new StringBuilder();
+ new AbstractVisitor() {
+ @Override
+ public void visit(jdk.internal.org.commonmark.node.Code code) {
+ headingContent.append(code.getLiteral());
+ super.visit(code);
+ }
+
+ @Override
+ public void visit(jdk.internal.org.commonmark.node.Text text) {
+ headingContent.append(text.getLiteral());
+ super.visit(text);
+ }
+ }.visit(heading);
+ tableOfContents.addLink(id, Text.of(headingContent));
+ }
+ }
+
+ @Override
+ protected void visitChildren(Node parent) {
+ Node node = parent.getFirstChild();
+ while (node != null) {
+ Node next = node.getNext();
+ context.render(node);
+ node = next;
+ }
+ }
+
+ private String getTag(Heading heading) {
+ // offset the heading level to allow for its position in the overall page
+ var eKind = element.getKind();
+ var offset = eKind.isField() || eKind.isExecutable() ? 3 // members
+ : eKind != ElementKind.OTHER ? 1 // module, package, class, interface
+ : 0; // doc file
+ return "h" + Math.min(heading.getLevel() + offset, 6);
+ }
+
+ private HtmlId getId(Heading heading) {
+ var list = new ArrayList();
+ heading.accept(new AbstractVisitor() {
+ @Override
+ public void visit(jdk.internal.org.commonmark.node.Text text) {
+ list.add(text.getLiteral());
+ }
+
+ @Override
+ public void visit(Code code) {
+ list.add(code.getLiteral());
+ }
+ });
+ return htmlIds.forHeading(String.join(" ", list), headingIds);
+ }
+ }
+ }
+
+ /*
+ * 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;
@@ -1406,6 +1746,12 @@ public abstract class HtmlDocletWriter {
return false;
}
+ @Override
+ public Boolean visitDocRoot(DocRootTree node, Content content) {
+ content.add(getInlineTagOutput(element, node, context));
+ return false;
+ }
+
@Override
public Boolean visitEndElement(EndElementTree node, Content content) {
content.add(RawHtml.endElement(node.getName()));
@@ -1527,6 +1873,8 @@ public abstract class HtmlDocletWriter {
for (DocTree docTree : trees.subList(trees.indexOf(node) + 1, trees.size())) {
if (docTree instanceof TextTree text) {
sb.append(text.getBody());
+ } else if (docTree instanceof RawTextTree raw) {
+ sb.append(raw.getContent().replaceAll("[^A-Za-z0-9]+", " "));
} else if (docTree instanceof LiteralTree literal) {
sb.append(literal.getBody().getBody());
} else if (docTree instanceof IndexTree index) {
@@ -1568,12 +1916,18 @@ public abstract class HtmlDocletWriter {
new DocLink(path, id));
configuration.indexBuilder.add(item);
}
- // Record second-level headings for use in table of contents
- if (tableOfContents != null && node.getName().toString().equalsIgnoreCase("h2")) {
+ if (includeHeadingInTableOfContents(node.getName())) {
tableOfContents.addLink(HtmlId.of(id), Text.of(headingContent));
}
}
+ private boolean includeHeadingInTableOfContents(CharSequence tag) {
+ // Record second-level headings for use in table of contents
+ // TODO: maybe extend this to all headings up to a given level
+ return tableOfContents != null
+ && tag.toString().equalsIgnoreCase("h2");
+ }
+
/**
* Returns true if relative links should be redirected.
*
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NewAPIListWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NewAPIListWriter.java
index a976ad21548..f46765933cf 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NewAPIListWriter.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NewAPIListWriter.java
@@ -29,7 +29,7 @@ import java.util.List;
import javax.lang.model.element.Element;
-import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.SinceTree;
import jdk.javadoc.internal.doclets.formats.html.Navigation.PageMode;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
@@ -107,7 +107,7 @@ public class NewAPIListWriter extends SummaryListWriter {
? getTableCaption(headingKey)
: Text.of(release),
element -> {
- List extends DocTree> since = getSinceTree(element);
+ List extends SinceTree> since = utils.getBlockTags(element, SINCE, SinceTree.class);
if (since.isEmpty()) {
return false;
}
@@ -130,10 +130,10 @@ public class NewAPIListWriter extends SummaryListWriter {
@Override
protected Content getExtraContent(Element element) {
- List extends DocTree> sinceTree = getSinceTree(element);
- if (!sinceTree.isEmpty()) {
- CommentHelper ch = utils.getCommentHelper(element);
- return Text.of(ch.getBody(sinceTree.get(0)).toString());
+ var sinceTrees = utils.getBlockTags(element, SINCE, SinceTree.class);
+ if (!sinceTrees.isEmpty()) {
+ // assumes a simple string value with no formatting
+ return Text.of(sinceTrees.getFirst().getBody().getFirst().toString());
}
return Text.EMPTY;
}
@@ -152,10 +152,6 @@ public class NewAPIListWriter extends SummaryListWriter {
return new HtmlStyle[]{ HtmlStyle.colSummaryItemName, HtmlStyle.colSecond, HtmlStyle.colLast };
}
- private List extends DocTree> getSinceTree(Element element) {
- return utils.hasDocCommentTree(element) ? utils.getBlockTags(element, SINCE) : List.of();
- }
-
private static String getHeading(HtmlConfiguration configuration) {
String label = configuration.getOptions().sinceLabel();
return label == null ? configuration.docResources.getText("doclet.New_API") : label;
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SystemPropertiesWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SystemPropertiesWriter.java
index 0b4e9580efb..45985532ad6 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SystemPropertiesWriter.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SystemPropertiesWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2024, 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
@@ -34,8 +34,6 @@ import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
-import javax.lang.model.element.Element;
-
import com.sun.source.doctree.DocTree;
import jdk.javadoc.internal.doclets.formats.html.Navigation.PageMode;
@@ -44,7 +42,7 @@ import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
import jdk.javadoc.internal.doclets.formats.html.markup.Text;
-import jdk.javadoc.internal.doclets.toolkit.DocletElement;
+import jdk.javadoc.internal.doclets.toolkit.DocFileElement;
import jdk.javadoc.internal.doclets.toolkit.OverviewElement;
import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
@@ -60,7 +58,7 @@ public class SystemPropertiesWriter extends HtmlDocletWriter {
/**
* Cached contents of {@code ...} tags of the HTML pages.
*/
- final Map titles = new WeakHashMap<>();
+ final Map titles = new WeakHashMap<>();
/**
* Constructs SystemPropertiesWriter object.
@@ -133,29 +131,26 @@ public class SystemPropertiesWriter extends HtmlDocletWriter {
private Content createLink(IndexItem i) {
assert i.getDocTree().getKind() == DocTree.Kind.SYSTEM_PROPERTY : i;
- Element element = i.getElement();
+ var element = i.getElement();
if (element instanceof OverviewElement) {
return links.createLink(pathToRoot.resolve(i.getUrl()),
resources.getText("doclet.Overview"));
- } else if (element instanceof DocletElement e) {
- // Implementations of DocletElement do not override equals and
- // hashCode; putting instances of DocletElement in a map is not
- // incorrect, but might well be inefficient
- String t = titles.computeIfAbsent(element, utils::getHTMLTitle);
+ } else if (element instanceof DocFileElement e) {
+ var fo = e.getFileObject();
+ var t = titles.computeIfAbsent(e, this::getFileTitle);
if (t.isBlank()) {
// The user should probably be notified (a warning?) that this
// file does not have a title
- Path p = Path.of(e.getFileObject().toUri());
+ var p = Path.of(fo.toUri());
t = p.getFileName().toString();
}
- ContentBuilder b = new ContentBuilder();
- b.add(HtmlTree.CODE(Text.of(i.getHolder() + ": ")));
- // non-program elements should be displayed using a normal font
- b.add(t);
+ var b = new ContentBuilder()
+ .add(HtmlTree.CODE(Text.of(i.getHolder() + ": ")))
+ .add(t);
return links.createLink(pathToRoot.resolve(i.getUrl()), b);
} else {
// program elements should be displayed using a code font
- Content link = links.createLink(pathToRoot.resolve(i.getUrl()), i.getHolder());
+ var link = links.createLink(pathToRoot.resolve(i.getUrl()), i.getHolder());
return HtmlTree.CODE(link);
}
}
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/ContentBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/ContentBuilder.java
index 34573dd7794..b059b0fdb36 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/ContentBuilder.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/ContentBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2024, 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
@@ -75,6 +75,13 @@ public class ContentBuilder extends Content {
return this;
}
+ /**
+ * {@return the contents of this builder}
+ */
+ public List getContents() {
+ return contents;
+ }
+
@Override
public boolean write(Writer writer, String newline, boolean atNewline) throws IOException {
for (Content content: contents) {
@@ -100,6 +107,11 @@ public class ContentBuilder extends Content {
return n;
}
+ @Override
+ public boolean isPhrasingContent() {
+ return contents.stream().allMatch(Content::isPhrasingContent);
+ }
+
private void ensureMutableContents() {
if (contents.isEmpty())
contents = new ArrayList<>();
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Entity.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Entity.java
index c96063d703f..542e081a583 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Entity.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Entity.java
@@ -68,6 +68,11 @@ public class Entity extends Content {
return false;
}
+ @Override
+ public boolean isPhrasingContent() {
+ return true;
+ }
+
@Override
public int charCount() {
return 1;
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTree.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTree.java
index 0ec4033a929..57adb966326 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTree.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTree.java
@@ -233,6 +233,20 @@ public class HtmlTree extends Content {
return this;
}
+ /**
+ * {@return the attributes of this node}
+ */
+ public Map getAttrs() {
+ return attrs;
+ }
+
+ /**
+ * {@return the contents of this node}
+ */
+ public List getContents() {
+ return content;
+ }
+
/**
* Adds each of a collection of items, using a map function to create the content for each item.
*
@@ -1009,6 +1023,11 @@ public class HtmlTree extends Content {
return (!hasContent() && !hasAttrs());
}
+ @Override
+ public boolean isPhrasingContent() {
+ return tagName.phrasingContent;
+ }
+
/**
* Returns true if the HTML tree has content.
*
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 5127be87ccd..a71ea7831da 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, 2024, 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,8 @@ package jdk.javadoc.internal.doclets.formats.html.markup;
import java.io.IOException;
import java.io.Writer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import jdk.javadoc.internal.doclets.formats.html.Content;
@@ -55,6 +57,16 @@ public class RawHtml extends Content {
};
}
+ /**
+ * 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.
*
@@ -129,6 +141,24 @@ public class RawHtml extends Content {
return rawHtmlContent.isEmpty();
}
+ Pattern tag = Pattern.compile("<(?[A-Za-z0-9]+)(\\s|>)");
+ @Override
+ public boolean isPhrasingContent() {
+ Matcher m = tag.matcher(rawHtmlContent);
+ while (m.find()) {
+ try {
+ var tn = TagName.of(m.group("tag"));
+ if (!tn.phrasingContent) {
+ return false;
+ }
+ } catch (IllegalArgumentException e) {
+ // unknown tag
+ return false;
+ }
+ }
+ return true;
+ }
+
@Override
public String toString() {
return rawHtmlContent;
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/TagName.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/TagName.java
index 67635e18377..d849c2f1798 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/TagName.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/TagName.java
@@ -25,6 +25,8 @@
package jdk.javadoc.internal.doclets.formats.html.markup;
+import java.util.Locale;
+
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
/**
@@ -34,20 +36,20 @@ import jdk.javadoc.internal.doclets.toolkit.util.Utils;
* @see