Skip to content

Commit

Permalink
StandardFormulaProcessor, FastFormulaProcessor (refactoring)
Browse files Browse the repository at this point in the history
- too long method
- similar code
  • Loading branch information
SoltauFintel committed Dec 28, 2023
1 parent cdaed28 commit a195630
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 177 deletions.
54 changes: 54 additions & 0 deletions jxls/src/main/java/org/jxls/formula/AbstractFormulaProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.jxls.common.CellRefColPrecedenceComparator;
import org.jxls.common.CellRefRowPrecedenceComparator;
import org.jxls.transform.Transformer;
import org.jxls.util.CellRefUtil;

/**
* Partial implementation of {@link FormulaProcessor} interface
Expand Down Expand Up @@ -336,4 +337,57 @@ protected List<CellRef> createTargetCellRefListByColumn(CellRef targetFormulaCel
}
return resultCellList;
}

static class FormulaProcessorContext {
Transformer transformer;
List<CellRef> targetFormulaCells;
Map<CellRef, List<CellRef>> targetCellRefMap;
Map<String, List<CellRef>> jointedCellRefMap;
List<CellRef> usedCellRefs = new ArrayList<>();
boolean isFormulaCellRefsEmpty;
boolean isFormulaJointedCellRefsEmpty;
String targetFormulaString;
CellRef targetFormulaCellRef;
}

protected void processTargetFormulaCells(CellData formulaCellData, Transformer transformer, Area area) {
transformer.getLogger().debug("Processing formula cell " + formulaCellData);
FormulaProcessorContext fpc = createFormulaProcessorContext(formulaCellData, transformer, area);

// process all of the result (target) formula cells
// a result formula cell is a cell into which the original cell with the formula was transformed
for (int i = 0; i < fpc.targetFormulaCells.size(); i++) {
fpc.targetFormulaCellRef = fpc.targetFormulaCells.get(i);
fpc.targetFormulaString = formulaCellData.getFormula();
if (formulaCellData.isParameterizedFormulaCell() && i < formulaCellData.getEvaluatedFormulas().size()) {
fpc.targetFormulaString = formulaCellData.getEvaluatedFormulas().get(i);
}
processTargetFormulaCell(i, formulaCellData, fpc);
}
}

protected abstract void processTargetFormulaCell(int i, CellData formulaCellData, FormulaProcessorContext fpc);

protected FormulaProcessorContext createFormulaProcessorContext(CellData formulaCellData, Transformer transformer, Area area) {
FormulaProcessorContext fpc = new FormulaProcessorContext();
fpc.transformer = transformer;
fpc.targetFormulaCells = formulaCellData.getTargetPos();
fpc.targetCellRefMap = buildTargetCellRefMap(transformer, area, formulaCellData);
fpc.jointedCellRefMap = buildJointedCellRefMap(transformer, formulaCellData);
return fpc;
}

protected void processTargetFormula(CellData formulaCellData, FormulaProcessorContext fpc) {
String sheetNameReplacementRegex = Pattern.quote(fpc.targetFormulaCellRef.getFormattedSheetName() + CellRefUtil.SHEET_NAME_DELIMITER);
fpc.targetFormulaString = fpc.targetFormulaString.replaceAll(sheetNameReplacementRegex, "");
// if there were no regular or jointed cell references found for this formula use a default value
// if set or 0
if (fpc.isFormulaCellRefsEmpty && fpc.isFormulaJointedCellRefsEmpty
&& (!formulaCellData.isParameterizedFormulaCell() || formulaCellData.isJointedFormulaCell())) {
fpc.targetFormulaString = formulaCellData.getDefaultValue() != null ? formulaCellData.getDefaultValue() : "0";
}
if (!fpc.targetFormulaString.isEmpty()) {
fpc.transformer.setFormula(new CellRef(fpc.targetFormulaCellRef), fpc.targetFormulaString);
}
}
}
166 changes: 75 additions & 91 deletions jxls/src/main/java/org/jxls/formula/FastFormulaProcessor.java
Original file line number Diff line number Diff line change
@@ -1,118 +1,102 @@
package org.jxls.formula;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jxls.area.Area;
import org.jxls.common.CellData;
import org.jxls.common.CellRef;
import org.jxls.transform.Transformer;
import org.jxls.util.CellRefUtil;

/**
* Fast formula processor implementation.
* It works correctly in 90% of cases and is much more faster than {@link StandardFormulaProcessor}.
*/
public class FastFormulaProcessor extends AbstractFormulaProcessor {

// TODO method too long
// TODO partially similar code to StandardFormulaProcessor
@Override
public void processAreaFormulas(Transformer transformer, Area area) {
Set<CellData> formulaCells = transformer.getFormulaCells();
for (CellData formulaCellData : formulaCells) {
if (area != null && area.getAreaRef() != null && !area.getAreaRef().getSheetName().equals(formulaCellData.getSheetName())) {
continue;
transformer.getFormulaCells().forEach(formulaCellData -> {
if (!(area != null && area.getAreaRef() != null && !area.getAreaRef().getSheetName().equals(formulaCellData.getSheetName()))) {
processTargetFormulaCells(formulaCellData, transformer, area);
}
List<CellRef> targetFormulaCells = formulaCellData.getTargetPos();
Map<CellRef, List<CellRef>> targetCellRefMap = buildTargetCellRefMap(transformer, area, formulaCellData);
Map<String, List<CellRef>> jointedCellRefMap = buildJointedCellRefMap(transformer, formulaCellData);
List<CellRef> usedCellRefs = new ArrayList<>();
});
}

// process all of the result (target) formula cells
// a result formula cell is a cell into which the original cell with the formula was transformed
for (int i = 0; i < targetFormulaCells.size(); i++) {
CellRef targetFormulaCellRef = targetFormulaCells.get(i);
String targetFormulaString = formulaCellData.getFormula();
if (formulaCellData.isParameterizedFormulaCell()) {
targetFormulaString = formulaCellData.getEvaluatedFormulas().get(i);
}
boolean isFormulaCellRefsEmpty = true;
// iterate through all the cell references used in the formula
for (Map.Entry<CellRef, List<CellRef>> cellRefEntry : targetCellRefMap.entrySet()) {
// target cells are the cells into which a cell ref from the original formula was transformed
List<CellRef> targetCells = cellRefEntry.getValue();
if (targetCells.isEmpty()) {
continue;
}
isFormulaCellRefsEmpty = false;
String replacementString;
// calculate the formula replacement string based on the formula strategy set for the cell
if (formulaCellData.getFormulaStrategy() == CellData.FormulaStrategy.BY_COLUMN) {
// BY_COLUMN strategy (non-default) means we will take only cell references in the same column as the original cell
List<CellRef> targetCellRefs = createTargetCellRefListByColumn(targetFormulaCellRef, targetCells, usedCellRefs);
usedCellRefs.addAll(targetCellRefs);
replacementString = createTargetCellRef(targetCellRefs);
} else if (targetCells.size() == targetFormulaCells.size()) {
// if the number of the cell reference target cells is the same as the number of cells into which
// the formula was transformed we assume that a formula target cell should use the
// corresponding target cell reference
CellRef targetCellRefCellRef = targetCells.get(i);
replacementString = targetCellRefCellRef.getCellName();
} else {
// trying to group the individual target cell refs used in a formula into a range
List<List<CellRef>> rangeList = groupByRanges(targetCells, targetFormulaCells.size());
if (rangeList.size() == targetFormulaCells.size()) {
// if the number of ranges equals to the number of target formula cells
// we assume the formula cells directly map onto ranges and so just taking a corresponding range by index
List<CellRef> range = rangeList.get(i);
replacementString = createTargetCellRef(range);
} else {
// the range grouping did not succeed and we just use the list of target cells to calculate the replacement string
replacementString = createTargetCellRef(targetCells);
}
}
String from = regexJointedLookBehind
+ sheetNameRegex(cellRefEntry)
+ getStrictCellNameRegex(Pattern.quote(cellRefEntry.getKey().getCellName()));
String to = Matcher.quoteReplacement(replacementString);
targetFormulaString = targetFormulaString.replaceAll(from, to);
}
boolean isFormulaJointedCellRefsEmpty = true;
// iterate through all the jointed cell references used in the formula
for (Map.Entry<String, List<CellRef>> jointedCellRefEntry : jointedCellRefMap.entrySet()) {
List<CellRef> targetCellRefList = jointedCellRefEntry.getValue();
if (targetCellRefList.isEmpty()) {
continue;
}
isFormulaJointedCellRefsEmpty = false;
// trying to group the target cell references into ranges
List<List<CellRef>> rangeList = groupByRanges(targetCellRefList, targetFormulaCells.size());
String replacementString;
if (rangeList.size() == targetFormulaCells.size()) {
// if the number of ranges equals to the number of target formula cells
// we assume the formula cells directly map onto ranges and so just taking a corresponding range by index
List<CellRef> range = rangeList.get(i);
replacementString = createTargetCellRef(range);
} else {
replacementString = createTargetCellRef(targetCellRefList);
}
targetFormulaString = targetFormulaString.replaceAll(Pattern.quote(jointedCellRefEntry.getKey()), replacementString);
}
String sheetNameReplacementRegex = targetFormulaCellRef.getFormattedSheetName() + CellRefUtil.SHEET_NAME_DELIMITER;
targetFormulaString = targetFormulaString.replaceAll(sheetNameReplacementRegex, "");
// if there were no regular or jointed cell references found for this formula use a default value
// if set or 0
if (isFormulaCellRefsEmpty && isFormulaJointedCellRefsEmpty
&& (!formulaCellData.isParameterizedFormulaCell() || formulaCellData.isJointedFormulaCell())) {
targetFormulaString = formulaCellData.getDefaultValue() != null ? formulaCellData.getDefaultValue() : "0";
@Override
protected void processTargetFormulaCell(int i, CellData formulaCellData, FormulaProcessorContext fpc) {
processTargetCellRefMap(i, formulaCellData, fpc.targetFormulaCellRef, fpc);
processJointedCellRefMap(i, fpc);
processTargetFormula(formulaCellData, fpc);
}

private void processTargetCellRefMap(int i, CellData formulaCellData, CellRef targetFormulaCellRef, FormulaProcessorContext fpc) {
fpc.isFormulaCellRefsEmpty = true;
// iterate through all the cell references used in the formula
for (Map.Entry<CellRef, List<CellRef>> cellRefEntry : fpc.targetCellRefMap.entrySet()) {
// target cells are the cells into which a cell ref from the original formula was transformed
List<CellRef> targetCells = cellRefEntry.getValue();
if (targetCells.isEmpty()) {
continue;
}
fpc.isFormulaCellRefsEmpty = false;
String replacementString;
// calculate the formula replacement string based on the formula strategy set for the cell
if (formulaCellData.getFormulaStrategy() == CellData.FormulaStrategy.BY_COLUMN) {
// BY_COLUMN strategy (non-default) means we will take only cell references in the same column as the original cell
List<CellRef> targetCellRefs = createTargetCellRefListByColumn(targetFormulaCellRef, targetCells, fpc.usedCellRefs);
fpc.usedCellRefs.addAll(targetCellRefs);
replacementString = createTargetCellRef(targetCellRefs);
} else if (targetCells.size() == fpc.targetFormulaCells.size()) {
// if the number of the cell reference target cells is the same as the number of cells into which
// the formula was transformed we assume that a formula target cell should use the
// corresponding target cell reference
CellRef targetCellRefCellRef = targetCells.get(i);
replacementString = targetCellRefCellRef.getCellName();
} else {
// trying to group the individual target cell refs used in a formula into a range
List<List<CellRef>> rangeList = groupByRanges(targetCells, fpc.targetFormulaCells.size());
if (rangeList.size() == fpc.targetFormulaCells.size()) {
// if the number of ranges equals to the number of target formula cells
// we assume the formula cells directly map onto ranges and so just taking a corresponding range by index
List<CellRef> range = rangeList.get(i);
replacementString = createTargetCellRef(range);
} else {
// the range grouping did not succeed and we just use the list of target cells to calculate the replacement string
replacementString = createTargetCellRef(targetCells);
}
transformer.setFormula(new CellRef(targetFormulaCellRef), targetFormulaString);
}
String from = regexJointedLookBehind
+ sheetNameRegex(cellRefEntry)
+ getStrictCellNameRegex(Pattern.quote(cellRefEntry.getKey().getCellName()));
String to = Matcher.quoteReplacement(replacementString);
fpc.targetFormulaString = fpc.targetFormulaString.replaceAll(from, to);
}
}

private void processJointedCellRefMap(int i, FormulaProcessorContext fpc) {
fpc.isFormulaJointedCellRefsEmpty = true;
// iterate through all the jointed cell references used in the formula
for (Map.Entry<String, List<CellRef>> jointedCellRefEntry : fpc.jointedCellRefMap.entrySet()) {
List<CellRef> targetCellRefList = jointedCellRefEntry.getValue();
if (targetCellRefList.isEmpty()) {
continue;
}
fpc.isFormulaJointedCellRefsEmpty = false;
// trying to group the target cell references into ranges
List<List<CellRef>> rangeList = groupByRanges(targetCellRefList, fpc.targetFormulaCells.size());
String replacementString;
if (rangeList.size() == fpc.targetFormulaCells.size()) {
// if the number of ranges equals to the number of target formula cells
// we assume the formula cells directly map onto ranges and so just taking a corresponding range by index
List<CellRef> range = rangeList.get(i);
replacementString = createTargetCellRef(range);
} else {
replacementString = createTargetCellRef(targetCellRefList);
}
fpc.targetFormulaString = fpc.targetFormulaString.replaceAll(Pattern.quote(jointedCellRefEntry.getKey()), replacementString);
}
}

Expand Down
Loading

0 comments on commit a195630

Please sign in to comment.