2009-10-28 38 views
16

Estoy usando el POI de Apache para manipular archivos de Excel (.xls) con Java.POI/Excel: aplicando fórmulas de una manera "relativa"

Estoy tratando de crear una nueva celda cuyo contenido es el resultado de una fórmula como si el usuario hubiera copiado/pegado la fórmula (lo que yo llamo el modo "relativo", opuesto a "absoluto").

Para ser más claro, aquí hay un ejemplo simple: La celda A1 contiene "1", B1 contiene "2", A2 contiene "3", B2 contiene "4". La celda A3 contiene la siguiente fórmula "= A1 + B1". Si copio la fórmula en la celda A4 en Excel, se convierte en "= A2 + B2": excel está adaptando el contenido de la fórmula de forma dinámica.

Desafortunadamente no puedo obtener el mismo resultado programáticamente. La única solución que encontré es tokenizar la fórmula y hacer el trabajo sucio por mi cuenta, pero realmente dudo que esto se suponga que se haga de esa manera. No pude encontrar lo que estoy buscando en las guías o en la API.

¿Hay una manera más fácil de resolver este problema? Si es el caso, ¿pueden señalarme en la dirección correcta?

Saludos,

Nils

Respuesta

2

No creo que existe. POI tendría que analizar la fórmula (teniendo en cuenta A1 frente a $ A $ 1 frente a $ A1, etc.) y no creo que tenga esa capacidad. Cuando hice esto en el pasado, siempre tuve que gestionarlo yo mismo. Lo siento, ¡no es la respuesta que esperabas!

8

Yo también creo que no hay una manera fácil de hacer esto.

Incluso el HSSF and XSSD examples en el sitio de POI, p. TimesheetDemo haga la construcción de fórmula manualmente. p.ej. alrededor de la línea 110

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

puede probar algunas bibliotecas de terceros de Excel, la mayoría de ellas puede manejar las fórmulas de rango relativo/absoluto.

+0

Tampoco sugiere una biblioteca concreta que resuelve el problema. – sven

4

Miré dentro clase FormulaEvaluator y encontré algunas clases internas de POI que pueden hacer el trabajo por nosotros.

FormulaParser, que analiza cadena a variedad de "analizar cosas":

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

PTGS es ahora nuestra fórmula en notación polaca inversa. Ahora va a través de todos los elementos y modificar las referencias de uno en uno como desee:

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

Y ya está listo para hacer "cosas" analizar de nuevo a la secuencia:

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

Este fue muy útil y parece ser la manera más fácil. Dos adiciones: 1) Se debe tener en cuenta que el código utiliza clases marcadas como PDI internas (por ejemplo, 'XSSFEvaluationWorkbook'), por lo que este código podría aparecer en versiones posteriores. 2) También se deben ajustar las "cosas" AreaPtgBase' que describen áreas (por ejemplo, 'A1: C3'). El código es directo basado en el manejo de 'RefPtgBase' mostrado en esta respuesta. – sven

7

En mi sentido, user2622016 tiene razón, excepto que su solución solo gestiona referencias de celda, a diferencia de referencias de área (no funcionará para =SUM(A1:B8) por ejemplo).

Así es como me fijo esto:

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

sigo sin saber si lo tenía correcto para todas las fórmulas de las celdas, pero funciona para mí, rápido y fiable.

+0

Me agarro de las pajas 2 años después, pero ¿sabe si existe un NPOI equivalente a setColumn(), setRow(), setFirstColumn() y setLastColumn()? ¿O si fue descontinuado en la versión reciente de POI/NPOI? – justiceorjustus

+1

@justiceorjustus: Sé que tengo 6 años de retraso, pero en NPOI, Row, Column, FirstRow, FirstColumn todos tienen public getter and setter. Por eso puedes escribir ref.Row + = shiftRows, ... – tsukumogami

3

Otra forma de copiar fórmula relativamente, probado 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)); 
} 

Actualización de fórmula compartido según sea necesario:

sharedFormula.convertSharedFormulas(sharedFormulaPtg, rowIndexOffset, columnIndexOffset); 

Como de poi 3,12, SharedFormula no soporta referencia de celda/fórmula de otras hojas (= 'Hoja1'! A1). Aquí está una actualización de 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; 
    } 

} 
Cuestiones relacionadas