IOFactory.php 8.5 KB

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