Как сохранить ширину ячеек при объединении ячеек в таблице слов с использованием apache poi? - PullRequest
0 голосов
/ 10 июля 2020

Я создаю текстовый документ с таблицей, используя Java и Apache POI.

Я могу достаточно легко создать таблицу, установить каждый столбец с разной шириной, а затем объединить ячейки для создания желаемый эффект (см. изображения ниже), однако, когда я открываю текстовый документ, некоторые ячейки были скорректированы так, что их края соединяются вместе. Я обнаружил, что добавление дополнительной строки в начало таблицы и оставление всех ячеек несвязанными сохраняет остальные строки нетронутыми, но удаление этой строки позже с помощью table.removeRow (0); влияет на остальные строки. Если я открою текстовый документ и вручную удалю строку, ячейки останутся на месте. Могу ли я что-нибудь сделать, чтобы сохранить расположение ячеек?

правильный макет с дополнительной неслитой верхней строкой

результат после удаления верхней строки

Это функция, которая создает слово do c и таблицу:

public static void createWord() {
    // Blank Document
    XWPFDocument document = new XWPFDocument();
    
    CTSectPr sectPr = document.getDocument().getBody().addNewSectPr();
    CTPageMar pageMar = sectPr.addNewPgMar();
    pageMar.setLeft(BigInteger.valueOf(300L));
    pageMar.setTop(BigInteger.valueOf(300L));
    pageMar.setRight(BigInteger.valueOf(300L));
    pageMar.setBottom(BigInteger.valueOf(300L));

    XWPFParagraph paragraph = document.createParagraph();
    paragraph.setSpacingBefore(0);
    paragraph.setSpacingAfter(0);
    
    // determine the number of rows and columns required 
    int rows = 3;
    int cols = 6; 
    
    // create table
    XWPFTable table = document.createTable(rows+1, cols);
    CTTblPr tblPr = table.getCTTbl().getTblPr();
    if (null == tblPr) {
        tblPr = table.getCTTbl().addNewTblPr();
    }

    // set table width
    CTTblWidth width = table.getCTTbl().addNewTblPr().addNewTblW();
    width.setType(STTblWidth.PCT);
    width.setW(BigInteger.valueOf(5000)); // 5000 * 1/50 = 100%
    
    //set row height
    for(XWPFTableRow row:table.getRows()) {
        row.setHeight(22);
    }
    
    // set width of each column
    for (int row = 0; row <= rows; row++) {
        setCellWidthPercentage(table, row, 0, 0.188);
        setCellWidthPercentage(table, row, 1, 0.125);
        setCellWidthPercentage(table, row, 2, 0.063);
        setCellWidthPercentage(table, row, 3, 0.25);
        setCellWidthPercentage(table, row, 4, 0.25);
        setCellWidthPercentage(table, row, 5, 0.125);
    }
    
    mergeCellHorizontally(table, 1, 0, 2);
    mergeCellHorizontally(table, 2, 0, 1);
    mergeCellHorizontally(table, 2, 2, 4);
    mergeCellHorizontally(table, 3, 1, 3);
    
    // remove first row (comment out this line to see issue)
    table.removeRow(0);
    
    // Write the Document in file system
    try {
        File docFile = new File("C:\\doc.docx");
        docFile.createNewFile();
        FileOutputStream out = new FileOutputStream(docFile, false); 
        
        document.write(out);
        out.close();
        document.close();           
    } catch(Exception ex) {
        ex.printStackTrace();
    }
}

Я использую приведенный ниже код для объединения ячеек по горизонтали:

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

и эта функция для присвоения значений ширины столбцам перед объединением:

private static void setCellWidthPercentage(XWPFTable table, int row, int col, double width) {
    // prevent out of bounds exception
    if (row < 0 || row >= table.getRows().size()) return;
    if (col < 0 || col >= table.getRow(row).getTableCells().size()) return;
    
    // assign widths in units of 1/50 of a percentage
    CTTblWidth tblW = table.getRow(row).getCell(col).getCTTc().addNewTcPr().addNewTcW();
    tblW.setType(STTblWidth.PCT);
    tblW.setW(BigInteger.valueOf(Math.round(width * 50)));
}

Заранее спасибо!

1 Ответ

1 голос
/ 11 июля 2020

