8298405: Implement JEP 467: Markdown Documentation Comments

8329296: Update Elements for '///' documentation comments

Co-authored-by: Jim Laskey <jlaskey@openjdk.org>
Reviewed-by: prappo, darcy, hannesw
This commit is contained in:
Jonathan Gibbons 2024-05-17 17:42:46 +00:00
parent 39a55e9779
commit 0a58cffe88
250 changed files with 26320 additions and 712 deletions

View File

@ -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, \
))

View File

@ -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 \

View File

@ -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");

View File

@ -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");

View File

@ -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.*'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 := \

View File

@ -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.

View File

@ -277,19 +277,26 @@ public interface Elements {
getElementValuesWithDefaults(AnnotationMirror a);
/**
* Returns the text of the documentation (&quot;Javadoc&quot;)
* Returns the text of the documentation (&quot;JavaDoc&quot;)
* comment of an element.
*
* <p> A documentation comment of an element is a comment that
* begins with "{@code /**}", ends with a separate
* "<code>*&#47;</code>", and immediately precedes the element,
* ignoring white space, annotations, end-of-line-comments ({@code
* "//"} comments), and intermediate traditional comments
* (<code>"/* ... *&#47;"</code> comments) that are not doc comments.
* Therefore, a documentation comment
* contains at least three "{@code *}" characters. The text
* <p>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.
*
* <p>There are two kinds of documentation comments, either based on
* <em>traditional comments</em> or based on a series of
* <em>end-of-line comments</em>. For both kinds, the text
* returned for the documentation comment is a processed form of
* the comment as it appears in source code:
* the comment as it appears in source code, as described below.
*
* <p>A {@linkplain DocCommentKind#TRADITIONAL traditional
* documentation comment} is a traditional comment that begins
* with "{@code /**}", and ends with a separate "<code>*&#47;</code>".
* (Therefore, such a comment contains at least three "{@code *}"
* characters.)
* The lines of such a comment are processed as follows:
* <ul>
* <li>The leading "{@code /**}" is removed, as are any
* immediately following space characters on that line. If all the
@ -297,8 +304,8 @@ public interface Elements {
* the returned comment.
* <li>For subsequent lines
* of the doc comment starting after the initial "{@code /**}",
* if the lines start with <em>zero</em> or more whitespace characters followed by
* <em>one</em> or more "{@code *}" characters,
* if the lines start with <em>zero</em> or more whitespace characters
* followed by <em>one</em> or more "{@code *}" characters,
* those leading whitespace characters are discarded as are any
* consecutive "{@code *}" characters appearing after the white
* space or starting the line.
@ -306,22 +313,88 @@ public interface Elements {
* form, the entire line is retained.
* <li> The trailing "<code>*&#47;</code>" is removed. The line
* with the trailing" <code>*&#47;</code>" also undergoes leading
* space and "{@code *}" character removal as described above. If all the characters
* of the line are removed, it makes no contribution to the
* returned comment.
* space and "{@code *}" character removal as described above.
* <li>The processed lines are then concatenated together,
* separated by newline ("{@code \n}") characters, and returned.
* </ul>
*
* <p>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:
* <ul>
* <li>Any leading whitespace and the three initial "{@code /}"
* characters are removed from each line.
* <li>The lines are shifted left, by removing leading whitespace
* characters, until the non-blank line with the least leading
* whitespace characters has no remaining leading whitespace
* characters.
* <li>Additional leading whitespace characters and any trailing
* whitespace characters in each line are preserved.
* <li>
* The processed lines are then concatenated together,
* separated by newline ("{@code \n}") characters, and returned.
* If the last line is not blank, the returned value will not be
* terminated by a newline character.
* </ul>
* The processed lines are then
* concatenated together (including line terminators) and
* returned.
*
* @param e the element being examined
* @return the documentation comment of the element, or {@code null}
* if there is none
* @jls 3.6 White Space
* @jls 3.7 Comments
*
* @apiNote
* Documentation comments are processed by the standard doclet
* used by the {@code javadoc} tool to generate API documentation.
*/
String getDocComment(Element e);
/**
* {@return the kind of the documentation comment for the given element,
* or {@code null} if there is no comment or the kind is not known}
*
* @implSpec The default implementation of this method returns
* {@code null}.
*
* @param e the element being examined
* @since 23
*/
default DocCommentKind getDocCommentKind(Element e) {
return null;
}
/**
* The kind of documentation comment.
*
* @since 23
*/
enum DocCommentKind {
/**
* The kind of comments whose lines are prefixed by {@code ///}.
*
* @apiNote
* The standard doclet used by the {@code javadoc} tool treats these comments
* as containing Markdown and documentation comment tags.
*
*
* @see <a href="https://openjdk.org/jeps/467">
* JEP 467: Markdown Documentation Comments</a>
*/
END_OF_LINE,
/**
* The kind of comments that begin with {@code /**}.
*
* @apiNote
* The standard doclet used by the {@code javadoc} tool treats these comments
* as containing HTML and documentation comment tags.
*/
TRADITIONAL
}
/**
* {@return {@code true} if the element is deprecated, {@code false} otherwise}
*

View File

@ -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
@ -161,6 +161,14 @@ public interface DocTree {
*/
LITERAL("literal"),
/**
* Used for instances of {@link RawTextTree}
* representing a fragment of Markdown content.
*
* @since 23
*/
MARKDOWN,
/**
* Used for instances of {@link ParamTree}
* representing an {@code @param} tag.

View File

@ -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
@ -247,6 +247,22 @@ public interface DocTreeVisitor<R,P> {
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

View File

@ -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;
* {&#064;inheritDoc supertype}
* </pre>
*
* @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 {

View File

@ -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.
*
* <p>
* The content may contain any text except that for
* {@linkplain InlineTagTree inline tags}.
*
* <p>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();
}

View File

@ -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
*
*/

View File

@ -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.
*

View File

@ -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<R,P> implements DocTreeVisitor<R,P> {
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}
*

View File

@ -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.
*
* <p>The supported file types are:
* <ul>
* <li>HTML files, identified by a file name ending in {@code .html},
* <li>Markdown files, identified by a file name ending in {@code .md}.
* </ul>
* 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 &lt;body&gt; 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.
*
* <p>The supported file types are:
* <ul>
* <li>HTML files, identified by a file name ending in {@code .html},
* <li>Markdown files, identified by a file name ending in {@code .md}.
* </ul>
* 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 <body>} 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
*/

