具有合并单元格的Apache POI和自动行高度

时间:2016-06-10 09:39:39

标签: java excel apache-poi

我正在使用Apache POI,我遇到了一个奇怪的问题。我可以自动调整行的大小,但前提是该行中没有合并的单元格。这是一个例子:

new FileOutputStream('test.xlsx').withStream { OutputStream os ->
    Workbook workbook = new XSSFWorkbook();
    Sheet sheet = workbook.createSheet();

    CellStyle wrapStyle = workbook.createCellStyle();
    wrapStyle.setWrapText(true);

    Row row = sheet.createRow(0); row.setRowStyle(wrapStyle);

    Cell cell = row.createCell(0); cell.setCellStyle(wrapStyle);
    cell.setCellValue("Very long text that needs to be wrapped")

    cell = row.createCell(1); cell.setCellStyle(wrapStyle);
    cell.setCellValue("Short text");

    cell = row.createCell(2); cell.setCellStyle(wrapStyle);
    cell.setCellValue("");

    // These two lines break row auto-height!
    //
    CellRangeAddress cellRangeAddress = new CellRangeAddress(0, 0, 1, 2);
    sheet.addMergedRegion(cellRangeAddress);

    workbook.write(os);
}

此代码生成以下文档: enter image description here

但是,只要我注释掉合并两个单元格的行,输出就像这样: enter image description here

这是一个错误吗?有没有人知道一种解决方法?

2 个答案:

答案 0 :(得分:0)

经过更多的研究,结果证明这是Excel本身的问题,而不是POI。对于在其中包含合并单元格的所有行,Excel确实失去了将行自动调整为内容的功能。有关详细信息,请参阅:

http://excel.tips.net/T003207_Automatic_Row_Height_For_Merged_Cells_with_Text_Wrap.html http://blog.contextures.com/archives/2012/06/07/autofit-merged-cell-row-height/

解决方法基于预测行的最大单元格中的行数,然后手动调整行高。代码基于这个讨论:

http://mail-archives.apache.org/mod_mbox/poi-user/200906.mbox/%3C24216153.post@talk.nabble.com%3E

下面的

screenRowInfo是我的自定义数据结构,用于跟踪工作表布局。您应该能够用等效函数和辅助函数替换它们。

NestedCellInfo

这个解决方案远非完美,但它让我继续前进。

答案 1 :(得分:0)

通过使用Val Blant的最后一部分,我做了一些比较容易使用的东西,但却很复杂。 请注意,出于个人原因,只有一行代码会在单元格的高度上添加一行。如果您不希望如此,请将其删除。也可以随意将其更改为非静态,我必须使用静态方法,因为我正在努力使特定类保持静态。

PS:这是我在stackoverflow上的第一篇文章,请保持温和。 :)

解决方案:

public static Boolean isCellMerged(Cell cell) {
        Sheet sheet = cell.getSheet();
        for (CellRangeAddress mergedRegionRange : sheet.getMergedRegions()) {
            Integer cellColumn = cell.getColumnIndex();
            Integer cellRow = cell.getRowIndex();
            if (mergedRegionRange.containsColumn(cellColumn) && mergedRegionRange.containsRow(cellRow)) {
                return true;
            }
        }
        return false;
    }

public static List<List<Cell>> getCellsInRowsInsideRegionRange(Cell cell) {
        Sheet sheet = cell.getSheet();
        List<List<Cell>> mergedRowList = new ArrayList<>();
        List<Cell> mergedCellsList = new ArrayList<>();

        //Nejdříve musíme zjistit sloučenou sekci dané buňky
        for (CellRangeAddress mergedRegionRange : sheet.getMergedRegions()) {
            Integer cellColumn = cell.getColumnIndex();
            Integer cellRow = cell.getRowIndex();
            if (mergedRegionRange.containsColumn(cellColumn) && mergedRegionRange.containsRow(cellRow)) {

                //Protože CellRangeAddress nemá moc metod, musíme si pomoci sami a získat z ní buňky a řádky
                for (Row row : sheet) {
                    for (Cell iteratedCell : row) {
                        Integer iteratedCellColumn = iteratedCell.getColumnIndex();
                        Integer iteratedCellRow = iteratedCell.getRowIndex();
                        if (mergedRegionRange.containsColumn(iteratedCellColumn) && mergedRegionRange.containsRow(iteratedCellRow)) {
                            //Rozdělování jednotlivých řádků
                            //Není-li řádek bez buněk...
                            if (!mergedCellsList.isEmpty()) {

                                //Tak buňku přidáme do Listu buněk...
                                mergedCellsList.add(iteratedCell);

                            } else {
                                //Pokud se jedná o první buňku prvního řádku, tak přidáme rovnou
                                mergedCellsList.add(iteratedCell);
                            }
                        }
                    }
                    //Vložíme List buněk daného řádku do Listu řádků
                    if (!mergedCellsList.isEmpty()) {
                        mergedRowList.add(mergedCellsList);
                    }

                    //A vyresetujeme list buněk (začneme tak nanovo novým řádkem)
                    mergedCellsList = null;
                    mergedCellsList = new ArrayList<>();
                }
                //Vrátíme výsledný List řádků, obsahující Listy buněk ve sloučené sekci.
                if (!mergedRowList.isEmpty()) {
                    return mergedRowList;
                } else {
                    return null;
                }
            }
        }
        return null;
    }

    public static void adjustRowHeightForRowWithNonMergedCells(Row row) {
        row.setHeight((short) -1);
    }

