package cn.com.qmth.examcloud.core.questions.base.word;

import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
import cn.com.qmth.examcloud.core.questions.base.IdUtils;
import cn.com.qmth.examcloud.core.questions.base.IoUtils;
import cn.com.qmth.examcloud.core.questions.base.ZipUtils;
import cn.com.qmth.examcloud.core.questions.base.enums.QuesUnit;
import freemarker.template.Template;
import net.sf.saxon.TransformerFactoryImpl;
import net.sourceforge.jeuclid.LayoutContext;
import net.sourceforge.jeuclid.context.LayoutContextImpl;
import net.sourceforge.jeuclid.context.StyleAttributeLayoutContext;
import net.sourceforge.jeuclid.converter.Converter;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.docx4j.Docx4J;
import org.docx4j.TraversalUtil;
import org.docx4j.XmlUtils;
import org.docx4j.convert.in.FlatOpcXmlImporter;
import org.docx4j.convert.in.xhtml.XHTMLImporterImpl;
import org.docx4j.convert.out.HTMLSettings;
import org.docx4j.convert.out.common.preprocess.PartialDeepCopy;
import org.docx4j.dml.CTBlip;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.ClassFinder;
import org.docx4j.jaxb.Context;
import org.docx4j.math.CTOMath;
import org.docx4j.math.CTOMathPara;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.io3.stores.ZipPartStore;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.Part;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPart;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.openpackaging.parts.relationships.Namespaces;
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
import org.docx4j.relationships.Relationship;
import org.docx4j.wml.*;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Namespace;
import org.dom4j.io.SAXReader;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBElement;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipOutputStream;

/**
 * docx处理工具类 Created by songyue on 17/3/10.
 */
public final class DocxProcessUtil {

    private static final Logger LOG = LoggerFactory.getLogger(DocxProcessUtil.class);

    public static final LayoutContext IMG_LAYOUT = new StyleAttributeLayoutContext(
            LayoutContextImpl.getDefaultLayoutContext(), "2em", java.awt.Color.BLACK);

    public static final String IMG_OUT_TYPE = "image/png";

    public static final String ENCODING = "utf-8";

    public static final String BASE64_HEADER = "data:image/png;base64,";

    public static final String TEMP_FILE_IMP = "docxImport/";

    public static final String TEMP_FILE_EXP = "docxExport/";

    public static final String OMML2MML_XSL = "OMML2MML.XSL";

    public static final String PAPER_TEMPLATE = "paper_template.ftl";

    public static final String ANSWER_TEMPLATE = "answer_template.ftl";

    public static final String DOCX_SUFFIX = ".docx";

    public static final String BODY_HEADER = "<w:body xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">";

    public static final String BODY_TAIL = "</w:body>";

    public static final String REL_ID_HEADER = "rId";

    static {
        init();
    }

    /**
     * 初始化文件夹
     */
    public static void init() {
        File temp_file_exp = new File(TEMP_FILE_EXP);
        File temp_file_imp = new File(TEMP_FILE_IMP);
        // 如果输出目标文件夹不存在，则创建
        if (!temp_file_imp.exists()) {
            temp_file_imp.mkdirs();
        }
        if (!temp_file_exp.exists()) {
            temp_file_exp.mkdirs();
        }
    }

    /**
     * 获取段落的所有文本
     *
     * @param p
     * @return
     */
    public static String getPText(P p) {
        String returnText = "";
        List<Object> tList = getAllElementWithBrFromObject(p, Text.class);
        for (Object obj : tList) {
        	if (obj.getClass().equals(Text.class)) {
	            Text text = (Text) obj;
	            returnText += text.getValue().trim();
        	}else if(obj.getClass().equals(Br.class)){
        		returnText += "<br/>";
        	}
        }
        return returnText.trim();
    }

