Passed
Pull Request — master (#4323)
by Owen
16:12 queued 04:14
created

Formula::convertDefinedNames()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3

Importance

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