Basics
Passing Data
Pass your data to the template as a Map<String, Object>. You can use nested maps and classes in any combination:
- Kotlin
- Java
import com.docstencil.core.api.OfficeTemplate
data class Address(val street: String, val city: String)
data class Person(val name: String, val address: Address)
fun main() {
val template = OfficeTemplate.fromFile("Template.docx")
val data = mapOf(
"person" to Person(
name = "Alice",
address = Address(street = "123 Main St", city = "Springfield")
)
)
val result = template.render(data)
result.writeToFile("Output.docx")
}
import com.docstencil.core.api.OfficeTemplate;
import com.docstencil.core.api.OfficeTemplateResult;
import java.util.Map;
public class Main {
public record Address(String street, String city) {}
public record Person(String name, Address address) {}
public static void main(String[] args) throws Exception {
OfficeTemplate template = OfficeTemplate.fromFile("Template.docx");
Map<String, Object> data = Map.of(
"person", new Person(
"Alice",
new Address("123 Main St", "Springfield")
)
);
OfficeTemplateResult result = template.render(data);
result.writeToFile("Output.docx");
}
}
Property Access
Use dot notation to access nested properties in your data:
DocStencil resolves properties uniformly regardless of how your data is structured. All of these work the same way:
- Maps:
mapOf("person" to mapOf("address" to mapOf("street" to "123 Main St"))) - Data classes: A
Persondata class with anaddressproperty - Java records: A
Personrecord with anaddress()accessor - POJOs with getters: A
Personclass withgetAddress()returning an object withgetStreet()
For Loops
Use {for ... in ...}...{end} to repeat content for each item in an Iterable.
{section.title}
{section.text}{end}
for loops also work in tables, where the loop repeats the entire table row:
Invoice #1234
| Description | Amount |
|---|---|
| {for pos in invoice.positions}{pos.description} | {pos.amount}{end} |
Conditionals
Use {if ...}...{end} to show content only when a condition is true. You can use comparison operators (==, !=, <, >, <=, >=) and logical operators (and, or, !).
Use code COUPON10 to get 10% off your next order.
{end}if statements can also be used inside table rows to conditionally render rows the same way for loops can be used there.
Builtin Functions
DocStencil provides several builtin helper functions. All builtin functions are prefixed with $.
Formatting Dates & Numbers
Use $format to format dates using Java's DateTimeFormatter patterns:
Invoice Date: {$format(invoice.date, "MMMM dd, yyyy")}
You can use the same $format function to format numbers using Java's DecimalFormat patterns:
Average: {$formatNumber(stats.average, "0.0")} points
Common patterns:
"0.0": one decimal place (e.g.,3.1)"0.00": two decimal places (e.g.,3.14)"#,##0.00": thousands separator with two decimals (e.g.,1,234.56)
Enumerating Items
Use $enumerate to get index and position information while iterating. Each item is wrapped with the following properties:
value: the original itemindex: zero-based position (0, 1, 2, ...)isFirst/isLast: boolean flags for first and last itemsisEven/isOdd: boolean flags based on index
Invoice #1234
| # | Description | Amount |
|---|---|---|
| {for pos in $enumerate(invoice.positions)}{pos.index + 1} | {pos.value.description} | {pos.value.amount}{end} |
Putting It All Together
Here's a complete invoice template that combines property access, loops, conditionals, and formatting:
INVOICE
From: {company.name}
{company.address}Bill To: {customer.name}
{customer.address}Invoice Date: {$formatDate(invoice.date, "MMMM dd, yyyy")}
Invoice #: {invoice.number}
| # | Description | Qty | Unit Price | Amount |
|---|---|---|---|---|
| {for item in $enumerate(invoice.items)}{item.index + 1} | {item.value.description} | {item.value.quantity} | {$formatNumber(item.value.unitPrice, "0.00")} | {$formatNumber(item.value.quantity * item.value.unitPrice, "0.00")}{end} |
Subtotal: {$formatNumber(invoice.subtotal, "#,##0.00")}
{if invoice.discount > 0}Discount: -{$formatNumber(invoice.discount, "#,##0.00")}
{end}
Total: {$formatNumber(invoice.total, "#,##0.00")}
{if invoice.notes}Notes: {invoice.notes}{end}
- Kotlin
- Java
val data = mapOf(
"company" to mapOf(
"name" to "Acme Corp",
"address" to "123 Business Ave, Suite 100"
),
"customer" to mapOf(
"name" to "John Smith",
"address" to "456 Customer Lane"
),
"invoice" to mapOf(
"number" to "INV-2024-001",
"date" to LocalDate.of(2024, 3, 15),
"items" to listOf(
mapOf("description" to "Web Development", "quantity" to 40, "unitPrice" to 150.00),
mapOf("description" to "UI Design", "quantity" to 20, "unitPrice" to 125.00),
mapOf("description" to "Hosting (Annual)", "quantity" to 1, "unitPrice" to 299.99)
),
"subtotal" to 8799.99,
"discount" to 500.00,
"total" to 8299.99,
"notes" to "Payment due within 30 days. Thank you for your business!"
)
)
Map<String, Object> data = Map.of(
"company", Map.of(
"name", "Acme Corp",
"address", "123 Business Ave, Suite 100"
),
"customer", Map.of(
"name", "John Smith",
"address", "456 Customer Lane"
),
"invoice", Map.of(
"number", "INV-2024-001",
"date", LocalDate.of(2024, 3, 15),
"items", List.of(
Map.of("description", "Web Development", "quantity", 40, "unitPrice", 150.00),
Map.of("description", "UI Design", "quantity", 20, "unitPrice", 125.00),
Map.of("description", "Hosting (Annual)", "quantity", 1, "unitPrice", 299.99)
),
"subtotal", 8799.99,
"discount", 500.00,
"total", 8299.99,
"notes", "Payment due within 30 days. Thank you for your business!"
)
);
INVOICE
From: Acme Corp
123 Business Ave, Suite 100
Bill To: John Smith
456 Customer Lane
Invoice Date: March 15, 2024
Invoice #: INV-2024-001
| # | Description | Qty | Unit Price | Amount |
|---|---|---|---|---|
| 1 | Web Development | 40 | $150.00 | $6,000.00 |
| 2 | UI Design | 20 | $125.00 | $2,500.00 |
| 3 | Hosting (Annual) | 1 | $299.99 | $299.99 |
Subtotal: $8,799.99
Discount: -$500.00
Total: $8,299.99
Notes: Payment due within 30 days. Thank you for your business!