    /**
     * 校验段落中是否含有公式或图片
     *
     * @param p
     * @return
     */
    public static boolean isText(P p) {
        List<Object> mathList = getAllElementFromObject(p, CTOMath.class);
        List<Object> mathParaList = getAllElementFromObject(p, CTOMathPara.class);
        List<Object> drawList = getAllImageElementFromObject(p);
        if (mathParaList.size() > 0 || mathList.size() > 0 || drawList.size() > 0) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 校验段落中是否含有序列
     *
     * @param p
     * @return
     */
    public static boolean isNumPr(P p) {
        PPr pPr = p.getPPr();
        if (pPr != null) {
            PPrBase.NumPr numPr = pPr.getNumPr();
            if (numPr != null && numPr.getNumId() != null) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * 获取文档包副本(去除段落)
     *
     * @param wordMLPackage
     * @return
     * @throws Exception
     */
    public static WordprocessingMLPackage getTmpPackage(WordprocessingMLPackage wordMLPackage) throws Exception {
        // 添加关系样式信息
        Set<String> relationshipTypes = new HashSet<>();
        relationshipTypes.add(wordMLPackage.getMainDocumentPart().getRelationshipType());
        // 深拷贝
        WordprocessingMLPackage tmpWordMlPackage = (WordprocessingMLPackage) PartialDeepCopy
                .process(wordMLPackage, relationshipTypes);
        // 移除所有段落
        initTmpPackage(tmpWordMlPackage);

        return tmpWordMlPackage;
    }

    /**
     * 初始化文档包(去除所有段落)
     *
     * @param wordMLPackage
     * @return
     * @throws Exception
     */
    public static void initTmpPackage(WordprocessingMLPackage wordMLPackage) throws Exception {
        // 移除所有段落
        List<Object> contentList = wordMLPackage.getMainDocumentPart().getContent();
        if (contentList == null) {
            return;
        }
        contentList.clear();
    }

    /**
     * 获得段落wordml
     *
     * @param p
     * @return
     */
    public static String getPWordMl(P p) throws Exception {
        return XmlUtils.marshaltoString(p);
    }

    /**
     * 转换wordMl为html
     *
     * @param wordMl
     * @return
     * @throws Exception
     */
    public static String docx2Html(String wordMl, WordprocessingMLPackage wordMLPackage) throws Exception {

        initTmpPackage(wordMLPackage);

        // 转换wordml为完整的package对象
        MainDocumentPart mdp = wordMLPackage.getMainDocumentPart();
        Object p = XmlUtils.unmarshalString(wordMl);

        mdp.getContent().add(p);

        // 转换公式为图片
        replaceMath2Image(wordMLPackage, (P) p);

        // 设置导出临时html参数
        HTMLSettings htmlSettings = Docx4J.createHTMLSettings();
        htmlSettings.setImageDirPath(TEMP_FILE_IMP + "_files");
        htmlSettings.setImageTargetUri(TEMP_FILE_IMP.substring(TEMP_FILE_IMP.lastIndexOf("/") + 1) + "_files");
        htmlSettings.setWmlPackage(wordMLPackage);
        String tmpHtmlPath = TEMP_FILE_IMP + IdUtils.uuid() + ".html";
        File tmpHtml = new File(tmpHtmlPath);
        OutputStream os = new FileOutputStream(tmpHtml);
        Docx4J.toHTML(htmlSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
        os.flush();
        os.close();
        // 格式化html代码
        String htmlStr = formatHtmlByPath(tmpHtmlPath);
        // 删除临时html文件
        FileUtils.deleteQuietly(tmpHtml);
        FileUtils.deleteQuietly(new File(TEMP_FILE_IMP + "_files"));
        return htmlStr;
    }

    /**
     * 替换所有公式为图片
     *
     * @param wordMLPackage
     * @throws Exception
     */
    public static void replaceAllMath2Image(WordprocessingMLPackage wordMLPackage) throws Exception {
        List<Object> pList = getAllElementFromObject(wordMLPackage.getMainDocumentPart(), P.class);
        for (Object pObject : pList) {
            P p = (P) pObject;
            List<Object> pContent = p.getContent();
            int index = 0;
            for (Object child : pContent) {
                if (child instanceof ContentAccessor) {
                    index++;
                    continue;
                }
                if (child instanceof JAXBElement)
                    child = ((JAXBElement<?>) child).getValue();
                if (child.getClass().equals(CTOMath.class)) {
                    String omml = XmlUtils.marshaltoString(child, true, true, Context.jc,
                            "http://schemas.openxmlformats.org/officeDocument/2006/math", "oMath", CTOMath.class);
                    byte[] imgByte = convertMathml2Img(omml2mml(omml));
                    pContent.set(index, newImage(wordMLPackage, imgByte, "math" + (index), "math" + (index)));
                }
                index++;
            }
        }
    }

    /**
     * 替换公式为图片
     *
     * @param p
     * @throws Exception
     */
    public static void replaceMath2Image(WordprocessingMLPackage wordMLPackage, P p) throws Exception {
        List<Object> pContent = p.getContent();
        int index = 0;
        for (Object child : pContent) {
            if (child instanceof ContentAccessor) {
                index++;
                continue;
            }
            if (child instanceof JAXBElement)
                child = ((JAXBElement<?>) child).getValue();
            if (child.getClass().equals(CTOMath.class)) {
                String omml = XmlUtils.marshaltoString(child, true, true, Context.jc,
                        "http://schemas.openxmlformats.org/officeDocument/2006/math", "oMath", CTOMath.class);
                byte[] imgByte = convertMathml2Img(omml2mml(omml));
                pContent.set(index, newImage(wordMLPackage, imgByte, "math" + (index), "math" + (index)));
            } else if (child.getClass().equals(CTOMathPara.class)) {
                String omml = XmlUtils.marshaltoString(child, true, true, Context.jc,
                        "http://schemas.openxmlformats.org/officeDocument/2006/math", "oMathPara", CTOMathPara.class);
                byte[] imgByte = convertMathml2Img(omml2mml(omml));
                pContent.set(index, newImage(wordMLPackage, imgByte, "math" + (index), "math" + (index)));
            }
            index++;
        }
    }

    /**
     * 创建新图片
     *
     * @param wordMLPackage
     * @param bytes
     * @param filenameHint
     * @param altText
     * @return
     * @throws Exception
     */
    public static R newImage(WordprocessingMLPackage wordMLPackage, byte[] bytes, String filenameHint, String altText)
            throws Exception {
        int id1 = (int) ((Math.random() * 1000) * (Math.random() * 1000));
        int id2 = (int) ((Math.random() * 1000) * (Math.random() * 1000));

        BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);

        Inline inline = imagePart.createImageInline(filenameHint, altText, id1, id2, false);

        ObjectFactory factory = Context.getWmlObjectFactory();
        R run = factory.createR();
        Drawing drawing = factory.createDrawing();
        run.getContent().add(drawing);
        drawing.getAnchorOrInline().add(inline);
        bytes = null;
        imagePart = null;
        return run;

    }

    /**
     * 获取wordMl固定节点下所有子节点
     *
     * @param obj
     * @param toSearch
     * @return
     */
    public static List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {

        List<Object> result = new ArrayList<>();
        if (obj instanceof JAXBElement)
            obj = ((JAXBElement<?>) obj).getValue();
        if (obj.getClass().equals(toSearch))
            result.add(obj);
        else if (obj instanceof ContentAccessor) {
            List<?> children = ((ContentAccessor) obj).getContent();
            for (Object child : children) {
                result.addAll(getAllElementFromObject(child, toSearch));
            }
        }
        return result;
    }
    public static List<Object> getAllImageElementFromObject(Object obj) {

        List<Object> result = new ArrayList<>();
        if (obj instanceof JAXBElement)
            obj = ((JAXBElement<?>) obj).getValue();
        if (obj.getClass().equals(Drawing.class)||obj.getClass().equals(Pict.class))
            result.add(obj);
        else if (obj instanceof ContentAccessor) {
            List<?> children = ((ContentAccessor) obj).getContent();
            for (Object child : children) {
                result.addAll(getAllImageElementFromObject(child));
            }
        }
        return result;
    }
    public static List<Object> getAllElementWithBrFromObject(Object obj, Class<?> toSearch) {

        List<Object> result = new ArrayList<>();
        if (obj instanceof JAXBElement) {
            obj = ((JAXBElement<?>) obj).getValue();
        }
        if (obj.getClass().equals(toSearch)||obj.getClass().equals(Br.class)) {
            result.add(obj);
        }else if (obj instanceof ContentAccessor) {
            List<?> children = ((ContentAccessor) obj).getContent();
            for (Object child : children) {
                result.addAll(getAllElementWithBrFromObject(child, toSearch));
            }
        }
        return result;
    }
    /**
     * omml转换成mml
     *
     * @param omml
     * @return
     */
    public static String omml2mml(String omml) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        InputStream stylesheet = DocxProcessUtil.class.getClassLoader().getResourceAsStream(OMML2MML_XSL);
        StringReader sr = new StringReader(omml);
        InputSource ommldata = new InputSource(sr);
        StringWriter writer = new StringWriter();
        org.w3c.dom.Document document = null;
        DocumentBuilder builder = null;
        Transformer transformer = null;
        builder = factory.newDocumentBuilder();
        document = builder.parse(ommldata);
        TransformerFactory tFactory = new TransformerFactoryImpl();
        StreamSource stylesource = new StreamSource(stylesheet);
        transformer = tFactory.newTransformer(stylesource);
        DOMSource source = new DOMSource(document);
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        IOUtils.closeQuietly(sr);
        IOUtils.closeQuietly(writer);
        String mml = writer.toString();
        mml = ("<mml:math" + StringUtils.substringAfter(mml, "<mml:math")).replace("mml:", "");
        return mml;
    }

    /**
     * 格式化转换后的html(html字符串)
     *
     * @param htmlStr
     * @return
     */
    public static String formatHtml(String htmlStr) throws Exception {
        org.jsoup.nodes.Document doc = Jsoup.parse(htmlStr);
        Elements divs = doc.select("div[class='document");
        doc.select("p").removeAttr("class").removeAttr("style");
        doc.select("div").removeAttr("class").removeAttr("style");
        doc.select("span").removeAttr("class").removeAttr("style");
        doc.select("span").stream().forEach(element -> {
            if (!element.hasText())
                element.remove();
        });
        if (divs.size() > 0) {
            htmlStr = divs.html();
        } else {
            htmlStr = doc.html();
        }
        return htmlStr;
    }

    /**
     * 获取html(html字符串)里面文本
     *
     * @param htmlStr
     * @return
     */
    public static String getTextInHtml(String htmlStr) {
        htmlStr = htmlStr.replaceAll("\\&[a-zA-Z]{1,10};", "").trim();
        if (!htmlStr.startsWith("<p>")) {
            return htmlStr;
        }

        try {
            org.jsoup.nodes.Document doc = Jsoup.parse(htmlStr);
            StringBuilder textStr = new StringBuilder();
            Elements links = doc.select("p").removeAttr("img");

            for (Element link : links) {
                textStr.append(link.text().trim());
            }

            return textStr.toString();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }

        return htmlStr;
    }

    /**
     * 格式化转换后的html(html临时文件)
     *
     * @param htmlPath
     * @return
     */
    public static String formatHtmlByPath(String htmlPath) throws Exception {
        String htmlStr = "";
        File htmlFile = new File(htmlPath);
        org.jsoup.nodes.Document doc = Jsoup.parse(htmlFile, ENCODING);
        Elements divs = doc.select("div[class='document']");
        doc.select("p").removeAttr("class").removeAttr("style");
        doc.select("div").removeAttr("class").removeAttr("style");
        doc.select("span").removeAttr("class").removeAttr("style");
        doc.select("span").stream().forEach(element -> {
            if (!element.hasText() && element.childNodeSize() == 0) {
                element.remove();
            }
        });
        Elements imgs = doc.select("img");
        for (org.jsoup.nodes.Element img : imgs) {
            String imgSrc = img.attr("src");
            if (!StringUtils.isEmpty(imgSrc)) {
                String imgBase64 = new String(getBase64ByPath(htmlFile.getParent() + "/" + imgSrc));
                img.attr("src", BASE64_HEADER + imgBase64);
            }
        }
        if (divs.size() > 0) {
            htmlStr = divs.html();
        } else {
            htmlStr = doc.html();
        }
        return htmlStr;
    }

    /**
     * 转换mathml为png图片
     *
     * @param mathMlStr
     * @return
     */
    public static byte[] convertMathml2Img(String mathMlStr) throws Exception {
        File xmlFile = null;
        File imgFile = null;
        FileOutputStream xmlFos = null;
        byte[] base64Byte = new byte[0];
        xmlFile = new File(TEMP_FILE_IMP, IdUtils.uuid());
        imgFile = new File(TEMP_FILE_IMP, IdUtils.uuid());
        xmlFos = new FileOutputStream(xmlFile);
        IOUtils.write(mathMlStr, xmlFos, ENCODING);
        xmlFos.flush();
        Converter.getInstance().convert(xmlFile, imgFile, IMG_OUT_TYPE, IMG_LAYOUT);
        base64Byte = IOUtils.toByteArray(new FileInputStream(imgFile));
        IOUtils.closeQuietly(xmlFos);
        FileUtils.deleteQuietly(xmlFile);
        FileUtils.deleteQuietly(imgFile);
        return base64Byte;
    }

    /**
     * 通过图片路径获取base64码
     *
     * @param imgFilePath
     * @return
     */
    public static byte[] getBase64ByPath(String imgFilePath) throws Exception {
        InputStream is = null;
        byte[] base64Byte = new byte[0];
        byte[] imgByte;
        File imgFile = new File(imgFilePath);
        is = new FileInputStream(imgFile);
        imgByte = IOUtils.toByteArray(is);
        base64Byte = Base64.encodeBase64(imgByte);
        IOUtils.closeQuietly(is);
//        FileUtils.deleteQuietly(imgFile);
        return base64Byte;
    }

    /**
     * 通过图片文件获取base64码
     *
     * @param imgFile
     * @return
     */
    public static byte[] getBase64ByFile(File imgFile) throws Exception {
        InputStream is = null;
        byte[] base64Byte = new byte[0];
        byte[] imgByte;
        is = new FileInputStream(imgFile);
        imgByte = IOUtils.toByteArray(is);
        base64Byte = Base64.encodeBase64(imgByte);
        IOUtils.closeQuietly(is);
        return base64Byte;
    }

    /**
     * 格式化段落wordml
     *
     * @param pWordMl
     * @return
     * @throws Exception
     */
    public static String formatPWordMl(String pWordMl) throws Exception {
        String tmpWordMl = pWordMl.replaceAll("\\\\\"", "\"");
        SAXReader sax = new SAXReader();
        ByteArrayInputStream is = new ByteArrayInputStream(tmpWordMl.getBytes("UTF-8"));
        org.dom4j.Document document = sax.read(is);
        org.dom4j.Element root = document.getRootElement();
        List<Namespace> namespaces = root.additionalNamespaces();
        namespaces.stream().forEach(namespace -> {
            root.remove(namespace);
        });
        String returnXml = CommonUtils.trimNoBlankSpace(root.asXML());
        return returnXml;
    }

    /**
     * 过滤试题单元标题
     *
     * @param p
     * @param quesUnit
     * @return
     */
    public static P formatP(P p, QuesUnit quesUnit) {
        List<Object> pContent = p.getContent();
        int index = 0;
        for (Object child : pContent) {
            if (child.getClass().equals(R.class)) {

                R r = (R) child;
                List<Object> rContent = r.getContent();

                for (Object rChild : rContent) {

                    if (rChild instanceof JAXBElement)
                        rChild = ((JAXBElement<?>) rChild).getValue();

                    if (rChild.getClass().equals(Text.class)) {
                        ++index;
                        Text text = (Text) rChild;
                        String tmpText = text.getValue();
                        if (quesUnit == QuesUnit.QUES_BODY) {

                            // 过滤题干标题
                            if (tmpText.matches("^\\d{1,}\\.[\\s\\S]*")) {
                                tmpText = tmpText.replaceFirst("\\d{1,}\\.", "");
                                text.setValue(tmpText);
                                return p;
                            } else if (tmpText.matches("^\\d{1,}$")) {
                                tmpText = tmpText.replaceFirst("\\d{1,}", "");
                                text.setValue(tmpText);
                            } else if (tmpText.contains(".")) {
                                tmpText = tmpText.replaceFirst("\\.", "");
                                text.setValue(tmpText);
                                return p;
                            }

                        } else if (quesUnit == QuesUnit.QUES_OPTION) {

                            // 过滤选项标题
                            if (tmpText.matches("^[A-Z]\\.[\\s\\S]*")) {
                                tmpText = tmpText.replaceFirst("[A-Z]\\.", "");
                                text.setValue(tmpText);
                                return p;
                            } else if (tmpText.matches("^[A-Z]$")) {
                                tmpText = tmpText.replaceFirst("[A-Z]", "");
                                text.setValue(tmpText);
                            } else if (tmpText.contains(".")) {
                                tmpText = tmpText.replaceFirst("\\.", "");
                                text.setValue(tmpText);
                                return p;
                            }

                        } else if (quesUnit == QuesUnit.QUES_ANSWER) {

                            // 过滤答案标题
                            if (index <= 5) {
                                tmpText = tmpText.replaceFirst("\\[", "").replaceFirst("\\]", "")
                                        .replaceFirst("答案", "").replaceFirst("[:|：]", "");
                                text.setValue(tmpText);
                            } else {
                                return p;
                            }
//                            if(tmpText.contains(":") || tmpText.contains("：")){
//                                if(tmpText.startsWith(":") || tmpText.startsWith("：")){
//                                    tmpText = tmpText.replaceFirst("[:|：]","");
//                                    text.setValue(tmpText);
//                                }else{
//                                    tmpText = tmpText.replaceFirst("\\[","").replaceFirst("\\]","").replaceFirst("答案","").replaceFirst("[:|：]","");
//                                    text.setValue(tmpText);
//                                }
//                                return p;
//                            } else {
//                                tmpText = tmpText.replaceFirst("\\[","").replaceFirst("\\]","").replaceFirst("答案","");
//                                text.setValue(tmpText);
//                            }
                        }
                    }
                }
            }
        }
        return p;
    }

    /**
     * 导出word
     */
    public static void exportWord(Object dataModel, String fileName, Template template) {
        // 输出文件
        File outFile = new File(TEMP_FILE_EXP + fileName);

        exportWordNew(dataModel, outFile, template);
    }

    /**
     * 导出Word
     */
    public static void exportWordNew(Object dataModel, File outFile, Template template) {
        if (template == null) {
            throw new RuntimeException("Export Word Template is null.");
        }

        try (FileOutputStream fos = new FileOutputStream(outFile);
             OutputStreamWriter osw = new OutputStreamWriter(fos, ENCODING);
             Writer out = new BufferedWriter(osw);
        ) {
            // 将模板和数据模型合并生成文件
            template.process(dataModel, out);
            out.flush();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            throw new RuntimeException("Export Word Fail.");
        }
    }

    public static boolean hasImage(String html) throws Exception {
        if (html == null) {
            return false;
        }
        String reg = "<\\s*img\\s+([^>]*)\\s*>";
        Pattern pattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(html);
        if (matcher.find()) {
            return true;
        }
        return false;
    }

    /**
     * 处理导出word中的图片
     *
     * @param fileName       需要带文件后缀
     * @param wordMLPackages
     * @throws Exception
     */
    public static void processImage(String fileName, List<WordprocessingMLPackage> wordMLPackages) throws Exception {
        LOG.info("试卷:“" + fileName + "”导出处理图片开始...");
        String filePath = TEMP_FILE_EXP + fileName;
        InputStream mainFile = new FileInputStream(filePath);
        // 取到当前生成的主Word
        FlatOpcXmlImporter flatOpcXmlImporter = new FlatOpcXmlImporter(mainFile);
        WordprocessingMLPackage wordMLPackage = (WordprocessingMLPackage) flatOpcXmlImporter.get();
        //构建relationships
        RelationshipsPart relationshipsPart = wordMLPackage.getMainDocumentPart().getRelationshipsPart();
        for (WordprocessingMLPackage wp : wordMLPackages) {
            if (wp == null) {
                continue;
            }
            // 获取资源文件存储
            ZipPartStore partStore = (ZipPartStore) wp.getSourcePartStore();
            // 获取图片资源定义、只取图片
            RelationshipsPart rp = wp.getMainDocumentPart().getRelationshipsPart();
            List<Relationship> relationships = rp.getRelationshipsByType(Namespaces.IMAGE);

            List<Part> parts = new ArrayList<>();
            for (Relationship relationship : relationships) {
                parts.add(rp.getPart(relationship));
            }
            // 添加资源文件定义
            for (Part p : parts) {
                String relId = p.getSourceRelationships().get(0).getId();
                //如果不存在该relId,拷贝图片
                if (!relationshipsPart.isRelIdOccupied(relId)) {
                    byte[] bytes = null;
                    if (partStore != null) {
                        bytes = partStore.getByteArray(p.getPartName().getName().substring(1)).getBytes();
                    } else {
                        if (p instanceof BinaryPart) {
                            BinaryPart imagePart = (BinaryPart) p;
                            bytes = imagePart.getBytes();
                        }
                    }
                    copyImage(wordMLPackage, bytes, relId);
                }
            }
            // 添加资源文件存储
            //wordMLPackage.setSourcePartStore(partStore);
        }
        // 以word2007标准模式重新保存(zip包)
        OutputStream os = new FileOutputStream(filePath);
        Docx4J.save(wordMLPackage, os, Docx4J.FLAG_SAVE_ZIP_FILE);
        IOUtils.closeQuietly(os);
        LOG.info("试卷:“" + fileName + "”导出处理图片结束");
    }

    public static void copyImage(WordprocessingMLPackage wordMLPackage, byte[] bytes, String relId) throws Exception {
        if (bytes == null || bytes.length == 0) {
            return;
        }
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        BufferedImage bi1 = ImageIO.read(bais);
        if (bi1 != null) {
            BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);
            imagePart.getRelLast().setId(relId);
        }
    }

    /**
     * word合并时复制图片
     * 将partStore中的Relationship复制到wordMLPackage中
     *
     * @param wordMLPackage
     * @param partStore
     * @param p
     * @throws Exception
     */
    public static void copyImage(WordprocessingMLPackage wordMLPackage, ZipPartStore partStore, Part p, String relId) throws Exception {
        byte[] bytes = partStore.getByteArray(p.getPartName().getName().substring(1)).getBytes();
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        BufferedImage bi1 = ImageIO.read(bais);
        if (bi1 != null) {
            BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);
            imagePart.getRelLast().setId(relId);
        }
    }

