From a1956304d2ac4419180c6594b0f44e13998b978a Mon Sep 17 00:00:00 2001 From: Marcus Warm Date: Thu, 28 Dec 2023 12:52:18 +0100 Subject: [PATCH] StandardFormulaProcessor, FastFormulaProcessor (refactoring) - too long method - similar code --- .../formula/AbstractFormulaProcessor.java | 54 ++++++ .../jxls/formula/FastFormulaProcessor.java | 166 ++++++++---------- .../formula/StandardFormulaProcessor.java | 154 +++++++--------- 3 files changed, 197 insertions(+), 177 deletions(-) diff --git a/jxls/src/main/java/org/jxls/formula/AbstractFormulaProcessor.java b/jxls/src/main/java/org/jxls/formula/AbstractFormulaProcessor.java index de1766bf..8e3fdab1 100644 --- a/jxls/src/main/java/org/jxls/formula/AbstractFormulaProcessor.java +++ b/jxls/src/main/java/org/jxls/formula/AbstractFormulaProcessor.java @@ -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 @@ -336,4 +337,57 @@ protected List createTargetCellRefListByColumn(CellRef targetFormulaCel } return resultCellList; } + + static class FormulaProcessorContext { + Transformer transformer; + List targetFormulaCells; + Map> targetCellRefMap; + Map> jointedCellRefMap; + List 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); + } + } } diff --git a/jxls/src/main/java/org/jxls/formula/FastFormulaProcessor.java b/jxls/src/main/java/org/jxls/formula/FastFormulaProcessor.java index 05b780db..9adf9c9b 100644 --- a/jxls/src/main/java/org/jxls/formula/FastFormulaProcessor.java +++ b/jxls/src/main/java/org/jxls/formula/FastFormulaProcessor.java @@ -1,9 +1,7 @@ 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; @@ -11,7 +9,6 @@ import org.jxls.common.CellData; import org.jxls.common.CellRef; import org.jxls.transform.Transformer; -import org.jxls.util.CellRefUtil; /** * Fast formula processor implementation. @@ -19,100 +16,87 @@ */ public class FastFormulaProcessor extends AbstractFormulaProcessor { - // TODO method too long - // TODO partially similar code to StandardFormulaProcessor @Override public void processAreaFormulas(Transformer transformer, Area area) { - Set 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 targetFormulaCells = formulaCellData.getTargetPos(); - Map> targetCellRefMap = buildTargetCellRefMap(transformer, area, formulaCellData); - Map> jointedCellRefMap = buildJointedCellRefMap(transformer, formulaCellData); - List 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> cellRefEntry : targetCellRefMap.entrySet()) { - // target cells are the cells into which a cell ref from the original formula was transformed - List 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 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> 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 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> jointedCellRefEntry : jointedCellRefMap.entrySet()) { - List targetCellRefList = jointedCellRefEntry.getValue(); - if (targetCellRefList.isEmpty()) { - continue; - } - isFormulaJointedCellRefsEmpty = false; - // trying to group the target cell references into ranges - List> 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 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> cellRefEntry : fpc.targetCellRefMap.entrySet()) { + // target cells are the cells into which a cell ref from the original formula was transformed + List 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 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> 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 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> jointedCellRefEntry : fpc.jointedCellRefMap.entrySet()) { + List targetCellRefList = jointedCellRefEntry.getValue(); + if (targetCellRefList.isEmpty()) { + continue; + } + fpc.isFormulaJointedCellRefsEmpty = false; + // trying to group the target cell references into ranges + List> 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 range = rangeList.get(i); + replacementString = createTargetCellRef(range); + } else { + replacementString = createTargetCellRef(targetCellRefList); + } + fpc.targetFormulaString = fpc.targetFormulaString.replaceAll(Pattern.quote(jointedCellRefEntry.getKey()), replacementString); } } diff --git a/jxls/src/main/java/org/jxls/formula/StandardFormulaProcessor.java b/jxls/src/main/java/org/jxls/formula/StandardFormulaProcessor.java index 49cd5f97..2dd33622 100644 --- a/jxls/src/main/java/org/jxls/formula/StandardFormulaProcessor.java +++ b/jxls/src/main/java/org/jxls/formula/StandardFormulaProcessor.java @@ -5,7 +5,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -14,7 +13,6 @@ import org.jxls.common.CellData; import org.jxls.common.CellRef; import org.jxls.transform.Transformer; -import org.jxls.util.CellRefUtil; /** * This is a standard formula processor implementation which takes into account @@ -26,8 +24,6 @@ public class StandardFormulaProcessor extends AbstractFormulaProcessor { private static final int MAX_NUM_ARGS_FOR_SUM = 255; - // TODO method too long - // TODO partially similar code to FastFormulaProcessor /** * The method transforms all the formula cells according to the command * transformations happened during the area processing @@ -36,87 +32,73 @@ public class StandardFormulaProcessor extends AbstractFormulaProcessor { */ @Override public void processAreaFormulas(Transformer transformer, Area area) { - Set formulaCells = transformer.getFormulaCells(); - for (CellData formulaCellData : formulaCells) { - if (formulaCellData.getArea() == null || !area.getAreaRef().getSheetName().equals(formulaCellData.getSheetName())) { + transformer.getFormulaCells().forEach(formulaCellData -> { + if (!(formulaCellData.getArea() == null || !area.getAreaRef().getSheetName().equals(formulaCellData.getSheetName()))) { + processTargetFormulaCells(formulaCellData, transformer, area); + } + }); + } + + @Override + protected void processTargetFormulaCell(int i, CellData formulaCellData, FormulaProcessorContext fpc) { + AreaRef formulaSourceAreaRef = formulaCellData.getArea().getAreaRef(); + AreaRef formulaTargetAreaRef = formulaCellData.getTargetParentAreaRef().get(i); + processTargetCellRefMap(formulaCellData, fpc.targetFormulaCellRef, formulaSourceAreaRef, formulaTargetAreaRef, fpc); + processJointedCellRefMap(fpc.targetFormulaCellRef, formulaSourceAreaRef, formulaTargetAreaRef, fpc); + processTargetFormula(formulaCellData, fpc); + } + + private void processTargetCellRefMap(CellData formulaCellData, CellRef targetFormulaCellRef, + AreaRef formulaSourceAreaRef, AreaRef formulaTargetAreaRef, FormulaProcessorContext fpc) { + fpc.isFormulaCellRefsEmpty = true; + for (Map.Entry> cellRefEntry : fpc.targetCellRefMap.entrySet()) { + List targetCells = cellRefEntry.getValue(); + if (targetCells.isEmpty()) { continue; } - transformer.getLogger().debug("Processing formula cell " + formulaCellData); - List targetFormulaCells = formulaCellData.getTargetPos(); - Map> targetCellRefMap = buildTargetCellRefMap(transformer, area, formulaCellData); - Map> jointedCellRefMap = buildJointedCellRefMap(transformer, formulaCellData); - List usedCellRefs = new ArrayList<>(); + fpc.isFormulaCellRefsEmpty = false; + List replacementCells = findFormulaCellRefReplacements( + fpc.transformer, targetFormulaCellRef, formulaSourceAreaRef, + formulaTargetAreaRef, cellRefEntry); + if (formulaCellData.getFormulaStrategy() == CellData.FormulaStrategy.BY_COLUMN) { + // for BY_COLUMN formula strategy we take only a subset of the cells + replacementCells = createTargetCellRefListByColumn(targetFormulaCellRef, replacementCells, fpc.usedCellRefs); + fpc.usedCellRefs.addAll(replacementCells); + } + String replacementString = createTargetCellRef(replacementCells); + if (fpc.targetFormulaString.startsWith("SUM") + && countOccurences(replacementString, ',') >= MAX_NUM_ARGS_FOR_SUM) { + // Excel doesn't support more than 255 arguments in functions. + // Thus, we just concatenate all cells with "+" to have the same effect (see issue B059 for more detail) + fpc.targetFormulaString = replacementString.replaceAll(",", "+"); + } else { + String from = regexJointedLookBehind + + sheetNameRegex(cellRefEntry) + + regexExcludePrefixSymbols + + Pattern.quote(cellRefEntry.getKey().getCellName()); + String to = Matcher.quoteReplacement(replacementString); + fpc.targetFormulaString = fpc.targetFormulaString.replaceAll(from, to); + } + } + } - // 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() && i < formulaCellData.getEvaluatedFormulas().size()) { - targetFormulaString = formulaCellData.getEvaluatedFormulas().get(i); - } - AreaRef formulaSourceAreaRef = formulaCellData.getArea().getAreaRef(); - AreaRef formulaTargetAreaRef = formulaCellData.getTargetParentAreaRef().get(i); - boolean isFormulaCellRefsEmpty = true; - for (Map.Entry> cellRefEntry : targetCellRefMap.entrySet()) { - List targetCells = cellRefEntry.getValue(); - if (targetCells.isEmpty()) { - continue; - } - isFormulaCellRefsEmpty = false; - List replacementCells = findFormulaCellRefReplacements( - transformer, targetFormulaCellRef, formulaSourceAreaRef, - formulaTargetAreaRef, cellRefEntry); - if (formulaCellData.getFormulaStrategy() == CellData.FormulaStrategy.BY_COLUMN) { - // for BY_COLUMN formula strategy we take only a subset of the cells - replacementCells = createTargetCellRefListByColumn(targetFormulaCellRef, replacementCells, - usedCellRefs); - usedCellRefs.addAll(replacementCells); - } - String replacementString = createTargetCellRef(replacementCells); - if (targetFormulaString.startsWith("SUM") - && countOccurences(replacementString, ',') >= MAX_NUM_ARGS_FOR_SUM) { - // Excel doesn't support more than 255 arguments in functions. - // Thus, we just concatenate all cells with "+" to have the same effect (see issue B059 for more detail) - targetFormulaString = replacementString.replaceAll(",", "+"); - } else { - String from = regexJointedLookBehind - + sheetNameRegex(cellRefEntry) - + regexExcludePrefixSymbols - + 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> jointedCellRefEntry : jointedCellRefMap.entrySet()) { - List targetCellRefList = jointedCellRefEntry.getValue(); - if (targetCellRefList.isEmpty()) { - continue; - } - Collections.sort(targetCellRefList); - isFormulaJointedCellRefsEmpty = false; - Map.Entry> cellRefMapEntryParam = - new AbstractMap.SimpleImmutableEntry>(null, targetCellRefList); - List replacementCells = findFormulaCellRefReplacements( - transformer, targetFormulaCellRef, formulaSourceAreaRef, - formulaTargetAreaRef, cellRefMapEntryParam); - String replacementString = createTargetCellRef(replacementCells); - targetFormulaString = targetFormulaString.replaceAll(Pattern.quote(jointedCellRefEntry.getKey()), replacementString); - } - String sheetNameReplacementRegex = Pattern.quote(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"; - } - if (!targetFormulaString.isEmpty()) { - transformer.setFormula(new CellRef(targetFormulaCellRef), targetFormulaString); - } + private void processJointedCellRefMap(CellRef targetFormulaCellRef, AreaRef formulaSourceAreaRef, AreaRef formulaTargetAreaRef, FormulaProcessorContext fpc) { + fpc.isFormulaJointedCellRefsEmpty = true; + // iterate through all the jointed cell references used in the formula + for (Map.Entry> jointedCellRefEntry : fpc.jointedCellRefMap.entrySet()) { + List targetCellRefList = jointedCellRefEntry.getValue(); + if (targetCellRefList.isEmpty()) { + continue; } + Collections.sort(targetCellRefList); + fpc.isFormulaJointedCellRefsEmpty = false; + Map.Entry> cellRefMapEntryParam = + new AbstractMap.SimpleImmutableEntry>(null, targetCellRefList); + List replacementCells = findFormulaCellRefReplacements( + fpc.transformer, targetFormulaCellRef, formulaSourceAreaRef, + formulaTargetAreaRef, cellRefMapEntryParam); + String replacementString = createTargetCellRef(replacementCells); + fpc.targetFormulaString = fpc.targetFormulaString.replaceAll(Pattern.quote(jointedCellRefEntry.getKey()), replacementString); } } @@ -170,14 +152,14 @@ private List findRelevantCellReferences(List cellReferenceTarg /** * Calculates a number of occurences of a symbol in the string - * @param str - - * @param ch - + * @param string - + * @param symbol - * @return - */ - private int countOccurences(String str, char ch) { + private int countOccurences(String string, char symbol) { int count = 0; - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) == ch) { + for (int i = 0; i < string.length(); i++) { + if (string.charAt(i) == symbol) { count++; } }