| 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 | import org.jspecify.annotations.Nullable; | |
| 8 | ||
| 9 | import java.math.BigInteger; | |
| 10 | import java.util.List; | |
| 11 | import java.util.function.Function; | |
| 12 | ||
| 13 | import static pro.verron.officestamper.asciidoc.AsciiDocModel.*; | |
| 14 | ||
| 15 | /// Renders [AsciiDocModel] into a [WordprocessingMLPackage] using docx4j. | |
| 16 | public final class AsciiDocToDocx | |
| 17 | implements Function<AsciiDocModel, WordprocessingMLPackage> { | |
| 18 | ||
| 19 | private static P createHeading(ObjectFactory factory, Heading heading) { | |
| 20 | P p = factory.createP(); | |
| 21 | PPr ppr = factory.createPPr(); | |
| 22 | PPrBase.PStyle pStyle = factory.createPPrBasePStyle(); | |
| 23 |
1
1. createHeading : removed call to org/docx4j/wml/PPrBase$PStyle::setVal → NO_COVERAGE |
pStyle.setVal("Heading" + heading.level()); |
| 24 |
1
1. createHeading : removed call to org/docx4j/wml/PPr::setPStyle → NO_COVERAGE |
ppr.setPStyle(pStyle); |
| 25 |
1
1. createHeading : removed call to org/docx4j/wml/P::setPPr → NO_COVERAGE |
p.setPPr(ppr); |
| 26 | ||
| 27 | RPr headingRunPr = factory.createRPr(); | |
| 28 | // Increase size a bit relative to level if heading styles are missing | |
| 29 | HpsMeasure sz = factory.createHpsMeasure(); | |
| 30 | int base = switch (heading.level()) { | |
| 31 | case 1 -> 32; // 16pt | |
| 32 | case 2 -> 28; | |
| 33 | case 3 -> 26; | |
| 34 | case 4 -> 24; | |
| 35 | case 5 -> 22; | |
| 36 | default -> 20; | |
| 37 | }; | |
| 38 |
1
1. createHeading : removed call to org/docx4j/wml/HpsMeasure::setVal → NO_COVERAGE |
sz.setVal(BigInteger.valueOf(base)); |
| 39 |
1
1. createHeading : removed call to org/docx4j/wml/RPr::setSz → NO_COVERAGE |
headingRunPr.setSz(sz); |
| 40 |
1
1. createHeading : removed call to org/docx4j/wml/RPr::setSzCs → NO_COVERAGE |
headingRunPr.setSzCs(sz); |
| 41 | ||
| 42 |
1
1. createHeading : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addInlines → NO_COVERAGE |
addInlines(factory, p, heading.inlines(), headingRunPr); |
| 43 |
1
1. createHeading : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createHeading → NO_COVERAGE |
return p; |
| 44 | } | |
| 45 | ||
| 46 | private static P createParagraph(ObjectFactory factory, Paragraph paragraph) { | |
| 47 | P p = factory.createP(); | |
| 48 |
1
1. createParagraph : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addInlines → KILLED |
addInlines(factory, p, paragraph.inlines(), null); |
| 49 |
1
1. createParagraph : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createParagraph → KILLED |
return p; |
| 50 | } | |
| 51 | ||
| 52 | private static void addInlines(ObjectFactory factory, P p, List<Inline> inlines, @Nullable RPr base) { | |
| 53 | for (Inline inline : inlines) { | |
| 54 |
1
1. addInlines : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::emitInline → KILLED |
emitInline(factory, p, inline, base); |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | private static Tbl createTable(ObjectFactory factory, Table table) { | |
| 59 | Tbl tbl = factory.createTbl(); | |
| 60 | // Minimal table without explicit grid; Word will auto-fit columns | |
| 61 | for (Row row : table.rows()) { | |
| 62 | Tr tr = factory.createTr(); | |
| 63 | for (Cell cell : row.cells()) { | |
| 64 | Tc tc = factory.createTc(); | |
| 65 | for (Block block : cell.blocks()) { | |
| 66 |
1
1. createTable : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addBlock → KILLED |
addBlock(factory, tc.getContent(), block); |
| 67 | } | |
| 68 | tr.getContent() | |
| 69 | .add(tc); | |
| 70 | } | |
| 71 | tbl.getContent() | |
| 72 | .add(tr); | |
| 73 | } | |
| 74 |
1
1. createTable : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createTable → KILLED |
return tbl; |
| 75 | } | |
| 76 | ||
| 77 | private static void addBlock(ObjectFactory factory, List<Object> content, Block block) | |
| 78 | throws UnsupportedOperationException { | |
| 79 | switch (block) { | |
| 80 | case Heading h -> content.add(createHeading(factory, h)); | |
| 81 | case Paragraph p -> content.add(createParagraph(factory, p)); | |
| 82 | case Table t -> content.add(createTable(factory, t)); | |
| 83 | case UnorderedList(List<ListItem> items1) -> { | |
| 84 | for (ListItem item : items1) { | |
| 85 | content.add(createListItem(factory, item, "* ")); | |
| 86 | } | |
| 87 | } | |
| 88 | case OrderedList(List<ListItem> items) -> { | |
| 89 | int i = 1; | |
| 90 | for (ListItem item : items) { | |
| 91 |
1
1. addBlock : Changed increment from 1 to -1 → NO_COVERAGE |
content.add(createListItem(factory, item, (i++) + ". ")); |
| 92 | } | |
| 93 | } | |
| 94 | case Blockquote b -> content.add(createBlockquote(factory, b)); | |
| 95 | case CodeBlock cb -> content.add(createCodeBlock(factory, cb)); | |
| 96 | case ImageBlock ib -> content.add(createImageBlock(factory, ib)); | |
| 97 | case Break _ -> throw new java.lang.UnsupportedOperationException("Breaks are not supported"); | |
| 98 | case CommentLine _ -> throw new UnsupportedOperationException("Comments are not supported"); | |
| 99 | case OpenBlock _ -> throw new UnsupportedOperationException("Open blocks are not supported"); | |
| 100 | case MacroBlock macroBlock -> throw new UnsupportedOperationException("Macro blocks are not supported"); | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | private static P createListItem(ObjectFactory factory, ListItem item, String prefix) { | |
| 105 | P p = factory.createP(); | |
| 106 | R r = factory.createR(); | |
| 107 | org.docx4j.wml.Text t = factory.createText(); | |
| 108 |
1
1. createListItem : removed call to org/docx4j/wml/Text::setValue → NO_COVERAGE |
t.setValue(prefix); |
| 109 | r.getContent() | |
| 110 | .add(t); | |
| 111 | p.getContent() | |
| 112 | .add(r); | |
| 113 |
1
1. createListItem : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addInlines → NO_COVERAGE |
addInlines(factory, p, item.inlines(), null); |
| 114 |
1
1. createListItem : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createListItem → NO_COVERAGE |
return p; |
| 115 | } | |
| 116 | ||
| 117 | private static P createBlockquote(ObjectFactory factory, Blockquote blockquote) { | |
| 118 | P p = factory.createP(); | |
| 119 | PPr ppr = factory.createPPr(); | |
| 120 | PPrBase.Ind ind = factory.createPPrBaseInd(); | |
| 121 |
1
1. createBlockquote : removed call to org/docx4j/wml/PPrBase$Ind::setLeft → NO_COVERAGE |
ind.setLeft(BigInteger.valueOf(720)); // 0.5 inch |
| 122 |
1
1. createBlockquote : removed call to org/docx4j/wml/PPr::setInd → NO_COVERAGE |
ppr.setInd(ind); |
| 123 |
1
1. createBlockquote : removed call to org/docx4j/wml/P::setPPr → NO_COVERAGE |
p.setPPr(ppr); |
| 124 |
1
1. createBlockquote : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addInlines → NO_COVERAGE |
addInlines(factory, p, blockquote.inlines(), null); |
| 125 |
1
1. createBlockquote : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createBlockquote → NO_COVERAGE |
return p; |
| 126 | } | |
| 127 | ||
| 128 | private static P createCodeBlock(ObjectFactory factory, CodeBlock codeBlock) { | |
| 129 | P p = factory.createP(); | |
| 130 | RPr rpr = factory.createRPr(); | |
| 131 | RFonts fonts = factory.createRFonts(); | |
| 132 |
1
1. createCodeBlock : removed call to org/docx4j/wml/RFonts::setAscii → NO_COVERAGE |
fonts.setAscii("Courier New"); |
| 133 |
1
1. createCodeBlock : removed call to org/docx4j/wml/RFonts::setHAnsi → NO_COVERAGE |
fonts.setHAnsi("Courier New"); |
| 134 |
1
1. createCodeBlock : removed call to org/docx4j/wml/RPr::setRFonts → NO_COVERAGE |
rpr.setRFonts(fonts); |
| 135 | ||
| 136 | String[] lines = codeBlock.content() | |
| 137 | .split("\n"); | |
| 138 |
2
1. createCodeBlock : changed conditional boundary → NO_COVERAGE 2. createCodeBlock : negated conditional → NO_COVERAGE |
for (int i = 0; i < lines.length; i++) { |
| 139 | R r = factory.createR(); | |
| 140 |
1
1. createCodeBlock : removed call to org/docx4j/wml/R::setRPr → NO_COVERAGE |
r.setRPr(rpr); |
| 141 | org.docx4j.wml.Text t = factory.createText(); | |
| 142 |
1
1. createCodeBlock : removed call to org/docx4j/wml/Text::setValue → NO_COVERAGE |
t.setValue(lines[i]); |
| 143 |
1
1. createCodeBlock : removed call to org/docx4j/wml/Text::setSpace → NO_COVERAGE |
t.setSpace("preserve"); |
| 144 | r.getContent() | |
| 145 | .add(t); | |
| 146 |
3
1. createCodeBlock : negated conditional → NO_COVERAGE 2. createCodeBlock : Replaced integer subtraction with addition → NO_COVERAGE 3. createCodeBlock : changed conditional boundary → NO_COVERAGE |
if (i < lines.length - 1) { |
| 147 | r.getContent() | |
| 148 | .add(factory.createBr()); | |
| 149 | } | |
| 150 | p.getContent() | |
| 151 | .add(r); | |
| 152 | } | |
| 153 |
1
1. createCodeBlock : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createCodeBlock → NO_COVERAGE |
return p; |
| 154 | } | |
| 155 | ||
| 156 | private static P createImageBlock(ObjectFactory factory, ImageBlock imageBlock) { | |
| 157 | P p = factory.createP(); | |
| 158 | R r = factory.createR(); | |
| 159 | org.docx4j.wml.Text t = factory.createText(); | |
| 160 |
1
1. createImageBlock : removed call to org/docx4j/wml/Text::setValue → NO_COVERAGE |
t.setValue("[Image: " + imageBlock.url() + " - " + imageBlock.altText() + "]"); |
| 161 | r.getContent() | |
| 162 | .add(t); | |
| 163 | p.getContent() | |
| 164 | .add(r); | |
| 165 |
1
1. createImageBlock : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::createImageBlock → NO_COVERAGE |
return p; |
| 166 | } | |
| 167 | ||
| 168 | private static void emitInline(ObjectFactory factory, P p, Inline inline, @Nullable RPr base) { | |
| 169 | switch (inline) { | |
| 170 | case AsciiDocModel.Text(String text) -> { | |
| 171 |
1
1. emitInline : negated conditional → KILLED |
RPr rpr = base != null ? deepCopy(factory, base) : factory.createRPr(); |
| 172 | String[] lines = text.split("\n", -1); | |
| 173 |
2
1. emitInline : negated conditional → KILLED 2. emitInline : changed conditional boundary → KILLED |
for (int i = 0; i < lines.length; i++) { |
| 174 |
1
1. emitInline : negated conditional → KILLED |
if (!lines[i].isEmpty()) { |
| 175 | R r = factory.createR(); | |
| 176 |
1
1. emitInline : removed call to org/docx4j/wml/R::setRPr → SURVIVED |
r.setRPr(rpr); |
| 177 | org.docx4j.wml.Text tx = factory.createText(); | |
| 178 |
1
1. emitInline : removed call to org/docx4j/wml/Text::setValue → KILLED |
tx.setValue(lines[i]); |
| 179 |
1
1. emitInline : removed call to org/docx4j/wml/Text::setSpace → SURVIVED |
tx.setSpace("preserve"); |
| 180 | r.getContent() | |
| 181 | .add(tx); | |
| 182 | p.getContent() | |
| 183 | .add(r); | |
| 184 | } | |
| 185 |
3
1. emitInline : changed conditional boundary → KILLED 2. emitInline : Replaced integer subtraction with addition → KILLED 3. emitInline : negated conditional → KILLED |
if (i < lines.length - 1) { |
| 186 | R r = factory.createR(); | |
| 187 |
1
1. emitInline : removed call to org/docx4j/wml/R::setRPr → NO_COVERAGE |
r.setRPr(rpr); |
| 188 | r.getContent() | |
| 189 | .add(factory.createBr()); | |
| 190 | p.getContent() | |
| 191 | .add(r); | |
| 192 | } | |
| 193 | } | |
| 194 | return; | |
| 195 | } | |
| 196 | case Bold(List<Inline> children) -> { | |
| 197 |
1
1. emitInline : negated conditional → NO_COVERAGE |
RPr next = base != null ? deepCopy(factory, base) : factory.createRPr(); |
| 198 |
1
1. emitInline : removed call to org/docx4j/wml/RPr::setB → NO_COVERAGE |
next.setB(new BooleanDefaultTrue()); |
| 199 | for (Inline child : children) { | |
| 200 |
1
1. emitInline : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::emitInline → NO_COVERAGE |
emitInline(factory, p, child, next); |
| 201 | } | |
| 202 | return; | |
| 203 | } | |
| 204 | case Italic(List<Inline> children) -> { | |
| 205 |
1
1. emitInline : negated conditional → NO_COVERAGE |
RPr next = base != null ? deepCopy(factory, base) : factory.createRPr(); |
| 206 |
1
1. emitInline : removed call to org/docx4j/wml/RPr::setI → NO_COVERAGE |
next.setI(new BooleanDefaultTrue()); |
| 207 | for (Inline child : children) { | |
| 208 |
1
1. emitInline : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::emitInline → NO_COVERAGE |
emitInline(factory, p, child, next); |
| 209 | } | |
| 210 | return; | |
| 211 | } | |
| 212 | case Tab _ -> { | |
| 213 | R r = factory.createR(); | |
| 214 | R.Tab tab = factory.createRTab(); | |
| 215 | r.getContent() | |
| 216 | .add(tab); | |
| 217 | p.getContent() | |
| 218 | .add(r); | |
| 219 | } | |
| 220 | default -> { /* DO NOTHING */ } | |
| 221 | } | |
| 222 | ||
| 223 |
1
1. emitInline : negated conditional → NO_COVERAGE |
if (inline instanceof Link link) { |
| 224 | R r = factory.createR(); | |
| 225 |
1
1. emitInline : negated conditional → NO_COVERAGE |
RPr rpr = base != null ? deepCopy(factory, base) : factory.createRPr(); |
| 226 | Color color = factory.createColor(); | |
| 227 |
1
1. emitInline : removed call to org/docx4j/wml/Color::setVal → NO_COVERAGE |
color.setVal("0000FF"); |
| 228 |
1
1. emitInline : removed call to org/docx4j/wml/RPr::setColor → NO_COVERAGE |
rpr.setColor(color); |
| 229 | U u = factory.createU(); | |
| 230 |
1
1. emitInline : removed call to org/docx4j/wml/U::setVal → NO_COVERAGE |
u.setVal(UnderlineEnumeration.SINGLE); |
| 231 |
1
1. emitInline : removed call to org/docx4j/wml/RPr::setU → NO_COVERAGE |
rpr.setU(u); |
| 232 |
1
1. emitInline : removed call to org/docx4j/wml/R::setRPr → NO_COVERAGE |
r.setRPr(rpr); |
| 233 | org.docx4j.wml.Text t = factory.createText(); | |
| 234 |
1
1. emitInline : removed call to org/docx4j/wml/Text::setValue → NO_COVERAGE |
t.setValue(link.text()); |
| 235 | r.getContent() | |
| 236 | .add(t); | |
| 237 | p.getContent() | |
| 238 | .add(r); | |
| 239 | } | |
| 240 | ||
| 241 |
1
1. emitInline : negated conditional → NO_COVERAGE |
if (inline instanceof InlineImage ii) { |
| 242 | R r = factory.createR(); | |
| 243 | org.docx4j.wml.Text t = factory.createText(); | |
| 244 |
1
1. emitInline : removed call to org/docx4j/wml/Text::setValue → NO_COVERAGE |
t.setValue("[Image: " + ii.path() + "]"); |
| 245 | r.getContent() | |
| 246 | .add(t); | |
| 247 | p.getContent() | |
| 248 | .add(r); | |
| 249 | } | |
| 250 | } | |
| 251 | ||
| 252 | private static RPr deepCopy(ObjectFactory factory, RPr src) { | |
| 253 | // Minimal copy of relevant props; docx4j doesn't offer a trivial clone here. | |
| 254 | RPr c = factory.createRPr(); | |
| 255 |
1
1. deepCopy : negated conditional → NO_COVERAGE |
if (src.getB() != null) { |
| 256 | BooleanDefaultTrue b = new BooleanDefaultTrue(); | |
| 257 |
1
1. deepCopy : removed call to org/docx4j/wml/RPr::setB → NO_COVERAGE |
c.setB(b); |
| 258 | } | |
| 259 |
1
1. deepCopy : negated conditional → NO_COVERAGE |
if (src.getI() != null) { |
| 260 | BooleanDefaultTrue i = new BooleanDefaultTrue(); | |
| 261 |
1
1. deepCopy : removed call to org/docx4j/wml/RPr::setI → NO_COVERAGE |
c.setI(i); |
| 262 | } | |
| 263 |
1
1. deepCopy : negated conditional → NO_COVERAGE |
if (src.getSz() != null) { |
| 264 | HpsMeasure sz = factory.createHpsMeasure(); | |
| 265 |
1
1. deepCopy : removed call to org/docx4j/wml/HpsMeasure::setVal → NO_COVERAGE |
sz.setVal(src.getSz() |
| 266 | .getVal()); | |
| 267 |
1
1. deepCopy : removed call to org/docx4j/wml/RPr::setSz → NO_COVERAGE |
c.setSz(sz); |
| 268 | HpsMeasure szCs = factory.createHpsMeasure(); | |
| 269 |
1
1. deepCopy : removed call to org/docx4j/wml/HpsMeasure::setVal → NO_COVERAGE |
szCs.setVal(src.getSz() |
| 270 | .getVal()); | |
| 271 |
1
1. deepCopy : removed call to org/docx4j/wml/RPr::setSzCs → NO_COVERAGE |
c.setSzCs(szCs); |
| 272 | } | |
| 273 |
1
1. deepCopy : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::deepCopy → NO_COVERAGE |
return c; |
| 274 | } | |
| 275 | ||
| 276 | /// Creates a new WordprocessingMLPackage and fills it with content from the model. | |
| 277 | /// | |
| 278 | /// @param model parsed AsciiDoc model | |
| 279 | /// | |
| 280 | /// @return package containing the rendered document | |
| 281 | public WordprocessingMLPackage apply(AsciiDocModel model) { | |
| 282 | try { | |
| 283 | var pkg = WordprocessingMLPackage.createPackage(); | |
| 284 | var factory = Context.getWmlObjectFactory(); | |
| 285 | pkg.getMainDocumentPart() | |
| 286 | .getContent() | |
| 287 |
1
1. apply : removed call to java/util/List::clear → SURVIVED |
.clear(); |
| 288 | ||
| 289 | for (Block block : model.getBlocks()) { | |
| 290 |
1
1. apply : removed call to pro/verron/officestamper/asciidoc/AsciiDocToDocx::addBlock → KILLED |
addBlock(factory, |
| 291 | pkg.getMainDocumentPart() | |
| 292 | .getContent(), | |
| 293 | block); | |
| 294 | } | |
| 295 |
1
1. apply : replaced return value with null for pro/verron/officestamper/asciidoc/AsciiDocToDocx::apply → KILLED |
return pkg; |
| 296 | } catch (Docx4JException e) { | |
| 297 | throw new IllegalStateException("Unable to create WordprocessingMLPackage", e); | |
| 298 | } | |
| 299 | } | |
| 300 | } | |
Mutations | ||
| 23 |
1.1 |
|
| 24 |
1.1 |
|
| 25 |
1.1 |
|
| 38 |
1.1 |
|
| 39 |
1.1 |
|
| 40 |
1.1 |
|
| 42 |
1.1 |
|
| 43 |
1.1 |
|
| 48 |
1.1 |
|
| 49 |
1.1 |
|
| 54 |
1.1 |
|
| 66 |
1.1 |
|
| 74 |
1.1 |
|
| 91 |
1.1 |
|
| 108 |
1.1 |
|
| 113 |
1.1 |
|
| 114 |
1.1 |
|
| 121 |
1.1 |
|
| 122 |
1.1 |
|
| 123 |
1.1 |
|
| 124 |
1.1 |
|
| 125 |
1.1 |
|
| 132 |
1.1 |
|
| 133 |
1.1 |
|
| 134 |
1.1 |
|
| 138 |
1.1 2.2 |
|
| 140 |
1.1 |
|
| 142 |
1.1 |
|
| 143 |
1.1 |
|
| 146 |
1.1 2.2 3.3 |
|
| 153 |
1.1 |
|
| 160 |
1.1 |
|
| 165 |
1.1 |
|
| 171 |
1.1 |
|
| 173 |
1.1 2.2 |
|
| 174 |
1.1 |
|
| 176 |
1.1 |
|
| 178 |
1.1 |
|
| 179 |
1.1 |
|
| 185 |
1.1 2.2 3.3 |
|
| 187 |
1.1 |
|
| 197 |
1.1 |
|
| 198 |
1.1 |
|
| 200 |
1.1 |
|
| 205 |
1.1 |
|
| 206 |
1.1 |
|
| 208 |
1.1 |
|
| 223 |
1.1 |
|
| 225 |
1.1 |
|
| 227 |
1.1 |
|
| 228 |
1.1 |
|
| 230 |
1.1 |
|
| 231 |
1.1 |
|
| 232 |
1.1 |
|
| 234 |
1.1 |
|
| 241 |
1.1 |
|
| 244 |
1.1 |
|
| 255 |
1.1 |
|
| 257 |
1.1 |
|
| 259 |
1.1 |
|
| 261 |
1.1 |
|
| 263 |
1.1 |
|
| 265 |
1.1 |
|
| 267 |
1.1 |
|
| 269 |
1.1 |
|
| 271 |
1.1 |
|
| 273 |
1.1 |
|
| 287 |
1.1 |
|
| 290 |
1.1 |
|
| 295 |
1.1 |