All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alphasystem.docx4j.builder.wml.WmlPackageBuilder Maven / Gradle / Ivy

package com.alphasystem.docx4j.builder.wml;

import com.alphasystem.commons.SystemException;
import com.alphasystem.commons.util.AppUtil;
import jakarta.xml.bind.JAXBException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.docx4j.Docx4jProperties;
import org.docx4j.XmlUtils;
import org.docx4j.jaxb.Context;
import org.docx4j.model.structure.PageSizePaper;
import org.docx4j.openpackaging.contenttype.CTOverride;
import org.docx4j.openpackaging.contenttype.ContentTypeManager;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.DocumentSettingsPart;
import org.docx4j.openpackaging.parts.WordprocessingML.NumberingDefinitionsPart;
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
import org.docx4j.openpackaging.parts.relationships.Namespaces;
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
import org.docx4j.wml.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Objects;

import static com.alphasystem.docx4j.builder.wml.WmlBuilderFactory.BOOLEAN_DEFAULT_TRUE_TRUE;
import static com.alphasystem.docx4j.builder.wml.WmlBuilderFactory.getCTRelBuilder;
import static java.lang.String.format;
import static org.docx4j.openpackaging.contenttype.ContentTypes.WORDPROCESSINGML_DOCUMENT;
import static org.docx4j.openpackaging.contenttype.ContentTypes.WORDPROCESSINGML_DOCUMENT_MACROENABLED;

/**
 * Fluent API for creating WordML package.
 *
 * @author sali
 */
public class WmlPackageBuilder {

    private static final String DEFAULT_TEMPLATE_PATH = "META-INF/default.dotx";
    private static final String DEFAULT_LANDSCAPE_TEMPLATE = "META-INF/default-landscape.dotx";
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final NumberingHelper numberingHelper = NumberingHelper.getInstance();
    ;
    private WordprocessingMLPackage wmlPackage;

    public WmlPackageBuilder() throws SystemException {
        this(new WmlPackageInputs());
    }

    public WmlPackageBuilder(WmlPackageInputs inputs) throws SystemException {
        initPackage(inputs);
    }

    private void initPackage(WmlPackageInputs inputs) throws SystemException {
        final var pageSizePaper = inputs.getPageSizePaper();
        final var landscape = inputs.isLandscape();
        final var templatePath = inputs.getTemplatePath();
        final var loadDefaultStyles = inputs.isLoadDefaultStyles();
        if (Objects.nonNull(pageSizePaper)) {
            try {
                wmlPackage = WordprocessingMLPackage.createPackage(pageSizePaper, landscape);
            } catch (InvalidFormatException e) {
                logger.warn("Unable to load package from page zie: {}", pageSizePaper.value());
            }
        }
        if (Objects.nonNull(templatePath)) {
            try {
                wmlPackage = new TemplateLoader(templatePath).getPackage();
            } catch (SystemException e) {
                logger.warn("Unable to load package from template: {}", templatePath);
            }
        }
        if (Objects.isNull(wmlPackage)) {
            try {
                wmlPackage = WordprocessingMLPackage.createPackage();
            } catch (InvalidFormatException e) {
                throw new SystemException("Unable to create default package", e);
            }
        }
        if (loadDefaultStyles) {
            try {
                Styles styles = new StyleLoader(null, "META-INF/styles.xml").getStyles();
                wmlPackage.getMainDocumentPart().getStyleDefinitionsPart().setJaxbElement(styles);
            } catch (Exception ex) {
                logger.warn("Unable to load default styles", ex);
            }
        }
    }

    @SafeVarargs
    public final > WmlPackageBuilder multiLevelHeading(T... items) {
        final var numberId = numberingHelper.populate(items);
        for (int i = 0; i < items.length; i++) {
            final T currentItem = items[i];
            final String styleName = currentItem.getStyleName();
            final StyleDefinitionsPart styleDefinitionsPart = wmlPackage.getMainDocumentPart().getStyleDefinitionsPart();
            Style style = styleDefinitionsPart.getStyleById(styleName);
            if (style == null) {
                throw new RuntimeException(format("No style found with id \"%s\"", styleName));
            }
            final var styleBuilder = new StyleBuilder(style);
            final PPr pPr = styleBuilder.getObject().getPPr();
            boolean setPPr = pPr == null;
            final var pPrBuilder = new PPrBuilder(pPr);
            Long level = i <= 0 ? null : (long) i;
            final PPrBase.NumPr numPr = pPrBuilder.getNumPrBuilder().withNumId(numberId)
                    .withIlvl(level).getObject();
            pPrBuilder.withNumPr(numPr);
            if (setPPr) {
                styleBuilder.withPPr(pPrBuilder.getObject());
            }
        }
        return this;
    }

