2009-10-28 18 views
16

Utilizzo il POI di Apache per manipolare file Excel (.xls) con Java.POI/Excel: applicazione di formule in modo "relativo"

Sto provando a creare una nuova cella il cui contenuto è il risultato di una formula come se l'utente avesse copiato/incollato la formula (quello che io chiamo il modo "relativo", come opposto a "assoluto").

Per rendermi più chiaro, ecco un semplice esempio: La cella A1 contiene "1", B1 contiene "2", A2 contiene "3", B2 contiene "4". La cella A3 contiene la seguente formula "= A1 + B1". Se copio la formula nella cella A4 sotto excel, diventa "= A2 + B2": excel sta adattando il contenuto della formula in modo dinamico.

Purtroppo non riesco a ottenere lo stesso risultato in modo programmatico. L'unica soluzione che ho trovato è quella di tokenizzare la formula e fare il lavoro sporco da solo, ma dubito davvero che questo dovrebbe essere fatto in questo modo. Non sono riuscito a trovare ciò che sto cercando nelle guide o nell'API.

C'è un modo più semplice per risolvere questo problema? Se è il caso, puoi indicarmi la direzione giusta?

Con i migliori saluti,

Nils

risposta

2

Non credo che ci sia. Il POI dovrebbe analizzare la formula (prendendo in considerazione A1 contro $ A $ 1 vs $ A1 ecc.) E non credo che abbia questa capacità. Quando l'ho fatto in passato, ho sempre dovuto gestirlo da solo. Scusa, non la risposta che speravi!

8

Anch'io penso che non ci sia un modo semplice per farlo.

Anche lo HSSF and XSSD examples nel sito POI, ad es. TimesheetDemo compila manualmente la formula. per esempio. intorno alla linea 110

String ref = (char)('A' + j) + "3:" + (char)('A' + j) + "12"; 
cell.setCellFormula("SUM(" + ref + ")"); 
-2

si può provare alcuni terzi eccellere librarys, la maggior parte di loro in grado di gestire le relative formule/range assoluto.

+0

Né suggerisce una libreria concreta che risolva il problema. – sven

4

Ho guardato dentro la classe FormulaEvaluator e ho trovato alcune classi interne POI che possono fare il lavoro per noi.

FormulaParser, che analizza String alla serie di "analizzare le cose":

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

PTGS è ora la nostra formula in notazione polacca inversa. Ora passare attraverso tutti gli elementi e modificare i riferimenti ad uno ad uno come si desidera:

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

e si è pronti a rendere "analizzare le cose" back to String:

formula = FormulaRenderer.toFormulaString(workbookWrapper, ptgs); 
cell.setCellFormula(formula); 
+0

Questo è stato molto utile e sembra essere il modo più semplice. Due aggiunte: 1) Si noti che il codice utilizza classi contrassegnate come POI interne (ad esempio 'XSSFEvaluationWorkbook'), pertanto questo codice potrebbe interrompersi nelle versioni successive. 2) Si dovrebbe anche regolare "AreaPtgBase'" cose "che descrivono le aree (ad esempio' A1: C3'). Il codice è diretto in base alla gestione di 'RefPtgBase'shown in questa risposta. – sven

7

Nel mio senso, user2622016 ha ragione, tranne che la sua soluzione gestisce solo i riferimenti cell, al contrario dei riferimenti dell'area (ad esempio, non funziona per =SUM(A1:B8)).

Ecco come ho risolto in questo modo:

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

Io ancora non so se ce l'avevo corretta per tutte le formule di cella, ma funziona per me, veloce e affidabile.

+0

2 anni dopo mi sto avvicinando, ma sai se esiste un equivalente NPOI per setColumn(), setRow(), setFirstColumn() e setLastColumn()? O se è stato interrotto nella versione recente di POI/NPOI? – justiceorjustus

+1

@justiceorjustus: So che sono in ritardo di 6 anni, ma in NPOI, Row, Column, FirstRow, FirstColumn hanno tutti getter e setter pubblici. Per questo motivo puoi scrivere ref.Row + = shiftRows, ... – tsukumogami

3

Un altro modo per copiare formula relativamente, controllato con 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)); 
} 

Aggiorna formula condivisa come necessario:

sharedFormula.convertSharedFormulas(sharedFormulaPtg, rowIndexOffset, columnIndexOffset); 

Al poi 3,12, SharedFormula non supporta riferimento di cella/formula dalla altri fogli (= 'Foglio1'! A1). Ecco un aggiornamento a 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; 
    } 

} 
Problemi correlati