AsciiDocToDocx.java
package pro.verron.officestamper.asciidoc;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.*;
import java.math.BigInteger;
import java.util.List;
import static pro.verron.officestamper.asciidoc.AsciiDocModel.*;
/// Renders [AsciiDocModel] into a [WordprocessingMLPackage] using docx4j.
public final class AsciiDocToDocx {
private AsciiDocToDocx() {}
/// Creates a new WordprocessingMLPackage and fills it with content from the model.
///
/// @param model parsed AsciiDoc model
///
/// @return package containing the rendered document
public static WordprocessingMLPackage compileToPackage(AsciiDocModel model) {
try {
var pkg = WordprocessingMLPackage.createPackage();
var factory = Context.getWmlObjectFactory();
for (Block block : model.getBlocks()) {
if (block instanceof Heading h) {
pkg.getMainDocumentPart()
.addObject(createHeading(factory, h));
}
else if (block instanceof Paragraph p) {
pkg.getMainDocumentPart()
.addObject(createParagraph(factory, p));
}
else if (block instanceof Table t) {
pkg.getMainDocumentPart()
.addObject(createTable(factory, t));
}
}
return pkg;
} catch (Docx4JException e) {
throw new IllegalStateException("Unable to create WordprocessingMLPackage", e);
}
}
private static P createHeading(ObjectFactory factory, Heading heading) {
P p = factory.createP();
PPr ppr = factory.createPPr();
PPrBase.PStyle pStyle = factory.createPPrBasePStyle();
pStyle.setVal("Heading" + heading.level());
ppr.setPStyle(pStyle);
p.setPPr(ppr);
RPr headingRunPr = factory.createRPr();
// Increase size a bit relative to level if heading styles are missing
HpsMeasure sz = factory.createHpsMeasure();
int base = switch (heading.level()) {
case 1 -> 32; // 16pt
case 2 -> 28;
case 3 -> 26;
case 4 -> 24;
case 5 -> 22;
default -> 20;
};
sz.setVal(BigInteger.valueOf(base));
headingRunPr.setSz(sz);
headingRunPr.setSzCs(sz);
addInlines(factory, p, heading.inlines(), headingRunPr);
return p;
}
private static P createParagraph(ObjectFactory factory, Paragraph paragraph) {
P p = factory.createP();
addInlines(factory, p, paragraph.inlines(), null);
return p;
}
private static void addInlines(ObjectFactory factory, P p, List<Inline> inlines, RPr base) {
for (Inline inline : inlines) {
emitInline(factory, p, inline, base);
}
}
private static Tbl createTable(ObjectFactory factory, Table table) {
Tbl tbl = factory.createTbl();
// Minimal table without explicit grid; Word will auto-fit columns
for (Row row : table.rows()) {
Tr tr = factory.createTr();
for (Cell cell : row.cells()) {
Tc tc = factory.createTc();
// Add a single paragraph per cell for now
P p = factory.createP();
addInlines(factory, p, cell.inlines(), null);
tc.getContent()
.add(p);
tr.getContent()
.add(tc);
}
tbl.getContent()
.add(tr);
}
return tbl;
}
private static void emitInline(ObjectFactory factory, P p, Inline inline, RPr base) {
if (inline instanceof AsciiDocModel.Text(String text)) {
R r = factory.createR();
RPr rpr = base != null ? deepCopy(factory, base) : factory.createRPr();
org.docx4j.wml.Text tx = factory.createText();
tx.setValue(text);
// Preserve spaces/tabs if present within text segments
tx.setSpace("preserve");
r.getContent()
.add(tx);
r.setRPr(rpr);
p.getContent()
.add(r);
return;
}
if (inline instanceof Bold(List<Inline> children)) {
RPr next = base != null ? deepCopy(factory, base) : factory.createRPr();
next.setB(new BooleanDefaultTrue());
for (Inline child : children) {
emitInline(factory, p, child, next);
}
return;
}
if (inline instanceof Italic(List<Inline> children)) {
RPr next = base != null ? deepCopy(factory, base) : factory.createRPr();
next.setI(new BooleanDefaultTrue());
for (Inline child : children) {
emitInline(factory, p, child, next);
}
return;
}
if (inline instanceof AsciiDocModel.Tab) {
R r = factory.createR();
R.Tab tab = factory.createRTab();
r.getContent()
.add(tab);
p.getContent()
.add(r);
}
}
private static RPr deepCopy(ObjectFactory factory, RPr src) {
// Minimal copy of relevant props; docx4j doesn't offer a trivial clone here.
RPr c = factory.createRPr();
if (src.getB() != null) {
BooleanDefaultTrue b = new BooleanDefaultTrue();
c.setB(b);
}
if (src.getI() != null) {
BooleanDefaultTrue i = new BooleanDefaultTrue();
c.setI(i);
}
if (src.getSz() != null) {
HpsMeasure sz = factory.createHpsMeasure();
sz.setVal(src.getSz()
.getVal());
c.setSz(sz);
HpsMeasure szCs = factory.createHpsMeasure();
szCs.setVal(src.getSz()
.getVal());
c.setSzCs(szCs);
}
return c;
}
}