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

xyz.proteanbear.template.PbPOIWordTemplate Maven / Gradle / Ivy

The newest version!
package xyz.proteanbear.template;

import org.apache.poi.common.usermodel.PictureType;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.proteanbear.template.annotation.PbPOIWordVariable;
import xyz.proteanbear.template.exception.FileSuffixNotSupportException;
import xyz.proteanbear.template.utils.ClassUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The tools is created for easy use of Apache POI.
 * This tool is used for reading and writing in Word.
 *
 * @author ProteanBear
 */
public class PbPOIWordTemplate
{
    private static final Logger logger = LoggerFactory.getLogger(PbPOIWordTemplate.class);

    /**
     * Record image insertion related content
     */
    public static class Image
    {
        private String url;
        private final PictureType type;
        private final String description;
        private final int width;
        private final int height;
        private final PbPOIWordVariable.AdaptionType adaption;

        public Image(
                String url, PictureType type, String description, int width, int height,
                PbPOIWordVariable.AdaptionType adaption
        )
        {
            this.url = url;
            this.type = type;
            this.description = description;
            this.width = width;
            this.height = height;
            this.adaption = adaption;
        }

        public String getUrl()
        {
            return url;
        }

        public void setUrl(String url)
        {
            this.url = url;
        }

        public PictureType getType()
        {
            return type;
        }

        public String getDescription()
        {
            return description;
        }

        public int getWidth()
        {
            return width;
        }

        public int getHeight()
        {
            return height;
        }

        public PbPOIWordVariable.AdaptionType getAdaption()
        {
            return adaption;
        }
    }

    /**
     * The start identifier of the replacement variable
     */
    private String variableStart = "{";

    /**
     * The end identifier of the replacement variable
     */
    private String variableEnd = "}";

    /**
     * The start identifier of the replacement table row loop data
     */
    private String loopStart = "[";

    /**
     * The end identifier of the replacement table row loop data
     */
    private String loopEnd = "]";

    /**
     * Set the start identifier of the replacement variable
     *
     * @param variableStart The start identifier of the replacement variable
     * @return current object
     */
    public PbPOIWordTemplate setVariableStart(String variableStart)
    {
        this.variableStart = variableStart;
        return this;
    }

    /**
     * Set the end identifier of the replacement variable
     *
     * @param variableEnd The start identifier of the replacement variable
     * @return current object
     */
    public PbPOIWordTemplate setVariableEnd(String variableEnd)
    {
        this.variableEnd = variableEnd;
        return this;
    }

    /**
     * Set the start identifier of the replacement table row loop data
     *
     * @param loopStart The start identifier of the replacement table row loop data
     * @return current object
     */
    public PbPOIWordTemplate setLoopStart(String loopStart)
    {
        this.loopStart = loopStart;
        return this;
    }

    /**
     * Set the end identifier of the replacement table row loop data
     *
     * @param loopEnd The start identifier of the replacement table row loop data
     * @return current object
     */
    public PbPOIWordTemplate setLoopEnd(String loopEnd)
    {
        this.loopEnd = loopEnd;
        return this;
    }

    /**
     * After reading the specified WORD template and replacing the contents of the template, a new WORD file is generated
     *
     * @param templateFile file must exist
     * @param toFile       new file for generated
     * @param data         Replace data objects with template variables declared by annotations
     * @throws IOException                   io exception
     * @throws NoSuchMethodException         No such method
     * @throws InvocationTargetException     Invocation target
     * @throws IllegalAccessException        Illegal access
     * @throws FileSuffixNotSupportException file suffix is not supported
     */
    public void writeTo(File templateFile, File toFile, Object data)
            throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException,
            FileSuffixNotSupportException
    {
        //File exists, delete the old file
        if (toFile.exists()) toFile.delete();
        toFile.createNewFile();

        writeTo(
                templateFile,
                Files.newOutputStream(toFile.toPath()),
                data
        );
    }

