All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.docs.JavadocIncludeExtension Maven / Gradle / Ivy

//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.docs;

import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.asciidoctor.Asciidoctor;
import org.asciidoctor.ast.Document;
import org.asciidoctor.extension.IncludeProcessor;
import org.asciidoctor.extension.PreprocessorReader;
import org.asciidoctor.jruby.extension.spi.ExtensionRegistry;
import org.xml.sax.InputSource;

/**
 * 

Asciidoctor include extension that includes into * the document the output produced by an XSL transformation of * parts of the javadoc of a file.

*

Example usage in an Asciidoc page:

*
 * include::javadoc[file=Source.java,xsl=source.xsl,tags=docs]
 * 
*

Available configuration parameters are:

*
*
file
*
Mandatory, specifies the file to read the javadoc from, relative to the root of the Jetty Project source.
*
xsl
*
Mandatory, specifies the XSL file to use to transform the javadoc, relative to the root of the documentation source.
*
tags
*
Optional, specifies the name of the tagged regions of the javadoc to include.
*
replace
*
Optional, specifies a comma-separated pair where the first element is a regular * expression and the second is the string replacement, applied to each included line.
*
*

An example javadoc could be:

*
 * /**
 *  * <p>Class description.</p>
 *  * <!-- tag::docs -->
 *  * <p>Parameters</p>
 *  * <table>
 *  *   <tr>
 *  *     <td>param</td>
 *  *     <td>value</td>
 *  *   </tr>
 *  * </table>
 *  * <!-- end::docs -->
 *  */
 *  public class A
 *  {
 *  }
 * 
*

The javadoc lines included in the tagged region "docs" (between {@code tag::docs} and {@code end::docs}) * will be stripped of the asterisk at the beginning of the line and wrapped * into a {@code <root>} element, so that it becomes a well-formed XML document.

*

Each line of the XML document is then passed through the regular expression specified by the {@code replace} * parameter (if any), and then transformed using the XSL file specified by the {@code xsl} parameter, * which should produce a valid Asciidoc block which is then included in the Asciidoc documentation page.

*/ public class JavadocIncludeExtension implements ExtensionRegistry { @Override public void register(Asciidoctor asciidoctor) { asciidoctor.javaExtensionRegistry().includeProcessor(JavadocIncludeExtension.JavadocIncludeProcessor.class); } public static class JavadocIncludeProcessor extends IncludeProcessor { private static final Pattern JAVADOC_INITIAL_ASTERISK = Pattern.compile("^\\s*\\*\\s*(.*)$"); private static final Pattern JAVADOC_INLINE_CODE = Pattern.compile("\\{@code ([^\\}]+)\\}"); @Override public boolean handles(String target) { return "javadoc".equals(target); } @Override public void process(Document document, PreprocessorReader reader, String target, Map attributes) { try { // Document attributes are converted by Asciidoctor to lowercase. Path jettyDocsPath = Path.of((String)document.getAttribute("project-basedir")); Path jettyRoot = jettyDocsPath.resolve("../..").normalize(); String file = (String)attributes.get("file"); if (file == null) throw new IllegalArgumentException("Missing 'file' attribute"); Path filePath = jettyRoot.resolve(file.trim()); String xsl = (String)attributes.get("xsl"); if (xsl == null) throw new IllegalArgumentException("Missing 'xsl' attribute"); Path xslPath = jettyDocsPath.resolve(xsl.trim()); List tagList = new ArrayList<>(); String tags = (String)attributes.get("tags"); if (tags != null) { for (String tag : tags.split(",")) { tag = tag.trim(); boolean exclude = tag.startsWith("!"); if (exclude) tag = tag.substring(1); if (tag.isEmpty()) throw new IllegalArgumentException("Invalid tag in 'tags' attribute: " + tags); tagList.add(new Tag(tag, exclude)); } } String replace = (String)attributes.get("replace"); List contentLines = new ArrayList<>(); contentLines.add(""); Iterator lines = Files.lines(filePath, StandardCharsets.UTF_8).iterator(); Deque tagStack = new ArrayDeque<>(); while (lines.hasNext()) { String line = lines.next(); // Strip the initial Javadoc asterisk. Matcher matcher = JAVADOC_INITIAL_ASTERISK.matcher(line); if (matcher.matches()) line = matcher.group(1); // Convert {@code X} into X line = JAVADOC_INLINE_CODE.matcher(line).replaceAll("$1"); boolean keepLine = tagList.isEmpty() || tagList.stream().allMatch(tag -> tag.exclude); if (tagStack.isEmpty()) { for (Tag tag : tagList) { if (line.contains("tag::" + tag.name)) tagStack.push(tag); } } else { Tag currentTag = tagStack.peek(); keepLine = !currentTag.exclude; if (line.contains("end::" + currentTag.name)) { tagStack.pop(); keepLine = false; } } if (keepLine) { if (replace == null) contentLines.add(line); else contentLines.addAll(replace(line, replace)); } } contentLines.add(""); String content = String.join("\n", contentLines); // Run the XML stylesheet over the remaining lines. DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); org.w3c.dom.Document xml = builder.parse(new InputSource(new StringReader(content))); Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(xslPath.toFile())); StringWriter output = new StringWriter(content.length()); transformer.transform(new DOMSource(xml), new StreamResult(output)); String asciidoc = output.toString(); asciidoc = Arrays.stream(asciidoc.split("\n")).map(String::stripLeading).collect(Collectors.joining("\n")); reader.pushInclude(asciidoc, "javadoc", target, 1, attributes); } catch (Throwable x) { reader.pushInclude(x.toString(), "javadoc", target, 1, attributes); x.printStackTrace(); } } private List replace(String line, String replace) { // Format is: (regexp,replacement). String[] parts = replace.split(","); String regExp = parts[0]; String replacement = parts[1].replace("\\n", "\n"); return List.of(line.replaceAll(regExp, replacement).split("\n")); } private static class Tag { private final String name; private final boolean exclude; private Tag(String name, boolean exclude) { this.name = name; this.exclude = exclude; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy