POI / Excel: применение формул относительным образом

Я использую POI Apache для управления файлами Excel (.xls) с помощью Java.

Я пытаюсь создать новую ячейку, содержимое которой является результатом формулы, как если бы пользователь скопировал/вставил формулу (то, что я называю «относительным» способом, а не «абсолютным»).

Чтобы было понятнее, вот простой пример:

  • Ячейка A1 содержит «1», B1 содержит «2», A2 содержит «3», B2 содержит «4».
  • Ячейка A3 содержит следующую формулу «=A1+B1».

Если я скопирую формулу в ячейку A4 под excel, она станет "=A2+B2" : excel динамически адаптирует содержимое формулы.

К сожалению, я не могу получить тот же результат программно. Единственное решение, которое я нашел, это токенизировать формулу и самому сделать грязную работу, но я очень сомневаюсь, что это должно быть сделано именно так. Мне не удалось найти то, что я ищу, ни в руководствах, ни в API.

Есть ли более простой способ решить эту проблему? Если это так, не могли бы вы указать мне правильное направление?

С наилучшими пожеланиями,

Нильс


person Nils    schedule 28.10.2009    source источник


Ответы (6)


На мой взгляд, user2622016 прав, за исключением того, что его решение управляет только ссылками на ячейки, а не ссылки на области (оно не будет работать для =SUM(A1:B8) например).

Вот как я это исправил:

private void copyFormula(Sheet sheet, Cell org, Cell dest) {
    if (org == null || dest == null || sheet == null 
            || org.getCellType() != Cell.CELL_TYPE_FORMULA)
        return;
    if (org.isPartOfArrayFormulaGroup())
        return;
    String formula = org.getCellFormula();
    int shiftRows = dest.getRowIndex() - org.getRowIndex();
    int shiftCols = dest.getColumnIndex() - org.getColumnIndex();
    XSSFEvaluationWorkbook workbookWrapper = 
            XSSFEvaluationWorkbook.create((XSSFWorkbook) sheet.getWorkbook());
    Ptg[] ptgs = FormulaParser.parse(formula, workbookWrapper, FormulaType.CELL
            , sheet.getWorkbook().getSheetIndex(sheet));
    for (Ptg ptg : ptgs) {
        if (ptg instanceof RefPtgBase) // base class for cell references
        {
            RefPtgBase ref = (RefPtgBase) ptg;
            if (ref.isColRelative())
                ref.setColumn(ref.getColumn() + shiftCols);
            if (ref.isRowRelative())
                ref.setRow(ref.getRow() + shiftRows);
        } else if (ptg instanceof AreaPtg) // base class for range references
        {
            AreaPtg ref = (AreaPtg) ptg;
            if (ref.isFirstColRelative())
                ref.setFirstColumn(ref.getFirstColumn() + shiftCols);
            if (ref.isLastColRelative())
                ref.setLastColumn(ref.getLastColumn() + shiftCols);
            if (ref.isFirstRowRelative())
                ref.setFirstRow(ref.getFirstRow() + shiftRows);
            if (ref.isLastRowRelative())
                ref.setLastRow(ref.getLastRow() + shiftRows);
        }
    }
    formula = FormulaRenderer.toFormulaString(workbookWrapper, ptgs);
    dest.setCellFormula(formula);
}

Я до сих пор не знаю, правильно ли он у меня был для всех формул ячеек, но у меня он работает, быстро и надежно.

person julien.giband    schedule 12.03.2014
comment
Я хватаюсь за соломинку 2 года спустя, но знаете ли вы, есть ли NPOI, эквивалентный setColumn(), setRow(), setFirstColumn() и setLastColumn()? Или если это было прекращено в последней версии POI/NPOI? - person justiceorjustus; 11.04.2017
comment
@justiceorjustus: я знаю, что опоздал на 6 лет, но в NPOI, Row, Column, FirstRow, FirstColumn есть общедоступные методы получения и установки. Из-за этого вы можете написать ref.Row += shiftRows,... - person tsukumogami; 20.07.2017

Я тоже думаю, что нет простого способа сделать это.

Даже примеры HSSF и XSSD на сайте POI, например. TimesheetDemo выполнить построение формулы вручную. например вокруг строки 110

String ref = (char)('A' + j) + "3:" + (char)('A' + j) + "12";
cell.setCellFormula("SUM(" + ref + ")");
person jitter    schedule 28.10.2009

Я заглянул внутрь класса FormulaEvaluator и нашел несколько внутренних классов POI, которые могут сделать всю работу за нас.

FormulaParser, который анализирует String для массива «разбирать вещи»:

String formula = cell.getCellFormula();
XSSFEvaluationWorkbook workbookWrapper = 
             XSSFEvaluationWorkbook.create((XSSFWorkbook) workbook);  
/* parse formula */
Ptg[] ptgs = FormulaParser.parse(formula, workbookWrapper, 
             FormulaType.CELL, 0 /*sheet index*/ );

ptgs теперь является нашей формулой в обратной польской нотации. Теперь просмотрите все элементы и измените ссылки одну за другой по своему усмотрению:

/* re-calculate cell references */
for( Ptg ptg  : ptgs )
    if( ptg instanceof RefPtgBase )    //base class for cell reference "things"
    {
        RefPtgBase ref = (RefPtgBase)ptg;
        if( ref.isColRelative() )
            ref.setColumn( ref.getColumn() + 0 );
        if( ref.isRowRelative() )
            ref.setRow( ref.getRow() + 1 );
    }

И вы готовы отрендерить "парсинг вещей" обратно в String:

formula = FormulaRenderer.toFormulaString(workbookWrapper, ptgs);
cell.setCellFormula( formula );
person user2622016    schedule 30.07.2013
comment
Это было очень полезно и кажется самым простым способом. Два дополнения: 1) Следует отметить, что код использует классы, помеченные как внутренние POI (например, XSSFEvaluationWorkbook), поэтому этот код может сломаться в более поздних версиях. 2) Также следует настроить AreaPtgBase вещей, описывающих области (например, A1:C3). Код прост и основан на обработке RefPtgBase, показанной в этом ответе. - person sven; 27.05.2015
comment
Я пробовал приведенный выше код и работал с Apache POI 4.1.2. Мне еще предстоит обрабатывать MAX, MIN случаи, когда используется диапазон ячеек. Спасибо @user2622016 - person Xtraterrestrial; 19.12.2020

Еще один способ относительного копирования формулы, протестирован на poi 3.12.

public static void copyCellFormula(Workbook workbook, int sheetIndex, int rowIndex, int sourceColumnIndex, int destinationColumnIndex){
    XSSFEvaluationWorkbook formulaParsingWorkbook = XSSFEvaluationWorkbook.create((XSSFWorkbook) workbook);
    SharedFormula sharedFormula = new SharedFormula(SpreadsheetVersion.EXCEL2007);
    Sheet sheet = workbook.getSheetAt(sheetIndex);
    Row lookupRow = sheet.getRow(rowIndex);
    Cell sourceCell = lookupRow.getCell(sourceColumnIndex);
    Ptg[] sharedFormulaPtg = FormulaParser.parse(sourceCell.getCellFormula(), formulaParsingWorkbook, FormulaType.CELL, sheetIndex);
    Ptg[] convertedFormulaPtg = sharedFormula.convertSharedFormulas(sharedFormulaPtg, 0, 1);
    Cell destinationCell = lookupRow.createCell(destinationColumnIndex);
    destinationCell.setCellFormula(FormulaRenderer.toFormulaString(formulaParsingWorkbook, convertedFormulaPtg));
}

При необходимости обновите общую формулу:

sharedFormula.convertSharedFormulas(sharedFormulaPtg, rowIndexOffset, columnIndexOffset);

Начиная с poi 3.12, SharedFormula не поддерживает ссылку на ячейку/формулу из других листов (='Sheet1'!A1). Вот обновление для SharedFormula:

public class SharedFormula {

    private final int _columnWrappingMask;
    private final int _rowWrappingMask;

    public SharedFormula(SpreadsheetVersion ssVersion) {
        this._columnWrappingMask = ssVersion.getLastColumnIndex();
        this._rowWrappingMask = ssVersion.getLastRowIndex();
    }

    public Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) {
        Ptg[] newPtgStack = new Ptg[ptgs.length];

        RefPtgBase areaNPtg = null;
        AreaPtgBase var9 = null;
        Object ptg = null;
        byte originalOperandClass = 0;
        for(int k = 0; k < ptgs.length; ++k) {
            ptg = ptgs[k];
            originalOperandClass = -1;
            if(!((Ptg)ptg).isBaseToken()) {
                originalOperandClass = ((Ptg)ptg).getPtgClass();
            }

            if(ptg instanceof RefPtgBase) {
                if(ptg instanceof Ref3DPxg) {
                    areaNPtg = (Ref3DPxg)ptg;
                    this.fixupRefRelativeRowAndColumn(areaNPtg, formulaRow, formulaColumn);
                    ptg = areaNPtg;
                }else if(ptg instanceof Ref3DPtg) {
                    areaNPtg = (Ref3DPtg)ptg;
                    this.fixupRefRelativeRowAndColumn(areaNPtg, formulaRow, formulaColumn);
                    ptg = areaNPtg;
                }else {
                    areaNPtg = (RefPtgBase)ptg;
                    ptg = new RefPtg(this.fixupRelativeRow(formulaRow, areaNPtg.getRow(), areaNPtg.isRowRelative()), this.fixupRelativeColumn(formulaColumn, areaNPtg.getColumn(), areaNPtg.isColRelative()), areaNPtg.isRowRelative(), areaNPtg.isColRelative());
                }
                ((Ptg)ptg).setClass(originalOperandClass);
            }else if(ptg instanceof AreaPtgBase) {
                if(ptg instanceof  Area3DPxg) {
                    var9 = (Area3DPxg)ptg;
                    this.fixupAreaRelativeRowAndColumn(var9, formulaRow, formulaColumn);
                    ptg = var9;
                }else if(ptg instanceof  Area3DPxg) {
                    var9 = (Area3DPtg)ptg;
                    this.fixupAreaRelativeRowAndColumn(var9, formulaRow, formulaColumn);
                    ptg = var9;
                }else {
                    var9 = (AreaPtgBase)ptg;
                    ptg = new AreaPtg(this.fixupRelativeRow(formulaRow, var9.getFirstRow(), var9.isFirstRowRelative()), this.fixupRelativeRow(formulaRow, var9.getLastRow(), var9.isLastRowRelative()), this.fixupRelativeColumn(formulaColumn, var9.getFirstColumn(), var9.isFirstColRelative()), this.fixupRelativeColumn(formulaColumn, var9.getLastColumn(), var9.isLastColRelative()), var9.isFirstRowRelative(), var9.isLastRowRelative(), var9.isFirstColRelative(), var9.isLastColRelative());
                }
                ((Ptg)ptg).setClass(originalOperandClass);
            }else if(ptg instanceof OperandPtg) {
                ptg = ((OperandPtg)ptg).copy();
            }

            newPtgStack[k] = (Ptg)ptg;
        }

        return newPtgStack;
    }

    protected void fixupRefRelativeRowAndColumn(RefPtgBase areaNPtg, int formulaRow, int formulaColumn){
        areaNPtg.setRow(this.fixupRelativeRow(formulaRow, areaNPtg.getRow(), areaNPtg.isRowRelative()));
        areaNPtg.setColumn(this.fixupRelativeColumn(formulaColumn, areaNPtg.getColumn(), areaNPtg.isColRelative()));
        areaNPtg.setRowRelative(areaNPtg.isRowRelative());
        areaNPtg.setColRelative(areaNPtg.isColRelative());
    }

    protected void fixupAreaRelativeRowAndColumn(AreaPtgBase var9, int formulaRow, int formulaColumn){
        var9.setFirstRow(this.fixupRelativeRow(formulaRow, var9.getFirstRow(), var9.isFirstRowRelative()));
        var9.setLastRow(this.fixupRelativeRow(formulaRow, var9.getLastRow(), var9.isLastRowRelative()));
        var9.setFirstColumn(this.fixupRelativeColumn(formulaColumn, var9.getFirstColumn(), var9.isFirstColRelative()));
        var9.setLastColumn(this.fixupRelativeColumn(formulaColumn, var9.getLastColumn(), var9.isLastColRelative()));
        var9.setFirstRowRelative(var9.isFirstRowRelative());
        var9.setLastRowRelative(var9.isLastRowRelative());
        var9.setFirstColRelative(var9.isFirstColRelative());
        var9.setLastColRelative(var9.isLastColRelative());
    }

    protected int fixupRelativeColumn(int currentcolumn, int column, boolean relative) {
        return relative?column + currentcolumn & this._columnWrappingMask:column;
    }

    protected int fixupRelativeRow(int currentrow, int row, boolean relative) {
        return relative?row + currentrow & this._rowWrappingMask:row;
    }

}
person jjcosare    schedule 20.07.2015

Я не думаю, что есть. POI пришлось бы анализировать формулу (с учетом A1, $A$1, $A1 и т. д.), и я не верю, что у нее есть такая возможность. Когда я делал это в прошлом, мне всегда приходилось справляться с этим самому. Извините - не тот ответ, на который вы рассчитывали!

person Brian Agnew    schedule 28.10.2009

вы можете попробовать некоторые сторонние библиотеки Excel, большинство из них могут обрабатывать формулы относительного/абсолютного диапазона.

person liya    schedule 29.10.2009
comment
Также он не предлагает конкретную библиотеку, которая решает проблему. - person sven; 27.05.2015