com.bxf.hradmin.testgen.service.impl.DocxTestGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.bxf.hradmin.testgen.service.impl.DocxTestGenerator.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Bo-Xuan Fan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.bxf.hradmin.testgen.service.impl;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSpacing;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STLineSpacingRule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.bxf.hradmin.common.exception.ModuleInfo;
import com.bxf.hradmin.testgen.model.QuestionSnapshot;
import com.bxf.hradmin.testgen.service.TestGenException;
import com.bxf.hradmin.testgen.service.TestGenerator;
import com.bxf.hradmin.utils.ZipUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * DocxTestGenerator
 *
 * @since 2017-02-26
 * @author Bo-Xuan Fan
 */
@Slf4j
@Service("docxTestGenerator")
public class DocxTestGenerator extends AbstractTestGenerator {

    @Value("${com.bxf.hradmin.testgen.service.font-size}")
    private int fontSize;

    @Value("${com.bxf.hradmin.testgen.service.font-ascii-family}")
    private String fontAsciiFamily;

    @Value("${com.bxf.hradmin.testgen.service.font-eastAsia-family}")
    private String fontEastAsiaFamily;

    @Override
    public void generate(String versionOid, List<QuestionSnapshot> questions) {
        File docxFile = new File(getRootPath(versionOid), "T-Java.docx");
        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(docxFile));
                InputStream is = TestGenerator.class.getResourceAsStream("/template/questions.docx")) {

            XWPFDocument doc = new XWPFDocument(is);
            replaceWordHolder(doc);

            XWPFTable table = doc.getTables().get(0);
            for (QuestionSnapshot question : questions) {
                XWPFTableRow row = table.createRow();
                writeQuestionNo(question.getQuestionNo(), row);
                writeQuestion(question, row);
            }

            //  template row
            table.removeRow(0);
            // 
            setBorders(table);
            // 
            doc.write(bos);

            File[] unzipFiles = { docxFile, new File(getRootPath(versionOid), "A-Java.txt") };

            File destination = new File(getRootPath(versionOid), "T-Java-docx.zip");
            ZipUtils.zip(destination, true, null, unzipFiles);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new TestGenException(e.getMessage(), ModuleInfo.TestGenMgr);
        }
    }

    private void replaceWordHolder(XWPFDocument doc) {
        doc.getParagraphs().forEach(paragraph -> {
            Map<String, String> data = new HashMap<>();
            data.put("score", "4");
            doReplace(paragraph, data);
        });

        doc.getFooterList().forEach(footer -> footer.getParagraphs().forEach(paragraph -> {
            Map<String, String> data = new HashMap<>();
            data.put("year", new SimpleDateFormat("yyyy").format(new Date()));
            doReplace(paragraph, data);
        }));
    }

    private void doReplace(XWPFParagraph p, Map<String, String> data) {
        String pText = p.getText(); // complete paragraph as string
        if (pText.contains("${")) { // if paragraph does not include our pattern, ignore
            Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}");
            Matcher matcher = pattern.matcher(pText);
            while (matcher.find()) { // for all patterns in the paragraph
                String key = matcher.group(1); // extract key start and end pos
                int start = matcher.start(1);
                int end = matcher.end(1);
                String value = data.get(key);
                if (value == null) {
                    value = "";
                }
                // get runs which contain the pattern
                SortedMap<Integer, XWPFRun> range = getPosToRuns(p).subMap(start - 2, true, end + 1, true);
                boolean isFoundDollarSign = false;
                boolean isFoundLeftBracket = false;
                boolean isFoundRightBracket = false;
                XWPFRun prevRun = null; // previous run handled in the loop
                XWPFRun found2Run = null; // run in which { was found
                int found2Pos = -1; // pos of { within above run
                for (XWPFRun run : range.values()) {
                    if (run == prevRun) {
                        continue; // this run has already been handled
                    }
                    if (isFoundRightBracket) {
                        break; // done working on current key pattern
                    }
                    prevRun = run;
                    for (int k = 0;; k++) { // iterate over texts of run r
                        if (isFoundRightBracket) {
                            break;
                        }
                        String txt = null;
                        try {
                            txt = run.getText(k); // note: should return null, but throws exception if the text does not exist
                        } catch (Exception e) {
                        }
                        if (txt == null) {
                            break; // no more texts in the run, exit loop
                        }
                        if (txt.contains("$") && !isFoundDollarSign) { // found $, replace it with value from data map
                            txt = txt.replaceFirst("\\$", value);
                            isFoundDollarSign = true;
                        }
                        if (txt.contains("{") && !isFoundLeftBracket && isFoundDollarSign) {
                            found2Run = run; // found { replace it with empty string and remember location
                            found2Pos = txt.indexOf('{');
                            txt = txt.replaceFirst("\\{", "");
                            isFoundLeftBracket = true;
                        }

                        // find } and set all chars between { and } to blank
                        if (isFoundDollarSign && isFoundLeftBracket && !isFoundRightBracket) {
                            if (txt.contains("}")) {
                                if (run == found2Run) { // complete pattern was within a single run
                                    txt = txt.substring(0, found2Pos) + txt.substring(txt.indexOf('}'));
                                } else {
                                    txt = txt.substring(txt.indexOf('}'));
                                }
                            } else if (run == found2Run) {
                                txt = txt.substring(0, found2Pos);
                            } else {
                                txt = ""; // run between { and }, set text to blank
                            }
                        }
                        if (txt.contains("}") && !isFoundRightBracket) {
                            txt = txt.replaceFirst("\\}", "");
                            isFoundRightBracket = true;
                        }
                        run.setText(txt, k);
                    }
                }
            }
        }
    }

    private NavigableMap<Integer, XWPFRun> getPosToRuns(XWPFParagraph paragraph) {
        int pos = 0;
        NavigableMap<Integer, XWPFRun> map = new TreeMap<>();
        for (XWPFRun run : paragraph.getRuns()) {
            String runText = run.getText(run.getTextPosition());
            if (runText != null && runText.length() > 0) {
                for (int i = 0; i < runText.length(); i++) {
                    map.put(pos + i, run);
                }
                pos += runText.length();
            }
        }
        return map;
    }

    private void writeQuestionNo(Integer questionNo, XWPFTableRow row) {
        XWPFTableCell cell = row.getCell(0);
        XWPFParagraph paragraph = cell.getParagraphs().get(0);
        adjustLineHeight(paragraph);
        // 
        paragraph.setAlignment(ParagraphAlignment.CENTER);
        // 
        cell.getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(735));

        XWPFRun run = createRun(paragraph);
        run.setText(String.valueOf(questionNo));
    }

    private void writeQuestion(QuestionSnapshot question, XWPFTableRow row) {
        //  & 
        StringBuilder builder = new StringBuilder().append(question.getDesc());
        builder.append(NEW_LINE);
        question.getAnswers().forEach(answer -> builder.append(NEW_LINE).append(answer.getAnswerNo()).append(". ")
                .append(answer.getDesc()));
        String desc = builder.toString();
        XWPFTableCell cell = row.getCell(1);
        cell.removeParagraph(0);

        String[] lines = desc.split(NEW_LINE);
        for (String line : lines) {
            // add new line and insert new text
            XWPFParagraph paragraph = cell.addParagraph();
            //  & ??
            paragraph.setAlignment(ParagraphAlignment.BOTH);
            adjustLineHeight(paragraph);
            XWPFRun run = createRun(paragraph);
            run.setText(line);
        }
    }

    private XWPFRun createRun(XWPFParagraph paragraph) {
        //  & ?
        XWPFRun run = paragraph.createRun();
        run.setFontSize(fontSize);
        CTFonts rFonts = run.getCTR().getRPr().addNewRFonts();
        rFonts.setEastAsia(fontEastAsiaFamily);
        rFonts.setAscii(fontAsciiFamily);
        return run;
    }

    private void adjustLineHeight(XWPFParagraph paragraph) {
        // ??
        paragraph.setSpacingAfter(0);
        paragraph.setSpacingBefore(0);
        // ?
        CTSpacing spacing = paragraph.getCTP().getPPr().getSpacing();
        spacing.setLineRule(STLineSpacingRule.EXACT);
        spacing.setLine(BigInteger.valueOf(360));
    }

    private void setBorders(XWPFTable table) {
        // 
        CTTblPr tblPr = table.getCTTbl().getTblPr();
        CTTblBorders borders = tblPr.addNewTblBorders();
        borders.addNewBottom().setVal(STBorder.SINGLE);
        borders.addNewLeft().setVal(STBorder.SINGLE);
        borders.addNewRight().setVal(STBorder.SINGLE);
        borders.addNewTop().setVal(STBorder.SINGLE);
        borders.addNewInsideH().setVal(STBorder.SINGLE);
        borders.addNewInsideV().setVal(STBorder.SINGLE);
    }
}