AsciiDocModel.java
package pro.verron.officestamper.asciidoc;
import java.util.*;
import static java.util.Collections.emptyList;
/// Represents a minimal in-memory model of an AsciiDoc document.
///
/// This model intentionally supports a compact subset sufficient for rendering to WordprocessingML and JavaFX Scene: -
/// Headings (levels 1..6) using leading '=' markers - Paragraphs separated by blank lines - Inline emphasis for bold
/// and italic using AsciiDoc-like markers: *bold*, _italic_
public final class AsciiDocModel {
private final List<Block> blocks;
private AsciiDocModel(List<Block> blocks) {
this.blocks = List.copyOf(blocks);
}
/// Creates a new [AsciiDocModel] from the provided blocks.
///
/// @param blocks ordered content blocks
///
/// @return immutable AsciiDocModel
public static AsciiDocModel of(List<Block> blocks) {
Objects.requireNonNull(blocks, "blocks");
return new AsciiDocModel(new ArrayList<>(blocks));
}
/// Returns the ordered list of blocks comprising the document.
///
/// @return immutable list of blocks
public List<Block> getBlocks() {
return blocks;
}
/// Marker interface for document blocks.
public sealed interface Block
permits Blockquote, Break, CodeBlock, CommentLine, Heading, ImageBlock, MacroBlock, OpenBlock,
OrderedList, Paragraph, Table, UnorderedList {
int size();
}
/// Inline fragment inside a paragraph/heading.
public sealed interface Inline
permits Bold, InlineImage, InlineMacro, Italic, Link, Styled, Sub, Sup, Tab, Text {
/// Returns the text of the inline fragment.
///
/// @return text
String text();
}
/// Heading block (levels 1..6).
///
/// @param level heading level
/// @param inlines inline fragments
public record Heading(List<String> header, int level, List<Inline> inlines)
implements Block {
public Heading(int level, List<Inline> inlines) {
this(emptyList(), level, inlines);
}
/// Constructor.
///
/// @param level heading level
/// @param inlines inline fragments
public Heading(List<String> header, int level, List<Inline> inlines) {
if (level < 1 || level > 6) {
throw new IllegalArgumentException("Heading level must be between 1 and 6");
}
this.header = List.copyOf(header);
this.level = level;
this.inlines = List.copyOf(inlines);
}
@Override
public int size() {
return 1;
}
}
/// Paragraph block.
///
/// @param inlines inline fragments
public record Paragraph(List<String> header, List<Inline> inlines)
implements Block {
public Paragraph(List<Inline> inlines) {
this(emptyList(), inlines);
}
/// Constructor.
///
/// @param inlines inline fragments
public Paragraph(List<String> header, List<Inline> inlines) {
this.header = List.copyOf(header);
this.inlines = List.copyOf(inlines);
}
@Override
public int size() {
return 1;
}
}
/// Text fragment.
///
/// @param text text
public record Text(String text)
implements Inline {}
/// Bold inline that can contain nested inlines.
///
/// @param children nested inline fragments
public record Bold(List<Inline> children)
implements Inline {
/// Constructor.
///
/// @param children nested inline fragments
public Bold(List<Inline> children) {
this.children = List.copyOf(children);
}
@Override
public String text() {
StringBuilder sb = new StringBuilder();
for (Inline in : children) sb.append(in.text());
return sb.toString();
}
}
public record Sup(List<Inline> children)
implements Inline {
public Sup(List<Inline> children) {
this.children = List.copyOf(children);
}
@Override
public String text() {
StringBuilder sb = new StringBuilder();
for (Inline in : children) sb.append(in.text());
return sb.toString();
}
}
public record Sub(List<Inline> children)
implements Inline {
public Sub(List<Inline> children) {
this.children = List.copyOf(children);
}
@Override
public String text() {
StringBuilder sb = new StringBuilder();
for (Inline in : children) sb.append(in.text());
return sb.toString();
}
}
/// Italic inline that can contain nested inlines.
///
/// @param children nested inline fragments
public record Italic(List<Inline> children)
implements Inline {
/// Constructor.
///
/// @param children nested inline fragments
public Italic(List<Inline> children) {
this.children = List.copyOf(children);
}
@Override
public String text() {
StringBuilder sb = new StringBuilder();
for (Inline in : children) sb.append(in.text());
return sb.toString();
}
}
/// Inline tab marker to be rendered as a DOCX tab stop.
public record Tab()
implements Inline {
@Override
public String text() {
return "\t";
}
}
/// Simple table block: list of rows; each row is a list of cells; each cell contains inline content.
///
/// @param rows table rows
public record Table(List<Row> rows)
implements Block {
/// Constructor.
///
/// @param rows table rows
public Table(List<Row> rows) {
this.rows = List.copyOf(rows);
}
@Override
public int size() {
return rows.stream()
.map(Row::cells)
.flatMap(Collection::stream)
.map(Cell::blocks)
.flatMap(Collection::stream)
.mapToInt(Block::size)
.sum();
}
}
/// Table row.
///
/// @param cells table cells
public record Row(List<Cell> cells, Optional<String> style) {
/// Constructor.
///
/// @param cells table cells
public Row(List<Cell> cells) {
this(cells, Optional.empty());
}
public Row(List<Cell> cells, Optional<String> style) {
this.cells = List.copyOf(cells);
this.style = style;
}
public static List<Row> listOf() {
return List.of(of(Cell.listOf()));
}
private static Row of(List<Cell> cells) {
return new Row(cells);
}
}
/// Table cell.
///
/// @param blocks cell content blocks
public record Cell(List<Block> blocks, Optional<String> style) {
public Cell(List<Block> blocks) {
this(blocks, Optional.empty());
}
/// Constructor.
///
/// @param blocks cell content blocks
public Cell(List<Block> blocks, Optional<String> style) {
this.blocks = List.copyOf(blocks);
this.style = style;
}
private static List<Cell> listOf() {
return List.of(ofInlines(List.of(new Text("A"))), ofInlines(List.of(new Text("B"))));
}
public static Cell ofInlines(List<Inline> inlines) {
return new Cell(List.of(new Paragraph(inlines)));
}
}
/// Unordered list.
///
/// @param items list items
public record UnorderedList(List<ListItem> items)
implements Block {
@Override
public int size() {
return items.size();
}
}
/// Ordered list.
///
/// @param items list items
public record OrderedList(List<ListItem> items)
implements Block {
@Override
public int size() {
return items.size();
}
}
/// List item.
///
/// @param inlines inline fragments
public record ListItem(List<Inline> inlines) {
/// Constructor.
///
/// @param inlines inline fragments
public ListItem(List<Inline> inlines) {
this.inlines = List.copyOf(inlines);
}
}
/// Blockquote.
///
/// @param inlines inline fragments
public record Blockquote(List<Inline> inlines)
implements Block {
/// Constructor.
///
/// @param inlines inline fragments
public Blockquote(List<Inline> inlines) {
this.inlines = List.copyOf(inlines);
}
@Override
public int size() {
return 1;
}
}
/// Code block.
///
/// @param language language
/// @param content code content
public record CodeBlock(String language, String content)
implements Block {
@Override
public int size() {
return 1;
}
}
/// Image block.
///
/// @param url image URL
/// @param altText alternative text
public record ImageBlock(String url, String altText)
implements Block {
@Override
public int size() {
return 1;
}
}
/// Link inline.
///
/// @param url link URL
/// @param text link text
public record Link(String url, String text)
implements Inline {}
/// Inline image.
///
/// @param path image path
/// @param map alternative text
public record InlineImage(String path, Map<String, String> map)
implements Inline {
public InlineImage(String path, Map<String, String> map) {
this.path = path;
this.map = Collections.unmodifiableMap(new TreeMap<>(map));
}
@Override
public String text() {
return path;
}
}
public record OpenBlock(List<String> header, List<Block> content)
implements Block {
@Override
public int size() {
return content.stream()
.mapToInt(Block::size)
.sum();
}
}
public record Break()
implements Block {
@Override
public int size() {
return 0;
}
}
public record CommentLine(String comment)
implements Block {
@Override
public int size() {
return 0;
}
}
public record Styled(String role, List<Inline> children)
implements Inline {
@Override
public String text() {
StringBuilder sb = new StringBuilder();
for (Inline in : children) sb.append(in.text());
return sb.toString();
}
}
public record MacroBlock(String name, String id, List<String> list)
implements Block {
@Override
public int size() {
return 1;
}
}
public record InlineMacro(String name, String id, List<String> list)
implements Inline {
@Override
public String text() {
return String.join("", list);
}
}
}