Bootstrap

java操作doc——java利用Aspose.Words操作Word文档并动态设置单元格合并

在实际工作中,如果业务线是管理类项目或者存在大量报表需要导出的业务时,可以借助第三方插件实现其对应功能。

尤其是需要对word文档的动态操作或者模板数据的定向合并,使用Aspose会相对来说容易一些,而且相关文档比较完整,具体如何使用,请参看如下案例:

下图展示的是数据模板图1:

一、什么是Aspose?

官网地址:Aspose.Words 产品系列 | Aspose API 参考

文档处理是个人和专业人士必备的技能。然而,手动文档处理可能非常耗时且容易。Aspose.Words Java 文档处理教程提供了使用代码自动生成和管理文档的全面指南。

这些教程涵盖了广泛的主题,从基本的表格处理到高级文档合并和水印。Aspose.Words 教程注重实际实施,使开发人员能够高效地简化文档处理任务并提供定制的解决方案。

无论您是初学者还是经验丰富的开发人员,Aspose.Words Java 文档处理教程都可以帮助您将文档处理技能提升到更高的水平。

二、springboot项目如何引入?

1.获得许可

2.引入pom

        <dependency>
            <groupId>aspose</groupId>
            <artifactId>words</artifactId>
            <version>21.4.0</version>
            <classifier>jdk17</classifier>
        </dependency>

 三、具体使用案例

1.集成授权

    public static void setAuthLicense() {
        try {
            //根目录下的授权文件
            ClassPathResource resource = new ClassPathResource("授权文件路径");
            URL licenseFileNameURL = resource.getUrl();
            String savePath = java.net.URLDecoder.decode(licenseFileNameURL.toString(), "utf-8");
            String licenseFileName = savePath.toString().substring(6);
            if (new File(licenseFileName).exists()) {
                License license = new License();
                license.setLicense(licenseFileName);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("aspose 授权异常");
        }
    }

2.设置模板绑定字段

    /**
     * 设置模板绑定字段
     *
     * @param document 默认的文档对象
     * @param fieldname 模板key
     * @param fieldvalue 模板value
     * @throws Exception
     */
    public static void mergeField(Document document, String fieldname,
                                  Object fieldvalue) throws Exception {
        document.getMailMerge().execute(new String[]{fieldname},
                new Object[]{fieldvalue});
    }

3.配置动态表格,主要用于集合遍历(数据列表展示)

public class ResultSetDataSource {
	
	public ResultSetDataSource(){
		
	}
	
	/**
	 * 处理自定义的数据源
	 * @param document 文档
	 * @param tableStartFieldName tableStart的域名
	 * @param fieldNames 列的域名数组
	 * @param fieldValueArray 域值的数组
	 * @throws Exception
	 */
	public void executeWithRegions(Document document, String tableStartFieldName, String[] fieldNames, String[][] fieldValueArray) throws Exception {
		 java.sql.ResultSet resultSet = createCachedRowSet(fieldNames);
		 int length = fieldValueArray.length;
		 for(int i = 0; i < length;i ++){
			 addRow(resultSet,fieldValueArray[i]);
		 }
		 com.aspose.words.net.System.Data.DataTable table = new com.aspose.words.net.System.Data.DataTable(resultSet, tableStartFieldName);
		 document.getMailMerge().executeWithRegions(table);
	}
	
	public  java.sql.ResultSet createCachedRowSet(String[] columnNames) throws Exception
    {
        RowSetMetaDataImpl metaData = new RowSetMetaDataImpl();
        metaData.setColumnCount(columnNames.length);
        for (int i = 0; i < columnNames.length; i++)
        {
            metaData.setColumnName(i + 1, columnNames[i]);
            metaData.setColumnType(i + 1, java.sql.Types.VARCHAR);
        }
		CachedRowSet rowSet = RowSetProvider.newFactory().createCachedRowSet();
        rowSet.setMetaData(metaData);
        return rowSet;
    }
	
	public void addRow(java.sql.ResultSet resultSet, String[] values) throws Exception
    {
        resultSet.moveToInsertRow();
        for (int i = 0; i < values.length; i++) {
			resultSet.updateString(i + 1, values[i]);
		}
        resultSet.insertRow();
        resultSet.moveToCurrentRow();
        resultSet.last();
    }

4.业务实现

public void testExportDoc(@RequestParam("id") String testId, HttpServletResponse response){
        //aspose 授权
        AsposeWordLib.setAuthLicense();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        //插入动态表格
        ResultSetDataSource dataSource = new ResultSetDataSource();
        OutputStream responseOutputStream = null;
        try{
            //基础数据赋值
            String uuid = testId;
            RequestLogUtils.setAttribute("code",uuid);
            ClassPathResource pathResource = new ClassPathResource("test01/test01.docx");
            InputStream in = pathResource.getInputStream();
            Document doc = new Document(in);
            WordLib.mergeField(doc, "code",uuid);
            WordLib.mergeField(doc, "productmodel", "2024-M11-");
            WordLib.mergeField(doc, "productname", "产品名称");
            WordLib.mergeField(doc, "username", "用户名");
            Date date = new Date();
            WordLib.mergeField(doc, "applytimestr",formatter.format(date));
            WordLib.mergeField(doc, "num",556251);

            //集合数据动态赋值
            String [] arrDefault = {"seq","name","sign"};
            String [][] arrayTab = new String[2][3];
            for (int i = 0; i < 2; i++) {
                arrayTab[i][0] = i + "";
                arrayTab[i][1] = "物资" + i;
                arrayTab[i][2] = "sign" + i;
            }
            dataSource.executeWithRegions(doc,"t1",arrDefault, arrayTab);

            String [] arrMerge = {"content","status","userStr"};
            String [][] arrayTabMerge = new String[3][3];
            for (int i = 0; i < 3; i++) {
                arrayTabMerge[i][0] = i + "、内容" + i;
                if(i%2 == 0){
                    arrayTabMerge[i][1] = "☑ 正常 ☐ 停用";
                }else {
                    arrayTabMerge[i][1] = "☐ 正常 ☑ 停用";
                }
                arrayTabMerge[i][2] = "用户";
            }
            dataSource.executeWithRegions(doc,"t2",arrMerge, arrayTabMerge);

            //默认的行数 = 固定表格数(从1开始计算)+动态计算的arrayTab长度
            int defaultRow = 7 + arrayTab.length;
            //需要合并的行数
            int dynamicMergeRowsCM = arrayTabMerge.length;
            //内容列表-详细内容单元格合并
            DocumentBuilder documentBuilder = new DocumentBuilder(doc);
            //移动到第一个表格的第defaultStaIndex行的第columnIndex列(第一个格子)的第一个字符(doc表格外的标题不做计算)
            documentBuilder.moveToCell(0, defaultRow, 0, 0);
            documentBuilder.getCellFormat().setVerticalMerge(CellMerge.FIRST);
            for(int i = 0; i < dynamicMergeRowsCM; i++ ) {
                documentBuilder.moveToCell(0, defaultRow + i, 0, 0);
                documentBuilder.getCellFormat().setVerticalMerge(CellMerge.PREVIOUS);
            }
            //内容列表-操作人单元格合并
            //移动到第一个表格的第defaultStaIndex行的第columnIndex列(第一个格子)的第一个字符(doc表格外的标题不做计算)
            documentBuilder.moveToCell(0, defaultRow, 3, 0);
            documentBuilder.getCellFormat().setVerticalMerge(CellMerge.FIRST);
            for(int i = 0; i < dynamicMergeRowsCM; i++ ) {
                documentBuilder.moveToCell(0, defaultRow + i, 3, 0);
                documentBuilder.getCellFormat().setVerticalMerge(CellMerge.PREVIOUS);
            }

            //返回前台
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            try {
                doc.save(outputStream, SaveFormat.DOCX);
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 建立一个文件的输出的输出流
            ByteArrayOutputStream byteArrayOutputStream = (ByteArrayOutputStream) outputStream;
            byte[] aByte = byteArrayOutputStream.toByteArray();

            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + uuid +".docx");
            String returnType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
            response.setContentType(returnType);
            responseOutputStream = response.getOutputStream();
            responseOutputStream.write(aByte);
            responseOutputStream.flush();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

5.单元格合并问题(仅展示垂直合并)

  • 解决不连续合并的问题(包括垂直和水平合并两种)
//默认的行数 = 固定表格数(从1开始计算)+动态计算的arrayTab长度
int defaultRow = 7 + arrayTab.length;
//需要合并的行数
int dynamicMergeRowsCM = arrayTabMerge.length;
//内容列表-详细内容单元格合并
DocumentBuilder documentBuilder = new DocumentBuilder(doc);
//移动到第一个表格的第defaultStaIndex行的第columnIndex列(第一个格子)的第一个字符(doc表格外的标题不做计算)
documentBuilder.moveToCell(0, defaultRow, 0, 0);
documentBuilder.getCellFormat().setVerticalMerge(CellMerge.FIRST);
for(int i = 0; i < dynamicMergeRowsCM; i++ ) {
    documentBuilder.moveToCell(0, defaultRow + i, 0, 0);
    documentBuilder.getCellFormat().setVerticalMerge(CellMerge.PREVIOUS);
}
  • 对于连续合并的模板(此方案必须基于保存的文件)
public static void mergeCells(Cell startCell, Cell endCell) {
        Table parentTable = startCell.getParentRow().getParentTable();
        Point startCellPos = new Point(startCell.getParentRow().indexOf(startCell), parentTable.indexOf(startCell.getParentRow()));
        Point endCellPos = new Point(endCell.getParentRow().indexOf(endCell), parentTable.indexOf(endCell.getParentRow()));

        Rectangle mergeRange = new Rectangle(Math.min(startCellPos.x, endCellPos.x), Math.min(startCellPos.y, endCellPos.y), Math.abs(endCellPos.x - startCellPos.x) + 1,
                Math.abs(endCellPos.y - startCellPos.y) + 1);

        for (Row row : parentTable.getRows()) {
            for (Cell cell : row.getCells()) {
                Point currentPos = new Point(row.indexOf(cell), parentTable.indexOf(row));

                if (mergeRange.contains(currentPos)) {
                    if (currentPos.x == mergeRange.x)
                        cell.getCellFormat().setHorizontalMerge(CellMerge.FIRST);
                    else
                        cell.getCellFormat().setHorizontalMerge(CellMerge.PREVIOUS);

                    if (currentPos.y == mergeRange.y)
                        cell.getCellFormat().setVerticalMerge(CellMerge.FIRST);
                    else
                        cell.getCellFormat().setVerticalMerge(CellMerge.PREVIOUS);
                }
            }
        }
    }

//具体实现方案
public static void main(String[] args) {
            ClassPathResource re = new ClassPathResource("exportDocTemplate/new241119/test02.docx");
            InputStream in = re.getInputStream();
            Document targetDoc = new Document(in);
            Table table = targetDoc.getFirstSection().getBody().getTables().get(0);
            Cell cellStartRange = table.getRows().get(12).getCells().get(0); //开始的行和列
            Cell cellEndRange = table.getRows().get(13).getCells().get(0); //结束的行列
            ReportExportCommonUtils.mergeCells(cellStartRange, cellEndRange);
            Cell s1 = table.getRows().get(12).getCells().get(3); //开始的行和列
            Cell e1 = table.getRows().get(13).getCells().get(3); //结束的行列
            ReportExportCommonUtils.mergeCells(s1, e1);
            Cell s2 = table.getRows().get(10).getCells().get(0); //开始的行和列
            Cell e2 = table.getRows().get(11).getCells().get(0); //结束的行列
            ReportExportCommonUtils.mergeCells(s2, e2);
            Cell s3 = table.getRows().get(10).getCells().get(3); //开始的行和列
            Cell e3 = table.getRows().get(11).getCells().get(3); //结束的行列
            mergeCells(s3, e3);
    }

6.最终效果展示

7.需要主要的问题点

  • 水平合并关键代码
documentBuilder.getCellFormat().setHorizontalMerge(CellMerge.FIRST);
  • 在编辑数据模板时需要切换域代码,也就是文中指定的数据模板图1
;