PreCalcTest.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheetTests\Writer;
  3. use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
  4. use PhpOffice\PhpSpreadsheet\IOFactory;
  5. use PhpOffice\PhpSpreadsheet\Shared\File;
  6. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  7. use PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension;
  8. use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
  9. class PreCalcTest extends AbstractFunctional
  10. {
  11. /** @var string */
  12. private $outfile = '';
  13. protected function tearDown(): void
  14. {
  15. if ($this->outfile !== '') {
  16. unlink($this->outfile);
  17. $this->outfile = '';
  18. }
  19. }
  20. public function providerPreCalc(): array
  21. {
  22. return [
  23. [true, 'Xlsx'],
  24. [false, 'Xlsx'],
  25. [null, 'Xlsx'],
  26. [true, 'Xls'],
  27. [false, 'Xls'],
  28. [null, 'Xls'],
  29. [true, 'Ods'],
  30. [false, 'Ods'],
  31. [null, 'Ods'],
  32. [true, 'Html'],
  33. [false, 'Html'],
  34. [null, 'Html'],
  35. [true, 'Csv'],
  36. [false, 'Csv'],
  37. [null, 'Csv'],
  38. ];
  39. }
  40. private static function autoSize(?ColumnDimension $columnDimension): void
  41. {
  42. if ($columnDimension === null) {
  43. self::fail('Unable to getColumnDimension');
  44. } else {
  45. $columnDimension->setAutoSize(true);
  46. }
  47. }
  48. private static function verifyA2(Calculation $calculation, string $title, ?bool $preCalc): void
  49. {
  50. $cellValue = 0;
  51. // A2 has no cached calculation value if preCalc is false
  52. if ($preCalc === false) {
  53. self::assertFalse($calculation->getValueFromCache("$title!A2", $cellValue));
  54. } else {
  55. self::assertTrue($calculation->getValueFromCache("$title!A2", $cellValue));
  56. self::assertSame(3, $cellValue);
  57. }
  58. }
  59. private const AUTOSIZE_TYPES = ['Xlsx', 'Xls', 'Html', 'Ods'];
  60. private static function verifyA3B2(Calculation $calculation, string $title, ?bool $preCalc, string $type): void
  61. {
  62. $cellValue = 0;
  63. if (in_array($type, self::AUTOSIZE_TYPES) || $preCalc !== false) {
  64. // These 3 types support auto-sizing.
  65. // A3 has cached calculation value because it is used in B2 calculation
  66. self::assertTrue($calculation->getValueFromCache("$title!A3", $cellValue));
  67. self::assertSame(11, $cellValue);
  68. // B2 has cached calculation value because its column is auto-sized
  69. self::assertTrue($calculation->getValueFromCache("$title!B2", $cellValue));
  70. self::assertSame(14, $cellValue);
  71. } else {
  72. self::assertFalse($calculation->getValueFromCache("$title!A3", $cellValue));
  73. self::assertFalse($calculation->getValueFromCache("$title!B2", $cellValue));
  74. }
  75. }
  76. private static function readFile(string $file): string
  77. {
  78. $dataOut = '';
  79. $data = file_get_contents($file);
  80. // confirm that file contains B2 pre-calculated or not as appropriate
  81. if ($data === false) {
  82. self::fail("Unable to read $file");
  83. } else {
  84. $dataOut = $data;
  85. }
  86. return $dataOut;
  87. }
  88. private function verifyXlsx(?bool $preCalc, string $type): void
  89. {
  90. if ($type === 'Xlsx') {
  91. $file = 'zip://';
  92. $file .= $this->outfile;
  93. $file .= '#xl/worksheets/sheet1.xml';
  94. $data = self::readFile($file);
  95. // confirm that file contains B2 pre-calculated or not as appropriate
  96. if ($preCalc === false) {
  97. self::assertStringContainsString('<c r="B2" t="str"><f>3+A3</f><v>0</v></c>', $data);
  98. } else {
  99. self::assertStringContainsString('<c r="B2"><f>3+A3</f><v>14</v></c>', $data);
  100. }
  101. $file = 'zip://';
  102. $file .= $this->outfile;
  103. $file .= '#xl/workbook.xml';
  104. $data = self::readFile($file);
  105. // confirm whether workbook is set to recalculate
  106. if ($preCalc === false) {
  107. self::assertStringContainsString('<calcPr calcId="999999" calcMode="auto" calcCompleted="0" fullCalcOnLoad="1" forceFullCalc="1"/>', $data);
  108. } else {
  109. self::assertStringContainsString('<calcPr calcId="999999" calcMode="auto" calcCompleted="1" fullCalcOnLoad="0" forceFullCalc="0"/>', $data);
  110. }
  111. }
  112. }
  113. private function verifyOds(?bool $preCalc, string $type): void
  114. {
  115. if ($type === 'Ods') {
  116. $file = 'zip://';
  117. $file .= $this->outfile;
  118. $file .= '#content.xml';
  119. $data = self::readFile($file);
  120. // confirm that file contains B2 pre-calculated or not as appropriate
  121. if ($preCalc === false) {
  122. self::assertStringContainsString('table:formula="of:=3+[.A3]" office:value-type="string" office:value="=3+A3"', $data);
  123. } else {
  124. self::assertStringContainsString(' table:formula="of:=3+[.A3]" office:value-type="float" office:value="14"', $data);
  125. }
  126. }
  127. }
  128. private function verifyHtml(?bool $preCalc, string $type): void
  129. {
  130. if ($type === 'Html') {
  131. $data = self::readFile($this->outfile);
  132. // confirm that file contains B2 pre-calculated or not as appropriate
  133. if ($preCalc === false) {
  134. self::assertStringContainsString('>=1+2</td>', $data);
  135. self::assertStringContainsString('>=3+A3</td>', $data);
  136. self::assertStringContainsString('>=5+6</td>', $data);
  137. } else {
  138. self::assertStringContainsString('>3</td>', $data);
  139. self::assertStringContainsString('>14</td>', $data);
  140. self::assertStringContainsString('>11</td>', $data);
  141. }
  142. }
  143. }
  144. private function verifyCsv(?bool $preCalc, string $type): void
  145. {
  146. if ($type === 'Csv') {
  147. $data = self::readFile($this->outfile);
  148. // confirm that file contains B2 pre-calculated or not as appropriate
  149. if ($preCalc === false) {
  150. self::assertStringContainsString('"=1+2"', $data);
  151. self::assertStringContainsString('"=3+A3"', $data);
  152. self::assertStringContainsString('"=5+6"', $data);
  153. } else {
  154. self::assertStringContainsString('"3"', $data);
  155. self::assertStringContainsString('"14"', $data);
  156. self::assertStringContainsString('"11"', $data);
  157. }
  158. }
  159. }
  160. /**
  161. * @dataProvider providerPreCalc
  162. */
  163. public function testPreCalc(?bool $preCalc, string $type): void
  164. {
  165. $spreadsheet = new Spreadsheet();
  166. $sheet = $spreadsheet->getActiveSheet();
  167. $sheet->getCell('A1')->setValue('Column not set to autoSize');
  168. $sheet->getCell('B1')->setValue('Column set to autoSize');
  169. $sheet->getCell('A2')->setValue('=1+2');
  170. $sheet->getCell('A3')->setValue('=5+6');
  171. $sheet->getCell('B2')->setValue('=3+A3');
  172. $columnDimension = $sheet->getColumnDimension('B');
  173. self::autoSize($columnDimension);
  174. $writer = IOFactory::createWriter($spreadsheet, $type);
  175. if ($preCalc !== null) {
  176. $writer->setPreCalculateFormulas($preCalc);
  177. }
  178. $this->outfile = File::temporaryFilename();
  179. $writer->save($this->outfile);
  180. $title = $sheet->getTitle();
  181. $calculation = Calculation::getInstance($spreadsheet);
  182. // verify values in Calculation cache
  183. self::verifyA2($calculation, $title, $preCalc);
  184. self::verifyA3B2($calculation, $title, $preCalc, $type);
  185. // verify values in output file
  186. $this->verifyXlsx($preCalc, $type);
  187. $this->verifyOds($preCalc, $type);
  188. $this->verifyHtml($preCalc, $type);
  189. $this->verifyCsv($preCalc, $type);
  190. }
  191. }