A modern, type-safe Kotlin library for building and manipulating structured document trees.
DocTree provides a clean, fluent API for creating rich document structures programmatically. Whether you're generating reports, building documentation tools, or creating content management systems, DocTree gives you the power to represent complex documents as typed object trees.
- π― Type-Safe DSL - Build documents using an intuitive Kotlin DSL
- ποΈ Clean Architecture - Separation of concerns between API, core, and implementation
- π Visitor Pattern - Traverse and transform document trees efficiently
- π¨ Rich Styling - Comprehensive styling system for text and blocks
- π Complex Structures - Support for tables, lists, code blocks, quotes, and more
- π Text Extraction - Extract plain text from any document structure
- π Statistics - Analyze document structure with built-in visitors
- π§΅ Thread-Safe - ID generation and core operations are thread-safe
- π Extensible - Easy to extend with custom node types and visitors
Add to your build.gradle.kts:
dependencies {
implementation("org.codirex.doctree:doc-tree:1.0.0")
}import org.codirex.doctree.api.document import org.codirex.doctree.api.visitor.toPlainText import org.codirex.doctree.style.* // Create a document using the fluent DSL val doc = document { metadata { title = "Getting Started with DocTree" author = "John Doe" keywords = listOf("documentation", "kotlin", "tutorial") } heading(1) { text("Welcome to DocTree") } paragraph { text("DocTree is a ") bold("powerful") text(" and ") italic("flexible") text(" library for building structured documents.") } list(ordered = true) { item { paragraph { text("Easy to use") } } item { paragraph { text("Type-safe") } } item { paragraph { text("Extensible") } } } codeBlock( language = "kotlin", code = """ val doc = document { paragraph { text("Hello, World!") } } """.trimIndent() ) } // Extract plain text val plainText = doc.toPlainText() println(plainText)
DocTree represents documents as hierarchical trees of nodes:
Document (root)
ββ Paragraph
β ββ TextRun (plain)
β ββ TextRun (bold)
ββ Heading
β ββ TextRun
ββ List
β ββ ListItem
β β ββ Paragraph
β ββ ListItem
β ββ Paragraph
ββ Table
ββ TableRow
β ββ TableCell
β ββ TableCell
ββ TableRow
ββ TableCell
ββ TableCell
- Document - Root container for all content
- Paragraph - Basic text block with styled runs
- Heading - Section headings (levels 1-6)
- ListBlock - Ordered or unordered lists
- ListItem - Individual list items
- CodeBlock - Syntax-highlighted code blocks
- BlockQuote - Quoted content
- Table - Tabular data with rows and cells
- Image - Embedded images
- HorizontalRule - Thematic breaks
- TextRun - Styled text content with formatting
DocTree provides comprehensive styling capabilities:
paragraph {
// Basic styling
text("Normal text")
bold("Bold text")
italic("Italic text")
underline("Underlined")
strikethrough("Strikethrough")
// Custom styling
text("Custom", TextStyle(
font = Font.ROBOTO,
size = 14f,
weight = 600,
color = Color.BLUE,
backgroundColor = Color.YELLOW,
decoration = setOf(TextDecoration.BOLD, TextDecoration.UNDERLINE)
))
// Code styling
code("inline code")
}val paragraph = Paragraph( id = "para-1", style = BlockStyle( margin = Spacing.all(16), padding = Spacing.symmetric(vertical = 8, horizontal = 12), border = Border.all(width = 1, color = Color.BLACK), background = Background(color = Color(240, 240, 240)), width = Dimension.percent(100f), textStyle = TextStyle(size = 14f) ) )
// Predefined colors val red = Color.RED val blue = Color.BLUE // Custom RGB colors val purple = Color(128, 0, 128) // Colors with transparency val semiTransparent = Color(255, 0, 0, 0.5f) // Hex colors val orange = Color.fromHex("#FF6600")
DocTree uses the Visitor pattern for tree traversal and transformation:
val plainText = document.toPlainText()
val stats = document.getStatistics() println("Paragraphs: ${stats.paragraphCount}") println("Headings: ${stats.headingCount}") println("Total words: ${stats.totalWords}") println("Total characters: ${stats.totalCharacters}")
Create custom visitors by extending AbstractNodeVisitor:
class MarkdownExportVisitor : AbstractNodeVisitor<String>() { override fun defaultResult() = "" override fun visitHeading(heading: Heading): String { val hashes = "#".repeat(heading.level) return "$hashes ${heading.getPlainText()}\n\n" } override fun visitParagraph(paragraph: Paragraph): String { return "${paragraph.getPlainText()}\n\n" } override fun visitCodeBlock(codeBlock: CodeBlock): String { val lang = codeBlock.language ?: "" return "```$lang\n${codeBlock.code}\n```\n\n" } // Implement other visit methods... } // Use the visitor val markdown = document.accept(MarkdownExportVisitor())
document {
table(columnCount = 3, hasHeaderRow = true) {
// Header row
row {
cell { paragraph { bold("Name") } }
cell { paragraph { bold("Age") } }
cell { paragraph { bold("Role") } }
}
// Data rows
row {
cell { paragraph { text("Alice") } }
cell { paragraph { text("30") } }
cell { paragraph { text("Developer") } }
}
row {
cell { paragraph { text("Bob") } }
cell { paragraph { text("25") } }
cell { paragraph { text("Designer") } }
}
}
}document {
list(ordered = true) {
item {
paragraph { text("First level item 1") }
}
item {
paragraph { text("First level item 2") }
// Nested list would be added as a separate ListBlock within the item
}
}
}paragraph {
text("This paragraph contains ")
bold("bold")
text(", ")
italic("italic")
text(", ")
underline("underlined")
text(", and ")
colored("colored", Color.RED)
text(" text, as well as ")
highlighted("highlighted", Color.YELLOW)
text(" content.")
}DocTree follows clean architecture principles:
org.codirex.doctree
βββ api/ # Public API layer
β βββ DocumentBuilder # Fluent DSL builder
β βββ visitor/ # Visitor implementations
β βββ AbstractNodeVisitor
β βββ TextExtractorVisitor
β βββ NodeCounterVisitor
β
βββ core/ # Core domain model
β βββ Node # Base node interface
β βββ NodeVisitor # Visitor interface
β βββ block/ # Block-level elements
β β βββ BlockElement # Base block class
β β βββ Document
β β βββ Paragraph
β β βββ Heading
β β βββ ListBlock
β β βββ CodeBlock
β β βββ Table
β β βββ ...
β βββ text/ # Text-level elements
β βββ TextRun
β
βββ style/ # Styling system
β βββ BlockStyle
β βββ TextStyle
β βββ Color
β βββ Font
β βββ Border
β βββ Spacing
β βββ ...
β
βββ utils/ # Utilities
βββ IdGenerator # Unique ID generation
- Separation of Concerns - Clear boundaries between API, domain, and utilities
- Immutability - Style objects are immutable; use
copy()for modifications - Type Safety - Strong typing throughout the API
- Extensibility - Easy to add new node types and visitors
- Testability - Pure functions and dependency injection where needed
- Extend
BlockElementor implementNode - Add visit method to
NodeVisitorinterface - Implement in all visitor implementations
- Add builder methods if needed
Extend AbstractNodeVisitor<R> and override methods for nodes you care about:
class MyCustomVisitor : AbstractNodeVisitor<MyResult>() { override fun defaultResult() = MyResult() override fun visitParagraph(paragraph: Paragraph): MyResult { // Your custom logic } }
- Memory Efficiency - Nodes are lightweight; styling is done via immutable data classes
- Thread Safety - ID generation is thread-safe; document trees themselves are not inherently thread-safe (use external synchronization if needed)
- Lazy Evaluation - Use visitors for on-demand processing
- Immutable Styles - Enables safe sharing and caching
DocTree is designed for testability:
@Test fun `should create document with proper structure`() { val doc = document { heading(1) { text("Title") } paragraph { text("Content") } } val stats = doc.getStatistics() assertEquals(1, stats.headingCount) assertEquals(1, stats.paragraphCount) }
- Initial release
- Complete document tree model
- Fluent DSL API
- Visitor pattern support
- Comprehensive styling system
- Built-in text extraction and statistics visitors
Contributions are welcome! Please feel free to submit pull requests or open issues.
- Clone the repository
- Open in IntelliJ IDEA
- Build:
./gradlew build - Test:
./gradlew test
This project is licensed under the Apache License 2.0. See the LICENSE file for the full text. Third-party attributions (if any) are listed in NOTICE.
Built with β€οΈ using Kotlin and modern software engineering principles.
- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with π by Codirex