POI Word 无法垂直合并新创建的单元格

POI Word Unable to merge newly created cell vertically

我知道如何使用 Apache POI word 垂直合并单元格。但是貌似如果新建一行,合并不会生效

这里是输入 table:

我想在 old row 2old row 3 之间添加一个新行,并将新行的第一列单元格合并到 C2 中,如下所示:

所以我创建了一个新行并将其添加到 old row 2 下面的 table,并尝试合并单元格

github源代码link is here,可以重现问题。

public class POIWordAddSubRowQuestionDemo{
    public static void main(String[] args) throws IOException, XmlException{
        ClassLoader classLoader = POIWordAddSubRowQuestionDemo.class.getClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream("input.docx");
        String outputDocxPath = "F:/TEMP/output.docx";
        assert inputStream != null;
        XWPFDocument doc = new XWPFDocument(inputStream);
        XWPFTable table = doc.getTables().get(0);
        //this is 'old row 2'
        XWPFTableRow secondRow = table.getRows().get(1);

        //create a new row that is based on 'old row 2'
        CTRow ctrow = CTRow.Factory.parse(secondRow.getCtRow().newInputStream());
        XWPFTableRow newRow = new XWPFTableRow(ctrow, table);
        XWPFRun xwpfRun = newRow.getCell(1).getParagraphs().get(0).getRuns().get(0);
        //set row text
        xwpfRun.setText("new row", 0);
        // add new row below 'old row 2'
        table.addRow(newRow, 2);

        //merge cells at first column of 'old row 2', 'new row', and 'old row 3'
        mergeCellVertically(doc.getTables().get(0), 0, 1, 3);

        FileOutputStream fos = new FileOutputStream(outputDocxPath);
        doc.write(fos);
        fos.close();
    }

    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);
        }
    }
}

但是合并没有成功,我得到:

我又尝试根据图3中的table合并得到图2中的table,成功了。两次尝试之间的唯一区别是 new row 不是新创建的,而是从 docx 文档中读取的,所以我相信创建新行是合并失败的原因。

那么有合并新建行的解决方案吗?我真的不想这样拆分此操作:添加行 > 将 docx 保存到磁盘 > 从磁盘读取 docx > 合并行。

这是您可能想要采用的 VBA 方法。它在第 3 行和第 4 行之间插入一个新行:

Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Tables(1)
  .Cell(4, 2).Range.InsertBreak (wdColumnBreak)
  .Rows.Add
  .Cell(2, 1).Merge MergeTo:=.Cell(4, 1)
  .Range.Characters.Last.Next.Delete
  .Cell(2, 1).Merge MergeTo:=.Cell(5, 1)
End With
Application.ScreenUpdating = True
End Sub

您遇到的问题不是 mergeCellVertically 方法,而是您复制 table 行的方法。复制基础 CTRow 并使用 XWPFTable.addRow 将其插入 CTTbl.TrArray 时,它必须完全完整。后面的改动就不写在XML里面了。我在回答 already. And I provided a method commitTableRows in my answer 中说过了。这个方法需要在文档写出来之前调用,所以后面的修改写在XML.

因为您正在复制第二行,这是合并的开始,所以该设置也会被复制。而后面调用的mergeCellVertically不生效。所以你的 newRow 仍然是合并的新起点。这就是你得到的。

所以在所有更改之后和写出之前,调用 commitTableRows.

完整示例:

import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;


public class WordInsertTableRowAndMerge {
    
 static XWPFTableRow insertNewTableRow(XWPFTableRow sourceTableRow, int pos) throws Exception {
  XWPFTable table = sourceTableRow.getTable();
  CTRow newCTRrow = CTRow.Factory.parse(sourceTableRow.getCtRow().newInputStream());
  XWPFTableRow tableRow = new XWPFTableRow(newCTRrow, table);
  table.addRow(tableRow, pos);
  return tableRow;
 }

 static void commitTableRows(XWPFTable table) {
  int rowNr = 0;
  for (XWPFTableRow tableRow : table.getRows()) {
   table.getCTTbl().setTrArray(rowNr++, tableRow.getCtRow());
  }
 }
    
 static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
  for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
   System.out.println("rowIndex: " + 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);
  }
 }
 
 public static void main(String[] args) throws Exception {

  XWPFDocument doc = new XWPFDocument(new FileInputStream("./source.docx"));
  
  XWPFTable table = doc.getTables().get(0);

  XWPFTableRow row = table.getRow(1);
  XWPFTableRow newRow = insertNewTableRow(row, 2);
  
  XWPFTableCell cell = newRow.getCell(0); if (cell == null) cell = newRow.addNewTableCell();
  // not needed because merged to cell above
  cell = newRow.getCell(1); if (cell == null) cell = newRow.addNewTableCell();
  for (XWPFParagraph paragraph : cell.getParagraphs()) { // only use first text runs in paragraphs
   for (int r = paragraph.getRuns().size()-1; r >= 0; r--) {
    XWPFRun run = paragraph.getRuns().get(r);
    if (r == 0) {
     run.setText("new row 1", 0);
    } else {
     paragraph.removeRun(r); 
    }
   }
  }

  mergeCellVertically(table, 0, 1, 3);

  commitTableRows(table);

  FileOutputStream out = new FileOutputStream("./result.docx");
  doc.write(out);
  out.close();
  doc.close();

 }
}