    public WmlPackageBuilder multiLevelHeading() {
        // copy Heading1, TOCHeading is based on it
        final StyleDefinitionsPart styleDefinitionsPart = wmlPackage.getMainDocumentPart().getStyleDefinitionsPart();
        final Style tocHeading = styleDefinitionsPart.getStyleById("TOCHeading");
        if (tocHeading != null) {
            String styleName = Headings.HEADING1.getStyleName();
            Style style = styleDefinitionsPart.getStyleById(styleName);
            if (style == null) {
                throw new RuntimeException(format("No style found with id \"%s\"", styleName));
            }
            StyleBuilder styleBuilder = new StyleBuilder(style, null);
            styleName = format("_%s", styleName);
            styleBuilder.withStyleId(styleName).withName(format("_%s", Headings.HEADING1.getName()))
                    .withUnhideWhenUsed((BooleanDefaultTrue) null).withSemiHidden((BooleanDefaultTrue) null)
                    .withHidden(BOOLEAN_DEFAULT_TRUE_TRUE);
            try {
                styleDefinitionsPart.getContents().getStyle().add(styleBuilder.getObject());
                new StyleBuilder(tocHeading).withBasedOn(styleName);
            } catch (Docx4JException e) {
                logger.warn("Unable to add style \"{}\" into style gallery.", styleName, e);
            }
        }
        return multiLevelHeading(Headings.HEADING1, Headings.HEADING2, Headings.HEADING3, Headings.HEADING4, Headings.HEADING5);
    }

    public WmlPackageBuilder styles(String... paths) throws SystemException {
        final StyleDefinitionsPart styleDefinitionsPart = wmlPackage.getMainDocumentPart().getStyleDefinitionsPart();
        final Styles styles;
        try {
            styles = new StyleLoader(styleDefinitionsPart.getContents(), paths).getStyles();
        } catch (Docx4JException e) {
            throw new SystemException(e.getMessage(), e);
        }
        styleDefinitionsPart.setJaxbElement(styles);
        return this;
    }

    public WmlPackageBuilder styles(Styles... styles) throws SystemException {
        if (ArrayUtils.isEmpty(styles)) {
            return this;
        }
        final StyleDefinitionsPart styleDefinitionsPart = wmlPackage.getMainDocumentPart().getStyleDefinitionsPart();
        try {
            for (Styles style : styles) {
                styleDefinitionsPart.getContents().getStyle().addAll(style.getStyle());
            }
        } catch (Docx4JException e) {
            throw new SystemException(e);
        }
        return this;
    }

    public WordprocessingMLPackage getPackage() throws SystemException {
        final var mainDocumentPart = wmlPackage.getMainDocumentPart();
        mainDocumentPart.getContent().clear();
        try {
            final var ndp = new NumberingDefinitionsPart();
            ndp.setJaxbElement(numberingHelper.getNumbering());
            mainDocumentPart.addTargetPart(ndp);
        } catch (InvalidFormatException e) {
            throw new SystemException(e);
        }
        return wmlPackage;
    }

    private static class TemplateLoader {

        private WordprocessingMLPackage wmlPackage;
        private final String contentType;

        TemplateLoader(String templatePath) throws SystemException {
            templatePath = StringUtils.isBlank(templatePath) ? DEFAULT_TEMPLATE_PATH : templatePath;
            contentType = templatePath.endsWith("dotm") /* macro enabled? */ ? WORDPROCESSINGML_DOCUMENT_MACROENABLED
                    : WORDPROCESSINGML_DOCUMENT;
            loadTemplate(templatePath);
        }