View File

@ -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<R,P> implements DocTreeVisitor<R, P> {
return defaultAction(node, p);
}
/**
* {@inheritDoc}
*
* @implSpec This implementation calls {@code defaultAction}.
*
* @param node {@inheritDoc}
* @param p {@inheritDoc}
* @return the result of {@code defaultAction}
*
* @since 23
*/
@Override
public R visitRawText(RawTextTree node, P p) {
return defaultAction(node, p);
}
/**
* {@inheritDoc}
*

View File

@ -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<Type, Type> 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<Void, Void>() {
@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<FileObject>
private static class DocFileObject extends ForwardingFileObject<FileObject>
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();

View File

@ -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 {

View File

@ -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> R getDocCommentItem(Element e, BiFunction<DocCommentTable, JCTree, R> 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)

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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<Comment> comments = getComments(Comment.CommentStyle.JAVADOC);
List<Comment> 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<Comment> getComments(Comment.CommentStyle style) {
private List<Comment> getDocComments() {
if (comments == null) {
return List.nil();
} else {
ListBuffer<Comment> 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();

View File

@ -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

View File

@ -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, D> R accept(DocTreeVisitor<R, D> v, D d) {
return v.visitRawText(this, d);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public String getContent() {
return code;
}
}
public static class DCReference extends DCEndPosTree<DCReference> implements ReferenceTree {
public final String signature;

View File

@ -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);
}

View File

@ -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<Void,Void> {
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<Void,Void> {
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);

View File

@ -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<DCTree> 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<int,int>
// 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<DCTree>, List<DCTree>> 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;
}
}
}
}

View File

@ -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.
*/

