Hi POI team,

We have found an issue where the POI XSLFTable::mergeCells method seems ineffective at setting rowspans if the table only has a single column.

We have seen that POI is setting the rowspan=... attribute in the output Slide XML, however MS PowerPoint is ignoring it. Trying the same cell merging manually in PowerPoint reveals that instead of setting rowspan, PowerPoint will remove the spanned-over rows and set the height of the first row to the total of the spanned over rows.

This makes me wonder if the POI XSLFTable::mergeCells method needs an update to mimick this technique?

Here's a java testcase showing the problem and our workaround:

=== BEGIN TestMergeCells.java ===

import java.awt.Color;
import java.awt.Rectangle;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.poi.sl.usermodel.TableCell.BorderEdge;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.apache.poi.xslf.usermodel.XSLFTable;
import org.apache.poi.xslf.usermodel.XSLFTableCell;
import org.apache.poi.xslf.usermodel.XSLFTableRow;

/**
 * Demonstrate problem with mergeCell rowspan in single column table
 * (POI writes out the rowspan=... to XML, but MS PowerPoint ignores it)
 */
public final class TestMergeCells {
  private TestMergeCells() {}

  // Populate a table
  private static void fillTable(XSLFTable tbl, int numRows, int numColumns, Color clr) {
    for (int r = 0; r < numRows; r++) {
      XSLFTableRow tr = tbl.addRow();
      tr.setHeight(50);
      for (int c = 0; c < numColumns; c++) {
        XSLFTableCell cell = tr.addCell();
        cell.setText("Row" + r + " Col" + c);

        cell.setBorderColor(BorderEdge.bottom, clr);
        cell.setBorderColor(BorderEdge.left, clr);
        cell.setBorderColor(BorderEdge.top, clr);
        cell.setBorderColor(BorderEdge.right, clr);
      }
    }
  }

  // Workaround function - should POI be doing this internally?
  private static void mergeCellsMod(XSLFTable tbl, int fromRow, int toRow, int fromCol, int toCol) {
    if (tbl.getNumberOfColumns() == 1) {
      double h = tbl.getRowHeight(fromRow);
      // accumulate spanned-over row heights as we remove them
      for (int r = toRow; r > fromRow; --r) {
        h += tbl.getRowHeight(r);
        tbl.removeRow(r);
      }
      tbl.setRowHeight(fromRow, h);
    }
    else {
      tbl.mergeCells(fromRow, toRow, fromCol, toCol);
    }
  }

  public static void main(String[] args) throws IOException{
    try (XMLSlideShow ppt = new XMLSlideShow()) {
      XSLFSlide slide = ppt.createSlide();

      // Red table has 2 columns, shows that merging rows works (and in 2nd column we demonstrate workaround function)
      XSLFTable ta = slide.createTable();
      ta.setAnchor(new Rectangle(50, 50, 300, 200));
      fillTable(ta, 4, 2, Color.red); // 4 rows, 2 cols

      // Blue Table has 1 column, shows that mergeCells is ineffective here when viewed in MS PowerPoint v2108
      XSLFTable tb = slide.createTable();
      tb.setAnchor(new Rectangle(400, 50, 150, 200));
      fillTable(tb, 4, 1, Color.blue); // 4 rows, 1 col

      // Green table, demonstrate basic workaround technique for row spans in single column tables
      XSLFTable tc = slide.createTable();
      tc.setAnchor(new Rectangle(200, 300, 150, 200));
      fillTable(tc, 4, 1, Color.green); // 4 rows, 1 col
      tc.removeRow(2);
      tc.setRowHeight(1, 100); // set height to 2x50

      // Magenta table demonstrates use a function to apply the workaround
      XSLFTable td = slide.createTable();
      td.setAnchor(new Rectangle(500, 300, 150, 200));
      fillTable(td, 4, 1, Color.magenta); // 4 rows, 1 col

      // Merge middle cells in first columns
      ta.mergeCells(1,2,0,0); // has the desired effect in 2-col table
      mergeCellsMod(ta, 0,1,1,1); // test workaround function on 2-col table
      mergeCellsMod(ta, 2,3,1,1); // ...and again

      tb.mergeCells(1,2,0,0); // MS PowerPoint ignores rowspan request in single column table!

      mergeCellsMod(td, 1, 2, 0, 0); // Workaround function is effective though

      try (FileOutputStream out = new FileOutputStream("TestMergeCells.pptx")) {
        ppt.write(out);
      }
    }
  }
}
=== END TestMergeCells.java ===

Thanks (and sorry if I've submitted this report/observation/suggestion the wrong way!),

Simon Davey


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@poi.apache.org
For additional commands, e-mail: dev-h...@poi.apache.org

Reply via email to