    /**
     * After reading the specified WORD template and replacing the contents of the template, a new WORD file is generated
     *
     * @param templateFile file must exist
     * @param outputStream Output stream
     * @param data         Replace data objects with template variables declared by annotations
     * @throws IOException               io exception
     * @throws NoSuchMethodException     No such method
     * @throws InvocationTargetException Invocation target
     * @throws IllegalAccessException    Illegal access
     */
    public void writeTo(File templateFile, OutputStream outputStream, Object data)
            throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
    {
        //Read Word template file
        if (!templateFile.exists()) throw new IOException("Template file is not exist");

        //Convert data objects to mapping dictionaries based on annotations
        Map dataMap = ClassUtils.dataMapBy(PbPOIWordVariable.class, data, new ClassUtils.Wrapper()
        {
            @Override
            public  Object wrap(T annotation, Object data)
            {
                PbPOIWordVariable variable = (PbPOIWordVariable) annotation;
                //Image path
                return variable.isImagePath()
                       ? new PbPOIWordTemplate.Image(
                        String.valueOf(data),
                        variable.imageType(),
                        variable.imageDescription(),
                        variable.imageWidth(),
                        variable.imageHeight(),
                        variable.adaption()
                ) : data;
            }
        });

        try (XWPFDocument template = new XWPFDocument(Files.newInputStream(templateFile.toPath())))
        {
            //Get ordinary paragraphs and replace all variables
            List paragraphList = template.getParagraphs();
            //Replace variables in the content
            paragraphList.forEach(paragraph -> replaceParagraph(paragraph, dataMap));

            //Get table paragraphs and replace
            List tables = template.getTables();
            //Replace variables in the tables
            tables.forEach(table -> replaceTableParagraph(table, dataMap));

            //Write Word file
            template.write(outputStream);
        }
    }

    /**
     * Replace the corresponding variable in the field content
     *
     * @param paragraph include a lot of runs
     * @param dataMap   data map
     */
    private void replaceParagraph(XWPFParagraph paragraph, Map dataMap)
    {
        //Search variable
        TextSegment start = paragraph.searchText(variableStart, new PositionInParagraph());
        if (start == null) return;
        TextSegment end = paragraph.searchText(
                variableEnd,
                new PositionInParagraph(start.getEndRun(), start.getEndText(), start.getEndChar())
        );
        if (end == null) return;

        //Find the replaced variable and tag
        String variableKey = paragraph.getText(new TextSegment(
                start.getBeginRun(), end.getEndRun(),
                start.getBeginText(), end.getEndText(),
                start.getBeginChar(), end.getEndChar()
        ));
        variableKey = variableKey.replace(variableStart, "")
                                 .replace(variableEnd, "");
        Object variable = dataMap.getOrDefault(variableKey, "");

        //Image
        if (variable instanceof Image)
        {
            replacePictureParagraph(paragraph, start, end, (Image) variable, variableEnd.length());
        }
        else
        {
            String variableValue = (variable == null ? "" : String.valueOf(variable));
            //Traversing Runs, replacing the contents of the variable
            replaceParagraph(paragraph, start, end, variableValue, variableEnd.length());
        }

        //Check again if there are any variables in the paragraph to replace
        replaceParagraph(paragraph, dataMap);
    }

    /**
     * Replace the corresponding variable in the field content
     *
     * @param paragraph         include a lot of runs
     * @param start             Replace the starting fragment position
     * @param end               Replace the ending fragment position
     * @param variableValue     Replaced content
     * @param variableEndLength Replacement terminator length
     */
    private void replaceParagraph(
            XWPFParagraph paragraph, TextSegment start, TextSegment end
            , String variableValue, int variableEndLength
    )
    {
        //Traversing Runs, replacing the contents of the variable
        List runs = paragraph.getRuns();
        XWPFRun runStart = runs.get(start.getBeginRun());
        XWPFRun runEnd = runs.get(end.getEndRun());
        String textBefore = runStart.text()
                                    .substring(0, start.getBeginChar());
        String textAfter = runEnd.text()
                                 .substring(end.getEndChar() + variableEndLength);
        StringBuilder builder = new StringBuilder();
        //If start-run and end-run is in the same sun
        if (start.getBeginRun() == end.getEndRun())
        {
            runStart.setText(
                    builder.append(textBefore)
                           .append(variableValue)
                           .append(textAfter)
                           .toString()
                    , 0);
        }
        //If start-run and end-run is in the different suns
        //Insert a new run after start-run
        else
        {
            runStart.setText(
                    builder.append(textBefore)
                           .append(variableValue)
                           .toString()
                    , 0);
            runEnd.setText(textAfter, 0);
            for (int pos = end.getEndRun(); pos > start.getBeginRun(); pos--)
            {
                paragraph.removeRun(pos);
            }
        }
    }

