Passed
Push — master ( 2d1f4e...e6aacf )
by
unknown
26:57 queued 19:21
created

Formula::convertFunctionNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

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