Проблема, которую вы видите, заключается в том, что Word отображает таблицы в соответствии с настройками ширины столбца строки, в которой больше всего столбцов. Если другие строки противоречат настройкам ширины столбца этой строки, то их настройка ширины столбца будет проигнорирована. И после объединения ячеек вы не корректируете настройки ширины столбца. Например, после mergeCellHorizontally(table, 0, 0, 2); столбец 0 в строке 0 теперь до столбца 2. Таким образом, для столбца 0 теперь нужна ширина столбцов 0 + 1 + 2, которые были ранее. Но поскольку вы не исправляете это, он остается шириной только столбца 0 и игнорируется при рендеринге, если это противоречит настройкам ширины строки с наибольшим количеством столбцов.

Итак, основная проблема заключается в том, что в вашем коде не хватает корректировки настроек ширины столбца в строках после объединения ячеек.

Я уже показал это в как установить определенную c ячейку ширина в другой строке в apache таблице poi? .

Но есть и другие проблемы.

Сначала метод mergeCellHorizontally должен объединять ячейки по горизонтали, устанавливая диапазон сетки вместо использования CTHMerge. Это гораздо более совместимо со всеми типами приложений для обработки текстов, которые открывают *.docx файлов, чем при использовании CTHMerge.

Во-вторых, всегда должна использоваться последняя версия apache poi. Текущий apache poi 4.1.2 предоставляет XWPFTable.setWidth и XWPFTableCell.setWidth . Так что никаких собственных методов set-width не требуется.

И в-третьих, вы должны создать сетку таблицы для таблицы с шириной столбцов. Это необходимо, чтобы Libreoffice / OpenOffice принимал ширину столбца. К сожалению, для этого необходимо вычислить ширину столбцов в единицах двадцатой точки (1/1440 дюйма), поскольку TblGrid - GridCol не принимает процентные значения.

В следующем полном примере все это показано и создается стол, который вы хотите.

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;

public class CreateWordTableMergedCells {

 //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:");

  // determine the number of rows and columns required 
  int rows = 3;
  int cols = 6; 

  //create table
  XWPFTable table = document.createTable(rows, cols);

  //set table width
  table.setWidth("100%"); 

  double[] columnWidths = new double[] { // columnWidths in percent
   0.188, 0.125, 0.062, 0.25, 0.25, 0.125
  };

  //create CTTblGrid for this table with widths of the columns. 
  //necessary for Libreoffice/Openoffice to accept the column widths.
  //values are in unit twentieths of a point (1/1440 of an inch)
  int w100Percent = 6*1440; // twentieths of a point (1/1440 of an inch); 6 inches
  //first column
  table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(
   Math.round(w100Percent*columnWidths[0])));
  //other columns
  for (int c = 1; c < cols; c++) {
   table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(
    Math.round(w100Percent*columnWidths[c])));
  }

  // set width of each column in each row
  for (int r = 0; r < rows; r++) {
   for (int c = 0; c < cols; c++) {
    table.getRow(r).getCell(c).setWidth("" + (columnWidths[c]*100.0) + "%");
   }
  }

  //using the merge method
  mergeCellHorizontally(table, 0, 0, 2); // after that column 0 is up to column 2
  //column 0 now need width of formerly columns 0 + 1 + 2
  table.getRow(0).getCell(0).setWidth("" + ((columnWidths[0]+columnWidths[1]+columnWidths[2])*100.0) + "%");

  mergeCellHorizontally(table, 1, 0, 1); // after that column 0 is up to column 1
  //column 0 now need width of formerly columns 0 + 1
  table.getRow(1).getCell(0).setWidth("" + ((columnWidths[0]+columnWidths[1])*100.0) + "%");
  mergeCellHorizontally(table, 1, 1, 3); // formerly col 2 is now col 1 and after that formerly column 2 is up to column 4
  //current column 1 now need width of formerly columns 2 + 3 + 4
  table.getRow(1).getCell(1).setWidth("" + ((columnWidths[2]+columnWidths[3]+columnWidths[4])*100.0) + "%");
 
  mergeCellHorizontally(table, 2, 1, 3); // after that column 1 is up to column 3
  //column 1 now need width of formerly columns 1 + 2 + 3
  table.getRow(2).getCell(1).setWidth("" + ((columnWidths[1]+columnWidths[2]+columnWidths[3])*100.0) + "%");

  paragraph = document.createParagraph();

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