    /**
     * Replace the corresponding variable in the field content with the picture
     *
     * @param paragraph         include a lot of runs
     * @param start             Replace the starting fragment position
     * @param end               Replace the ending fragment position
     * @param image             Replaced content
     * @param variableEndLength Replacement terminator length
     */
    private void replacePictureParagraph(
            XWPFParagraph paragraph, TextSegment start, TextSegment end
            , Image image, int variableEndLength
    )
    {
        //Traversing Runs, replacing the contents of the variable
        List runs = paragraph.getRuns();
        XWPFRun runStart = runs.get(start.getBeginRun());
        XWPFRun runEnd = runs.get(end.getEndRun());
        String textAfter = runEnd.text()
                                 .substring(end.getEndChar() + variableEndLength);

        //Read the image file form the url
        if (image.url != null)
        {
            try (InputStream imageInput = new URL(image.url).openStream())
            {
                BufferedImage bufferedImage = ImageIO.read(new URL(image.url));
                if (bufferedImage == null) throw new IOException("Read image file failed.");

                //self-adaption
                //Calculate Image Size
                int width = bufferedImage.getWidth();
                int height = bufferedImage.getHeight();
                switch (image.getAdaption())
                {
                    case WIDTH:
                        width = image.getWidth();
                        height = (image.getWidth() * bufferedImage.getHeight()) / bufferedImage.getWidth();
                        break;
                    case HEIGHT:
                        width = bufferedImage.getWidth() * image.getHeight() / bufferedImage.getHeight();
                        height = image.getHeight();
                        break;
                    case AUTO:
                        if (bufferedImage.getWidth() > image.getWidth())
                        {
                            width = image.getWidth();
                            height = (width * bufferedImage.getHeight()) / bufferedImage.getWidth();
                        }
                        else if (bufferedImage.getHeight() > image.getHeight())
                        {
                            height = image.getHeight();
                            width = bufferedImage.getWidth() * height / bufferedImage.getHeight();
                        }
                        break;
                    case NONE:
                    default:
                        width = image.getWidth();
                        height = image.getHeight();
                }

                runStart.addPicture(
                        imageInput,
                        image.type,
                        image.description,
                        Units.toEMU(width),
                        Units.toEMU(height)
                );
            }
            catch (IOException | InvalidFormatException e)
            {
                logger.error("Add a picture(url:{}) failed:", image.url, e);
            }
        }

        //If start-run and end-run is in the same sun
        if (start.getBeginRun() == end.getEndRun())
        {
            runStart.setText(" ", 0);
        }
        //If start-run and end-run is in the different suns
        //Insert a new run after start-run
        else
        {
            runStart.setText(" ", 0);
            runEnd.setText(textAfter, 0);
            for (int pos = end.getEndRun(); pos > start.getBeginRun(); pos--)
            {
                paragraph.removeRun(pos);
            }
        }
    }

