mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
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:
parent
39a55e9779
commit
0a58cffe88
@ -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, \
|
||||
))
|
||||
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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.*'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
26
make/modules/jdk.unsupported/Java.gmk
Normal file
26
make/modules/jdk.unsupported/Java.gmk
Normal 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
|
||||
@ -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 := \
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -277,19 +277,26 @@ public interface Elements {
|
||||
getElementValuesWithDefaults(AnnotationMirror a);
|
||||
|
||||
/**
|
||||
* Returns the text of the documentation ("Javadoc")
|
||||
* Returns the text of the documentation ("JavaDoc")
|
||||
* comment of an element.
|
||||
*
|
||||
* <p> A documentation comment of an element is a comment that
|
||||
* begins with "{@code /**}", ends with a separate
|
||||
* "<code>*/</code>", and immediately precedes the element,
|
||||
* ignoring white space, annotations, end-of-line-comments ({@code
|
||||
* "//"} comments), and intermediate traditional comments
|
||||
* (<code>"/* ... */"</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>*/</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>*/</code>" is removed. The line
|
||||
* with the trailing" <code>*/</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}
|
||||
*
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -33,6 +33,11 @@ package com.sun.source.doctree;
|
||||
* {@inheritDoc supertype}
|
||||
* </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 {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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}
|
||||
*
|
||||
|
||||
@ -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 <body> tag, and any enclosing tags are ignored.
|
||||
* Returns {@code null} if no doc comment was found.
|
||||
* Returns the doc comment tree of the given file, which must
|
||||
* be of one of the supported file types, and whose path is
|
||||
* specified relative to the given element.
|
||||
*
|
||||
* <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
|
||||
*/
|
||||
|
||||
@ -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}
|
||||
*
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
@ -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('|');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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 ``
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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('*');
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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('_');
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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++;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, a notice that is now available elsewhere in this distribution
|
||||
* accompanied the original version of this file, and, per its terms,
|
||||
* should not be removed.
|
||||
*/
|
||||
|
||||
package jdk.internal.org.commonmark.internal.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Escaping {
|
||||
|
||||
public static final String ESCAPABLE = "[!\"#$%&\'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~-]";
|
||||
|
||||
public static final String ENTITY = "&(?:#x[a-f0-9]{1,6}|#[0-9]{1,7}|[a-z][a-z0-9]{1,31});";
|
||||
|
||||
private static final Pattern BACKSLASH_OR_AMP = Pattern.compile("[\\\\&]");
|
||||
|
||||
private static final Pattern ENTITY_OR_ESCAPED_CHAR =
|
||||
Pattern.compile("\\\\" + ESCAPABLE + '|' + ENTITY, Pattern.CASE_INSENSITIVE);
|
||||
|
||||
// From RFC 3986 (see "reserved", "unreserved") except don't escape '[' or ']' to be compatible with JS encodeURI
|
||||
private static final Pattern ESCAPE_IN_URI =
|
||||
Pattern.compile("(%[a-fA-F0-9]{0,2}|[^:/?#@!$&'()*+,;=a-zA-Z0-9\\-._~])");
|
||||
|
||||
private static final char[] HEX_DIGITS =
|
||||
new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||
|
||||
private static final Pattern WHITESPACE = Pattern.compile("[ \t\r\n]+");
|
||||
|
||||
private static final Replacer UNESCAPE_REPLACER = new Replacer() {
|
||||
@Override
|
||||
public void replace(String input, StringBuilder sb) {
|
||||
if (input.charAt(0) == '\\') {
|
||||
sb.append(input, 1, input.length());
|
||||
} else {
|
||||
sb.append(Html5Entities.entityToString(input));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static final Replacer URI_REPLACER = new Replacer() {
|
||||
@Override
|
||||
public void replace(String input, StringBuilder sb) {
|
||||
if (input.startsWith("%")) {
|
||||
if (input.length() == 3) {
|
||||
// Already percent-encoded, preserve
|
||||
sb.append(input);
|
||||
} else {
|
||||
// %25 is the percent-encoding for %
|
||||
sb.append("%25");
|
||||
sb.append(input, 1, input.length());
|
||||
}
|
||||
} else {
|
||||
byte[] bytes = input.getBytes(Charset.forName("UTF-8"));
|
||||
for (byte b : bytes) {
|
||||
sb.append('%');
|
||||
sb.append(HEX_DIGITS[(b >> 4) & 0xF]);
|
||||
sb.append(HEX_DIGITS[b & 0xF]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static String escapeHtml(String input) {
|
||||
// Avoid building a new string in the majority of cases (nothing to escape)
|
||||
StringBuilder sb = null;
|
||||
|
||||
loop:
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
char c = input.charAt(i);
|
||||
String replacement;
|
||||
switch (c) {
|
||||
case '&':
|
||||
replacement = "&";
|
||||
break;
|
||||
case '<':
|
||||
replacement = "<";
|
||||
break;
|
||||
case '>':
|
||||
replacement = ">";
|
||||
break;
|
||||
case '\"':
|
||||
replacement = """;
|
||||
break;
|
||||
default:
|
||||
if (sb != null) {
|
||||
sb.append(c);
|
||||
}
|
||||
continue loop;
|
||||
}
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder();
|
||||
sb.append(input, 0, i);
|
||||
}
|
||||
sb.append(replacement);
|
||||
}
|
||||
|
||||
return sb != null ? sb.toString() : input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace entities and backslash escapes with literal characters.
|
||||
*/
|
||||
public static String unescapeString(String s) {
|
||||
if (BACKSLASH_OR_AMP.matcher(s).find()) {
|
||||
return replaceAll(ENTITY_OR_ESCAPED_CHAR, s, UNESCAPE_REPLACER);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
public static String percentEncodeUrl(String s) {
|
||||
return replaceAll(ESCAPE_IN_URI, s, URI_REPLACER);
|
||||
}
|
||||
|
||||
public static String normalizeLabelContent(String input) {
|
||||
String trimmed = input.trim();
|
||||
|
||||
// This is necessary to correctly case fold "\u1e9e" to "SS":
|
||||
// "\u1e9e".toLowerCase(Locale.ROOT) -> "\u00df"
|
||||
// "\u00df".toUpperCase(Locale.ROOT) -> "SS"
|
||||
// Note that doing upper first (or only upper without lower) wouldn't work because:
|
||||
// "\u1e9e".toUpperCase(Locale.ROOT) -> "\u1e9e"
|
||||
String caseFolded = trimmed.toLowerCase(Locale.ROOT).toUpperCase(Locale.ROOT);
|
||||
|
||||
return WHITESPACE.matcher(caseFolded).replaceAll(" ");
|
||||
}
|
||||
|
||||
private static String replaceAll(Pattern p, String s, Replacer replacer) {
|
||||
Matcher matcher = p.matcher(s);
|
||||
|
||||
if (!matcher.find()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(s.length() + 16);
|
||||
int lastEnd = 0;
|
||||
do {
|
||||
sb.append(s, lastEnd, matcher.start());
|
||||
replacer.replace(matcher.group(), sb);
|
||||
lastEnd = matcher.end();
|
||||
} while (matcher.find());
|
||||
|
||||
if (lastEnd != s.length()) {
|
||||
sb.append(s, lastEnd, s.length());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private interface Replacer {
|
||||
void replace(String input, StringBuilder sb);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user