8286470: Support searching for sections in class/package javadoc
Reviewed-by: jjg
This commit is contained in:
parent
55d297fdda
commit
a92363461d
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets
formats/html
toolkit/util
test/langtools/jdk/javadoc/doclet/testAutoHeaderId
@ -104,6 +104,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocLink;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.IndexItem;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils.DeclarationPreviewLanguageFeatures;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils.ElementFlag;
|
||||
@ -1400,8 +1401,8 @@ public class HtmlDocletWriter {
|
||||
@Override
|
||||
public Boolean visitStartElement(StartElementTree node, Content content) {
|
||||
Content attrs = new ContentBuilder();
|
||||
if (node.getName().toString().matches("(?i)h[1-6]") && !hasIdAttribute(node)) {
|
||||
generateHeadingId(node, trees, attrs);
|
||||
if (node.getName().toString().matches("(?i)h[1-6]")) {
|
||||
createSectionIdAndIndex(node, trees, attrs, element, context);
|
||||
}
|
||||
for (DocTree dt : node.getAttributes()) {
|
||||
dt.accept(this, attrs);
|
||||
@ -1476,14 +1477,20 @@ public class HtmlDocletWriter {
|
||||
return name != null && name.toString().equalsIgnoreCase(s);
|
||||
}
|
||||
|
||||
private boolean hasIdAttribute(StartElementTree node) {
|
||||
return node.getAttributes().stream().anyMatch(
|
||||
dt -> dt instanceof AttributeTree at && equalsIgnoreCase(at.getName(), "id"));
|
||||
private Optional<String> getIdAttributeValue(StartElementTree node) {
|
||||
return node.getAttributes().stream()
|
||||
.filter(dt -> dt instanceof AttributeTree at && equalsIgnoreCase(at.getName(), "id"))
|
||||
.map(dt -> ((AttributeTree)dt).getValue().toString())
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private void generateHeadingId(StartElementTree node, List<? extends DocTree> trees, Content content) {
|
||||
private void createSectionIdAndIndex(StartElementTree node, List<? extends DocTree> trees, Content attrs,
|
||||
Element element, TagletWriterImpl.Context context) {
|
||||
// Use existing id attribute if available
|
||||
String id = getIdAttributeValue(node).orElse(null);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String tagName = node.getName().toString().toLowerCase(Locale.ROOT);
|
||||
// Go through heading content to collect content and look for existing id
|
||||
for (DocTree docTree : trees.subList(trees.indexOf(node) + 1, trees.size())) {
|
||||
if (docTree instanceof TextTree text) {
|
||||
sb.append(text.getBody());
|
||||
@ -1492,17 +1499,31 @@ public class HtmlDocletWriter {
|
||||
} else if (docTree instanceof LinkTree link) {
|
||||
var label = link.getLabel();
|
||||
sb.append(label.isEmpty() ? link.getReference().getSignature() : label.toString());
|
||||
} else if (id == null && docTree instanceof StartElementTree nested
|
||||
&& equalsIgnoreCase(nested.getName(), "a")) {
|
||||
// Use id of embedded anchor element if present
|
||||
id = getIdAttributeValue(nested).orElse(null);
|
||||
} else if (docTree instanceof EndElementTree endElement
|
||||
&& equalsIgnoreCase(endElement.getName(), tagName)) {
|
||||
break;
|
||||
} else if (docTree instanceof StartElementTree nested
|
||||
&& equalsIgnoreCase(nested.getName(), "a")
|
||||
&& hasIdAttribute(nested)) {
|
||||
return; // Avoid generating id if embedded <a id=...> is present
|
||||
}
|
||||
}
|
||||
HtmlId htmlId = htmlIds.forHeading(sb, headingIds);
|
||||
content.add("id=\"").add(htmlId.name()).add("\"");
|
||||
String headingContent = sb.toString().trim();
|
||||
if (id == null) {
|
||||
// Generate id attribute
|
||||
HtmlId htmlId = htmlIds.forHeading(headingContent, headingIds);
|
||||
id = htmlId.name();
|
||||
attrs.add("id=\"").add(htmlId.name()).add("\"");
|
||||
}
|
||||
// Generate index item
|
||||
if (!headingContent.isEmpty() && configuration.mainIndex != null) {
|
||||
String tagText = headingContent.replaceAll("\\s+", " ");
|
||||
IndexItem item = IndexItem.of(element, node, tagText,
|
||||
getTagletWriterInstance(context).getHolderName(element),
|
||||
resources.getText("doclet.Section"),
|
||||
new DocLink(path, id));
|
||||
configuration.mainIndex.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -908,56 +908,7 @@ public class TagletWriterImpl extends TagletWriter {
|
||||
HtmlId id = HtmlIds.forText(tagText, htmlWriter.indexAnchorTable);
|
||||
result = HtmlTree.SPAN(id, HtmlStyle.searchTagResult, tagContent);
|
||||
if (options.createIndex() && !tagText.isEmpty()) {
|
||||
String holder = new SimpleElementVisitor14<String, Void>() {
|
||||
|
||||
@Override
|
||||
public String visitModule(ModuleElement e, Void p) {
|
||||
return resources.getText("doclet.module")
|
||||
+ " " + utils.getFullyQualifiedName(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitPackage(PackageElement e, Void p) {
|
||||
return resources.getText("doclet.package")
|
||||
+ " " + utils.getFullyQualifiedName(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitType(TypeElement e, Void p) {
|
||||
return utils.getTypeElementKindName(e, true)
|
||||
+ " " + utils.getFullyQualifiedName(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitExecutable(ExecutableElement e, Void p) {
|
||||
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
|
||||
+ "." + utils.getSimpleName(e)
|
||||
+ utils.flatSignature(e, htmlWriter.getCurrentPageElement());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitVariable(VariableElement e, Void p) {
|
||||
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
|
||||
+ "." + utils.getSimpleName(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitUnknown(Element e, Void p) {
|
||||
if (e instanceof DocletElement de) {
|
||||
return switch (de.getSubKind()) {
|
||||
case OVERVIEW -> resources.getText("doclet.Overview");
|
||||
case DOCFILE -> getHolderName(de);
|
||||
};
|
||||
} else {
|
||||
return super.visitUnknown(e, p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String defaultAction(Element e, Void p) {
|
||||
return utils.getFullyQualifiedName(e);
|
||||
}
|
||||
}.visit(element);
|
||||
String holder = getHolderName(element);
|
||||
IndexItem item = IndexItem.of(element, tree, tagText, holder, desc,
|
||||
new DocLink(htmlWriter.path, id.name()));
|
||||
configuration.mainIndex.add(item);
|
||||
@ -966,6 +917,59 @@ public class TagletWriterImpl extends TagletWriter {
|
||||
return result;
|
||||
}
|
||||
|
||||
String getHolderName(Element element) {
|
||||
return new SimpleElementVisitor14<String, Void>() {
|
||||
|
||||
@Override
|
||||
public String visitModule(ModuleElement e, Void p) {
|
||||
return resources.getText("doclet.module")
|
||||
+ " " + utils.getFullyQualifiedName(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitPackage(PackageElement e, Void p) {
|
||||
return resources.getText("doclet.package")
|
||||
+ " " + utils.getFullyQualifiedName(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitType(TypeElement e, Void p) {
|
||||
return utils.getTypeElementKindName(e, true)
|
||||
+ " " + utils.getFullyQualifiedName(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitExecutable(ExecutableElement e, Void p) {
|
||||
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
|
||||
+ "." + utils.getSimpleName(e)
|
||||
+ utils.flatSignature(e, htmlWriter.getCurrentPageElement());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitVariable(VariableElement e, Void p) {
|
||||
return utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e))
|
||||
+ "." + utils.getSimpleName(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitUnknown(Element e, Void p) {
|
||||
if (e instanceof DocletElement de) {
|
||||
return switch (de.getSubKind()) {
|
||||
case OVERVIEW -> resources.getText("doclet.Overview");
|
||||
case DOCFILE -> getHolderName(de);
|
||||
};
|
||||
} else {
|
||||
return super.visitUnknown(e, p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String defaultAction(Element e, Void p) {
|
||||
return utils.getFullyQualifiedName(e);
|
||||
}
|
||||
}.visit(element);
|
||||
}
|
||||
|
||||
private String getHolderName(DocletElement de) {
|
||||
PackageElement pe = de.getPackageElement();
|
||||
if (pe.isUnnamed()) {
|
||||
|
@ -166,6 +166,7 @@ doclet.Enclosing_Class=Enclosing class:
|
||||
doclet.Enclosing_Interface=Enclosing interface:
|
||||
doclet.Inheritance_Tree=Inheritance Tree
|
||||
doclet.ReferencedIn=Referenced In
|
||||
doclet.Section=Section
|
||||
doclet.External_Specification=External Specification
|
||||
doclet.External_Specifications=External Specifications
|
||||
doclet.External_Specifications.All_Specifications=All Specifications
|
||||
|
@ -209,7 +209,7 @@ public class IndexItem {
|
||||
Objects.requireNonNull(link);
|
||||
|
||||
switch (docTree.getKind()) {
|
||||
case INDEX, SPEC, SYSTEM_PROPERTY -> { }
|
||||
case INDEX, SPEC, SYSTEM_PROPERTY, START_ELEMENT -> { }
|
||||
default -> throw new IllegalArgumentException(docTree.getKind().toString());
|
||||
}
|
||||
|
||||
@ -340,7 +340,7 @@ public class IndexItem {
|
||||
|
||||
protected Category getCategory(DocTree docTree) {
|
||||
return switch (docTree.getKind()) {
|
||||
case INDEX, SPEC, SYSTEM_PROPERTY -> Category.TAGS;
|
||||
case INDEX, SPEC, SYSTEM_PROPERTY, START_ELEMENT -> Category.TAGS;
|
||||
default -> throw new IllegalArgumentException(docTree.getKind().toString());
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8289332
|
||||
* @bug 8289332 8286470
|
||||
* @summary Auto-generate ids for user-defined headings
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.javadoc/jdk.javadoc.internal.tool
|
||||
@ -79,6 +79,10 @@ public class TestAutoHeaderId extends JavadocTester {
|
||||
*
|
||||
* <h4></h4>
|
||||
*
|
||||
* <h2> Multi-line
|
||||
* heading with extra
|
||||
* whitespace</h2>
|
||||
*
|
||||
* Last sentence.
|
||||
*/
|
||||
public class C {
|
||||
@ -121,6 +125,32 @@ public class TestAutoHeaderId extends JavadocTester {
|
||||
""",
|
||||
"""
|
||||
<h4 id="-heading"></h4>
|
||||
""");
|
||||
""",
|
||||
"""
|
||||
<h2 id="multi-line-heading-with-extra-whitespace-heading"> Multi-line
|
||||
heading with extra
|
||||
whitespace</h2>""");
|
||||
checkOutput("tag-search-index.js", true,
|
||||
"""
|
||||
{"l":"Duplicate Text","h":"class p.C","d":"Section","u":"p/C.html#duplicate-text-heading"}""",
|
||||
"""
|
||||
{"l":"Duplicate Text","h":"class p.C","d":"Section","u":"p/C.html#duplicate-text-heading1"}""",
|
||||
"""
|
||||
{"l":"Embedded A-Tag with ID","h":"class p.C","d":"Section","u":"p/C.html#fixed-id-2"}""",
|
||||
"""
|
||||
{"l":"Embedded Code Tag","h":"class p.C","d":"Section","u":"p/C.html#embedded-code-tag-heading"}""",
|
||||
"""
|
||||
{"l":"Embedded Link Tag","h":"class p.C","d":"Section","u":"p/C.html#embedded-link-tag-heading"}""",
|
||||
"""
|
||||
{"l":"Extra (#*!. chars","h":"class p.C","d":"Section","u":"p/C.html#extra-chars-heading"}""",
|
||||
"""
|
||||
{"l":"First Header","h":"class p.C","d":"Section","u":"p/C.html#first-header-heading"}""",
|
||||
"""
|
||||
{"l":"Header with ID","h":"class p.C","d":"Section","u":"p/C.html#fixed-id-1"}""",
|
||||
"""
|
||||
{"l":"Multi-line heading with extra whitespace","h":"class p.C","d":"Section","u":"p/C.html\
|
||||
#multi-line-heading-with-extra-whitespace-heading"}""",
|
||||
"""
|
||||
{"l":"Other attributes","h":"class p.C","d":"Section","u":"p/C.html#other-attributes-heading"}""");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user