    /**
     * 获取word二进制数据(空文档,只有样式和资源)
     *
     * @param wordMLPackage
     * @return
     * @throws Exception
     */
    public static byte[] getPkgByte(WordprocessingMLPackage wordMLPackage) throws Exception {

        DocxProcessUtil.initTmpPackage(wordMLPackage);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Docx4J.save(wordMLPackage, outputStream);
        byte[] bytes = outputStream.toByteArray();

        outputStream.flush();
        IOUtils.closeQuietly(outputStream);
        return bytes;
    }

    /**
     * 通过二进制流获取word文档包
     *
     * @param bytes
     * @return
     */
    public static WordprocessingMLPackage getPkg(byte[] bytes) {
        if (bytes.length == 0)
            return null;
        InputStream inputStream = new ByteArrayInputStream(bytes);
        WordprocessingMLPackage wordMLPackage = null;
        try {
            wordMLPackage = Docx4J.load(inputStream);
        } catch (Docx4JException e) {
            LOG.error(e.getMessage(), e);
        }
        return wordMLPackage;
    }

    /**
     * 初始化word文档图片资源路径,防止导出试卷时资源ID冲突
     *
     * @param wordMLPackage
     */
    public static void initPkgImage(WordprocessingMLPackage wordMLPackage) {
        // 获取文档中所有图片引用位置
        ClassFinder finder = new ClassFinder(CTBlip.class);
        new TraversalUtil(wordMLPackage.getMainDocumentPart().getContent(), finder);
        List<Object> blips = finder.results;
        // 获取文档中所有图片引用定义
        List<Relationship> relationships = wordMLPackage.getMainDocumentPart().getRelationshipsPart()
                .getRelationshipsByType(Namespaces.IMAGE);
        // 同步替换资源ID
        int index = 0;
        for (Relationship relationship : relationships) {
            String tmpId = relationship.getId();
            String tmp = getRldNum() + (++index);
            for (Object obj : blips) {
                if (obj.getClass().equals(CTBlip.class)) {
                    CTBlip ctBlip = (CTBlip) obj;
                    String tmpEmbed = ctBlip.getEmbed();
                    if (tmpId.equals(tmpEmbed)) {
                        relationship.setId(tmp);
                        ctBlip.setEmbed(tmp);
                    }
                }
            }
        }
    }

    /**
     * 获取RldNum
     *
     * @return
     */
    public static String getRldNum() {
        return REL_ID_HEADER + CommonUtils.getCurNum();
    }

    /**
     * 下载word文件
     *
     * @param fileNames 需带后缀
     * @param response
     * @throws Exception
     */
    public static void processDownload(List<String> fileNames, HttpServletResponse response) throws Exception {
        String zipFileName = fileNames.get(0);
        response.setHeader("Content-Disposition", "attachment; filename=" + new String(zipFileName.getBytes("UTF-8"), "iso-8859-1") + ".zip");
        // 设置强制下载不打开
        response.setContentType("application/octet-stream;charset=utf-8");

        try (ZipOutputStream out = new ZipOutputStream(response.getOutputStream());) {
            for (String fileName : fileNames) {
                ZipUtils.doCompress(TEMP_FILE_EXP + fileName, out);
                response.flushBuffer();
            }
        } catch (IOException e) {
            LOG.error(e.getMessage(), e);
        }

        //删除本地文件
        for (String fileName : fileNames) {
            File file = new File(TEMP_FILE_EXP + fileName);
            if (file.exists()) {
                IoUtils.removeFile(file);
            }
        }
    }

    /**
     * html转word
     */
    public static String html2Docx(WordprocessingMLPackage wordMLPackage, String html) throws Exception {
        if (StringUtils.isBlank(html)) {
            return "";
        }
        initTmpPackage(wordMLPackage);
        RFonts rfonts = Context.getWmlObjectFactory().createRFonts();
        rfonts.setAscii("eastAsia");
        XHTMLImporterImpl.addFontMapping("eastAsia", rfonts);
        XHTMLImporterImpl XHTMLImporter = new XHTMLImporterImpl(wordMLPackage);
        XHTMLImporter.setHyperlinkStyle("Hyperlink");

        try {
            List<Object> values = XHTMLImporter.convert(html, TEMP_FILE_IMP);
            wordMLPackage.getMainDocumentPart().getContent().addAll(values);
        } catch (Docx4JException e) {
            //内容示例：
            // ""                 会报错
            // "abc"              会报错
            // "<xxx>abc<xxx>"    不报错
            throw new IllegalArgumentException("内容需要定义在HTML标签中:"+e.getCause().getMessage());
        }

        //转换完后就初始化image
        initPkgImage(wordMLPackage);

        // 获取word文档中所有段落
        List<Object> pList = getAllElementFromObject(wordMLPackage.getMainDocumentPart(), P.class);

        String wordMl = "";
        for (Object p : pList) {
            wordMl += formatPWordMl(XmlUtils.marshaltoString(p));
        }
        return wordMl;
    }

    public static byte[] getWordBytesByQuestion(WordprocessingMLPackage wordMLPackage, List<String> wordXmls) throws Exception {
        RelationshipsPart mainRelationshipsPart = wordMLPackage.getMainDocumentPart().getRelationshipsPart();
        // 获取总的资源文件存储
        ZipPartStore partStore = (ZipPartStore) wordMLPackage.getSourcePartStore();
        //创建子Word
        WordprocessingMLPackage subWordPackage = WordprocessingMLPackage.createPackage();
        MainDocumentPart subMainDocumentPart = subWordPackage.getMainDocumentPart();
        //向子Word中添加内容
        for (String wordXml : wordXmls) {
            wordXml = "<myElement>" + wordXml + "</myElement>";
            Document document = DocumentHelper.parseText(wordXml);
            org.dom4j.Element rootElement = document.getRootElement();
            List<org.dom4j.Element> nodes = rootElement.elements();
            for (org.dom4j.Element element : nodes) {
                Object obj = XmlUtils.unmarshalString(element.asXML());
                subMainDocumentPart.addObject(obj);
            }
        }
        //向子Word中添加图片的关联关系
        RelationshipsPart subRelationshipsPart = subMainDocumentPart.getRelationshipsPart();
        List<Relationship> subRelationships = subRelationshipsPart.getRelationshipsByType(Namespaces.IMAGE);
        List<Object> blips = subMainDocumentPart.getJAXBNodesViaXPath("//a:blip", false);
        for (Object blip : blips) {
            if (blip.getClass().equals(CTBlip.class)) {
                CTBlip cTBlip = (CTBlip) blip;
                Relationship relationship = mainRelationshipsPart.getRelationshipByID(cTBlip.getEmbed());
                if (relationship != null) {
                    subRelationships.add(relationship);
                }
            }
        }
        //向子Word中添加资源
        List<Part> parts = new ArrayList<>();
        for (Relationship relationship : subRelationships) {
            if (mainRelationshipsPart.getPart(relationship) != null) {
                parts.add(mainRelationshipsPart.getPart(relationship));
            }
        }
        for (Part p : parts) {
            if (p.getSourceRelationships() != null &&
                    p.getSourceRelationships().size() > 0) {
                String relId = p.getSourceRelationships().get(0).getId();
                if (mainRelationshipsPart.isRelIdOccupied(relId)) {
                    DocxProcessUtil.copyImage(subWordPackage, partStore, p, relId);
                }
            }
        }
        initTmpPackage(subWordPackage);
        byte[] writeByte = getPkgByte(subWordPackage);
        subWordPackage = null;
        wordXmls.clear();
        wordXmls = null;
        return writeByte;
    }

    public static int getOptionNum(String wordText) {
        if (wordText.matches("^[a-zA-Z]\\.[\\s\\S]*")) {
            Integer num = CommonUtils.characterToNumber(wordText.substring(0, 1));
            return num;
        }
        return -1;
    }

    /**
     * 检测P标签中是否有值
     *
     * @return
     */
    public static boolean isTextFromP(String strHtml) {
        String value = "";
        String regex = "<p>(.*)</p>";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(strHtml);
        while (matcher.find()) {
            value = matcher.group(1);
        }
        if (StringUtils.isBlank(value)) {
            return true;
        }
        return false;
    }

}