|
@@ -0,0 +1,324 @@
|
|
|
+package com.dayou.utils;
|
|
|
+
|
|
|
+import com.aspose.words.*;
|
|
|
+import com.aspose.words.net.System.Data.DataRow;
|
|
|
+import com.aspose.words.net.System.Data.DataTable;
|
|
|
+
|
|
|
+import java.awt.*;
|
|
|
+import java.awt.geom.AffineTransform;
|
|
|
+import java.awt.image.BufferedImage;
|
|
|
+import java.awt.image.ColorModel;
|
|
|
+import java.awt.image.WritableRaster;
|
|
|
+import java.beans.PropertyDescriptor;
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.UUID;
|
|
|
+
|
|
|
+public class AsposeWordUtil {
|
|
|
+
|
|
|
+ private AsposeWordUtil() {
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调整bufferedimage大小
|
|
|
+ * @param source BufferedImage 原始image
|
|
|
+ * @param targetW int 目标宽
|
|
|
+ * @param targetH int 目标高
|
|
|
+ * @param flag boolean 是否同比例调整
|
|
|
+ * @return BufferedImage 返回新image
|
|
|
+ */
|
|
|
+ public static BufferedImage resizeBufferedImage(BufferedImage source, int targetW, int targetH, boolean flag) {
|
|
|
+ int type = source.getType();
|
|
|
+ BufferedImage target = null;
|
|
|
+ double sx = (double) targetW / source.getWidth();
|
|
|
+ double sy = (double) targetH / source.getHeight();
|
|
|
+ if (flag && sx > sy) {
|
|
|
+ sx = sy;
|
|
|
+ targetW = (int) (sx * source.getWidth());
|
|
|
+ } else if(flag && sx <= sy){
|
|
|
+ sy = sx;
|
|
|
+ targetH = (int) (sy * source.getHeight());
|
|
|
+ }
|
|
|
+ if (type == BufferedImage.TYPE_CUSTOM) {
|
|
|
+ ColorModel cm = source.getColorModel();
|
|
|
+ WritableRaster raster = cm.createCompatibleWritableRaster(targetW, targetH);
|
|
|
+ boolean alphaPremultiplied = cm.isAlphaPremultiplied();
|
|
|
+ target = new BufferedImage(cm, raster, alphaPremultiplied, null);
|
|
|
+ } else {
|
|
|
+ target = new BufferedImage(targetW, targetH, type);
|
|
|
+ }
|
|
|
+ Graphics2D g = target.createGraphics();
|
|
|
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
|
|
+ g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
|
|
|
+ g.dispose();
|
|
|
+ return target;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 填充 word 模板(object数据格式)
|
|
|
+ * @param modelWordByte word模版二进制文件
|
|
|
+ * @param obj 要填充的数据
|
|
|
+ * @return 组合数据之后的word二进制
|
|
|
+ */
|
|
|
+ public static byte[] fillWordDataByDomain(byte[] modelWordByte, Object obj) {
|
|
|
+ try {
|
|
|
+ Class<?> aClass = obj.getClass();
|
|
|
+ Field[] fields = aClass.getDeclaredFields();
|
|
|
+ Map<String, Object> data = new HashMap<>(fields.length);
|
|
|
+ for (Field field : fields) {
|
|
|
+ PropertyDescriptor pd = new PropertyDescriptor(field.getName(), aClass);
|
|
|
+ Method method = pd.getReadMethod();
|
|
|
+ String key = field.getName();
|
|
|
+ Object value = method.invoke(obj);
|
|
|
+ if (value != null) {
|
|
|
+ data.put(key, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return fillWordDataByMap(modelWordByte, data);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ return new byte[0];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 填充 word 模板(map数据格式)
|
|
|
+ * @param file word二进制
|
|
|
+ * @param data 要填充的数据
|
|
|
+ * @return 组合数据之后的word二进制
|
|
|
+ */
|
|
|
+ public static byte[] fillWordDataByMap(byte[] file, Map<String, Object> data) throws Exception {
|
|
|
+ byte[] ret = null;
|
|
|
+ if (data == null || data.isEmpty()) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ try (InputStream is = new ByteArrayInputStream(file);
|
|
|
+ ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
|
|
+ Document doc = new Document(is);
|
|
|
+ DocumentBuilder builder = new DocumentBuilder(doc);
|
|
|
+ Map<String, String> toData = new HashMap<>();
|
|
|
+ for (Map.Entry<String, Object> en : data.entrySet()) {
|
|
|
+ String key = en.getKey();
|
|
|
+ Object value = en.getValue();
|
|
|
+
|
|
|
+ if (value instanceof List) {
|
|
|
+ //写入表数据
|
|
|
+ DataTable dataTable = fillListData((List) value, key);
|
|
|
+ doc.getMailMerge().executeWithRegions(dataTable);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (value instanceof BufferedImage) {
|
|
|
+ builder.moveToMergeField(key);
|
|
|
+ builder.insertImage((BufferedImage) value);
|
|
|
+ }
|
|
|
+
|
|
|
+ String valueStr = String.valueOf(en.getValue());
|
|
|
+ if (value == null || value.equals("null")) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ toData.put(key, valueStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ String[] fieldNames = new String[toData.size()];
|
|
|
+ String[] values = new String[toData.size()];
|
|
|
+
|
|
|
+ int i = 0;
|
|
|
+ for (Map.Entry<String, String> entry : toData.entrySet()) {
|
|
|
+ fieldNames[i] = entry.getKey();
|
|
|
+ values[i] = entry.getValue();
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ //合并数据
|
|
|
+ doc.getMailMerge().execute(fieldNames, values);
|
|
|
+ doc.save(out, SaveOptions.createSaveOptions(SaveFormat.DOCX));
|
|
|
+ ret = out.toByteArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 封装 list 数据到 word 模板中(word表格)
|
|
|
+ * @param list 数据
|
|
|
+ * @param tableName 表格列表变量名称
|
|
|
+ * @return word表格数据DataTable
|
|
|
+ */
|
|
|
+ private static DataTable fillListData(List<Object> list, String tableName) throws Exception {
|
|
|
+
|
|
|
+ //创建DataTable
|
|
|
+ DataTable dataTable = new DataTable(tableName);
|
|
|
+ // 获取类的Class对象
|
|
|
+ Class<?> clazz = list.get(0).getClass();
|
|
|
+
|
|
|
+ // 获取类的所有属性
|
|
|
+ Field[] fields = clazz.getDeclaredFields();
|
|
|
+
|
|
|
+ // 遍历集合对象类的所有属性并添加为DataTable的字段
|
|
|
+ for (Field field : fields) {
|
|
|
+ // 添加字段到DataTable,并将属性名作为字段名
|
|
|
+ dataTable.getColumns().add(field.getName());
|
|
|
+ }
|
|
|
+ // 循环对象集合
|
|
|
+ for (Object obj : list) {
|
|
|
+ //创建DataRow,封装该行数据
|
|
|
+ DataRow dataRow = dataTable.newRow();
|
|
|
+ // 向dataRow中添加对象每个属性的值
|
|
|
+ for (int i = 0; i < fields.length; i++) {
|
|
|
+ fields[i].setAccessible(true);
|
|
|
+ // 通过下标设置(我们的类有几个属性,DataTable就创建了几个字段,所以这里可以直接通过下标设置值,因为创建字段的顺序和我们设置值的顺序一样)
|
|
|
+ dataRow.set(i, fields[i].get(obj));
|
|
|
+ // 还可以通过字段名设置(如下)
|
|
|
+ //dataRow.set(fields[i].getName(), fields[i].get(obj));
|
|
|
+ }
|
|
|
+ dataTable.getRows().add(dataRow);
|
|
|
+ }
|
|
|
+ return dataTable;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @Description 向书签后插入文档
|
|
|
+ * @param mainDoc 主文档
|
|
|
+ * @param tobeInserted 拼接的文档
|
|
|
+ * @param isPortrait 纸张方向
|
|
|
+ * @param bookmark 书签
|
|
|
+ */
|
|
|
+ public static Document insertDocumentAfterBookMark(Document mainDoc, Document tobeInserted, String bookmark, boolean isPortrait)
|
|
|
+ throws Exception {
|
|
|
+ if (mainDoc == null) {
|
|
|
+ return null;
|
|
|
+ } else if (tobeInserted == null) {
|
|
|
+ return mainDoc;
|
|
|
+ } else {
|
|
|
+ //构建新文档
|
|
|
+ DocumentBuilder mainDocBuilder = new DocumentBuilder(mainDoc);
|
|
|
+ // 判断书签是否为空
|
|
|
+ if (bookmark != null && !bookmark.isEmpty()) { // 书签位置插入
|
|
|
+ //获取到书签
|
|
|
+ BookmarkCollection bms = mainDoc.getRange().getBookmarks();
|
|
|
+ Bookmark bm = bms.get(bookmark);
|
|
|
+ if (bm != null) {
|
|
|
+ // 移动光标到书签
|
|
|
+ mainDocBuilder.moveToBookmark(bookmark, true, false);
|
|
|
+ // 在文档中插入段落分隔符
|
|
|
+ mainDocBuilder.writeln();
|
|
|
+ // 设置纸张方向
|
|
|
+// if (isPortrait) {
|
|
|
+// //纵向
|
|
|
+// mainDocBuilder.getPageSetup().setOrientation(Orientation.PORTRAIT);
|
|
|
+// mainDocBuilder.insertBreak(BreakType.SECTION_BREAK_NEW_PAGE);
|
|
|
+// } else {
|
|
|
+// //横向
|
|
|
+// mainDocBuilder.getPageSetup().setOrientation(Orientation.LANDSCAPE);
|
|
|
+// mainDocBuilder.insertBreak(BreakType.SECTION_BREAK_NEW_PAGE);
|
|
|
+// }
|
|
|
+ //获取到插入的位置
|
|
|
+ Node insertAfterNode = mainDocBuilder.getCurrentParagraph().getPreviousSibling();
|
|
|
+
|
|
|
+ insertDocumentAfterNode(insertAfterNode, mainDoc, tobeInserted);
|
|
|
+ }
|
|
|
+ } else { // 文档末尾拼接
|
|
|
+ appendDoc(mainDoc, tobeInserted, true);
|
|
|
+ }
|
|
|
+ return mainDoc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @Description 在指定节点后插入word文档
|
|
|
+ * @param insertAfterNode 插入的位置
|
|
|
+ * @param mainDoc 主word文件
|
|
|
+ * @param srcDoc 引用的word文件
|
|
|
+ */
|
|
|
+ public static void insertDocumentAfterNode(Node insertAfterNode, Document mainDoc, Document srcDoc) throws Exception {
|
|
|
+ // Aspose定义的nodeType 8 和 5 指的是段落或者表格,插入word应该只能在这两个位置插入,具体没测试
|
|
|
+ if (insertAfterNode.getNodeType() != 8 & insertAfterNode.getNodeType() != 5) {
|
|
|
+ throw new Exception("The destination node should be either a paragraph or table.");
|
|
|
+ } else {
|
|
|
+ CompositeNode dstStory = insertAfterNode.getParentNode();
|
|
|
+
|
|
|
+ while (null != srcDoc.getLastSection().getBody().getLastParagraph()
|
|
|
+ && !srcDoc.getLastSection().getBody().getLastParagraph().hasChildNodes()) {
|
|
|
+ // 移除节点
|
|
|
+ srcDoc.getLastSection().getBody().getLastParagraph().remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将节点从srcDoc文档中导入
|
|
|
+ NodeImporter importer = new NodeImporter(srcDoc, mainDoc, 1);
|
|
|
+ int sectCount = srcDoc.getSections().getCount();
|
|
|
+
|
|
|
+
|
|
|
+ for (int sectIndex = 0; sectIndex < sectCount; ++sectIndex) {
|
|
|
+ Section srcSection = srcDoc.getSections().get(sectIndex);
|
|
|
+ int nodeCount = srcSection.getBody().getChildNodes().getCount();
|
|
|
+
|
|
|
+ for (int nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) {
|
|
|
+ Node srcNode = srcSection.getBody().getChildNodes().get(nodeIndex);
|
|
|
+ Node newNode = importer.importNode(srcNode, true);
|
|
|
+ dstStory.insertAfter(newNode, insertAfterNode);
|
|
|
+ insertAfterNode = newNode;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @Description 文档拼接
|
|
|
+ * @param dstDoc
|
|
|
+ * @param srcDoc
|
|
|
+ * @param includeSection
|
|
|
+ */
|
|
|
+ private static void appendDoc(Document dstDoc, Document srcDoc, boolean includeSection) throws Exception {
|
|
|
+ if (includeSection) {
|
|
|
+ for (Section srcSection : srcDoc.getSections()) {
|
|
|
+ Node dstNode = dstDoc.importNode(srcSection, true, 0);
|
|
|
+ dstDoc.appendChild(dstNode);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Node node = dstDoc.getLastSection().getBody().getLastParagraph();
|
|
|
+ if (node == null) {
|
|
|
+ node = new Paragraph(srcDoc);
|
|
|
+ dstDoc.getLastSection().getBody().appendChild(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (node.getNodeType() != 8 & node.getNodeType() != 5) {
|
|
|
+ throw new Exception("Use appendDoc(dstDoc, srcDoc, true) instead of appendDoc(dstDoc, srcDoc, false)");
|
|
|
+ }
|
|
|
+
|
|
|
+ insertDocumentAfterNode(node, dstDoc, srcDoc);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新文档的目录页码
|
|
|
+ * @param doc 文档
|
|
|
+ * @return Document
|
|
|
+ * @throws Exception 抛出异常
|
|
|
+ */
|
|
|
+ public static Document updateCatalogueLink(Document doc) throws Exception {
|
|
|
+ // 这里的Field和反射的重名了
|
|
|
+ // 循环整个目录并更新页码
|
|
|
+ for (com.aspose.words.Field field : doc.getRange().getFields()){
|
|
|
+ if (field.getType() == FieldType.FIELD_TOC)
|
|
|
+ {
|
|
|
+ FieldToc toc = (FieldToc)field;
|
|
|
+ //定位toc就只更新页码
|
|
|
+ toc.updatePageNumbers();
|
|
|
+ } else{
|
|
|
+ // 更新目录(会掉样式)
|
|
|
+ field.update();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return doc;
|
|
|
+ }
|
|
|
+}
|