View File

@ -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;

View File

@ -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

View File

@ -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.
*
* <p>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<Boolean, Void> 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 <T> 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 <T extends DocTree> boolean equal(T item1, T item2) {
return item1 == item2;
}
/**
* Shallow "equals" for two lists of doc tree nodes.
*
* @param <T> 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 <T extends DocTree> 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<DelimiterProcessor> 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<DCTree> 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<DCTree> 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);
}
}
}
}

View File

@ -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.
* <p>
* 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 {
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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
}
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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).
* <p>
* 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)}).
* </p>
* <p>
* The parsed tables are turned into {@link TableBlock} blocks.
* </p>
*
* @see <a href="https://github.github.com/gfm/#tables-extension-">Tables (extension) in GitHub Flavored Markdown Spec</a>
*/
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<Character> getSpecialCharacters() {
return Collections.singleton('|');
}
});
}
}

View File

@ -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<SourceLine> rowLines = new ArrayList<>();
private final List<TableCellInfo> columns;
private boolean canHaveLazyContinuationLines = true;
private TableBlockParser(List<TableCellInfo> 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<SourceSpan> 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<SourceLine> 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<SourceLine> 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<SourceLine> 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<SourceLine> 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<TableCellInfo> parseSeparator(CharSequence s) {
List<TableCellInfo> 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<SourceLine> 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<TableCellInfo> columns = parseSeparator(separatorLine.getContent());
if (columns != null && !columns.isEmpty()) {
SourceLine paragraph = paragraphLines.get(0);
List<SourceLine> 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;
}
}
}

View File

@ -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<String, String> getAttributes(Node node, String tagName) {
return context.extendAttributes(node, tagName, Collections.<String, String>emptyMap());
}
private Map<String, String> 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.<String, String>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;
}
}
}

View File

@ -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<TableCell.Alignment> 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;
}
}
}

View File

@ -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<Class<? extends Node>> 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);
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}

View File

@ -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 (<code>[</code>) or images (<code>![</code>).
*/
public class Bracket {
public final Text node;
/**
* The position of the marker for the bracket (<code>[</code> or <code>![</code>)
*/
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;
}
}

View File

@ -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<Text> 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<Text> 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<Text> 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<Text> 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);
}
}

View File

@ -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) {
}
}

View File

@ -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<Class<? extends Block>> 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<Class<? extends Block>, BlockParserFactory> NODES_TO_CORE_FACTORIES;
static {
Map<Class<? extends Block>, 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<BlockParserFactory> blockParserFactories;
private final InlineParserFactory inlineParserFactory;
private final List<DelimiterProcessor> delimiterProcessors;
private final IncludeSourceSpans includeSourceSpans;
private final DocumentBlockParser documentBlockParser;
private final LinkReferenceDefinitions definitions = new LinkReferenceDefinitions();
private final List<OpenBlockParser> openBlockParsers = new ArrayList<>();
private final List<BlockParser> allBlockParsers = new ArrayList<>();
public DocumentParser(List<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory,
List<DelimiterProcessor> 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<Class<? extends Block>> getDefaultBlockParserTypes() {
return CORE_FACTORY_TYPES;
}
public static List<BlockParserFactory> calculateBlockParserFactories(List<BlockParserFactory> customBlockParserFactories, Set<Class<? extends Block>> enabledBlockTypes) {
List<BlockParserFactory> 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<Class<? extends Block>> 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<SourceSpan> 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;
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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("^<![A-Z]"),
Pattern.compile(">")
},
{
Pattern.compile("^<!\\[CDATA\\["),
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();
}
}
}