        public WordprocessingMLPackage getPackage() {
            return wmlPackage;
        }

        private void loadTemplate(String templatePath) throws SystemException {
            final var wmlPackages = AppUtil.processResource(templatePath, this::loadTemplate);
            if (wmlPackages.isEmpty()) {
                return;
            }
            wmlPackage = wmlPackages.get(0);
        }

        private WordprocessingMLPackage loadTemplate(URL url) {
            try (final var is = url.openStream()) {
                final var wmlPackage = WordprocessingMLPackage.load(is);
                // Replace dotx content type with docx
                ContentTypeManager ctm = wmlPackage.getContentTypeManager();

                // Get 
                CTOverride override = ctm.getOverrideContentType().get(new URI("/word/document.xml")); // note this assumption

                override.setContentType(contentType);

                // Create settings part, and init content
                final var dsp = new DocumentSettingsPart();
                CTSettings settings = Context.getWmlObjectFactory().createCTSettings();
                dsp.setJaxbElement(settings);
                wmlPackage.getMainDocumentPart().addTargetPart(dsp);

                // Create external rel
                RelationshipsPart rp = RelationshipsPart.createRelationshipsPartForPart(dsp);
                org.docx4j.relationships.Relationship rel = new org.docx4j.relationships.ObjectFactory().createRelationship();
                rel.setType(Namespaces.ATTACHED_TEMPLATE);
                rel.setTarget(url.toExternalForm());
                rel.setTargetMode("External");
                rp.addRelationship(rel); // addRelationship sets the rel's @Id

                settings.setAttachedTemplate(getCTRelBuilder().withId(rel.getId()).getObject());
                return wmlPackage;
            } catch (IOException | Docx4JException | URISyntaxException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static class StyleLoader {

        private Styles styles;

        StyleLoader(Styles styles, String... paths) throws SystemException {
            this.styles = styles;
            loadStyles(paths);
        }

        public Styles getStyles() {
            return styles;
        }

        private void loadStyles(String... paths) throws SystemException {
            if (Objects.isNull(paths)) {
                return;
            }
            for (String path : paths) {
                final var newStyles = AppUtil.processResource(path, StyleLoader::unmarshal);
                newStyles.forEach(otherStyles -> {
                    if (Objects.isNull(styles)) {
                        styles = otherStyles;
                    } else {
                        styles.getStyle().addAll(otherStyles.getStyle());
                    }
                });
            }
        }

        private static Styles unmarshal(URL url) {
            try (final var is = url.openStream()) {
                return (Styles) XmlUtils.unmarshal(is);
            } catch (IOException | JAXBException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static class WmlPackageInputs {

        private String templatePath;
        private  boolean loadDefaultStyles;
        private boolean landscape;
        private PageSizePaper pageSizePaper;

        public WmlPackageInputs() {
        }

        public String getTemplatePath() {
            return templatePath;
        }

        public boolean isLoadDefaultStyles() {
            return loadDefaultStyles;
        }

        public boolean isLandscape() {
            return landscape;
        }

        public PageSizePaper getPageSizePaper() {
            return pageSizePaper;
        }

        public WmlPackageInputs withTemplatePath(String templatePath) {
            this.templatePath = templatePath;
            return this;
        }

        public WmlPackageInputs useDefaultTemplate() {
            this.templatePath = DEFAULT_TEMPLATE_PATH;
            return this;
        }

        public WmlPackageInputs useDefaultStyles() {
            this.loadDefaultStyles = true;
            return this;
        }

        public WmlPackageInputs useLandscape() {
            this.templatePath = DEFAULT_LANDSCAPE_TEMPLATE;
            this.landscape = true;
            return this;
        }

        public WmlPackageInputs withPageSizePaper(PageSizePaper pageSizePaper) {
            this.pageSizePaper = pageSizePaper;
            return this;
        }

        public WmlPackageInputs withPageSizePaper(String value) {
            final var paperSize = Docx4jProperties.getProperties().getProperty("docx4j.PageSize", value);
            try {
                pageSizePaper = PageSizePaper.valueOf(paperSize);
            } catch (IllegalArgumentException ex) {
                System.err.printf("Invalid page size value: %s%n", value);
            }
            return this;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy