如何用APACHE POI在word中对表格进行colspan处理

回答 3 浏览 7518 2016-01-07

我有一个代码,但它是用于表格的行跨度。你能帮我把它转换为colspan代码吗?

private static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow){
    for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++){
        XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
        if(rowIndex == fromRow){
            // The first merged cell is set with RESTART merge value
            cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
        }else{
            // Cells which join (merge) the first one, are set with CONTINUE
            cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
        }
    }
}
Wiguna R 提问于2016-01-07
3 个回答
#1楼 已采纳
得票数 11

原理是一样的,只是用org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHMerge而不是org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge

但我会更仔细地处理单元格中的TcPr 元素。您的实际代码每次都只是创建新的TcPr 元素。如果单元格中已经有这样的元素怎么办?

举例说明。

import java.io.File;
import java.io.FileOutputStream;

import java.math.BigInteger;

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.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;

import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;

public class CreateWordTableMerge {

 static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
  for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++){
   XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
   CTVMerge vmerge = CTVMerge.Factory.newInstance();
   if(rowIndex == fromRow){
    // The first merged cell is set with RESTART merge value
    vmerge.setVal(STMerge.RESTART);
   } else {
    // Cells which join (merge) the first one, are set with CONTINUE
    vmerge.setVal(STMerge.CONTINUE);
    // and the content should be removed
    for (int i = cell.getParagraphs().size(); i > 0; i--) {
     cell.removeParagraph(0);
    }
    cell.addParagraph();
   }
   // Try getting the TcPr. Not simply setting an new one every time.
   CTTcPr tcPr = cell.getCTTc().getTcPr();
   if (tcPr != null) {
    tcPr.setVMerge(vmerge);
   } else {
    // only set an new TcPr if there is not one already
    tcPr = CTTcPr.Factory.newInstance();
    tcPr.setVMerge(vmerge);
    cell.getCTTc().setTcPr(tcPr);
   }
  }
 }

 static void mergeCellHorizontally(XWPFTable table, int row, int fromCol, int toCol) {
  for(int colIndex = fromCol; colIndex <= toCol; colIndex++){
   XWPFTableCell cell = table.getRow(row).getCell(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);
    // and the content should be removed
    for (int i = cell.getParagraphs().size(); i > 0; i--) {
     cell.removeParagraph(0);
    }
    cell.addParagraph();
   }
   // 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);
   }
  }
 }

 public static void main(String[] args) throws Exception {

  XWPFDocument document= new XWPFDocument();

  XWPFParagraph paragraph = document.createParagraph();
  XWPFRun run=paragraph.createRun();  
  run.setText("The table:");

  //create table
  XWPFTable table = document.createTable(3,5);

  for (int row = 0; row < 3; row++) {
   for (int col = 0; col < 5; col++) {
    table.getRow(row).getCell(col).setText("row " + row + ", col " + col);
   }
  }


  //create CTTblGrid for this table with widths of the 5 columns. 
  //necessary for Libreoffice/Openoffice to accept the column widths.
  //values are in unit twentieths of a point (1/1440 of an inch)
  //first column = 1 inches width
  table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(1*1440));
  //other columns (2 in this case) also each 1 inches width
  for (int col = 1 ; col < 5; col++) {
   table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1*1440));
  }


  //create and set column widths for all columns in all rows
  //most examples don't set the type of the CTTblWidth but this
  //is necessary for working in all office versions
  for (int col = 0; col < 5; col++) {
   CTTblWidth tblWidth = CTTblWidth.Factory.newInstance();
   tblWidth.setW(BigInteger.valueOf(1*1440));
   tblWidth.setType(STTblWidth.DXA);
   for (int row = 0; row < 3; row++) {
    CTTcPr tcPr = table.getRow(row).getCell(col).getCTTc().getTcPr();
    if (tcPr != null) {
     tcPr.setTcW(tblWidth);
    } else {
     tcPr = CTTcPr.Factory.newInstance();
     tcPr.setTcW(tblWidth);
     table.getRow(row).getCell(col).getCTTc().setTcPr(tcPr);
    }
   }
  }

  //using the merge methods
  mergeCellVertically(table, 0, 0, 1); 
  mergeCellHorizontally(table, 1, 2, 3); 
  mergeCellHorizontally(table, 2, 1, 4); 

  paragraph = document.createParagraph();

  FileOutputStream out = new FileOutputStream("create_table.docx"); 
  document.write(out);
  out.close();

  System.out.println("create_table.docx written successully");
 }
}

2018年3月11日编辑。

有两种设置水平合并的方法。第一种是使用CTHMerge,这与使用CTVMerge的垂直合并类似,它不明确需要一个表格网格。第二种是使用网格跨度属性。这种方法需要一个表格网格,而且必须删除与第一种方法合并的单元格。

Microsoft Word支持所有的方法。

Libreoffice Writer也支持CTHMerge,但因为要正确渲染表格,所以必须设置表格网格。

WPS Writer只支持设置网格跨度。

因此,这应该是最兼容的解决方案。

import java.io.File;
import java.io.FileOutputStream;

import java.math.BigInteger;

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.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;

import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;

public class CreateWordTableMerge {

 static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
  for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
   XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
   CTVMerge vmerge = CTVMerge.Factory.newInstance();
   if(rowIndex == fromRow){
    // The first merged cell is set with RESTART merge value
    vmerge.setVal(STMerge.RESTART);
   } else {
    // Cells which join (merge) the first one, are set with CONTINUE
    vmerge.setVal(STMerge.CONTINUE);
    // and the content should be removed
    for (int i = cell.getParagraphs().size(); i > 0; i--) {
     cell.removeParagraph(0);
    }
    cell.addParagraph();
   }
   // Try getting the TcPr. Not simply setting an new one every time.
   CTTcPr tcPr = cell.getCTTc().getTcPr();
   if (tcPr == null) tcPr = cell.getCTTc().addNewTcPr();
   tcPr.setVMerge(vmerge);
  }
 }

 //merging horizontally by setting grid span instead of using CTHMerge
 static void mergeCellHorizontally(XWPFTable table, int row, int fromCol, int toCol) {
  XWPFTableCell cell = table.getRow(row).getCell(fromCol);
  // Try getting the TcPr. Not simply setting an new one every time.
  CTTcPr tcPr = cell.getCTTc().getTcPr();
  if (tcPr == null) tcPr = cell.getCTTc().addNewTcPr();
  // The first merged cell has grid span property set
  if (tcPr.isSetGridSpan()) {
   tcPr.getGridSpan().setVal(BigInteger.valueOf(toCol-fromCol+1));
  } else {
   tcPr.addNewGridSpan().setVal(BigInteger.valueOf(toCol-fromCol+1));
  }
  // Cells which join (merge) the first one, must be removed
  for(int colIndex = toCol; colIndex > fromCol; colIndex--) {
   table.getRow(row).getCtRow().removeTc(colIndex);
   table.getRow(row).removeCell(colIndex);
  }
 }

 public static void main(String[] args) throws Exception {

  XWPFDocument document= new XWPFDocument();

  XWPFParagraph paragraph = document.createParagraph();
  XWPFRun run=paragraph.createRun();  
  run.setText("The table:");

  //create table
  XWPFTable table = document.createTable(3,5);

  for (int row = 0; row < 3; row++) {
   for (int col = 0; col < 5; col++) {
    table.getRow(row).getCell(col).setText("row " + row + ", col " + col);
   }
  }

  //create CTTblGrid for this table with widths of the 5 columns. 
  //necessary for Libreoffice/Openoffice to accept the column widths.
  //values are in unit twentieths of a point (1/1440 of an inch)
  //first column = 1 inches width
  table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(1*1440));
  //other columns (2 in this case) also each 1 inches width
  for (int col = 1 ; col < 5; col++) {
   table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1*1440));
  }

  //create and set column widths for all columns in all rows
  //most examples don't set the type of the CTTblWidth but this
  //is necessary for working in all office versions
  for (int col = 0; col < 5; col++) {
   CTTblWidth tblWidth = CTTblWidth.Factory.newInstance();
   tblWidth.setW(BigInteger.valueOf(1*1440));
   tblWidth.setType(STTblWidth.DXA);
   for (int row = 0; row < 3; row++) {
    CTTcPr tcPr = table.getRow(row).getCell(col).getCTTc().getTcPr();
    if (tcPr != null) {
     tcPr.setTcW(tblWidth);
    } else {
     tcPr = CTTcPr.Factory.newInstance();
     tcPr.setTcW(tblWidth);
     table.getRow(row).getCell(col).getCTTc().setTcPr(tcPr);
    }
   }
  }

  //using the merge methods
  mergeCellVertically(table, 0, 0, 1); 
  mergeCellHorizontally(table, 1, 2, 3); 
  mergeCellHorizontally(table, 2, 1, 4); 

  paragraph = document.createParagraph();

  FileOutputStream out = new FileOutputStream("create_table.docx"); 
  document.write(out);
  out.close();

  System.out.println("create_table.docx written successully");
 }
}
Axel Richter 提问于2016-01-07
Axel Richter 修改于2019-03-22
谢谢先生。我不知道是否有一个org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge。我的代码已经解决了,但也许以后我还会再问:)Wiguna R 2016-01-09
不客气。如果我找到一个像cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);这样的代码,那么我想知道getCTTc()addNewTcPr()addNewVMerge()会返回什么对象。为此,我通过这些方法询问互联网搜索引擎。它把我指向grepcode.com/file/repo1.maven.org/maven2/org.apache.poi/…Axel Richter 2016-01-09
PIC 1这里出了问题,如果我把HMerge和VMergePIC 2结合起来。如果没有,那就完全好了。Wiguna R 2016-01-13
对不起,无法重现。你的prntscr.com/9pgi8x在我的代码中是mergeCellVertically(table, 0, 0, 1); mergeCellVertically(table, 1, 0, 1); mergeCellHorizontally(table, 0, 2, 3); mergeCellHorizontally(table, 0, 4, 5); mergeCellHorizontally(table, 0, 6, 7); mergeCellHorizontally(table, 1, 1, 7); 。而这正是我所期望的,因为你的row1col1同时向上和向右合并。Axel Richter 2016-01-13
因此,这不能被修复吗?Wiguna R 2016-01-20
#2楼
得票数 2
private static void mergeCellHorizontally(XWPFTable table, int row, int fromCell, int toCell){
    for(int cellIndex = fromCell; cellIndex <= toCell; cellIndex++){
        XWPFTableCell cell = table.getRow(row).getCell(cellIndex);
        if(cellIndex == fromCell){
            // The first merged cell is set with RESTART merge value
            cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
        }else{
            // Cells which join (merge) the first one, are set with CONTINUE
            cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
        }
    }
}
user3819658 提问于2020-10-06
感谢您的答复,但孩子,我希望我仍然在这个项目上。Wiguna R 2020-10-06
#3楼
得票数 0

Axel Richter的回答非常完整。但是,如果你不使用CTTcPr和其他XML密钥,并与POI API混合,就会出现问题。

你必须使用

        // Cells which join (merge) the first one, must be removed
        int sizeOfTc = currentRow.getCtRow().sizeOfTcArray();
        for(int colIndex = toCol; colIndex > fromCol; colIndex--) {
            if (colIndex < sizeOfTc) currentRow.getCtRow().removeTc(colIndex);
            currentRow.removeCell(colIndex);
        }

因为Apache POI上有一个错误。removeCell()方法并不能从行中删除Tc。如果你使用removeTc(),它不会从内部POI的列表中删除单元格。 你必须同时使用这两个方法。

Scarminio 提问于2018-05-07
标签