关于JAVA POI解析WPS docx文档中的table(复杂表格包含单元格横向,纵向的合并)

java poi 专栏收录该内容
1 篇文章 0 订阅

关于JAVA POI解析WPS docx文档中的table(复杂表格包含单元格横向,纵向的合并)

首先,关于poi解析表格先阅读一篇他人的博客

使用poi读取word2007(.docx)中的复杂表格.
这篇博客提到了,如何用poi将单元格合并。

static void mergeCellHorizontally(XWPFTable table, int row, int fromCol, int toCol) {
        for(int colIndex = fromCol; colIndex <= toCol; colIndex++){
            CTHMerge hmerge = CTHMerge.Factory.newInstance();
            if(colIndex == fromCol){
                // The first merged cell is set with RESTART merge value
                hmerge.setVal(STMerge.RESTART);
            } else {
                // Cells which join (merge) the first one, are set with CONTINUE
                hmerge.setVal(STMerge.CONTINUE);
            }
            XWPFTableCell cell = table.getRow(row).getCell(colIndex);
            // Try getting the TcPr. Not simply setting an new one every time.
            CTTcPr tcPr = cell.getCTTc().getTcPr();
            if (tcPr != null) {
                tcPr.setHMerge(hmerge);
            } else {
                // only set an new TcPr if there is not one already
                tcPr = CTTcPr.Factory.newInstance();
                tcPr.setHMerge(hmerge);
                cell.getCTTc().setTcPr(tcPr);
            }
        }
    }

记住CTTcPr 这个类,他包装了单元格的格式信息
CTTcPr tcPr = cell.getCTTc().getTcPr();

但是,当我开始解析wps的docx后发现横向合并的单元格信息根本拿不到。就是HMerge这个对象。一直为空。VMerge倒是没问题。

打开docx文件的xml文件一看发现了问题

	<w:tc>
      <w:tcPr>
        <w:tcW w:w="1000" w:type="dxa"/>
        <w:hMerge w:val="restart"/>
      </w:tcPr>
      <w:p>
        <w:r>
          <w:t>row 1, col 2</w:t>
        </w:r>
      </w:p>
    </w:tc>

这是我们期望的格式

	<w:tc>
      <w:tcPr>
        <w:tcW w:w="1000" w:type="dxa"/>
        <w:gridSpan w:val="2"/>
      </w:tcPr>
      <w:p>
        <w:r>
          <w:t>row 1, col 2</w:t>
        </w:r>
      </w:p>
    </w:tc>

wps docx打开是这样的。区别在于hMerge 和gridSpan

hMerge不释放td对象。比如一行5列。1-2合并
那么在poi里得到5个cell对象。1的hMerge枚举为started。2为continue。
gridSpan反之。1-2合并,只能拿到4个cell。

虽然费了一般波折,总算可以解析出来了。废话不多
说。先看效果,再上代码

在这里插入图片描述
这是docx文档中的表格

在这里插入图片描述
这是解析,并用html生成的table

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.junit.Test;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * poi 解析wps docx中的table
 */

public class poiTest {

    @Test
    public void poiTest() throws IOException {
        File file = new File("C:\\Users\\xxx\\xxx\\xxx.docx");
        FileInputStream fis = new FileInputStream(file);
        XWPFDocument document = new XWPFDocument(fis);
        List<XWPFTable> tables = document.getTables();
        XWPFTable table = tables.get(0);
        List<XWPFTableRow> rows = table.getRows();
        /**
         * 声明一个集合数组。
         * 数组代表行,集合代表列
         * map装解析出来的单元格对象
         */
        List<Map<String, Object>>[] tableParse = new ArrayList[rows.size()];
        /**
         * 解析横向单元格合并
         */
        hMerger(tableParse,rows);
        /**
         * 解析纵向单元格合并
         */
        vMerger(tableParse,rows);
        System.out.println(excelToHtml(tableParse));
    }

    /**
     * tcPr.getVMerge().getVal() == STMerge.CONTINUE
     * 当某个单元格的vMerge属性为STMerge.CONTINUE时
     * 代表是被合并的纵向单元格
     *
     * @param tableParse
     * @param c 列下标
     * @param r 行下标
     */
    private void vMergeAdd(List<Map<String, Object>>[] tableParse,int c, int r) {
        for (int a = r-1; a >= 0; a--) {
            Map<String, Object> cell = tableParse[a].get(c);
            if(cell.get("type").equals(1)){
                cell.put("row",(int)cell.get("row")+1);
                return;
            }
        }
    }

    /**
     * 拼装成html字符串
     * @param tableParse
     * @return
     */

    private String excelToHtml(List<Map<String, Object>>[] tableParse){
        StringBuilder sb = new StringBuilder("<table border='1px' border-color='grey' style='width:100%'>");
        for(int r = 0;r<tableParse.length;r++){
            sb.append("<tr>");
            List<Map<String,Object>> cells = tableParse[r];
            for(int c = 0; c< cells.size();c++){
                tdMaker(sb,cells.get(c));
            }
            sb.append("</tr>");
        }
        sb.append("</table>");
        return sb.toString();
    }

    /**
     * 生成td字符串
     * @param sb
     * @param cell
     */


    private void tdMaker(StringBuilder sb,Map<String,Object> cell){
        if((int)cell.get("type")==1){
            sb.append("<td");
            if(cell.get("col")!=null){
                sb.append(" colspan='"+cell.get("col")+"'");
            }
            if(cell.get("row")!=null){
                sb.append(" rowspan='"+cell.get("row")+"'");
            }
            sb.append(">");
            sb.append(cell.get("text"));
            sb.append("</td>");
        }
    }

    /**
     * 如果有单元格横向的合并。会释放td位置
     * 比如一行五列,1-2合并。poi只能解析到1,3,4,5四个cell
     * 为了方便纵向单元格合并的解析。补上被合并的td对象
     *
     * @param tableParse
     * @param rows
     */

    private void hMerger(List<Map<String, Object>>[] tableParse,List<XWPFTableRow> rows){
        for (int r = 0; r < rows.size(); r++) {
            tableParse[r] = new ArrayList<>();
            XWPFTableRow row = rows.get(r);
            List<XWPFTableCell> tableCells = row.getTableCells();
            for (int c = 0; c < tableCells.size(); c++) {
                Map<String, Object> cellMap = new HashMap<>();
                tableParse[r].add(cellMap);
                XWPFTableCell cell = tableCells.get(c);
                cellMap.put("text", cell.getText());
                cellMap.put("type",1);
                CTTcPr tcPr = cell.getCTTc().getTcPr();
                if (tcPr.getGridSpan() != null) {
                    cellMap.put("col", tcPr.getGridSpan().getVal());
                    for(int g = 0;g<tcPr.getGridSpan().getVal().intValue()-1;g++){
                        Map<String,Object> visCell = new HashMap<>();
                        visCell.put("type",0);
                        tableParse[r].add(visCell);
                    }
                }
            }
        }
    }

    /**
     * 纵向单元格合并的解析
     * 纵向单元格合并的解析比较简单。
     * 如果存在vMerge的val为STMerge。CONTINUE
     * 表明是前面行的合并单元格
     *
     * @param tableParse
     * @param rows
     */

    private void vMerger(List<Map<String, Object>>[] tableParse,List<XWPFTableRow> rows){
        for (int r = 0; r < rows.size(); r++) {
            XWPFTableRow row = rows.get(r);
            List<XWPFTableCell> tableCells = row.getTableCells();
            for (int c = 0; c < tableCells.size(); c++) {
                XWPFTableCell cell = tableCells.get(c);
                CTTcPr tcPr = cell.getCTTc().getTcPr();
                if (tcPr.getVMerge() != null) {
                    if (tcPr.getVMerge().getVal() == STMerge.RESTART) {
                        tableParse[r].get(c).put("row", 1);
                    } else if(tcPr.getVMerge().getVal() == STMerge.CONTINUE){
                        tableParse[r].get(c).put("type", 0);
                        vMergeAdd(tableParse, c, r);
                    }
                }
            }
        }
    }
}

代码注释有两个关键点可能没说明白。第一,为什么要补虚拟单元格。
因为gridSpan合并的单元格只有一个cell。假设3*5的表格,如果第一行1-2-3合并。那么第一行的第二个单元会对应第二行的第四个单元格。如果恰好第一行第二个单元格和第二行第四个单元格合并。那么在做纵向单元格合并解析的时候不好处理。

第二,纵向单元格合并解析的思路。当遍历单元格的时候,如果找到vMerge的值为STMerge.STARTED说明这个单元有纵向合并,row值set1。当vMerge的值为STMerge.CONTINUE时说明这个单元被纵向合并。找上一行同列的单元格row值。如果不为空则row值加一。本单元格type标记为0(表示不生成td)。因为做横向单元格解析时已经补过虚拟单元格。所以在纵向解析时,纵向合并的单元格一定是列下标相同的。另,windows office和wps office还是有少许区别的。

注 : 这是我的第二篇blog。大佬总说要写blog。我觉的如果能百度到别人的答案就没必要写下来。但是这次的问题百度了很久,最后靠自己解决的,所以写下来,希望能帮到大家

  • 2
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值