html转word,使用模版引擎生成html,然后转word
<!-- 模版引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<!-- docx4j 核心库 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-Internal</artifactId>
<version>8.3.9</version>
</dependency>
<!-- HTML 转换支持 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-ImportXHTML</artifactId>
<version>8.3.8</version>
</dependency>
<!-- 图片处理依赖 -->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.16</version>
</dependency>
<!-- 模版引擎结束 -->
wordUtil
import cn.hutool.core.map.MapUtil;
import com.xxx.service.html2word.freemaker.HtmlGenerator;
import org.docx4j.Docx4J;
import org.docx4j.convert.in.xhtml.XHTMLImporterImpl;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.HeaderPart;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.relationships.Relationships;
import org.docx4j.wml.*;
import java.io.File;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Map;
import java.util.Optional;
/**
* Word文档工具类
* 用于生成带有页眉和内容的Word文档
*/
public class WordUtil {
private static final ObjectFactory factory = new ObjectFactory();
private static final HtmlGenerator htmlGenerator = new HtmlGenerator();
// 字体样式常量
private static final class FontStyle {
static final String HEITI = "黑体";
static final String FANGSONG = "仿宋";
static final String SONGTI = "宋体";
static final int HEADER_TITLE_SIZE = 36; // 页眉标题字号
static final int HEADER_TEXT_SIZE = 24; // 页眉正文字号
static final int TAB_POSITION = 9350; // 制表符位置
}
/**
* 生成Word文档
* @param template HTML模板名称
* @param data 数据Map,包含headerText1(页眉第一行)和headerText2(页眉第二行)
* @param outputFilePath 输出流
* @throws Exception 处理异常
*/
public static void toWord(String template, Map<String,Object> data, OutputStream outputFilePath) throws Exception {
WordprocessingMLPackage wordPackage = createWordPackage(template, data);
saveWordDocument(wordPackage, outputFilePath);
}
/**
* 创建Word文档包
*/
private static WordprocessingMLPackage createWordPackage(String template, Map<String,Object> data) throws Exception {
WordprocessingMLPackage wordPackage = WordprocessingMLPackage.createPackage();
// 添加页眉
addHeader(wordPackage, data);
// 添加文档内容
addDocumentContent(wordPackage, template, data);
return wordPackage;
}
/**
* 添加文档内容
*/
private static void addDocumentContent(WordprocessingMLPackage wordPackage, String template, Map<String,Object> data) throws Exception {
String html = htmlGenerator.generate(template, data);
XHTMLImporterImpl importer = new XHTMLImporterImpl(wordPackage);
importer.setHyperlinkStyle("Hyperlink");
wordPackage.getMainDocumentPart().getContent().addAll(importer.convert(html, null));
}
/**
* 保存Word文档
*/
private static void saveWordDocument(WordprocessingMLPackage wordPackage, OutputStream outputFilePath) throws Exception {
if (outputFilePath != null) {
Docx4J.save(wordPackage, outputFilePath, Docx4J.FLAG_SAVE_ZIP_FILE);
} else {
Docx4J.save(wordPackage, new File("output22.docx"), Docx4J.FLAG_SAVE_ZIP_FILE);
}
}
/**
* 添加页眉
*/
private static void addHeader(WordprocessingMLPackage wordPackage, Map<String,Object> data) throws Exception {
HeaderPart headerPart = new HeaderPart();
wordPackage.getMainDocumentPart().addTargetPart(headerPart);
Hdr hdr = factory.createHdr();
addHeaderTitle(hdr, MapUtil.getStr(data, "headerTitle"));
addHeaderText(hdr, MapUtil.getStr(data, "headerText"));
headerPart.setJaxbElement(hdr);
setupHeaderReference(wordPackage, headerPart);
}
/**
* 设置页眉引用
*/
private static void setupHeaderReference(WordprocessingMLPackage wordPackage, HeaderPart headerPart) throws Docx4JException {
Body body = wordPackage.getMainDocumentPart().getContents().getBody();
SectPr sectPr = Optional.ofNullable(body.getSectPr())
.orElseGet(() -> {
SectPr newSectPr = factory.createSectPr();
body.setSectPr(newSectPr);
return newSectPr;
});
MainDocumentPart mainDocumentPart = wordPackage.getMainDocumentPart();
Relationships relationships = mainDocumentPart.getRelationshipsPart().getRelationships();
relationships.getRelationship().stream()
.filter(item -> "header.xml".equals(item.getTarget()))
.findFirst()
.ifPresent(relationship -> {
HeaderReference headerRef = factory.createHeaderReference();
headerRef.setType(HdrFtrRef.DEFAULT);
headerRef.setId(relationship.getId());
sectPr.getEGHdrFtrReferences().add(headerRef);
});
}
/**
* 添加页眉标题(第一行)
*/
private static void addHeaderTitle(Hdr hdr, String headerText) {
if (headerText == null) return;
P p = factory.createP();
setParagraphAlignment(p, JcEnumeration.CENTER);
R r = factory.createR();
Text text = createText(headerText);
r.getContent().add(text);
// 设置字体样式
RPr rpr = factory.createRPr();
setFont(rpr, FontStyle.HEITI, FontStyle.HEADER_TITLE_SIZE, true);
r.setRPr(rpr);
p.getContent().add(r);
hdr.getContent().add(p);
}
/**
* 添加页眉文本(第二行)
*/
private static void addHeaderText(Hdr hdr, String headerText) {
if (headerText == null) return;
P p = factory.createP();
PPr ppr = factory.createPPr();
p.setPPr(ppr);
// 添加左侧文本
addLeftText(p, headerText);
// 添加制表符
addTab(p);
// 添加右侧页码
addPageNumber(p);
// 设置制表符位置
setTabStop(ppr);
hdr.getContent().add(p);
}
/**
* 添加左侧文本
*/
private static void addLeftText(P p, String text) {
R r = factory.createR();
r.getContent().add(createText(text));
RPr rpr = factory.createRPr();
setFont(rpr, FontStyle.FANGSONG, FontStyle.HEADER_TEXT_SIZE, false);
r.setRPr(rpr);
p.getContent().add(r);
}
/**
* 添加页码
*/
private static void addPageNumber(P p) {
R r = factory.createR();
// 添加"第"字
r.getContent().add(createText("第"));
// 添加页码域
addPageField(r);
// 添加"页"字
r.getContent().add(createText("页"));
// 设置字体
RPr rpr = factory.createRPr();
setFont(rpr, FontStyle.SONGTI, FontStyle.HEADER_TEXT_SIZE, false);
r.setRPr(rpr);
p.getContent().add(r);
}
/**
* 添加页码域
*/
private static void addPageField(R r) {
FldChar fldCharBegin = factory.createFldChar();
fldCharBegin.setFldCharType(STFldCharType.BEGIN);
r.getContent().add(fldCharBegin);
Text pageNum = createText(" PAGE ");
pageNum.setSpace("preserve");
r.getContent().add(pageNum);
FldChar fldCharEnd = factory.createFldChar();
fldCharEnd.setFldCharType(STFldCharType.END);
r.getContent().add(fldCharEnd);
}
/**
* 创建文本对象
*/
private static Text createText(String value) {
Text text = factory.createText();
text.setValue(value);
return text;
}
/**
* 添加制表符
*/
private static void addTab(P p) {
R r = factory.createR();
R.Tab tab = factory.createRTab();
r.getContent().add(tab);
p.getContent().add(r);
}
/**
* 设置制表符位置
*/
private static void setTabStop(PPr ppr) {
Tabs tabs = factory.createTabs();
CTTabStop tabStop = factory.createCTTabStop();
tabStop.setVal(STTabJc.RIGHT);
tabStop.setPos(BigInteger.valueOf(FontStyle.TAB_POSITION));
tabs.getTab().add(tabStop);
ppr.setTabs(tabs);
}
/**
* 设置段落对齐方式
*/
private static void setParagraphAlignment(P p, JcEnumeration alignment) {
PPr ppr = factory.createPPr();
Jc jc = factory.createJc();
jc.setVal(alignment);
ppr.setJc(jc);
p.setPPr(ppr);
}
/**
* 设置字体样式
*/
private static void setFont(RPr rpr, String fontName, int fontSize, boolean bold) {
// 设置字体
RFonts rFonts = factory.createRFonts();
rFonts.setAscii(fontName);
rFonts.setEastAsia(fontName);
rpr.setRFonts(rFonts);
// 设置字号
HpsMeasure size = new HpsMeasure();
size.setVal(BigInteger.valueOf(fontSize));
rpr.setSz(size);
// 设置加粗
if (bold) {
BooleanDefaultTrue bdt = new BooleanDefaultTrue();
bdt.setVal(Boolean.TRUE);
rpr.setB(bdt);
}
}
}
import java.net.URL;
public class ResourceLoader {
public static String CLASS_PATH_PREFIX ="classpath:";
/**
* classpath中获取资源
* @Title: getResource
* @Description: classpath中获取资源
* @param resource
* @return
* @author lizp-c
*/
public static URL getResource(String resource) {
ClassLoader classLoader = null;
classLoader = Thread.currentThread().getContextClassLoader();
return classLoader.getResource(resource);
}
/**
* classpath 中搜索路径
* @Title: getPath
* @Description:
* @param resource
* @return
* @author lizp-c
*/
public static String getPath(String resource){
if(resource!=null){
if(resource.startsWith(CLASS_PATH_PREFIX)){
resource = getPath("")+resource.replaceAll(CLASS_PATH_PREFIX, "");
}
}
URL url = getResource(resource);
if(url==null)
return null;
return url.getPath().replaceAll("%20", " ");
}
/**
*
* @Title: getPath
* @Description:
* @param resource
* @param clazz
* @return
* @author lizp-c
*/
public static String getPath(String resource,Class clazz){
URL url = getResource(resource, clazz);
if(url==null)
return null;
return url.getPath().replaceAll("%20", " ");
}
/**
* 指定class中获取资源
* @Title: getResource
* @Description: 指定class中获取资源
* @param resource
* @param clazz
* @return
* @author lizp-c
*/
public static URL getResource(String resource,Class clazz){
return clazz.getResource(resource);
}
/**
* If running under JDK 1.2 load the specified class using the
* <code>Thread</code> <code>contextClassLoader</code> if that fails try
* Class.forname. Under JDK 1.1 only Class.forName is used.
*
*/
public static Class loadClass(String clazz) throws ClassNotFoundException {
return Class.forName(clazz);
}
import freemarker.template.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
/**
* freemark配置
* @ClassName: FreemarkerConfiguration
* @Description: freemark配置
* @author lizp-c
*/
public class FreemarkerConfiguration {
private static final Logger logger = LoggerFactory.getLogger(FreemarkerConfiguration.class);
private static Configuration config = null;
/**
* 获取 FreemarkerConfiguration
*
* @Title: getConfiguation
* @Description:
* @return
*/
public static synchronized Configuration getConfiguration() {
if (config == null) {
setConfiguation();
}
return config;
}
/**
* 设置 配置
* @Title: setConfiguation
* @Description:
*/
private static void setConfiguation() {
config = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
String path = ResourceLoader.getPath("");
try {
config.setDirectoryForTemplateLoading(new File(path));
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;
public class HtmlGenerator {
/**
* Generate html string.
*
* @param template the name of freemarker teamlate.
* @param variables the data of teamlate.
* @return htmlStr
* @throws IOException
* @throws TemplateException
* @throws Exception
*/
public String generate(String template, Map<String,Object> variables) throws IOException, TemplateException{
BufferedWriter writer = null;
String htmlContent;
try{
Configuration config = FreemarkerConfiguration.getConfiguration();
config.setClassForTemplateLoading(this.getClass(),"/template");
config.setDefaultEncoding("UTF-8");
Template tp = config.getTemplate(template);
StringWriter stringWriter = new StringWriter();
writer = new BufferedWriter(stringWriter);
tp.setEncoding("UTF-8");
tp.process(variables, writer);
htmlContent = stringWriter.toString();
writer.flush();
}finally{
if(writer!=null)
writer.close();
}
return htmlContent;
}
}
resources下放置template目录,然后放置html模版。
<html>
<head>
<title></title>
</head>
<body>
<div>
<table cellspacing="0" cellpadding="0">
<tr style="height:28.35pt">
<td>
<p>
<span>工程名称</span>
</p>
</td>
<td colspan="3">
<p >
<span>${name}</span>
</p>
</td>
</tr>
</table>