public static void adjustRowHeightForRowWithMergedCells(Row row) {
        Sheet sheet = row.getSheet();
        Cell longestTextCell = null;

        //Potřebujeme získat buňku s nejdelším textem
        for (Cell iteratedCell : row) {
            String iteratedTextString = iteratedCell.getStringCellValue();

            if (longestTextCell != null && StringUtils.isNotBlank(longestTextCell.getStringCellValue())) {
                if (iteratedTextString.length() > longestTextCell.getStringCellValue().length()) {
                    longestTextCell = iteratedCell;
                }
            } else {
                longestTextCell = iteratedCell;
            }

        }

        //Z textově nejobsáhlejší buňky potřebujeme dostat údaje
        String longestText = "";

        if (StringUtils.isNotBlank(longestTextCell.getStringCellValue()) && longestTextCell != null) {
            longestText = longestTextCell.getStringCellValue();

            //Protože textově nejobsáhlejší buňka nemusí nutně být sloučeného typu, je zapotřebí to všude ošetřit
            Boolean isLongestTextCellMerged = isCellMerged(longestTextCell);
            Float longestCellWidthInPixels = 0f;
            Float longestMergedCellWidthInPixels = 0f;

            //Získat šířku nesloučené nejobsáhlejší buňky je jednoduché
            if (!isLongestTextCellMerged) {
                Integer longestCellColumnIndex = longestTextCell.getColumnIndex();
                longestCellWidthInPixels = sheet.getColumnWidthInPixels(longestCellColumnIndex);

            } else {

                //Musíme přijít na šířku sloučené buňky namísto buňky uvnitř sloučené buňky
                List<List<Cell>> cellsInMergedRegion = getCellsInRowsInsideRegionRange(longestTextCell);
                longestMergedCellWidthInPixels = 0f;

                //Projdeme řádky
                for (List<Cell> iteratedCell2List : cellsInMergedRegion) {
                    Float iteratedMergedCell2WidthInPixels = 0f;

                    //Projdeme jednotlivé buňky ve sloučené buňce na řádku a sečteme jejich šířky
                    for (Cell iteratedCell2 : iteratedCell2List) {
                        Integer iteratedCell2ColumnIndex = iteratedCell2.getColumnIndex();
                        Float iteratedCell2ColumnWidthInPixels = sheet.getColumnWidthInPixels(iteratedCell2ColumnIndex);

                        iteratedMergedCell2WidthInPixels = iteratedMergedCell2WidthInPixels + iteratedCell2ColumnWidthInPixels;
                    }

                    //Získáme šířku nejširší sloučené buňky na řádku
                    if (iteratedMergedCell2WidthInPixels > longestMergedCellWidthInPixels) {
                        longestMergedCellWidthInPixels = iteratedMergedCell2WidthInPixels;
                    }

                    //Resetujeme sčítání
                    iteratedMergedCell2WidthInPixels = 0f;
                }
            }

            //Uložíme si nejširší buňku dle toho, zda je sloučená či nikoliv
            Float longestWidthInPixels;
            if (isLongestTextCellMerged) {
                longestWidthInPixels = longestMergedCellWidthInPixels;
            } else {
                longestWidthInPixels = longestCellWidthInPixels;
            }

            //Potřebujeme font
            Workbook wb = sheet.getWorkbook();
            Short fontIndex = longestTextCell.getCellStyle().getFontIndex();
            Font excelFont = wb.getFontAt(fontIndex);

            //Potřebujeme i jeho styl
            Integer excelFontStyle = java.awt.Font.PLAIN;
            if (excelFont.getBold()) excelFontStyle = java.awt.Font.BOLD;
            if (excelFont.getItalic()) excelFontStyle = java.awt.Font.ITALIC;

            //Potřebujeme získat skutečný font i s velikostí
            java.awt.Font currentFont = new java.awt.Font(excelFont.getFontName(), excelFontStyle, excelFont.getFontHeightInPoints());

            //Získáme řetězec s vlastností
            AttributedString attributedString = new AttributedString(longestText);
            attributedString.addAttribute(TextAttribute.FONT, currentFont);

            //Použijeme LineBreakMeasurer k zjištění kolik řádků bude text potřebovat
            FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);
            LineBreakMeasurer measurer = new LineBreakMeasurer(attributedString.getIterator(), fontRenderContext);

            Integer nextPosition = 0;
            Integer lineCount = 0;

            while (measurer.getPosition() < longestText.length()) {
                nextPosition = measurer.nextOffset(longestWidthInPixels);

                //Také musíme ošetřit případ manuálně zadaných LineBreaků pro všechny možné techtle mechtle :-S
                String textLine = StringUtils.substring(longestText, measurer.getPosition(), nextPosition);
                Boolean containsNewLine = StringUtils.containsIgnoreCase(textLine, "\r") || StringUtils.containsIgnoreCase(textLine, "\\r") || StringUtils.containsIgnoreCase(textLine, "\n") || StringUtils.containsIgnoreCase(textLine, "\\n");

                if (containsNewLine) {

                    if (StringUtils.containsIgnoreCase(textLine, "\r\n") || StringUtils.containsIgnoreCase(textLine, "\\r\\n")) {
                        lineCount = lineCount + StringUtils.countMatches(textLine, "\n");
                    } else {

                        if (StringUtils.containsIgnoreCase(textLine, "\r") || StringUtils.containsIgnoreCase(textLine, "\\r")) {
                            lineCount = lineCount + StringUtils.countMatches(textLine, "\r");
                        }
                        if (StringUtils.containsIgnoreCase(textLine, "\n") || StringUtils.containsIgnoreCase(textLine, "\\n")) {
                            lineCount = lineCount + StringUtils.countMatches(textLine, "\n");
                        }

                    }

                    lineCount = lineCount + StringUtils.countMatches(textLine, "\\r?\\n");
                }

                lineCount++;
                measurer.setPosition(nextPosition);
            }

            //Máme počet řádků, zbývá konečný dopočet výšky řádku a jeho použití
            if (lineCount > 1) {

                Float fontHeight = currentFont.getLineMetrics(longestText, fontRenderContext).getHeight();

                //Pro jistotu přidáme jeden řádek navíc, člověk nikdy neví...
                lineCount = lineCount + 1;

                //Potřebujeme získat poslední řádek
                Row lastRow = null;

                if (isCellMerged(longestTextCell)) {
                    List<List<Cell>> mergedCellsInRows = getCellsInRowsInsideRegionRange(longestTextCell);
                    Integer lastRowInMergedSectionIndex = mergedCellsInRows.size() - 1;
                    List<Cell> lastRowInMergedSection = mergedCellsInRows.get(lastRowInMergedSectionIndex);
                    lastRow = lastRowInMergedSection.get(0).getRow();
                } else {
                    lastRow = longestTextCell.getRow();
                }

                //Je potřeba ošetřit velikosti, pokud má sloučená buňka vícero řádků
                Float cellsMergedAboveHeight = 0f;
                if (isCellMerged(longestTextCell)) {
                    if (getCellsInRowsInsideRegionRange(longestTextCell).size() > 1) {
                        List<List<Cell>> mergedCellsInRows = getCellsInRowsInsideRegionRange(longestTextCell);
                        for (List<Cell> rowsWithCells : mergedCellsInRows){
                            if (!lastRow.equals(rowsWithCells.get(0).getRow())){
                                cellsMergedAboveHeight = cellsMergedAboveHeight + rowsWithCells.get(0).getRow().getHeight();
                            }
                        }
                    }
                }
                //Vzorec je ((Velikost fontu krát počet řádků plus (počet řádků krát volný prostor mezi řádky)) krát přepočet Excelu) mínus výška sloučených buněk nad posledním řádkem.
                Short finalRowHeight = (short) (((fontHeight * lineCount + (lineCount * 15))* 10) - cellsMergedAboveHeight);

                //A výsledek nastavíme na poslední řádek, protože jinak to przní sloupce vlevo a vpravo od vyšších řádků
                lastRow.setHeight(finalRowHeight);

            }
        }
    }