| 1 | package pro.verron.officestamper.asciidoc; | |
| 2 | ||
| 3 | import org.docx4j.jaxb.Context; | |
| 4 | import org.docx4j.openpackaging.exceptions.Docx4JException; | |
| 5 | import org.docx4j.openpackaging.packages.WordprocessingMLPackage; | |
| 6 | import org.docx4j.wml.*; | |
| 7 | ||
| 8 | import java.math.BigInteger; | |
| 9 | import java.util.List; | |
| 10 | ||
| 11 | import static pro.verron.officestamper.asciidoc.AsciiDocModel.*; | |
| 12 | ||
| 13 | /// Renders [AsciiDocModel] into a [WordprocessingMLPackage] using docx4j. | |
| 14 | public final class AsciiDocToDocx { | |
| 15 | private AsciiDocToDocx() {} | |
| 16 | ||
| 17 | /// Creates a new WordprocessingMLPackage and fills it with content from the model. | |
| 18 | /// | |
| 19 | /// @param model parsed AsciiDoc model | |
| 20 | /// | |
| 21 | /// @return package containing the rendered document | |
| 22 | public static WordprocessingMLPackage compileToPackage(AsciiDocModel model) { | |
| 23 | try { | |
| 24 | var pkg = WordprocessingMLPackage.createPackage(); | |
| 25 | var factory = Context.getWmlObjectFactory(); | |
| 26 | ||
| 27 | for (Block block : model.getBlocks()) { | |
| 28 |
1
1. compileToPackage : negated conditional → NO_COVERAGE |
if (block instanceof Heading h) { |
| 29 | pkg.getMainDocumentPart() | |
| 30 |
1
1. compileToPackage : removed call to org/docx4j/openpackaging/parts/WordprocessingML/MainDocumentPart::addObject → NO_COVERAGE |
.addObject(createHeading(factory, h)); |
| 31 | } | |
| 32 |
1
1. compileToPackage : negated conditional → NO_COVERAGE |
else if (block instanceof Paragraph p) { |
| 33 | pkg.getMainDocumentPart() | |
| 34 |
1
1. compileToPackage : removed call to org/docx4j/openpackaging/parts/WordprocessingML/MainDocumentPart::addObject → NO_COVERAGE |
.addObject(createParagraph(factory, p)); |
| 35 | } | |
| 36 |
1
1. compileToPackage : negated conditional → NO_COVERAGE |
else if (block instanceof Table t) { |
| 37 | pkg.getMainDocumentPart() | |
| 38 |
1
1. compileToPackage : removed call to org/docx4j/openpackaging/parts/WordprocessingML/MainDocumentPart::addObject → NO_COVERAGE |
.addObject(createTable(factory, t)); |
| 39 | } | |
| 40 | } | |
| 41 |
1
1. compileToPackage : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::compileToPackage → NO_COVERAGE |
return pkg; |
| 42 | } catch (Docx4JException e) { | |
| 43 | throw new IllegalStateException("Unable to create WordprocessingMLPackage", e); | |
| 44 | } | |
| 45 | } | |
| 46 | ||
| 47 | private static P createHeading(ObjectFactory factory, Heading heading) { | |
| 48 | P p = factory.createP(); | |
| 49 | PPr ppr = factory.createPPr(); | |
| 50 | PPrBase.PStyle pStyle = factory.createPPrBasePStyle(); | |
| 51 |
1
1. createHeading : removed call to org/docx4j/wml/PPrBase$PStyle::setVal → NO_COVERAGE |
pStyle.setVal("Heading" + heading.level()); |
| 52 |
1
1. createHeading : removed call to org/docx4j/wml/PPr::setPStyle → NO_COVERAGE |
ppr.setPStyle(pStyle); |
| 53 |
1
1. createHeading : removed call to org/docx4j/wml/P::setPPr → NO_COVERAGE |
p.setPPr(ppr); |
| 54 | ||
| 55 | RPr headingRunPr = factory.createRPr(); | |
| 56 | // Increase size a bit relative to level if heading styles are missing | |
| 57 | HpsMeasure sz = factory.createHpsMeasure(); | |
| 58 | int base = switch (heading.level()) { | |
| 59 | case 1 -> 32; // 16pt | |
| 60 | case 2 -> 28; | |
| 61 | case 3 -> 26; | |
| 62 | case 4 -> 24; | |
| 63 | case 5 -> 22; | |
| 64 | default -> 20; | |
| 65 | }; | |
| 66 |
1
1. createHeading : removed call to org/docx4j/wml/HpsMeasure::setVal → NO_COVERAGE |
sz.setVal(BigInteger.valueOf(base)); |
| 67 |
1
1. createHeading : removed call to org/docx4j/wml/RPr::setSz → NO_COVERAGE |
headingRunPr.setSz(sz); |
| 68 |
1
1. createHeading : removed call to org/docx4j/wml/RPr::setSzCs → NO_COVERAGE |
headingRunPr.setSzCs(sz); |
| 69 | ||
| 70 |
1
1. createHeading : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addInlines → NO_COVERAGE |
addInlines(factory, p, heading.inlines(), headingRunPr); |
| 71 |
1
1. createHeading : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createHeading → NO_COVERAGE |
return p; |
| 72 | } | |
| 73 | ||
| 74 | private static P createParagraph(ObjectFactory factory, Paragraph paragraph) { | |
| 75 | P p = factory.createP(); | |
| 76 |
1
1. createParagraph : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addInlines → NO_COVERAGE |
addInlines(factory, p, paragraph.inlines(), null); |
| 77 |
1
1. createParagraph : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createParagraph → NO_COVERAGE |
return p; |
| 78 | } | |
| 79 | ||
| 80 | private static void addInlines(ObjectFactory factory, P p, List<Inline> inlines, RPr base) { | |
| 81 | for (Inline inline : inlines) { | |
| 82 |
1
1. addInlines : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::emitInline → NO_COVERAGE |
emitInline(factory, p, inline, base); |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | private static Tbl createTable(ObjectFactory factory, Table table) { | |
| 87 | Tbl tbl = factory.createTbl(); | |
| 88 | // Minimal table without explicit grid; Word will auto-fit columns | |
| 89 | for (Row row : table.rows()) { | |
| 90 | Tr tr = factory.createTr(); | |
| 91 | for (Cell cell : row.cells()) { | |
| 92 | Tc tc = factory.createTc(); | |
| 93 | // Add a single paragraph per cell for now | |
| 94 | P p = factory.createP(); | |
| 95 |
1
1. createTable : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addInlines → NO_COVERAGE |
addInlines(factory, p, cell.inlines(), null); |
| 96 | tc.getContent() | |
| 97 | .add(p); | |
| 98 | tr.getContent() | |
| 99 | .add(tc); | |
| 100 | } | |
| 101 | tbl.getContent() | |
| 102 | .add(tr); | |
| 103 | } | |
| 104 |
1
1. createTable : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createTable → NO_COVERAGE |
return tbl; |
| 105 | } | |
| 106 | ||
| 107 | private static void emitInline(ObjectFactory factory, P p, Inline inline, RPr base) { | |
| 108 |
1
1. emitInline : negated conditional → NO_COVERAGE |
if (inline instanceof AsciiDocModel.Text(String text)) { |
| 109 | R r = factory.createR(); | |
| 110 |
1
1. emitInline : negated conditional → NO_COVERAGE |
RPr rpr = base != null ? deepCopy(factory, base) : factory.createRPr(); |
| 111 | org.docx4j.wml.Text tx = factory.createText(); | |
| 112 |
1
1. emitInline : removed call to org/docx4j/wml/Text::setValue → NO_COVERAGE |
tx.setValue(text); |
| 113 | // Preserve spaces/tabs if present within text segments | |
| 114 |
1
1. emitInline : removed call to org/docx4j/wml/Text::setSpace → NO_COVERAGE |
tx.setSpace("preserve"); |
| 115 | r.getContent() | |
| 116 | .add(tx); | |
| 117 |
1
1. emitInline : removed call to org/docx4j/wml/R::setRPr → NO_COVERAGE |
r.setRPr(rpr); |
| 118 | p.getContent() | |
| 119 | .add(r); | |
| 120 | return; | |
| 121 | } | |
| 122 | ||
| 123 |
1
1. emitInline : negated conditional → NO_COVERAGE |
if (inline instanceof Bold(List<Inline> children)) { |
| 124 |
1
1. emitInline : negated conditional → NO_COVERAGE |
RPr next = base != null ? deepCopy(factory, base) : factory.createRPr(); |
| 125 |
1
1. emitInline : removed call to org/docx4j/wml/RPr::setB → NO_COVERAGE |
next.setB(new BooleanDefaultTrue()); |
| 126 | for (Inline child : children) { | |
| 127 |
1
1. emitInline : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::emitInline → NO_COVERAGE |
emitInline(factory, p, child, next); |
| 128 | } | |
| 129 | return; | |
| 130 | } | |
| 131 | ||
| 132 |
1
1. emitInline : negated conditional → NO_COVERAGE |
if (inline instanceof Italic(List<Inline> children)) { |
| 133 |
1
1. emitInline : negated conditional → NO_COVERAGE |
RPr next = base != null ? deepCopy(factory, base) : factory.createRPr(); |
| 134 |
1
1. emitInline : removed call to org/docx4j/wml/RPr::setI → NO_COVERAGE |
next.setI(new BooleanDefaultTrue()); |
| 135 | for (Inline child : children) { | |
| 136 |
1
1. emitInline : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::emitInline → NO_COVERAGE |
emitInline(factory, p, child, next); |
| 137 | } | |
| 138 | return; | |
| 139 | } | |
| 140 | ||
| 141 |
1
1. emitInline : negated conditional → NO_COVERAGE |
if (inline instanceof AsciiDocModel.Tab) { |
| 142 | R r = factory.createR(); | |
| 143 | R.Tab tab = factory.createRTab(); | |
| 144 | r.getContent() | |
| 145 | .add(tab); | |
| 146 | p.getContent() | |
| 147 | .add(r); | |
| 148 | } | |
| 149 | } | |
| 150 | ||
| 151 | private static RPr deepCopy(ObjectFactory factory, RPr src) { | |
| 152 | // Minimal copy of relevant props; docx4j doesn't offer a trivial clone here. | |
| 153 | RPr c = factory.createRPr(); | |
| 154 |
1
1. deepCopy : negated conditional → NO_COVERAGE |
if (src.getB() != null) { |
| 155 | BooleanDefaultTrue b = new BooleanDefaultTrue(); | |
| 156 |
1
1. deepCopy : removed call to org/docx4j/wml/RPr::setB → NO_COVERAGE |
c.setB(b); |
| 157 | } | |
| 158 |
1
1. deepCopy : negated conditional → NO_COVERAGE |
if (src.getI() != null) { |
| 159 | BooleanDefaultTrue i = new BooleanDefaultTrue(); | |
| 160 |
1
1. deepCopy : removed call to org/docx4j/wml/RPr::setI → NO_COVERAGE |
c.setI(i); |
| 161 | } | |
| 162 |
1
1. deepCopy : negated conditional → NO_COVERAGE |
if (src.getSz() != null) { |
| 163 | HpsMeasure sz = factory.createHpsMeasure(); | |
| 164 |
1
1. deepCopy : removed call to org/docx4j/wml/HpsMeasure::setVal → NO_COVERAGE |
sz.setVal(src.getSz() |
| 165 | .getVal()); | |
| 166 |
1
1. deepCopy : removed call to org/docx4j/wml/RPr::setSz → NO_COVERAGE |
c.setSz(sz); |
| 167 | HpsMeasure szCs = factory.createHpsMeasure(); | |
| 168 |
1
1. deepCopy : removed call to org/docx4j/wml/HpsMeasure::setVal → NO_COVERAGE |
szCs.setVal(src.getSz() |
| 169 | .getVal()); | |
| 170 |
1
1. deepCopy : removed call to org/docx4j/wml/RPr::setSzCs → NO_COVERAGE |
c.setSzCs(szCs); |
| 171 | } | |
| 172 |
1
1. deepCopy : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::deepCopy → NO_COVERAGE |
return c; |
| 173 | } | |
| 174 | } | |
Mutations | ||
| 28 |
1.1 |
|
| 30 |
1.1 |
|
| 32 |
1.1 |
|
| 34 |
1.1 |
|
| 36 |
1.1 |
|
| 38 |
1.1 |
|
| 41 |
1.1 |
|
| 51 |
1.1 |
|
| 52 |
1.1 |
|
| 53 |
1.1 |
|
| 66 |
1.1 |
|
| 67 |
1.1 |
|
| 68 |
1.1 |
|
| 70 |
1.1 |
|
| 71 |
1.1 |
|
| 76 |
1.1 |
|
| 77 |
1.1 |
|
| 82 |
1.1 |
|
| 95 |
1.1 |
|
| 104 |
1.1 |
|
| 108 |
1.1 |
|
| 110 |
1.1 |
|
| 112 |
1.1 |
|
| 114 |
1.1 |
|
| 117 |
1.1 |
|
| 123 |
1.1 |
|
| 124 |
1.1 |
|
| 125 |
1.1 |
|
| 127 |
1.1 |
|
| 132 |
1.1 |
|
| 133 |
1.1 |
|
| 134 |
1.1 |
|
| 136 |
1.1 |
|
| 141 |
1.1 |
|
| 154 |
1.1 |
|
| 156 |
1.1 |
|
| 158 |
1.1 |
|
| 160 |
1.1 |
|
| 162 |
1.1 |
|
| 164 |
1.1 |
|
| 166 |
1.1 |
|
| 168 |
1.1 |
|
| 170 |
1.1 |
|
| 172 |
1.1 |