Passed
Pull Request — master (#4468)
by Owen
10:02
created

Formula   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 109
Duplicated Lines 0 %

Test Coverage

Coverage 95.08%

Importance

Changes 0
Metric Value
wmc 16
eloc 58
c 0
b 0
f 0
dl 0
loc 109
ccs 58
cts 61
cp 0.9508
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A convertDefinedNames() 0 24 3
A convertFormula() 0 10 2
B convertCellReferences() 0 56 9
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
4
5
use Composer\Pcre\Preg;
6
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
7
use PhpOffice\PhpSpreadsheet\DefinedName;
8
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
9
10
class Formula
11
{
12
    /** @var string[] */
13
    private array $definedNames = [];
14
15
    /**
16
     * @param DefinedName[] $definedNames
17
     */
18 40
    public function __construct(array $definedNames)
19
    {
20 40
        foreach ($definedNames as $definedName) {
21 2
            $this->definedNames[] = $definedName->getName();
22
        }
23
    }
24
25 9
    public function convertFormula(string $formula, string $worksheetName = ''): string
26
    {
27 9
        $formula = $this->convertCellReferences($formula, $worksheetName);
28 9
        $formula = $this->convertDefinedNames($formula);
29
30 9
        if (!str_starts_with($formula, '=')) {
31
            $formula = '=' . $formula;
32
        }
33
34 9
        return 'of:' . $formula;
35
    }
36
37 9
    private function convertDefinedNames(string $formula): string
38
    {
39 9
        $splitCount = Preg::matchAllWithOffsets(
40 9
            '/' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '/mui',
41 9
            $formula,
42 9
            $splitRanges
43 9
        );
44
45 9
        $lengths = array_map([StringHelper::class, 'strlenAllowNull'], array_column($splitRanges[0], 0));
46 9
        $offsets = array_column($splitRanges[0], 1);
47 9
        $values = array_column($splitRanges[0], 0);
48
49 9
        while ($splitCount > 0) {
50 9
            --$splitCount;
51 9
            $length = $lengths[$splitCount];
52 9
            $offset = $offsets[$splitCount];
53 9
            $value = $values[$splitCount];
54
55 9
            if (in_array($value, $this->definedNames, true)) {
56 1
                $formula = substr($formula, 0, $offset) . '$$' . $value . substr($formula, $offset + $length);
57
            }
58
        }
59
60 9
        return $formula;
61
    }
62
63 9
    private function convertCellReferences(string $formula, string $worksheetName): string
64
    {
65 9
        $splitCount = Preg::matchAllWithOffsets(
66 9
            '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
67 9
            $formula,
68 9
            $splitRanges
69 9
        );
70
71 9
        $lengths = array_map([StringHelper::class, 'strlenAllowNull'], array_column($splitRanges[0], 0));
72 9
        $offsets = array_column($splitRanges[0], 1);
73
74 9
        $worksheets = $splitRanges[2];
75 9
        $columns = $splitRanges[6];
76 9
        $rows = $splitRanges[7];
77
78
        // Replace any commas in the formula with semi-colons for Ods
79
        // If by chance there are commas in worksheet names, then they will be "fixed" again in the loop
80
        //    because we've already extracted worksheet names with our Preg::matchAllWithOffsets()
81 9
        $formula = str_replace(',', ';', $formula);
82 9
        while ($splitCount > 0) {
83 7
            --$splitCount;
84 7
            $length = $lengths[$splitCount];
85 7
            $offset = $offsets[$splitCount];
86 7
            $worksheet = $worksheets[$splitCount][0];
87 7
            $column = $columns[$splitCount][0];
88 7
            $row = $rows[$splitCount][0];
89
90 7
            $newRange = '';
91 7
            if (empty($worksheet)) {
92 7
                if (($offset === 0) || ($formula[$offset - 1] !== ':')) {
93
                    // We need a worksheet
94 7
                    $worksheet = $worksheetName;
95
                }
96
            } else {
97
                $worksheet = str_replace("''", "'", trim($worksheet, "'"));
98
            }
99 7
            if (!empty($worksheet)) {
100
                $newRange = "['" . str_replace("'", "''", $worksheet) . "'";
101 7
            } elseif (substr($formula, $offset - 1, 1) !== ':') {
102 7
                $newRange = '[';
103
            }
104 7
            $newRange .= '.';
105
106
            //if (!empty($column)) { // phpstan says always true
107 7
            $newRange .= $column;
108
            //}
109 7
            if (!empty($row)) {
110 7
                $newRange .= $row;
111
            }
112
            // close the wrapping [] unless this is the first part of a range
113 7
            $newRange .= substr($formula, $offset + $length, 1) !== ':' ? ']' : '';
114
115 7
            $formula = substr($formula, 0, $offset) . $newRange . substr($formula, $offset + $length);
116
        }
117
118 9
        return $formula;
119
    }
120
}
121