/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php

https://github.com/sbourget/moodle · PHP · 242 lines · 184 code · 31 blank · 27 comment · 14 complexity · e6dbddc78b339c45787c5eb0aacbce79 MD5 · raw file

  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  3. use Exception;
  4. use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
  5. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  6. use PhpOffice\PhpSpreadsheet\DefinedName;
  7. use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
  8. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  9. use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  10. class DefinedNames
  11. {
  12. /** @var XMLWriter */
  13. private $objWriter;
  14. /** @var Spreadsheet */
  15. private $spreadsheet;
  16. public function __construct(XMLWriter $objWriter, Spreadsheet $spreadsheet)
  17. {
  18. $this->objWriter = $objWriter;
  19. $this->spreadsheet = $spreadsheet;
  20. }
  21. public function write(): void
  22. {
  23. // Write defined names
  24. $this->objWriter->startElement('definedNames');
  25. // Named ranges
  26. if (count($this->spreadsheet->getDefinedNames()) > 0) {
  27. // Named ranges
  28. $this->writeNamedRangesAndFormulae();
  29. }
  30. // Other defined names
  31. $sheetCount = $this->spreadsheet->getSheetCount();
  32. for ($i = 0; $i < $sheetCount; ++$i) {
  33. // NamedRange for autoFilter
  34. $this->writeNamedRangeForAutofilter($this->spreadsheet->getSheet($i), $i);
  35. // NamedRange for Print_Titles
  36. $this->writeNamedRangeForPrintTitles($this->spreadsheet->getSheet($i), $i);
  37. // NamedRange for Print_Area
  38. $this->writeNamedRangeForPrintArea($this->spreadsheet->getSheet($i), $i);
  39. }
  40. $this->objWriter->endElement();
  41. }
  42. /**
  43. * Write defined names.
  44. */
  45. private function writeNamedRangesAndFormulae(): void
  46. {
  47. // Loop named ranges
  48. $definedNames = $this->spreadsheet->getDefinedNames();
  49. foreach ($definedNames as $definedName) {
  50. $this->writeDefinedName($definedName);
  51. }
  52. }
  53. /**
  54. * Write Defined Name for named range.
  55. */
  56. private function writeDefinedName(DefinedName $definedName): void
  57. {
  58. // definedName for named range
  59. $local = -1;
  60. if ($definedName->getLocalOnly() && $definedName->getScope() !== null) {
  61. try {
  62. $local = $definedName->getScope()->getParent()->getIndex($definedName->getScope());
  63. } catch (Exception $e) {
  64. // See issue 2266 - deleting sheet which contains
  65. // defined names will cause Exception above.
  66. return;
  67. }
  68. }
  69. $this->objWriter->startElement('definedName');
  70. $this->objWriter->writeAttribute('name', $definedName->getName());
  71. if ($local >= 0) {
  72. $this->objWriter->writeAttribute(
  73. 'localSheetId',
  74. "$local"
  75. );
  76. }
  77. $definedRange = $this->getDefinedRange($definedName);
  78. $this->objWriter->writeRawData($definedRange);
  79. $this->objWriter->endElement();
  80. }
  81. /**
  82. * Write Defined Name for autoFilter.
  83. */
  84. private function writeNamedRangeForAutofilter(Worksheet $worksheet, int $worksheetId = 0): void
  85. {
  86. // NamedRange for autoFilter
  87. $autoFilterRange = $worksheet->getAutoFilter()->getRange();
  88. if (!empty($autoFilterRange)) {
  89. $this->objWriter->startElement('definedName');
  90. $this->objWriter->writeAttribute('name', '_xlnm._FilterDatabase');
  91. $this->objWriter->writeAttribute('localSheetId', "$worksheetId");
  92. $this->objWriter->writeAttribute('hidden', '1');
  93. // Create absolute coordinate and write as raw text
  94. $range = Coordinate::splitRange($autoFilterRange);
  95. $range = $range[0];
  96. // Strip any worksheet ref so we can make the cell ref absolute
  97. [, $range[0]] = Worksheet::extractSheetTitle($range[0], true);
  98. $range[0] = Coordinate::absoluteCoordinate($range[0]);
  99. $range[1] = Coordinate::absoluteCoordinate($range[1]);
  100. $range = implode(':', $range);
  101. $this->objWriter->writeRawData('\'' . str_replace("'", "''", $worksheet->getTitle() ?? '') . '\'!' . $range);
  102. $this->objWriter->endElement();
  103. }
  104. }
  105. /**
  106. * Write Defined Name for PrintTitles.
  107. */
  108. private function writeNamedRangeForPrintTitles(Worksheet $worksheet, int $worksheetId = 0): void
  109. {
  110. // NamedRange for PrintTitles
  111. if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $worksheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
  112. $this->objWriter->startElement('definedName');
  113. $this->objWriter->writeAttribute('name', '_xlnm.Print_Titles');
  114. $this->objWriter->writeAttribute('localSheetId', "$worksheetId");
  115. // Setting string
  116. $settingString = '';
  117. // Columns to repeat
  118. if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
  119. $repeat = $worksheet->getPageSetup()->getColumnsToRepeatAtLeft();
  120. $settingString .= '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
  121. }
  122. // Rows to repeat
  123. if ($worksheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
  124. if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
  125. $settingString .= ',';
  126. }
  127. $repeat = $worksheet->getPageSetup()->getRowsToRepeatAtTop();
  128. $settingString .= '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
  129. }
  130. $this->objWriter->writeRawData($settingString);
  131. $this->objWriter->endElement();
  132. }
  133. }
  134. /**
  135. * Write Defined Name for PrintTitles.
  136. */
  137. private function writeNamedRangeForPrintArea(Worksheet $worksheet, int $worksheetId = 0): void
  138. {
  139. // NamedRange for PrintArea
  140. if ($worksheet->getPageSetup()->isPrintAreaSet()) {
  141. $this->objWriter->startElement('definedName');
  142. $this->objWriter->writeAttribute('name', '_xlnm.Print_Area');
  143. $this->objWriter->writeAttribute('localSheetId', "$worksheetId");
  144. // Print area
  145. $printArea = Coordinate::splitRange($worksheet->getPageSetup()->getPrintArea());
  146. $chunks = [];
  147. foreach ($printArea as $printAreaRect) {
  148. $printAreaRect[0] = Coordinate::absoluteReference($printAreaRect[0]);
  149. $printAreaRect[1] = Coordinate::absoluteReference($printAreaRect[1]);
  150. $chunks[] = '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!' . implode(':', $printAreaRect);
  151. }
  152. $this->objWriter->writeRawData(implode(',', $chunks));
  153. $this->objWriter->endElement();
  154. }
  155. }
  156. private function getDefinedRange(DefinedName $definedName): string
  157. {
  158. $definedRange = $definedName->getValue();
  159. $splitCount = preg_match_all(
  160. '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
  161. $definedRange,
  162. $splitRanges,
  163. PREG_OFFSET_CAPTURE
  164. );
  165. $lengths = array_map('strlen', array_column($splitRanges[0], 0));
  166. $offsets = array_column($splitRanges[0], 1);
  167. $worksheets = $splitRanges[2];
  168. $columns = $splitRanges[6];
  169. $rows = $splitRanges[7];
  170. while ($splitCount > 0) {
  171. --$splitCount;
  172. $length = $lengths[$splitCount];
  173. $offset = $offsets[$splitCount];
  174. $worksheet = $worksheets[$splitCount][0];
  175. $column = $columns[$splitCount][0];
  176. $row = $rows[$splitCount][0];
  177. $newRange = '';
  178. if (empty($worksheet)) {
  179. if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) {
  180. // We should have a worksheet
  181. $ws = $definedName->getWorksheet();
  182. $worksheet = ($ws === null) ? null : $ws->getTitle();
  183. }
  184. } else {
  185. $worksheet = str_replace("''", "'", trim($worksheet, "'"));
  186. }
  187. if (!empty($worksheet)) {
  188. $newRange = "'" . str_replace("'", "''", $worksheet) . "'!";
  189. }
  190. $newRange = "{$newRange}{$column}{$row}";
  191. $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length);
  192. }
  193. if (substr($definedRange, 0, 1) === '=') {
  194. $definedRange = substr($definedRange, 1);
  195. }
  196. return $definedRange;
  197. }
  198. }