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