See Release Notes
Long Term Support Release
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Calculation\Engine; 4 5 use PhpOffice\PhpSpreadsheet\Calculation\Exception; 6 7 class BranchPruner 8 { 9 /** 10 * @var bool 11 */ 12 protected $branchPruningEnabled = true; 13 14 /** 15 * Used to generate unique store keys. 16 * 17 * @var int 18 */ 19 private $branchStoreKeyCounter = 0; 20 21 /** 22 * currently pending storeKey (last item of the storeKeysStack. 23 * 24 * @var ?string 25 */ 26 protected $pendingStoreKey; 27 28 /** 29 * @var string[] 30 */ 31 protected $storeKeysStack = []; 32 33 /** 34 * @var bool[] 35 */ 36 protected $conditionMap = []; 37 38 /** 39 * @var bool[] 40 */ 41 protected $thenMap = []; 42 43 /** 44 * @var bool[] 45 */ 46 protected $elseMap = []; 47 48 /** 49 * @var int[] 50 */ 51 protected $braceDepthMap = []; 52 53 /** 54 * @var null|string 55 */ 56 protected $currentCondition; 57 58 /** 59 * @var null|string 60 */ 61 protected $currentOnlyIf; 62 63 /** 64 * @var null|string 65 */ 66 protected $currentOnlyIfNot; 67 68 /** 69 * @var null|string 70 */ 71 protected $previousStoreKey; 72 73 public function __construct(bool $branchPruningEnabled) 74 { 75 $this->branchPruningEnabled = $branchPruningEnabled; 76 } 77 78 public function clearBranchStore(): void 79 { 80 $this->branchStoreKeyCounter = 0; 81 } 82 83 public function initialiseForLoop(): void 84 { 85 $this->currentCondition = null; 86 $this->currentOnlyIf = null; 87 $this->currentOnlyIfNot = null; 88 $this->previousStoreKey = null; 89 $this->pendingStoreKey = empty($this->storeKeysStack) ? null : end($this->storeKeysStack); 90 91 if ($this->branchPruningEnabled) { 92 $this->initialiseCondition(); 93 $this->initialiseThen(); 94 $this->initialiseElse(); 95 } 96 } 97 98 private function initialiseCondition(): void 99 { 100 if (isset($this->conditionMap[$this->pendingStoreKey]) && $this->conditionMap[$this->pendingStoreKey]) { 101 $this->currentCondition = $this->pendingStoreKey; 102 $stackDepth = count($this->storeKeysStack); 103 if ($stackDepth > 1) { 104 // nested if 105 $this->previousStoreKey = $this->storeKeysStack[$stackDepth - 2]; 106 } 107 } 108 } 109 110 private function initialiseThen(): void 111 { 112 if (isset($this->thenMap[$this->pendingStoreKey]) && $this->thenMap[$this->pendingStoreKey]) { 113 $this->currentOnlyIf = $this->pendingStoreKey; 114 } elseif ( 115 isset($this->previousStoreKey, $this->thenMap[$this->previousStoreKey]) 116 && $this->thenMap[$this->previousStoreKey] 117 ) { 118 $this->currentOnlyIf = $this->previousStoreKey; 119 } 120 } 121 122 private function initialiseElse(): void 123 { 124 if (isset($this->elseMap[$this->pendingStoreKey]) && $this->elseMap[$this->pendingStoreKey]) { 125 $this->currentOnlyIfNot = $this->pendingStoreKey; 126 } elseif ( 127 isset($this->previousStoreKey, $this->elseMap[$this->previousStoreKey]) 128 && $this->elseMap[$this->previousStoreKey] 129 ) { 130 $this->currentOnlyIfNot = $this->previousStoreKey; 131 } 132 } 133 134 public function decrementDepth(): void 135 { 136 if (!empty($this->pendingStoreKey)) { 137 --$this->braceDepthMap[$this->pendingStoreKey]; 138 } 139 } 140 141 public function incrementDepth(): void 142 { 143 if (!empty($this->pendingStoreKey)) { 144 ++$this->braceDepthMap[$this->pendingStoreKey]; 145 } 146 } 147 148 public function functionCall(string $functionName): void 149 { 150 if ($this->branchPruningEnabled && ($functionName === 'IF(')) { 151 // we handle a new if 152 $this->pendingStoreKey = $this->getUnusedBranchStoreKey(); 153 $this->storeKeysStack[] = $this->pendingStoreKey; 154 $this->conditionMap[$this->pendingStoreKey] = true; 155 $this->braceDepthMap[$this->pendingStoreKey] = 0; 156 } elseif (!empty($this->pendingStoreKey) && array_key_exists($this->pendingStoreKey, $this->braceDepthMap)) { 157 // this is not an if but we go deeper 158 ++$this->braceDepthMap[$this->pendingStoreKey]; 159 } 160 } 161 162 public function argumentSeparator(): void 163 { 164 if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === 0) { 165 // We must go to the IF next argument 166 if ($this->conditionMap[$this->pendingStoreKey]) { 167 $this->conditionMap[$this->pendingStoreKey] = false; 168 $this->thenMap[$this->pendingStoreKey] = true; 169 } elseif ($this->thenMap[$this->pendingStoreKey]) { 170 $this->thenMap[$this->pendingStoreKey] = false; 171 $this->elseMap[$this->pendingStoreKey] = true; 172 } elseif ($this->elseMap[$this->pendingStoreKey]) { 173 throw new Exception('Reaching fourth argument of an IF'); 174 } 175 } 176 } 177 178 /** 179 * @param mixed $value 180 */ 181 public function closingBrace($value): void 182 { 183 if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === -1) { 184 // we are closing an IF( 185 if ($value !== 'IF(') { 186 throw new Exception('Parser bug we should be in an "IF("'); 187 } 188 189 if ($this->conditionMap[$this->pendingStoreKey]) { 190 throw new Exception('We should not be expecting a condition'); 191 } 192 193 $this->thenMap[$this->pendingStoreKey] = false; 194 $this->elseMap[$this->pendingStoreKey] = false; 195 --$this->braceDepthMap[$this->pendingStoreKey]; 196 array_pop($this->storeKeysStack); 197 $this->pendingStoreKey = null; 198 } 199 } 200 201 public function currentCondition(): ?string 202 { 203 return $this->currentCondition; 204 } 205 206 public function currentOnlyIf(): ?string 207 { 208 return $this->currentOnlyIf; 209 } 210 211 public function currentOnlyIfNot(): ?string 212 { 213 return $this->currentOnlyIfNot; 214 } 215 216 private function getUnusedBranchStoreKey(): string 217 { 218 $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter; 219 ++$this->branchStoreKeyCounter; 220 221 return $storeKeyValue; 222 } 223 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body