Asciidoc Java
The module asciidoc-java implements a light Asciidoc syntax parser in plain Java. It aims at making it easier and lighter than using JRuby to embed Asciidoc in any Java based (native or not) application.
Its usage is pretty simple:
-
You get a
Parserinstance to load the model of your document in memory (AST), -
You visit the model as you want (or reusing provided
Visitor<?>) to render the loadedDocument.
|
TIP
|
the |
Here is a basic example:
final var parser = new Parser(); (1)
final var doc = parser.parse(myDocContent, new Parser.ParserContext(ContextResolver.of(Path.of("asciidoc/content")))); (2)
final var renderer = new AsciidoctorLikeHtmlRenderer(); (3)
renderer.visit(doc); (4)
final var html = renderer.result(); (5)
- Create a parser instance, can be a singleton in an application,
- Load the document model, can also be a singleton in an application when the conditions depends on attributes,
- Create a renderer (here a html renderer, optionally you can pass it attributes using the other constructor),
- Visit the document,
- Get back the output of the renderer (html output here).
|
IMPORTANT
|
the asciidoc syntax is not 100% implemented yet, if you encounter anything you miss, feel free to open an issue or pull request on our bugtracker. |
Parser attributes
These attributes change how the parser reads a document. Set them in the document (:keep-paragraphs: false), or pass them to the Parser constructor to apply them to every document.
| Name | Default | Description |
|---|---|---|
|
|
|
The inline macros whose target can hold blanks, as |
|
|
unset |
|
|
|
|
Each paragraph of the blocks of an admonition block, a list item, a callout item or an |
Source positions
The model does not keep the source text of the elements. To get it, for example to look each paragraph up in a translation file before rendering, register a SourceListener in the ParserContext. The parser calls it for each block-level element with a SourceSpan: the file the element was read from (an included file for included elements), its first and last line, and its raw lines before attribute substitution, without the attribute list and title lines preceding it. List items and table cells are read from their own reader, so their line numbers are relative to the item or cell. Where the parser evaluates a conditional while it reads, in the header and in a table, an element read from the branch that holds keeps its own line numbers and raw lines, nested directive lines included; a conditional nested in such a branch is read the same way, one level deep: a third level fails the parse with a message.
final var spans = new IdentityHashMap<Element, SourceSpan>(); (1)
final var doc = parser.parse(myDocContent, new Parser.ParserContext(resolver, spans::put)); (2)
- Elements are records and compare by value, so key the map by identity,
- The listener is the second constructor argument; the one-argument constructor keeps working and tracks nothing.
Markdown output
GithubFlavoredMarkdownRenderer renders the same model as GitHub-Flavored Markdown, for example to publish a Markdown copy of a page for AI agents or to feed a text pipeline:
final var renderer = new GithubFlavoredMarkdownRenderer(new GithubFlavoredMarkdownRenderer.Configuration()
.setAttributes(Map.of("imagesdir", "/images"))); (1)
renderer.visit(doc);
final var markdown = renderer.result();
- Pass the attributes the renderer uses when the document does not define them, as for the HTML renderer.
The Markdown renderer writes each construct as follows:
| AsciiDoc | Markdown |
|---|---|
|
Plain text |
Written with a backslash before each character a Markdown parser reads as markup: |
|
Section ( |
ATX heading ( |
|
Source block |
Fenced code block with the language. A diagram style such as |
|
Table |
Pipe table. |
|
Admonition |
GitHub alert ( |
|
Callouts |
Numbered list after the code block, the |
|
Description list |
Bold term followed by its description. |
|
Collapsible example block ( |
|
|
Footnote |
|
|
Id of a section or of a block |
Empty |
|
Cross reference ( |
Link to the anchor, its text being the |
|
Cross reference to another page ( |
Link to the rendered page, |
|
Table of contents ( |
List of links to the sections, where asciidoctor places it: at the top, after the preamble ( |
|
Icon ( |
|
|
Conditional ( |
The content of the branch that holds, where the block is written. As asciidoctor removes the directive lines before parsing, conditional lines inside a paragraph, a list item or an admonition stay in that paragraph, joined with a space like its other lines, and a conditional paragraph between blank lines stays a paragraph of its own. Inside a table, the parser evaluates the directive while it reads the rows, with the attributes it knows at that point (the document attributes and its own), as asciidoctor's preprocessor does, so an attribute given only to the renderer does not change the rows. |
When neither the document nor the configuration defines an attribute, the Markdown renderer leaves its reference literal. An include macro reaching the renderer fails, since the parser resolves includes before rendering. A macro the renderer has no case for, such as tooltip:foo[a hint], is written as text by default, as asciidoctor writes a macro no extension registers: Configuration.setUnknownMacro(UnknownMacro.IGNORE) writes nothing, UnknownMacro.FAIL throws an IllegalArgumentException naming the macro, UnknownMacro.TEXT writes the macro as text, and the attribute yupiik-renderer-githubflavoredmarkdown-unknownMacro, in the document or in the attributes of the configuration, wins over the option. A subclass renders its own macros in onMissingMacro.
A subclass that wants a text run written as it is overrides escapeText. One renderer instance can render several documents one after the other: each call to visit starts from an empty output. A subclass can override any rendering method, and the override also applies inside admonitions, lists and tables.
The Markdown renderer reads these attributes:
| Attribute | Use |
|---|---|
|
|
Skips the document title. |
|
|
Prefixed to a relative image target. |
|
|
Build the id of a section without an explicit id, as in the HTML renderer. |
|
|
Build the target of an |
|
|
Extensions of the documents an |
|
|
Write the table of contents, as in asciidoctor. |
|
|
What the renderer does with a macro it has no case for: |
Reading the model in a visitor
A custom visitor can read the model as the HTML and Markdown renderers do, with two classes of io.yupiik.asciidoc.renderer:
| Class | Use |
|---|---|
|
|
Reads the values the parser keeps as raw text: the id, the style and the options of a block, attribute references in titles, the parts of a cross reference or image target, macro labels such as the keys of |
|
|
Keeps the state of a rendering: the attributes of the document then the fallback ones, the text of a cross reference to each id, the ids the document links to, the sections of the table of contents and the footnotes. The renderer calls its |
new AsciidoctorLikeHtmlRenderer(configuration, sibling) and new GithubFlavoredMarkdownRenderer(configuration, sibling) take a subclass of VisitorSibling, whose readings then apply to the renderer and to its state.
Syntax
The parser supports standard Asciidoc syntax including:
-
Headings (e.g.
= Document Title,== Section) -
Discrete headings (
[discrete]or[float]above a heading): a title block that opens no section, ends none and stays out of the table of contents -
Paragraphs and text formatting (
*bold*,_italic_,monospace) -
Unordered lists (
*) and ordered lists (.) -
Links (
link:url[text]) and images (image:path[alt]) -
Tables (
|===) -
Source blocks (
[source,...]) -
Admonitions (NOTE:, TIP:, IMPORTANT:, WARNING:, CAUTION:)
-
Block titles, a
.Titleline ortitle=on the attribute line above a paragraph, an admonition, a list, a code block, a table, an image or an open block, written as asciidoctor writes them (<div class="title">, a<caption>for a table) -
Open/closed blocks, quote blocks, listing blocks, example blocks
-
Block ids, written
[#id],[id=name]or as an anchor line[[id]]([[id, reftext]]with a reference text) above a block or the document title -
Quoted values of named attributes,
[title="One, two"]or[title='One, two'], on a block attribute line as in a macro (link:url[text,window='_blank']): as in asciidoctor, the quotes are not part of the value and keep its commas,\'is a quote inside single quotes, and a single quote that is never closed stays text. A single-quoted positional value, such asxref:page.adoc['Getting started'], keeps its quotes -
Inline anchors,
[[id]]or[[id, reftext]], written as an empty<a id>when text follows them on the line, as asciidoctor does; at the end of a line, the anchor stays the id of the text before it -
Shorthands on a block style,
[NOTE#note.important%collapsible]giving the styleNOTE, the idnote, the roleimportantand the optioncollapsible, and chained options,[%collapsible%open]. A line without a style keeps the whole shorthand as one value, so[#io.yupiik.test.MyObject]is that id -
Macros (
image:,video:,audio:,icon:,btn:,kbd:,menu:,xref:, footnote), the keys ofkbd:being split on the first,or` after the first character, so `kbd:[Ctrl,Shift]` and `kbd:[Ctrl+Shift]` are both two keys and `kbd:[Ctrl+]isCtrland+ -
An inline macro starts where its name stands, as in asciidoctor: the target is the run without blanks before the
[, up to its first colon, and the name is the run of word characters and hyphens before that colon, soEnvironment variable: env_var:X[]keepsEnvironment variable:as text,name.footnote:[..]is a footnote andTime: 10:00 [foo]holds no macro. The parser has no registry of names, so every name is read the same way:hyperlink:x[y]is a macro namedhyperlink, where asciidoctor readshyperthen a link. The macros of theblank-target-macrosattribute also take a target holding blanks, asimage:my file.png[alt]. -
A macro with another name, such as
tooltip:foo[a hint], is kept in the model as aMacroelement; both renderers write it as text by default, as asciidoctor does with a macro no extension registers, or ignore it or fail on it when theunknownMacrooption of each renderer (see below and the Markdown output section) or its attribute asks for it, and a subclass ofAsciidoctorLikeHtmlRendereror ofGithubFlavoredMarkdownRendererrenders it inonMissingMacro -
Cross references (
<<id>>,<<id,text>>,<<page.adoc#id,text>>,xref:page.adoc#id[text]), the<<...>>form reading the part before a#as a page whatever its extension, as asciidoctor does -
Include directives with
tag/tags/lines/leveloffset/indentfiltering -
Conditionals (
ifdef::[],ifndef::[],ifeval::[]withelse::[],elsif::[]), in the document header as in the body, the condition ofifevalwritten in its brackets as asciidoctor requires (ifeval::[{version} > 1]), the inline formifdef::name[content]and multi-attribute conditions (name1,name2for an or,name1+name2for an and) -
Attributes and substitutions (
:name: value,{name},:!name:to unset,{name}escaping) -
Inline passthroughs in a code span: the text between single, double or triple plus signs inside the backticks is written as it is, without attribute substitution, and so is a double-plus run inside a longer code span; a span opening with a passthrough ends after it, so that passthrough can hold a backtick
-
Callouts, footnotes, index terms, counters
-
Table features: cell specifiers before
|(a span2+,.2+or2.3+, a duplication factor3*, an alignment^,>or.^, and a stylea,d,e,h,l,mors, at the start of a line or after a blank), cells put into rows by column count, header option, autowidth, stripes, column alignment -
Nested ordered/unordered lists,
[start=NN]on ordered lists,[qanda]/[horizontal]description lists -
Video macros for YouTube/Vimeo embed, audio/video with controls
-
Man page doctype (
= commandname(1)) -
Document authors, either with the author line (
firstname middlename lastname <mail>, multiple authors being separated by;, attribute entries being supported before it) or with attribute entries (:author:/:email:for a single author,:authors:or:author_<n>:/:email_<n>:for multiple ones) -
Revision line (
v1.0, 2013-01-01: the remark, a revision number without a date requiring thevprefix) -
Author and revision attributes deduced from the header (
authorcount,authors,firstname,middlename,lastname,authorinitials,revnumber,revdate,revremarkand their_<n>variants), the attribute entries of the document winning over the values deduced from the author line,Authoralso exposing the deduced names -
Markdown pipe tables (
|---|) -
Bibliography/glossary entries (
[[[ref]]]) -
Bare email autolink (
user@example.com) -
Inline STEM (
$...$,stem:[...]) -
Block STEM (
[stem],[latexmath],[asciimath]+ passthrough) -
Section numbering (
:sectnums:), special sections ([appendix],[bibliography],[glossary],[index]) -
Inline role
[.role]#text#,%option prefix -
TOC placement macro
toc::[], Mermaid diagrams
|
IMPORTANT
|
as in asciidoctor the document header ends on the first empty line so the line following the title is always the author line, unless it is an attribute entry or a preprocessor directive such as |
CommonMark syntax
The parser also supports a subset of CommonMark syntax for convenience:
-
**bold**→ bold text -
~~strikethrough~~→ strikethrough text -
code→ inline code -
[text](url)→ inline links -
→ inline images -
# Heading→ section headings (level based on#count) -
---,***,___→ horizontal rules -
|---|→ pipe tables (Markdown table syntax) -
**** content ****→ sidebar block -
Trailing double space at end of line → hard line break (
<br>)
These syntaxes are recognized alongside native Asciidoc syntax and can be mixed freely.
HTML renderer configuration
AsciidoctorLikeHtmlRenderer.Configuration changes the HTML that AsciidoctorLikeHtmlRenderer writes. The asciidoc goal of the Maven plugin sets these options with yupiik.asciidoc. properties, such as yupiik.asciidoc.sectionIdOnTitle, and the asciidoc-java launcher sets sectionTag, sectionIdOnTitle, skipSectionBody, skipGlobalContentWrapper, supportDataAttributes and unknownMacro with --section-tag, --section-id-on-title, --skip-section-body, --skip-global-content-wrapper, --data-attribute and --unknown-macro.
| Option | Default | Description |
|---|---|---|
|
|
|
The tag that wraps a section and its title. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
What the renderer does with a macro it has no method for, such as |
The :sectanchors: attribute writes an anchor link before the title of each section (:sectanchors: after writes it after the title), and the :sectlinks: attribute turns the title into a link to the section. Both attributes can be set in the document or in the attributes of the configuration.
A section without an explicit id gets the id asciidoctor gives it: the title in lower case, without its markup and punctuation, after idprefix (_ by default), with each run of spaces, dots and hyphens replaced by idseparator (_ by default). When an earlier section or block already has that id, the separator and a number follow it: _same_2, _same_3. A title with a typographic replacement such as --, a footnote or a counter can still get another id than in asciidoctor. With an empty :idprefix: and :idseparator: -, == Vert.x and the Getting-started guide gets vert-x-and-the-getting-started-guide; up to version 1.2.16, it got vertxandthegettingstartedguide.