/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * CALDocToHTMLUtilities.java * Creation date: Sep 28, 2005. * By: Joseph Wong */ package org.openquark.cal.caldoc; import java.text.BreakIterator; import java.util.Locale; import javax.swing.text.html.HTML; import org.openquark.cal.caldoc.HTMLBuilder.AttributeList; import org.openquark.cal.compiler.CALDocComment; import org.openquark.cal.compiler.CALDocCommentTextBlockTraverser; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.CALDocComment.CodeSegment; import org.openquark.cal.compiler.CALDocComment.DataConsLinkSegment; import org.openquark.cal.compiler.CALDocComment.EmphasizedSegment; import org.openquark.cal.compiler.CALDocComment.FunctionOrClassMethodLinkSegment; import org.openquark.cal.compiler.CALDocComment.ListItem; import org.openquark.cal.compiler.CALDocComment.ListParagraph; import org.openquark.cal.compiler.CALDocComment.ModuleLinkSegment; import org.openquark.cal.compiler.CALDocComment.ModuleReference; import org.openquark.cal.compiler.CALDocComment.PlainTextSegment; import org.openquark.cal.compiler.CALDocComment.ScopedEntityReference; import org.openquark.cal.compiler.CALDocComment.StronglyEmphasizedSegment; import org.openquark.cal.compiler.CALDocComment.SubscriptSegment; import org.openquark.cal.compiler.CALDocComment.SuperscriptSegment; import org.openquark.cal.compiler.CALDocComment.TextBlock; import org.openquark.cal.compiler.CALDocComment.TextParagraph; import org.openquark.cal.compiler.CALDocComment.TypeClassLinkSegment; import org.openquark.cal.compiler.CALDocComment.TypeConsLinkSegment; import org.openquark.cal.compiler.CALDocComment.URLSegment; import org.openquark.cal.services.CALFeatureName; import org.openquark.cal.services.LocaleUtilities; /** * This is a utility class containing helper methods for converting CALDoc into * properly formatted HTML. * * @author Joseph Wong */ public final class CALDocToHTMLUtilities { /** * This class encapsulates a piece of content that is convertible to HTML. It is effectively used as a * lazily-evaluated thunk that is executed to generate the HTML when needed (and at the correct position!) * * @author Joseph Wong */ static abstract class ContentConvertibleToHTML { /** @return true if there is no content; false otherwise. */ abstract boolean isEmpty(); /** * Generate the content as HTML. * @param builder the HTMLBuilder to use for generating the HTML. * @param referenceGenerator the reference generator to use for generating cross references. */ abstract void generateHTML(HTMLBuilder builder, CrossReferenceHTMLGenerator referenceGenerator); } /** * A subclass of ContentConvertibleToHTML for representing simple string content. * * @author Joseph Wong */ static final class SimpleStringContent extends ContentConvertibleToHTML { /** The string content. */ private final String content; /** @param content the string content. Can be null.*/ SimpleStringContent(final String content) { this.content = (content != null) ? content.trim() : null; // trim the content of its leading and trailing whitespace } /** {@inheritDoc} */ @Override final boolean isEmpty() { return content == null || content.length() == 0; } /** {@inheritDoc} */ @Override final void generateHTML(final HTMLBuilder currentPage, final CrossReferenceHTMLGenerator referenceGenerator) { currentPage.addText(content); } } /** * A subclass of ContentConvertibleToHTML for representing a single CALDoc paragraph. * * @author Joseph Wong */ static final class SingleParagraphContent extends ContentConvertibleToHTML { /** The CALDoc paragraph. */ private final CALDocComment.Paragraph paragraph; /** The style class for code blocks. Can be null. */ private final StyleClass codeStyleClass; /** The HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ private final HTML.Tag codeFormattingTag; /** * @param paragraph the CALDoc paragraph. * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ SingleParagraphContent(final CALDocComment.Paragraph paragraph, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { this.paragraph = paragraph; this.codeStyleClass = codeStyleClass; this.codeFormattingTag = codeFormattingTag; } /** {@inheritDoc} */ @Override final boolean isEmpty() { return paragraph == null; } /** {@inheritDoc} */ @Override final void generateHTML(final HTMLBuilder currentPage, final CrossReferenceHTMLGenerator referenceGenerator) { generateHTMLForCALDocParagraph(paragraph, currentPage, referenceGenerator, codeStyleClass, codeFormattingTag); } } /** * A subclass of ContentConvertibleToHTML for representing the initial segments of a text paragraph. * * @author Joseph Wong */ static final class TextParagraphInitialSegments extends ContentConvertibleToHTML { /** The CALDoc text paragraph. */ private final CALDocComment.TextParagraph paragraph; /** The number of complete initial segments to include. */ private final int nCompleteSegments; /** The plain text to form the end of the generated text. */ private final String endPlainText; /** The style class for code blocks. Can be null. */ private final StyleClass codeStyleClass; /** The HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ private final HTML.Tag codeFormattingTag; /** * @param paragraph the CALDoc text paragraph. * @param nCompleteSegments the number of complete initial segments to include. * @param endPlainText the plain text to form the end of the generated text. * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ TextParagraphInitialSegments(final CALDocComment.TextParagraph paragraph, final int nCompleteSegments, final String endPlainText, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { this.paragraph = paragraph; this.nCompleteSegments = Math.min(paragraph.getNSegments(), nCompleteSegments); this.endPlainText = endPlainText; this.codeStyleClass = codeStyleClass; this.codeFormattingTag = codeFormattingTag; } /** {@inheritDoc} */ @Override final boolean isEmpty() { return paragraph == null || paragraph.getNSegments() == 0 || (nCompleteSegments == 0 && endPlainText.length() == 0); } /** {@inheritDoc} */ @Override final void generateHTML(final HTMLBuilder currentPage, final CrossReferenceHTMLGenerator referenceGenerator) { for (int i = 0; i < nCompleteSegments; i++) { generateHTMLForCALDocSegment(paragraph.getNthSegment(i), currentPage, referenceGenerator, codeStyleClass, codeFormattingTag); } currentPage.addText(htmlEscape(endPlainText)); } } /** * A subclass of ContentConvertibleToHTML for representing a single CALDoc text block. * * @author Joseph Wong */ static final class SingleTextBlockContent extends ContentConvertibleToHTML { /** The CALDoc text block. */ private final CALDocComment.TextBlock textBlock; /** The style class for code blocks. Can be null. */ private final StyleClass codeStyleClass; /** The HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ private final HTML.Tag codeFormattingTag; /** * @param textBlock the CALDoc text block. * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ SingleTextBlockContent(final CALDocComment.TextBlock textBlock, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { this.textBlock = textBlock; this.codeStyleClass = codeStyleClass; this.codeFormattingTag = codeFormattingTag; } /** {@inheritDoc} */ @Override final boolean isEmpty() { return textBlock == null || textBlock.getNParagraphs() == 0; } /** {@inheritDoc} */ @Override final void generateHTML(final HTMLBuilder currentPage, final CrossReferenceHTMLGenerator referenceGenerator) { generateHTMLForCALDocTextBlock(textBlock, currentPage, referenceGenerator, codeStyleClass, codeFormattingTag); } } /** * Abstract base class for a generator capable of generating appropriate HTML for cross-references. * * Generators that produce the HTML as strings should use {@link CALDocToHTMLUtilities.CrossReferenceHTMLStringGenerator} * as the base class. * * @author Joseph Wong * @see CALDocToHTMLUtilities.CrossReferenceHTMLStringGenerator */ public static abstract class CrossReferenceHTMLGenerator { /** Package-scoped constructor. */ CrossReferenceHTMLGenerator() {} /** * Generates HTML for a module cross-reference. * @param builder the HTMLBuilder to use for generating the cross-reference. * @param reference the module cross-reference. */ abstract void generateModuleReference(HTMLBuilder builder, ModuleReference reference); /** * Generates HTML for a type constructor cross-reference. * @param builder the HTMLBuilder to use for generating the cross-reference. * @param reference the type constructor cross-reference. */ abstract void generateTypeConsReference(HTMLBuilder builder, ScopedEntityReference reference); /** * Generates HTML for a data constructor cross-reference. * @param builder the HTMLBuilder to use for generating the cross-reference. * @param reference the data constructor cross-reference. */ abstract void generateDataConsReference(HTMLBuilder builder, ScopedEntityReference reference); /** * Generates HTML for a function or class method cross-reference. * @param builder the HTMLBuilder to use for generating the cross-reference. * @param reference the function or class method cross-reference. */ abstract void generateFunctionOrClassMethodReference(HTMLBuilder builder, ScopedEntityReference reference); /** * Generates HTML for a type class cross-reference. * @param builder the HTMLBuilder to use for generating the cross-reference. * @param reference the type class cross-reference. */ abstract void generateTypeClassReference(HTMLBuilder builder, ScopedEntityReference reference); } /** * Abstract base class for a generator capable of generating appropriate HTML text for cross-references. * * @author Joseph Wong */ public static abstract class CrossReferenceHTMLStringGenerator extends CrossReferenceHTMLGenerator { /** * {@inheritDoc} */ @Override final void generateModuleReference(final HTMLBuilder builder, final ModuleReference reference) { builder.addText(getModuleReferenceHTML(reference)); } /** * {@inheritDoc} */ @Override final void generateTypeConsReference(final HTMLBuilder builder, final ScopedEntityReference reference) { builder.addText(getTypeConsReferenceHTML(reference)); } /** * {@inheritDoc} */ @Override final void generateDataConsReference(final HTMLBuilder builder, final ScopedEntityReference reference) { builder.addText(getDataConsReferenceHTML(reference)); } /** * {@inheritDoc} */ @Override final void generateFunctionOrClassMethodReference(final HTMLBuilder builder, final ScopedEntityReference reference) { builder.addText(getFunctionOrClassMethodReferenceHTML(reference)); } /** * {@inheritDoc} */ @Override final void generateTypeClassReference(final HTMLBuilder builder, final ScopedEntityReference reference) { builder.addText(getTypeClassReferenceHTML(reference)); } /** * Generates HTML for a module cross-reference. * @param reference the module cross-reference. * @return the appropriate HTML text. */ public abstract String getModuleReferenceHTML(ModuleReference reference); /** * Generates HTML for a type constructor cross-reference. * @param reference the type constructor cross-reference. * @return the appropriate HTML text. */ public abstract String getTypeConsReferenceHTML(ScopedEntityReference reference); /** * Generates HTML for a data constructor cross-reference. * @param reference the data constructor cross-reference. * @return the appropriate HTML text. */ public abstract String getDataConsReferenceHTML(ScopedEntityReference reference); /** * Generates HTML for a function or class method cross-reference. * @param reference the function or class method cross-reference. * @return the appropriate HTML text. */ public abstract String getFunctionOrClassMethodReferenceHTML(ScopedEntityReference reference); /** * Generates HTML for a type class cross-reference. * @param reference the type class cross-reference. * @return the appropriate HTML text. */ public abstract String getTypeClassReferenceHTML(ScopedEntityReference reference); } /** * Abstract base class for a CrossReferenceHTMLGenerator implementation that is built to handle cross-references * expressed as CALFeatureNames. * * @author Joseph Wong */ public static abstract class CALFeatureCrossReferenceGenerator extends CrossReferenceHTMLGenerator { /** * {@inheritDoc} */ @Override final void generateModuleReference(final HTMLBuilder builder, final ModuleReference reference) { builder.addText(getRelatedFeatureLinkHTML(CALFeatureName.getModuleFeatureName(reference.getName()), reference.getModuleNameInSource())); } /** * {@inheritDoc} */ @Override final void generateTypeConsReference(final HTMLBuilder builder, final ScopedEntityReference reference) { builder.addText(getRelatedFeatureLinkHTML(CALFeatureName.getTypeConstructorFeatureName(reference.getName()), reference.getModuleNameInSource())); } /** * {@inheritDoc} */ @Override final void generateDataConsReference(final HTMLBuilder builder, final ScopedEntityReference reference) { builder.addText(getRelatedFeatureLinkHTML(CALFeatureName.getDataConstructorFeatureName(reference.getName()), reference.getModuleNameInSource())); } /** * {@inheritDoc} */ @Override final void generateFunctionOrClassMethodReference(final HTMLBuilder builder, final ScopedEntityReference reference) { final QualifiedName qualifiedName = reference.getName(); CALFeatureName featureName; if (isClassMethodName(qualifiedName)) { featureName = CALFeatureName.getClassMethodFeatureName(qualifiedName); } else { featureName = CALFeatureName.getFunctionFeatureName(qualifiedName); } builder.addText(getRelatedFeatureLinkHTML(featureName, reference.getModuleNameInSource())); } /** * {@inheritDoc} */ @Override final void generateTypeClassReference(final HTMLBuilder builder, final ScopedEntityReference reference) { builder.addText(getRelatedFeatureLinkHTML(CALFeatureName.getTypeClassFeatureName(reference.getName()), reference.getModuleNameInSource())); } /** * Returns whether the given qualified name is a class method name. * @param qualifiedName the name to check. * @return true if the given qualified name is a class method name, false otherwise. */ public abstract boolean isClassMethodName(QualifiedName qualifiedName); /** * Builds a well-formed HTML fragment for the cross-reference, given here as a CALFeatureName. * @param featureName the cross-reference. * @param moduleNameInSource how the module name portion of the reference appears in source. Could be the empty string if the reference is unqualified in source. * @return the HTML fragment for the cross-reference. */ public abstract String getRelatedFeatureLinkHTML(CALFeatureName featureName, String moduleNameInSource); } /** * Implements a traverser which traverses through a CALDoc text block and generates the corresponding HTML for it. * * @author Joseph Wong */ private static final class TextBlockHTMLGenerator extends CALDocCommentTextBlockTraverser<TextBlockHTMLGenerator.VisitationOption, Void> { /** * The HTMLBuilder to use for generating the HTML. */ private final HTMLBuilder builder; /** * The cross-reference generator to use for generating HTML for cross-references. */ private final CrossReferenceHTMLGenerator crossRefGen; /** * The HTML attribute to use for elements that represent "@code" blocks in the text block. */ private final HTMLBuilder.AttributeList codeStyleClassAttribute; /** * The HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ private final HTML.Tag codeFormattingTag; /** * The nesting level of "@code"/"@link" blocks which require code formatting. It starts out at 0. */ private int codeFormattingLevel = 0; /** An enumeration representing the various visitation options. */ private static enum VisitationOption { /** * Visitation argument value for suppressing the initial paragraph tag in a block of paragraphs during generation. */ SUPPRESS_INITIAL_PARAGRAPH_TAG, /** * Visitation argument value for suppressing the initial paragraph tag in a block of paragraphs * and trimming one leading whitespace character during generation. */ SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR, /** * Visitation argument value for trimming one leading whitespace character during generation. */ TRIM_LEADING_WHITESPACE_CHAR_IN_SEGMENT, /** * Visitation argument value for indicating that the segment being generated is the last one in a paragraph whose paragraph * tags are to be suppressed. */ LAST_SEGMENT_IN_SUPPRESSED_PARAGRAPH } /** * Implements a text block traverser which determines whether a text block spans more than one source line. * * @author Joseph Wong */ private static final class MultipleLinesChecker extends CALDocCommentTextBlockTraverser<Void, Void> { /** * Keeps track of whether the text block being traversed spans more than one source line. */ private boolean spansMultipleLines = false; /** * Determines whether the text block being traversed spans more than one source line. * @param block the text block to be traversed. * @param arg unused argument. * @return null. */ @Override public Void visitTextBlock(final TextBlock block, final Void arg) { if (block.getNParagraphs() > 1) { spansMultipleLines = true; } else { // only traverse into the block to check if there is just zero or one paragraph in the block super.visitTextBlock(block, arg); } return null; } /** * Determines whether the plain text segment being traversed spans more than one source line. * @param segment the segment to be traversed. * @param arg unused argument. * @return null. */ @Override public Void visitPlainTextSegment(final PlainTextSegment segment, final Void arg) { if (segment.getText().indexOf('\n') >= 0 || segment.getText().indexOf('\r') >= 0) { spansMultipleLines = true; } return super.visitPlainTextSegment(segment, arg); } } /** * Constructs a TextBlockHTMLGenerator for generating HTML for a text block. * @param builder the HTMLBuilder to use for generating the HTML. * @param crossRefGen the cross-reference generator to use for generating HTML for cross-references. * @param codeStyleClass the style class to use for HTML elements that represent "@code" blocks in the text block. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ private TextBlockHTMLGenerator(final HTMLBuilder builder, final CrossReferenceHTMLGenerator crossRefGen, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { if (builder == null) { throw new NullPointerException(); } this.builder = builder; if (crossRefGen == null) { throw new NullPointerException(); } this.crossRefGen = crossRefGen; if (codeStyleClass == null) { this.codeStyleClassAttribute = HTMLBuilder.AttributeList.make(); } else { this.codeStyleClassAttribute = HTMLBuilder.AttributeList.make(HTML.Attribute.CLASS, codeStyleClass.toHTML()); } if (codeFormattingTag == null) { throw new NullPointerException(); } this.codeFormattingTag = codeFormattingTag; } /** * Generates HTML for the given text block. * @param block the text block to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitTextBlock(final TextBlock block, final VisitationOption arg) { for (int i = 0, n = block.getNParagraphs(); i < n; i++) { final boolean isFirstSegment = (i == 0); final VisitationOption paragraphArg; if (isFirstSegment) { if (arg == VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG || arg == VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR) { paragraphArg = arg; } else { paragraphArg = VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG; } } else { paragraphArg = null; } block.getNthParagraph(i).accept(this, paragraphArg); } return null; } /** * Generates HTML for the given text paragraph. * @param paragraph the text paragraph to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitTextParagraph(final TextParagraph paragraph, final VisitationOption arg) { boolean suppressParagraphTag = (arg == VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG || arg == VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR); if (!suppressParagraphTag) { builder.emptyTag(HTML.Tag.P); } for (int i = 0, n = paragraph.getNSegments(); i < n; i++) { final boolean isFirstSegment = (i == 0); final boolean isLastSegment = (i == n - 1); final VisitationOption segmentArg; if (isFirstSegment && arg == VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR) { segmentArg = VisitationOption.TRIM_LEADING_WHITESPACE_CHAR_IN_SEGMENT; } else if (isLastSegment && suppressParagraphTag) { segmentArg = VisitationOption.LAST_SEGMENT_IN_SUPPRESSED_PARAGRAPH; } else { segmentArg = null; } paragraph.getNthSegment(i).accept(this, segmentArg); } return null; } /** * Generates HTML for the given list paragraph. * @param paragraph the list paragraph to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitListParagraph(final ListParagraph paragraph, final VisitationOption arg) { final HTML.Tag listTag = paragraph.isOrdered() ? HTML.Tag.OL : HTML.Tag.UL; builder.openTag(listTag); super.visitListParagraph(paragraph, arg); builder.closeTag(listTag); return null; } /** * Generates HTML for the given list item. * @param item the list item to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitListItem(final ListItem item, final VisitationOption arg) { builder.openTag(HTML.Tag.LI); super.visitListItem(item, VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR); builder.closeTag(HTML.Tag.LI); return null; } /** * Generates HTML for the given plain text segment. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitPlainTextSegment(final PlainTextSegment segment, final VisitationOption arg) { String text = segment.getText(); if (arg == VisitationOption.TRIM_LEADING_WHITESPACE_CHAR_IN_SEGMENT) { if (text.length() > 0 && Character.isWhitespace(text.charAt(0))) { text = text.substring(1); } } else if (arg == VisitationOption.LAST_SEGMENT_IN_SUPPRESSED_PARAGRAPH) { text = text.replaceAll("\\s+\\z", ""); } builder.addText(htmlEscape(text)); return super.visitPlainTextSegment(segment, arg); } /** * Generates HTML for the given URL segment. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitURLSegment(final URLSegment segment, final VisitationOption arg) { builder.addTaggedText(HTML.Tag.A, AttributeList.make(HTML.Attribute.HREF, htmlEscape(segment.getURL())), htmlEscape(segment.getURL())); return super.visitURLSegment(segment, arg); } /** * Adds an open tag for code formatting. */ private void startCodeFormatting() { startCodeFormatting(codeFormattingTag); } /** * Adds an open tag for code formatting. * @param wrapperTag the tag to use. */ private void startCodeFormatting(final HTML.Tag wrapperTag) { if (codeFormattingLevel == 0) { builder.openTag(wrapperTag, codeStyleClassAttribute); } codeFormattingLevel++; } /** * Adds a close tag for code formatting. */ private void endCodeFormatting() { endCodeFormatting(codeFormattingTag); } /** * Adds a close tag for code formatting. * @param wrapperTag the tag to use. */ private void endCodeFormatting(final HTML.Tag wrapperTag) { codeFormattingLevel--; if (codeFormattingLevel == 0) { builder.closeTag(wrapperTag); } } /** * Generates HTML for the given inline module cross-reference. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitModuleLinkSegment(final ModuleLinkSegment segment, final VisitationOption arg) { startCodeFormatting(); crossRefGen.generateModuleReference(builder, segment.getReference()); endCodeFormatting(); return super.visitModuleLinkSegment(segment, arg); } /** * Generates HTML for the given inline function or class method cross-reference. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitFunctionOrClassMethodLinkSegment(final FunctionOrClassMethodLinkSegment segment, final VisitationOption arg) { startCodeFormatting(); crossRefGen.generateFunctionOrClassMethodReference(builder, segment.getReference()); endCodeFormatting(); return super.visitFunctionOrClassMethodLinkSegment(segment, arg); } /** * Generates HTML for the given inline type constructor cross-reference. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitTypeConsLinkSegment(final TypeConsLinkSegment segment, final VisitationOption arg) { startCodeFormatting(); crossRefGen.generateTypeConsReference(builder, segment.getReference()); endCodeFormatting(); return super.visitTypeConsLinkSegment(segment, arg); } /** * Generates HTML for the given inline data constructor cross-reference. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitDataConsLinkSegment(final DataConsLinkSegment segment, final VisitationOption arg) { startCodeFormatting(); crossRefGen.generateDataConsReference(builder, segment.getReference()); endCodeFormatting(); return super.visitDataConsLinkSegment(segment, arg); } /** * Generates HTML for the given inline type class cross-reference. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitTypeClassLinkSegment(final TypeClassLinkSegment segment, final VisitationOption arg) { startCodeFormatting(); crossRefGen.generateTypeClassReference(builder, segment.getReference()); endCodeFormatting(); return super.visitTypeClassLinkSegment(segment, arg); } /** * Generates HTML for the given code segment. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitCodeSegment(final CodeSegment segment, final VisitationOption arg) { final MultipleLinesChecker multipleLinesChecker = new MultipleLinesChecker(); segment.accept(multipleLinesChecker, null); final HTML.Tag wrapperTag = multipleLinesChecker.spansMultipleLines ? HTML.Tag.PRE : codeFormattingTag; if (multipleLinesChecker.spansMultipleLines) { builder.newline(); } startCodeFormatting(wrapperTag); super.visitCodeSegment(segment, VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR); endCodeFormatting(wrapperTag); return null; } /** * Generates HTML for the given emphasized segment. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitEmphasizedSegment(final EmphasizedSegment segment, final VisitationOption arg) { builder.openTag(HTML.Tag.EM); super.visitEmphasizedSegment(segment, VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR); builder.closeTag(HTML.Tag.EM); return null; } /** * Generates HTML for the given strongly emphasized segment. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitStronglyEmphasizedSegment(final StronglyEmphasizedSegment segment, final VisitationOption arg) { builder.openTag(HTML.Tag.STRONG); super.visitStronglyEmphasizedSegment(segment, VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR); builder.closeTag(HTML.Tag.STRONG); return null; } /** * Generates HTML for the given superscript segment. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitSuperscriptSegment(final SuperscriptSegment segment, final VisitationOption arg) { builder.openTag(HTML.Tag.SUP); super.visitSuperscriptSegment(segment, VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR); builder.closeTag(HTML.Tag.SUP); return null; } /** * Generates HTML for the given superscript segment. * @param segment the segment to be traversed. * @param arg additional argument for the traversal. * @return null. */ @Override public Void visitSubscriptSegment(final SubscriptSegment segment, final VisitationOption arg) { builder.openTag(HTML.Tag.SUB); super.visitSubscriptSegment(segment, VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG_AND_TRIM_LEADING_WHITESPACE_CHAR); builder.closeTag(HTML.Tag.SUB); return null; } } /** Private constructor. */ private CALDocToHTMLUtilities() {} /** * Generates the HTML for a text block appearing in a CALDoc comment. * @param textBlock the text block to be formatted as HTML. * @param crossRefGen the cross reference generator. * @return the HTML for the text block. */ public static String getHTMLForCALDocTextBlock(final CALDocComment.TextBlock textBlock, final CrossReferenceHTMLGenerator crossRefGen) { // specifies 'null' for the style class of code blocks and HTML.Tag.CODE for the tag final StyleClass codeStyleClass = null; final HTML.Tag codeFormattingTag = HTML.Tag.CODE; return getHTMLForCALDocTextBlock(textBlock, crossRefGen, codeStyleClass, codeFormattingTag); } /** * Generates the HTML for a text block appearing in a CALDoc comment. * @param textBlock the text block to be formatted as HTML. * @param crossRefGen the cross reference generator. * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. * @return the HTML for the text block. */ static String getHTMLForCALDocTextBlock(final CALDocComment.TextBlock textBlock, final CrossReferenceHTMLGenerator crossRefGen, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { final HTMLBuilder builder = new HTMLBuilder(); generateHTMLForCALDocTextBlock(textBlock, builder, crossRefGen, codeStyleClass, codeFormattingTag); return builder.toString(); } /** * Generates the HTML for a text block appearing in a CALDoc comment. * @param textBlock the text block to be formatted as HTML. * @param builder the HTML builder for aggregating the result. * @param crossRefGen the cross reference generator. * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ static void generateHTMLForCALDocTextBlock(final CALDocComment.TextBlock textBlock, final HTMLBuilder builder, final CrossReferenceHTMLGenerator crossRefGen, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { textBlock.accept(new TextBlockHTMLGenerator(builder, crossRefGen, codeStyleClass, codeFormattingTag), null); } /** * Generates the HTML for a paragraph appearing in a CALDoc comment. * @param paragraph the paragraph to be formatted as HTML. * @param builder the HTML builder for aggregating the result. * @param crossRefGen the cross reference generator. * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ static void generateHTMLForCALDocParagraph(final CALDocComment.Paragraph paragraph, final HTMLBuilder builder, final CrossReferenceHTMLGenerator crossRefGen, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { paragraph.accept(new TextBlockHTMLGenerator(builder, crossRefGen, codeStyleClass, codeFormattingTag), TextBlockHTMLGenerator.VisitationOption.SUPPRESS_INITIAL_PARAGRAPH_TAG); } /** * Generates the HTML for a segment appearing in a CALDoc comment. * @param segment the segment to be formatted as HTML. * @param builder the HTML builder for aggregating the result. * @param crossRefGen the cross reference generator. * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. */ static void generateHTMLForCALDocSegment(final CALDocComment.Segment segment, final HTMLBuilder builder, final CrossReferenceHTMLGenerator crossRefGen, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { segment.accept(new TextBlockHTMLGenerator(builder, crossRefGen, codeStyleClass, codeFormattingTag), null); } /** * Builds the HTML for the summary of a CALDoc comment. * @param docComment the CALDoc comment. * @param crossRefGen the cross reference generator. * @param locale the locale for the documentation generation. * @param returnsColonPrefix the appropriately localized version of "Returns:". * @return the HTML for the summary of a CALDoc comment. */ public static String getHTMLForCALDocSummary(final CALDocComment docComment, final CrossReferenceHTMLGenerator crossRefGen, final Locale locale, final String returnsColonPrefix) { // specifies 'null' for the style class of code blocks and HTML.Tag.CODE for the tag final StyleClass codeStyleClass = null; final HTML.Tag codeFormattingTag = HTML.Tag.CODE; return getHTMLForCALDocSummary(docComment, crossRefGen, locale, returnsColonPrefix, codeStyleClass, codeFormattingTag); } /** * Builds the HTML for the summary of a CALDoc comment. * @param docComment the CALDoc comment. * @param crossRefGen the cross reference generator. * @param locale the locale for the documentation generation. * @param returnsColonPrefix the appropriately localized version of "Returns:". * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. * @return the HTML for the summary of a CALDoc comment. */ static String getHTMLForCALDocSummary(final CALDocComment docComment, final CrossReferenceHTMLGenerator crossRefGen, final Locale locale, final String returnsColonPrefix, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { final ContentConvertibleToHTML summaryFromCALDoc = getSummaryFromCALDoc(docComment, crossRefGen, locale, returnsColonPrefix, codeStyleClass, codeFormattingTag); if (summaryFromCALDoc != null) { final HTMLBuilder builder = new HTMLBuilder(); summaryFromCALDoc.generateHTML(builder, crossRefGen); return builder.toString(); } else { return ""; // there is no summary, so the valid HTML for no summary is the empty string. } } /** * Fetches the summary (short description) from a CALDoc comment. * @param docComment the CALDoc comment to be extracted. Can be null. * @param referenceGenerator the reference generator to use for generating cross references. * @param locale the locale to use for obtaining the appropriate BreakIterator. * @param returnsColonPrefix the appropriately localized version of "Returns:". * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. * @return the summary, or null if none is available. */ static ContentConvertibleToHTML getSummaryFromCALDoc(final CALDocComment docComment, final CrossReferenceHTMLGenerator referenceGenerator, final Locale locale, final String returnsColonPrefix, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { /// If there is no comment or if it has no main description, return null // if (docComment == null) { return null; } /// If there is an {@summary} block, use it. // final CALDocComment.TextBlock summaryBlock = docComment.getSummary(); if (summaryBlock != null) { return new SingleTextBlockContent(summaryBlock, codeStyleClass, codeFormattingTag); } /// There is no {@summary} block, so try using the first sentence as the summary. // final CALDocComment.TextBlock textBlock = docComment.getDescriptionBlock(); if (textBlock != null) { final ContentConvertibleToHTML firstSentence = getFirstSentenceFromCALDocTextBlock(textBlock, locale, codeStyleClass, codeFormattingTag); if (firstSentence != null && !firstSentence.isEmpty()) { return firstSentence; } } /// There is no available first sentence, so use the return block instead. // final CALDocComment.TextBlock returnBlock = docComment.getReturnBlock(); if (returnBlock != null) { final String returnBlockHTML = getHTMLForCALDocTextBlock(returnBlock, referenceGenerator, codeStyleClass, codeFormattingTag); if (returnBlockHTML != null && returnBlockHTML.length() > 0) { return new SimpleStringContent(returnsColonPrefix + " " + returnBlockHTML); } } /// Absolutely nothing available, so we return null as per the spec. // return null; } /** * Obtains the first sentence from a CALDoc text block. * @param textBlock the text block. Can be null. * @param locale the locale to use for obtaining the appropriate BreakIterator. * @param codeStyleClass the style class to use for code blocks. Can be null. * @param codeFormattingTag the HTML tag to use for formatting a code fragment. Should be either HTML.Tag.TT or HTML.Tag.CODE. * @return the first sentence, or null if none is available. */ static ContentConvertibleToHTML getFirstSentenceFromCALDocTextBlock(final CALDocComment.TextBlock textBlock, final Locale locale, final StyleClass codeStyleClass, final HTML.Tag codeFormattingTag) { if (textBlock == null) { return null; } if (textBlock.getNParagraphs() == 0) { return null; } /// Set up a BreakIterator to parse the text and identify the first sentence. // BreakIterator breakIterator; if (LocaleUtilities.isInvariantLocale(locale)) { breakIterator = BreakIterator.getSentenceInstance(); } else { breakIterator = BreakIterator.getSentenceInstance(locale); } /// If the first paragraph is a text paragraph, then try to identify which one contains the first "sentence break". // final CALDocComment.Paragraph paragraph = textBlock.getNthParagraph(0); if (paragraph instanceof CALDocComment.TextParagraph) { final CALDocComment.TextParagraph textParagraph = (CALDocComment.TextParagraph)paragraph; for (int i = 0, n = textParagraph.getNSegments(); i < n; i++) { final CALDocComment.Segment segment = textParagraph.getNthSegment(i); if (segment instanceof CALDocComment.PlainTextSegment) { final CALDocComment.PlainTextSegment plainTextSegment = (CALDocComment.PlainTextSegment)segment; // Pad the string out with some whitespace at the end, so that if the break iterator returns // the end of string as the boundary, we know that the original string could not have contained the // boundary (or otherwise the boundary would've occurred before the end of the padded string). final String text = plainTextSegment.getText() + " "; breakIterator.setText(text); final int firstSentenceBoundary = breakIterator.next(); if (firstSentenceBoundary != BreakIterator.DONE && firstSentenceBoundary < text.length()) { return new TextParagraphInitialSegments(textParagraph, i, text.substring(0, firstSentenceBoundary), codeStyleClass, codeFormattingTag); } } } } // it's either not a text paragraph, or a "first sentence" could not be found, so just use the entire paragraph return new SingleParagraphContent(paragraph, codeStyleClass, codeFormattingTag); } /** * Escapes the text into proper HTML, converting characters into character entities when required. * @param text the text to be escaped. * @return the escaped text. */ static String htmlEscape(final String text) { return text .replaceAll("&", "&") // replace the ampersand first because it will show up later as part of character entities .replaceAll("<", "<") .replaceAll(">", ">"); } }