IOFactory.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet;
  3. use PhpOffice\PhpSpreadsheet\Reader\IReader;
  4. use PhpOffice\PhpSpreadsheet\Shared\File;
  5. use PhpOffice\PhpSpreadsheet\Writer\IWriter;
  6. /**
  7. * Factory to create readers and writers easily.
  8. *
  9. * It is not required to use this class, but it should make it easier to read and write files.
  10. * Especially for reading files with an unknown format.
  11. */
  12. abstract class IOFactory
  13. {
  14. public const READER_XLSX = 'Xlsx';
  15. public const READER_XLS = 'Xls';
  16. public const READER_XML = 'Xml';
  17. public const READER_ODS = 'Ods';
  18. public const READER_SYLK = 'Slk';
  19. public const READER_SLK = 'Slk';
  20. public const READER_GNUMERIC = 'Gnumeric';
  21. public const READER_HTML = 'Html';
  22. public const READER_CSV = 'Csv';
  23. public const WRITER_XLSX = 'Xlsx';
  24. public const WRITER_XLS = 'Xls';
  25. public const WRITER_ODS = 'Ods';
  26. public const WRITER_CSV = 'Csv';
  27. public const WRITER_HTML = 'Html';
  28. /** @var string[] */
  29. private static $readers = [
  30. self::READER_XLSX => Reader\Xlsx::class,
  31. self::READER_XLS => Reader\Xls::class,
  32. self::READER_XML => Reader\Xml::class,
  33. self::READER_ODS => Reader\Ods::class,
  34. self::READER_SLK => Reader\Slk::class,
  35. self::READER_GNUMERIC => Reader\Gnumeric::class,
  36. self::READER_HTML => Reader\Html::class,
  37. self::READER_CSV => Reader\Csv::class,
  38. ];
  39. /** @var string[] */
  40. private static $writers = [
  41. self::WRITER_XLS => Writer\Xls::class,
  42. self::WRITER_XLSX => Writer\Xlsx::class,
  43. self::WRITER_ODS => Writer\Ods::class,
  44. self::WRITER_CSV => Writer\Csv::class,
  45. self::WRITER_HTML => Writer\Html::class,
  46. 'Tcpdf' => Writer\Pdf\Tcpdf::class,
  47. 'Dompdf' => Writer\Pdf\Dompdf::class,
  48. 'Mpdf' => Writer\Pdf\Mpdf::class,
  49. ];
  50. /**
  51. * Create Writer\IWriter.
  52. */
  53. public static function createWriter(Spreadsheet $spreadsheet, string $writerType): IWriter
  54. {
  55. if (!isset(self::$writers[$writerType])) {
  56. throw new Writer\Exception("No writer found for type $writerType");
  57. }
  58. // Instantiate writer
  59. /** @var IWriter */
  60. $className = self::$writers[$writerType];
  61. return new $className($spreadsheet);
  62. }
  63. /**
  64. * Create IReader.
  65. */
  66. public static function createReader(string $readerType): IReader
  67. {
  68. if (!isset(self::$readers[$readerType])) {
  69. throw new Reader\Exception("No reader found for type $readerType");
  70. }
  71. // Instantiate reader
  72. /** @var IReader */
  73. $className = self::$readers[$readerType];
  74. return new $className();
  75. }
  76. /**
  77. * Loads Spreadsheet from file using automatic Reader\IReader resolution.
  78. *
  79. * @param string $filename The name of the spreadsheet file
  80. * @param int $flags the optional second parameter flags may be used to identify specific elements
  81. * that should be loaded, but which won't be loaded by default, using these values:
  82. * IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file.
  83. * IReader::READ_DATA_ONLY - Read cell values only, not formatting or merge structure.
  84. * IReader::IGNORE_EMPTY_CELLS - Don't load empty cells into the model.
  85. * @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
  86. * all possible Readers until it finds a match; but this allows you to pass in a
  87. * list of Readers so it will only try the subset that you specify here.
  88. * Values in this list can be any of the constant values defined in the set
  89. * IOFactory::READER_*.
  90. */
  91. public static function load(string $filename, int $flags = 0, ?array $readers = null): Spreadsheet
  92. {
  93. $reader = self::createReaderForFile($filename, $readers);
  94. return $reader->load($filename, $flags);
  95. }
  96. /**
  97. * Identify file type using automatic IReader resolution.
  98. */
  99. public static function identify(string $filename, ?array $readers = null): string
  100. {
  101. $reader = self::createReaderForFile($filename, $readers);
  102. $className = get_class($reader);
  103. $classType = explode('\\', $className);
  104. unset($reader);
  105. return array_pop($classType);
  106. }
  107. /**
  108. * Create Reader\IReader for file using automatic IReader resolution.
  109. *
  110. * @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
  111. * all possible Readers until it finds a match; but this allows you to pass in a
  112. * list of Readers so it will only try the subset that you specify here.
  113. * Values in this list can be any of the constant values defined in the set
  114. * IOFactory::READER_*.
  115. */
  116. public static function createReaderForFile(string $filename, ?array $readers = null): IReader
  117. {
  118. File::assertFile($filename);
  119. $testReaders = self::$readers;
  120. if ($readers !== null) {
  121. $readers = array_map('strtoupper', $readers);
  122. $testReaders = array_filter(
  123. self::$readers,
  124. function (string $readerType) use ($readers) {
  125. return in_array(strtoupper($readerType), $readers, true);
  126. },
  127. ARRAY_FILTER_USE_KEY
  128. );
  129. }
  130. // First, lucky guess by inspecting file extension
  131. $guessedReader = self::getReaderTypeFromExtension($filename);
  132. if (($guessedReader !== null) && array_key_exists($guessedReader, $testReaders)) {
  133. $reader = self::createReader($guessedReader);
  134. // Let's see if we are lucky
  135. if ($reader->canRead($filename)) {
  136. return $reader;
  137. }
  138. }
  139. // If we reach here then "lucky guess" didn't give any result
  140. // Try walking through all the options in self::$readers (or the selected subset)
  141. foreach ($testReaders as $readerType => $class) {
  142. // Ignore our original guess, we know that won't work
  143. if ($readerType !== $guessedReader) {
  144. $reader = self::createReader($readerType);
  145. if ($reader->canRead($filename)) {
  146. return $reader;
  147. }
  148. }
  149. }
  150. throw new Reader\Exception('Unable to identify a reader for this file');
  151. }
  152. /**
  153. * Guess a reader type from the file extension, if any.
  154. */
  155. private static function getReaderTypeFromExtension(string $filename): ?string
  156. {
  157. $pathinfo = pathinfo($filename);
  158. if (!isset($pathinfo['extension'])) {
  159. return null;
  160. }
  161. switch (strtolower($pathinfo['extension'])) {
  162. case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet
  163. case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded)
  164. case 'xltx': // Excel (OfficeOpenXML) Template
  165. case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded)
  166. return 'Xlsx';
  167. case 'xls': // Excel (BIFF) Spreadsheet
  168. case 'xlt': // Excel (BIFF) Template
  169. return 'Xls';
  170. case 'ods': // Open/Libre Offic Calc
  171. case 'ots': // Open/Libre Offic Calc Template
  172. return 'Ods';
  173. case 'slk':
  174. return 'Slk';
  175. case 'xml': // Excel 2003 SpreadSheetML
  176. return 'Xml';
  177. case 'gnumeric':
  178. return 'Gnumeric';
  179. case 'htm':
  180. case 'html':
  181. return 'Html';
  182. case 'csv':
  183. // Do nothing
  184. // We must not try to use CSV reader since it loads
  185. // all files including Excel files etc.
  186. return null;
  187. default:
  188. return null;
  189. }
  190. }
  191. /**
  192. * Register a writer with its type and class name.
  193. */
  194. public static function registerWriter(string $writerType, string $writerClass): void
  195. {
  196. if (!is_a($writerClass, IWriter::class, true)) {
  197. throw new Writer\Exception('Registered writers must implement ' . IWriter::class);
  198. }
  199. self::$writers[$writerType] = $writerClass;
  200. }
  201. /**
  202. * Register a reader with its type and class name.
  203. */
  204. public static function registerReader(string $readerType, string $readerClass): void
  205. {
  206. if (!is_a($readerClass, IReader::class, true)) {
  207. throw new Reader\Exception('Registered readers must implement ' . IReader::class);
  208. }
  209. self::$readers[$readerType] = $readerClass;
  210. }
  211. }