    /**
     * Replace the corresponding variable in the table
     *
     * @param table   include table paragraph
     * @param dataMap data map
     */
    private void replaceTableParagraph(XWPFTable table, Map dataMap)
    {
        //Record the rows used for data loops
        Map rowForLoop = new HashMap<>();
        //Record a description of the relevant substitution variables used for the data loop
        // (including start position, end position, variable index key name, and get method)
        Map>> replace = new HashMap<>();
        //Method for obtaining corresponding fields in cached data
        Map> methodCache = new HashMap<>();
        //Record the number of rows of data
        AtomicInteger dataLength = new AtomicInteger(0);

        //Traversing a table to replace normal variable content
        //And record and judge the rows that need data loops
        int[] index = {0, 0, 0};
        table.getRows()
             .forEach(row -> {
                 index[1] = 0;
                 row.getTableCells()
                    .forEach(cell -> {
                        //Check if the contents of the data loop are included in the cell
                        String content = cell.getText();
                        if (content.contains(loopStart)
                                && content.contains(loopEnd)
                                && !rowForLoop.containsKey(index[0]))
                        {
                            rowForLoop.put(index[0], row);
                        }

                        //Traversing paragraphs to replace common variables
                        //If a data loop is included, the search records all of the loop data descriptions in each cell
                        Map> map = replace.getOrDefault(index[1], new HashMap<>());
                        replace.put(index[1], map);
                        index[2] = 0;
                        cell.getParagraphs()
                            .forEach(paragraph -> {
                                replaceParagraph(paragraph, dataMap);
                                if (rowForLoop.containsKey(index[0]))
                                {
                                    List list = map.getOrDefault(index[2], new ArrayList<>());
                                    map.put(index[2]++, list);
                                    searchLoopVariable(
                                            paragraph,
                                            list,
                                            dataMap,
                                            methodCache,
                                            dataLength,
                                            new PositionInParagraph()
                                    );
                                }
                            });

                        index[1]++;
                    });
                 index[0]++;
             });

        //Traversing the rows of data that need to be looped,
        //inserting the looped rows of data under the rows of data
        rowForLoop.forEach((pos, row) -> {
            //Create new row and insert
            XWPFTableRow newRow;
            for (index[0] = dataLength.intValue() - 1; index[0] > 0; index[0]--)
            {
                index[1] = 0;
                newRow = insertTableRow(pos + 1, row, table);
                newRow.getTableCells()
                      .forEach(
                              cell -> {
                                  index[2] = 0;
                                  cell.getParagraphs()
                                      .forEach(
                                              paragraph -> {
                                                  if (paragraph.getRuns()
                                                               .isEmpty())
                                                  {
                                                      return;
                                                  }
                                                  replace.get(index[1])
                                                         .get(index[2]++)
                                                         .forEach(
                                                                 replacement -> replaceParagraph(
                                                                         paragraph,
                                                                         replacement.getStart(),
                                                                         replacement.getEnd(),
                                                                         replacement.valueAt(index[0], dataMap),
                                                                         loopEnd.length()
                                                                 ));
                                              }

                                      );
                                  index[1]++;
                              }
                      );
            }

            //Replace first line
            index[0] = index[1] = 0;
            row.getTableCells()
               .forEach(cell -> {
                   index[2] = 0;
                   cell.getParagraphs()
                       .forEach(
                               paragraph -> replace.get(index[1])
                                                   .get(index[2]++)
                                                   .forEach(
                                                           replacement -> replaceParagraph(
                                                                   paragraph,
                                                                   replacement.getStart(),
                                                                   replacement.getEnd(),
                                                                   replacement.valueAt(index[0], dataMap),
                                                                   loopEnd.length()
                                                           ))

                       );
                   index[1]++;
               });
        });
    }

    /**
     * Copy to generate a new line,and insert row into target position.
     *
     * @param pos   insert position
     * @param row   Source row
     * @param table target table
     * @return new row
     */
    private XWPFTableRow insertTableRow(int pos, XWPFTableRow row, XWPFTable table)
    {
        CTTbl ctTbl = table.getCTTbl();
        int sizeCol = ctTbl.sizeOfTrArray() > 0
                      ? ctTbl.getTrArray(0)
                             .sizeOfTcArray()
                      : 0;
        XWPFTableRow newRow = table.insertNewTableRow(pos);

        //Copy row's style
        newRow.getCtRow()
              .setTrPr(row.getCtRow()
                          .getTrPr());
        XWPFTableCell cell;
        //Copy all cells
        for (int i = 0; i < sizeCol; i++)
        {
            cell = newRow.createCell();
            //Copy cell's style
            cell.getCTTc()
                .setTcPr(row.getCell(i)
                            .getCTTc()
                            .getTcPr());

            //Copy all paragraphs in cell
            for (XWPFParagraph paragraph : row.getCell(i)
                                              .getParagraphs())
            {
                XWPFParagraph newParagraph = cell.addParagraph();
                //Copy paragraph's style
                newParagraph.getCTP()
                            .setPPr(paragraph.getCTP()
                                             .getPPr());

                //Copy all runs in paragraphs
                for (XWPFRun run : paragraph.getRuns())
                {
                    XWPFRun newRun = newParagraph.createRun();
                    //Copy run's style
                    newRun.getCTR()
                          .setRPr(run.getCTR()
                                     .getRPr());
                    //Copy content
                    newRun.setText(run.text());
                }
            }
        }
        return newRow;
    }

    /**
     * Description of the search data loop
     *
     * @param paragraph   Searched paragraph
     * @param list        description list
     * @param dataMap     data map
     * @param methodCache Method for obtaining corresponding fields in cached data
     * @param dataLength  The length of the replaced data
     */
    private void searchLoopVariable(
            XWPFParagraph paragraph,
            List list,
            Map dataMap,
            Map> methodCache,
            AtomicInteger dataLength,
            PositionInParagraph startPosition
    )
    {
        //Search variable
        TextSegment start = paragraph.searchText(loopStart, startPosition);
        if (start == null) return;
        TextSegment end = paragraph.searchText(
                loopEnd,
                new PositionInParagraph(start.getEndRun(), start.getEndText(), start.getEndChar())
        );
        if (end == null) return;

        //Find the replaced variable and tag
        String variableKey = paragraph.getText(new TextSegment(
                start.getBeginRun(), end.getEndRun(),
                start.getBeginText(), end.getEndText(),
                start.getBeginChar(), end.getEndChar()
        ));
        variableKey = variableKey.replace(loopStart, "")
                                 .replace(loopEnd, "");

        //Find the replaced variable getting method
        Replacement replacement = new Replacement();
        replacement.setStart(start);
        replacement.setEnd(end);
        replacement.setIndexKey(variableKey);
        if (variableKey.contains("."))
        {
            String[] split = variableKey.split("\\.");
            replacement.setIndexKey(split[0]);
            if (methodCache.containsKey(split[0])
                    && methodCache.get(split[0])
                                  .containsKey(split[1]))
            {
                replacement.setMethod(methodCache.get(split[0])
                                                 .get(split[1]));
            }
            else if (dataMap.containsKey(split[0]))
            {
                Object object = dataMap.get(split[0]);
                if ((object instanceof ArrayList))
                {
                    List curList = (ArrayList) object;
                    if (!curList.isEmpty())
                    {
                        dataLength.set(Math.max(dataLength.intValue(), curList.size()));

                        object = curList.get(0);
                        try
                        {
                            Method method = ClassUtils.methodGetterOf(split[1], object.getClass());
                            replacement.setMethod(method);
                            methodCache.put(split[0], new HashMap()
                            {{
                                put(split[1], method);
                            }});
                        }
                        catch (NoSuchMethodException | NoSuchFieldException e)
                        {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            }
        }
        list.add(replacement);

        //Check again if there are any variables in the paragraph to replace
        searchLoopVariable(
                paragraph, list, dataMap, methodCache, dataLength,
                new PositionInParagraph(end.getEndRun(), end.getEndText(), end.getEndChar())
        );
    }

    /**
     * Describe the relevant content of the replacement
     */
    private static class Replacement
    {
        private TextSegment start;
        private TextSegment end;
        private String indexKey;
        private Method method;

        public String valueAt(int index, Map ofData)
        {
            String value = "";
            if (isIndexValue())
            {
                value = String.valueOf(index + 1);
            }
            else if (getMethod() != null)
            {
                try
                {
                    List list = ((List) ofData.get(getIndexKey()));
                    if (list != null && !list.isEmpty() && index < list.size())
                    {
                        value = String.valueOf(
                                getMethod().invoke(list.get(index))
                        );
                    }
                }
                catch (IllegalAccessException | InvocationTargetException e)
                {
                    logger.error(e.getMessage(), e);
                    value = "";
                }
            }
            return value;
        }

        public boolean isIndexValue()
        {
            return "index".equals(indexKey);
        }

        public TextSegment getStart()
        {
            return start;
        }

        public void setStart(TextSegment start)
        {
            this.start = start;
        }

        public TextSegment getEnd()
        {
            return end;
        }

        public void setEnd(TextSegment end)
        {
            this.end = end;
        }

        public String getIndexKey()
        {
            return indexKey;
        }

        public void setIndexKey(String indexKey)
        {
            this.indexKey = indexKey;
        }

        public Method getMethod()
        {
            return method;
        }

        public void setMethod(Method method)
        {
            this.method = method;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy