使用 *ngFor 将 html 个文件转换为 Java 中的 pdf

Converting html files with *ngFor to pdf in Java

我必须在我的 Java Web 应用程序(Maven,在服务器上运行)中生成文档,并且必须将来自 Java class 的数据插入到该文档中。
我希望能够编写一个带有占位符的 HTML 文件。应用程序中的占位符应替换为 Java class.
中的数据 我还希望能够使用像 *ngFor 这样的条件(例如将列表插入 a )或 *ngIf from Angular (或具有类似功能的属性)。

有人知道这方面的图书馆吗?

我很了解 Java、HTML 等,所以使用这样的库(如果有的话)对我来说不是问题

同时我自己写了一个小脚本。如果有人需要类似的东西,我已将其包含在答案中

在此期间,我进一步搜索,不幸的是,到目前为止我还没有找到合适的解决方案。因此,我现在开始自己编写解决方案。所需的努力比预期的要少得多。这是我当前的代码。它目前是一个草稿,当然需要一些改进。

package com.XYZ.file.bo;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.XYZ.file.service.FileService;
import com.XYZ.servicelocator.ServiceLocator;
import com.XYZ.util.TechnicalException;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;

public class TemplateGeneratorBO {

    private FileService fileService;

    private static final String DOC_TEMPLATE_DIR = FileBO.BASE_DIR + "templates/";

    public File generateDoc(String tempFileName, String saveFolder, String saveFileName, Object entity) {
        String htmlDoc = parseHtmlDoc(tempFileName, entity);
        htmlDoc = replaceSpecialChars(htmlDoc);
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        ConverterProperties converterProperties = new ConverterProperties();
        HtmlConverter.convertToPdf(htmlDoc, outStream, converterProperties);
        InputStream inStream = new ByteArrayInputStream(outStream.toByteArray());

        saveFolder += "/" + callGetter(entity, "id") + "/templates";
        if (!getFileService().createAndSaveFile(saveFolder, saveFileName + ".pdf", inStream)) {
            int counter = 0;
            boolean success = false;
            do {
                counter++;
                success = getFileService().createAndSaveFile(saveFolder, saveFileName + "-" + counter + ".pdf",
                        inStream);
            } while (!success);
            return getFileService().getFile(saveFolder, saveFileName + "-" + counter + ".pdf");
        }
        return getFileService().getFile(saveFolder, saveFileName + ".pdf");
    }

    private String parseHtmlDoc(String fileName, Object entity) {
        try {
            File htmlFile = new File(DOC_TEMPLATE_DIR + fileName);
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(htmlFile);
            doc.getDocumentElement().normalize();

            Element elm = doc.getDocumentElement();
            NodeList headList = elm.getElementsByTagName("head");
            NodeList bodyList = elm.getElementsByTagName("body");

            verifyTemplate(fileName, elm, headList, bodyList);

            Node head = headList.item(0);
            String html = "<html>" + xmlToString(head) + "<body>";

            html += nodeToString(bodyList.item(0),
                    newTempEntList(new TemplateGenEntity(entity.getClass().getSimpleName(), entity)));

            html += "</body></html>";
            return html;
        } catch (Exception exc) {
            throw new TechnicalException("DocGenerator Exception with file " + fileName, exc);
        }
    }

    private String nodeToString(Node parentNode, List<TemplateGenEntity> entities)
            throws TransformerException, ScriptException {
        NodeList nodes = parentNode.getChildNodes();
        StringBuilder string = new StringBuilder("");

        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element elm = (Element) node;
                string.append(elementToString(elm, entities));
            } else {
                string.append(insertValues(xmlToString(node), entities));
            }
        }

        return string.toString();
    }

    private String elementToString(Element elm, List<TemplateGenEntity> entities)
            throws ScriptException, TransformerException {
        if (!proofNgIf(elm, entities)) {
            return "";
        }
        if (elm.hasAttribute("ngFor")) {
            return ngForElementToString(elm, entities);
        }
        return "<" + elm.getNodeName() + getElementAttributes(elm) + ">" + nodeToString(elm, entities) + "</"
                + elm.getNodeName() + ">";
    }

    @SuppressWarnings("unchecked")
    private String ngForElementToString(Element elm, List<TemplateGenEntity> entities)
            throws ScriptException, TransformerException {
        String attrs = getElementAttributes(elm);
        String ngFor = elm.getAttribute("ngFor");
        String[] ngForList = ngFor.split(" of ");
        StringBuilder string = new StringBuilder();

        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        for (TemplateGenEntity ent : entities) {
            engine.put(ent.getEntityName(), ent.getEntity());
        }
        List<Object> list = (List<Object>) engine.eval(ngForList[1]);

        for (Object obj : list) {
            string.append("<" + elm.getNodeName() + attrs + ">"
                    + nodeToString(elm, newTempEntList(entities, new TemplateGenEntity(ngForList[0], obj))) + "</"
                    + elm.getNodeName() + ">");
        }

        return string.toString();
    }

    /**
     * 
     * @return true if no ngIf or ngIf condition is true
     * @throws ScriptException
     */
    private boolean proofNgIf(Element elm, List<TemplateGenEntity> entities) throws ScriptException {
        if (!elm.hasAttribute("ngIf")) {
            return true;
        }
        String ngIf = elm.getAttribute("ngIf");
        if (ngIf.isBlank()) {
            throw new TechnicalException("Document template contains empty ngIf!");
        }

        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        for (TemplateGenEntity ent : entities) {
            engine.put(ent.getEntityName(), ent.getEntity());
        }
        return (boolean) engine.eval(ngIf);
    }

    private String insertValues(String strIn, List<TemplateGenEntity> entities) throws ScriptException {
        StringBuilder str = new StringBuilder(strIn);
        int begin = str.indexOf("{{");
        int end = str.indexOf("}}") + 2;
        while (begin != -1 && end != 1) {
            String var = str.substring(begin, end);
            var = var.replace("{{", "");
            var = var.replace("}}", "");

            ScriptEngineManager factory = new ScriptEngineManager();
            ScriptEngine engine = factory.getEngineByName("JavaScript");
            for (TemplateGenEntity ent : entities) {
                engine.put(ent.getEntityName(), ent.getEntity());
            }
            Object val = engine.eval(var);
            String valStr = objectToStr(val);

            str = str.replace(begin, end, valStr);

            begin = str.indexOf("{{");
            end = str.indexOf("}}") + 2;
        }
        return str.toString();
    }

    private String xmlToString(Node node) throws TransformerException {
        StringWriter writer = new StringWriter();
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        transformer.setOutputProperty(OutputKeys.INDENT, "no");
        transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");
        transformer.transform(new DOMSource(node), new StreamResult(writer));
        return writer.toString();
    }

    private String replaceSpecialChars(String str) {
        str = str.replace("&gt;", ">");
        return str;
    }

    private String getElementAttributes(Element elm) {
        StringBuilder attrStr = new StringBuilder();

        NamedNodeMap attrs = elm.getAttributes();
        for (int i = 0; i < attrs.getLength(); i++) {
            Attr attr = (Attr) attrs.item(i);
            String attrName = attr.getName();
            String attrVal = attr.getValue();
            if (attrName.equals("ngIf") || attrName.equals("ngFor")) {
                continue;
            }

            attrStr.append(" " + attrName + "=\"" + attrVal + "\"");
        }

        return attrStr.toString();
    }

    private void verifyTemplate(String fileName, Element elm, NodeList head, NodeList body) {
        if (!elm.getNodeName().equalsIgnoreCase("html")) {
            throw new TechnicalException("Document template " + fileName + " doesn't starts with html node!");
        }

        if (head.getLength() != 1 || head.item(0) == null) {
            throw new TechnicalException("Document template " + fileName + " doesn't contains head!");
        }

        if (body.getLength() != 1 || head.item(0) == null) {
            throw new TechnicalException("Document template " + fileName + " doesn't contains body!");
        }
    }

    private FileService getFileService() {
        if (fileService == null) {
            fileService = ServiceLocator.locateService(FileService.class);
        }
        return fileService;
    }

    private Object callGetter(Object obj, String fieldName) {
        PropertyDescriptor pd;
        try {
            pd = new PropertyDescriptor(fieldName, obj.getClass());
            return pd.getReadMethod().invoke(obj);
        } catch (IntrospectionException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            throw new TechnicalException(e.getMessage(), e);
        }
    }

    private String objectToStr(Object obj) {
        if (obj instanceof Date) {
            return new SimpleDateFormat("dd.MM.yyyy").format(obj);
        }
        return obj.toString();
    }

    public static List<TemplateGenEntity> newTempEntList(TemplateGenEntity entity) {
        List<TemplateGenEntity> list = new ArrayList<>();
        list.add(entity);
        return list;
    }

    public static List<TemplateGenEntity> newTempEntList(List<TemplateGenEntity> entities, TemplateGenEntity entity) {
        List<TemplateGenEntity> list = new ArrayList<>();
        for (TemplateGenEntity ent : entities) {
            list.add(ent);
        }
        list.add(entity);
        return list;
    }

    public class TemplateGenEntity {
        private String entityName;
        private Object entity;

        public TemplateGenEntity(String entityName, Object entity) {
            this.entityName = entityName;
            this.entity = entity;
        }

        public String getEntityName() {
            return entityName;
        }

        public void setEntityName(String entityName) {
            this.entityName = entityName;
        }

        public Object getEntity() {
            return entity;
        }

        public void setEntity(Object entity) {
            this.entity = entity;
        }
    }
}

使用 freemarker 进行占位符替换,使用 pd4ml 进行 html 转换,对我来说效果很好。