View File

@ -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<CharSequence> 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();
}
}
}
}

View File

@ -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<DelimiterProcessor> delimiterProcessors;
private final LinkReferenceDefinitions linkReferenceDefinitions;
public InlineParserContextImpl(List<DelimiterProcessor> delimiterProcessors,
LinkReferenceDefinitions linkReferenceDefinitions) {
this.delimiterProcessors = delimiterProcessors;
this.linkReferenceDefinitions = linkReferenceDefinitions;
}
@Override
public List<DelimiterProcessor> getCustomDelimiterProcessors() {
return delimiterProcessors;
}
@Override
public LinkReferenceDefinition getLinkReferenceDefinition(String label) {
return linkReferenceDefinitions.get(label);
}
}

View File

@ -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<Character, DelimiterProcessor> delimiterProcessors;
private final InlineParserContext context;
private final Map<Character, List<InlineContentParser>> 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 (<code>[</code> or <code>![)</code>).
*/
private Bracket lastBracket;
public InlineParserImpl(InlineParserContext inlineParserContext) {
this.delimiterProcessors = calculateDelimiterProcessors(inlineParserContext.getCustomDelimiterProcessors());
this.context = inlineParserContext;
this.inlineParsers = new HashMap<>();
this.inlineParsers.put('\\', Collections.<InlineContentParser>singletonList(new BackslashInlineParser()));
this.inlineParsers.put('`', Collections.<InlineContentParser>singletonList(new BackticksInlineParser()));
this.inlineParsers.put('&', Collections.<InlineContentParser>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<Character> delimiterCharacters, Set<Character> 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<Character, DelimiterProcessor> calculateDelimiterProcessors(List<DelimiterProcessor> delimiterProcessors) {
Map<Character, DelimiterProcessor> map = new HashMap<>();
addDelimiterProcessors(Arrays.<DelimiterProcessor>asList(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map);
addDelimiterProcessors(delimiterProcessors, map);
return map;
}
@Override
public Scanner scanner() {
return scanner;
}
private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> 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<Character, DelimiterProcessor> 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<InlineContentParser> 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<Text> characters = res.characters;
// Add entry to stack for this opener
lastDelimiter = new Delimiter(characters, delimiterChar, res.canOpen, res.canClose, lastDelimiter);
if (lastDelimiter.previous != null) {
lastDelimiter.previous.next = lastDelimiter;
}
return characters;
}
/**
* Add open bracket to delimiter stack and add a text node to block's children.
*/
private Node parseOpenBracket() {
Position start = scanner.position();
scanner.next();
Position contentPosition = scanner.position();
Text node = text(scanner.getSource(start, contentPosition));
// Add entry to stack for this opener
addBracket(Bracket.link(node, start, contentPosition, lastBracket, lastDelimiter));
return node;
}
/**
* If next character is [, and ! delimiter to delimiter stack and add a text node to block's children.
* Otherwise just add a text node.
*/
private Node parseBang() {
Position start = scanner.position();
scanner.next();
if (scanner.next('[')) {
Position contentPosition = scanner.position();
Text node = text(scanner.getSource(start, contentPosition));
// Add entry to stack for this opener
addBracket(Bracket.image(node, start, contentPosition, lastBracket, lastDelimiter));
return node;
} else {
return text(scanner.getSource(start, scanner.position()));
}
}
/**
* Try to match close bracket against an opening in the delimiter stack. Return either a link or image, or a
* plain [ character. If there is a matching delimiter, remove it from the delimiter stack.
*/
private Node parseCloseBracket() {
Position beforeClose = scanner.position();
scanner.next();
Position afterClose = scanner.position();
// Get previous `[` or `![`
Bracket opener = lastBracket;
if (opener == null) {
// No matching opener, just return a literal.
return text(scanner.getSource(beforeClose, afterClose));
}
if (!opener.allowed) {
// Matching opener but it's not allowed, just return a literal.
removeLastBracket();
return text(scanner.getSource(beforeClose, afterClose));
}
// Check to see if we have a link/image
String dest = null;
String title = null;
// Maybe a inline link like `[foo](/uri "title")`
if (scanner.next('(')) {
scanner.whitespace();
dest = parseLinkDestination(scanner);
if (dest == null) {
scanner.setPosition(afterClose);
} else {
int whitespace = scanner.whitespace();
// title needs a whitespace before
if (whitespace >= 1) {
title = parseLinkTitle(scanner);
scanner.whitespace();
}
if (!scanner.next(')')) {
// Don't have a closing `)`, so it's not a destination and title -> reset.
// Note that something like `[foo](` could be valid, `(` will just be text.
scanner.setPosition(afterClose);
dest = null;
title = null;
}
}
}
// Maybe a reference link like `[foo][bar]`, `[foo][]` or `[foo]`.
// Note that even `[foo](` could be a valid link if there's a reference, which is why this is not just an `else`
// here.
if (dest == null) {
// See if there's a link label like `[bar]` or `[]`
String ref = parseLinkLabel(scanner);
if (ref == null) {
scanner.setPosition(afterClose);
}
if ((ref == null || ref.isEmpty()) && !opener.bracketAfter) {
// If the second label is empty `[foo][]` or missing `[foo]`, then the first label is the reference.
// But it can only be a reference when there's no (unescaped) bracket in it.
// If there is, we don't even need to try to look up the reference. This is an optimization.
ref = scanner.getSource(opener.contentPosition, beforeClose).getContent();
}
if (ref != null) {
LinkReferenceDefinition definition = context.getLinkReferenceDefinition(ref);
if (definition != null) {
dest = definition.getDestination();
title = definition.getTitle();
}
}
}
if (dest != null) {
// If we got here, we have a link or image
Node linkOrImage = opener.image ? new Image(dest, title) : new Link(dest, title);
// Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link
Node node = opener.node.getNext();
while (node != null) {
Node next = node.getNext();
linkOrImage.appendChild(node);
node = next;
}
if (includeSourceSpans) {
linkOrImage.setSourceSpans(scanner.getSource(opener.markerPosition, scanner.position()).getSourceSpans());
}
// Process delimiters such as emphasis inside link/image
processDelimiters(opener.previousDelimiter);
mergeChildTextNodes(linkOrImage);
// We don't need the corresponding text node anymore, we turned it into a link/image node
opener.node.unlink();
removeLastBracket();
// Links within links are not allowed. We found this link, so there can be no other link around it.
if (!opener.image) {
Bracket bracket = lastBracket;
while (bracket != null) {
if (!bracket.image) {
// Disallow link opener. It will still get matched, but will not result in a link.
bracket.allowed = false;
}
bracket = bracket.previous;
}
}
return linkOrImage;
} else {
// No link or image, parse just the bracket as text and continue
removeLastBracket();
scanner.setPosition(afterClose);
return text(scanner.getSource(beforeClose, afterClose));
}
}
private void addBracket(Bracket bracket) {
if (lastBracket != null) {
lastBracket.bracketAfter = true;
}
lastBracket = bracket;
}
private void removeLastBracket() {
lastBracket = lastBracket.previous;
}
/**
* Attempt to parse link destination, returning the string or null if no match.
*/
private String parseLinkDestination(Scanner scanner) {
char delimiter = scanner.peek();
Position start = scanner.position();
if (!LinkScanner.scanLinkDestination(scanner)) {
return null;
}
String dest;
if (delimiter == '<') {
// chop off surrounding <..>:
String rawDestination = scanner.getSource(start, scanner.position()).getContent();
dest = rawDestination.substring(1, rawDestination.length() - 1);
} else {
dest = scanner.getSource(start, scanner.position()).getContent();
}
return Escaping.unescapeString(dest);
}
/**
* Attempt to parse link title (sans quotes), returning the string or null if no match.
*/
private String parseLinkTitle(Scanner scanner) {
Position start = scanner.position();
if (!LinkScanner.scanLinkTitle(scanner)) {
return null;
}
// chop off ', " or parens
String rawTitle = scanner.getSource(start, scanner.position()).getContent();
String title = rawTitle.substring(1, rawTitle.length() - 1);
return Escaping.unescapeString(title);
}
/**
* Attempt to parse a link label, returning the label between the brackets or null.
*/
String parseLinkLabel(Scanner scanner) {
if (!scanner.next('[')) {
return null;
}
Position start = scanner.position();
if (!LinkScanner.scanLinkLabelContent(scanner)) {
return null;
}
Position end = scanner.position();
if (!scanner.next(']')) {
return null;
}
String content = scanner.getSource(start, end).getContent();
// spec: A link label can have at most 999 characters inside the square brackets.
if (content.length() > 999) {
return null;
}
return content;
}
private Node parseLineBreak() {
scanner.next();
if (trailingSpaces >= 2) {
return new HardLineBreak();
} else {
return new SoftLineBreak();
}
}
/**
* Parse the next character as plain text, and possibly more if the following characters are non-special.
*/
private Node parseText() {
Position start = scanner.position();
scanner.next();
char c;
while (true) {
c = scanner.peek();
if (c == Scanner.END || specialCharacters.get(c)) {
break;
}
scanner.next();
}
SourceLines source = scanner.getSource(start, scanner.position());
String content = source.getContent();
if (c == '\n') {
// We parsed until the end of the line. Trim any trailing spaces and remember them (for hard line breaks).
int end = 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<Text> 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<Character, Delimiter> 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<Text> characters;
final boolean canClose;
final boolean canOpen;
DelimiterData(List<Text> characters, boolean canOpen, boolean canClose) {
this.characters = characters;
this.canOpen = canOpen;
this.canClose = canClose;
}
}
}

View File

@ -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 <a href="https://spec.commonmark.org/0.29/#link-reference-definition">Link reference definitions</a>
*/
public class LinkReferenceDefinitionParser {
private State state = State.START_DEFINITION;
private final List<SourceLine> paragraphLines = new ArrayList<>();
private final List<LinkReferenceDefinition> definitions = new ArrayList<>();
private final List<SourceSpan> 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<SourceSpan> getParagraphSourceSpans() {
return sourceSpans;
}
List<LinkReferenceDefinition> 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,
}
}

View File

@ -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<String, LinkReferenceDefinition> 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);
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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<LinkReferenceDefinition> getDefinitions() {
return linkReferenceDefinitionParser.getDefinitions();
}
}

View File

@ -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<DelimiterProcessor> 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<DelimiterProcessor> 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);
}
}

View File

@ -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));
}
}

View File

@ -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('*');
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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 `<!--`, a string of
// characters not including the string `-->`, 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 <![CDATA[, a string of characters not including 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 <!, an ASCII letter, zero or more characters not including
// the character >, and the character >.
scanner.match(asciiLetter);
if (scanner.whitespace() <= 0) {
return false;
}
if (scanner.find('>') >= 0) {
scanner.next();
return true;
}
return false;
}
}

View File

@ -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);
}

View File

@ -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).
* <p>
* 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();
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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).
* <p>
* 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<SourceLine> 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<SourceLine> 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<String> 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());
}
}
}

View File

@ -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('_');
}
}

View File

@ -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<Class<? extends Node>, 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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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++;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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 = "&amp;";
break;
case '<':
replacement = "&lt;";
break;
case '>':
replacement = "&gt;";
break;
case '\"':
replacement = "&quot;";
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);
}
}

View File

@ -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<String, String> 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<String, String> readEntities() {
Map<String, String> 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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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.
* <p>
* 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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

Some files were not shown because too many files have changed in this diff Show More