AsciiDocModel.java

package pro.verron.officestamper.asciidoc;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/// 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 {@link 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 Heading, Paragraph, Table {}

    /// Inline fragment inside a paragraph/heading.
    public sealed interface Inline
            permits Text, Bold, Italic, Tab {
        /// 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(int level, List<Inline> inlines)
            implements Block {
        /// Constructor.
        ///
        /// @param level heading level
        /// @param inlines inline fragments
        public Heading(int level, List<Inline> inlines) {
            if (level < 1 || level > 6) {
                throw new IllegalArgumentException("Heading level must be between 1 and 6");
            }
            this.level = level;
            this.inlines = List.copyOf(inlines);
        }
    }

    /// Paragraph block.
    ///
    /// @param inlines inline fragments
    public record Paragraph(List<Inline> inlines)
            implements Block {
        /// Constructor.
        ///
        /// @param inlines inline fragments
        public Paragraph(List<Inline> inlines) {
            this.inlines = List.copyOf(inlines);
        }
    }

    /// 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();
        }
    }

    /// 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);
        }
    }

    /// Table row.
    ///
    /// @param cells table cells
    public record Row(List<Cell> cells) {
        /// Constructor.
        ///
        /// @param cells table cells
        public Row(List<Cell> cells) {
            this.cells = List.copyOf(cells);
        }
    }

    /// Table cell.
    ///
    /// @param inlines inline fragments
    public record Cell(List<Inline> inlines) {
        /// Constructor.
        ///
        /// @param inlines inline fragments
        public Cell(List<Inline> inlines) {
            this.inlines = List.copyOf(inlines);
        }
    }
}