8288699: cleanup HTML tree in HtmlDocletWriter.commentTagsToContent
Reviewed-by: hannesw
This commit is contained in:
parent
6aaf141f61
commit
54b4576f78
@ -877,6 +877,32 @@ public class DocCommentParser {
|
||||
}
|
||||
nextChar();
|
||||
}
|
||||
} else {
|
||||
String CDATA = "[CDATA["; // full prefix is <![CDATA[
|
||||
for (int i = 0; i < CDATA.length(); i++) {
|
||||
if (ch == CDATA.charAt(i)) {
|
||||
nextChar();
|
||||
} else {
|
||||
return erroneous("dc.invalid.html", p);
|
||||
}
|
||||
}
|
||||
// suffix is ]]>
|
||||
while (bp < buflen) {
|
||||
if (ch == ']') {
|
||||
int n = 0;
|
||||
while (bp < buflen && ch == ']') {
|
||||
n++;
|
||||
nextChar();
|
||||
}
|
||||
if (n >= 2 && ch == '>') {
|
||||
nextChar();
|
||||
return m.at(p).newTextTree(newString(p, bp));
|
||||
}
|
||||
} else {
|
||||
nextChar();
|
||||
}
|
||||
}
|
||||
return erroneous("dc.invalid.html", p);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3339,6 +3339,9 @@ compiler.err.dc.bad.inline.tag=\
|
||||
compiler.err.dc.identifier.expected=\
|
||||
identifier expected
|
||||
|
||||
compiler.err.dc.invalid.html=\
|
||||
invalid HTML
|
||||
|
||||
compiler.err.dc.malformed.html=\
|
||||
malformed HTML
|
||||
|
||||
|
@ -125,7 +125,7 @@ public abstract class AbstractOverviewIndexWriter extends HtmlDocletWriter {
|
||||
protected void addConfigurationTitle(Content target) {
|
||||
String doctitle = configuration.getOptions().docTitle();
|
||||
if (!doctitle.isEmpty()) {
|
||||
var title = new RawHtml(doctitle);
|
||||
var title = RawHtml.of(doctitle);
|
||||
var heading = HtmlTree.HEADING(Headings.PAGE_TITLE_HEADING,
|
||||
HtmlStyle.title, title);
|
||||
var div = HtmlTree.DIV(HtmlStyle.header, heading);
|
||||
|
@ -500,7 +500,7 @@ public class HtmlDocletWriter {
|
||||
*/
|
||||
protected HtmlTree getHeader(Navigation.PageMode pageMode, Element element) {
|
||||
return HtmlTree.HEADER()
|
||||
.add(new RawHtml(replaceDocRootDir(options.top())))
|
||||
.add(RawHtml.of(replaceDocRootDir(options.top())))
|
||||
.add(getNavBar(pageMode, element).getContent());
|
||||
}
|
||||
|
||||
@ -515,7 +515,7 @@ public class HtmlDocletWriter {
|
||||
*/
|
||||
protected Navigation getNavBar(Navigation.PageMode pageMode, Element element) {
|
||||
return new Navigation(element, configuration, pageMode, path)
|
||||
.setUserHeader(new RawHtml(replaceDocRootDir(options.header())));
|
||||
.setUserHeader(RawHtml.of(replaceDocRootDir(options.header())));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -532,7 +532,7 @@ public class HtmlDocletWriter {
|
||||
.add(new HtmlTree(TagName.HR))
|
||||
.add(HtmlTree.P(HtmlStyle.legalCopy,
|
||||
HtmlTree.SMALL(
|
||||
new RawHtml(replaceDocRootDir(bottom)))));
|
||||
RawHtml.of(replaceDocRootDir(bottom)))));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1004,7 +1004,7 @@ public class HtmlDocletWriter {
|
||||
}
|
||||
case START_ELEMENT -> {
|
||||
// @see <a href="...">...</a>
|
||||
return new RawHtml(replaceDocRootDir(removeTrailingSlash(seeText)));
|
||||
return RawHtml.of(replaceDocRootDir(removeTrailingSlash(seeText)));
|
||||
}
|
||||
case REFERENCE -> {
|
||||
// @see reference label...
|
||||
@ -1484,7 +1484,6 @@ public class HtmlDocletWriter {
|
||||
}
|
||||
};
|
||||
CommentHelper ch = utils.getCommentHelper(element);
|
||||
// Array of all possible inline tags for this javadoc run
|
||||
configuration.tagletManager.checkTags(element, trees, true);
|
||||
commentRemoved = false;
|
||||
|
||||
@ -1511,103 +1510,108 @@ public class HtmlDocletWriter {
|
||||
}
|
||||
}
|
||||
|
||||
boolean allDone = new SimpleDocTreeVisitor<Boolean, Content>() {
|
||||
var docTreeVisitor = new SimpleDocTreeVisitor<Boolean, Content>() {
|
||||
|
||||
private boolean inAnAtag() {
|
||||
if (utils.isStartElement(tag)) {
|
||||
StartElementTree st = (StartElementTree)tag;
|
||||
Name name = st.getName();
|
||||
if (name != null) {
|
||||
HtmlTag htag = HtmlTag.get(name);
|
||||
return htag != null && htag.equals(HtmlTag.A);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return (tag instanceof StartElementTree st) && equalsIgnoreCase(st.getName(), "a");
|
||||
}
|
||||
|
||||
private boolean equalsIgnoreCase(Name name, String s) {
|
||||
return name != null && name.toString().equalsIgnoreCase(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitAttribute(AttributeTree node, Content c) {
|
||||
StringBuilder sb = new StringBuilder(SPACER).append(node.getName().toString());
|
||||
public Boolean visitAttribute(AttributeTree node, Content content) {
|
||||
if (!content.isEmpty()) {
|
||||
content.add(" ");
|
||||
}
|
||||
content.add(node.getName());
|
||||
if (node.getValueKind() == ValueKind.EMPTY) {
|
||||
result.add(sb);
|
||||
return false;
|
||||
}
|
||||
sb.append("=");
|
||||
content.add("=");
|
||||
String quote = switch (node.getValueKind()) {
|
||||
case DOUBLE -> "\"";
|
||||
case SINGLE -> "'";
|
||||
default -> "";
|
||||
};
|
||||
sb.append(quote);
|
||||
result.add(sb);
|
||||
Content docRootContent = new ContentBuilder();
|
||||
content.add(quote);
|
||||
|
||||
boolean isHRef = inAnAtag() && node.getName().toString().equalsIgnoreCase("href");
|
||||
/* In the following code for an attribute value:
|
||||
* 1. {@docRoot} followed by text beginning "/.." is replaced by the value
|
||||
* of the docrootParent option, followed by the remainder of the text
|
||||
* 2. in the value of an "href" attribute in a <a> tag, an initial text
|
||||
* value will have a relative link redirected.
|
||||
* Note that, realistically, it only makes sense to ever use {@docRoot}
|
||||
* at the beginning of a URL in an attribute value, but this is not
|
||||
* required or enforced.
|
||||
*/
|
||||
boolean isHRef = inAnAtag() && equalsIgnoreCase(node.getName(), "href");
|
||||
boolean first = true;
|
||||
DocRootTree pendingDocRoot = null;
|
||||
for (DocTree dt : node.getValue()) {
|
||||
if (utils.isText(dt) && isHRef) {
|
||||
String text = ((TextTree) dt).getBody();
|
||||
if (text.startsWith("/..") && !options.docrootParent().isEmpty()) {
|
||||
result.add(options.docrootParent());
|
||||
docRootContent = new ContentBuilder();
|
||||
result.add(textCleanup(text.substring(3), isLastNode));
|
||||
} else {
|
||||
if (!docRootContent.isEmpty()) {
|
||||
docRootContent = copyDocRootContent(docRootContent);
|
||||
} else {
|
||||
text = redirectRelativeLinks(element, (TextTree) dt);
|
||||
if (pendingDocRoot != null) {
|
||||
if (dt instanceof TextTree tt) {
|
||||
String text = tt.getBody();
|
||||
if (text.startsWith("/..") && !options.docrootParent().isEmpty()) {
|
||||
content.add(options.docrootParent());
|
||||
content.add(textCleanup(text.substring(3), isLastNode));
|
||||
pendingDocRoot = null;
|
||||
continue;
|
||||
}
|
||||
result.add(textCleanup(text, isLastNode));
|
||||
}
|
||||
} else {
|
||||
docRootContent = copyDocRootContent(docRootContent);
|
||||
dt.accept(this, docRootContent);
|
||||
pendingDocRoot.accept(this, content);
|
||||
pendingDocRoot = null;
|
||||
}
|
||||
|
||||
if (dt instanceof TextTree tt) {
|
||||
String text = tt.getBody();
|
||||
if (first && isHRef) {
|
||||
text = redirectRelativeLinks(element, tt);
|
||||
}
|
||||
content.add(textCleanup(text, isLastNode));
|
||||
} else if (dt instanceof DocRootTree drt) {
|
||||
// defer until we see what, if anything, follows this node
|
||||
pendingDocRoot = drt;
|
||||
} else {
|
||||
dt.accept(this, content);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
copyDocRootContent(docRootContent);
|
||||
result.add(quote);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitComment(CommentTree node, Content c) {
|
||||
result.add(new RawHtml(node.getBody()));
|
||||
return false;
|
||||
}
|
||||
|
||||
private Content copyDocRootContent(Content content) {
|
||||
if (!content.isEmpty()) {
|
||||
result.add(content);
|
||||
return new ContentBuilder();
|
||||
if (pendingDocRoot != null) {
|
||||
pendingDocRoot.accept(this, content);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitDocRoot(DocRootTree node, Content c) {
|
||||
Content docRootContent = getInlineTagOutput(element, node, context);
|
||||
if (c != null) {
|
||||
c.add(docRootContent);
|
||||
} else {
|
||||
result.add(docRootContent);
|
||||
}
|
||||
content.add(quote);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitEndElement(EndElementTree node, Content c) {
|
||||
RawHtml rawHtml = new RawHtml("</" + node.getName() + ">");
|
||||
result.add(rawHtml);
|
||||
public Boolean visitComment(CommentTree node, Content content) {
|
||||
content.add(RawHtml.comment(node.getBody()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitEntity(EntityTree node, Content c) {
|
||||
result.add(new RawHtml(node.toString()));
|
||||
public Boolean visitDocRoot(DocRootTree node, Content content) {
|
||||
content.add(getInlineTagOutput(element, node, context));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitErroneous(ErroneousTree node, Content c) {
|
||||
public Boolean visitEndElement(EndElementTree node, Content content) {
|
||||
content.add(RawHtml.endElement(node.getName()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitEntity(EntityTree node, Content content) {
|
||||
content.add(Entity.of(node.getName()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitErroneous(ErroneousTree node, Content content) {
|
||||
DocTreePath dtp = ch.getDocTreePath(node);
|
||||
if (dtp != null) {
|
||||
String body = node.getBody();
|
||||
@ -1617,11 +1621,11 @@ public class HtmlDocletWriter {
|
||||
if (!configuration.isDocLintSyntaxGroupEnabled()) {
|
||||
messages.warning(dtp, "doclet.tag.invalid_input", body);
|
||||
}
|
||||
result.add(invalidTagOutput(resources.getText("doclet.tag.invalid_input", body),
|
||||
content.add(invalidTagOutput(resources.getText("doclet.tag.invalid_input", body),
|
||||
Optional.empty()));
|
||||
} else {
|
||||
messages.warning(dtp, "doclet.tag.invalid_usage", body);
|
||||
result.add(invalidTagOutput(resources.getText("doclet.tag.invalid", tagName),
|
||||
content.add(invalidTagOutput(resources.getText("doclet.tag.invalid", tagName),
|
||||
Optional.of(Text.of(body))));
|
||||
}
|
||||
}
|
||||
@ -1629,24 +1633,24 @@ public class HtmlDocletWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitInheritDoc(InheritDocTree node, Content c) {
|
||||
public Boolean visitInheritDoc(InheritDocTree node, Content content) {
|
||||
Content output = getInlineTagOutput(element, node, context);
|
||||
result.add(output);
|
||||
content.add(output);
|
||||
// if we obtained the first sentence successfully, nothing more to do
|
||||
return (context.isFirstSentence && !output.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitIndex(IndexTree node, Content p) {
|
||||
public Boolean visitIndex(IndexTree node, Content content) {
|
||||
Content output = getInlineTagOutput(element, node, context);
|
||||
if (output != null) {
|
||||
result.add(output);
|
||||
content.add(output);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitLink(LinkTree node, Content c) {
|
||||
public Boolean visitLink(LinkTree node, Content content) {
|
||||
var inTags = context.inTags;
|
||||
if (inTags.contains(LINK) || inTags.contains(LINK_PLAIN) || inTags.contains(SEE)) {
|
||||
DocTreePath dtp = ch.getDocTreePath(node);
|
||||
@ -1657,55 +1661,50 @@ public class HtmlDocletWriter {
|
||||
if (label.isEmpty()) {
|
||||
label = Text.of(node.getReference().getSignature());
|
||||
}
|
||||
result.add(label);
|
||||
content.add(label);
|
||||
} else {
|
||||
Content content = seeTagToContent(element, node, context.within(node));
|
||||
result.add(content);
|
||||
Content c = seeTagToContent(element, node, context.within(node));
|
||||
content.add(c);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitLiteral(LiteralTree node, Content c) {
|
||||
public Boolean visitLiteral(LiteralTree node, Content content) {
|
||||
String s = node.getBody().getBody();
|
||||
Content content = Text.of(utils.normalizeNewlines(s));
|
||||
if (node.getKind() == CODE)
|
||||
content = HtmlTree.CODE(content);
|
||||
result.add(content);
|
||||
Content t = Text.of(utils.normalizeNewlines(s));
|
||||
content.add(node.getKind() == CODE ? HtmlTree.CODE(t) : t);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitSee(SeeTree node, Content c) {
|
||||
result.add(seeTagToContent(element, node, context));
|
||||
public Boolean visitSee(SeeTree node, Content content) {
|
||||
content.add(seeTagToContent(element, node, context));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitStartElement(StartElementTree node, Content c) {
|
||||
String text = "<" + node.getName();
|
||||
RawHtml rawHtml = new RawHtml(utils.normalizeNewlines(text));
|
||||
result.add(rawHtml);
|
||||
|
||||
public Boolean visitStartElement(StartElementTree node, Content content) {
|
||||
Content attrs = new ContentBuilder();
|
||||
for (DocTree dt : node.getAttributes()) {
|
||||
dt.accept(this, null);
|
||||
dt.accept(this, attrs);
|
||||
}
|
||||
result.add(new RawHtml(node.isSelfClosing() ? "/>" : ">"));
|
||||
content.add(RawHtml.startElement(node.getName(), attrs, node.isSelfClosing()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitSummary(SummaryTree node, Content c) {
|
||||
public Boolean visitSummary(SummaryTree node, Content content) {
|
||||
Content output = getInlineTagOutput(element, node, context);
|
||||
result.add(output);
|
||||
content.add(output);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitSystemProperty(SystemPropertyTree node, Content p) {
|
||||
public Boolean visitSystemProperty(SystemPropertyTree node, Content content) {
|
||||
Content output = getInlineTagOutput(element, node, context);
|
||||
if (output != null) {
|
||||
result.add(output);
|
||||
content.add(output);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1728,23 +1727,28 @@ public class HtmlDocletWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visitText(TextTree node, Content c) {
|
||||
public Boolean visitText(TextTree node, Content content) {
|
||||
String text = node.getBody();
|
||||
result.add(new RawHtml(textCleanup(text, isLastNode, commentRemoved)));
|
||||
result.add(text.startsWith("<![CDATA[")
|
||||
? RawHtml.cdata(text)
|
||||
: Text.of(textCleanup(text, isLastNode, commentRemoved)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean defaultAction(DocTree node, Content c) {
|
||||
protected Boolean defaultAction(DocTree node, Content content) {
|
||||
Content output = getInlineTagOutput(element, node, context);
|
||||
if (output != null) {
|
||||
result.add(output);
|
||||
content.add(output);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}.visit(tag, null);
|
||||
};
|
||||
|
||||
boolean allDone = docTreeVisitor.visit(tag, result);
|
||||
commentRemoved = false;
|
||||
|
||||
if (allDone)
|
||||
break;
|
||||
}
|
||||
@ -1897,15 +1901,6 @@ public class HtmlDocletWriter {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* According to
|
||||
* <cite>The Java Language Specification</cite>,
|
||||
* all the outer classes and static nested classes are core classes.
|
||||
*/
|
||||
public boolean isCoreClass(TypeElement typeElement) {
|
||||
return utils.getEnclosingTypeElement(typeElement) == null || utils.isStatic(typeElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the annotation types info for the given element}
|
||||
*
|
||||
@ -2166,6 +2161,7 @@ public class HtmlDocletWriter {
|
||||
}
|
||||
}.visit(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content visitAnnotation(AnnotationMirror a, Void p) {
|
||||
List<Content> list = getAnnotations(List.of(a), false);
|
||||
@ -2175,10 +2171,12 @@ public class HtmlDocletWriter {
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content visitEnumConstant(VariableElement c, Void p) {
|
||||
return getDocLink(HtmlLinkInfo.Kind.ANNOTATION, c, c.getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content visitArray(List<? extends AnnotationValue> vals, Void p) {
|
||||
ContentBuilder buf = new ContentBuilder();
|
||||
@ -2190,6 +2188,7 @@ public class HtmlDocletWriter {
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Content defaultAction(Object o, Void p) {
|
||||
return Text.of(annotationValue.toString());
|
||||
@ -2274,10 +2273,6 @@ public class HtmlDocletWriter {
|
||||
return HtmlStyle.valueOf(page);
|
||||
}
|
||||
|
||||
Script getMainBodyScript() {
|
||||
return mainBodyScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of module/package specific stylesheets for the element.
|
||||
* @param element module/Package element
|
||||
|
@ -134,7 +134,7 @@ public class HtmlSerialFieldWriter extends FieldWriterImpl
|
||||
CommentHelper ch = utils.getCommentHelper(field);
|
||||
List<? extends DocTree> description = ch.getDescription(serialFieldTag);
|
||||
if (!description.isEmpty()) {
|
||||
Content serialFieldContent = new RawHtml(ch.getText(description));
|
||||
Content serialFieldContent = RawHtml.of(ch.getText(description)); // should interpret tags
|
||||
var div = HtmlTree.DIV(HtmlStyle.block, serialFieldContent);
|
||||
content.add(div);
|
||||
}
|
||||
|
@ -530,7 +530,7 @@ public class Signatures {
|
||||
*/
|
||||
private int appendTypeParameters(Content target, int lastLineSeparator) {
|
||||
// Apply different wrapping strategies for type parameters
|
||||
// depending of combined length of type parameters and return type.
|
||||
// depending on the combined length of type parameters and return type.
|
||||
int typeParamLength = typeParameters.charCount();
|
||||
|
||||
if (typeParamLength >= TYPE_PARAMS_MAX_INLINE_LENGTH) {
|
||||
|
@ -52,6 +52,7 @@ import com.sun.source.doctree.ReturnTree;
|
||||
import com.sun.source.doctree.SeeTree;
|
||||
import com.sun.source.doctree.SnippetTree;
|
||||
import com.sun.source.doctree.SystemPropertyTree;
|
||||
import com.sun.source.doctree.TextTree;
|
||||
import com.sun.source.doctree.ThrowsTree;
|
||||
import com.sun.source.util.DocTreePath;
|
||||
import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
|
||||
@ -307,7 +308,7 @@ public class TagletWriterImpl extends TagletWriter {
|
||||
public Content returnTagOutput(Element element, ReturnTree returnTag, boolean inline) {
|
||||
CommentHelper ch = utils.getCommentHelper(element);
|
||||
List<? extends DocTree> desc = ch.getDescription(returnTag);
|
||||
Content content = htmlWriter.commentTagsToContent(element, desc , context.within(returnTag));
|
||||
Content content = htmlWriter.commentTagsToContent(element, desc, context.within(returnTag));
|
||||
return inline
|
||||
? new ContentBuilder(contents.getContent("doclet.Returns_0", content))
|
||||
: new ContentBuilder(HtmlTree.DT(contents.returns), HtmlTree.DD(content));
|
||||
@ -371,7 +372,7 @@ public class TagletWriterImpl extends TagletWriter {
|
||||
many = true;
|
||||
}
|
||||
return new ContentBuilder(
|
||||
HtmlTree.DT(new RawHtml(header)),
|
||||
HtmlTree.DT(RawHtml.of(header)),
|
||||
HtmlTree.DD(body));
|
||||
}
|
||||
|
||||
@ -504,9 +505,9 @@ public class TagletWriterImpl extends TagletWriter {
|
||||
excName = htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.MEMBER,
|
||||
substituteType));
|
||||
} else if (exception == null) {
|
||||
excName = new RawHtml(throwsTag.getExceptionName().toString());
|
||||
excName = RawHtml.of(throwsTag.getExceptionName().toString());
|
||||
} else if (exception.asType() == null) {
|
||||
excName = new RawHtml(utils.getFullyQualifiedName(exception));
|
||||
excName = Text.of(utils.getFullyQualifiedName(exception));
|
||||
} else {
|
||||
HtmlLinkInfo link = new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.MEMBER,
|
||||
exception.asType());
|
||||
|
@ -41,6 +41,16 @@ public class Entity extends Content {
|
||||
|
||||
public final String text;
|
||||
|
||||
/**
|
||||
* Creates an entity with a given name or numeric value.
|
||||
*
|
||||
* @param name the name, or numeric value
|
||||
* @return the entity
|
||||
*/
|
||||
public static Entity of(CharSequence name) {
|
||||
return new Entity("&" + name + ";");
|
||||
}
|
||||
|
||||
private Entity(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
@ -36,14 +36,91 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;
|
||||
*/
|
||||
public class RawHtml extends Content {
|
||||
|
||||
private final String rawHtmlContent;
|
||||
protected final String rawHtmlContent;
|
||||
|
||||
/**
|
||||
* Creates HTML for an arbitrary string of HTML.
|
||||
* The string is accepted as-is and is not validated in any way.
|
||||
* It should be syntactically well-formed and contain matching {@code <} and {@code >},
|
||||
* and matching quotes for attributes.
|
||||
*
|
||||
* @param rawHtml the string
|
||||
* @return the HTML
|
||||
*/
|
||||
public static RawHtml of(CharSequence rawHtml) {
|
||||
return new RawHtml(rawHtml) {
|
||||
@Override
|
||||
public int charCount() {
|
||||
return charCount(rawHtmlContent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates HTML for the start of an element.
|
||||
*
|
||||
* @param name the name of the element
|
||||
* @param attrs content containing any attributes
|
||||
* @param selfClosing whether this is a self-closing element.
|
||||
* @return the HTML
|
||||
*/
|
||||
public static RawHtml startElement(CharSequence name, Content attrs, boolean selfClosing) {
|
||||
StringBuilder sb = new StringBuilder("<" + name);
|
||||
if (!attrs.isEmpty()) {
|
||||
sb.append(" ");
|
||||
sb.append(attrs);
|
||||
}
|
||||
sb.append(selfClosing ? "/>" : ">");
|
||||
return new RawHtml(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates HTML for the end of an element.
|
||||
*
|
||||
* @param name the name of the element
|
||||
* @return the HTML
|
||||
*/
|
||||
public static RawHtml endElement(CharSequence name) {
|
||||
return new RawHtml("</" + name + ">");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates HTML for an HTML comment.
|
||||
*
|
||||
* The body will be enclosed in {@code <!--} and {@code -->} if it does not
|
||||
* already begin and end with those sequences.
|
||||
*
|
||||
* @param body the body of the comment
|
||||
*
|
||||
* @return the HTML
|
||||
*/
|
||||
public static RawHtml comment(String body) {
|
||||
return section("<!--", body, "-->");
|
||||
}
|
||||
/**
|
||||
* Creates HTML for an HTML CDATA section.
|
||||
*
|
||||
* The body will be enclosed in {@code <![CDATA]} and {@code ]]>} if it does not
|
||||
* already begin and end with those sequences.
|
||||
*
|
||||
* @param body the body of the CDATA section
|
||||
*
|
||||
* @return the HTML
|
||||
*/
|
||||
public static RawHtml cdata(String body) {
|
||||
return section("<![CDATA[", body, "]]>");
|
||||
}
|
||||
|
||||
private static RawHtml section(String prefix, String body, String suffix) {
|
||||
return new RawHtml(body.startsWith(prefix) && body.endsWith(suffix) ? body : prefix + body + suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to construct a RawHtml object.
|
||||
*
|
||||
* @param rawHtml raw HTML text to be added
|
||||
*/
|
||||
public RawHtml(CharSequence rawHtml) {
|
||||
private RawHtml(CharSequence rawHtml) {
|
||||
rawHtmlContent = rawHtml.toString();
|
||||
}
|
||||
|
||||
@ -59,12 +136,7 @@ public class RawHtml extends Content {
|
||||
|
||||
private enum State { TEXT, ENTITY, TAG, STRING }
|
||||
|
||||
@Override
|
||||
public int charCount() {
|
||||
return charCount(rawHtmlContent);
|
||||
}
|
||||
|
||||
static int charCount(CharSequence htmlText) {
|
||||
protected static int charCount(CharSequence htmlText) {
|
||||
State state = State.TEXT;
|
||||
int count = 0;
|
||||
for (int i = 0; i < htmlText.length(); i++) {
|
||||
@ -80,9 +152,12 @@ public class RawHtml extends Content {
|
||||
count++;
|
||||
break;
|
||||
case '\r':
|
||||
case '\n':
|
||||
// Windows uses "\r\n" as line separator while UNIX uses "\n".
|
||||
// Ignore line separators to get consistent results across platforms.
|
||||
// Skip the "\r" to get consistent results across platforms.
|
||||
if (i + 1 < htmlText.length() && htmlText.charAt(i + 1) == '\n') {
|
||||
i++;
|
||||
}
|
||||
count++;
|
||||
break;
|
||||
default:
|
||||
count++;
|
||||
|
@ -33,6 +33,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;
|
||||
|
||||
/**
|
||||
* Class for containing immutable string content for HTML tags of javadoc output.
|
||||
* Any special HTML characters will be escaped if and when the content is written out.
|
||||
*/
|
||||
public class Text extends Content {
|
||||
private final String string;
|
||||
@ -55,7 +56,7 @@ public class Text extends Content {
|
||||
* @param content content for the object
|
||||
*/
|
||||
private Text(CharSequence content) {
|
||||
string = Entity.escapeHtmlChars(content);
|
||||
string = content.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,7 +66,20 @@ public class Text extends Content {
|
||||
|
||||
@Override
|
||||
public int charCount() {
|
||||
return RawHtml.charCount(string);
|
||||
return charCount(string);
|
||||
}
|
||||
|
||||
static int charCount(CharSequence cs) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < cs.length(); i++) {
|
||||
// Windows uses "\r\n" as line separator while UNIX uses "\n".
|
||||
// Skip the "\r" to get consistent results across platforms.
|
||||
if (cs.charAt(i) == '\r' && (i + 1 < cs.length()) && cs.charAt(i + 1) == '\n') {
|
||||
i++;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -75,7 +89,7 @@ public class Text extends Content {
|
||||
|
||||
@Override
|
||||
public boolean write(Writer out, boolean atNewline) throws IOException {
|
||||
out.write(string);
|
||||
out.write(Entity.escapeHtmlChars(string));
|
||||
return string.endsWith(DocletConstants.NL);
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;
|
||||
/**
|
||||
* Class for generating string content for HTML tags of javadoc output.
|
||||
* The content is mutable to the extent that additional content may be added.
|
||||
* Any special HTML characters will be escaped if and when the content is written out.
|
||||
*/
|
||||
public class TextBuilder extends Content {
|
||||
|
||||
@ -52,19 +53,17 @@ public class TextBuilder extends Content {
|
||||
* @param initialContent initial content for the object
|
||||
*/
|
||||
public TextBuilder(CharSequence initialContent) {
|
||||
stringBuilder = new StringBuilder();
|
||||
Entity.escapeHtmlChars(initialContent, stringBuilder);
|
||||
stringBuilder = new StringBuilder(initialContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds content for the StringContent object. The method escapes
|
||||
* HTML characters for the string content that is added.
|
||||
* Adds content for the StringContent object.
|
||||
*
|
||||
* @param strContent string content to be added
|
||||
*/
|
||||
@Override
|
||||
public TextBuilder add(CharSequence strContent) {
|
||||
Entity.escapeHtmlChars(strContent, stringBuilder);
|
||||
stringBuilder.append(strContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -75,7 +74,7 @@ public class TextBuilder extends Content {
|
||||
|
||||
@Override
|
||||
public int charCount() {
|
||||
return RawHtml.charCount(stringBuilder.toString());
|
||||
return Text.charCount(stringBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -85,7 +84,7 @@ public class TextBuilder extends Content {
|
||||
|
||||
@Override
|
||||
public boolean write(Writer out, boolean atNewline) throws IOException {
|
||||
String s = stringBuilder.toString();
|
||||
String s = Entity.escapeHtmlChars(stringBuilder);
|
||||
out.write(s);
|
||||
return s.endsWith(DocletConstants.NL);
|
||||
}
|
||||
|
@ -128,6 +128,40 @@ public class CommentUtils {
|
||||
return treeFactory.newTextTree(resources.getText(key));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a string, looking for simple embedded HTML.
|
||||
* @param s the string
|
||||
* @return the list of parsed {@code DocTree} nodes
|
||||
*/
|
||||
private List<DocTree> parse(String s) {
|
||||
List<DocTree> list = null;
|
||||
Pattern p = Pattern.compile("(?i)<(/)?([a-z0-9]+)(/)?>");
|
||||
Matcher m = p.matcher(s);
|
||||
int start = 0;
|
||||
while (m.find()) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
if (m.start() > 0) {
|
||||
list.add(treeFactory.newTextTree(s.substring(start, m.start())));
|
||||
}
|
||||
Name name = elementUtils.getName(m.group(2));
|
||||
list.add(m.group(1) == null
|
||||
? treeFactory.newStartElementTree(name, List.of(), m.group(3) != null)
|
||||
: treeFactory.newEndElementTree(name));
|
||||
start = m.end();
|
||||
}
|
||||
if (list == null) {
|
||||
return List.of(treeFactory.newTextTree(s));
|
||||
} else {
|
||||
if (start < s.length()) {
|
||||
list.add(treeFactory.newTextTree(s.substring(start, s.length())));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public void setEnumValuesTree(ExecutableElement ee) {
|
||||
List<DocTree> fullBody = new ArrayList<>();
|
||||
fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_values_doc.fullbody")));
|
||||
@ -142,8 +176,7 @@ public class CommentUtils {
|
||||
}
|
||||
|
||||
public void setEnumValueOfTree(ExecutableElement ee) {
|
||||
List<DocTree> fullBody = new ArrayList<>();
|
||||
fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.fullbody")));
|
||||
List<DocTree> fullBody = parse(resources.getText("doclet.enum_valueof_doc.fullbody"));
|
||||
|
||||
List<DocTree> tags = new ArrayList<>();
|
||||
|
||||
@ -242,15 +275,15 @@ public class CommentUtils {
|
||||
int start = 0;
|
||||
while (m.find(start)) {
|
||||
if (m.start() > start) {
|
||||
contents.add(treeFactory.newTextTree(body.substring(start, m.start())));
|
||||
contents.addAll(parse(body.substring(start, m.start())));
|
||||
}
|
||||
ReferenceTree refTree = treeFactory.newReferenceTree(m.group(1));
|
||||
List<DocTree> descr = List.of(treeFactory.newTextTree(m.group(2).trim())) ;
|
||||
List<DocTree> descr = parse(m.group(2).trim());
|
||||
contents.add(treeFactory.newLinkTree(refTree, descr));
|
||||
start = m.end();
|
||||
}
|
||||
if (start < body.length()) {
|
||||
contents.add(treeFactory.newTextTree(body.substring(start)));
|
||||
contents.addAll(parse(body.substring(start)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -485,16 +518,16 @@ public class CommentUtils {
|
||||
String text = resources.getText(key);
|
||||
int index = text.indexOf("{0}");
|
||||
if (index == -1) {
|
||||
return List.of(treeFactory.newTextTree(text));
|
||||
return parse(text);
|
||||
} else {
|
||||
Name CODE = elementUtils.getName("code");
|
||||
return List.of(
|
||||
treeFactory.newTextTree(text.substring(0, index)),
|
||||
treeFactory.newStartElementTree(CODE, List.of(), false),
|
||||
treeFactory.newTextTree(name.toString()),
|
||||
treeFactory.newEndElementTree(CODE),
|
||||
treeFactory.newTextTree(text.substring(index + 3))
|
||||
);
|
||||
var list = new ArrayList<DocTree>();
|
||||
list.addAll(parse(text.substring(0, index)));
|
||||
list.add(treeFactory.newStartElementTree(CODE, List.of(), false));
|
||||
list.add(treeFactory.newTextTree(name.toString())) ;
|
||||
list.add(treeFactory.newEndElementTree(CODE));
|
||||
list.addAll(parse(text.substring(index + 3)));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,9 +133,7 @@ public abstract class Content {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of characters of plain text content in this object
|
||||
* (optional operation.)
|
||||
* @return the number of characters of plain text content in this
|
||||
* {@return the number of characters of plain text content in this object}
|
||||
*/
|
||||
public int charCount() {
|
||||
return 0;
|
||||
|
@ -107,7 +107,7 @@ public final class UserTaglet implements Taglet {
|
||||
@Override
|
||||
public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter writer) {
|
||||
Content output = writer.getOutputInstance();
|
||||
output.add(new RawHtml(userTaglet.toString(List.of(tag), element)));
|
||||
output.add(RawHtml.of(userTaglet.toString(List.of(tag), element)));
|
||||
return output;
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ public final class UserTaglet implements Taglet {
|
||||
if (!tags.isEmpty()) {
|
||||
String tagString = userTaglet.toString(tags, holder);
|
||||
if (tagString != null) {
|
||||
output.add(new RawHtml(tagString));
|
||||
output.add(RawHtml.of(tagString));
|
||||
}
|
||||
}
|
||||
return output;
|
||||
|
@ -314,7 +314,7 @@ public class CommentHelper {
|
||||
public String visitSee(SeeTree node, Void p) {
|
||||
Utils utils = configuration.utils;
|
||||
return node.getReference().stream()
|
||||
.filter(utils::isText)
|
||||
.filter(dt -> dt.getKind() == DocTree.Kind.TEXT)
|
||||
.map(dt -> ((TextTree) dt).getBody())
|
||||
.collect(Collectors.joining());
|
||||
}
|
||||
|
@ -2176,18 +2176,6 @@ public class Utils {
|
||||
return mdle.getQualifiedName().toString();
|
||||
}
|
||||
|
||||
public boolean isStartElement(DocTree doctree) {
|
||||
return isKind(doctree, START_ELEMENT);
|
||||
}
|
||||
|
||||
public boolean isText(DocTree doctree) {
|
||||
return isKind(doctree, TEXT);
|
||||
}
|
||||
|
||||
private boolean isKind(DocTree doctree, DocTree.Kind match) {
|
||||
return doctree.getKind() == match;
|
||||
}
|
||||
|
||||
private final CommentHelperCache commentHelperCache = new CommentHelperCache(this);
|
||||
|
||||
public CommentHelper getCommentHelper(Element element) {
|
||||
|
@ -44,6 +44,7 @@ public class TestTypeAnnotations extends JavadocTester {
|
||||
@Test
|
||||
public void test() {
|
||||
javadoc("-d", "out",
|
||||
"-Xdoclint:none",
|
||||
"--no-platform-links",
|
||||
"-sourcepath", testSrc,
|
||||
"-private",
|
||||
|
32
test/langtools/tools/javac/diags/examples/InvalidHtml.java
Normal file
32
test/langtools/tools/javac/diags/examples/InvalidHtml.java
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
// key: compiler.err.dc.invalid.html
|
||||
// key: compiler.note.note
|
||||
// key: compiler.note.proc.messager
|
||||
// run: backdoor
|
||||
// options: -processor DocCommentProcessor -proc:only
|
||||
|
||||
/** <![CDATA[ */
|
||||
class InvalidHtml { }
|
||||
|
Loading…
Reference in New Issue
Block a user