The AsciidoctorJ API exposes a set of extension points. An extension point can manipulate the generated result of an Asciidoc document. For example, an implementation of the Postprocessor
inserts custom footer text.
A tabbed code block requires the insertion of dynamic behavior and custom styling. For this purpose, we’ll need to add CSS and JavaScript to the <head>
tag of a generated HTML document. The DocinfoProcessor
extension point can directly manipulate the header and footer of a document and is a good fit for our use case.
Listing 1 shows a class that implements the abstract class DocinfoProcessor
. The code example omits elaborate implementation details e.g. how to read a file from the classpath. For more details, have a look at the full source code of the class.
TabbedCodeBlockDocinfoProcessor.java
import org.asciidoctor.ast.Document;
import org.asciidoctor.extension.DocinfoProcessor;
import java.util.Map;
public class TabbedCodeBlockDocinfoProcessor extends DocinfoProcessor {
public static final String TABBED_CODE_CSS_FILE_PATH_ATTRIBUTE = "tabbed-code-css-path";
public static final String TABBED_CODE_JS_FILE_PATH_ATTRIBUTE = "tabbed-code-js-path";
public static final String DEFAULT_CSS_FILE_PATH = "/codeBlockSwitch.css";
public static final String DEFAULT_JS_FILE_PATH = "/codeBlockSwitch.js";
@Override
public String process(Document document) {
if (document.isBasebackend("html")) {
Map<String, Object> attributes = document.getAttributes();
String cssPath = getCssPath(attributes, TABBED_CODE_CSS_FILE_PATH_ATTRIBUTE, DEFAULT_CSS_FILE_PATH);
String jsPath = getCssPath(attributes, TABBED_CODE_JS_FILE_PATH_ATTRIBUTE, DEFAULT_JS_FILE_PATH);
String css = readFileContentsFromClasspath(cssPath);
String js = readFileContentsFromClasspath(jsPath);
return modifyHeadHtml(css, js);
}
return null;
}
...
}
Listing 1. An implementation of DocinfoProcessor
I’d like to point out two notable pieces in this code example. You might have noticed that the process
method only handles HTML processing. We do this for a simple reason. Adding a tabbed code block to other backends like PDF files would require different handling. By default the class uses a predefined CSS and JavaScript file that ships with the distribution of the extension. Alternatively, the end user can also set an attribute for providing custom styling or JavaScript behavior.
Every extension you’d like to make available to the Asciidoctor processor needs to be registered. You do this by implementing a class of type ExtensionRegistry
. Listing 2 demonstrates an implementation that adds the TabbedCodeBlockDocinfoProcessor
to the extension registry.
TabbedCodeBlockExtension.java
import org.asciidoctor.Asciidoctor;
import org.asciidoctor.extension.JavaExtensionRegistry;
import org.asciidoctor.extension.spi.ExtensionRegistry;
public class TabbedCodeBlockExtension implements ExtensionRegistry {
@Override
public void register(Asciidoctor asciidoctor) {
JavaExtensionRegistry javaExtensionRegistry = asciidoctor.javaExtensionRegistry();
javaExtensionRegistry.docinfoProcessor(TabbedCodeBlockDocinfoProcessor.class);
}
}
Listing 2. Registering the DocinfoProcessor with the extension registry
We are not done yet. You also need to create a file named org.asciidoctor.extension.spi.ExtensionRegistry
and point it to the extension implementation so that Asciidoctor can discover it.
META-INF/services/org.asciidoctor.extension.spi.ExtensionRegistry
com.bmuschko.asciidoctorj.tabbedcode.TabbedCodeBlockExtension
Listing 3. Making the extension discoverable
Voilà, you wrote your first AsciidoctorJ extension. Next, we’ll concentrate on the important aspects of testing, publishing and the usage of the extension.