Block Processor

A block processor is very similar to a block macro processor. But in contrast to a block macro a block processor is called for a block having a certain name instead of a macro invocation. Therefore block processors rather transform blocks instead of creating them as block macro processors do.

The following example shows a block processor that converts the whole text of a block to upper case if it has the name yell. That means that our block processor will convert blocks like this:

yell-block.adoc
[yell]
I really mean it

After the processing this block will look like this

I REALLY MEAN IT

The BlockProcessor looks like this:

A BlockProcessor that transforms the content of a block to upper case
@Name("yell")                                              (1)
@Contexts({Contexts.PARAGRAPH})                            (2)
@ContentModel(ContentModel.SIMPLE)                         (3)
public class YellBlockProcessor extends BlockProcessor {   (4)

    @Override
    public Object process(                                 (5)
            StructuralNode parent, Reader reader, Map<String, Object> attributes) {

        String content = reader.read();
        String yellContent = content.toUpperCase();

        return createBlock(parent, "paragraph", yellContent, attributes);
    }

}
1 The annotation @Name defines the block name that this block processor handles.
2 The annotation @Contexts defines the block types that this block processor handles like paragraphs, listing blocks, or open blocks. Constants for all contexts are also defined in this annotation. Note that this annotation takes a list of block types, so that a block processor can process paragraph blocks as well as example blocks with the same block name.
3 The annotation @ContentModel defines what this processor produces. Constants for all contexts are also defined for the annotation class. In this case the block processor creates a simple paragraph, therefore the content model ContentModel.SIMPLE is defined.
4 All block processors must extend org.asciidoctor.extension.BlockProcessor.
5 A block processor must implement the method process(). Here the implementation gets the raw block content from the reader, transforms it and creates and returns a new block that contains the transformed content.

Attributes

Block Processors receive attributes in their process() method. These attributes contain the additional key value pairs that were applied to the block. You can read more about element attributes in general in the AsciiDoc documentation.

If we convert the following document:

yell-block-attributes.adoc
[yell,loudness=3]
I really mean it

Then a Block Processor can access the attribute loudness like this:

YellBlockProcessorWithAttributes.java
@Name("yell")
@Contexts({Contexts.PARAGRAPH})
@ContentModel(ContentModel.SIMPLE)
public class YellBlockProcessorWithAttributes extends BlockProcessor {

    @Override
    public Object process(
            StructuralNode parent, Reader reader, Map<String, Object> attributes) {

        String content = reader.read();
        String yellContent = content.toUpperCase();

        String loudness = (String) attributes.get("loudness"); (1)
        if (loudness != null) {
            yellContent += IntStream.range(0, Integer.parseInt(loudness))
                    .mapToObj(i -> "!")
                    .collect(joining());
        }

        return createBlock(parent, "paragraph", yellContent, attributes);
    }

}
1 Attribute values from the source document are usually Strings.

Positional attributes

Besides defining attributes as key value pairs like in [yell,loudness=3] AsciiDoc also supports positional attributes. For positional attributes the meaning is implicitly derived from the position in the attribute list. In our example the attribute loudness might be defined as the first positional attribute after the block style:

yell-block-positional.adoc
[yell,5]
I really mean it

Then a Block Processor can access the attribute loudness like this:

YellBlockProcessorWithPositionalAttributes.java
@Name("yell")
@PositionalAttributes({"loudness"})                            (1)
@Contexts({Contexts.PARAGRAPH})
@ContentModel(ContentModel.SIMPLE)
public class YellBlockProcessorWithPositionalAttributes extends BlockProcessor {

    @Override
    public Object process(
            StructuralNode parent, Reader reader, Map<String, Object> attributes) {

        String content = reader.read();
        String yellContent = content.toUpperCase();

        String loudness = (String) attributes.get("loudness"); (2)
        if (loudness != null) {
            yellContent += IntStream.range(0, Integer.parseInt(loudness))
                    .mapToObj(i -> "!")
                    .collect(joining());
        }

        return createBlock(parent, "paragraph", yellContent, attributes);
    }

}
1 The attribute names defined in the annotation reflect the attribute positions, i.e., loudness is the first positional attribute. A second attribute exclamationmark would be defined as @PositionalAttributes({"loudness", "exclamationmark"}) so that the Block Processor can access the attributes of the block defined with [yell,3,"¡"].
2 For the code itself there is no difference between a positional and a named attribute.