|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* PHPExcel |
|
4
|
|
|
* |
|
5
|
|
|
* Copyright (c) 2006 - 2012 PHPExcel |
|
6
|
|
|
* |
|
7
|
|
|
* This library is free software; you can redistribute it and/or |
|
8
|
|
|
* modify it under the terms of the GNU Lesser General Public |
|
9
|
|
|
* License as published by the Free Software Foundation; either |
|
10
|
|
|
* version 2.1 of the License, or (at your option) any later version. |
|
11
|
|
|
* |
|
12
|
|
|
* This library is distributed in the hope that it will be useful, |
|
13
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
15
|
|
|
* Lesser General Public License for more details. |
|
16
|
|
|
* |
|
17
|
|
|
* You should have received a copy of the GNU Lesser General Public |
|
18
|
|
|
* License along with this library; if not, write to the Free Software |
|
19
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
20
|
|
|
* |
|
21
|
|
|
* @category PHPExcel |
|
22
|
|
|
* @package PHPExcel_Reader_Excel5 |
|
23
|
|
|
* @copyright Copyright (c) 2006 - 2012 PHPExcel (http://www.codeplex.com/PHPExcel) |
|
24
|
|
|
* @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL |
|
25
|
|
|
* @version 1.7.7, 2012-05-19 |
|
26
|
|
|
*/ |
|
27
|
|
|
|
|
28
|
|
|
// Original file header of ParseXL (used as the base for this class): |
|
29
|
|
|
// -------------------------------------------------------------------------------- |
|
30
|
|
|
// Adapted from Excel_Spreadsheet_Reader developed by users bizon153, |
|
31
|
|
|
// trex005, and mmp11 (SourceForge.net) |
|
32
|
|
|
// http://sourceforge.net/projects/phpexcelreader/ |
|
33
|
|
|
// Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ... |
|
34
|
|
|
// Modelled moreso after Perl Excel Parse/Write modules |
|
35
|
|
|
// Added Parse_Excel_Spreadsheet object |
|
36
|
|
|
// Reads a whole worksheet or tab as row,column array or as |
|
37
|
|
|
// associated hash of indexed rows and named column fields |
|
38
|
|
|
// Added variables for worksheet (tab) indexes and names |
|
39
|
|
|
// Added an object call for loading individual woorksheets |
|
40
|
|
|
// Changed default indexing defaults to 0 based arrays |
|
41
|
|
|
// Fixed date/time and percent formats |
|
42
|
|
|
// Includes patches found at SourceForge... |
|
43
|
|
|
// unicode patch by nobody |
|
44
|
|
|
// unpack("d") machine depedency patch by matchy |
|
45
|
|
|
// boundsheet utf16 patch by bjaenichen |
|
46
|
|
|
// Renamed functions for shorter names |
|
47
|
|
|
// General code cleanup and rigor, including <80 column width |
|
48
|
|
|
// Included a testcase Excel file and PHP example calls |
|
49
|
|
|
// Code works for PHP 5.x |
|
50
|
|
|
|
|
51
|
|
|
// Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ... |
|
52
|
|
|
// http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334 |
|
53
|
|
|
// Decoding of formula conditions, results, and tokens. |
|
54
|
|
|
// Support for user-defined named cells added as an array "namedcells" |
|
55
|
|
|
// Patch code for user-defined named cells supports single cells only. |
|
56
|
|
|
// NOTE: this patch only works for BIFF8 as BIFF5-7 use a different |
|
57
|
|
|
// external sheet reference structure |
|
58
|
|
|
|
|
59
|
|
|
|
|
60
|
|
|
/** PHPExcel root directory */ |
|
61
|
|
View Code Duplication |
if (!defined('PHPEXCEL_ROOT')) { |
|
62
|
|
|
/** |
|
63
|
|
|
* @ignore |
|
64
|
|
|
*/ |
|
65
|
|
|
define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../../'); |
|
66
|
|
|
require(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php'); |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* PHPExcel_Reader_Excel5 |
|
71
|
|
|
* |
|
72
|
|
|
* This class uses {@link http://sourceforge.net/projects/phpexcelreader/parseXL} |
|
73
|
|
|
* |
|
74
|
|
|
* @category PHPExcel |
|
75
|
|
|
* @package PHPExcel_Reader_Excel5 |
|
76
|
|
|
* @copyright Copyright (c) 2006 - 2012 PHPExcel (http://www.codeplex.com/PHPExcel) |
|
77
|
|
|
*/ |
|
78
|
|
|
class PHPExcel_Reader_Excel5 implements PHPExcel_Reader_IReader |
|
79
|
|
|
{ |
|
80
|
|
|
// ParseXL definitions |
|
81
|
|
|
const XLS_BIFF8 = 0x0600; |
|
82
|
|
|
const XLS_BIFF7 = 0x0500; |
|
83
|
|
|
const XLS_WorkbookGlobals = 0x0005; |
|
84
|
|
|
const XLS_Worksheet = 0x0010; |
|
85
|
|
|
|
|
86
|
|
|
// record identifiers |
|
87
|
|
|
const XLS_Type_FORMULA = 0x0006; |
|
88
|
|
|
const XLS_Type_EOF = 0x000a; |
|
89
|
|
|
const XLS_Type_PROTECT = 0x0012; |
|
90
|
|
|
const XLS_Type_OBJECTPROTECT = 0x0063; |
|
91
|
|
|
const XLS_Type_SCENPROTECT = 0x00dd; |
|
92
|
|
|
const XLS_Type_PASSWORD = 0x0013; |
|
93
|
|
|
const XLS_Type_HEADER = 0x0014; |
|
94
|
|
|
const XLS_Type_FOOTER = 0x0015; |
|
95
|
|
|
const XLS_Type_EXTERNSHEET = 0x0017; |
|
96
|
|
|
const XLS_Type_DEFINEDNAME = 0x0018; |
|
97
|
|
|
const XLS_Type_VERTICALPAGEBREAKS = 0x001a; |
|
98
|
|
|
const XLS_Type_HORIZONTALPAGEBREAKS = 0x001b; |
|
99
|
|
|
const XLS_Type_NOTE = 0x001c; |
|
100
|
|
|
const XLS_Type_SELECTION = 0x001d; |
|
101
|
|
|
const XLS_Type_DATEMODE = 0x0022; |
|
102
|
|
|
const XLS_Type_EXTERNNAME = 0x0023; |
|
103
|
|
|
const XLS_Type_LEFTMARGIN = 0x0026; |
|
104
|
|
|
const XLS_Type_RIGHTMARGIN = 0x0027; |
|
105
|
|
|
const XLS_Type_TOPMARGIN = 0x0028; |
|
106
|
|
|
const XLS_Type_BOTTOMMARGIN = 0x0029; |
|
107
|
|
|
const XLS_Type_PRINTGRIDLINES = 0x002b; |
|
108
|
|
|
const XLS_Type_FILEPASS = 0x002f; |
|
109
|
|
|
const XLS_Type_FONT = 0x0031; |
|
110
|
|
|
const XLS_Type_CONTINUE = 0x003c; |
|
111
|
|
|
const XLS_Type_PANE = 0x0041; |
|
112
|
|
|
const XLS_Type_CODEPAGE = 0x0042; |
|
113
|
|
|
const XLS_Type_DEFCOLWIDTH = 0x0055; |
|
114
|
|
|
const XLS_Type_OBJ = 0x005d; |
|
115
|
|
|
const XLS_Type_COLINFO = 0x007d; |
|
116
|
|
|
const XLS_Type_IMDATA = 0x007f; |
|
117
|
|
|
const XLS_Type_SHEETPR = 0x0081; |
|
118
|
|
|
const XLS_Type_HCENTER = 0x0083; |
|
119
|
|
|
const XLS_Type_VCENTER = 0x0084; |
|
120
|
|
|
const XLS_Type_SHEET = 0x0085; |
|
121
|
|
|
const XLS_Type_PALETTE = 0x0092; |
|
122
|
|
|
const XLS_Type_SCL = 0x00a0; |
|
123
|
|
|
const XLS_Type_PAGESETUP = 0x00a1; |
|
124
|
|
|
const XLS_Type_MULRK = 0x00bd; |
|
125
|
|
|
const XLS_Type_MULBLANK = 0x00be; |
|
126
|
|
|
const XLS_Type_DBCELL = 0x00d7; |
|
127
|
|
|
const XLS_Type_XF = 0x00e0; |
|
128
|
|
|
const XLS_Type_MERGEDCELLS = 0x00e5; |
|
129
|
|
|
const XLS_Type_MSODRAWINGGROUP = 0x00eb; |
|
130
|
|
|
const XLS_Type_MSODRAWING = 0x00ec; |
|
131
|
|
|
const XLS_Type_SST = 0x00fc; |
|
132
|
|
|
const XLS_Type_LABELSST = 0x00fd; |
|
133
|
|
|
const XLS_Type_EXTSST = 0x00ff; |
|
134
|
|
|
const XLS_Type_EXTERNALBOOK = 0x01ae; |
|
135
|
|
|
const XLS_Type_DATAVALIDATIONS = 0x01b2; |
|
136
|
|
|
const XLS_Type_TXO = 0x01b6; |
|
137
|
|
|
const XLS_Type_HYPERLINK = 0x01b8; |
|
138
|
|
|
const XLS_Type_DATAVALIDATION = 0x01be; |
|
139
|
|
|
const XLS_Type_DIMENSION = 0x0200; |
|
140
|
|
|
const XLS_Type_BLANK = 0x0201; |
|
141
|
|
|
const XLS_Type_NUMBER = 0x0203; |
|
142
|
|
|
const XLS_Type_LABEL = 0x0204; |
|
143
|
|
|
const XLS_Type_BOOLERR = 0x0205; |
|
144
|
|
|
const XLS_Type_STRING = 0x0207; |
|
145
|
|
|
const XLS_Type_ROW = 0x0208; |
|
146
|
|
|
const XLS_Type_INDEX = 0x020b; |
|
147
|
|
|
const XLS_Type_ARRAY = 0x0221; |
|
148
|
|
|
const XLS_Type_DEFAULTROWHEIGHT = 0x0225; |
|
149
|
|
|
const XLS_Type_WINDOW2 = 0x023e; |
|
150
|
|
|
const XLS_Type_RK = 0x027e; |
|
151
|
|
|
const XLS_Type_STYLE = 0x0293; |
|
152
|
|
|
const XLS_Type_FORMAT = 0x041e; |
|
153
|
|
|
const XLS_Type_SHAREDFMLA = 0x04bc; |
|
154
|
|
|
const XLS_Type_BOF = 0x0809; |
|
155
|
|
|
const XLS_Type_SHEETPROTECTION = 0x0867; |
|
156
|
|
|
const XLS_Type_RANGEPROTECTION = 0x0868; |
|
157
|
|
|
const XLS_Type_SHEETLAYOUT = 0x0862; |
|
158
|
|
|
const XLS_Type_XFEXT = 0x087d; |
|
159
|
|
|
const XLS_Type_UNKNOWN = 0xffff; |
|
160
|
|
|
|
|
161
|
|
|
|
|
162
|
|
|
/** |
|
163
|
|
|
* Read data only? |
|
164
|
|
|
* Identifies whether the Reader should only read data values for cells, and ignore any formatting information; |
|
165
|
|
|
* or whether it should read both data and formatting |
|
166
|
|
|
* |
|
167
|
|
|
* @var boolean |
|
168
|
|
|
*/ |
|
169
|
|
|
private $_readDataOnly = false; |
|
170
|
|
|
|
|
171
|
|
|
/** |
|
172
|
|
|
* Restrict which sheets should be loaded? |
|
173
|
|
|
* This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded. |
|
174
|
|
|
* |
|
175
|
|
|
* @var array of string |
|
176
|
|
|
*/ |
|
177
|
|
|
private $_loadSheetsOnly = null; |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* PHPExcel_Reader_IReadFilter instance |
|
181
|
|
|
* |
|
182
|
|
|
* @var PHPExcel_Reader_IReadFilter |
|
183
|
|
|
*/ |
|
184
|
|
|
private $_readFilter = null; |
|
185
|
|
|
|
|
186
|
|
|
/** |
|
187
|
|
|
* Summary Information stream data. |
|
188
|
|
|
* |
|
189
|
|
|
* @var string |
|
190
|
|
|
*/ |
|
191
|
|
|
private $_summaryInformation; |
|
192
|
|
|
|
|
193
|
|
|
/** |
|
194
|
|
|
* Extended Summary Information stream data. |
|
195
|
|
|
* |
|
196
|
|
|
* @var string |
|
197
|
|
|
*/ |
|
198
|
|
|
private $_documentSummaryInformation; |
|
199
|
|
|
|
|
200
|
|
|
/** |
|
201
|
|
|
* User-Defined Properties stream data. |
|
202
|
|
|
* |
|
203
|
|
|
* @var string |
|
204
|
|
|
*/ |
|
205
|
|
|
private $_userDefinedProperties; |
|
|
|
|
|
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* Workbook stream data. (Includes workbook globals substream as well as sheet substreams) |
|
209
|
|
|
* |
|
210
|
|
|
* @var string |
|
211
|
|
|
*/ |
|
212
|
|
|
private $_data; |
|
213
|
|
|
|
|
214
|
|
|
/** |
|
215
|
|
|
* Size in bytes of $this->_data |
|
216
|
|
|
* |
|
217
|
|
|
* @var int |
|
218
|
|
|
*/ |
|
219
|
|
|
private $_dataSize; |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* Current position in stream |
|
223
|
|
|
* |
|
224
|
|
|
* @var integer |
|
225
|
|
|
*/ |
|
226
|
|
|
private $_pos; |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* Workbook to be returned by the reader. |
|
230
|
|
|
* |
|
231
|
|
|
* @var PHPExcel |
|
232
|
|
|
*/ |
|
233
|
|
|
private $_phpExcel; |
|
234
|
|
|
|
|
235
|
|
|
/** |
|
236
|
|
|
* Worksheet that is currently being built by the reader. |
|
237
|
|
|
* |
|
238
|
|
|
* @var PHPExcel_Worksheet |
|
239
|
|
|
*/ |
|
240
|
|
|
private $_phpSheet; |
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* BIFF version |
|
244
|
|
|
* |
|
245
|
|
|
* @var int |
|
246
|
|
|
*/ |
|
247
|
|
|
private $_version; |
|
248
|
|
|
|
|
249
|
|
|
/** |
|
250
|
|
|
* Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95) |
|
251
|
|
|
* For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE' |
|
252
|
|
|
* |
|
253
|
|
|
* @var string |
|
254
|
|
|
*/ |
|
255
|
|
|
private $_codepage; |
|
256
|
|
|
|
|
257
|
|
|
/** |
|
258
|
|
|
* Shared formats |
|
259
|
|
|
* |
|
260
|
|
|
* @var array |
|
261
|
|
|
*/ |
|
262
|
|
|
private $_formats; |
|
263
|
|
|
|
|
264
|
|
|
/** |
|
265
|
|
|
* Shared fonts |
|
266
|
|
|
* |
|
267
|
|
|
* @var array |
|
268
|
|
|
*/ |
|
269
|
|
|
private $_objFonts; |
|
270
|
|
|
|
|
271
|
|
|
/** |
|
272
|
|
|
* Color palette |
|
273
|
|
|
* |
|
274
|
|
|
* @var array |
|
275
|
|
|
*/ |
|
276
|
|
|
private $_palette; |
|
277
|
|
|
|
|
278
|
|
|
/** |
|
279
|
|
|
* Worksheets |
|
280
|
|
|
* |
|
281
|
|
|
* @var array |
|
282
|
|
|
*/ |
|
283
|
|
|
private $_sheets; |
|
284
|
|
|
|
|
285
|
|
|
/** |
|
286
|
|
|
* External books |
|
287
|
|
|
* |
|
288
|
|
|
* @var array |
|
289
|
|
|
*/ |
|
290
|
|
|
private $_externalBooks; |
|
291
|
|
|
|
|
292
|
|
|
/** |
|
293
|
|
|
* REF structures. Only applies to BIFF8. |
|
294
|
|
|
* |
|
295
|
|
|
* @var array |
|
296
|
|
|
*/ |
|
297
|
|
|
private $_ref; |
|
298
|
|
|
|
|
299
|
|
|
/** |
|
300
|
|
|
* External names |
|
301
|
|
|
* |
|
302
|
|
|
* @var array |
|
303
|
|
|
*/ |
|
304
|
|
|
private $_externalNames; |
|
305
|
|
|
|
|
306
|
|
|
/** |
|
307
|
|
|
* Defined names |
|
308
|
|
|
* |
|
309
|
|
|
* @var array |
|
310
|
|
|
*/ |
|
311
|
|
|
private $_definedname; |
|
312
|
|
|
|
|
313
|
|
|
/** |
|
314
|
|
|
* Shared strings. Only applies to BIFF8. |
|
315
|
|
|
* |
|
316
|
|
|
* @var array |
|
317
|
|
|
*/ |
|
318
|
|
|
private $_sst; |
|
319
|
|
|
|
|
320
|
|
|
/** |
|
321
|
|
|
* Panes are frozen? (in sheet currently being read). See WINDOW2 record. |
|
322
|
|
|
* |
|
323
|
|
|
* @var boolean |
|
324
|
|
|
*/ |
|
325
|
|
|
private $_frozen; |
|
326
|
|
|
|
|
327
|
|
|
/** |
|
328
|
|
|
* Fit printout to number of pages? (in sheet currently being read). See SHEETPR record. |
|
329
|
|
|
* |
|
330
|
|
|
* @var boolean |
|
331
|
|
|
*/ |
|
332
|
|
|
private $_isFitToPages; |
|
333
|
|
|
|
|
334
|
|
|
/** |
|
335
|
|
|
* Objects. One OBJ record contributes with one entry. |
|
336
|
|
|
* |
|
337
|
|
|
* @var array |
|
338
|
|
|
*/ |
|
339
|
|
|
private $_objs; |
|
340
|
|
|
|
|
341
|
|
|
/** |
|
342
|
|
|
* Text Objects. One TXO record corresponds with one entry. |
|
343
|
|
|
* |
|
344
|
|
|
* @var array |
|
345
|
|
|
*/ |
|
346
|
|
|
private $_textObjects; |
|
347
|
|
|
|
|
348
|
|
|
/** |
|
349
|
|
|
* Cell Annotations (BIFF8) |
|
350
|
|
|
* |
|
351
|
|
|
* @var array |
|
352
|
|
|
*/ |
|
353
|
|
|
private $_cellNotes; |
|
354
|
|
|
|
|
355
|
|
|
/** |
|
356
|
|
|
* The combined MSODRAWINGGROUP data |
|
357
|
|
|
* |
|
358
|
|
|
* @var string |
|
359
|
|
|
*/ |
|
360
|
|
|
private $_drawingGroupData; |
|
361
|
|
|
|
|
362
|
|
|
/** |
|
363
|
|
|
* The combined MSODRAWING data (per sheet) |
|
364
|
|
|
* |
|
365
|
|
|
* @var string |
|
366
|
|
|
*/ |
|
367
|
|
|
private $_drawingData; |
|
368
|
|
|
|
|
369
|
|
|
/** |
|
370
|
|
|
* Keep track of XF index |
|
371
|
|
|
* |
|
372
|
|
|
* @var int |
|
373
|
|
|
*/ |
|
374
|
|
|
private $_xfIndex; |
|
375
|
|
|
|
|
376
|
|
|
/** |
|
377
|
|
|
* Mapping of XF index (that is a cell XF) to final index in cellXf collection |
|
378
|
|
|
* |
|
379
|
|
|
* @var array |
|
380
|
|
|
*/ |
|
381
|
|
|
private $_mapCellXfIndex; |
|
382
|
|
|
|
|
383
|
|
|
/** |
|
384
|
|
|
* Mapping of XF index (that is a style XF) to final index in cellStyleXf collection |
|
385
|
|
|
* |
|
386
|
|
|
* @var array |
|
387
|
|
|
*/ |
|
388
|
|
|
private $_mapCellStyleXfIndex; |
|
389
|
|
|
|
|
390
|
|
|
/** |
|
391
|
|
|
* The shared formulas in a sheet. One SHAREDFMLA record contributes with one value. |
|
392
|
|
|
* |
|
393
|
|
|
* @var array |
|
394
|
|
|
*/ |
|
395
|
|
|
private $_sharedFormulas; |
|
396
|
|
|
|
|
397
|
|
|
/** |
|
398
|
|
|
* The shared formula parts in a sheet. One FORMULA record contributes with one value if it |
|
399
|
|
|
* refers to a shared formula. |
|
400
|
|
|
* |
|
401
|
|
|
* @var array |
|
402
|
|
|
*/ |
|
403
|
|
|
private $_sharedFormulaParts; |
|
404
|
|
|
|
|
405
|
|
|
|
|
406
|
|
|
/** |
|
407
|
|
|
* Create a new PHPExcel_Reader_Excel5 instance |
|
408
|
|
|
*/ |
|
409
|
|
|
public function __construct() { |
|
410
|
|
|
$this->_readFilter = new PHPExcel_Reader_DefaultReadFilter(); |
|
411
|
|
|
} |
|
412
|
|
|
|
|
413
|
|
|
|
|
414
|
|
|
/** |
|
415
|
|
|
* Read data only? |
|
416
|
|
|
* If this is true, then the Reader will only read data values for cells, it will not read any formatting information. |
|
417
|
|
|
* If false (the default) it will read data and formatting. |
|
418
|
|
|
* |
|
419
|
|
|
* @return boolean |
|
420
|
|
|
*/ |
|
421
|
|
|
public function getReadDataOnly() |
|
422
|
|
|
{ |
|
423
|
|
|
return $this->_readDataOnly; |
|
424
|
|
|
} |
|
425
|
|
|
|
|
426
|
|
|
|
|
427
|
|
|
/** |
|
428
|
|
|
* Set read data only |
|
429
|
|
|
* Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting information. |
|
430
|
|
|
* Set to false (the default) to advise the Reader to read both data and formatting for cells. |
|
431
|
|
|
* |
|
432
|
|
|
* @param boolean $pValue |
|
433
|
|
|
* |
|
434
|
|
|
* @return PHPExcel_Reader_Excel5 |
|
435
|
|
|
*/ |
|
436
|
|
|
public function setReadDataOnly($pValue = false) |
|
437
|
|
|
{ |
|
438
|
|
|
$this->_readDataOnly = $pValue; |
|
439
|
|
|
return $this; |
|
440
|
|
|
} |
|
441
|
|
|
|
|
442
|
|
|
|
|
443
|
|
|
/** |
|
444
|
|
|
* Get which sheets to load |
|
445
|
|
|
* Returns either an array of worksheet names (the list of worksheets that should be loaded), or a null |
|
446
|
|
|
* indicating that all worksheets in the workbook should be loaded. |
|
447
|
|
|
* |
|
448
|
|
|
* @return mixed |
|
449
|
|
|
*/ |
|
450
|
|
|
public function getLoadSheetsOnly() |
|
451
|
|
|
{ |
|
452
|
|
|
return $this->_loadSheetsOnly; |
|
453
|
|
|
} |
|
454
|
|
|
|
|
455
|
|
|
|
|
456
|
|
|
/** |
|
457
|
|
|
* Set which sheets to load |
|
458
|
|
|
* |
|
459
|
|
|
* @param mixed $value |
|
460
|
|
|
* This should be either an array of worksheet names to be loaded, or a string containing a single worksheet name. |
|
461
|
|
|
* If NULL, then it tells the Reader to read all worksheets in the workbook |
|
462
|
|
|
* |
|
463
|
|
|
* @return PHPExcel_Reader_Excel5 |
|
464
|
|
|
*/ |
|
465
|
|
|
public function setLoadSheetsOnly($value = null) |
|
466
|
|
|
{ |
|
467
|
|
|
$this->_loadSheetsOnly = is_array($value) ? |
|
468
|
|
|
$value : array($value); |
|
469
|
|
|
return $this; |
|
470
|
|
|
} |
|
471
|
|
|
|
|
472
|
|
|
|
|
473
|
|
|
/** |
|
474
|
|
|
* Set all sheets to load |
|
475
|
|
|
* Tells the Reader to load all worksheets from the workbook. |
|
476
|
|
|
* |
|
477
|
|
|
* @return PHPExcel_Reader_Excel5 |
|
478
|
|
|
*/ |
|
479
|
|
|
public function setLoadAllSheets() |
|
480
|
|
|
{ |
|
481
|
|
|
$this->_loadSheetsOnly = null; |
|
|
|
|
|
|
482
|
|
|
return $this; |
|
483
|
|
|
} |
|
484
|
|
|
|
|
485
|
|
|
|
|
486
|
|
|
/** |
|
487
|
|
|
* Read filter |
|
488
|
|
|
* |
|
489
|
|
|
* @return PHPExcel_Reader_IReadFilter |
|
490
|
|
|
*/ |
|
491
|
|
|
public function getReadFilter() { |
|
492
|
|
|
return $this->_readFilter; |
|
493
|
|
|
} |
|
494
|
|
|
|
|
495
|
|
|
|
|
496
|
|
|
/** |
|
497
|
|
|
* Set read filter |
|
498
|
|
|
* |
|
499
|
|
|
* @param PHPExcel_Reader_IReadFilter $pValue |
|
500
|
|
|
* @return PHPExcel_Reader_Excel5 |
|
501
|
|
|
*/ |
|
502
|
|
|
public function setReadFilter(PHPExcel_Reader_IReadFilter $pValue) { |
|
503
|
|
|
$this->_readFilter = $pValue; |
|
504
|
|
|
return $this; |
|
505
|
|
|
} |
|
506
|
|
|
|
|
507
|
|
|
|
|
508
|
|
|
/** |
|
509
|
|
|
* Can the current PHPExcel_Reader_IReader read the file? |
|
510
|
|
|
* |
|
511
|
|
|
* @param string $pFileName |
|
|
|
|
|
|
512
|
|
|
* @return boolean |
|
513
|
|
|
* @throws Exception |
|
514
|
|
|
*/ |
|
515
|
|
|
public function canRead($pFilename) |
|
516
|
|
|
{ |
|
517
|
|
|
// Check if file exists |
|
518
|
|
|
if (!file_exists($pFilename)) { |
|
519
|
|
|
throw new Exception("Could not open " . $pFilename . " for reading! File does not exist."); |
|
520
|
|
|
} |
|
521
|
|
|
|
|
522
|
|
|
try { |
|
523
|
|
|
// Use ParseXL for the hard work. |
|
524
|
|
|
$ole = new PHPExcel_Shared_OLERead(); |
|
525
|
|
|
|
|
526
|
|
|
// get excel data |
|
527
|
|
|
$res = $ole->read($pFilename); |
|
|
|
|
|
|
528
|
|
|
return true; |
|
529
|
|
|
|
|
530
|
|
|
} catch (Exception $e) { |
|
531
|
|
|
return false; |
|
532
|
|
|
} |
|
533
|
|
|
} |
|
534
|
|
|
|
|
535
|
|
|
|
|
536
|
|
|
/** |
|
537
|
|
|
* Reads names of the worksheets from a file, without parsing the whole file to a PHPExcel object |
|
538
|
|
|
* |
|
539
|
|
|
* @param string $pFilename |
|
540
|
|
|
* @throws Exception |
|
541
|
|
|
*/ |
|
542
|
|
|
public function listWorksheetNames($pFilename) |
|
543
|
|
|
{ |
|
544
|
|
|
// Check if file exists |
|
545
|
|
|
if (!file_exists($pFilename)) { |
|
546
|
|
|
throw new Exception("Could not open " . $pFilename . " for reading! File does not exist."); |
|
547
|
|
|
} |
|
548
|
|
|
|
|
549
|
|
|
$worksheetNames = array(); |
|
550
|
|
|
|
|
551
|
|
|
// Read the OLE file |
|
552
|
|
|
$this->_loadOLE($pFilename); |
|
553
|
|
|
|
|
554
|
|
|
// total byte size of Excel data (workbook global substream + sheet substreams) |
|
555
|
|
|
$this->_dataSize = strlen($this->_data); |
|
556
|
|
|
|
|
557
|
|
|
$this->_pos = 0; |
|
558
|
|
|
$this->_sheets = array(); |
|
559
|
|
|
|
|
560
|
|
|
// Parse Workbook Global Substream |
|
561
|
|
View Code Duplication |
while ($this->_pos < $this->_dataSize) { |
|
562
|
|
|
$code = self::_GetInt2d($this->_data, $this->_pos); |
|
563
|
|
|
|
|
564
|
|
|
switch ($code) { |
|
565
|
|
|
case self::XLS_Type_BOF: $this->_readBof(); break; |
|
566
|
|
|
case self::XLS_Type_SHEET: $this->_readSheet(); break; |
|
567
|
|
|
case self::XLS_Type_EOF: $this->_readDefault(); break 2; |
|
568
|
|
|
default: $this->_readDefault(); break; |
|
|
|
|
|
|
569
|
|
|
} |
|
570
|
|
|
} |
|
571
|
|
|
|
|
572
|
|
|
foreach ($this->_sheets as $sheet) { |
|
573
|
|
|
if ($sheet['sheetType'] != 0x00) { |
|
574
|
|
|
// 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module |
|
575
|
|
|
continue; |
|
576
|
|
|
} |
|
577
|
|
|
|
|
578
|
|
|
$worksheetNames[] = $sheet['name']; |
|
579
|
|
|
} |
|
580
|
|
|
|
|
581
|
|
|
return $worksheetNames; |
|
582
|
|
|
} |
|
583
|
|
|
|
|
584
|
|
|
|
|
585
|
|
|
/** |
|
586
|
|
|
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns) |
|
587
|
|
|
* |
|
588
|
|
|
* @param string $pFilename |
|
589
|
|
|
* @throws Exception |
|
590
|
|
|
*/ |
|
591
|
|
|
public function listWorksheetInfo($pFilename) |
|
592
|
|
|
{ |
|
593
|
|
|
// Check if file exists |
|
594
|
|
|
if (!file_exists($pFilename)) { |
|
595
|
|
|
throw new Exception("Could not open " . $pFilename . " for reading! File does not exist."); |
|
596
|
|
|
} |
|
597
|
|
|
|
|
598
|
|
|
$worksheetInfo = array(); |
|
599
|
|
|
|
|
600
|
|
|
// Read the OLE file |
|
601
|
|
|
$this->_loadOLE($pFilename); |
|
602
|
|
|
|
|
603
|
|
|
// total byte size of Excel data (workbook global substream + sheet substreams) |
|
604
|
|
|
$this->_dataSize = strlen($this->_data); |
|
605
|
|
|
|
|
606
|
|
|
// initialize |
|
607
|
|
|
$this->_pos = 0; |
|
608
|
|
|
$this->_sheets = array(); |
|
609
|
|
|
|
|
610
|
|
|
// Parse Workbook Global Substream |
|
611
|
|
View Code Duplication |
while ($this->_pos < $this->_dataSize) { |
|
612
|
|
|
$code = self::_GetInt2d($this->_data, $this->_pos); |
|
613
|
|
|
|
|
614
|
|
|
switch ($code) { |
|
615
|
|
|
case self::XLS_Type_BOF: $this->_readBof(); break; |
|
616
|
|
|
case self::XLS_Type_SHEET: $this->_readSheet(); break; |
|
617
|
|
|
case self::XLS_Type_EOF: $this->_readDefault(); break 2; |
|
618
|
|
|
default: $this->_readDefault(); break; |
|
|
|
|
|
|
619
|
|
|
} |
|
620
|
|
|
} |
|
621
|
|
|
|
|
622
|
|
|
// Parse the individual sheets |
|
623
|
|
|
foreach ($this->_sheets as $sheet) { |
|
624
|
|
|
|
|
625
|
|
|
if ($sheet['sheetType'] != 0x00) { |
|
626
|
|
|
// 0x00: Worksheet |
|
627
|
|
|
// 0x02: Chart |
|
628
|
|
|
// 0x06: Visual Basic module |
|
629
|
|
|
continue; |
|
630
|
|
|
} |
|
631
|
|
|
|
|
632
|
|
|
$tmpInfo = array(); |
|
633
|
|
|
$tmpInfo['worksheetName'] = $sheet['name']; |
|
634
|
|
|
$tmpInfo['lastColumnLetter'] = 'A'; |
|
635
|
|
|
$tmpInfo['lastColumnIndex'] = 0; |
|
636
|
|
|
$tmpInfo['totalRows'] = 0; |
|
637
|
|
|
$tmpInfo['totalColumns'] = 0; |
|
638
|
|
|
|
|
639
|
|
|
$this->_pos = $sheet['offset']; |
|
640
|
|
|
|
|
641
|
|
|
while ($this->_pos <= $this->_dataSize - 4) { |
|
642
|
|
|
$code = self::_GetInt2d($this->_data, $this->_pos); |
|
643
|
|
|
|
|
644
|
|
|
switch ($code) { |
|
645
|
|
|
case self::XLS_Type_RK: |
|
646
|
|
|
case self::XLS_Type_LABELSST: |
|
647
|
|
|
case self::XLS_Type_NUMBER: |
|
648
|
|
|
case self::XLS_Type_FORMULA: |
|
649
|
|
|
case self::XLS_Type_BOOLERR: |
|
650
|
|
|
case self::XLS_Type_LABEL: |
|
651
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
652
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
653
|
|
|
|
|
654
|
|
|
// move stream pointer to next record |
|
655
|
|
|
$this->_pos += 4 + $length; |
|
656
|
|
|
|
|
657
|
|
|
$rowIndex = self::_GetInt2d($recordData, 0) + 1; |
|
658
|
|
|
$columnIndex = self::_GetInt2d($recordData, 2); |
|
659
|
|
|
|
|
660
|
|
|
$tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex); |
|
661
|
|
|
$tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex); |
|
662
|
|
|
break; |
|
663
|
|
|
case self::XLS_Type_BOF: $this->_readBof(); break; |
|
664
|
|
|
case self::XLS_Type_EOF: $this->_readDefault(); break 2; |
|
665
|
|
|
default: $this->_readDefault(); break; |
|
|
|
|
|
|
666
|
|
|
} |
|
667
|
|
|
} |
|
668
|
|
|
|
|
669
|
|
|
$tmpInfo['lastColumnLetter'] = PHPExcel_Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']); |
|
670
|
|
|
$tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1; |
|
671
|
|
|
|
|
672
|
|
|
$worksheetInfo[] = $tmpInfo; |
|
673
|
|
|
} |
|
674
|
|
|
|
|
675
|
|
|
return $worksheetInfo; |
|
676
|
|
|
} |
|
677
|
|
|
|
|
678
|
|
|
|
|
679
|
|
|
/** |
|
680
|
|
|
* Loads PHPExcel from file |
|
681
|
|
|
* |
|
682
|
|
|
* @param string $pFilename |
|
683
|
|
|
* @return PHPExcel |
|
684
|
|
|
* @throws Exception |
|
685
|
|
|
*/ |
|
686
|
|
|
public function load($pFilename) |
|
687
|
|
|
{ |
|
688
|
|
|
// Read the OLE file |
|
689
|
|
|
$this->_loadOLE($pFilename); |
|
690
|
|
|
|
|
691
|
|
|
// Initialisations |
|
692
|
|
|
$this->_phpExcel = new PHPExcel; |
|
693
|
|
|
$this->_phpExcel->removeSheetByIndex(0); // remove 1st sheet |
|
694
|
|
|
if (!$this->_readDataOnly) { |
|
695
|
|
|
$this->_phpExcel->removeCellStyleXfByIndex(0); // remove the default style |
|
696
|
|
|
$this->_phpExcel->removeCellXfByIndex(0); // remove the default style |
|
697
|
|
|
} |
|
698
|
|
|
|
|
699
|
|
|
// Read the summary information stream (containing meta data) |
|
700
|
|
|
$this->_readSummaryInformation(); |
|
701
|
|
|
|
|
702
|
|
|
// Read the Additional document summary information stream (containing application-specific meta data) |
|
703
|
|
|
$this->_readDocumentSummaryInformation(); |
|
704
|
|
|
|
|
705
|
|
|
// total byte size of Excel data (workbook global substream + sheet substreams) |
|
706
|
|
|
$this->_dataSize = strlen($this->_data); |
|
707
|
|
|
|
|
708
|
|
|
// initialize |
|
709
|
|
|
$this->_pos = 0; |
|
710
|
|
|
$this->_codepage = 'CP1252'; |
|
711
|
|
|
$this->_formats = array(); |
|
712
|
|
|
$this->_objFonts = array(); |
|
713
|
|
|
$this->_palette = array(); |
|
714
|
|
|
$this->_sheets = array(); |
|
715
|
|
|
$this->_externalBooks = array(); |
|
716
|
|
|
$this->_ref = array(); |
|
717
|
|
|
$this->_definedname = array(); |
|
718
|
|
|
$this->_sst = array(); |
|
719
|
|
|
$this->_drawingGroupData = ''; |
|
720
|
|
|
$this->_xfIndex = ''; |
|
|
|
|
|
|
721
|
|
|
$this->_mapCellXfIndex = array(); |
|
722
|
|
|
$this->_mapCellStyleXfIndex = array(); |
|
723
|
|
|
|
|
724
|
|
|
// Parse Workbook Global Substream |
|
725
|
|
|
while ($this->_pos < $this->_dataSize) { |
|
726
|
|
|
$code = self::_GetInt2d($this->_data, $this->_pos); |
|
727
|
|
|
|
|
728
|
|
|
switch ($code) { |
|
729
|
|
|
case self::XLS_Type_BOF: $this->_readBof(); break; |
|
730
|
|
|
case self::XLS_Type_FILEPASS: $this->_readFilepass(); break; |
|
731
|
|
|
case self::XLS_Type_CODEPAGE: $this->_readCodepage(); break; |
|
732
|
|
|
case self::XLS_Type_DATEMODE: $this->_readDateMode(); break; |
|
733
|
|
|
case self::XLS_Type_FONT: $this->_readFont(); break; |
|
734
|
|
|
case self::XLS_Type_FORMAT: $this->_readFormat(); break; |
|
735
|
|
|
case self::XLS_Type_XF: $this->_readXf(); break; |
|
736
|
|
|
case self::XLS_Type_XFEXT: $this->_readXfExt(); break; |
|
737
|
|
|
case self::XLS_Type_STYLE: $this->_readStyle(); break; |
|
738
|
|
|
case self::XLS_Type_PALETTE: $this->_readPalette(); break; |
|
739
|
|
|
case self::XLS_Type_SHEET: $this->_readSheet(); break; |
|
740
|
|
|
case self::XLS_Type_EXTERNALBOOK: $this->_readExternalBook(); break; |
|
741
|
|
|
case self::XLS_Type_EXTERNNAME: $this->_readExternName(); break; |
|
742
|
|
|
case self::XLS_Type_EXTERNSHEET: $this->_readExternSheet(); break; |
|
743
|
|
|
case self::XLS_Type_DEFINEDNAME: $this->_readDefinedName(); break; |
|
744
|
|
|
case self::XLS_Type_MSODRAWINGGROUP: $this->_readMsoDrawingGroup(); break; |
|
745
|
|
|
case self::XLS_Type_SST: $this->_readSst(); break; |
|
746
|
|
|
case self::XLS_Type_EOF: $this->_readDefault(); break 2; |
|
747
|
|
|
default: $this->_readDefault(); break; |
|
|
|
|
|
|
748
|
|
|
} |
|
749
|
|
|
} |
|
750
|
|
|
|
|
751
|
|
|
// Resolve indexed colors for font, fill, and border colors |
|
752
|
|
|
// Cannot be resolved already in XF record, because PALETTE record comes afterwards |
|
753
|
|
|
if (!$this->_readDataOnly) { |
|
754
|
|
|
foreach ($this->_objFonts as $objFont) { |
|
755
|
|
View Code Duplication |
if (isset($objFont->colorIndex)) { |
|
756
|
|
|
$color = self::_readColor($objFont->colorIndex,$this->_palette,$this->_version); |
|
757
|
|
|
$objFont->getColor()->setRGB($color['rgb']); |
|
758
|
|
|
} |
|
759
|
|
|
} |
|
760
|
|
|
|
|
761
|
|
|
foreach ($this->_phpExcel->getCellXfCollection() as $objStyle) { |
|
762
|
|
|
// fill start and end color |
|
763
|
|
|
$fill = $objStyle->getFill(); |
|
764
|
|
|
|
|
765
|
|
|
if (isset($fill->startcolorIndex)) { |
|
766
|
|
|
$startColor = self::_readColor($fill->startcolorIndex,$this->_palette,$this->_version); |
|
|
|
|
|
|
767
|
|
|
$fill->getStartColor()->setRGB($startColor['rgb']); |
|
768
|
|
|
} |
|
769
|
|
|
|
|
770
|
|
|
if (isset($fill->endcolorIndex)) { |
|
771
|
|
|
$endColor = self::_readColor($fill->endcolorIndex,$this->_palette,$this->_version); |
|
|
|
|
|
|
772
|
|
|
$fill->getEndColor()->setRGB($endColor['rgb']); |
|
773
|
|
|
} |
|
774
|
|
|
|
|
775
|
|
|
// border colors |
|
776
|
|
|
$top = $objStyle->getBorders()->getTop(); |
|
777
|
|
|
$right = $objStyle->getBorders()->getRight(); |
|
778
|
|
|
$bottom = $objStyle->getBorders()->getBottom(); |
|
779
|
|
|
$left = $objStyle->getBorders()->getLeft(); |
|
780
|
|
|
$diagonal = $objStyle->getBorders()->getDiagonal(); |
|
781
|
|
|
|
|
782
|
|
View Code Duplication |
if (isset($top->colorIndex)) { |
|
783
|
|
|
$borderTopColor = self::_readColor($top->colorIndex,$this->_palette,$this->_version); |
|
|
|
|
|
|
784
|
|
|
$top->getColor()->setRGB($borderTopColor['rgb']); |
|
785
|
|
|
} |
|
786
|
|
|
|
|
787
|
|
View Code Duplication |
if (isset($right->colorIndex)) { |
|
788
|
|
|
$borderRightColor = self::_readColor($right->colorIndex,$this->_palette,$this->_version); |
|
789
|
|
|
$right->getColor()->setRGB($borderRightColor['rgb']); |
|
790
|
|
|
} |
|
791
|
|
|
|
|
792
|
|
View Code Duplication |
if (isset($bottom->colorIndex)) { |
|
793
|
|
|
$borderBottomColor = self::_readColor($bottom->colorIndex,$this->_palette,$this->_version); |
|
794
|
|
|
$bottom->getColor()->setRGB($borderBottomColor['rgb']); |
|
795
|
|
|
} |
|
796
|
|
|
|
|
797
|
|
View Code Duplication |
if (isset($left->colorIndex)) { |
|
798
|
|
|
$borderLeftColor = self::_readColor($left->colorIndex,$this->_palette,$this->_version); |
|
799
|
|
|
$left->getColor()->setRGB($borderLeftColor['rgb']); |
|
800
|
|
|
} |
|
801
|
|
|
|
|
802
|
|
View Code Duplication |
if (isset($diagonal->colorIndex)) { |
|
803
|
|
|
$borderDiagonalColor = self::_readColor($diagonal->colorIndex,$this->_palette,$this->_version); |
|
804
|
|
|
$diagonal->getColor()->setRGB($borderDiagonalColor['rgb']); |
|
805
|
|
|
} |
|
806
|
|
|
} |
|
807
|
|
|
} |
|
808
|
|
|
|
|
809
|
|
|
// treat MSODRAWINGGROUP records, workbook-level Escher |
|
810
|
|
|
if (!$this->_readDataOnly && $this->_drawingGroupData) { |
|
811
|
|
|
$escherWorkbook = new PHPExcel_Shared_Escher(); |
|
812
|
|
|
$reader = new PHPExcel_Reader_Excel5_Escher($escherWorkbook); |
|
813
|
|
|
$escherWorkbook = $reader->load($this->_drawingGroupData); |
|
814
|
|
|
|
|
815
|
|
|
// debug Escher stream |
|
816
|
|
|
//$debug = new Debug_Escher(new PHPExcel_Shared_Escher()); |
|
817
|
|
|
//$debug->load($this->_drawingGroupData); |
|
818
|
|
|
} |
|
819
|
|
|
|
|
820
|
|
|
// Parse the individual sheets |
|
821
|
|
|
foreach ($this->_sheets as $sheet) { |
|
822
|
|
|
|
|
823
|
|
|
if ($sheet['sheetType'] != 0x00) { |
|
824
|
|
|
// 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module |
|
825
|
|
|
continue; |
|
826
|
|
|
} |
|
827
|
|
|
|
|
828
|
|
|
// check if sheet should be skipped |
|
829
|
|
|
if (isset($this->_loadSheetsOnly) && !in_array($sheet['name'], $this->_loadSheetsOnly)) { |
|
830
|
|
|
continue; |
|
831
|
|
|
} |
|
832
|
|
|
|
|
833
|
|
|
// add sheet to PHPExcel object |
|
834
|
|
|
$this->_phpSheet = $this->_phpExcel->createSheet(); |
|
835
|
|
|
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula |
|
836
|
|
|
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet |
|
837
|
|
|
// name in line with the formula, not the reverse |
|
838
|
|
|
$this->_phpSheet->setTitle($sheet['name'],false); |
|
839
|
|
|
$this->_phpSheet->setSheetState($sheet['sheetState']); |
|
840
|
|
|
|
|
841
|
|
|
$this->_pos = $sheet['offset']; |
|
842
|
|
|
|
|
843
|
|
|
// Initialize isFitToPages. May change after reading SHEETPR record. |
|
844
|
|
|
$this->_isFitToPages = false; |
|
845
|
|
|
|
|
846
|
|
|
// Initialize drawingData |
|
847
|
|
|
$this->_drawingData = ''; |
|
848
|
|
|
|
|
849
|
|
|
// Initialize objs |
|
850
|
|
|
$this->_objs = array(); |
|
851
|
|
|
|
|
852
|
|
|
// Initialize shared formula parts |
|
853
|
|
|
$this->_sharedFormulaParts = array(); |
|
854
|
|
|
|
|
855
|
|
|
// Initialize shared formulas |
|
856
|
|
|
$this->_sharedFormulas = array(); |
|
857
|
|
|
|
|
858
|
|
|
// Initialize text objs |
|
859
|
|
|
$this->_textObjects = array(); |
|
860
|
|
|
|
|
861
|
|
|
// Initialize cell annotations |
|
862
|
|
|
$this->_cellNotes = array(); |
|
863
|
|
|
$this->textObjRef = -1; |
|
864
|
|
|
|
|
865
|
|
|
while ($this->_pos <= $this->_dataSize - 4) { |
|
866
|
|
|
$code = self::_GetInt2d($this->_data, $this->_pos); |
|
867
|
|
|
|
|
868
|
|
|
switch ($code) { |
|
869
|
|
|
case self::XLS_Type_BOF: $this->_readBof(); break; |
|
870
|
|
|
case self::XLS_Type_PRINTGRIDLINES: $this->_readPrintGridlines(); break; |
|
871
|
|
|
case self::XLS_Type_DEFAULTROWHEIGHT: $this->_readDefaultRowHeight(); break; |
|
872
|
|
|
case self::XLS_Type_SHEETPR: $this->_readSheetPr(); break; |
|
873
|
|
|
case self::XLS_Type_HORIZONTALPAGEBREAKS: $this->_readHorizontalPageBreaks(); break; |
|
874
|
|
|
case self::XLS_Type_VERTICALPAGEBREAKS: $this->_readVerticalPageBreaks(); break; |
|
875
|
|
|
case self::XLS_Type_HEADER: $this->_readHeader(); break; |
|
876
|
|
|
case self::XLS_Type_FOOTER: $this->_readFooter(); break; |
|
877
|
|
|
case self::XLS_Type_HCENTER: $this->_readHcenter(); break; |
|
878
|
|
|
case self::XLS_Type_VCENTER: $this->_readVcenter(); break; |
|
879
|
|
|
case self::XLS_Type_LEFTMARGIN: $this->_readLeftMargin(); break; |
|
880
|
|
|
case self::XLS_Type_RIGHTMARGIN: $this->_readRightMargin(); break; |
|
881
|
|
|
case self::XLS_Type_TOPMARGIN: $this->_readTopMargin(); break; |
|
882
|
|
|
case self::XLS_Type_BOTTOMMARGIN: $this->_readBottomMargin(); break; |
|
883
|
|
|
case self::XLS_Type_PAGESETUP: $this->_readPageSetup(); break; |
|
884
|
|
|
case self::XLS_Type_PROTECT: $this->_readProtect(); break; |
|
885
|
|
|
case self::XLS_Type_SCENPROTECT: $this->_readScenProtect(); break; |
|
886
|
|
|
case self::XLS_Type_OBJECTPROTECT: $this->_readObjectProtect(); break; |
|
887
|
|
|
case self::XLS_Type_PASSWORD: $this->_readPassword(); break; |
|
888
|
|
|
case self::XLS_Type_DEFCOLWIDTH: $this->_readDefColWidth(); break; |
|
889
|
|
|
case self::XLS_Type_COLINFO: $this->_readColInfo(); break; |
|
890
|
|
|
case self::XLS_Type_DIMENSION: $this->_readDefault(); break; |
|
891
|
|
|
case self::XLS_Type_ROW: $this->_readRow(); break; |
|
892
|
|
|
case self::XLS_Type_DBCELL: $this->_readDefault(); break; |
|
893
|
|
|
case self::XLS_Type_RK: $this->_readRk(); break; |
|
894
|
|
|
case self::XLS_Type_LABELSST: $this->_readLabelSst(); break; |
|
895
|
|
|
case self::XLS_Type_MULRK: $this->_readMulRk(); break; |
|
896
|
|
|
case self::XLS_Type_NUMBER: $this->_readNumber(); break; |
|
897
|
|
|
case self::XLS_Type_FORMULA: $this->_readFormula(); break; |
|
898
|
|
|
case self::XLS_Type_SHAREDFMLA: $this->_readSharedFmla(); break; |
|
899
|
|
|
case self::XLS_Type_BOOLERR: $this->_readBoolErr(); break; |
|
900
|
|
|
case self::XLS_Type_MULBLANK: $this->_readMulBlank(); break; |
|
901
|
|
|
case self::XLS_Type_LABEL: $this->_readLabel(); break; |
|
902
|
|
|
case self::XLS_Type_BLANK: $this->_readBlank(); break; |
|
903
|
|
|
case self::XLS_Type_MSODRAWING: $this->_readMsoDrawing(); break; |
|
904
|
|
|
case self::XLS_Type_OBJ: $this->_readObj(); break; |
|
905
|
|
|
case self::XLS_Type_WINDOW2: $this->_readWindow2(); break; |
|
906
|
|
|
case self::XLS_Type_SCL: $this->_readScl(); break; |
|
907
|
|
|
case self::XLS_Type_PANE: $this->_readPane(); break; |
|
908
|
|
|
case self::XLS_Type_SELECTION: $this->_readSelection(); break; |
|
909
|
|
|
case self::XLS_Type_MERGEDCELLS: $this->_readMergedCells(); break; |
|
910
|
|
|
case self::XLS_Type_HYPERLINK: $this->_readHyperLink(); break; |
|
911
|
|
|
case self::XLS_Type_DATAVALIDATIONS: $this->_readDataValidations(); break; |
|
912
|
|
|
case self::XLS_Type_DATAVALIDATION: $this->_readDataValidation(); break; |
|
913
|
|
|
case self::XLS_Type_SHEETLAYOUT: $this->_readSheetLayout(); break; |
|
914
|
|
|
case self::XLS_Type_SHEETPROTECTION: $this->_readSheetProtection(); break; |
|
915
|
|
|
case self::XLS_Type_RANGEPROTECTION: $this->_readRangeProtection(); break; |
|
916
|
|
|
case self::XLS_Type_NOTE: $this->_readNote(); break; |
|
917
|
|
|
//case self::XLS_Type_IMDATA: $this->_readImData(); break; |
|
918
|
|
|
case self::XLS_Type_TXO: $this->_readTextObject(); break; |
|
919
|
|
|
case self::XLS_Type_CONTINUE: $this->_readContinue(); break; |
|
920
|
|
|
case self::XLS_Type_EOF: $this->_readDefault(); break 2; |
|
921
|
|
|
default: $this->_readDefault(); break; |
|
|
|
|
|
|
922
|
|
|
} |
|
923
|
|
|
|
|
924
|
|
|
} |
|
925
|
|
|
|
|
926
|
|
|
// treat MSODRAWING records, sheet-level Escher |
|
927
|
|
|
if (!$this->_readDataOnly && $this->_drawingData) { |
|
928
|
|
|
$escherWorksheet = new PHPExcel_Shared_Escher(); |
|
929
|
|
|
$reader = new PHPExcel_Reader_Excel5_Escher($escherWorksheet); |
|
930
|
|
|
$escherWorksheet = $reader->load($this->_drawingData); |
|
931
|
|
|
|
|
932
|
|
|
// debug Escher stream |
|
933
|
|
|
//$debug = new Debug_Escher(new PHPExcel_Shared_Escher()); |
|
934
|
|
|
//$debug->load($this->_drawingData); |
|
935
|
|
|
|
|
936
|
|
|
// get all spContainers in one long array, so they can be mapped to OBJ records |
|
937
|
|
|
$allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers(); |
|
938
|
|
|
} |
|
939
|
|
|
|
|
940
|
|
|
// treat OBJ records |
|
941
|
|
|
foreach ($this->_objs as $n => $obj) { |
|
942
|
|
|
// echo '<hr /><b>Object</b> reference is ',$n,'<br />'; |
|
943
|
|
|
// var_dump($obj); |
|
944
|
|
|
// echo '<br />'; |
|
945
|
|
|
|
|
946
|
|
|
// the first shape container never has a corresponding OBJ record, hence $n + 1 |
|
947
|
|
|
$spContainer = $allSpContainers[$n + 1]; |
|
|
|
|
|
|
948
|
|
|
|
|
949
|
|
|
// we skip all spContainers that are a part of a group shape since we cannot yet handle those |
|
950
|
|
|
if ($spContainer->getNestingLevel() > 1) { |
|
951
|
|
|
continue; |
|
952
|
|
|
} |
|
953
|
|
|
|
|
954
|
|
|
// calculate the width and height of the shape |
|
955
|
|
|
list($startColumn, $startRow) = PHPExcel_Cell::coordinateFromString($spContainer->getStartCoordinates()); |
|
956
|
|
|
list($endColumn, $endRow) = PHPExcel_Cell::coordinateFromString($spContainer->getEndCoordinates()); |
|
957
|
|
|
|
|
958
|
|
|
$startOffsetX = $spContainer->getStartOffsetX(); |
|
959
|
|
|
$startOffsetY = $spContainer->getStartOffsetY(); |
|
960
|
|
|
$endOffsetX = $spContainer->getEndOffsetX(); |
|
961
|
|
|
$endOffsetY = $spContainer->getEndOffsetY(); |
|
962
|
|
|
|
|
963
|
|
|
$width = PHPExcel_Shared_Excel5::getDistanceX($this->_phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX); |
|
964
|
|
|
$height = PHPExcel_Shared_Excel5::getDistanceY($this->_phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY); |
|
965
|
|
|
|
|
966
|
|
|
// calculate offsetX and offsetY of the shape |
|
967
|
|
|
$offsetX = $startOffsetX * PHPExcel_Shared_Excel5::sizeCol($this->_phpSheet, $startColumn) / 1024; |
|
968
|
|
|
$offsetY = $startOffsetY * PHPExcel_Shared_Excel5::sizeRow($this->_phpSheet, $startRow) / 256; |
|
969
|
|
|
|
|
970
|
|
|
switch ($obj['otObjType']) { |
|
971
|
|
|
|
|
972
|
|
|
case 0x19: |
|
973
|
|
|
// Note |
|
974
|
|
|
// echo 'Cell Annotation Object<br />'; |
|
975
|
|
|
// echo 'Object ID is ',$obj['idObjID'],'<br />'; |
|
976
|
|
|
// |
|
977
|
|
|
if (isset($this->_cellNotes[$obj['idObjID']])) { |
|
978
|
|
|
$cellNote = $this->_cellNotes[$obj['idObjID']]; |
|
979
|
|
|
|
|
980
|
|
|
// echo '_cellNotes[',$obj['idObjID'],']: '; |
|
981
|
|
|
// var_dump($cellNote); |
|
982
|
|
|
// echo '<br />'; |
|
983
|
|
|
// |
|
984
|
|
|
if (isset($this->_textObjects[$obj['idObjID']])) { |
|
985
|
|
|
$textObject = $this->_textObjects[$obj['idObjID']]; |
|
986
|
|
|
// echo '_textObject: '; |
|
987
|
|
|
// var_dump($textObject); |
|
988
|
|
|
// echo '<br />'; |
|
989
|
|
|
// |
|
990
|
|
|
$this->_cellNotes[$obj['idObjID']]['objTextData'] = $textObject; |
|
991
|
|
|
$text = $textObject['text']; |
|
992
|
|
|
} |
|
993
|
|
|
// echo $text,'<br />'; |
|
994
|
|
|
} |
|
995
|
|
|
break; |
|
996
|
|
|
|
|
997
|
|
|
case 0x08: |
|
998
|
|
|
// echo 'Picture Object<br />'; |
|
999
|
|
|
// picture |
|
1000
|
|
|
|
|
1001
|
|
|
// get index to BSE entry (1-based) |
|
1002
|
|
|
$BSEindex = $spContainer->getOPT(0x0104); |
|
1003
|
|
|
$BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection(); |
|
|
|
|
|
|
1004
|
|
|
$BSE = $BSECollection[$BSEindex - 1]; |
|
1005
|
|
|
$blipType = $BSE->getBlipType(); |
|
1006
|
|
|
|
|
1007
|
|
|
// need check because some blip types are not supported by Escher reader such as EMF |
|
1008
|
|
|
if ($blip = $BSE->getBlip()) { |
|
1009
|
|
|
$ih = imagecreatefromstring($blip->getData()); |
|
1010
|
|
|
$drawing = new PHPExcel_Worksheet_MemoryDrawing(); |
|
1011
|
|
|
$drawing->setImageResource($ih); |
|
1012
|
|
|
|
|
1013
|
|
|
// width, height, offsetX, offsetY |
|
1014
|
|
|
$drawing->setResizeProportional(false); |
|
1015
|
|
|
$drawing->setWidth($width); |
|
1016
|
|
|
$drawing->setHeight($height); |
|
1017
|
|
|
$drawing->setOffsetX($offsetX); |
|
1018
|
|
|
$drawing->setOffsetY($offsetY); |
|
1019
|
|
|
|
|
1020
|
|
|
switch ($blipType) { |
|
1021
|
|
|
case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG: |
|
1022
|
|
|
$drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_JPEG); |
|
1023
|
|
|
$drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_JPEG); |
|
1024
|
|
|
break; |
|
1025
|
|
|
|
|
1026
|
|
|
case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG: |
|
1027
|
|
|
$drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_PNG); |
|
1028
|
|
|
$drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_PNG); |
|
1029
|
|
|
break; |
|
1030
|
|
|
} |
|
1031
|
|
|
|
|
1032
|
|
|
$drawing->setWorksheet($this->_phpSheet); |
|
1033
|
|
|
$drawing->setCoordinates($spContainer->getStartCoordinates()); |
|
1034
|
|
|
} |
|
1035
|
|
|
|
|
1036
|
|
|
break; |
|
1037
|
|
|
|
|
1038
|
|
|
default: |
|
1039
|
|
|
// other object type |
|
1040
|
|
|
break; |
|
1041
|
|
|
|
|
1042
|
|
|
} |
|
1043
|
|
|
} |
|
1044
|
|
|
|
|
1045
|
|
|
// treat SHAREDFMLA records |
|
1046
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
1047
|
|
|
foreach ($this->_sharedFormulaParts as $cell => $baseCell) { |
|
1048
|
|
|
list($column, $row) = PHPExcel_Cell::coordinateFromString($cell); |
|
1049
|
|
|
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($column, $row, $this->_phpSheet->getTitle()) ) { |
|
1050
|
|
|
$formula = $this->_getFormulaFromStructure($this->_sharedFormulas[$baseCell], $cell); |
|
1051
|
|
|
$this->_phpSheet->getCell($cell)->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA); |
|
1052
|
|
|
} |
|
1053
|
|
|
} |
|
1054
|
|
|
} |
|
1055
|
|
|
|
|
1056
|
|
|
if (!empty($this->_cellNotes)) { |
|
1057
|
|
|
foreach($this->_cellNotes as $note => $noteDetails) { |
|
1058
|
|
|
// echo '<b>Cell annotation ',$note,'</b><br />'; |
|
1059
|
|
|
// var_dump($noteDetails); |
|
1060
|
|
|
// echo '<br />'; |
|
1061
|
|
|
$cellAddress = str_replace('$','',$noteDetails['cellRef']); |
|
1062
|
|
|
$this->_phpSheet->getComment( $cellAddress ) |
|
1063
|
|
|
->setAuthor( $noteDetails['author'] ) |
|
1064
|
|
|
->setText($this->_parseRichText($noteDetails['objTextData']['text']) ); |
|
1065
|
|
|
} |
|
1066
|
|
|
} |
|
1067
|
|
|
} |
|
1068
|
|
|
|
|
1069
|
|
|
// add the named ranges (defined names) |
|
1070
|
|
|
foreach ($this->_definedname as $definedName) { |
|
1071
|
|
|
if ($definedName['isBuiltInName']) { |
|
1072
|
|
|
switch ($definedName['name']) { |
|
1073
|
|
|
|
|
1074
|
|
|
case pack('C', 0x06): |
|
1075
|
|
|
// print area |
|
1076
|
|
|
// in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2 |
|
1077
|
|
|
|
|
1078
|
|
|
$ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma? |
|
1079
|
|
|
|
|
1080
|
|
|
$extractedRanges = array(); |
|
1081
|
|
|
foreach ($ranges as $range) { |
|
1082
|
|
|
// $range should look like one of these |
|
1083
|
|
|
// Foo!$C$7:$J$66 |
|
1084
|
|
|
// Bar!$A$1:$IV$2 |
|
1085
|
|
|
|
|
1086
|
|
|
$explodes = explode('!', $range); // FIXME: what if sheetname contains exclamation mark? |
|
1087
|
|
|
$sheetName = $explodes[0]; |
|
1088
|
|
|
|
|
1089
|
|
|
if (count($explodes) == 2) { |
|
1090
|
|
|
$extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66 |
|
1091
|
|
|
} |
|
1092
|
|
|
} |
|
1093
|
|
|
if ($docSheet = $this->_phpExcel->getSheetByName($sheetName)) { |
|
|
|
|
|
|
1094
|
|
|
$docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2 |
|
1095
|
|
|
} |
|
1096
|
|
|
break; |
|
1097
|
|
|
|
|
1098
|
|
|
case pack('C', 0x07): |
|
1099
|
|
|
// print titles (repeating rows) |
|
1100
|
|
|
// Assuming BIFF8, there are 3 cases |
|
1101
|
|
|
// 1. repeating rows |
|
1102
|
|
|
// formula looks like this: Sheet!$A$1:$IV$2 |
|
1103
|
|
|
// rows 1-2 repeat |
|
1104
|
|
|
// 2. repeating columns |
|
1105
|
|
|
// formula looks like this: Sheet!$A$1:$B$65536 |
|
1106
|
|
|
// columns A-B repeat |
|
1107
|
|
|
// 3. both repeating rows and repeating columns |
|
1108
|
|
|
// formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2 |
|
1109
|
|
|
|
|
1110
|
|
|
$ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma? |
|
1111
|
|
|
|
|
1112
|
|
|
foreach ($ranges as $range) { |
|
1113
|
|
|
// $range should look like this one of these |
|
1114
|
|
|
// Sheet!$A$1:$B$65536 |
|
1115
|
|
|
// Sheet!$A$1:$IV$2 |
|
1116
|
|
|
|
|
1117
|
|
|
$explodes = explode('!', $range); |
|
1118
|
|
|
|
|
1119
|
|
|
if (count($explodes) == 2) { |
|
1120
|
|
|
if ($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) { |
|
1121
|
|
|
|
|
1122
|
|
|
$extractedRange = $explodes[1]; |
|
1123
|
|
|
$extractedRange = str_replace('$', '', $extractedRange); |
|
1124
|
|
|
|
|
1125
|
|
|
$coordinateStrings = explode(':', $extractedRange); |
|
1126
|
|
|
if (count($coordinateStrings) == 2) { |
|
1127
|
|
|
list($firstColumn, $firstRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[0]); |
|
1128
|
|
|
list($lastColumn, $lastRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[1]); |
|
1129
|
|
|
|
|
1130
|
|
|
if ($firstColumn == 'A' and $lastColumn == 'IV') { |
|
|
|
|
|
|
1131
|
|
|
// then we have repeating rows |
|
1132
|
|
|
$docSheet->getPageSetup()->setRowsToRepeatAtTop(array($firstRow, $lastRow)); |
|
1133
|
|
|
} elseif ($firstRow == 1 and $lastRow == 65536) { |
|
|
|
|
|
|
1134
|
|
|
// then we have repeating columns |
|
1135
|
|
|
$docSheet->getPageSetup()->setColumnsToRepeatAtLeft(array($firstColumn, $lastColumn)); |
|
1136
|
|
|
} |
|
1137
|
|
|
} |
|
1138
|
|
|
} |
|
1139
|
|
|
} |
|
1140
|
|
|
} |
|
1141
|
|
|
break; |
|
1142
|
|
|
|
|
1143
|
|
|
} |
|
1144
|
|
|
} else { |
|
1145
|
|
|
// Extract range |
|
1146
|
|
|
$explodes = explode('!', $definedName['formula']); |
|
1147
|
|
|
|
|
1148
|
|
|
if (count($explodes) == 2) { |
|
1149
|
|
|
if (($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) || |
|
1150
|
|
|
($docSheet = $this->_phpExcel->getSheetByName(trim($explodes[0],"'")))) { |
|
|
|
|
|
|
1151
|
|
|
$extractedRange = $explodes[1]; |
|
1152
|
|
|
$extractedRange = str_replace('$', '', $extractedRange); |
|
1153
|
|
|
|
|
1154
|
|
|
$localOnly = ($definedName['scope'] == 0) ? false : true; |
|
1155
|
|
|
|
|
1156
|
|
|
$scope = ($definedName['scope'] == 0) ? |
|
1157
|
|
|
null : $this->_phpExcel->getSheetByName($this->_sheets[$definedName['scope'] - 1]['name']); |
|
1158
|
|
|
|
|
1159
|
|
|
$this->_phpExcel->addNamedRange( new PHPExcel_NamedRange((string)$definedName['name'], $docSheet, $extractedRange, $localOnly, $scope) ); |
|
|
|
|
|
|
1160
|
|
|
} |
|
1161
|
|
|
} else { |
|
|
|
|
|
|
1162
|
|
|
// Named Value |
|
1163
|
|
|
// TODO Provide support for named values |
|
1164
|
|
|
} |
|
1165
|
|
|
} |
|
1166
|
|
|
} |
|
1167
|
|
|
|
|
1168
|
|
|
return $this->_phpExcel; |
|
1169
|
|
|
} |
|
1170
|
|
|
|
|
1171
|
|
|
|
|
1172
|
|
|
/** |
|
1173
|
|
|
* Use OLE reader to extract the relevant data streams from the OLE file |
|
1174
|
|
|
* |
|
1175
|
|
|
* @param string $pFilename |
|
1176
|
|
|
*/ |
|
1177
|
|
|
private function _loadOLE($pFilename) |
|
1178
|
|
|
{ |
|
1179
|
|
|
// OLE reader |
|
1180
|
|
|
$ole = new PHPExcel_Shared_OLERead(); |
|
1181
|
|
|
|
|
1182
|
|
|
// get excel data, |
|
1183
|
|
|
$res = $ole->read($pFilename); |
|
|
|
|
|
|
1184
|
|
|
// Get workbook data: workbook stream + sheet streams |
|
1185
|
|
|
$this->_data = $ole->getStream($ole->wrkbook); |
|
1186
|
|
|
|
|
1187
|
|
|
// Get summary information data |
|
1188
|
|
|
$this->_summaryInformation = $ole->getStream($ole->summaryInformation); |
|
1189
|
|
|
|
|
1190
|
|
|
// Get additional document summary information data |
|
1191
|
|
|
$this->_documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation); |
|
1192
|
|
|
|
|
1193
|
|
|
// Get user-defined property data |
|
1194
|
|
|
// $this->_userDefinedProperties = $ole->getUserDefinedProperties(); |
|
1195
|
|
|
} |
|
1196
|
|
|
|
|
1197
|
|
|
|
|
1198
|
|
|
/** |
|
1199
|
|
|
* Read summary information |
|
1200
|
|
|
*/ |
|
1201
|
|
|
private function _readSummaryInformation() |
|
1202
|
|
|
{ |
|
1203
|
|
|
if (!isset($this->_summaryInformation)) { |
|
1204
|
|
|
return; |
|
1205
|
|
|
} |
|
1206
|
|
|
|
|
1207
|
|
|
// offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) |
|
1208
|
|
|
// offset: 2; size: 2; |
|
1209
|
|
|
// offset: 4; size: 2; OS version |
|
1210
|
|
|
// offset: 6; size: 2; OS indicator |
|
1211
|
|
|
// offset: 8; size: 16 |
|
1212
|
|
|
// offset: 24; size: 4; section count |
|
1213
|
|
|
$secCount = self::_GetInt4d($this->_summaryInformation, 24); |
|
1214
|
|
|
|
|
1215
|
|
|
// offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9 |
|
1216
|
|
|
// offset: 44; size: 4 |
|
1217
|
|
|
$secOffset = self::_GetInt4d($this->_summaryInformation, 44); |
|
1218
|
|
|
|
|
1219
|
|
|
// section header |
|
1220
|
|
|
// offset: $secOffset; size: 4; section length |
|
1221
|
|
|
$secLength = self::_GetInt4d($this->_summaryInformation, $secOffset); |
|
1222
|
|
|
|
|
1223
|
|
|
// offset: $secOffset+4; size: 4; property count |
|
1224
|
|
|
$countProperties = self::_GetInt4d($this->_summaryInformation, $secOffset+4); |
|
1225
|
|
|
|
|
1226
|
|
|
// initialize code page (used to resolve string values) |
|
1227
|
|
|
$codePage = 'CP1252'; |
|
1228
|
|
|
|
|
1229
|
|
|
// offset: ($secOffset+8); size: var |
|
1230
|
|
|
// loop through property decarations and properties |
|
1231
|
|
|
for ($i = 0; $i < $countProperties; ++$i) { |
|
1232
|
|
|
|
|
1233
|
|
|
// offset: ($secOffset+8) + (8 * $i); size: 4; property ID |
|
1234
|
|
|
$id = self::_GetInt4d($this->_summaryInformation, ($secOffset+8) + (8 * $i)); |
|
1235
|
|
|
|
|
1236
|
|
|
// Use value of property id as appropriate |
|
1237
|
|
|
// offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48) |
|
1238
|
|
|
$offset = self::_GetInt4d($this->_summaryInformation, ($secOffset+12) + (8 * $i)); |
|
1239
|
|
|
|
|
1240
|
|
|
$type = self::_GetInt4d($this->_summaryInformation, $secOffset + $offset); |
|
1241
|
|
|
|
|
1242
|
|
|
// initialize property value |
|
1243
|
|
|
$value = null; |
|
1244
|
|
|
|
|
1245
|
|
|
// extract property value based on property type |
|
1246
|
|
|
switch ($type) { |
|
1247
|
|
|
case 0x02: // 2 byte signed integer |
|
1248
|
|
|
$value = self::_GetInt2d($this->_summaryInformation, $secOffset + 4 + $offset); |
|
1249
|
|
|
break; |
|
1250
|
|
|
|
|
1251
|
|
|
case 0x03: // 4 byte signed integer |
|
1252
|
|
|
$value = self::_GetInt4d($this->_summaryInformation, $secOffset + 4 + $offset); |
|
1253
|
|
|
break; |
|
1254
|
|
|
|
|
1255
|
|
|
case 0x13: // 4 byte unsigned integer |
|
1256
|
|
|
// not needed yet, fix later if necessary |
|
1257
|
|
|
break; |
|
1258
|
|
|
|
|
1259
|
|
View Code Duplication |
case 0x1E: // null-terminated string prepended by dword string length |
|
1260
|
|
|
$byteLength = self::_GetInt4d($this->_summaryInformation, $secOffset + 4 + $offset); |
|
1261
|
|
|
$value = substr($this->_summaryInformation, $secOffset + 8 + $offset, $byteLength); |
|
1262
|
|
|
$value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage); |
|
1263
|
|
|
$value = rtrim($value); |
|
1264
|
|
|
break; |
|
1265
|
|
|
|
|
1266
|
|
View Code Duplication |
case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) |
|
1267
|
|
|
// PHP-time |
|
1268
|
|
|
$value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->_summaryInformation, $secOffset + 4 + $offset, 8)); |
|
1269
|
|
|
break; |
|
1270
|
|
|
|
|
1271
|
|
|
case 0x47: // Clipboard format |
|
1272
|
|
|
// not needed yet, fix later if necessary |
|
1273
|
|
|
break; |
|
1274
|
|
|
} |
|
1275
|
|
|
|
|
1276
|
|
|
switch ($id) { |
|
1277
|
|
|
case 0x01: // Code Page |
|
1278
|
|
|
$codePage = PHPExcel_Shared_CodePage::NumberToName($value); |
|
1279
|
|
|
break; |
|
1280
|
|
|
|
|
1281
|
|
|
case 0x02: // Title |
|
1282
|
|
|
$this->_phpExcel->getProperties()->setTitle($value); |
|
1283
|
|
|
break; |
|
1284
|
|
|
|
|
1285
|
|
|
case 0x03: // Subject |
|
1286
|
|
|
$this->_phpExcel->getProperties()->setSubject($value); |
|
1287
|
|
|
break; |
|
1288
|
|
|
|
|
1289
|
|
|
case 0x04: // Author (Creator) |
|
1290
|
|
|
$this->_phpExcel->getProperties()->setCreator($value); |
|
1291
|
|
|
break; |
|
1292
|
|
|
|
|
1293
|
|
|
case 0x05: // Keywords |
|
1294
|
|
|
$this->_phpExcel->getProperties()->setKeywords($value); |
|
1295
|
|
|
break; |
|
1296
|
|
|
|
|
1297
|
|
|
case 0x06: // Comments (Description) |
|
1298
|
|
|
$this->_phpExcel->getProperties()->setDescription($value); |
|
1299
|
|
|
break; |
|
1300
|
|
|
|
|
1301
|
|
|
case 0x07: // Template |
|
1302
|
|
|
// Not supported by PHPExcel |
|
1303
|
|
|
break; |
|
1304
|
|
|
|
|
1305
|
|
|
case 0x08: // Last Saved By (LastModifiedBy) |
|
1306
|
|
|
$this->_phpExcel->getProperties()->setLastModifiedBy($value); |
|
1307
|
|
|
break; |
|
1308
|
|
|
|
|
1309
|
|
|
case 0x09: // Revision |
|
1310
|
|
|
// Not supported by PHPExcel |
|
1311
|
|
|
break; |
|
1312
|
|
|
|
|
1313
|
|
|
case 0x0A: // Total Editing Time |
|
1314
|
|
|
// Not supported by PHPExcel |
|
1315
|
|
|
break; |
|
1316
|
|
|
|
|
1317
|
|
|
case 0x0B: // Last Printed |
|
1318
|
|
|
// Not supported by PHPExcel |
|
1319
|
|
|
break; |
|
1320
|
|
|
|
|
1321
|
|
|
case 0x0C: // Created Date/Time |
|
1322
|
|
|
$this->_phpExcel->getProperties()->setCreated($value); |
|
1323
|
|
|
break; |
|
1324
|
|
|
|
|
1325
|
|
|
case 0x0D: // Modified Date/Time |
|
1326
|
|
|
$this->_phpExcel->getProperties()->setModified($value); |
|
1327
|
|
|
break; |
|
1328
|
|
|
|
|
1329
|
|
|
case 0x0E: // Number of Pages |
|
1330
|
|
|
// Not supported by PHPExcel |
|
1331
|
|
|
break; |
|
1332
|
|
|
|
|
1333
|
|
|
case 0x0F: // Number of Words |
|
1334
|
|
|
// Not supported by PHPExcel |
|
1335
|
|
|
break; |
|
1336
|
|
|
|
|
1337
|
|
|
case 0x10: // Number of Characters |
|
1338
|
|
|
// Not supported by PHPExcel |
|
1339
|
|
|
break; |
|
1340
|
|
|
|
|
1341
|
|
|
case 0x11: // Thumbnail |
|
1342
|
|
|
// Not supported by PHPExcel |
|
1343
|
|
|
break; |
|
1344
|
|
|
|
|
1345
|
|
|
case 0x12: // Name of creating application |
|
1346
|
|
|
// Not supported by PHPExcel |
|
1347
|
|
|
break; |
|
1348
|
|
|
|
|
1349
|
|
|
case 0x13: // Security |
|
1350
|
|
|
// Not supported by PHPExcel |
|
1351
|
|
|
break; |
|
1352
|
|
|
|
|
1353
|
|
|
} |
|
1354
|
|
|
} |
|
1355
|
|
|
} |
|
1356
|
|
|
|
|
1357
|
|
|
|
|
1358
|
|
|
/** |
|
1359
|
|
|
* Read additional document summary information |
|
1360
|
|
|
*/ |
|
1361
|
|
|
private function _readDocumentSummaryInformation() |
|
1362
|
|
|
{ |
|
1363
|
|
|
if (!isset($this->_documentSummaryInformation)) { |
|
1364
|
|
|
return; |
|
1365
|
|
|
} |
|
1366
|
|
|
|
|
1367
|
|
|
// offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) |
|
1368
|
|
|
// offset: 2; size: 2; |
|
1369
|
|
|
// offset: 4; size: 2; OS version |
|
1370
|
|
|
// offset: 6; size: 2; OS indicator |
|
1371
|
|
|
// offset: 8; size: 16 |
|
1372
|
|
|
// offset: 24; size: 4; section count |
|
1373
|
|
|
$secCount = self::_GetInt4d($this->_documentSummaryInformation, 24); |
|
1374
|
|
|
// echo '$secCount = ',$secCount,'<br />'; |
|
1375
|
|
|
|
|
1376
|
|
|
// offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae |
|
1377
|
|
|
// offset: 44; size: 4; first section offset |
|
1378
|
|
|
$secOffset = self::_GetInt4d($this->_documentSummaryInformation, 44); |
|
1379
|
|
|
// echo '$secOffset = ',$secOffset,'<br />'; |
|
1380
|
|
|
|
|
1381
|
|
|
// section header |
|
1382
|
|
|
// offset: $secOffset; size: 4; section length |
|
1383
|
|
|
$secLength = self::_GetInt4d($this->_documentSummaryInformation, $secOffset); |
|
1384
|
|
|
// echo '$secLength = ',$secLength,'<br />'; |
|
1385
|
|
|
|
|
1386
|
|
|
// offset: $secOffset+4; size: 4; property count |
|
1387
|
|
|
$countProperties = self::_GetInt4d($this->_documentSummaryInformation, $secOffset+4); |
|
1388
|
|
|
// echo '$countProperties = ',$countProperties,'<br />'; |
|
1389
|
|
|
|
|
1390
|
|
|
// initialize code page (used to resolve string values) |
|
1391
|
|
|
$codePage = 'CP1252'; |
|
1392
|
|
|
|
|
1393
|
|
|
// offset: ($secOffset+8); size: var |
|
1394
|
|
|
// loop through property decarations and properties |
|
1395
|
|
|
for ($i = 0; $i < $countProperties; ++$i) { |
|
1396
|
|
|
// echo 'Property ',$i,'<br />'; |
|
1397
|
|
|
// offset: ($secOffset+8) + (8 * $i); size: 4; property ID |
|
1398
|
|
|
$id = self::_GetInt4d($this->_documentSummaryInformation, ($secOffset+8) + (8 * $i)); |
|
1399
|
|
|
// echo 'ID is ',$id,'<br />'; |
|
1400
|
|
|
|
|
1401
|
|
|
// Use value of property id as appropriate |
|
1402
|
|
|
// offset: 60 + 8 * $i; size: 4; offset from beginning of section (48) |
|
1403
|
|
|
$offset = self::_GetInt4d($this->_documentSummaryInformation, ($secOffset+12) + (8 * $i)); |
|
1404
|
|
|
|
|
1405
|
|
|
$type = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + $offset); |
|
1406
|
|
|
// echo 'Type is ',$type,', '; |
|
1407
|
|
|
|
|
1408
|
|
|
// initialize property value |
|
1409
|
|
|
$value = null; |
|
1410
|
|
|
|
|
1411
|
|
|
// extract property value based on property type |
|
1412
|
|
|
switch ($type) { |
|
1413
|
|
|
case 0x02: // 2 byte signed integer |
|
1414
|
|
|
$value = self::_GetInt2d($this->_documentSummaryInformation, $secOffset + 4 + $offset); |
|
1415
|
|
|
break; |
|
1416
|
|
|
|
|
1417
|
|
|
case 0x03: // 4 byte signed integer |
|
1418
|
|
|
$value = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + 4 + $offset); |
|
1419
|
|
|
break; |
|
1420
|
|
|
|
|
1421
|
|
|
case 0x0B: // Boolean |
|
1422
|
|
|
$value = self::_GetInt2d($this->_documentSummaryInformation, $secOffset + 4 + $offset); |
|
1423
|
|
|
$value = ($value == 0 ? false : true); |
|
1424
|
|
|
break; |
|
1425
|
|
|
|
|
1426
|
|
|
case 0x13: // 4 byte unsigned integer |
|
1427
|
|
|
// not needed yet, fix later if necessary |
|
1428
|
|
|
break; |
|
1429
|
|
|
|
|
1430
|
|
View Code Duplication |
case 0x1E: // null-terminated string prepended by dword string length |
|
1431
|
|
|
$byteLength = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + 4 + $offset); |
|
1432
|
|
|
$value = substr($this->_documentSummaryInformation, $secOffset + 8 + $offset, $byteLength); |
|
1433
|
|
|
$value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage); |
|
1434
|
|
|
$value = rtrim($value); |
|
1435
|
|
|
break; |
|
1436
|
|
|
|
|
1437
|
|
View Code Duplication |
case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) |
|
1438
|
|
|
// PHP-Time |
|
1439
|
|
|
$value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->_documentSummaryInformation, $secOffset + 4 + $offset, 8)); |
|
1440
|
|
|
break; |
|
1441
|
|
|
|
|
1442
|
|
|
case 0x47: // Clipboard format |
|
1443
|
|
|
// not needed yet, fix later if necessary |
|
1444
|
|
|
break; |
|
1445
|
|
|
} |
|
1446
|
|
|
|
|
1447
|
|
|
switch ($id) { |
|
1448
|
|
|
case 0x01: // Code Page |
|
1449
|
|
|
$codePage = PHPExcel_Shared_CodePage::NumberToName($value); |
|
1450
|
|
|
break; |
|
1451
|
|
|
|
|
1452
|
|
|
case 0x02: // Category |
|
1453
|
|
|
$this->_phpExcel->getProperties()->setCategory($value); |
|
1454
|
|
|
break; |
|
1455
|
|
|
|
|
1456
|
|
|
case 0x03: // Presentation Target |
|
1457
|
|
|
// Not supported by PHPExcel |
|
1458
|
|
|
break; |
|
1459
|
|
|
|
|
1460
|
|
|
case 0x04: // Bytes |
|
1461
|
|
|
// Not supported by PHPExcel |
|
1462
|
|
|
break; |
|
1463
|
|
|
|
|
1464
|
|
|
case 0x05: // Lines |
|
1465
|
|
|
// Not supported by PHPExcel |
|
1466
|
|
|
break; |
|
1467
|
|
|
|
|
1468
|
|
|
case 0x06: // Paragraphs |
|
1469
|
|
|
// Not supported by PHPExcel |
|
1470
|
|
|
break; |
|
1471
|
|
|
|
|
1472
|
|
|
case 0x07: // Slides |
|
1473
|
|
|
// Not supported by PHPExcel |
|
1474
|
|
|
break; |
|
1475
|
|
|
|
|
1476
|
|
|
case 0x08: // Notes |
|
1477
|
|
|
// Not supported by PHPExcel |
|
1478
|
|
|
break; |
|
1479
|
|
|
|
|
1480
|
|
|
case 0x09: // Hidden Slides |
|
1481
|
|
|
// Not supported by PHPExcel |
|
1482
|
|
|
break; |
|
1483
|
|
|
|
|
1484
|
|
|
case 0x0A: // MM Clips |
|
1485
|
|
|
// Not supported by PHPExcel |
|
1486
|
|
|
break; |
|
1487
|
|
|
|
|
1488
|
|
|
case 0x0B: // Scale Crop |
|
1489
|
|
|
// Not supported by PHPExcel |
|
1490
|
|
|
break; |
|
1491
|
|
|
|
|
1492
|
|
|
case 0x0C: // Heading Pairs |
|
1493
|
|
|
// Not supported by PHPExcel |
|
1494
|
|
|
break; |
|
1495
|
|
|
|
|
1496
|
|
|
case 0x0D: // Titles of Parts |
|
1497
|
|
|
// Not supported by PHPExcel |
|
1498
|
|
|
break; |
|
1499
|
|
|
|
|
1500
|
|
|
case 0x0E: // Manager |
|
1501
|
|
|
$this->_phpExcel->getProperties()->setManager($value); |
|
1502
|
|
|
break; |
|
1503
|
|
|
|
|
1504
|
|
|
case 0x0F: // Company |
|
1505
|
|
|
$this->_phpExcel->getProperties()->setCompany($value); |
|
1506
|
|
|
break; |
|
1507
|
|
|
|
|
1508
|
|
|
case 0x10: // Links up-to-date |
|
1509
|
|
|
// Not supported by PHPExcel |
|
1510
|
|
|
break; |
|
1511
|
|
|
|
|
1512
|
|
|
} |
|
1513
|
|
|
} |
|
1514
|
|
|
} |
|
1515
|
|
|
|
|
1516
|
|
|
|
|
1517
|
|
|
/** |
|
1518
|
|
|
* Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record. |
|
1519
|
|
|
*/ |
|
1520
|
|
View Code Duplication |
private function _readDefault() |
|
1521
|
|
|
{ |
|
1522
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1523
|
|
|
// $recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1524
|
|
|
|
|
1525
|
|
|
// move stream pointer to next record |
|
1526
|
|
|
$this->_pos += 4 + $length; |
|
1527
|
|
|
} |
|
1528
|
|
|
|
|
1529
|
|
|
|
|
1530
|
|
|
/** |
|
1531
|
|
|
* The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions, |
|
1532
|
|
|
* this record stores a note (cell note). This feature was significantly enhanced in Excel 97. |
|
1533
|
|
|
*/ |
|
1534
|
|
|
private function _readNote() |
|
1535
|
|
|
{ |
|
1536
|
|
|
// echo '<b>Read Cell Annotation</b><br />'; |
|
1537
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1538
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1539
|
|
|
|
|
1540
|
|
|
// move stream pointer to next record |
|
1541
|
|
|
$this->_pos += 4 + $length; |
|
1542
|
|
|
|
|
1543
|
|
|
if ($this->_readDataOnly) { |
|
1544
|
|
|
return; |
|
1545
|
|
|
} |
|
1546
|
|
|
|
|
1547
|
|
|
$cellAddress = $this->_readBIFF8CellAddress(substr($recordData, 0, 4)); |
|
1548
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
1549
|
|
|
$noteObjID = self::_GetInt2d($recordData, 6); |
|
1550
|
|
|
$noteAuthor = self::_readUnicodeStringLong(substr($recordData, 8)); |
|
1551
|
|
|
$noteAuthor = $noteAuthor['value']; |
|
1552
|
|
|
// echo 'Note Address=',$cellAddress,'<br />'; |
|
1553
|
|
|
// echo 'Note Object ID=',$noteObjID,'<br />'; |
|
1554
|
|
|
// echo 'Note Author=',$noteAuthor,'<hr />'; |
|
1555
|
|
|
// |
|
1556
|
|
|
$this->_cellNotes[$noteObjID] = array('cellRef' => $cellAddress, |
|
1557
|
|
|
'objectID' => $noteObjID, |
|
1558
|
|
|
'author' => $noteAuthor |
|
1559
|
|
|
); |
|
1560
|
|
|
} else { |
|
1561
|
|
|
$extension = false; |
|
1562
|
|
|
if ($cellAddress == '$B$65536') { |
|
1563
|
|
|
// If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation |
|
1564
|
|
|
// note from the previous cell annotation. We're not yet handling this, so annotations longer than the |
|
1565
|
|
|
// max 2048 bytes will probably throw a wobbly. |
|
1566
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
1567
|
|
|
$extension = true; |
|
1568
|
|
|
$cellAddress = array_pop(array_keys($this->_phpSheet->getComments())); |
|
|
|
|
|
|
1569
|
|
|
} |
|
1570
|
|
|
// echo 'Note Address=',$cellAddress,'<br />'; |
|
1571
|
|
|
|
|
1572
|
|
|
$cellAddress = str_replace('$','',$cellAddress); |
|
1573
|
|
|
$noteLength = self::_GetInt2d($recordData, 4); |
|
1574
|
|
|
$noteText = trim(substr($recordData, 6)); |
|
1575
|
|
|
// echo 'Note Length=',$noteLength,'<br />'; |
|
1576
|
|
|
// echo 'Note Text=',$noteText,'<br />'; |
|
1577
|
|
|
|
|
1578
|
|
|
if ($extension) { |
|
1579
|
|
|
// Concatenate this extension with the currently set comment for the cell |
|
1580
|
|
|
$comment = $this->_phpSheet->getComment( $cellAddress ); |
|
1581
|
|
|
$commentText = $comment->getText()->getPlainText(); |
|
1582
|
|
|
$comment->setText($this->_parseRichText($commentText.$noteText) ); |
|
1583
|
|
|
} else { |
|
1584
|
|
|
// Set comment for the cell |
|
1585
|
|
|
$this->_phpSheet->getComment( $cellAddress ) |
|
1586
|
|
|
// ->setAuthor( $author ) |
|
1587
|
|
|
->setText($this->_parseRichText($noteText) ); |
|
1588
|
|
|
} |
|
1589
|
|
|
} |
|
1590
|
|
|
|
|
1591
|
|
|
} |
|
1592
|
|
|
|
|
1593
|
|
|
|
|
1594
|
|
|
/** |
|
1595
|
|
|
* The TEXT Object record contains the text associated with a cell annotation. |
|
1596
|
|
|
*/ |
|
1597
|
|
|
private function _readTextObject() |
|
1598
|
|
|
{ |
|
1599
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1600
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1601
|
|
|
|
|
1602
|
|
|
// move stream pointer to next record |
|
1603
|
|
|
$this->_pos += 4 + $length; |
|
1604
|
|
|
|
|
1605
|
|
|
if ($this->_readDataOnly) { |
|
1606
|
|
|
return; |
|
1607
|
|
|
} |
|
1608
|
|
|
|
|
1609
|
|
|
// recordData consists of an array of subrecords looking like this: |
|
1610
|
|
|
// grbit: 2 bytes; Option Flags |
|
1611
|
|
|
// rot: 2 bytes; rotation |
|
1612
|
|
|
// cchText: 2 bytes; length of the text (in the first continue record) |
|
1613
|
|
|
// cbRuns: 2 bytes; length of the formatting (in the second continue record) |
|
1614
|
|
|
// followed by the continuation records containing the actual text and formatting |
|
1615
|
|
|
$grbitOpts = self::_GetInt2d($recordData, 0); |
|
1616
|
|
|
$rot = self::_GetInt2d($recordData, 2); |
|
1617
|
|
|
$cchText = self::_GetInt2d($recordData, 10); |
|
1618
|
|
|
$cbRuns = self::_GetInt2d($recordData, 12); |
|
1619
|
|
|
$text = $this->_getSplicedRecordData(); |
|
1620
|
|
|
|
|
1621
|
|
|
$this->_textObjects[$this->textObjRef] = array( |
|
1622
|
|
|
'text' => substr($text["recordData"],$text["spliceOffsets"][0]+1,$cchText), |
|
1623
|
|
|
'format' => substr($text["recordData"],$text["spliceOffsets"][1],$cbRuns), |
|
1624
|
|
|
'alignment' => $grbitOpts, |
|
1625
|
|
|
'rotation' => $rot |
|
1626
|
|
|
); |
|
1627
|
|
|
|
|
1628
|
|
|
// echo '<b>_readTextObject()</b><br />'; |
|
1629
|
|
|
// var_dump($this->_textObjects[$this->textObjRef]); |
|
1630
|
|
|
// echo '<br />'; |
|
1631
|
|
|
} |
|
1632
|
|
|
|
|
1633
|
|
|
|
|
1634
|
|
|
/** |
|
1635
|
|
|
* Read BOF |
|
1636
|
|
|
*/ |
|
1637
|
|
|
private function _readBof() |
|
1638
|
|
|
{ |
|
1639
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1640
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1641
|
|
|
|
|
1642
|
|
|
// move stream pointer to next record |
|
1643
|
|
|
$this->_pos += 4 + $length; |
|
1644
|
|
|
|
|
1645
|
|
|
// offset: 2; size: 2; type of the following data |
|
1646
|
|
|
$substreamType = self::_GetInt2d($recordData, 2); |
|
1647
|
|
|
|
|
1648
|
|
|
switch ($substreamType) { |
|
1649
|
|
|
case self::XLS_WorkbookGlobals: |
|
1650
|
|
|
$version = self::_GetInt2d($recordData, 0); |
|
1651
|
|
|
if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) { |
|
1652
|
|
|
throw new Exception('Cannot read this Excel file. Version is too old.'); |
|
1653
|
|
|
} |
|
1654
|
|
|
$this->_version = $version; |
|
1655
|
|
|
break; |
|
1656
|
|
|
|
|
1657
|
|
|
case self::XLS_Worksheet: |
|
1658
|
|
|
// do not use this version information for anything |
|
1659
|
|
|
// it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream |
|
1660
|
|
|
break; |
|
1661
|
|
|
|
|
1662
|
|
|
default: |
|
1663
|
|
|
// substream, e.g. chart |
|
1664
|
|
|
// just skip the entire substream |
|
1665
|
|
|
do { |
|
1666
|
|
|
$code = self::_GetInt2d($this->_data, $this->_pos); |
|
1667
|
|
|
$this->_readDefault(); |
|
1668
|
|
|
} while ($code != self::XLS_Type_EOF && $this->_pos < $this->_dataSize); |
|
1669
|
|
|
break; |
|
1670
|
|
|
} |
|
1671
|
|
|
} |
|
1672
|
|
|
|
|
1673
|
|
|
|
|
1674
|
|
|
/** |
|
1675
|
|
|
* FILEPASS |
|
1676
|
|
|
* |
|
1677
|
|
|
* This record is part of the File Protection Block. It |
|
1678
|
|
|
* contains information about the read/write password of the |
|
1679
|
|
|
* file. All record contents following this record will be |
|
1680
|
|
|
* encrypted. |
|
1681
|
|
|
* |
|
1682
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
1683
|
|
|
* Excel File Format" |
|
1684
|
|
|
*/ |
|
1685
|
|
View Code Duplication |
private function _readFilepass() |
|
1686
|
|
|
{ |
|
1687
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1688
|
|
|
// $recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1689
|
|
|
|
|
1690
|
|
|
// move stream pointer to next record |
|
1691
|
|
|
$this->_pos += 4 + $length; |
|
1692
|
|
|
|
|
1693
|
|
|
throw new Exception('Cannot read encrypted file'); |
|
1694
|
|
|
} |
|
1695
|
|
|
|
|
1696
|
|
|
|
|
1697
|
|
|
/** |
|
1698
|
|
|
* CODEPAGE |
|
1699
|
|
|
* |
|
1700
|
|
|
* This record stores the text encoding used to write byte |
|
1701
|
|
|
* strings, stored as MS Windows code page identifier. |
|
1702
|
|
|
* |
|
1703
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
1704
|
|
|
* Excel File Format" |
|
1705
|
|
|
*/ |
|
1706
|
|
|
private function _readCodepage() |
|
1707
|
|
|
{ |
|
1708
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1709
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1710
|
|
|
|
|
1711
|
|
|
// move stream pointer to next record |
|
1712
|
|
|
$this->_pos += 4 + $length; |
|
1713
|
|
|
|
|
1714
|
|
|
// offset: 0; size: 2; code page identifier |
|
1715
|
|
|
$codepage = self::_GetInt2d($recordData, 0); |
|
1716
|
|
|
|
|
1717
|
|
|
$this->_codepage = PHPExcel_Shared_CodePage::NumberToName($codepage); |
|
1718
|
|
|
} |
|
1719
|
|
|
|
|
1720
|
|
|
|
|
1721
|
|
|
/** |
|
1722
|
|
|
* DATEMODE |
|
1723
|
|
|
* |
|
1724
|
|
|
* This record specifies the base date for displaying date |
|
1725
|
|
|
* values. All dates are stored as count of days past this |
|
1726
|
|
|
* base date. In BIFF2-BIFF4 this record is part of the |
|
1727
|
|
|
* Calculation Settings Block. In BIFF5-BIFF8 it is |
|
1728
|
|
|
* stored in the Workbook Globals Substream. |
|
1729
|
|
|
* |
|
1730
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
1731
|
|
|
* Excel File Format" |
|
1732
|
|
|
*/ |
|
1733
|
|
|
private function _readDateMode() |
|
1734
|
|
|
{ |
|
1735
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1736
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1737
|
|
|
|
|
1738
|
|
|
// move stream pointer to next record |
|
1739
|
|
|
$this->_pos += 4 + $length; |
|
1740
|
|
|
|
|
1741
|
|
|
// offset: 0; size: 2; 0 = base 1900, 1 = base 1904 |
|
1742
|
|
|
PHPExcel_Shared_Date::setExcelCalendar(PHPExcel_Shared_Date::CALENDAR_WINDOWS_1900); |
|
1743
|
|
|
if (ord($recordData{0}) == 1) { |
|
1744
|
|
|
PHPExcel_Shared_Date::setExcelCalendar(PHPExcel_Shared_Date::CALENDAR_MAC_1904); |
|
1745
|
|
|
} |
|
1746
|
|
|
} |
|
1747
|
|
|
|
|
1748
|
|
|
|
|
1749
|
|
|
/** |
|
1750
|
|
|
* Read a FONT record |
|
1751
|
|
|
*/ |
|
1752
|
|
|
private function _readFont() |
|
1753
|
|
|
{ |
|
1754
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1755
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1756
|
|
|
|
|
1757
|
|
|
// move stream pointer to next record |
|
1758
|
|
|
$this->_pos += 4 + $length; |
|
1759
|
|
|
|
|
1760
|
|
|
if (!$this->_readDataOnly) { |
|
1761
|
|
|
$objFont = new PHPExcel_Style_Font(); |
|
1762
|
|
|
|
|
1763
|
|
|
// offset: 0; size: 2; height of the font (in twips = 1/20 of a point) |
|
1764
|
|
|
$size = self::_GetInt2d($recordData, 0); |
|
1765
|
|
|
$objFont->setSize($size / 20); |
|
1766
|
|
|
|
|
1767
|
|
|
// offset: 2; size: 2; option flags |
|
1768
|
|
|
// bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8) |
|
1769
|
|
|
// bit: 1; mask 0x0002; italic |
|
1770
|
|
|
$isItalic = (0x0002 & self::_GetInt2d($recordData, 2)) >> 1; |
|
1771
|
|
|
if ($isItalic) $objFont->setItalic(true); |
|
1772
|
|
|
|
|
1773
|
|
|
// bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8) |
|
1774
|
|
|
// bit: 3; mask 0x0008; strike |
|
1775
|
|
|
$isStrike = (0x0008 & self::_GetInt2d($recordData, 2)) >> 3; |
|
1776
|
|
|
if ($isStrike) $objFont->setStrikethrough(true); |
|
1777
|
|
|
|
|
1778
|
|
|
// offset: 4; size: 2; colour index |
|
1779
|
|
|
$colorIndex = self::_GetInt2d($recordData, 4); |
|
1780
|
|
|
$objFont->colorIndex = $colorIndex; |
|
|
|
|
|
|
1781
|
|
|
|
|
1782
|
|
|
// offset: 6; size: 2; font weight |
|
1783
|
|
|
$weight = self::_GetInt2d($recordData, 6); |
|
1784
|
|
|
switch ($weight) { |
|
1785
|
|
|
case 0x02BC: |
|
1786
|
|
|
$objFont->setBold(true); |
|
1787
|
|
|
break; |
|
1788
|
|
|
} |
|
1789
|
|
|
|
|
1790
|
|
|
// offset: 8; size: 2; escapement type |
|
1791
|
|
|
$escapement = self::_GetInt2d($recordData, 8); |
|
1792
|
|
|
switch ($escapement) { |
|
1793
|
|
|
case 0x0001: |
|
1794
|
|
|
$objFont->setSuperScript(true); |
|
1795
|
|
|
break; |
|
1796
|
|
|
case 0x0002: |
|
1797
|
|
|
$objFont->setSubScript(true); |
|
1798
|
|
|
break; |
|
1799
|
|
|
} |
|
1800
|
|
|
|
|
1801
|
|
|
// offset: 10; size: 1; underline type |
|
1802
|
|
|
$underlineType = ord($recordData{10}); |
|
1803
|
|
|
switch ($underlineType) { |
|
1804
|
|
|
case 0x00: |
|
1805
|
|
|
break; // no underline |
|
1806
|
|
|
case 0x01: |
|
1807
|
|
|
$objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLE); |
|
1808
|
|
|
break; |
|
1809
|
|
|
case 0x02: |
|
1810
|
|
|
$objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_DOUBLE); |
|
1811
|
|
|
break; |
|
1812
|
|
|
case 0x21: |
|
1813
|
|
|
$objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLEACCOUNTING); |
|
1814
|
|
|
break; |
|
1815
|
|
|
case 0x22: |
|
1816
|
|
|
$objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_DOUBLEACCOUNTING); |
|
1817
|
|
|
break; |
|
1818
|
|
|
} |
|
1819
|
|
|
|
|
1820
|
|
|
// offset: 11; size: 1; font family |
|
1821
|
|
|
// offset: 12; size: 1; character set |
|
1822
|
|
|
// offset: 13; size: 1; not used |
|
1823
|
|
|
// offset: 14; size: var; font name |
|
1824
|
|
View Code Duplication |
if ($this->_version == self::XLS_BIFF8) { |
|
1825
|
|
|
$string = self::_readUnicodeStringShort(substr($recordData, 14)); |
|
1826
|
|
|
} else { |
|
1827
|
|
|
$string = $this->_readByteStringShort(substr($recordData, 14)); |
|
1828
|
|
|
} |
|
1829
|
|
|
$objFont->setName($string['value']); |
|
1830
|
|
|
|
|
1831
|
|
|
$this->_objFonts[] = $objFont; |
|
1832
|
|
|
} |
|
1833
|
|
|
} |
|
1834
|
|
|
|
|
1835
|
|
|
|
|
1836
|
|
|
/** |
|
1837
|
|
|
* FORMAT |
|
1838
|
|
|
* |
|
1839
|
|
|
* This record contains information about a number format. |
|
1840
|
|
|
* All FORMAT records occur together in a sequential list. |
|
1841
|
|
|
* |
|
1842
|
|
|
* In BIFF2-BIFF4 other records referencing a FORMAT record |
|
1843
|
|
|
* contain a zero-based index into this list. From BIFF5 on |
|
1844
|
|
|
* the FORMAT record contains the index itself that will be |
|
1845
|
|
|
* used by other records. |
|
1846
|
|
|
* |
|
1847
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
1848
|
|
|
* Excel File Format" |
|
1849
|
|
|
*/ |
|
1850
|
|
|
private function _readFormat() |
|
1851
|
|
|
{ |
|
1852
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1853
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1854
|
|
|
|
|
1855
|
|
|
// move stream pointer to next record |
|
1856
|
|
|
$this->_pos += 4 + $length; |
|
1857
|
|
|
|
|
1858
|
|
|
if (!$this->_readDataOnly) { |
|
1859
|
|
|
$indexCode = self::_GetInt2d($recordData, 0); |
|
1860
|
|
|
|
|
1861
|
|
View Code Duplication |
if ($this->_version == self::XLS_BIFF8) { |
|
1862
|
|
|
$string = self::_readUnicodeStringLong(substr($recordData, 2)); |
|
1863
|
|
|
} else { |
|
1864
|
|
|
// BIFF7 |
|
1865
|
|
|
$string = $this->_readByteStringShort(substr($recordData, 2)); |
|
1866
|
|
|
} |
|
1867
|
|
|
|
|
1868
|
|
|
$formatString = $string['value']; |
|
1869
|
|
|
$this->_formats[$indexCode] = $formatString; |
|
1870
|
|
|
} |
|
1871
|
|
|
} |
|
1872
|
|
|
|
|
1873
|
|
|
|
|
1874
|
|
|
/** |
|
1875
|
|
|
* XF - Extended Format |
|
1876
|
|
|
* |
|
1877
|
|
|
* This record contains formatting information for cells, rows, columns or styles. |
|
1878
|
|
|
* According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF |
|
1879
|
|
|
* and 1 cell XF. |
|
1880
|
|
|
* Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF |
|
1881
|
|
|
* and XF record 15 is a cell XF |
|
1882
|
|
|
* We only read the first cell style XF and skip the remaining cell style XF records |
|
1883
|
|
|
* We read all cell XF records. |
|
1884
|
|
|
* |
|
1885
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
1886
|
|
|
* Excel File Format" |
|
1887
|
|
|
*/ |
|
1888
|
|
|
private function _readXf() |
|
1889
|
|
|
{ |
|
1890
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
1891
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
1892
|
|
|
|
|
1893
|
|
|
// move stream pointer to next record |
|
1894
|
|
|
$this->_pos += 4 + $length; |
|
1895
|
|
|
|
|
1896
|
|
|
$objStyle = new PHPExcel_Style(); |
|
1897
|
|
|
|
|
1898
|
|
|
if (!$this->_readDataOnly) { |
|
1899
|
|
|
// offset: 0; size: 2; Index to FONT record |
|
1900
|
|
|
if (self::_GetInt2d($recordData, 0) < 4) { |
|
1901
|
|
|
$fontIndex = self::_GetInt2d($recordData, 0); |
|
1902
|
|
|
} else { |
|
1903
|
|
|
// this has to do with that index 4 is omitted in all BIFF versions for some strange reason |
|
1904
|
|
|
// check the OpenOffice documentation of the FONT record |
|
1905
|
|
|
$fontIndex = self::_GetInt2d($recordData, 0) - 1; |
|
1906
|
|
|
} |
|
1907
|
|
|
$objStyle->setFont($this->_objFonts[$fontIndex]); |
|
1908
|
|
|
|
|
1909
|
|
|
// offset: 2; size: 2; Index to FORMAT record |
|
1910
|
|
|
$numberFormatIndex = self::_GetInt2d($recordData, 2); |
|
1911
|
|
|
if (isset($this->_formats[$numberFormatIndex])) { |
|
1912
|
|
|
// then we have user-defined format code |
|
1913
|
|
|
$numberformat = array('code' => $this->_formats[$numberFormatIndex]); |
|
1914
|
|
|
} elseif (($code = PHPExcel_Style_NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') { |
|
1915
|
|
|
// then we have built-in format code |
|
1916
|
|
|
$numberformat = array('code' => $code); |
|
1917
|
|
|
} else { |
|
1918
|
|
|
// we set the general format code |
|
1919
|
|
|
$numberformat = array('code' => 'General'); |
|
1920
|
|
|
} |
|
1921
|
|
|
$objStyle->getNumberFormat()->setFormatCode($numberformat['code']); |
|
1922
|
|
|
|
|
1923
|
|
|
// offset: 4; size: 2; XF type, cell protection, and parent style XF |
|
1924
|
|
|
// bit 2-0; mask 0x0007; XF_TYPE_PROT |
|
1925
|
|
|
$xfTypeProt = self::_GetInt2d($recordData, 4); |
|
1926
|
|
|
// bit 0; mask 0x01; 1 = cell is locked |
|
1927
|
|
|
$isLocked = (0x01 & $xfTypeProt) >> 0; |
|
1928
|
|
|
$objStyle->getProtection()->setLocked($isLocked ? |
|
1929
|
|
|
PHPExcel_Style_Protection::PROTECTION_INHERIT : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED); |
|
1930
|
|
|
|
|
1931
|
|
|
// bit 1; mask 0x02; 1 = Formula is hidden |
|
1932
|
|
|
$isHidden = (0x02 & $xfTypeProt) >> 1; |
|
1933
|
|
|
$objStyle->getProtection()->setHidden($isHidden ? |
|
1934
|
|
|
PHPExcel_Style_Protection::PROTECTION_PROTECTED : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED); |
|
1935
|
|
|
|
|
1936
|
|
|
// bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF |
|
1937
|
|
|
$isCellStyleXf = (0x04 & $xfTypeProt) >> 2; |
|
1938
|
|
|
|
|
1939
|
|
|
// offset: 6; size: 1; Alignment and text break |
|
1940
|
|
|
// bit 2-0, mask 0x07; horizontal alignment |
|
1941
|
|
|
$horAlign = (0x07 & ord($recordData{6})) >> 0; |
|
1942
|
|
|
switch ($horAlign) { |
|
1943
|
|
|
case 0: |
|
1944
|
|
|
$objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_GENERAL); |
|
1945
|
|
|
break; |
|
1946
|
|
|
case 1: |
|
1947
|
|
|
$objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT); |
|
1948
|
|
|
break; |
|
1949
|
|
|
case 2: |
|
1950
|
|
|
$objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER); |
|
1951
|
|
|
break; |
|
1952
|
|
|
case 3: |
|
1953
|
|
|
$objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_RIGHT); |
|
1954
|
|
|
break; |
|
1955
|
|
|
case 5: |
|
1956
|
|
|
$objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY); |
|
1957
|
|
|
break; |
|
1958
|
|
|
case 6: |
|
1959
|
|
|
$objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER_CONTINUOUS); |
|
1960
|
|
|
break; |
|
1961
|
|
|
} |
|
1962
|
|
|
// bit 3, mask 0x08; wrap text |
|
1963
|
|
|
$wrapText = (0x08 & ord($recordData{6})) >> 3; |
|
1964
|
|
|
switch ($wrapText) { |
|
1965
|
|
|
case 0: |
|
1966
|
|
|
$objStyle->getAlignment()->setWrapText(false); |
|
1967
|
|
|
break; |
|
1968
|
|
|
case 1: |
|
1969
|
|
|
$objStyle->getAlignment()->setWrapText(true); |
|
1970
|
|
|
break; |
|
1971
|
|
|
} |
|
1972
|
|
|
// bit 6-4, mask 0x70; vertical alignment |
|
1973
|
|
|
$vertAlign = (0x70 & ord($recordData{6})) >> 4; |
|
1974
|
|
|
switch ($vertAlign) { |
|
1975
|
|
|
case 0: |
|
1976
|
|
|
$objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_TOP); |
|
1977
|
|
|
break; |
|
1978
|
|
|
case 1: |
|
1979
|
|
|
$objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_CENTER); |
|
1980
|
|
|
break; |
|
1981
|
|
|
case 2: |
|
1982
|
|
|
$objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_BOTTOM); |
|
1983
|
|
|
break; |
|
1984
|
|
|
case 3: |
|
1985
|
|
|
$objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_JUSTIFY); |
|
1986
|
|
|
break; |
|
1987
|
|
|
} |
|
1988
|
|
|
|
|
1989
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
1990
|
|
|
// offset: 7; size: 1; XF_ROTATION: Text rotation angle |
|
1991
|
|
|
$angle = ord($recordData{7}); |
|
1992
|
|
|
$rotation = 0; |
|
1993
|
|
|
if ($angle <= 90) { |
|
1994
|
|
|
$rotation = $angle; |
|
1995
|
|
|
} else if ($angle <= 180) { |
|
1996
|
|
|
$rotation = 90 - $angle; |
|
1997
|
|
|
} else if ($angle == 255) { |
|
1998
|
|
|
$rotation = -165; |
|
1999
|
|
|
} |
|
2000
|
|
|
$objStyle->getAlignment()->setTextRotation($rotation); |
|
2001
|
|
|
|
|
2002
|
|
|
// offset: 8; size: 1; Indentation, shrink to cell size, and text direction |
|
2003
|
|
|
// bit: 3-0; mask: 0x0F; indent level |
|
2004
|
|
|
$indent = (0x0F & ord($recordData{8})) >> 0; |
|
2005
|
|
|
$objStyle->getAlignment()->setIndent($indent); |
|
2006
|
|
|
|
|
2007
|
|
|
// bit: 4; mask: 0x10; 1 = shrink content to fit into cell |
|
2008
|
|
|
$shrinkToFit = (0x10 & ord($recordData{8})) >> 4; |
|
2009
|
|
|
switch ($shrinkToFit) { |
|
2010
|
|
|
case 0: |
|
2011
|
|
|
$objStyle->getAlignment()->setShrinkToFit(false); |
|
2012
|
|
|
break; |
|
2013
|
|
|
case 1: |
|
2014
|
|
|
$objStyle->getAlignment()->setShrinkToFit(true); |
|
2015
|
|
|
break; |
|
2016
|
|
|
} |
|
2017
|
|
|
|
|
2018
|
|
|
// offset: 9; size: 1; Flags used for attribute groups |
|
2019
|
|
|
|
|
2020
|
|
|
// offset: 10; size: 4; Cell border lines and background area |
|
2021
|
|
|
// bit: 3-0; mask: 0x0000000F; left style |
|
2022
|
|
View Code Duplication |
if ($bordersLeftStyle = self::_mapBorderStyle((0x0000000F & self::_GetInt4d($recordData, 10)) >> 0)) { |
|
2023
|
|
|
$objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle); |
|
2024
|
|
|
} |
|
2025
|
|
|
// bit: 7-4; mask: 0x000000F0; right style |
|
2026
|
|
View Code Duplication |
if ($bordersRightStyle = self::_mapBorderStyle((0x000000F0 & self::_GetInt4d($recordData, 10)) >> 4)) { |
|
2027
|
|
|
$objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle); |
|
2028
|
|
|
} |
|
2029
|
|
|
// bit: 11-8; mask: 0x00000F00; top style |
|
2030
|
|
View Code Duplication |
if ($bordersTopStyle = self::_mapBorderStyle((0x00000F00 & self::_GetInt4d($recordData, 10)) >> 8)) { |
|
2031
|
|
|
$objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle); |
|
2032
|
|
|
} |
|
2033
|
|
|
// bit: 15-12; mask: 0x0000F000; bottom style |
|
2034
|
|
View Code Duplication |
if ($bordersBottomStyle = self::_mapBorderStyle((0x0000F000 & self::_GetInt4d($recordData, 10)) >> 12)) { |
|
2035
|
|
|
$objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle); |
|
2036
|
|
|
} |
|
2037
|
|
|
// bit: 22-16; mask: 0x007F0000; left color |
|
2038
|
|
|
$objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::_GetInt4d($recordData, 10)) >> 16; |
|
|
|
|
|
|
2039
|
|
|
|
|
2040
|
|
|
// bit: 29-23; mask: 0x3F800000; right color |
|
2041
|
|
|
$objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::_GetInt4d($recordData, 10)) >> 23; |
|
2042
|
|
|
|
|
2043
|
|
|
// bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom |
|
2044
|
|
|
$diagonalDown = (0x40000000 & self::_GetInt4d($recordData, 10)) >> 30 ? |
|
2045
|
|
|
true : false; |
|
2046
|
|
|
|
|
2047
|
|
|
// bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right |
|
2048
|
|
|
$diagonalUp = (0x80000000 & self::_GetInt4d($recordData, 10)) >> 31 ? |
|
2049
|
|
|
true : false; |
|
2050
|
|
|
|
|
2051
|
|
View Code Duplication |
if ($diagonalUp == false && $diagonalDown == false) { |
|
|
|
|
|
|
2052
|
|
|
$objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_NONE); |
|
2053
|
|
|
} elseif ($diagonalUp == true && $diagonalDown == false) { |
|
|
|
|
|
|
2054
|
|
|
$objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_UP); |
|
2055
|
|
|
} elseif ($diagonalUp == false && $diagonalDown == true) { |
|
|
|
|
|
|
2056
|
|
|
$objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_DOWN); |
|
2057
|
|
|
} elseif ($diagonalUp == true && $diagonalDown == true) { |
|
|
|
|
|
|
2058
|
|
|
$objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_BOTH); |
|
2059
|
|
|
} |
|
2060
|
|
|
|
|
2061
|
|
|
// offset: 14; size: 4; |
|
2062
|
|
|
// bit: 6-0; mask: 0x0000007F; top color |
|
2063
|
|
|
$objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::_GetInt4d($recordData, 14)) >> 0; |
|
2064
|
|
|
|
|
2065
|
|
|
// bit: 13-7; mask: 0x00003F80; bottom color |
|
2066
|
|
|
$objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::_GetInt4d($recordData, 14)) >> 7; |
|
2067
|
|
|
|
|
2068
|
|
|
// bit: 20-14; mask: 0x001FC000; diagonal color |
|
2069
|
|
|
$objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::_GetInt4d($recordData, 14)) >> 14; |
|
2070
|
|
|
|
|
2071
|
|
|
// bit: 24-21; mask: 0x01E00000; diagonal style |
|
2072
|
|
View Code Duplication |
if ($bordersDiagonalStyle = self::_mapBorderStyle((0x01E00000 & self::_GetInt4d($recordData, 14)) >> 21)) { |
|
2073
|
|
|
$objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle); |
|
2074
|
|
|
} |
|
2075
|
|
|
|
|
2076
|
|
|
// bit: 31-26; mask: 0xFC000000 fill pattern |
|
2077
|
|
|
if ($fillType = self::_mapFillPattern((0xFC000000 & self::_GetInt4d($recordData, 14)) >> 26)) { |
|
2078
|
|
|
$objStyle->getFill()->setFillType($fillType); |
|
2079
|
|
|
} |
|
2080
|
|
|
// offset: 18; size: 2; pattern and background colour |
|
2081
|
|
|
// bit: 6-0; mask: 0x007F; color index for pattern color |
|
2082
|
|
|
$objStyle->getFill()->startcolorIndex = (0x007F & self::_GetInt2d($recordData, 18)) >> 0; |
|
|
|
|
|
|
2083
|
|
|
|
|
2084
|
|
|
// bit: 13-7; mask: 0x3F80; color index for pattern background |
|
2085
|
|
|
$objStyle->getFill()->endcolorIndex = (0x3F80 & self::_GetInt2d($recordData, 18)) >> 7; |
|
|
|
|
|
|
2086
|
|
|
} else { |
|
2087
|
|
|
// BIFF5 |
|
2088
|
|
|
|
|
2089
|
|
|
// offset: 7; size: 1; Text orientation and flags |
|
2090
|
|
|
$orientationAndFlags = ord($recordData{7}); |
|
2091
|
|
|
|
|
2092
|
|
|
// bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation |
|
2093
|
|
|
$xfOrientation = (0x03 & $orientationAndFlags) >> 0; |
|
2094
|
|
|
switch ($xfOrientation) { |
|
2095
|
|
|
case 0: |
|
2096
|
|
|
$objStyle->getAlignment()->setTextRotation(0); |
|
2097
|
|
|
break; |
|
2098
|
|
|
case 1: |
|
2099
|
|
|
$objStyle->getAlignment()->setTextRotation(-165); |
|
2100
|
|
|
break; |
|
2101
|
|
|
case 2: |
|
2102
|
|
|
$objStyle->getAlignment()->setTextRotation(90); |
|
2103
|
|
|
break; |
|
2104
|
|
|
case 3: |
|
2105
|
|
|
$objStyle->getAlignment()->setTextRotation(-90); |
|
2106
|
|
|
break; |
|
2107
|
|
|
} |
|
2108
|
|
|
|
|
2109
|
|
|
// offset: 8; size: 4; cell border lines and background area |
|
2110
|
|
|
$borderAndBackground = self::_GetInt4d($recordData, 8); |
|
2111
|
|
|
|
|
2112
|
|
|
// bit: 6-0; mask: 0x0000007F; color index for pattern color |
|
2113
|
|
|
$objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0; |
|
2114
|
|
|
|
|
2115
|
|
|
// bit: 13-7; mask: 0x00003F80; color index for pattern background |
|
2116
|
|
|
$objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7; |
|
2117
|
|
|
|
|
2118
|
|
|
// bit: 21-16; mask: 0x003F0000; fill pattern |
|
2119
|
|
|
$objStyle->getFill()->setFillType(self::_mapFillPattern((0x003F0000 & $borderAndBackground) >> 16)); |
|
2120
|
|
|
|
|
2121
|
|
|
// bit: 24-22; mask: 0x01C00000; bottom line style |
|
2122
|
|
|
$objStyle->getBorders()->getBottom()->setBorderStyle(self::_mapBorderStyle((0x01C00000 & $borderAndBackground) >> 22)); |
|
2123
|
|
|
|
|
2124
|
|
|
// bit: 31-25; mask: 0xFE000000; bottom line color |
|
2125
|
|
|
$objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25; |
|
2126
|
|
|
|
|
2127
|
|
|
// offset: 12; size: 4; cell border lines |
|
2128
|
|
|
$borderLines = self::_GetInt4d($recordData, 12); |
|
2129
|
|
|
|
|
2130
|
|
|
// bit: 2-0; mask: 0x00000007; top line style |
|
2131
|
|
|
$objStyle->getBorders()->getTop()->setBorderStyle(self::_mapBorderStyle((0x00000007 & $borderLines) >> 0)); |
|
2132
|
|
|
|
|
2133
|
|
|
// bit: 5-3; mask: 0x00000038; left line style |
|
2134
|
|
|
$objStyle->getBorders()->getLeft()->setBorderStyle(self::_mapBorderStyle((0x00000038 & $borderLines) >> 3)); |
|
2135
|
|
|
|
|
2136
|
|
|
// bit: 8-6; mask: 0x000001C0; right line style |
|
2137
|
|
|
$objStyle->getBorders()->getRight()->setBorderStyle(self::_mapBorderStyle((0x000001C0 & $borderLines) >> 6)); |
|
2138
|
|
|
|
|
2139
|
|
|
// bit: 15-9; mask: 0x0000FE00; top line color index |
|
2140
|
|
|
$objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9; |
|
2141
|
|
|
|
|
2142
|
|
|
// bit: 22-16; mask: 0x007F0000; left line color index |
|
2143
|
|
|
$objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16; |
|
2144
|
|
|
|
|
2145
|
|
|
// bit: 29-23; mask: 0x3F800000; right line color index |
|
2146
|
|
|
$objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23; |
|
2147
|
|
|
} |
|
2148
|
|
|
|
|
2149
|
|
|
// add cellStyleXf or cellXf and update mapping |
|
2150
|
|
|
if ($isCellStyleXf) { |
|
2151
|
|
|
// we only read one style XF record which is always the first |
|
2152
|
|
|
if ($this->_xfIndex == 0) { |
|
2153
|
|
|
$this->_phpExcel->addCellStyleXf($objStyle); |
|
2154
|
|
|
$this->_mapCellStyleXfIndex[$this->_xfIndex] = 0; |
|
2155
|
|
|
} |
|
2156
|
|
|
} else { |
|
2157
|
|
|
// we read all cell XF records |
|
2158
|
|
|
$this->_phpExcel->addCellXf($objStyle); |
|
2159
|
|
|
$this->_mapCellXfIndex[$this->_xfIndex] = count($this->_phpExcel->getCellXfCollection()) - 1; |
|
2160
|
|
|
} |
|
2161
|
|
|
|
|
2162
|
|
|
// update XF index for when we read next record |
|
2163
|
|
|
++$this->_xfIndex; |
|
2164
|
|
|
} |
|
2165
|
|
|
} |
|
2166
|
|
|
|
|
2167
|
|
|
|
|
2168
|
|
|
/** |
|
2169
|
|
|
* |
|
2170
|
|
|
*/ |
|
2171
|
|
|
private function _readXfExt() |
|
2172
|
|
|
{ |
|
2173
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2174
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2175
|
|
|
|
|
2176
|
|
|
// move stream pointer to next record |
|
2177
|
|
|
$this->_pos += 4 + $length; |
|
2178
|
|
|
|
|
2179
|
|
|
if (!$this->_readDataOnly) { |
|
2180
|
|
|
// offset: 0; size: 2; 0x087D = repeated header |
|
2181
|
|
|
|
|
2182
|
|
|
// offset: 2; size: 2 |
|
2183
|
|
|
|
|
2184
|
|
|
// offset: 4; size: 8; not used |
|
2185
|
|
|
|
|
2186
|
|
|
// offset: 12; size: 2; record version |
|
2187
|
|
|
|
|
2188
|
|
|
// offset: 14; size: 2; index to XF record which this record modifies |
|
2189
|
|
|
$ixfe = self::_GetInt2d($recordData, 14); |
|
2190
|
|
|
|
|
2191
|
|
|
// offset: 16; size: 2; not used |
|
2192
|
|
|
|
|
2193
|
|
|
// offset: 18; size: 2; number of extension properties that follow |
|
2194
|
|
|
$cexts = self::_GetInt2d($recordData, 18); |
|
2195
|
|
|
|
|
2196
|
|
|
// start reading the actual extension data |
|
2197
|
|
|
$offset = 20; |
|
2198
|
|
|
while ($offset < $length) { |
|
2199
|
|
|
// extension type |
|
2200
|
|
|
$extType = self::_GetInt2d($recordData, $offset); |
|
2201
|
|
|
|
|
2202
|
|
|
// extension length |
|
2203
|
|
|
$cb = self::_GetInt2d($recordData, $offset + 2); |
|
2204
|
|
|
|
|
2205
|
|
|
// extension data |
|
2206
|
|
|
$extData = substr($recordData, $offset + 4, $cb); |
|
2207
|
|
|
|
|
2208
|
|
|
switch ($extType) { |
|
2209
|
|
View Code Duplication |
case 4: // fill start color |
|
2210
|
|
|
$xclfType = self::_GetInt2d($extData, 0); // color type |
|
2211
|
|
|
$xclrValue = substr($extData, 4, 4); // color value (value based on color type) |
|
2212
|
|
|
|
|
2213
|
|
|
if ($xclfType == 2) { |
|
2214
|
|
|
$rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2})); |
|
2215
|
|
|
|
|
2216
|
|
|
// modify the relevant style property |
|
2217
|
|
|
if ( isset($this->_mapCellXfIndex[$ixfe]) ) { |
|
2218
|
|
|
$fill = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFill(); |
|
2219
|
|
|
$fill->getStartColor()->setRGB($rgb); |
|
2220
|
|
|
unset($fill->startcolorIndex); // normal color index does not apply, discard |
|
2221
|
|
|
} |
|
2222
|
|
|
} |
|
2223
|
|
|
break; |
|
2224
|
|
|
|
|
2225
|
|
View Code Duplication |
case 5: // fill end color |
|
2226
|
|
|
$xclfType = self::_GetInt2d($extData, 0); // color type |
|
2227
|
|
|
$xclrValue = substr($extData, 4, 4); // color value (value based on color type) |
|
2228
|
|
|
|
|
2229
|
|
|
if ($xclfType == 2) { |
|
2230
|
|
|
$rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2})); |
|
2231
|
|
|
|
|
2232
|
|
|
// modify the relevant style property |
|
2233
|
|
|
if ( isset($this->_mapCellXfIndex[$ixfe]) ) { |
|
2234
|
|
|
$fill = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFill(); |
|
2235
|
|
|
$fill->getEndColor()->setRGB($rgb); |
|
2236
|
|
|
unset($fill->endcolorIndex); // normal color index does not apply, discard |
|
2237
|
|
|
} |
|
2238
|
|
|
} |
|
2239
|
|
|
break; |
|
2240
|
|
|
|
|
2241
|
|
View Code Duplication |
case 7: // border color top |
|
2242
|
|
|
$xclfType = self::_GetInt2d($extData, 0); // color type |
|
2243
|
|
|
$xclrValue = substr($extData, 4, 4); // color value (value based on color type) |
|
2244
|
|
|
|
|
2245
|
|
|
if ($xclfType == 2) { |
|
2246
|
|
|
$rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2})); |
|
2247
|
|
|
|
|
2248
|
|
|
// modify the relevant style property |
|
2249
|
|
|
if ( isset($this->_mapCellXfIndex[$ixfe]) ) { |
|
2250
|
|
|
$top = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getTop(); |
|
2251
|
|
|
$top->getColor()->setRGB($rgb); |
|
2252
|
|
|
unset($top->colorIndex); // normal color index does not apply, discard |
|
2253
|
|
|
} |
|
2254
|
|
|
} |
|
2255
|
|
|
break; |
|
2256
|
|
|
|
|
2257
|
|
View Code Duplication |
case 8: // border color bottom |
|
2258
|
|
|
$xclfType = self::_GetInt2d($extData, 0); // color type |
|
2259
|
|
|
$xclrValue = substr($extData, 4, 4); // color value (value based on color type) |
|
2260
|
|
|
|
|
2261
|
|
|
if ($xclfType == 2) { |
|
2262
|
|
|
$rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2})); |
|
2263
|
|
|
|
|
2264
|
|
|
// modify the relevant style property |
|
2265
|
|
|
if ( isset($this->_mapCellXfIndex[$ixfe]) ) { |
|
2266
|
|
|
$bottom = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getBottom(); |
|
2267
|
|
|
$bottom->getColor()->setRGB($rgb); |
|
2268
|
|
|
unset($bottom->colorIndex); // normal color index does not apply, discard |
|
2269
|
|
|
} |
|
2270
|
|
|
} |
|
2271
|
|
|
break; |
|
2272
|
|
|
|
|
2273
|
|
View Code Duplication |
case 9: // border color left |
|
2274
|
|
|
$xclfType = self::_GetInt2d($extData, 0); // color type |
|
2275
|
|
|
$xclrValue = substr($extData, 4, 4); // color value (value based on color type) |
|
2276
|
|
|
|
|
2277
|
|
|
if ($xclfType == 2) { |
|
2278
|
|
|
$rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2})); |
|
2279
|
|
|
|
|
2280
|
|
|
// modify the relevant style property |
|
2281
|
|
|
if ( isset($this->_mapCellXfIndex[$ixfe]) ) { |
|
2282
|
|
|
$left = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getLeft(); |
|
2283
|
|
|
$left->getColor()->setRGB($rgb); |
|
2284
|
|
|
unset($left->colorIndex); // normal color index does not apply, discard |
|
2285
|
|
|
} |
|
2286
|
|
|
} |
|
2287
|
|
|
break; |
|
2288
|
|
|
|
|
2289
|
|
View Code Duplication |
case 10: // border color right |
|
2290
|
|
|
$xclfType = self::_GetInt2d($extData, 0); // color type |
|
2291
|
|
|
$xclrValue = substr($extData, 4, 4); // color value (value based on color type) |
|
2292
|
|
|
|
|
2293
|
|
|
if ($xclfType == 2) { |
|
2294
|
|
|
$rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2})); |
|
2295
|
|
|
|
|
2296
|
|
|
// modify the relevant style property |
|
2297
|
|
|
if ( isset($this->_mapCellXfIndex[$ixfe]) ) { |
|
2298
|
|
|
$right = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getRight(); |
|
2299
|
|
|
$right->getColor()->setRGB($rgb); |
|
2300
|
|
|
unset($right->colorIndex); // normal color index does not apply, discard |
|
2301
|
|
|
} |
|
2302
|
|
|
} |
|
2303
|
|
|
break; |
|
2304
|
|
|
|
|
2305
|
|
View Code Duplication |
case 11: // border color diagonal |
|
2306
|
|
|
$xclfType = self::_GetInt2d($extData, 0); // color type |
|
2307
|
|
|
$xclrValue = substr($extData, 4, 4); // color value (value based on color type) |
|
2308
|
|
|
|
|
2309
|
|
|
if ($xclfType == 2) { |
|
2310
|
|
|
$rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2})); |
|
2311
|
|
|
|
|
2312
|
|
|
// modify the relevant style property |
|
2313
|
|
|
if ( isset($this->_mapCellXfIndex[$ixfe]) ) { |
|
2314
|
|
|
$diagonal = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getDiagonal(); |
|
2315
|
|
|
$diagonal->getColor()->setRGB($rgb); |
|
2316
|
|
|
unset($diagonal->colorIndex); // normal color index does not apply, discard |
|
2317
|
|
|
} |
|
2318
|
|
|
} |
|
2319
|
|
|
break; |
|
2320
|
|
|
|
|
2321
|
|
View Code Duplication |
case 13: // font color |
|
2322
|
|
|
$xclfType = self::_GetInt2d($extData, 0); // color type |
|
2323
|
|
|
$xclrValue = substr($extData, 4, 4); // color value (value based on color type) |
|
2324
|
|
|
|
|
2325
|
|
|
if ($xclfType == 2) { |
|
2326
|
|
|
$rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2})); |
|
2327
|
|
|
|
|
2328
|
|
|
// modify the relevant style property |
|
2329
|
|
|
if ( isset($this->_mapCellXfIndex[$ixfe]) ) { |
|
2330
|
|
|
$font = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFont(); |
|
2331
|
|
|
$font->getColor()->setRGB($rgb); |
|
2332
|
|
|
unset($font->colorIndex); // normal color index does not apply, discard |
|
2333
|
|
|
} |
|
2334
|
|
|
} |
|
2335
|
|
|
break; |
|
2336
|
|
|
} |
|
2337
|
|
|
|
|
2338
|
|
|
$offset += $cb; |
|
2339
|
|
|
} |
|
2340
|
|
|
} |
|
2341
|
|
|
|
|
2342
|
|
|
} |
|
2343
|
|
|
|
|
2344
|
|
|
|
|
2345
|
|
|
/** |
|
2346
|
|
|
* Read STYLE record |
|
2347
|
|
|
*/ |
|
2348
|
|
|
private function _readStyle() |
|
2349
|
|
|
{ |
|
2350
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2351
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2352
|
|
|
|
|
2353
|
|
|
// move stream pointer to next record |
|
2354
|
|
|
$this->_pos += 4 + $length; |
|
2355
|
|
|
|
|
2356
|
|
|
if (!$this->_readDataOnly) { |
|
2357
|
|
|
// offset: 0; size: 2; index to XF record and flag for built-in style |
|
2358
|
|
|
$ixfe = self::_GetInt2d($recordData, 0); |
|
2359
|
|
|
|
|
2360
|
|
|
// bit: 11-0; mask 0x0FFF; index to XF record |
|
2361
|
|
|
$xfIndex = (0x0FFF & $ixfe) >> 0; |
|
2362
|
|
|
|
|
2363
|
|
|
// bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style |
|
2364
|
|
|
$isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15); |
|
2365
|
|
|
|
|
2366
|
|
|
if ($isBuiltIn) { |
|
2367
|
|
|
// offset: 2; size: 1; identifier for built-in style |
|
2368
|
|
|
$builtInId = ord($recordData{2}); |
|
2369
|
|
|
|
|
2370
|
|
|
switch ($builtInId) { |
|
2371
|
|
|
case 0x00: |
|
2372
|
|
|
// currently, we are not using this for anything |
|
2373
|
|
|
break; |
|
2374
|
|
|
|
|
2375
|
|
|
default: |
|
2376
|
|
|
break; |
|
2377
|
|
|
} |
|
2378
|
|
|
|
|
2379
|
|
|
} else { |
|
|
|
|
|
|
2380
|
|
|
// user-defined; not supported by PHPExcel |
|
2381
|
|
|
} |
|
2382
|
|
|
} |
|
2383
|
|
|
} |
|
2384
|
|
|
|
|
2385
|
|
|
|
|
2386
|
|
|
/** |
|
2387
|
|
|
* Read PALETTE record |
|
2388
|
|
|
*/ |
|
2389
|
|
|
private function _readPalette() |
|
2390
|
|
|
{ |
|
2391
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2392
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2393
|
|
|
|
|
2394
|
|
|
// move stream pointer to next record |
|
2395
|
|
|
$this->_pos += 4 + $length; |
|
2396
|
|
|
|
|
2397
|
|
|
if (!$this->_readDataOnly) { |
|
2398
|
|
|
// offset: 0; size: 2; number of following colors |
|
2399
|
|
|
$nm = self::_GetInt2d($recordData, 0); |
|
2400
|
|
|
|
|
2401
|
|
|
// list of RGB colors |
|
2402
|
|
|
for ($i = 0; $i < $nm; ++$i) { |
|
2403
|
|
|
$rgb = substr($recordData, 2 + 4 * $i, 4); |
|
2404
|
|
|
$this->_palette[] = self::_readRGB($rgb); |
|
2405
|
|
|
} |
|
2406
|
|
|
} |
|
2407
|
|
|
} |
|
2408
|
|
|
|
|
2409
|
|
|
|
|
2410
|
|
|
/** |
|
2411
|
|
|
* SHEET |
|
2412
|
|
|
* |
|
2413
|
|
|
* This record is located in the Workbook Globals |
|
2414
|
|
|
* Substream and represents a sheet inside the workbook. |
|
2415
|
|
|
* One SHEET record is written for each sheet. It stores the |
|
2416
|
|
|
* sheet name and a stream offset to the BOF record of the |
|
2417
|
|
|
* respective Sheet Substream within the Workbook Stream. |
|
2418
|
|
|
* |
|
2419
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
2420
|
|
|
* Excel File Format" |
|
2421
|
|
|
*/ |
|
2422
|
|
|
private function _readSheet() |
|
2423
|
|
|
{ |
|
2424
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2425
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2426
|
|
|
|
|
2427
|
|
|
// move stream pointer to next record |
|
2428
|
|
|
$this->_pos += 4 + $length; |
|
2429
|
|
|
|
|
2430
|
|
|
// offset: 0; size: 4; absolute stream position of the BOF record of the sheet |
|
2431
|
|
|
$rec_offset = self::_GetInt4d($recordData, 0); |
|
2432
|
|
|
|
|
2433
|
|
|
// offset: 4; size: 1; sheet state |
|
2434
|
|
|
switch (ord($recordData{4})) { |
|
2435
|
|
|
case 0x00: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VISIBLE; break; |
|
2436
|
|
|
case 0x01: $sheetState = PHPExcel_Worksheet::SHEETSTATE_HIDDEN; break; |
|
2437
|
|
|
case 0x02: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VERYHIDDEN; break; |
|
2438
|
|
|
default: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VISIBLE; break; |
|
|
|
|
|
|
2439
|
|
|
} |
|
2440
|
|
|
|
|
2441
|
|
|
// offset: 5; size: 1; sheet type |
|
2442
|
|
|
$sheetType = ord($recordData{5}); |
|
2443
|
|
|
|
|
2444
|
|
|
// offset: 6; size: var; sheet name |
|
2445
|
|
View Code Duplication |
if ($this->_version == self::XLS_BIFF8) { |
|
2446
|
|
|
$string = self::_readUnicodeStringShort(substr($recordData, 6)); |
|
2447
|
|
|
$rec_name = $string['value']; |
|
2448
|
|
|
} elseif ($this->_version == self::XLS_BIFF7) { |
|
2449
|
|
|
$string = $this->_readByteStringShort(substr($recordData, 6)); |
|
2450
|
|
|
$rec_name = $string['value']; |
|
2451
|
|
|
} |
|
2452
|
|
|
|
|
2453
|
|
|
$this->_sheets[] = array( |
|
2454
|
|
|
'name' => $rec_name, |
|
|
|
|
|
|
2455
|
|
|
'offset' => $rec_offset, |
|
2456
|
|
|
'sheetState' => $sheetState, |
|
2457
|
|
|
'sheetType' => $sheetType, |
|
2458
|
|
|
); |
|
2459
|
|
|
} |
|
2460
|
|
|
|
|
2461
|
|
|
|
|
2462
|
|
|
/** |
|
2463
|
|
|
* Read EXTERNALBOOK record |
|
2464
|
|
|
*/ |
|
2465
|
|
|
private function _readExternalBook() |
|
2466
|
|
|
{ |
|
2467
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2468
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2469
|
|
|
|
|
2470
|
|
|
// move stream pointer to next record |
|
2471
|
|
|
$this->_pos += 4 + $length; |
|
2472
|
|
|
|
|
2473
|
|
|
// offset within record data |
|
2474
|
|
|
$offset = 0; |
|
2475
|
|
|
|
|
2476
|
|
|
// there are 4 types of records |
|
2477
|
|
|
if (strlen($recordData) > 4) { |
|
2478
|
|
|
// external reference |
|
2479
|
|
|
// offset: 0; size: 2; number of sheet names ($nm) |
|
2480
|
|
|
$nm = self::_GetInt2d($recordData, 0); |
|
2481
|
|
|
$offset += 2; |
|
2482
|
|
|
|
|
2483
|
|
|
// offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length) |
|
2484
|
|
|
$encodedUrlString = self::_readUnicodeStringLong(substr($recordData, 2)); |
|
2485
|
|
|
$offset += $encodedUrlString['size']; |
|
2486
|
|
|
|
|
2487
|
|
|
// offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length) |
|
2488
|
|
|
$externalSheetNames = array(); |
|
2489
|
|
|
for ($i = 0; $i < $nm; ++$i) { |
|
2490
|
|
|
$externalSheetNameString = self::_readUnicodeStringLong(substr($recordData, $offset)); |
|
2491
|
|
|
$externalSheetNames[] = $externalSheetNameString['value']; |
|
2492
|
|
|
$offset += $externalSheetNameString['size']; |
|
2493
|
|
|
} |
|
2494
|
|
|
|
|
2495
|
|
|
// store the record data |
|
2496
|
|
|
$this->_externalBooks[] = array( |
|
2497
|
|
|
'type' => 'external', |
|
2498
|
|
|
'encodedUrl' => $encodedUrlString['value'], |
|
2499
|
|
|
'externalSheetNames' => $externalSheetNames, |
|
2500
|
|
|
); |
|
2501
|
|
|
|
|
2502
|
|
|
} elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) { |
|
2503
|
|
|
// internal reference |
|
2504
|
|
|
// offset: 0; size: 2; number of sheet in this document |
|
2505
|
|
|
// offset: 2; size: 2; 0x01 0x04 |
|
2506
|
|
|
$this->_externalBooks[] = array( |
|
2507
|
|
|
'type' => 'internal', |
|
2508
|
|
|
); |
|
2509
|
|
|
} elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) { |
|
2510
|
|
|
// add-in function |
|
2511
|
|
|
// offset: 0; size: 2; 0x0001 |
|
2512
|
|
|
$this->_externalBooks[] = array( |
|
2513
|
|
|
'type' => 'addInFunction', |
|
2514
|
|
|
); |
|
2515
|
|
|
} elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) { |
|
2516
|
|
|
// DDE links, OLE links |
|
2517
|
|
|
// offset: 0; size: 2; 0x0000 |
|
2518
|
|
|
// offset: 2; size: var; encoded source document name |
|
2519
|
|
|
$this->_externalBooks[] = array( |
|
2520
|
|
|
'type' => 'DDEorOLE', |
|
2521
|
|
|
); |
|
2522
|
|
|
} |
|
2523
|
|
|
} |
|
2524
|
|
|
|
|
2525
|
|
|
|
|
2526
|
|
|
/** |
|
2527
|
|
|
* Read EXTERNNAME record. |
|
2528
|
|
|
*/ |
|
2529
|
|
|
private function _readExternName() |
|
2530
|
|
|
{ |
|
2531
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2532
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2533
|
|
|
|
|
2534
|
|
|
// move stream pointer to next record |
|
2535
|
|
|
$this->_pos += 4 + $length; |
|
2536
|
|
|
|
|
2537
|
|
|
// external sheet references provided for named cells |
|
2538
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
2539
|
|
|
// offset: 0; size: 2; options |
|
2540
|
|
|
$options = self::_GetInt2d($recordData, 0); |
|
2541
|
|
|
|
|
2542
|
|
|
// offset: 2; size: 2; |
|
2543
|
|
|
|
|
2544
|
|
|
// offset: 4; size: 2; not used |
|
2545
|
|
|
|
|
2546
|
|
|
// offset: 6; size: var |
|
2547
|
|
|
$nameString = self::_readUnicodeStringShort(substr($recordData, 6)); |
|
2548
|
|
|
|
|
2549
|
|
|
// offset: var; size: var; formula data |
|
2550
|
|
|
$offset = 6 + $nameString['size']; |
|
2551
|
|
|
$formula = $this->_getFormulaFromStructure(substr($recordData, $offset)); |
|
2552
|
|
|
|
|
2553
|
|
|
$this->_externalNames[] = array( |
|
2554
|
|
|
'name' => $nameString['value'], |
|
2555
|
|
|
'formula' => $formula, |
|
2556
|
|
|
); |
|
2557
|
|
|
} |
|
2558
|
|
|
} |
|
2559
|
|
|
|
|
2560
|
|
|
|
|
2561
|
|
|
/** |
|
2562
|
|
|
* Read EXTERNSHEET record |
|
2563
|
|
|
*/ |
|
2564
|
|
|
private function _readExternSheet() |
|
2565
|
|
|
{ |
|
2566
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2567
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2568
|
|
|
|
|
2569
|
|
|
// move stream pointer to next record |
|
2570
|
|
|
$this->_pos += 4 + $length; |
|
2571
|
|
|
|
|
2572
|
|
|
// external sheet references provided for named cells |
|
2573
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
2574
|
|
|
// offset: 0; size: 2; number of following ref structures |
|
2575
|
|
|
$nm = self::_GetInt2d($recordData, 0); |
|
2576
|
|
|
for ($i = 0; $i < $nm; ++$i) { |
|
2577
|
|
|
$this->_ref[] = array( |
|
2578
|
|
|
// offset: 2 + 6 * $i; index to EXTERNALBOOK record |
|
2579
|
|
|
'externalBookIndex' => self::_GetInt2d($recordData, 2 + 6 * $i), |
|
2580
|
|
|
// offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record |
|
2581
|
|
|
'firstSheetIndex' => self::_GetInt2d($recordData, 4 + 6 * $i), |
|
2582
|
|
|
// offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record |
|
2583
|
|
|
'lastSheetIndex' => self::_GetInt2d($recordData, 6 + 6 * $i), |
|
2584
|
|
|
); |
|
2585
|
|
|
} |
|
2586
|
|
|
} |
|
2587
|
|
|
} |
|
2588
|
|
|
|
|
2589
|
|
|
|
|
2590
|
|
|
/** |
|
2591
|
|
|
* DEFINEDNAME |
|
2592
|
|
|
* |
|
2593
|
|
|
* This record is part of a Link Table. It contains the name |
|
2594
|
|
|
* and the token array of an internal defined name. Token |
|
2595
|
|
|
* arrays of defined names contain tokens with aberrant |
|
2596
|
|
|
* token classes. |
|
2597
|
|
|
* |
|
2598
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
2599
|
|
|
* Excel File Format" |
|
2600
|
|
|
*/ |
|
2601
|
|
|
private function _readDefinedName() |
|
2602
|
|
|
{ |
|
2603
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2604
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2605
|
|
|
|
|
2606
|
|
|
// move stream pointer to next record |
|
2607
|
|
|
$this->_pos += 4 + $length; |
|
2608
|
|
|
|
|
2609
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
2610
|
|
|
// retrieves named cells |
|
2611
|
|
|
|
|
2612
|
|
|
// offset: 0; size: 2; option flags |
|
2613
|
|
|
$opts = self::_GetInt2d($recordData, 0); |
|
2614
|
|
|
|
|
2615
|
|
|
// bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name |
|
2616
|
|
|
$isBuiltInName = (0x0020 & $opts) >> 5; |
|
2617
|
|
|
|
|
2618
|
|
|
// offset: 2; size: 1; keyboard shortcut |
|
2619
|
|
|
|
|
2620
|
|
|
// offset: 3; size: 1; length of the name (character count) |
|
2621
|
|
|
$nlen = ord($recordData{3}); |
|
2622
|
|
|
|
|
2623
|
|
|
// offset: 4; size: 2; size of the formula data (it can happen that this is zero) |
|
2624
|
|
|
// note: there can also be additional data, this is not included in $flen |
|
2625
|
|
|
$flen = self::_GetInt2d($recordData, 4); |
|
2626
|
|
|
|
|
2627
|
|
|
// offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based) |
|
2628
|
|
|
$scope = self::_GetInt2d($recordData, 8); |
|
2629
|
|
|
|
|
2630
|
|
|
// offset: 14; size: var; Name (Unicode string without length field) |
|
2631
|
|
|
$string = self::_readUnicodeString(substr($recordData, 14), $nlen); |
|
2632
|
|
|
|
|
2633
|
|
|
// offset: var; size: $flen; formula data |
|
2634
|
|
|
$offset = 14 + $string['size']; |
|
2635
|
|
|
$formulaStructure = pack('v', $flen) . substr($recordData, $offset); |
|
2636
|
|
|
|
|
2637
|
|
|
try { |
|
2638
|
|
|
$formula = $this->_getFormulaFromStructure($formulaStructure); |
|
2639
|
|
|
} catch (Exception $e) { |
|
2640
|
|
|
$formula = ''; |
|
2641
|
|
|
} |
|
2642
|
|
|
|
|
2643
|
|
|
$this->_definedname[] = array( |
|
2644
|
|
|
'isBuiltInName' => $isBuiltInName, |
|
2645
|
|
|
'name' => $string['value'], |
|
2646
|
|
|
'formula' => $formula, |
|
2647
|
|
|
'scope' => $scope, |
|
2648
|
|
|
); |
|
2649
|
|
|
} |
|
2650
|
|
|
} |
|
2651
|
|
|
|
|
2652
|
|
|
|
|
2653
|
|
|
/** |
|
2654
|
|
|
* Read MSODRAWINGGROUP record |
|
2655
|
|
|
*/ |
|
2656
|
|
View Code Duplication |
private function _readMsoDrawingGroup() |
|
2657
|
|
|
{ |
|
2658
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2659
|
|
|
|
|
2660
|
|
|
// get spliced record data |
|
2661
|
|
|
$splicedRecordData = $this->_getSplicedRecordData(); |
|
2662
|
|
|
$recordData = $splicedRecordData['recordData']; |
|
2663
|
|
|
|
|
2664
|
|
|
$this->_drawingGroupData .= $recordData; |
|
2665
|
|
|
} |
|
2666
|
|
|
|
|
2667
|
|
|
|
|
2668
|
|
|
/** |
|
2669
|
|
|
* SST - Shared String Table |
|
2670
|
|
|
* |
|
2671
|
|
|
* This record contains a list of all strings used anywhere |
|
2672
|
|
|
* in the workbook. Each string occurs only once. The |
|
2673
|
|
|
* workbook uses indexes into the list to reference the |
|
2674
|
|
|
* strings. |
|
2675
|
|
|
* |
|
2676
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
2677
|
|
|
* Excel File Format" |
|
2678
|
|
|
**/ |
|
2679
|
|
|
private function _readSst() |
|
2680
|
|
|
{ |
|
2681
|
|
|
// offset within (spliced) record data |
|
2682
|
|
|
$pos = 0; |
|
2683
|
|
|
|
|
2684
|
|
|
// get spliced record data |
|
2685
|
|
|
$splicedRecordData = $this->_getSplicedRecordData(); |
|
2686
|
|
|
|
|
2687
|
|
|
$recordData = $splicedRecordData['recordData']; |
|
2688
|
|
|
$spliceOffsets = $splicedRecordData['spliceOffsets']; |
|
2689
|
|
|
|
|
2690
|
|
|
// offset: 0; size: 4; total number of strings in the workbook |
|
2691
|
|
|
$pos += 4; |
|
2692
|
|
|
|
|
2693
|
|
|
// offset: 4; size: 4; number of following strings ($nm) |
|
2694
|
|
|
$nm = self::_GetInt4d($recordData, 4); |
|
2695
|
|
|
$pos += 4; |
|
2696
|
|
|
|
|
2697
|
|
|
// loop through the Unicode strings (16-bit length) |
|
2698
|
|
|
for ($i = 0; $i < $nm; ++$i) { |
|
2699
|
|
|
|
|
2700
|
|
|
// number of characters in the Unicode string |
|
2701
|
|
|
$numChars = self::_GetInt2d($recordData, $pos); |
|
2702
|
|
|
$pos += 2; |
|
2703
|
|
|
|
|
2704
|
|
|
// option flags |
|
2705
|
|
|
$optionFlags = ord($recordData{$pos}); |
|
2706
|
|
|
++$pos; |
|
2707
|
|
|
|
|
2708
|
|
|
// bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed |
|
2709
|
|
|
$isCompressed = (($optionFlags & 0x01) == 0) ; |
|
2710
|
|
|
|
|
2711
|
|
|
// bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic |
|
2712
|
|
|
$hasAsian = (($optionFlags & 0x04) != 0); |
|
2713
|
|
|
|
|
2714
|
|
|
// bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text |
|
2715
|
|
|
$hasRichText = (($optionFlags & 0x08) != 0); |
|
2716
|
|
|
|
|
2717
|
|
|
if ($hasRichText) { |
|
2718
|
|
|
// number of Rich-Text formatting runs |
|
2719
|
|
|
$formattingRuns = self::_GetInt2d($recordData, $pos); |
|
2720
|
|
|
$pos += 2; |
|
2721
|
|
|
} |
|
2722
|
|
|
|
|
2723
|
|
|
if ($hasAsian) { |
|
2724
|
|
|
// size of Asian phonetic setting |
|
2725
|
|
|
$extendedRunLength = self::_GetInt4d($recordData, $pos); |
|
2726
|
|
|
$pos += 4; |
|
2727
|
|
|
} |
|
2728
|
|
|
|
|
2729
|
|
|
// expected byte length of character array if not split |
|
2730
|
|
|
$len = ($isCompressed) ? $numChars : $numChars * 2; |
|
2731
|
|
|
|
|
2732
|
|
|
// look up limit position |
|
2733
|
|
|
foreach ($spliceOffsets as $spliceOffset) { |
|
2734
|
|
|
// it can happen that the string is empty, therefore we need |
|
2735
|
|
|
// <= and not just < |
|
2736
|
|
|
if ($pos <= $spliceOffset) { |
|
2737
|
|
|
$limitpos = $spliceOffset; |
|
2738
|
|
|
break; |
|
2739
|
|
|
} |
|
2740
|
|
|
} |
|
2741
|
|
|
|
|
2742
|
|
|
if ($pos + $len <= $limitpos) { |
|
2743
|
|
|
// character array is not split between records |
|
2744
|
|
|
|
|
2745
|
|
|
$retstr = substr($recordData, $pos, $len); |
|
2746
|
|
|
$pos += $len; |
|
2747
|
|
|
|
|
2748
|
|
|
} else { |
|
2749
|
|
|
// character array is split between records |
|
2750
|
|
|
|
|
2751
|
|
|
// first part of character array |
|
2752
|
|
|
$retstr = substr($recordData, $pos, $limitpos - $pos); |
|
|
|
|
|
|
2753
|
|
|
|
|
2754
|
|
|
$bytesRead = $limitpos - $pos; |
|
2755
|
|
|
|
|
2756
|
|
|
// remaining characters in Unicode string |
|
2757
|
|
|
$charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2)); |
|
2758
|
|
|
|
|
2759
|
|
|
$pos = $limitpos; |
|
2760
|
|
|
|
|
2761
|
|
|
// keep reading the characters |
|
2762
|
|
|
while ($charsLeft > 0) { |
|
2763
|
|
|
|
|
2764
|
|
|
// look up next limit position, in case the string span more than one continue record |
|
2765
|
|
|
foreach ($spliceOffsets as $spliceOffset) { |
|
2766
|
|
|
if ($pos < $spliceOffset) { |
|
2767
|
|
|
$limitpos = $spliceOffset; |
|
2768
|
|
|
break; |
|
2769
|
|
|
} |
|
2770
|
|
|
} |
|
2771
|
|
|
|
|
2772
|
|
|
// repeated option flags |
|
2773
|
|
|
// OpenOffice.org documentation 5.21 |
|
2774
|
|
|
$option = ord($recordData{$pos}); |
|
2775
|
|
|
++$pos; |
|
2776
|
|
|
|
|
2777
|
|
|
if ($isCompressed && ($option == 0)) { |
|
2778
|
|
|
// 1st fragment compressed |
|
2779
|
|
|
// this fragment compressed |
|
2780
|
|
|
$len = min($charsLeft, $limitpos - $pos); |
|
2781
|
|
|
$retstr .= substr($recordData, $pos, $len); |
|
2782
|
|
|
$charsLeft -= $len; |
|
2783
|
|
|
$isCompressed = true; |
|
2784
|
|
|
|
|
2785
|
|
|
} elseif (!$isCompressed && ($option != 0)) { |
|
2786
|
|
|
// 1st fragment uncompressed |
|
2787
|
|
|
// this fragment uncompressed |
|
2788
|
|
|
$len = min($charsLeft * 2, $limitpos - $pos); |
|
2789
|
|
|
$retstr .= substr($recordData, $pos, $len); |
|
2790
|
|
|
$charsLeft -= $len / 2; |
|
2791
|
|
|
$isCompressed = false; |
|
2792
|
|
|
|
|
2793
|
|
|
} elseif (!$isCompressed && ($option == 0)) { |
|
2794
|
|
|
// 1st fragment uncompressed |
|
2795
|
|
|
// this fragment compressed |
|
2796
|
|
|
$len = min($charsLeft, $limitpos - $pos); |
|
2797
|
|
|
for ($j = 0; $j < $len; ++$j) { |
|
2798
|
|
|
$retstr .= $recordData{$pos + $j} . chr(0); |
|
2799
|
|
|
} |
|
2800
|
|
|
$charsLeft -= $len; |
|
2801
|
|
|
$isCompressed = false; |
|
2802
|
|
|
|
|
2803
|
|
|
} else { |
|
2804
|
|
|
// 1st fragment compressed |
|
2805
|
|
|
// this fragment uncompressed |
|
2806
|
|
|
$newstr = ''; |
|
2807
|
|
|
for ($j = 0; $j < strlen($retstr); ++$j) { |
|
2808
|
|
|
$newstr .= $retstr[$j] . chr(0); |
|
2809
|
|
|
} |
|
2810
|
|
|
$retstr = $newstr; |
|
2811
|
|
|
$len = min($charsLeft * 2, $limitpos - $pos); |
|
2812
|
|
|
$retstr .= substr($recordData, $pos, $len); |
|
2813
|
|
|
$charsLeft -= $len / 2; |
|
2814
|
|
|
$isCompressed = false; |
|
2815
|
|
|
} |
|
2816
|
|
|
|
|
2817
|
|
|
$pos += $len; |
|
2818
|
|
|
} |
|
2819
|
|
|
} |
|
2820
|
|
|
|
|
2821
|
|
|
// convert to UTF-8 |
|
2822
|
|
|
$retstr = self::_encodeUTF16($retstr, $isCompressed); |
|
2823
|
|
|
|
|
2824
|
|
|
// read additional Rich-Text information, if any |
|
2825
|
|
|
$fmtRuns = array(); |
|
2826
|
|
|
if ($hasRichText) { |
|
2827
|
|
|
// list of formatting runs |
|
2828
|
|
|
for ($j = 0; $j < $formattingRuns; ++$j) { |
|
|
|
|
|
|
2829
|
|
|
// first formatted character; zero-based |
|
2830
|
|
|
$charPos = self::_GetInt2d($recordData, $pos + $j * 4); |
|
2831
|
|
|
|
|
2832
|
|
|
// index to font record |
|
2833
|
|
|
$fontIndex = self::_GetInt2d($recordData, $pos + 2 + $j * 4); |
|
2834
|
|
|
|
|
2835
|
|
|
$fmtRuns[] = array( |
|
2836
|
|
|
'charPos' => $charPos, |
|
2837
|
|
|
'fontIndex' => $fontIndex, |
|
2838
|
|
|
); |
|
2839
|
|
|
} |
|
2840
|
|
|
$pos += 4 * $formattingRuns; |
|
2841
|
|
|
} |
|
2842
|
|
|
|
|
2843
|
|
|
// read additional Asian phonetics information, if any |
|
2844
|
|
|
if ($hasAsian) { |
|
2845
|
|
|
// For Asian phonetic settings, we skip the extended string data |
|
2846
|
|
|
$pos += $extendedRunLength; |
|
|
|
|
|
|
2847
|
|
|
} |
|
2848
|
|
|
|
|
2849
|
|
|
// store the shared sting |
|
2850
|
|
|
$this->_sst[] = array( |
|
2851
|
|
|
'value' => $retstr, |
|
2852
|
|
|
'fmtRuns' => $fmtRuns, |
|
2853
|
|
|
); |
|
2854
|
|
|
} |
|
2855
|
|
|
|
|
2856
|
|
|
// _getSplicedRecordData() takes care of moving current position in data stream |
|
2857
|
|
|
} |
|
2858
|
|
|
|
|
2859
|
|
|
|
|
2860
|
|
|
/** |
|
2861
|
|
|
* Read PRINTGRIDLINES record |
|
2862
|
|
|
*/ |
|
2863
|
|
|
private function _readPrintGridlines() |
|
2864
|
|
|
{ |
|
2865
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2866
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2867
|
|
|
|
|
2868
|
|
|
// move stream pointer to next record |
|
2869
|
|
|
$this->_pos += 4 + $length; |
|
2870
|
|
|
|
|
2871
|
|
|
if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) { |
|
2872
|
|
|
// offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines |
|
2873
|
|
|
$printGridlines = (bool) self::_GetInt2d($recordData, 0); |
|
2874
|
|
|
$this->_phpSheet->setPrintGridlines($printGridlines); |
|
2875
|
|
|
} |
|
2876
|
|
|
} |
|
2877
|
|
|
|
|
2878
|
|
|
|
|
2879
|
|
|
/** |
|
2880
|
|
|
* Read DEFAULTROWHEIGHT record |
|
2881
|
|
|
*/ |
|
2882
|
|
|
private function _readDefaultRowHeight() |
|
2883
|
|
|
{ |
|
2884
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2885
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2886
|
|
|
|
|
2887
|
|
|
// move stream pointer to next record |
|
2888
|
|
|
$this->_pos += 4 + $length; |
|
2889
|
|
|
|
|
2890
|
|
|
// offset: 0; size: 2; option flags |
|
2891
|
|
|
// offset: 2; size: 2; default height for unused rows, (twips 1/20 point) |
|
2892
|
|
|
$height = self::_GetInt2d($recordData, 2); |
|
2893
|
|
|
$this->_phpSheet->getDefaultRowDimension()->setRowHeight($height / 20); |
|
2894
|
|
|
} |
|
2895
|
|
|
|
|
2896
|
|
|
|
|
2897
|
|
|
/** |
|
2898
|
|
|
* Read SHEETPR record |
|
2899
|
|
|
*/ |
|
2900
|
|
|
private function _readSheetPr() |
|
2901
|
|
|
{ |
|
2902
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2903
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2904
|
|
|
|
|
2905
|
|
|
// move stream pointer to next record |
|
2906
|
|
|
$this->_pos += 4 + $length; |
|
2907
|
|
|
|
|
2908
|
|
|
// offset: 0; size: 2 |
|
2909
|
|
|
|
|
2910
|
|
|
// bit: 6; mask: 0x0040; 0 = outline buttons above outline group |
|
2911
|
|
|
$isSummaryBelow = (0x0040 & self::_GetInt2d($recordData, 0)) >> 6; |
|
2912
|
|
|
$this->_phpSheet->setShowSummaryBelow($isSummaryBelow); |
|
2913
|
|
|
|
|
2914
|
|
|
// bit: 7; mask: 0x0080; 0 = outline buttons left of outline group |
|
2915
|
|
|
$isSummaryRight = (0x0080 & self::_GetInt2d($recordData, 0)) >> 7; |
|
2916
|
|
|
$this->_phpSheet->setShowSummaryRight($isSummaryRight); |
|
2917
|
|
|
|
|
2918
|
|
|
// bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages |
|
2919
|
|
|
// this corresponds to radio button setting in page setup dialog in Excel |
|
2920
|
|
|
$this->_isFitToPages = (bool) ((0x0100 & self::_GetInt2d($recordData, 0)) >> 8); |
|
2921
|
|
|
} |
|
2922
|
|
|
|
|
2923
|
|
|
|
|
2924
|
|
|
/** |
|
2925
|
|
|
* Read HORIZONTALPAGEBREAKS record |
|
2926
|
|
|
*/ |
|
2927
|
|
View Code Duplication |
private function _readHorizontalPageBreaks() |
|
2928
|
|
|
{ |
|
2929
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2930
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2931
|
|
|
|
|
2932
|
|
|
// move stream pointer to next record |
|
2933
|
|
|
$this->_pos += 4 + $length; |
|
2934
|
|
|
|
|
2935
|
|
|
if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) { |
|
2936
|
|
|
|
|
2937
|
|
|
// offset: 0; size: 2; number of the following row index structures |
|
2938
|
|
|
$nm = self::_GetInt2d($recordData, 0); |
|
2939
|
|
|
|
|
2940
|
|
|
// offset: 2; size: 6 * $nm; list of $nm row index structures |
|
2941
|
|
|
for ($i = 0; $i < $nm; ++$i) { |
|
2942
|
|
|
$r = self::_GetInt2d($recordData, 2 + 6 * $i); |
|
2943
|
|
|
$cf = self::_GetInt2d($recordData, 2 + 6 * $i + 2); |
|
2944
|
|
|
$cl = self::_GetInt2d($recordData, 2 + 6 * $i + 4); |
|
2945
|
|
|
|
|
2946
|
|
|
// not sure why two column indexes are necessary? |
|
2947
|
|
|
$this->_phpSheet->setBreakByColumnAndRow($cf, $r, PHPExcel_Worksheet::BREAK_ROW); |
|
2948
|
|
|
} |
|
2949
|
|
|
} |
|
2950
|
|
|
} |
|
2951
|
|
|
|
|
2952
|
|
|
|
|
2953
|
|
|
/** |
|
2954
|
|
|
* Read VERTICALPAGEBREAKS record |
|
2955
|
|
|
*/ |
|
2956
|
|
View Code Duplication |
private function _readVerticalPageBreaks() |
|
2957
|
|
|
{ |
|
2958
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2959
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2960
|
|
|
|
|
2961
|
|
|
// move stream pointer to next record |
|
2962
|
|
|
$this->_pos += 4 + $length; |
|
2963
|
|
|
|
|
2964
|
|
|
if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) { |
|
2965
|
|
|
// offset: 0; size: 2; number of the following column index structures |
|
2966
|
|
|
$nm = self::_GetInt2d($recordData, 0); |
|
2967
|
|
|
|
|
2968
|
|
|
// offset: 2; size: 6 * $nm; list of $nm row index structures |
|
2969
|
|
|
for ($i = 0; $i < $nm; ++$i) { |
|
2970
|
|
|
$c = self::_GetInt2d($recordData, 2 + 6 * $i); |
|
2971
|
|
|
$rf = self::_GetInt2d($recordData, 2 + 6 * $i + 2); |
|
2972
|
|
|
$rl = self::_GetInt2d($recordData, 2 + 6 * $i + 4); |
|
2973
|
|
|
|
|
2974
|
|
|
// not sure why two row indexes are necessary? |
|
2975
|
|
|
$this->_phpSheet->setBreakByColumnAndRow($c, $rf, PHPExcel_Worksheet::BREAK_COLUMN); |
|
2976
|
|
|
} |
|
2977
|
|
|
} |
|
2978
|
|
|
} |
|
2979
|
|
|
|
|
2980
|
|
|
|
|
2981
|
|
|
/** |
|
2982
|
|
|
* Read HEADER record |
|
2983
|
|
|
*/ |
|
2984
|
|
View Code Duplication |
private function _readHeader() |
|
2985
|
|
|
{ |
|
2986
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
2987
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
2988
|
|
|
|
|
2989
|
|
|
// move stream pointer to next record |
|
2990
|
|
|
$this->_pos += 4 + $length; |
|
2991
|
|
|
|
|
2992
|
|
|
if (!$this->_readDataOnly) { |
|
2993
|
|
|
// offset: 0; size: var |
|
2994
|
|
|
// realized that $recordData can be empty even when record exists |
|
2995
|
|
|
if ($recordData) { |
|
2996
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
2997
|
|
|
$string = self::_readUnicodeStringLong($recordData); |
|
2998
|
|
|
} else { |
|
2999
|
|
|
$string = $this->_readByteStringShort($recordData); |
|
3000
|
|
|
} |
|
3001
|
|
|
|
|
3002
|
|
|
$this->_phpSheet->getHeaderFooter()->setOddHeader($string['value']); |
|
3003
|
|
|
$this->_phpSheet->getHeaderFooter()->setEvenHeader($string['value']); |
|
3004
|
|
|
} |
|
3005
|
|
|
} |
|
3006
|
|
|
} |
|
3007
|
|
|
|
|
3008
|
|
|
|
|
3009
|
|
|
/** |
|
3010
|
|
|
* Read FOOTER record |
|
3011
|
|
|
*/ |
|
3012
|
|
View Code Duplication |
private function _readFooter() |
|
3013
|
|
|
{ |
|
3014
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3015
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3016
|
|
|
|
|
3017
|
|
|
// move stream pointer to next record |
|
3018
|
|
|
$this->_pos += 4 + $length; |
|
3019
|
|
|
|
|
3020
|
|
|
if (!$this->_readDataOnly) { |
|
3021
|
|
|
// offset: 0; size: var |
|
3022
|
|
|
// realized that $recordData can be empty even when record exists |
|
3023
|
|
|
if ($recordData) { |
|
3024
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
3025
|
|
|
$string = self::_readUnicodeStringLong($recordData); |
|
3026
|
|
|
} else { |
|
3027
|
|
|
$string = $this->_readByteStringShort($recordData); |
|
3028
|
|
|
} |
|
3029
|
|
|
$this->_phpSheet->getHeaderFooter()->setOddFooter($string['value']); |
|
3030
|
|
|
$this->_phpSheet->getHeaderFooter()->setEvenFooter($string['value']); |
|
3031
|
|
|
} |
|
3032
|
|
|
} |
|
3033
|
|
|
} |
|
3034
|
|
|
|
|
3035
|
|
|
|
|
3036
|
|
|
/** |
|
3037
|
|
|
* Read HCENTER record |
|
3038
|
|
|
*/ |
|
3039
|
|
|
private function _readHcenter() |
|
3040
|
|
|
{ |
|
3041
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3042
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3043
|
|
|
|
|
3044
|
|
|
// move stream pointer to next record |
|
3045
|
|
|
$this->_pos += 4 + $length; |
|
3046
|
|
|
|
|
3047
|
|
|
if (!$this->_readDataOnly) { |
|
3048
|
|
|
// offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally |
|
3049
|
|
|
$isHorizontalCentered = (bool) self::_GetInt2d($recordData, 0); |
|
3050
|
|
|
|
|
3051
|
|
|
$this->_phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered); |
|
3052
|
|
|
} |
|
3053
|
|
|
} |
|
3054
|
|
|
|
|
3055
|
|
|
|
|
3056
|
|
|
/** |
|
3057
|
|
|
* Read VCENTER record |
|
3058
|
|
|
*/ |
|
3059
|
|
|
private function _readVcenter() |
|
3060
|
|
|
{ |
|
3061
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3062
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3063
|
|
|
|
|
3064
|
|
|
// move stream pointer to next record |
|
3065
|
|
|
$this->_pos += 4 + $length; |
|
3066
|
|
|
|
|
3067
|
|
|
if (!$this->_readDataOnly) { |
|
3068
|
|
|
// offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered |
|
3069
|
|
|
$isVerticalCentered = (bool) self::_GetInt2d($recordData, 0); |
|
3070
|
|
|
|
|
3071
|
|
|
$this->_phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered); |
|
3072
|
|
|
} |
|
3073
|
|
|
} |
|
3074
|
|
|
|
|
3075
|
|
|
|
|
3076
|
|
|
/** |
|
3077
|
|
|
* Read LEFTMARGIN record |
|
3078
|
|
|
*/ |
|
3079
|
|
|
private function _readLeftMargin() |
|
3080
|
|
|
{ |
|
3081
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3082
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3083
|
|
|
|
|
3084
|
|
|
// move stream pointer to next record |
|
3085
|
|
|
$this->_pos += 4 + $length; |
|
3086
|
|
|
|
|
3087
|
|
|
if (!$this->_readDataOnly) { |
|
3088
|
|
|
// offset: 0; size: 8 |
|
3089
|
|
|
$this->_phpSheet->getPageMargins()->setLeft(self::_extractNumber($recordData)); |
|
3090
|
|
|
} |
|
3091
|
|
|
} |
|
3092
|
|
|
|
|
3093
|
|
|
|
|
3094
|
|
|
/** |
|
3095
|
|
|
* Read RIGHTMARGIN record |
|
3096
|
|
|
*/ |
|
3097
|
|
|
private function _readRightMargin() |
|
3098
|
|
|
{ |
|
3099
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3100
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3101
|
|
|
|
|
3102
|
|
|
// move stream pointer to next record |
|
3103
|
|
|
$this->_pos += 4 + $length; |
|
3104
|
|
|
|
|
3105
|
|
|
if (!$this->_readDataOnly) { |
|
3106
|
|
|
// offset: 0; size: 8 |
|
3107
|
|
|
$this->_phpSheet->getPageMargins()->setRight(self::_extractNumber($recordData)); |
|
3108
|
|
|
} |
|
3109
|
|
|
} |
|
3110
|
|
|
|
|
3111
|
|
|
|
|
3112
|
|
|
/** |
|
3113
|
|
|
* Read TOPMARGIN record |
|
3114
|
|
|
*/ |
|
3115
|
|
|
private function _readTopMargin() |
|
3116
|
|
|
{ |
|
3117
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3118
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3119
|
|
|
|
|
3120
|
|
|
// move stream pointer to next record |
|
3121
|
|
|
$this->_pos += 4 + $length; |
|
3122
|
|
|
|
|
3123
|
|
|
if (!$this->_readDataOnly) { |
|
3124
|
|
|
// offset: 0; size: 8 |
|
3125
|
|
|
$this->_phpSheet->getPageMargins()->setTop(self::_extractNumber($recordData)); |
|
3126
|
|
|
} |
|
3127
|
|
|
} |
|
3128
|
|
|
|
|
3129
|
|
|
|
|
3130
|
|
|
/** |
|
3131
|
|
|
* Read BOTTOMMARGIN record |
|
3132
|
|
|
*/ |
|
3133
|
|
|
private function _readBottomMargin() |
|
3134
|
|
|
{ |
|
3135
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3136
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3137
|
|
|
|
|
3138
|
|
|
// move stream pointer to next record |
|
3139
|
|
|
$this->_pos += 4 + $length; |
|
3140
|
|
|
|
|
3141
|
|
|
if (!$this->_readDataOnly) { |
|
3142
|
|
|
// offset: 0; size: 8 |
|
3143
|
|
|
$this->_phpSheet->getPageMargins()->setBottom(self::_extractNumber($recordData)); |
|
3144
|
|
|
} |
|
3145
|
|
|
} |
|
3146
|
|
|
|
|
3147
|
|
|
|
|
3148
|
|
|
/** |
|
3149
|
|
|
* Read PAGESETUP record |
|
3150
|
|
|
*/ |
|
3151
|
|
|
private function _readPageSetup() |
|
3152
|
|
|
{ |
|
3153
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3154
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3155
|
|
|
|
|
3156
|
|
|
// move stream pointer to next record |
|
3157
|
|
|
$this->_pos += 4 + $length; |
|
3158
|
|
|
|
|
3159
|
|
|
if (!$this->_readDataOnly) { |
|
3160
|
|
|
// offset: 0; size: 2; paper size |
|
3161
|
|
|
$paperSize = self::_GetInt2d($recordData, 0); |
|
3162
|
|
|
|
|
3163
|
|
|
// offset: 2; size: 2; scaling factor |
|
3164
|
|
|
$scale = self::_GetInt2d($recordData, 2); |
|
3165
|
|
|
|
|
3166
|
|
|
// offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed |
|
3167
|
|
|
$fitToWidth = self::_GetInt2d($recordData, 6); |
|
3168
|
|
|
|
|
3169
|
|
|
// offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed |
|
3170
|
|
|
$fitToHeight = self::_GetInt2d($recordData, 8); |
|
3171
|
|
|
|
|
3172
|
|
|
// offset: 10; size: 2; option flags |
|
3173
|
|
|
|
|
3174
|
|
|
// bit: 1; mask: 0x0002; 0=landscape, 1=portrait |
|
3175
|
|
|
$isPortrait = (0x0002 & self::_GetInt2d($recordData, 10)) >> 1; |
|
3176
|
|
|
|
|
3177
|
|
|
// bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init |
|
3178
|
|
|
// when this bit is set, do not use flags for those properties |
|
3179
|
|
|
$isNotInit = (0x0004 & self::_GetInt2d($recordData, 10)) >> 2; |
|
3180
|
|
|
|
|
3181
|
|
|
if (!$isNotInit) { |
|
3182
|
|
|
$this->_phpSheet->getPageSetup()->setPaperSize($paperSize); |
|
3183
|
|
|
switch ($isPortrait) { |
|
3184
|
|
|
case 0: $this->_phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE); break; |
|
3185
|
|
|
case 1: $this->_phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_PORTRAIT); break; |
|
3186
|
|
|
} |
|
3187
|
|
|
|
|
3188
|
|
|
$this->_phpSheet->getPageSetup()->setScale($scale, false); |
|
3189
|
|
|
$this->_phpSheet->getPageSetup()->setFitToPage((bool) $this->_isFitToPages); |
|
3190
|
|
|
$this->_phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false); |
|
3191
|
|
|
$this->_phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false); |
|
3192
|
|
|
} |
|
3193
|
|
|
|
|
3194
|
|
|
// offset: 16; size: 8; header margin (IEEE 754 floating-point value) |
|
3195
|
|
|
$marginHeader = self::_extractNumber(substr($recordData, 16, 8)); |
|
3196
|
|
|
$this->_phpSheet->getPageMargins()->setHeader($marginHeader); |
|
3197
|
|
|
|
|
3198
|
|
|
// offset: 24; size: 8; footer margin (IEEE 754 floating-point value) |
|
3199
|
|
|
$marginFooter = self::_extractNumber(substr($recordData, 24, 8)); |
|
3200
|
|
|
$this->_phpSheet->getPageMargins()->setFooter($marginFooter); |
|
3201
|
|
|
} |
|
3202
|
|
|
} |
|
3203
|
|
|
|
|
3204
|
|
|
|
|
3205
|
|
|
/** |
|
3206
|
|
|
* PROTECT - Sheet protection (BIFF2 through BIFF8) |
|
3207
|
|
|
* if this record is omitted, then it also means no sheet protection |
|
3208
|
|
|
*/ |
|
3209
|
|
|
private function _readProtect() |
|
3210
|
|
|
{ |
|
3211
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3212
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3213
|
|
|
|
|
3214
|
|
|
// move stream pointer to next record |
|
3215
|
|
|
$this->_pos += 4 + $length; |
|
3216
|
|
|
|
|
3217
|
|
|
if ($this->_readDataOnly) { |
|
3218
|
|
|
return; |
|
3219
|
|
|
} |
|
3220
|
|
|
|
|
3221
|
|
|
// offset: 0; size: 2; |
|
3222
|
|
|
|
|
3223
|
|
|
// bit 0, mask 0x01; 1 = sheet is protected |
|
3224
|
|
|
$bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0; |
|
3225
|
|
|
$this->_phpSheet->getProtection()->setSheet((bool)$bool); |
|
3226
|
|
|
} |
|
3227
|
|
|
|
|
3228
|
|
|
|
|
3229
|
|
|
/** |
|
3230
|
|
|
* SCENPROTECT |
|
3231
|
|
|
*/ |
|
3232
|
|
|
private function _readScenProtect() |
|
3233
|
|
|
{ |
|
3234
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3235
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3236
|
|
|
|
|
3237
|
|
|
// move stream pointer to next record |
|
3238
|
|
|
$this->_pos += 4 + $length; |
|
3239
|
|
|
|
|
3240
|
|
|
if ($this->_readDataOnly) { |
|
3241
|
|
|
return; |
|
3242
|
|
|
} |
|
3243
|
|
|
|
|
3244
|
|
|
// offset: 0; size: 2; |
|
3245
|
|
|
|
|
3246
|
|
|
// bit: 0, mask 0x01; 1 = scenarios are protected |
|
3247
|
|
|
$bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0; |
|
3248
|
|
|
|
|
3249
|
|
|
$this->_phpSheet->getProtection()->setScenarios((bool)$bool); |
|
3250
|
|
|
} |
|
3251
|
|
|
|
|
3252
|
|
|
|
|
3253
|
|
|
/** |
|
3254
|
|
|
* OBJECTPROTECT |
|
3255
|
|
|
*/ |
|
3256
|
|
|
private function _readObjectProtect() |
|
3257
|
|
|
{ |
|
3258
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3259
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3260
|
|
|
|
|
3261
|
|
|
// move stream pointer to next record |
|
3262
|
|
|
$this->_pos += 4 + $length; |
|
3263
|
|
|
|
|
3264
|
|
|
if ($this->_readDataOnly) { |
|
3265
|
|
|
return; |
|
3266
|
|
|
} |
|
3267
|
|
|
|
|
3268
|
|
|
// offset: 0; size: 2; |
|
3269
|
|
|
|
|
3270
|
|
|
// bit: 0, mask 0x01; 1 = objects are protected |
|
3271
|
|
|
$bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0; |
|
3272
|
|
|
|
|
3273
|
|
|
$this->_phpSheet->getProtection()->setObjects((bool)$bool); |
|
3274
|
|
|
} |
|
3275
|
|
|
|
|
3276
|
|
|
|
|
3277
|
|
|
/** |
|
3278
|
|
|
* PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8) |
|
3279
|
|
|
*/ |
|
3280
|
|
|
private function _readPassword() |
|
3281
|
|
|
{ |
|
3282
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3283
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3284
|
|
|
|
|
3285
|
|
|
// move stream pointer to next record |
|
3286
|
|
|
$this->_pos += 4 + $length; |
|
3287
|
|
|
|
|
3288
|
|
|
if (!$this->_readDataOnly) { |
|
3289
|
|
|
// offset: 0; size: 2; 16-bit hash value of password |
|
3290
|
|
|
$password = strtoupper(dechex(self::_GetInt2d($recordData, 0))); // the hashed password |
|
3291
|
|
|
$this->_phpSheet->getProtection()->setPassword($password, true); |
|
3292
|
|
|
} |
|
3293
|
|
|
} |
|
3294
|
|
|
|
|
3295
|
|
|
|
|
3296
|
|
|
/** |
|
3297
|
|
|
* Read DEFCOLWIDTH record |
|
3298
|
|
|
*/ |
|
3299
|
|
|
private function _readDefColWidth() |
|
3300
|
|
|
{ |
|
3301
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3302
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3303
|
|
|
|
|
3304
|
|
|
// move stream pointer to next record |
|
3305
|
|
|
$this->_pos += 4 + $length; |
|
3306
|
|
|
|
|
3307
|
|
|
// offset: 0; size: 2; default column width |
|
3308
|
|
|
$width = self::_GetInt2d($recordData, 0); |
|
3309
|
|
|
if ($width != 8) { |
|
3310
|
|
|
$this->_phpSheet->getDefaultColumnDimension()->setWidth($width); |
|
3311
|
|
|
} |
|
3312
|
|
|
} |
|
3313
|
|
|
|
|
3314
|
|
|
|
|
3315
|
|
|
/** |
|
3316
|
|
|
* Read COLINFO record |
|
3317
|
|
|
*/ |
|
3318
|
|
|
private function _readColInfo() |
|
3319
|
|
|
{ |
|
3320
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3321
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3322
|
|
|
|
|
3323
|
|
|
// move stream pointer to next record |
|
3324
|
|
|
$this->_pos += 4 + $length; |
|
3325
|
|
|
|
|
3326
|
|
|
if (!$this->_readDataOnly) { |
|
3327
|
|
|
// offset: 0; size: 2; index to first column in range |
|
3328
|
|
|
$fc = self::_GetInt2d($recordData, 0); // first column index |
|
3329
|
|
|
|
|
3330
|
|
|
// offset: 2; size: 2; index to last column in range |
|
3331
|
|
|
$lc = self::_GetInt2d($recordData, 2); // first column index |
|
3332
|
|
|
|
|
3333
|
|
|
// offset: 4; size: 2; width of the column in 1/256 of the width of the zero character |
|
3334
|
|
|
$width = self::_GetInt2d($recordData, 4); |
|
3335
|
|
|
|
|
3336
|
|
|
// offset: 6; size: 2; index to XF record for default column formatting |
|
3337
|
|
|
$xfIndex = self::_GetInt2d($recordData, 6); |
|
3338
|
|
|
|
|
3339
|
|
|
// offset: 8; size: 2; option flags |
|
3340
|
|
|
|
|
3341
|
|
|
// bit: 0; mask: 0x0001; 1= columns are hidden |
|
3342
|
|
|
$isHidden = (0x0001 & self::_GetInt2d($recordData, 8)) >> 0; |
|
3343
|
|
|
|
|
3344
|
|
|
// bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline) |
|
3345
|
|
|
$level = (0x0700 & self::_GetInt2d($recordData, 8)) >> 8; |
|
3346
|
|
|
|
|
3347
|
|
|
// bit: 12; mask: 0x1000; 1 = collapsed |
|
3348
|
|
|
$isCollapsed = (0x1000 & self::_GetInt2d($recordData, 8)) >> 12; |
|
3349
|
|
|
|
|
3350
|
|
|
// offset: 10; size: 2; not used |
|
3351
|
|
|
|
|
3352
|
|
|
for ($i = $fc; $i <= $lc; ++$i) { |
|
3353
|
|
|
if ($lc == 255 || $lc == 256) { |
|
3354
|
|
|
$this->_phpSheet->getDefaultColumnDimension()->setWidth($width / 256); |
|
3355
|
|
|
break; |
|
3356
|
|
|
} |
|
3357
|
|
|
$this->_phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256); |
|
3358
|
|
|
$this->_phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden); |
|
3359
|
|
|
$this->_phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level); |
|
3360
|
|
|
$this->_phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed); |
|
3361
|
|
|
$this->_phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3362
|
|
|
} |
|
3363
|
|
|
} |
|
3364
|
|
|
} |
|
3365
|
|
|
|
|
3366
|
|
|
|
|
3367
|
|
|
/** |
|
3368
|
|
|
* ROW |
|
3369
|
|
|
* |
|
3370
|
|
|
* This record contains the properties of a single row in a |
|
3371
|
|
|
* sheet. Rows and cells in a sheet are divided into blocks |
|
3372
|
|
|
* of 32 rows. |
|
3373
|
|
|
* |
|
3374
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3375
|
|
|
* Excel File Format" |
|
3376
|
|
|
*/ |
|
3377
|
|
|
private function _readRow() |
|
3378
|
|
|
{ |
|
3379
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3380
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3381
|
|
|
|
|
3382
|
|
|
// move stream pointer to next record |
|
3383
|
|
|
$this->_pos += 4 + $length; |
|
3384
|
|
|
|
|
3385
|
|
|
if (!$this->_readDataOnly) { |
|
3386
|
|
|
// offset: 0; size: 2; index of this row |
|
3387
|
|
|
$r = self::_GetInt2d($recordData, 0); |
|
3388
|
|
|
|
|
3389
|
|
|
// offset: 2; size: 2; index to column of the first cell which is described by a cell record |
|
3390
|
|
|
|
|
3391
|
|
|
// offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1 |
|
3392
|
|
|
|
|
3393
|
|
|
// offset: 6; size: 2; |
|
3394
|
|
|
|
|
3395
|
|
|
// bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point |
|
3396
|
|
|
$height = (0x7FFF & self::_GetInt2d($recordData, 6)) >> 0; |
|
3397
|
|
|
|
|
3398
|
|
|
// bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height |
|
3399
|
|
|
$useDefaultHeight = (0x8000 & self::_GetInt2d($recordData, 6)) >> 15; |
|
3400
|
|
|
|
|
3401
|
|
|
if (!$useDefaultHeight) { |
|
3402
|
|
|
$this->_phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20); |
|
3403
|
|
|
} |
|
3404
|
|
|
|
|
3405
|
|
|
// offset: 8; size: 2; not used |
|
3406
|
|
|
|
|
3407
|
|
|
// offset: 10; size: 2; not used in BIFF5-BIFF8 |
|
3408
|
|
|
|
|
3409
|
|
|
// offset: 12; size: 4; option flags and default row formatting |
|
3410
|
|
|
|
|
3411
|
|
|
// bit: 2-0: mask: 0x00000007; outline level of the row |
|
3412
|
|
|
$level = (0x00000007 & self::_GetInt4d($recordData, 12)) >> 0; |
|
3413
|
|
|
$this->_phpSheet->getRowDimension($r + 1)->setOutlineLevel($level); |
|
3414
|
|
|
|
|
3415
|
|
|
// bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed |
|
3416
|
|
|
$isCollapsed = (0x00000010 & self::_GetInt4d($recordData, 12)) >> 4; |
|
3417
|
|
|
$this->_phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed); |
|
3418
|
|
|
|
|
3419
|
|
|
// bit: 5; mask: 0x00000020; 1 = row is hidden |
|
3420
|
|
|
$isHidden = (0x00000020 & self::_GetInt4d($recordData, 12)) >> 5; |
|
3421
|
|
|
$this->_phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden); |
|
3422
|
|
|
|
|
3423
|
|
|
// bit: 7; mask: 0x00000080; 1 = row has explicit format |
|
3424
|
|
|
$hasExplicitFormat = (0x00000080 & self::_GetInt4d($recordData, 12)) >> 7; |
|
3425
|
|
|
|
|
3426
|
|
|
// bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record |
|
3427
|
|
|
$xfIndex = (0x0FFF0000 & self::_GetInt4d($recordData, 12)) >> 16; |
|
3428
|
|
|
|
|
3429
|
|
|
if ($hasExplicitFormat) { |
|
3430
|
|
|
$this->_phpSheet->getRowDimension($r + 1)->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3431
|
|
|
} |
|
3432
|
|
|
} |
|
3433
|
|
|
} |
|
3434
|
|
|
|
|
3435
|
|
|
|
|
3436
|
|
|
/** |
|
3437
|
|
|
* Read RK record |
|
3438
|
|
|
* This record represents a cell that contains an RK value |
|
3439
|
|
|
* (encoded integer or floating-point value). If a |
|
3440
|
|
|
* floating-point value cannot be encoded to an RK value, |
|
3441
|
|
|
* a NUMBER record will be written. This record replaces the |
|
3442
|
|
|
* record INTEGER written in BIFF2. |
|
3443
|
|
|
* |
|
3444
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3445
|
|
|
* Excel File Format" |
|
3446
|
|
|
*/ |
|
3447
|
|
View Code Duplication |
private function _readRk() |
|
3448
|
|
|
{ |
|
3449
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3450
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3451
|
|
|
|
|
3452
|
|
|
// move stream pointer to next record |
|
3453
|
|
|
$this->_pos += 4 + $length; |
|
3454
|
|
|
|
|
3455
|
|
|
// offset: 0; size: 2; index to row |
|
3456
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
3457
|
|
|
|
|
3458
|
|
|
// offset: 2; size: 2; index to column |
|
3459
|
|
|
$column = self::_GetInt2d($recordData, 2); |
|
3460
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($column); |
|
3461
|
|
|
|
|
3462
|
|
|
// Read cell? |
|
3463
|
|
|
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
3464
|
|
|
// offset: 4; size: 2; index to XF record |
|
3465
|
|
|
$xfIndex = self::_GetInt2d($recordData, 4); |
|
3466
|
|
|
|
|
3467
|
|
|
// offset: 6; size: 4; RK value |
|
3468
|
|
|
$rknum = self::_GetInt4d($recordData, 6); |
|
3469
|
|
|
$numValue = self::_GetIEEE754($rknum); |
|
3470
|
|
|
|
|
3471
|
|
|
$cell = $this->_phpSheet->getCell($columnString . ($row + 1)); |
|
3472
|
|
|
if (!$this->_readDataOnly) { |
|
3473
|
|
|
// add style information |
|
3474
|
|
|
$cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3475
|
|
|
} |
|
3476
|
|
|
|
|
3477
|
|
|
// add cell |
|
3478
|
|
|
$cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC); |
|
3479
|
|
|
} |
|
3480
|
|
|
} |
|
3481
|
|
|
|
|
3482
|
|
|
|
|
3483
|
|
|
/** |
|
3484
|
|
|
* Read LABELSST record |
|
3485
|
|
|
* This record represents a cell that contains a string. It |
|
3486
|
|
|
* replaces the LABEL record and RSTRING record used in |
|
3487
|
|
|
* BIFF2-BIFF5. |
|
3488
|
|
|
* |
|
3489
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3490
|
|
|
* Excel File Format" |
|
3491
|
|
|
*/ |
|
3492
|
|
|
private function _readLabelSst() |
|
3493
|
|
|
{ |
|
3494
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3495
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3496
|
|
|
|
|
3497
|
|
|
// move stream pointer to next record |
|
3498
|
|
|
$this->_pos += 4 + $length; |
|
3499
|
|
|
|
|
3500
|
|
|
// offset: 0; size: 2; index to row |
|
3501
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
3502
|
|
|
|
|
3503
|
|
|
// offset: 2; size: 2; index to column |
|
3504
|
|
|
$column = self::_GetInt2d($recordData, 2); |
|
3505
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($column); |
|
3506
|
|
|
|
|
3507
|
|
|
// Read cell? |
|
3508
|
|
|
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
3509
|
|
|
// offset: 4; size: 2; index to XF record |
|
3510
|
|
|
$xfIndex = self::_GetInt2d($recordData, 4); |
|
3511
|
|
|
|
|
3512
|
|
|
// offset: 6; size: 4; index to SST record |
|
3513
|
|
|
$index = self::_GetInt4d($recordData, 6); |
|
3514
|
|
|
|
|
3515
|
|
|
// add cell |
|
3516
|
|
|
if (($fmtRuns = $this->_sst[$index]['fmtRuns']) && !$this->_readDataOnly) { |
|
3517
|
|
|
// then we should treat as rich text |
|
3518
|
|
|
$richText = new PHPExcel_RichText(); |
|
3519
|
|
|
$charPos = 0; |
|
3520
|
|
|
$sstCount = count($this->_sst[$index]['fmtRuns']); |
|
3521
|
|
|
for ($i = 0; $i <= $sstCount; ++$i) { |
|
3522
|
|
|
if (isset($fmtRuns[$i])) { |
|
3523
|
|
|
$text = PHPExcel_Shared_String::Substring($this->_sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos); |
|
3524
|
|
|
$charPos = $fmtRuns[$i]['charPos']; |
|
3525
|
|
|
} else { |
|
3526
|
|
|
$text = PHPExcel_Shared_String::Substring($this->_sst[$index]['value'], $charPos, PHPExcel_Shared_String::CountCharacters($this->_sst[$index]['value'])); |
|
3527
|
|
|
} |
|
3528
|
|
|
|
|
3529
|
|
|
if (PHPExcel_Shared_String::CountCharacters($text) > 0) { |
|
3530
|
|
|
if ($i == 0) { // first text run, no style |
|
3531
|
|
|
$richText->createText($text); |
|
3532
|
|
|
} else { |
|
3533
|
|
|
$textRun = $richText->createTextRun($text); |
|
3534
|
|
|
if (isset($fmtRuns[$i - 1])) { |
|
3535
|
|
|
if ($fmtRuns[$i - 1]['fontIndex'] < 4) { |
|
3536
|
|
|
$fontIndex = $fmtRuns[$i - 1]['fontIndex']; |
|
3537
|
|
|
} else { |
|
3538
|
|
|
// this has to do with that index 4 is omitted in all BIFF versions for some strange reason |
|
3539
|
|
|
// check the OpenOffice documentation of the FONT record |
|
3540
|
|
|
$fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1; |
|
3541
|
|
|
} |
|
3542
|
|
|
$textRun->setFont(clone $this->_objFonts[$fontIndex]); |
|
3543
|
|
|
} |
|
3544
|
|
|
} |
|
3545
|
|
|
} |
|
3546
|
|
|
} |
|
3547
|
|
|
$cell = $this->_phpSheet->getCell($columnString . ($row + 1)); |
|
3548
|
|
|
$cell->setValueExplicit($richText, PHPExcel_Cell_DataType::TYPE_STRING); |
|
3549
|
|
|
} else { |
|
3550
|
|
|
$cell = $this->_phpSheet->getCell($columnString . ($row + 1)); |
|
3551
|
|
|
$cell->setValueExplicit($this->_sst[$index]['value'], PHPExcel_Cell_DataType::TYPE_STRING); |
|
3552
|
|
|
} |
|
3553
|
|
|
|
|
3554
|
|
|
if (!$this->_readDataOnly) { |
|
3555
|
|
|
// add style information |
|
3556
|
|
|
$cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3557
|
|
|
} |
|
3558
|
|
|
} |
|
3559
|
|
|
} |
|
3560
|
|
|
|
|
3561
|
|
|
|
|
3562
|
|
|
/** |
|
3563
|
|
|
* Read MULRK record |
|
3564
|
|
|
* This record represents a cell range containing RK value |
|
3565
|
|
|
* cells. All cells are located in the same row. |
|
3566
|
|
|
* |
|
3567
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3568
|
|
|
* Excel File Format" |
|
3569
|
|
|
*/ |
|
3570
|
|
|
private function _readMulRk() |
|
3571
|
|
|
{ |
|
3572
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3573
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3574
|
|
|
|
|
3575
|
|
|
// move stream pointer to next record |
|
3576
|
|
|
$this->_pos += 4 + $length; |
|
3577
|
|
|
|
|
3578
|
|
|
// offset: 0; size: 2; index to row |
|
3579
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
3580
|
|
|
|
|
3581
|
|
|
// offset: 2; size: 2; index to first column |
|
3582
|
|
|
$colFirst = self::_GetInt2d($recordData, 2); |
|
3583
|
|
|
|
|
3584
|
|
|
// offset: var; size: 2; index to last column |
|
3585
|
|
|
$colLast = self::_GetInt2d($recordData, $length - 2); |
|
3586
|
|
|
$columns = $colLast - $colFirst + 1; |
|
3587
|
|
|
|
|
3588
|
|
|
// offset within record data |
|
3589
|
|
|
$offset = 4; |
|
3590
|
|
|
|
|
3591
|
|
|
for ($i = 0; $i < $columns; ++$i) { |
|
3592
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($colFirst + $i); |
|
3593
|
|
|
|
|
3594
|
|
|
// Read cell? |
|
3595
|
|
|
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
3596
|
|
|
|
|
3597
|
|
|
// offset: var; size: 2; index to XF record |
|
3598
|
|
|
$xfIndex = self::_GetInt2d($recordData, $offset); |
|
3599
|
|
|
|
|
3600
|
|
|
// offset: var; size: 4; RK value |
|
3601
|
|
|
$numValue = self::_GetIEEE754(self::_GetInt4d($recordData, $offset + 2)); |
|
3602
|
|
|
$cell = $this->_phpSheet->getCell($columnString . ($row + 1)); |
|
3603
|
|
|
if (!$this->_readDataOnly) { |
|
3604
|
|
|
// add style |
|
3605
|
|
|
$cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3606
|
|
|
} |
|
3607
|
|
|
|
|
3608
|
|
|
// add cell value |
|
3609
|
|
|
$cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC); |
|
3610
|
|
|
} |
|
3611
|
|
|
|
|
3612
|
|
|
$offset += 6; |
|
3613
|
|
|
} |
|
3614
|
|
|
} |
|
3615
|
|
|
|
|
3616
|
|
|
|
|
3617
|
|
|
/** |
|
3618
|
|
|
* Read NUMBER record |
|
3619
|
|
|
* This record represents a cell that contains a |
|
3620
|
|
|
* floating-point value. |
|
3621
|
|
|
* |
|
3622
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3623
|
|
|
* Excel File Format" |
|
3624
|
|
|
*/ |
|
3625
|
|
View Code Duplication |
private function _readNumber() |
|
3626
|
|
|
{ |
|
3627
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3628
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3629
|
|
|
|
|
3630
|
|
|
// move stream pointer to next record |
|
3631
|
|
|
$this->_pos += 4 + $length; |
|
3632
|
|
|
|
|
3633
|
|
|
// offset: 0; size: 2; index to row |
|
3634
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
3635
|
|
|
|
|
3636
|
|
|
// offset: 2; size 2; index to column |
|
3637
|
|
|
$column = self::_GetInt2d($recordData, 2); |
|
3638
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($column); |
|
3639
|
|
|
|
|
3640
|
|
|
// Read cell? |
|
3641
|
|
|
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
3642
|
|
|
// offset 4; size: 2; index to XF record |
|
3643
|
|
|
$xfIndex = self::_GetInt2d($recordData, 4); |
|
3644
|
|
|
|
|
3645
|
|
|
$numValue = self::_extractNumber(substr($recordData, 6, 8)); |
|
3646
|
|
|
|
|
3647
|
|
|
$cell = $this->_phpSheet->getCell($columnString . ($row + 1)); |
|
3648
|
|
|
if (!$this->_readDataOnly) { |
|
3649
|
|
|
// add cell style |
|
3650
|
|
|
$cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3651
|
|
|
} |
|
3652
|
|
|
|
|
3653
|
|
|
// add cell value |
|
3654
|
|
|
$cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC); |
|
3655
|
|
|
} |
|
3656
|
|
|
} |
|
3657
|
|
|
|
|
3658
|
|
|
|
|
3659
|
|
|
/** |
|
3660
|
|
|
* Read FORMULA record + perhaps a following STRING record if formula result is a string |
|
3661
|
|
|
* This record contains the token array and the result of a |
|
3662
|
|
|
* formula cell. |
|
3663
|
|
|
* |
|
3664
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3665
|
|
|
* Excel File Format" |
|
3666
|
|
|
*/ |
|
3667
|
|
|
private function _readFormula() |
|
3668
|
|
|
{ |
|
3669
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3670
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3671
|
|
|
|
|
3672
|
|
|
// move stream pointer to next record |
|
3673
|
|
|
$this->_pos += 4 + $length; |
|
3674
|
|
|
|
|
3675
|
|
|
// offset: 0; size: 2; row index |
|
3676
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
3677
|
|
|
|
|
3678
|
|
|
// offset: 2; size: 2; col index |
|
3679
|
|
|
$column = self::_GetInt2d($recordData, 2); |
|
3680
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($column); |
|
3681
|
|
|
|
|
3682
|
|
|
// offset: 20: size: variable; formula structure |
|
3683
|
|
|
$formulaStructure = substr($recordData, 20); |
|
3684
|
|
|
|
|
3685
|
|
|
// offset: 14: size: 2; option flags, recalculate always, recalculate on open etc. |
|
3686
|
|
|
$options = self::_GetInt2d($recordData, 14); |
|
3687
|
|
|
|
|
3688
|
|
|
// bit: 0; mask: 0x0001; 1 = recalculate always |
|
3689
|
|
|
// bit: 1; mask: 0x0002; 1 = calculate on open |
|
3690
|
|
|
// bit: 2; mask: 0x0008; 1 = part of a shared formula |
|
3691
|
|
|
$isPartOfSharedFormula = (bool) (0x0008 & $options); |
|
3692
|
|
|
|
|
3693
|
|
|
// WARNING: |
|
3694
|
|
|
// We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true |
|
3695
|
|
|
// the formula data may be ordinary formula data, therefore we need to check |
|
3696
|
|
|
// explicitly for the tExp token (0x01) |
|
3697
|
|
|
$isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure{2}) == 0x01; |
|
3698
|
|
|
|
|
3699
|
|
|
if ($isPartOfSharedFormula) { |
|
3700
|
|
|
// part of shared formula which means there will be a formula with a tExp token and nothing else |
|
3701
|
|
|
// get the base cell, grab tExp token |
|
3702
|
|
|
$baseRow = self::_GetInt2d($formulaStructure, 3); |
|
3703
|
|
|
$baseCol = self::_GetInt2d($formulaStructure, 5); |
|
3704
|
|
|
$this->_baseCell = PHPExcel_Cell::stringFromColumnIndex($baseCol). ($baseRow + 1); |
|
3705
|
|
|
} |
|
3706
|
|
|
|
|
3707
|
|
|
// Read cell? |
|
3708
|
|
|
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
3709
|
|
|
|
|
3710
|
|
|
if ($isPartOfSharedFormula) { |
|
3711
|
|
|
// formula is added to this cell after the sheet has been read |
|
3712
|
|
|
$this->_sharedFormulaParts[$columnString . ($row + 1)] = $this->_baseCell; |
|
3713
|
|
|
} |
|
3714
|
|
|
|
|
3715
|
|
|
// offset: 16: size: 4; not used |
|
3716
|
|
|
|
|
3717
|
|
|
// offset: 4; size: 2; XF index |
|
3718
|
|
|
$xfIndex = self::_GetInt2d($recordData, 4); |
|
3719
|
|
|
|
|
3720
|
|
|
// offset: 6; size: 8; result of the formula |
|
3721
|
|
|
if ( (ord($recordData{6}) == 0) |
|
3722
|
|
|
&& (ord($recordData{12}) == 255) |
|
3723
|
|
|
&& (ord($recordData{13}) == 255) ) { |
|
3724
|
|
|
|
|
3725
|
|
|
// String formula. Result follows in appended STRING record |
|
3726
|
|
|
$dataType = PHPExcel_Cell_DataType::TYPE_STRING; |
|
3727
|
|
|
|
|
3728
|
|
|
// read possible SHAREDFMLA record |
|
3729
|
|
|
$code = self::_GetInt2d($this->_data, $this->_pos); |
|
3730
|
|
|
if ($code == self::XLS_Type_SHAREDFMLA) { |
|
3731
|
|
|
$this->_readSharedFmla(); |
|
3732
|
|
|
} |
|
3733
|
|
|
|
|
3734
|
|
|
// read STRING record |
|
3735
|
|
|
$value = $this->_readString(); |
|
3736
|
|
|
|
|
3737
|
|
View Code Duplication |
} elseif ((ord($recordData{6}) == 1) |
|
3738
|
|
|
&& (ord($recordData{12}) == 255) |
|
3739
|
|
|
&& (ord($recordData{13}) == 255)) { |
|
3740
|
|
|
|
|
3741
|
|
|
// Boolean formula. Result is in +2; 0=false, 1=true |
|
3742
|
|
|
$dataType = PHPExcel_Cell_DataType::TYPE_BOOL; |
|
3743
|
|
|
$value = (bool) ord($recordData{8}); |
|
3744
|
|
|
|
|
3745
|
|
|
} elseif ((ord($recordData{6}) == 2) |
|
3746
|
|
|
&& (ord($recordData{12}) == 255) |
|
3747
|
|
|
&& (ord($recordData{13}) == 255)) { |
|
3748
|
|
|
|
|
3749
|
|
|
// Error formula. Error code is in +2 |
|
3750
|
|
|
$dataType = PHPExcel_Cell_DataType::TYPE_ERROR; |
|
3751
|
|
|
$value = self::_mapErrorCode(ord($recordData{8})); |
|
3752
|
|
|
|
|
3753
|
|
View Code Duplication |
} elseif ((ord($recordData{6}) == 3) |
|
3754
|
|
|
&& (ord($recordData{12}) == 255) |
|
3755
|
|
|
&& (ord($recordData{13}) == 255)) { |
|
3756
|
|
|
|
|
3757
|
|
|
// Formula result is a null string |
|
3758
|
|
|
$dataType = PHPExcel_Cell_DataType::TYPE_NULL; |
|
3759
|
|
|
$value = ''; |
|
3760
|
|
|
|
|
3761
|
|
|
} else { |
|
3762
|
|
|
|
|
3763
|
|
|
// forumla result is a number, first 14 bytes like _NUMBER record |
|
3764
|
|
|
$dataType = PHPExcel_Cell_DataType::TYPE_NUMERIC; |
|
3765
|
|
|
$value = self::_extractNumber(substr($recordData, 6, 8)); |
|
3766
|
|
|
|
|
3767
|
|
|
} |
|
3768
|
|
|
|
|
3769
|
|
|
$cell = $this->_phpSheet->getCell($columnString . ($row + 1)); |
|
3770
|
|
|
if (!$this->_readDataOnly) { |
|
3771
|
|
|
// add cell style |
|
3772
|
|
|
$cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3773
|
|
|
} |
|
3774
|
|
|
|
|
3775
|
|
|
// store the formula |
|
3776
|
|
|
if (!$isPartOfSharedFormula) { |
|
3777
|
|
|
// not part of shared formula |
|
3778
|
|
|
// add cell value. If we can read formula, populate with formula, otherwise just used cached value |
|
3779
|
|
|
try { |
|
3780
|
|
|
if ($this->_version != self::XLS_BIFF8) { |
|
3781
|
|
|
throw new Exception('Not BIFF8. Can only read BIFF8 formulas'); |
|
3782
|
|
|
} |
|
3783
|
|
|
$formula = $this->_getFormulaFromStructure($formulaStructure); // get formula in human language |
|
3784
|
|
|
$cell->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA); |
|
3785
|
|
|
|
|
3786
|
|
|
} catch (Exception $e) { |
|
3787
|
|
|
$cell->setValueExplicit($value, $dataType); |
|
3788
|
|
|
} |
|
3789
|
|
|
} else { |
|
3790
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
|
|
|
|
|
3791
|
|
|
// do nothing at this point, formula id added later in the code |
|
3792
|
|
|
} else { |
|
3793
|
|
|
$cell->setValueExplicit($value, $dataType); |
|
3794
|
|
|
} |
|
3795
|
|
|
} |
|
3796
|
|
|
|
|
3797
|
|
|
// store the cached calculated value |
|
3798
|
|
|
$cell->setCalculatedValue($value); |
|
3799
|
|
|
} |
|
3800
|
|
|
} |
|
3801
|
|
|
|
|
3802
|
|
|
|
|
3803
|
|
|
/** |
|
3804
|
|
|
* Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader, |
|
3805
|
|
|
* which usually contains relative references. |
|
3806
|
|
|
* These will be used to construct the formula in each shared formula part after the sheet is read. |
|
3807
|
|
|
*/ |
|
3808
|
|
|
private function _readSharedFmla() |
|
3809
|
|
|
{ |
|
3810
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3811
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3812
|
|
|
|
|
3813
|
|
|
// move stream pointer to next record |
|
3814
|
|
|
$this->_pos += 4 + $length; |
|
3815
|
|
|
|
|
3816
|
|
|
// offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything |
|
3817
|
|
|
$cellRange = substr($recordData, 0, 6); |
|
3818
|
|
|
$cellRange = $this->_readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax |
|
3819
|
|
|
|
|
3820
|
|
|
// offset: 6, size: 1; not used |
|
3821
|
|
|
|
|
3822
|
|
|
// offset: 7, size: 1; number of existing FORMULA records for this shared formula |
|
3823
|
|
|
$no = ord($recordData{7}); |
|
3824
|
|
|
|
|
3825
|
|
|
// offset: 8, size: var; Binary token array of the shared formula |
|
3826
|
|
|
$formula = substr($recordData, 8); |
|
3827
|
|
|
|
|
3828
|
|
|
// at this point we only store the shared formula for later use |
|
3829
|
|
|
$this->_sharedFormulas[$this->_baseCell] = $formula; |
|
3830
|
|
|
|
|
3831
|
|
|
} |
|
3832
|
|
|
|
|
3833
|
|
|
|
|
3834
|
|
|
/** |
|
3835
|
|
|
* Read a STRING record from current stream position and advance the stream pointer to next record |
|
3836
|
|
|
* This record is used for storing result from FORMULA record when it is a string, and |
|
3837
|
|
|
* it occurs directly after the FORMULA record |
|
3838
|
|
|
* |
|
3839
|
|
|
* @return string The string contents as UTF-8 |
|
3840
|
|
|
*/ |
|
3841
|
|
|
private function _readString() |
|
3842
|
|
|
{ |
|
3843
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3844
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3845
|
|
|
|
|
3846
|
|
|
// move stream pointer to next record |
|
3847
|
|
|
$this->_pos += 4 + $length; |
|
3848
|
|
|
|
|
3849
|
|
|
if ($this->_version == self::XLS_BIFF8) { |
|
3850
|
|
|
$string = self::_readUnicodeStringLong($recordData); |
|
3851
|
|
|
$value = $string['value']; |
|
3852
|
|
|
} else { |
|
3853
|
|
|
$string = $this->_readByteStringLong($recordData); |
|
3854
|
|
|
$value = $string['value']; |
|
3855
|
|
|
} |
|
3856
|
|
|
|
|
3857
|
|
|
return $value; |
|
3858
|
|
|
} |
|
3859
|
|
|
|
|
3860
|
|
|
|
|
3861
|
|
|
/** |
|
3862
|
|
|
* Read BOOLERR record |
|
3863
|
|
|
* This record represents a Boolean value or error value |
|
3864
|
|
|
* cell. |
|
3865
|
|
|
* |
|
3866
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3867
|
|
|
* Excel File Format" |
|
3868
|
|
|
*/ |
|
3869
|
|
|
private function _readBoolErr() |
|
3870
|
|
|
{ |
|
3871
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3872
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3873
|
|
|
|
|
3874
|
|
|
// move stream pointer to next record |
|
3875
|
|
|
$this->_pos += 4 + $length; |
|
3876
|
|
|
|
|
3877
|
|
|
// offset: 0; size: 2; row index |
|
3878
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
3879
|
|
|
|
|
3880
|
|
|
// offset: 2; size: 2; column index |
|
3881
|
|
|
$column = self::_GetInt2d($recordData, 2); |
|
3882
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($column); |
|
3883
|
|
|
|
|
3884
|
|
|
// Read cell? |
|
3885
|
|
|
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
3886
|
|
|
// offset: 4; size: 2; index to XF record |
|
3887
|
|
|
$xfIndex = self::_GetInt2d($recordData, 4); |
|
3888
|
|
|
|
|
3889
|
|
|
// offset: 6; size: 1; the boolean value or error value |
|
3890
|
|
|
$boolErr = ord($recordData{6}); |
|
3891
|
|
|
|
|
3892
|
|
|
// offset: 7; size: 1; 0=boolean; 1=error |
|
3893
|
|
|
$isError = ord($recordData{7}); |
|
3894
|
|
|
|
|
3895
|
|
|
$cell = $this->_phpSheet->getCell($columnString . ($row + 1)); |
|
3896
|
|
|
switch ($isError) { |
|
3897
|
|
|
case 0: // boolean |
|
3898
|
|
|
$value = (bool) $boolErr; |
|
3899
|
|
|
|
|
3900
|
|
|
// add cell value |
|
3901
|
|
|
$cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_BOOL); |
|
3902
|
|
|
break; |
|
3903
|
|
|
|
|
3904
|
|
|
case 1: // error type |
|
3905
|
|
|
$value = self::_mapErrorCode($boolErr); |
|
3906
|
|
|
|
|
3907
|
|
|
// add cell value |
|
3908
|
|
|
$cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_ERROR); |
|
3909
|
|
|
break; |
|
3910
|
|
|
} |
|
3911
|
|
|
|
|
3912
|
|
|
if (!$this->_readDataOnly) { |
|
3913
|
|
|
// add cell style |
|
3914
|
|
|
$cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3915
|
|
|
} |
|
3916
|
|
|
} |
|
3917
|
|
|
} |
|
3918
|
|
|
|
|
3919
|
|
|
|
|
3920
|
|
|
/** |
|
3921
|
|
|
* Read MULBLANK record |
|
3922
|
|
|
* This record represents a cell range of empty cells. All |
|
3923
|
|
|
* cells are located in the same row |
|
3924
|
|
|
* |
|
3925
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3926
|
|
|
* Excel File Format" |
|
3927
|
|
|
*/ |
|
3928
|
|
|
private function _readMulBlank() |
|
3929
|
|
|
{ |
|
3930
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3931
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3932
|
|
|
|
|
3933
|
|
|
// move stream pointer to next record |
|
3934
|
|
|
$this->_pos += 4 + $length; |
|
3935
|
|
|
|
|
3936
|
|
|
// offset: 0; size: 2; index to row |
|
3937
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
3938
|
|
|
|
|
3939
|
|
|
// offset: 2; size: 2; index to first column |
|
3940
|
|
|
$fc = self::_GetInt2d($recordData, 2); |
|
3941
|
|
|
|
|
3942
|
|
|
// offset: 4; size: 2 x nc; list of indexes to XF records |
|
3943
|
|
|
// add style information |
|
3944
|
|
|
if (!$this->_readDataOnly) { |
|
3945
|
|
|
for ($i = 0; $i < $length / 2 - 3; ++$i) { |
|
3946
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($fc + $i); |
|
3947
|
|
|
|
|
3948
|
|
|
// Read cell? |
|
3949
|
|
View Code Duplication |
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
3950
|
|
|
$xfIndex = self::_GetInt2d($recordData, 4 + 2 * $i); |
|
3951
|
|
|
$this->_phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
3952
|
|
|
} |
|
3953
|
|
|
} |
|
3954
|
|
|
} |
|
3955
|
|
|
|
|
3956
|
|
|
// offset: 6; size 2; index to last column (not needed) |
|
3957
|
|
|
} |
|
3958
|
|
|
|
|
3959
|
|
|
|
|
3960
|
|
|
/** |
|
3961
|
|
|
* Read LABEL record |
|
3962
|
|
|
* This record represents a cell that contains a string. In |
|
3963
|
|
|
* BIFF8 it is usually replaced by the LABELSST record. |
|
3964
|
|
|
* Excel still uses this record, if it copies unformatted |
|
3965
|
|
|
* text cells to the clipboard. |
|
3966
|
|
|
* |
|
3967
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
3968
|
|
|
* Excel File Format" |
|
3969
|
|
|
*/ |
|
3970
|
|
|
private function _readLabel() |
|
3971
|
|
|
{ |
|
3972
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
3973
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
3974
|
|
|
|
|
3975
|
|
|
// move stream pointer to next record |
|
3976
|
|
|
$this->_pos += 4 + $length; |
|
3977
|
|
|
|
|
3978
|
|
|
// offset: 0; size: 2; index to row |
|
3979
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
3980
|
|
|
|
|
3981
|
|
|
// offset: 2; size: 2; index to column |
|
3982
|
|
|
$column = self::_GetInt2d($recordData, 2); |
|
3983
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($column); |
|
3984
|
|
|
|
|
3985
|
|
|
// Read cell? |
|
3986
|
|
|
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
3987
|
|
|
// offset: 4; size: 2; XF index |
|
3988
|
|
|
$xfIndex = self::_GetInt2d($recordData, 4); |
|
3989
|
|
|
|
|
3990
|
|
|
// add cell value |
|
3991
|
|
|
// todo: what if string is very long? continue record |
|
3992
|
|
View Code Duplication |
if ($this->_version == self::XLS_BIFF8) { |
|
3993
|
|
|
$string = self::_readUnicodeStringLong(substr($recordData, 6)); |
|
3994
|
|
|
$value = $string['value']; |
|
3995
|
|
|
} else { |
|
3996
|
|
|
$string = $this->_readByteStringLong(substr($recordData, 6)); |
|
3997
|
|
|
$value = $string['value']; |
|
3998
|
|
|
} |
|
3999
|
|
|
$cell = $this->_phpSheet->getCell($columnString . ($row + 1)); |
|
4000
|
|
|
$cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_STRING); |
|
4001
|
|
|
|
|
4002
|
|
|
if (!$this->_readDataOnly) { |
|
4003
|
|
|
// add cell style |
|
4004
|
|
|
$cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
4005
|
|
|
} |
|
4006
|
|
|
} |
|
4007
|
|
|
} |
|
4008
|
|
|
|
|
4009
|
|
|
|
|
4010
|
|
|
/** |
|
4011
|
|
|
* Read BLANK record |
|
4012
|
|
|
*/ |
|
4013
|
|
|
private function _readBlank() |
|
4014
|
|
|
{ |
|
4015
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4016
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4017
|
|
|
|
|
4018
|
|
|
// move stream pointer to next record |
|
4019
|
|
|
$this->_pos += 4 + $length; |
|
4020
|
|
|
|
|
4021
|
|
|
// offset: 0; size: 2; row index |
|
4022
|
|
|
$row = self::_GetInt2d($recordData, 0); |
|
4023
|
|
|
|
|
4024
|
|
|
// offset: 2; size: 2; col index |
|
4025
|
|
|
$col = self::_GetInt2d($recordData, 2); |
|
4026
|
|
|
$columnString = PHPExcel_Cell::stringFromColumnIndex($col); |
|
4027
|
|
|
|
|
4028
|
|
|
// Read cell? |
|
4029
|
|
View Code Duplication |
if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) { |
|
4030
|
|
|
// offset: 4; size: 2; XF index |
|
4031
|
|
|
$xfIndex = self::_GetInt2d($recordData, 4); |
|
4032
|
|
|
|
|
4033
|
|
|
// add style information |
|
4034
|
|
|
if (!$this->_readDataOnly) { |
|
4035
|
|
|
$this->_phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->_mapCellXfIndex[$xfIndex]); |
|
4036
|
|
|
} |
|
4037
|
|
|
} |
|
4038
|
|
|
|
|
4039
|
|
|
} |
|
4040
|
|
|
|
|
4041
|
|
|
|
|
4042
|
|
|
/** |
|
4043
|
|
|
* Read MSODRAWING record |
|
4044
|
|
|
*/ |
|
4045
|
|
View Code Duplication |
private function _readMsoDrawing() |
|
4046
|
|
|
{ |
|
4047
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4048
|
|
|
|
|
4049
|
|
|
// get spliced record data |
|
4050
|
|
|
$splicedRecordData = $this->_getSplicedRecordData(); |
|
4051
|
|
|
$recordData = $splicedRecordData['recordData']; |
|
4052
|
|
|
|
|
4053
|
|
|
$this->_drawingData .= $recordData; |
|
4054
|
|
|
} |
|
4055
|
|
|
|
|
4056
|
|
|
|
|
4057
|
|
|
/** |
|
4058
|
|
|
* Read OBJ record |
|
4059
|
|
|
*/ |
|
4060
|
|
|
private function _readObj() |
|
4061
|
|
|
{ |
|
4062
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4063
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4064
|
|
|
|
|
4065
|
|
|
// move stream pointer to next record |
|
4066
|
|
|
$this->_pos += 4 + $length; |
|
4067
|
|
|
|
|
4068
|
|
|
if ($this->_readDataOnly || $this->_version != self::XLS_BIFF8) { |
|
4069
|
|
|
return; |
|
4070
|
|
|
} |
|
4071
|
|
|
|
|
4072
|
|
|
// recordData consists of an array of subrecords looking like this: |
|
4073
|
|
|
// ft: 2 bytes; ftCmo type (0x15) |
|
4074
|
|
|
// cb: 2 bytes; size in bytes of ftCmo data |
|
4075
|
|
|
// ot: 2 bytes; Object Type |
|
4076
|
|
|
// id: 2 bytes; Object id number |
|
4077
|
|
|
// grbit: 2 bytes; Option Flags |
|
4078
|
|
|
// data: var; subrecord data |
|
4079
|
|
|
|
|
4080
|
|
|
// for now, we are just interested in the second subrecord containing the object type |
|
4081
|
|
|
$ftCmoType = self::_GetInt2d($recordData, 0); |
|
4082
|
|
|
$cbCmoSize = self::_GetInt2d($recordData, 2); |
|
4083
|
|
|
$otObjType = self::_GetInt2d($recordData, 4); |
|
4084
|
|
|
$idObjID = self::_GetInt2d($recordData, 6); |
|
4085
|
|
|
$grbitOpts = self::_GetInt2d($recordData, 6); |
|
4086
|
|
|
|
|
4087
|
|
|
$this->_objs[] = array( |
|
4088
|
|
|
'ftCmoType' => $ftCmoType, |
|
4089
|
|
|
'cbCmoSize' => $cbCmoSize, |
|
4090
|
|
|
'otObjType' => $otObjType, |
|
4091
|
|
|
'idObjID' => $idObjID, |
|
4092
|
|
|
'grbitOpts' => $grbitOpts |
|
4093
|
|
|
); |
|
4094
|
|
|
$this->textObjRef = $idObjID; |
|
4095
|
|
|
|
|
4096
|
|
|
// echo '<b>_readObj()</b><br />'; |
|
4097
|
|
|
// var_dump(end($this->_objs)); |
|
4098
|
|
|
// echo '<br />'; |
|
4099
|
|
|
} |
|
4100
|
|
|
|
|
4101
|
|
|
|
|
4102
|
|
|
/** |
|
4103
|
|
|
* Read WINDOW2 record |
|
4104
|
|
|
*/ |
|
4105
|
|
|
private function _readWindow2() |
|
4106
|
|
|
{ |
|
4107
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4108
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4109
|
|
|
|
|
4110
|
|
|
// move stream pointer to next record |
|
4111
|
|
|
$this->_pos += 4 + $length; |
|
4112
|
|
|
|
|
4113
|
|
|
// offset: 0; size: 2; option flags |
|
4114
|
|
|
$options = self::_GetInt2d($recordData, 0); |
|
4115
|
|
|
|
|
4116
|
|
|
// bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines |
|
4117
|
|
|
$showGridlines = (bool) ((0x0002 & $options) >> 1); |
|
4118
|
|
|
$this->_phpSheet->setShowGridlines($showGridlines); |
|
4119
|
|
|
|
|
4120
|
|
|
// bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers |
|
4121
|
|
|
$showRowColHeaders = (bool) ((0x0004 & $options) >> 2); |
|
4122
|
|
|
$this->_phpSheet->setShowRowColHeaders($showRowColHeaders); |
|
4123
|
|
|
|
|
4124
|
|
|
// bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen |
|
4125
|
|
|
$this->_frozen = (bool) ((0x0008 & $options) >> 3); |
|
4126
|
|
|
|
|
4127
|
|
|
// bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left |
|
4128
|
|
|
$this->_phpSheet->setRightToLeft((bool)((0x0040 & $options) >> 6)); |
|
4129
|
|
|
|
|
4130
|
|
|
// bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active |
|
4131
|
|
|
$isActive = (bool) ((0x0400 & $options) >> 10); |
|
4132
|
|
|
if ($isActive) { |
|
4133
|
|
|
$this->_phpExcel->setActiveSheetIndex($this->_phpExcel->getIndex($this->_phpSheet)); |
|
4134
|
|
|
} |
|
4135
|
|
|
} |
|
4136
|
|
|
|
|
4137
|
|
|
|
|
4138
|
|
|
/** |
|
4139
|
|
|
* Read SCL record |
|
4140
|
|
|
*/ |
|
4141
|
|
|
private function _readScl() |
|
4142
|
|
|
{ |
|
4143
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4144
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4145
|
|
|
|
|
4146
|
|
|
// move stream pointer to next record |
|
4147
|
|
|
$this->_pos += 4 + $length; |
|
4148
|
|
|
|
|
4149
|
|
|
// offset: 0; size: 2; numerator of the view magnification |
|
4150
|
|
|
$numerator = self::_GetInt2d($recordData, 0); |
|
4151
|
|
|
|
|
4152
|
|
|
// offset: 2; size: 2; numerator of the view magnification |
|
4153
|
|
|
$denumerator = self::_GetInt2d($recordData, 2); |
|
4154
|
|
|
|
|
4155
|
|
|
// set the zoom scale (in percent) |
|
4156
|
|
|
$this->_phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator); |
|
4157
|
|
|
} |
|
4158
|
|
|
|
|
4159
|
|
|
|
|
4160
|
|
|
/** |
|
4161
|
|
|
* Read PANE record |
|
4162
|
|
|
*/ |
|
4163
|
|
|
private function _readPane() |
|
4164
|
|
|
{ |
|
4165
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4166
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4167
|
|
|
|
|
4168
|
|
|
// move stream pointer to next record |
|
4169
|
|
|
$this->_pos += 4 + $length; |
|
4170
|
|
|
|
|
4171
|
|
|
if (!$this->_readDataOnly) { |
|
4172
|
|
|
// offset: 0; size: 2; position of vertical split |
|
4173
|
|
|
$px = self::_GetInt2d($recordData, 0); |
|
4174
|
|
|
|
|
4175
|
|
|
// offset: 2; size: 2; position of horizontal split |
|
4176
|
|
|
$py = self::_GetInt2d($recordData, 2); |
|
4177
|
|
|
|
|
4178
|
|
|
if ($this->_frozen) { |
|
4179
|
|
|
// frozen panes |
|
4180
|
|
|
$this->_phpSheet->freezePane(PHPExcel_Cell::stringFromColumnIndex($px) . ($py + 1)); |
|
4181
|
|
|
} else { |
|
|
|
|
|
|
4182
|
|
|
// unfrozen panes; split windows; not supported by PHPExcel core |
|
4183
|
|
|
} |
|
4184
|
|
|
} |
|
4185
|
|
|
} |
|
4186
|
|
|
|
|
4187
|
|
|
|
|
4188
|
|
|
/** |
|
4189
|
|
|
* Read SELECTION record. There is one such record for each pane in the sheet. |
|
4190
|
|
|
*/ |
|
4191
|
|
|
private function _readSelection() |
|
4192
|
|
|
{ |
|
4193
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4194
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4195
|
|
|
|
|
4196
|
|
|
// move stream pointer to next record |
|
4197
|
|
|
$this->_pos += 4 + $length; |
|
4198
|
|
|
|
|
4199
|
|
|
if (!$this->_readDataOnly) { |
|
4200
|
|
|
// offset: 0; size: 1; pane identifier |
|
4201
|
|
|
$paneId = ord($recordData{0}); |
|
4202
|
|
|
|
|
4203
|
|
|
// offset: 1; size: 2; index to row of the active cell |
|
4204
|
|
|
$r = self::_GetInt2d($recordData, 1); |
|
4205
|
|
|
|
|
4206
|
|
|
// offset: 3; size: 2; index to column of the active cell |
|
4207
|
|
|
$c = self::_GetInt2d($recordData, 3); |
|
4208
|
|
|
|
|
4209
|
|
|
// offset: 5; size: 2; index into the following cell range list to the |
|
4210
|
|
|
// entry that contains the active cell |
|
4211
|
|
|
$index = self::_GetInt2d($recordData, 5); |
|
4212
|
|
|
|
|
4213
|
|
|
// offset: 7; size: var; cell range address list containing all selected cell ranges |
|
4214
|
|
|
$data = substr($recordData, 7); |
|
4215
|
|
|
$cellRangeAddressList = $this->_readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax |
|
4216
|
|
|
|
|
4217
|
|
|
$selectedCells = $cellRangeAddressList['cellRangeAddresses'][0]; |
|
4218
|
|
|
|
|
4219
|
|
|
// first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!) |
|
4220
|
|
|
if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) { |
|
4221
|
|
|
$selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells); |
|
4222
|
|
|
} |
|
4223
|
|
|
|
|
4224
|
|
|
// first row '1' + last row '65536' indicates that full column is selected |
|
4225
|
|
|
if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) { |
|
4226
|
|
|
$selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells); |
|
4227
|
|
|
} |
|
4228
|
|
|
|
|
4229
|
|
|
// first column 'A' + last column 'IV' indicates that full row is selected |
|
4230
|
|
|
if (preg_match('/^(A[0-9]+\:)IV([0-9]+)$/', $selectedCells)) { |
|
4231
|
|
|
$selectedCells = preg_replace('/^(A[0-9]+\:)IV([0-9]+)$/', '${1}XFD${2}', $selectedCells); |
|
4232
|
|
|
} |
|
4233
|
|
|
|
|
4234
|
|
|
$this->_phpSheet->setSelectedCells($selectedCells); |
|
4235
|
|
|
} |
|
4236
|
|
|
} |
|
4237
|
|
|
|
|
4238
|
|
|
|
|
4239
|
|
|
private function _includeCellRangeFiltered($cellRangeAddress) |
|
4240
|
|
|
{ |
|
4241
|
|
|
$includeCellRange = true; |
|
4242
|
|
|
if ($this->getReadFilter() !== NULL) { |
|
4243
|
|
|
$includeCellRange = false; |
|
4244
|
|
|
$rangeBoundaries = PHPExcel_Cell::getRangeBoundaries($cellRangeAddress); |
|
4245
|
|
|
$rangeBoundaries[1][0]++; |
|
4246
|
|
|
for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; $row++) { |
|
4247
|
|
|
for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; $column++) { |
|
4248
|
|
|
if ($this->getReadFilter()->readCell($column, $row, $this->_phpSheet->getTitle())) { |
|
4249
|
|
|
$includeCellRange = true; |
|
4250
|
|
|
break 2; |
|
4251
|
|
|
} |
|
4252
|
|
|
} |
|
4253
|
|
|
} |
|
4254
|
|
|
} |
|
4255
|
|
|
return $includeCellRange; |
|
4256
|
|
|
} |
|
4257
|
|
|
|
|
4258
|
|
|
|
|
4259
|
|
|
/** |
|
4260
|
|
|
* MERGEDCELLS |
|
4261
|
|
|
* |
|
4262
|
|
|
* This record contains the addresses of merged cell ranges |
|
4263
|
|
|
* in the current sheet. |
|
4264
|
|
|
* |
|
4265
|
|
|
* -- "OpenOffice.org's Documentation of the Microsoft |
|
4266
|
|
|
* Excel File Format" |
|
4267
|
|
|
*/ |
|
4268
|
|
|
private function _readMergedCells() |
|
4269
|
|
|
{ |
|
4270
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4271
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4272
|
|
|
|
|
4273
|
|
|
// move stream pointer to next record |
|
4274
|
|
|
$this->_pos += 4 + $length; |
|
4275
|
|
|
|
|
4276
|
|
|
if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) { |
|
4277
|
|
|
$cellRangeAddressList = $this->_readBIFF8CellRangeAddressList($recordData); |
|
4278
|
|
|
foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) { |
|
4279
|
|
|
if ($this->_includeCellRangeFiltered($cellRangeAddress)) { |
|
4280
|
|
|
$this->_phpSheet->mergeCells($cellRangeAddress); |
|
4281
|
|
|
} |
|
4282
|
|
|
} |
|
4283
|
|
|
} |
|
4284
|
|
|
} |
|
4285
|
|
|
|
|
4286
|
|
|
|
|
4287
|
|
|
/** |
|
4288
|
|
|
* Read HYPERLINK record |
|
4289
|
|
|
*/ |
|
4290
|
|
|
private function _readHyperLink() |
|
4291
|
|
|
{ |
|
4292
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4293
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4294
|
|
|
|
|
4295
|
|
|
// move stream pointer forward to next record |
|
4296
|
|
|
$this->_pos += 4 + $length; |
|
4297
|
|
|
|
|
4298
|
|
|
if (!$this->_readDataOnly) { |
|
4299
|
|
|
// offset: 0; size: 8; cell range address of all cells containing this hyperlink |
|
4300
|
|
|
try { |
|
4301
|
|
|
$cellRange = $this->_readBIFF8CellRangeAddressFixed($recordData, 0, 8); |
|
4302
|
|
|
} catch (Exception $e) { |
|
4303
|
|
|
return; |
|
4304
|
|
|
} |
|
4305
|
|
|
|
|
4306
|
|
|
// offset: 8, size: 16; GUID of StdLink |
|
4307
|
|
|
|
|
4308
|
|
|
// offset: 24, size: 4; unknown value |
|
4309
|
|
|
|
|
4310
|
|
|
// offset: 28, size: 4; option flags |
|
4311
|
|
|
|
|
4312
|
|
|
// bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL |
|
4313
|
|
|
$isFileLinkOrUrl = (0x00000001 & self::_GetInt2d($recordData, 28)) >> 0; |
|
4314
|
|
|
|
|
4315
|
|
|
// bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL |
|
4316
|
|
|
$isAbsPathOrUrl = (0x00000001 & self::_GetInt2d($recordData, 28)) >> 1; |
|
4317
|
|
|
|
|
4318
|
|
|
// bit: 2 (and 4); mask: 0x00000014; 0 = no description |
|
4319
|
|
|
$hasDesc = (0x00000014 & self::_GetInt2d($recordData, 28)) >> 2; |
|
4320
|
|
|
|
|
4321
|
|
|
// bit: 3; mask: 0x00000008; 0 = no text, 1 = has text |
|
4322
|
|
|
$hasText = (0x00000008 & self::_GetInt2d($recordData, 28)) >> 3; |
|
4323
|
|
|
|
|
4324
|
|
|
// bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame |
|
4325
|
|
|
$hasFrame = (0x00000080 & self::_GetInt2d($recordData, 28)) >> 7; |
|
4326
|
|
|
|
|
4327
|
|
|
// bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name) |
|
4328
|
|
|
$isUNC = (0x00000100 & self::_GetInt2d($recordData, 28)) >> 8; |
|
4329
|
|
|
|
|
4330
|
|
|
// offset within record data |
|
4331
|
|
|
$offset = 32; |
|
4332
|
|
|
|
|
4333
|
|
|
if ($hasDesc) { |
|
4334
|
|
|
// offset: 32; size: var; character count of description text |
|
4335
|
|
|
$dl = self::_GetInt4d($recordData, 32); |
|
4336
|
|
|
// offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated |
|
4337
|
|
|
$desc = self::_encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false); |
|
4338
|
|
|
$offset += 4 + 2 * $dl; |
|
4339
|
|
|
} |
|
4340
|
|
|
if ($hasFrame) { |
|
4341
|
|
|
$fl = self::_GetInt4d($recordData, $offset); |
|
4342
|
|
|
$offset += 4 + 2 * $fl; |
|
4343
|
|
|
} |
|
4344
|
|
|
|
|
4345
|
|
|
// detect type of hyperlink (there are 4 types) |
|
4346
|
|
|
$hyperlinkType = null; |
|
4347
|
|
|
|
|
4348
|
|
|
if ($isUNC) { |
|
4349
|
|
|
$hyperlinkType = 'UNC'; |
|
4350
|
|
|
} else if (!$isFileLinkOrUrl) { |
|
4351
|
|
|
$hyperlinkType = 'workbook'; |
|
4352
|
|
|
} else if (ord($recordData{$offset}) == 0x03) { |
|
4353
|
|
|
$hyperlinkType = 'local'; |
|
4354
|
|
|
} else if (ord($recordData{$offset}) == 0xE0) { |
|
4355
|
|
|
$hyperlinkType = 'URL'; |
|
4356
|
|
|
} |
|
4357
|
|
|
|
|
4358
|
|
|
switch ($hyperlinkType) { |
|
4359
|
|
|
case 'URL': |
|
4360
|
|
|
// section 5.58.2: Hyperlink containing a URL |
|
4361
|
|
|
// e.g. http://example.org/index.php |
|
4362
|
|
|
|
|
4363
|
|
|
// offset: var; size: 16; GUID of URL Moniker |
|
4364
|
|
|
$offset += 16; |
|
4365
|
|
|
// offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word |
|
4366
|
|
|
$us = self::_GetInt4d($recordData, $offset); |
|
4367
|
|
|
$offset += 4; |
|
4368
|
|
|
// offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated |
|
4369
|
|
|
$url = self::_encodeUTF16(substr($recordData, $offset, $us - 2), false); |
|
4370
|
|
|
$url .= $hasText ? '#' : ''; |
|
4371
|
|
|
$offset += $us; |
|
4372
|
|
|
break; |
|
4373
|
|
|
|
|
4374
|
|
|
case 'local': |
|
4375
|
|
|
// section 5.58.3: Hyperlink to local file |
|
4376
|
|
|
// examples: |
|
4377
|
|
|
// mydoc.txt |
|
4378
|
|
|
// ../../somedoc.xls#Sheet!A1 |
|
4379
|
|
|
|
|
4380
|
|
|
// offset: var; size: 16; GUI of File Moniker |
|
4381
|
|
|
$offset += 16; |
|
4382
|
|
|
|
|
4383
|
|
|
// offset: var; size: 2; directory up-level count. |
|
4384
|
|
|
$upLevelCount = self::_GetInt2d($recordData, $offset); |
|
4385
|
|
|
$offset += 2; |
|
4386
|
|
|
|
|
4387
|
|
|
// offset: var; size: 4; character count of the shortened file path and name, including trailing zero word |
|
4388
|
|
|
$sl = self::_GetInt4d($recordData, $offset); |
|
4389
|
|
|
$offset += 4; |
|
4390
|
|
|
|
|
4391
|
|
|
// offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string) |
|
4392
|
|
|
$shortenedFilePath = substr($recordData, $offset, $sl); |
|
4393
|
|
|
$shortenedFilePath = self::_encodeUTF16($shortenedFilePath, true); |
|
4394
|
|
|
$shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero |
|
4395
|
|
|
|
|
4396
|
|
|
$offset += $sl; |
|
4397
|
|
|
|
|
4398
|
|
|
// offset: var; size: 24; unknown sequence |
|
4399
|
|
|
$offset += 24; |
|
4400
|
|
|
|
|
4401
|
|
|
// extended file path |
|
4402
|
|
|
// offset: var; size: 4; size of the following file link field including string lenth mark |
|
4403
|
|
|
$sz = self::_GetInt4d($recordData, $offset); |
|
4404
|
|
|
$offset += 4; |
|
4405
|
|
|
|
|
4406
|
|
|
// only present if $sz > 0 |
|
4407
|
|
|
if ($sz > 0) { |
|
4408
|
|
|
// offset: var; size: 4; size of the character array of the extended file path and name |
|
4409
|
|
|
$xl = self::_GetInt4d($recordData, $offset); |
|
4410
|
|
|
$offset += 4; |
|
4411
|
|
|
|
|
4412
|
|
|
// offset: var; size 2; unknown |
|
4413
|
|
|
$offset += 2; |
|
4414
|
|
|
|
|
4415
|
|
|
// offset: var; size $xl; character array of the extended file path and name. |
|
4416
|
|
|
$extendedFilePath = substr($recordData, $offset, $xl); |
|
4417
|
|
|
$extendedFilePath = self::_encodeUTF16($extendedFilePath, false); |
|
4418
|
|
|
$offset += $xl; |
|
4419
|
|
|
} |
|
4420
|
|
|
|
|
4421
|
|
|
// construct the path |
|
4422
|
|
|
$url = str_repeat('..\\', $upLevelCount); |
|
4423
|
|
|
$url .= ($sz > 0) ? |
|
4424
|
|
|
$extendedFilePath : $shortenedFilePath; // use extended path if available |
|
|
|
|
|
|
4425
|
|
|
$url .= $hasText ? '#' : ''; |
|
4426
|
|
|
|
|
4427
|
|
|
break; |
|
4428
|
|
|
|
|
4429
|
|
|
|
|
4430
|
|
|
case 'UNC': |
|
4431
|
|
|
// section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path |
|
4432
|
|
|
// todo: implement |
|
4433
|
|
|
return; |
|
4434
|
|
|
|
|
4435
|
|
|
case 'workbook': |
|
4436
|
|
|
// section 5.58.5: Hyperlink to the Current Workbook |
|
4437
|
|
|
// e.g. Sheet2!B1:C2, stored in text mark field |
|
4438
|
|
|
$url = 'sheet://'; |
|
4439
|
|
|
break; |
|
4440
|
|
|
|
|
4441
|
|
|
default: |
|
4442
|
|
|
return; |
|
4443
|
|
|
|
|
4444
|
|
|
} |
|
4445
|
|
|
|
|
4446
|
|
|
if ($hasText) { |
|
4447
|
|
|
// offset: var; size: 4; character count of text mark including trailing zero word |
|
4448
|
|
|
$tl = self::_GetInt4d($recordData, $offset); |
|
4449
|
|
|
$offset += 4; |
|
4450
|
|
|
// offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated |
|
4451
|
|
|
$text = self::_encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false); |
|
4452
|
|
|
$url .= $text; |
|
4453
|
|
|
} |
|
4454
|
|
|
|
|
4455
|
|
|
// apply the hyperlink to all the relevant cells |
|
4456
|
|
|
foreach (PHPExcel_Cell::extractAllCellReferencesInRange($cellRange) as $coordinate) { |
|
4457
|
|
|
$this->_phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url); |
|
4458
|
|
|
} |
|
4459
|
|
|
} |
|
4460
|
|
|
} |
|
4461
|
|
|
|
|
4462
|
|
|
|
|
4463
|
|
|
/** |
|
4464
|
|
|
* Read DATAVALIDATIONS record |
|
4465
|
|
|
*/ |
|
4466
|
|
View Code Duplication |
private function _readDataValidations() |
|
4467
|
|
|
{ |
|
4468
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4469
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4470
|
|
|
|
|
4471
|
|
|
// move stream pointer forward to next record |
|
4472
|
|
|
$this->_pos += 4 + $length; |
|
4473
|
|
|
} |
|
4474
|
|
|
|
|
4475
|
|
|
|
|
4476
|
|
|
/** |
|
4477
|
|
|
* Read DATAVALIDATION record |
|
4478
|
|
|
*/ |
|
4479
|
|
|
private function _readDataValidation() |
|
4480
|
|
|
{ |
|
4481
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4482
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4483
|
|
|
|
|
4484
|
|
|
// move stream pointer forward to next record |
|
4485
|
|
|
$this->_pos += 4 + $length; |
|
4486
|
|
|
|
|
4487
|
|
|
if ($this->_readDataOnly) { |
|
4488
|
|
|
return; |
|
4489
|
|
|
} |
|
4490
|
|
|
|
|
4491
|
|
|
// offset: 0; size: 4; Options |
|
4492
|
|
|
$options = self::_GetInt4d($recordData, 0); |
|
4493
|
|
|
|
|
4494
|
|
|
// bit: 0-3; mask: 0x0000000F; type |
|
4495
|
|
|
$type = (0x0000000F & $options) >> 0; |
|
4496
|
|
View Code Duplication |
switch ($type) { |
|
4497
|
|
|
case 0x00: $type = PHPExcel_Cell_DataValidation::TYPE_NONE; break; |
|
4498
|
|
|
case 0x01: $type = PHPExcel_Cell_DataValidation::TYPE_WHOLE; break; |
|
4499
|
|
|
case 0x02: $type = PHPExcel_Cell_DataValidation::TYPE_DECIMAL; break; |
|
4500
|
|
|
case 0x03: $type = PHPExcel_Cell_DataValidation::TYPE_LIST; break; |
|
4501
|
|
|
case 0x04: $type = PHPExcel_Cell_DataValidation::TYPE_DATE; break; |
|
4502
|
|
|
case 0x05: $type = PHPExcel_Cell_DataValidation::TYPE_TIME; break; |
|
4503
|
|
|
case 0x06: $type = PHPExcel_Cell_DataValidation::TYPE_TEXTLENGTH; break; |
|
4504
|
|
|
case 0x07: $type = PHPExcel_Cell_DataValidation::TYPE_CUSTOM; break; |
|
4505
|
|
|
} |
|
4506
|
|
|
|
|
4507
|
|
|
// bit: 4-6; mask: 0x00000070; error type |
|
4508
|
|
|
$errorStyle = (0x00000070 & $options) >> 4; |
|
4509
|
|
View Code Duplication |
switch ($errorStyle) { |
|
4510
|
|
|
case 0x00: $errorStyle = PHPExcel_Cell_DataValidation::STYLE_STOP; break; |
|
4511
|
|
|
case 0x01: $errorStyle = PHPExcel_Cell_DataValidation::STYLE_WARNING; break; |
|
4512
|
|
|
case 0x02: $errorStyle = PHPExcel_Cell_DataValidation::STYLE_INFORMATION; break; |
|
4513
|
|
|
} |
|
4514
|
|
|
|
|
4515
|
|
|
// bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list) |
|
4516
|
|
|
// I have only seen cases where this is 1 |
|
4517
|
|
|
$explicitFormula = (0x00000080 & $options) >> 7; |
|
4518
|
|
|
|
|
4519
|
|
|
// bit: 8; mask: 0x00000100; 1= empty cells allowed |
|
4520
|
|
|
$allowBlank = (0x00000100 & $options) >> 8; |
|
4521
|
|
|
|
|
4522
|
|
|
// bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity |
|
4523
|
|
|
$suppressDropDown = (0x00000200 & $options) >> 9; |
|
4524
|
|
|
|
|
4525
|
|
|
// bit: 18; mask: 0x00040000; 1= show prompt box if cell selected |
|
4526
|
|
|
$showInputMessage = (0x00040000 & $options) >> 18; |
|
4527
|
|
|
|
|
4528
|
|
|
// bit: 19; mask: 0x00080000; 1= show error box if invalid values entered |
|
4529
|
|
|
$showErrorMessage = (0x00080000 & $options) >> 19; |
|
4530
|
|
|
|
|
4531
|
|
|
// bit: 20-23; mask: 0x00F00000; condition operator |
|
4532
|
|
|
$operator = (0x00F00000 & $options) >> 20; |
|
4533
|
|
View Code Duplication |
switch ($operator) { |
|
4534
|
|
|
case 0x00: $operator = PHPExcel_Cell_DataValidation::OPERATOR_BETWEEN ; break; |
|
4535
|
|
|
case 0x01: $operator = PHPExcel_Cell_DataValidation::OPERATOR_NOTBETWEEN ; break; |
|
4536
|
|
|
case 0x02: $operator = PHPExcel_Cell_DataValidation::OPERATOR_EQUAL ; break; |
|
4537
|
|
|
case 0x03: $operator = PHPExcel_Cell_DataValidation::OPERATOR_NOTEQUAL ; break; |
|
4538
|
|
|
case 0x04: $operator = PHPExcel_Cell_DataValidation::OPERATOR_GREATERTHAN ; break; |
|
4539
|
|
|
case 0x05: $operator = PHPExcel_Cell_DataValidation::OPERATOR_LESSTHAN ; break; |
|
4540
|
|
|
case 0x06: $operator = PHPExcel_Cell_DataValidation::OPERATOR_GREATERTHANOREQUAL; break; |
|
4541
|
|
|
case 0x07: $operator = PHPExcel_Cell_DataValidation::OPERATOR_LESSTHANOREQUAL ; break; |
|
4542
|
|
|
} |
|
4543
|
|
|
|
|
4544
|
|
|
// offset: 4; size: var; title of the prompt box |
|
4545
|
|
|
$offset = 4; |
|
4546
|
|
|
$string = self::_readUnicodeStringLong(substr($recordData, $offset)); |
|
4547
|
|
|
$promptTitle = $string['value'] !== chr(0) ? |
|
4548
|
|
|
$string['value'] : ''; |
|
4549
|
|
|
$offset += $string['size']; |
|
4550
|
|
|
|
|
4551
|
|
|
// offset: var; size: var; title of the error box |
|
4552
|
|
|
$string = self::_readUnicodeStringLong(substr($recordData, $offset)); |
|
4553
|
|
|
$errorTitle = $string['value'] !== chr(0) ? |
|
4554
|
|
|
$string['value'] : ''; |
|
4555
|
|
|
$offset += $string['size']; |
|
4556
|
|
|
|
|
4557
|
|
|
// offset: var; size: var; text of the prompt box |
|
4558
|
|
|
$string = self::_readUnicodeStringLong(substr($recordData, $offset)); |
|
4559
|
|
|
$prompt = $string['value'] !== chr(0) ? |
|
4560
|
|
|
$string['value'] : ''; |
|
4561
|
|
|
$offset += $string['size']; |
|
4562
|
|
|
|
|
4563
|
|
|
// offset: var; size: var; text of the error box |
|
4564
|
|
|
$string = self::_readUnicodeStringLong(substr($recordData, $offset)); |
|
4565
|
|
|
$error = $string['value'] !== chr(0) ? |
|
4566
|
|
|
$string['value'] : ''; |
|
4567
|
|
|
$offset += $string['size']; |
|
4568
|
|
|
|
|
4569
|
|
|
// offset: var; size: 2; size of the formula data for the first condition |
|
4570
|
|
|
$sz1 = self::_GetInt2d($recordData, $offset); |
|
4571
|
|
|
$offset += 2; |
|
4572
|
|
|
|
|
4573
|
|
|
// offset: var; size: 2; not used |
|
4574
|
|
|
$offset += 2; |
|
4575
|
|
|
|
|
4576
|
|
|
// offset: var; size: $sz1; formula data for first condition (without size field) |
|
4577
|
|
|
$formula1 = substr($recordData, $offset, $sz1); |
|
4578
|
|
|
$formula1 = pack('v', $sz1) . $formula1; // prepend the length |
|
4579
|
|
|
try { |
|
4580
|
|
|
$formula1 = $this->_getFormulaFromStructure($formula1); |
|
4581
|
|
|
|
|
4582
|
|
|
// in list type validity, null characters are used as item separators |
|
4583
|
|
|
if ($type == PHPExcel_Cell_DataValidation::TYPE_LIST) { |
|
4584
|
|
|
$formula1 = str_replace(chr(0), ',', $formula1); |
|
4585
|
|
|
} |
|
4586
|
|
|
} catch (Exception $e) { |
|
4587
|
|
|
return; |
|
4588
|
|
|
} |
|
4589
|
|
|
$offset += $sz1; |
|
4590
|
|
|
|
|
4591
|
|
|
// offset: var; size: 2; size of the formula data for the first condition |
|
4592
|
|
|
$sz2 = self::_GetInt2d($recordData, $offset); |
|
4593
|
|
|
$offset += 2; |
|
4594
|
|
|
|
|
4595
|
|
|
// offset: var; size: 2; not used |
|
4596
|
|
|
$offset += 2; |
|
4597
|
|
|
|
|
4598
|
|
|
// offset: var; size: $sz2; formula data for second condition (without size field) |
|
4599
|
|
|
$formula2 = substr($recordData, $offset, $sz2); |
|
4600
|
|
|
$formula2 = pack('v', $sz2) . $formula2; // prepend the length |
|
4601
|
|
|
try { |
|
4602
|
|
|
$formula2 = $this->_getFormulaFromStructure($formula2); |
|
4603
|
|
|
} catch (Exception $e) { |
|
4604
|
|
|
return; |
|
4605
|
|
|
} |
|
4606
|
|
|
$offset += $sz2; |
|
4607
|
|
|
|
|
4608
|
|
|
// offset: var; size: var; cell range address list with |
|
4609
|
|
|
$cellRangeAddressList = $this->_readBIFF8CellRangeAddressList(substr($recordData, $offset)); |
|
4610
|
|
|
$cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses']; |
|
4611
|
|
|
|
|
4612
|
|
|
foreach ($cellRangeAddresses as $cellRange) { |
|
4613
|
|
|
$stRange = $this->_phpSheet->shrinkRangeToFit($cellRange); |
|
4614
|
|
|
$stRange = PHPExcel_Cell::extractAllCellReferencesInRange($stRange); |
|
4615
|
|
|
foreach ($stRange as $coordinate) { |
|
4616
|
|
|
$objValidation = $this->_phpSheet->getCell($coordinate)->getDataValidation(); |
|
4617
|
|
|
$objValidation->setType($type); |
|
4618
|
|
|
$objValidation->setErrorStyle($errorStyle); |
|
4619
|
|
|
$objValidation->setAllowBlank((bool)$allowBlank); |
|
4620
|
|
|
$objValidation->setShowInputMessage((bool)$showInputMessage); |
|
4621
|
|
|
$objValidation->setShowErrorMessage((bool)$showErrorMessage); |
|
4622
|
|
|
$objValidation->setShowDropDown(!$suppressDropDown); |
|
4623
|
|
|
$objValidation->setOperator($operator); |
|
4624
|
|
|
$objValidation->setErrorTitle($errorTitle); |
|
4625
|
|
|
$objValidation->setError($error); |
|
4626
|
|
|
$objValidation->setPromptTitle($promptTitle); |
|
4627
|
|
|
$objValidation->setPrompt($prompt); |
|
4628
|
|
|
$objValidation->setFormula1($formula1); |
|
4629
|
|
|
$objValidation->setFormula2($formula2); |
|
4630
|
|
|
} |
|
4631
|
|
|
} |
|
4632
|
|
|
|
|
4633
|
|
|
} |
|
4634
|
|
|
|
|
4635
|
|
|
|
|
4636
|
|
|
/** |
|
4637
|
|
|
* Read SHEETLAYOUT record. Stores sheet tab color information. |
|
4638
|
|
|
*/ |
|
4639
|
|
|
private function _readSheetLayout() |
|
4640
|
|
|
{ |
|
4641
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4642
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4643
|
|
|
|
|
4644
|
|
|
// move stream pointer to next record |
|
4645
|
|
|
$this->_pos += 4 + $length; |
|
4646
|
|
|
|
|
4647
|
|
|
// local pointer in record data |
|
4648
|
|
|
$offset = 0; |
|
4649
|
|
|
|
|
4650
|
|
|
if (!$this->_readDataOnly) { |
|
4651
|
|
|
// offset: 0; size: 2; repeated record identifier 0x0862 |
|
4652
|
|
|
|
|
4653
|
|
|
// offset: 2; size: 10; not used |
|
4654
|
|
|
|
|
4655
|
|
|
// offset: 12; size: 4; size of record data |
|
4656
|
|
|
// Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?) |
|
4657
|
|
|
$sz = self::_GetInt4d($recordData, 12); |
|
4658
|
|
|
|
|
4659
|
|
|
switch ($sz) { |
|
4660
|
|
|
case 0x14: |
|
4661
|
|
|
// offset: 16; size: 2; color index for sheet tab |
|
4662
|
|
|
$colorIndex = self::_GetInt2d($recordData, 16); |
|
4663
|
|
|
$color = self::_readColor($colorIndex,$this->_palette,$this->_version); |
|
4664
|
|
|
$this->_phpSheet->getTabColor()->setRGB($color['rgb']); |
|
4665
|
|
|
break; |
|
4666
|
|
|
|
|
4667
|
|
|
case 0x28: |
|
4668
|
|
|
// TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007 |
|
4669
|
|
|
return; |
|
4670
|
|
|
break; |
|
|
|
|
|
|
4671
|
|
|
} |
|
4672
|
|
|
} |
|
4673
|
|
|
} |
|
4674
|
|
|
|
|
4675
|
|
|
|
|
4676
|
|
|
/** |
|
4677
|
|
|
* Read SHEETPROTECTION record (FEATHEADR) |
|
4678
|
|
|
*/ |
|
4679
|
|
|
private function _readSheetProtection() |
|
4680
|
|
|
{ |
|
4681
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4682
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4683
|
|
|
|
|
4684
|
|
|
// move stream pointer to next record |
|
4685
|
|
|
$this->_pos += 4 + $length; |
|
4686
|
|
|
|
|
4687
|
|
|
if ($this->_readDataOnly) { |
|
4688
|
|
|
return; |
|
4689
|
|
|
} |
|
4690
|
|
|
|
|
4691
|
|
|
// offset: 0; size: 2; repeated record header |
|
4692
|
|
|
|
|
4693
|
|
|
// offset: 2; size: 2; FRT cell reference flag (=0 currently) |
|
4694
|
|
|
|
|
4695
|
|
|
// offset: 4; size: 8; Currently not used and set to 0 |
|
4696
|
|
|
|
|
4697
|
|
|
// offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag) |
|
4698
|
|
|
$isf = self::_GetInt2d($recordData, 12); |
|
4699
|
|
|
if ($isf != 2) { |
|
4700
|
|
|
return; |
|
4701
|
|
|
} |
|
4702
|
|
|
|
|
4703
|
|
|
// offset: 14; size: 1; =1 since this is a feat header |
|
4704
|
|
|
|
|
4705
|
|
|
// offset: 15; size: 4; size of rgbHdrSData |
|
4706
|
|
|
|
|
4707
|
|
|
// rgbHdrSData, assume "Enhanced Protection" |
|
4708
|
|
|
// offset: 19; size: 2; option flags |
|
4709
|
|
|
$options = self::_GetInt2d($recordData, 19); |
|
4710
|
|
|
|
|
4711
|
|
|
// bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects |
|
4712
|
|
|
$bool = (0x0001 & $options) >> 0; |
|
4713
|
|
|
$this->_phpSheet->getProtection()->setObjects(!$bool); |
|
4714
|
|
|
|
|
4715
|
|
|
// bit: 1; mask 0x0002; edit scenarios |
|
4716
|
|
|
$bool = (0x0002 & $options) >> 1; |
|
4717
|
|
|
$this->_phpSheet->getProtection()->setScenarios(!$bool); |
|
4718
|
|
|
|
|
4719
|
|
|
// bit: 2; mask 0x0004; format cells |
|
4720
|
|
|
$bool = (0x0004 & $options) >> 2; |
|
4721
|
|
|
$this->_phpSheet->getProtection()->setFormatCells(!$bool); |
|
4722
|
|
|
|
|
4723
|
|
|
// bit: 3; mask 0x0008; format columns |
|
4724
|
|
|
$bool = (0x0008 & $options) >> 3; |
|
4725
|
|
|
$this->_phpSheet->getProtection()->setFormatColumns(!$bool); |
|
4726
|
|
|
|
|
4727
|
|
|
// bit: 4; mask 0x0010; format rows |
|
4728
|
|
|
$bool = (0x0010 & $options) >> 4; |
|
4729
|
|
|
$this->_phpSheet->getProtection()->setFormatRows(!$bool); |
|
4730
|
|
|
|
|
4731
|
|
|
// bit: 5; mask 0x0020; insert columns |
|
4732
|
|
|
$bool = (0x0020 & $options) >> 5; |
|
4733
|
|
|
$this->_phpSheet->getProtection()->setInsertColumns(!$bool); |
|
4734
|
|
|
|
|
4735
|
|
|
// bit: 6; mask 0x0040; insert rows |
|
4736
|
|
|
$bool = (0x0040 & $options) >> 6; |
|
4737
|
|
|
$this->_phpSheet->getProtection()->setInsertRows(!$bool); |
|
4738
|
|
|
|
|
4739
|
|
|
// bit: 7; mask 0x0080; insert hyperlinks |
|
4740
|
|
|
$bool = (0x0080 & $options) >> 7; |
|
4741
|
|
|
$this->_phpSheet->getProtection()->setInsertHyperlinks(!$bool); |
|
4742
|
|
|
|
|
4743
|
|
|
// bit: 8; mask 0x0100; delete columns |
|
4744
|
|
|
$bool = (0x0100 & $options) >> 8; |
|
4745
|
|
|
$this->_phpSheet->getProtection()->setDeleteColumns(!$bool); |
|
4746
|
|
|
|
|
4747
|
|
|
// bit: 9; mask 0x0200; delete rows |
|
4748
|
|
|
$bool = (0x0200 & $options) >> 9; |
|
4749
|
|
|
$this->_phpSheet->getProtection()->setDeleteRows(!$bool); |
|
4750
|
|
|
|
|
4751
|
|
|
// bit: 10; mask 0x0400; select locked cells |
|
4752
|
|
|
$bool = (0x0400 & $options) >> 10; |
|
4753
|
|
|
$this->_phpSheet->getProtection()->setSelectLockedCells(!$bool); |
|
4754
|
|
|
|
|
4755
|
|
|
// bit: 11; mask 0x0800; sort cell range |
|
4756
|
|
|
$bool = (0x0800 & $options) >> 11; |
|
4757
|
|
|
$this->_phpSheet->getProtection()->setSort(!$bool); |
|
4758
|
|
|
|
|
4759
|
|
|
// bit: 12; mask 0x1000; auto filter |
|
4760
|
|
|
$bool = (0x1000 & $options) >> 12; |
|
4761
|
|
|
$this->_phpSheet->getProtection()->setAutoFilter(!$bool); |
|
4762
|
|
|
|
|
4763
|
|
|
// bit: 13; mask 0x2000; pivot tables |
|
4764
|
|
|
$bool = (0x2000 & $options) >> 13; |
|
4765
|
|
|
$this->_phpSheet->getProtection()->setPivotTables(!$bool); |
|
4766
|
|
|
|
|
4767
|
|
|
// bit: 14; mask 0x4000; select unlocked cells |
|
4768
|
|
|
$bool = (0x4000 & $options) >> 14; |
|
4769
|
|
|
$this->_phpSheet->getProtection()->setSelectUnlockedCells(!$bool); |
|
4770
|
|
|
|
|
4771
|
|
|
// offset: 21; size: 2; not used |
|
4772
|
|
|
} |
|
4773
|
|
|
|
|
4774
|
|
|
|
|
4775
|
|
|
/** |
|
4776
|
|
|
* Read RANGEPROTECTION record |
|
4777
|
|
|
* Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification, |
|
4778
|
|
|
* where it is referred to as FEAT record |
|
4779
|
|
|
*/ |
|
4780
|
|
|
private function _readRangeProtection() |
|
4781
|
|
|
{ |
|
4782
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4783
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4784
|
|
|
|
|
4785
|
|
|
// move stream pointer to next record |
|
4786
|
|
|
$this->_pos += 4 + $length; |
|
4787
|
|
|
|
|
4788
|
|
|
// local pointer in record data |
|
4789
|
|
|
$offset = 0; |
|
4790
|
|
|
|
|
4791
|
|
|
if (!$this->_readDataOnly) { |
|
4792
|
|
|
$offset += 12; |
|
4793
|
|
|
|
|
4794
|
|
|
// offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag |
|
4795
|
|
|
$isf = self::_GetInt2d($recordData, 12); |
|
4796
|
|
|
if ($isf != 2) { |
|
4797
|
|
|
// we only read FEAT records of type 2 |
|
4798
|
|
|
return; |
|
4799
|
|
|
} |
|
4800
|
|
|
$offset += 2; |
|
4801
|
|
|
|
|
4802
|
|
|
$offset += 5; |
|
4803
|
|
|
|
|
4804
|
|
|
// offset: 19; size: 2; count of ref ranges this feature is on |
|
4805
|
|
|
$cref = self::_GetInt2d($recordData, 19); |
|
4806
|
|
|
$offset += 2; |
|
4807
|
|
|
|
|
4808
|
|
|
$offset += 6; |
|
4809
|
|
|
|
|
4810
|
|
|
// offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record) |
|
4811
|
|
|
$cellRanges = array(); |
|
4812
|
|
|
for ($i = 0; $i < $cref; ++$i) { |
|
4813
|
|
|
try { |
|
4814
|
|
|
$cellRange = $this->_readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8)); |
|
4815
|
|
|
} catch (Exception $e) { |
|
4816
|
|
|
return; |
|
4817
|
|
|
} |
|
4818
|
|
|
$cellRanges[] = $cellRange; |
|
4819
|
|
|
$offset += 8; |
|
4820
|
|
|
} |
|
4821
|
|
|
|
|
4822
|
|
|
// offset: var; size: var; variable length of feature specific data |
|
4823
|
|
|
$rgbFeat = substr($recordData, $offset); |
|
4824
|
|
|
$offset += 4; |
|
4825
|
|
|
|
|
4826
|
|
|
// offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit) |
|
4827
|
|
|
$wPassword = self::_GetInt4d($recordData, $offset); |
|
4828
|
|
|
$offset += 4; |
|
4829
|
|
|
|
|
4830
|
|
|
// Apply range protection to sheet |
|
4831
|
|
|
if ($cellRanges) { |
|
|
|
|
|
|
4832
|
|
|
$this->_phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true); |
|
4833
|
|
|
} |
|
4834
|
|
|
} |
|
4835
|
|
|
} |
|
4836
|
|
|
|
|
4837
|
|
|
|
|
4838
|
|
|
/** |
|
4839
|
|
|
* Read IMDATA record |
|
4840
|
|
|
*/ |
|
4841
|
|
|
private function _readImData() |
|
|
|
|
|
|
4842
|
|
|
{ |
|
4843
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4844
|
|
|
|
|
4845
|
|
|
// get spliced record data |
|
4846
|
|
|
$splicedRecordData = $this->_getSplicedRecordData(); |
|
4847
|
|
|
$recordData = $splicedRecordData['recordData']; |
|
4848
|
|
|
|
|
4849
|
|
|
// UNDER CONSTRUCTION |
|
4850
|
|
|
|
|
4851
|
|
|
// offset: 0; size: 2; image format |
|
4852
|
|
|
$cf = self::_GetInt2d($recordData, 0); |
|
4853
|
|
|
|
|
4854
|
|
|
// offset: 2; size: 2; environment from which the file was written |
|
4855
|
|
|
$env = self::_GetInt2d($recordData, 2); |
|
4856
|
|
|
|
|
4857
|
|
|
// offset: 4; size: 4; length of the image data |
|
4858
|
|
|
$lcb = self::_GetInt4d($recordData, 4); |
|
4859
|
|
|
|
|
4860
|
|
|
// offset: 8; size: var; image data |
|
4861
|
|
|
$iData = substr($recordData, 8); |
|
4862
|
|
|
|
|
4863
|
|
|
switch ($cf) { |
|
4864
|
|
|
case 0x09: // Windows bitmap format |
|
4865
|
|
|
// BITMAPCOREINFO |
|
4866
|
|
|
// 1. BITMAPCOREHEADER |
|
4867
|
|
|
// offset: 0; size: 4; bcSize, Specifies the number of bytes required by the structure |
|
4868
|
|
|
$bcSize = self::_GetInt4d($iData, 0); |
|
4869
|
|
|
// var_dump($bcSize); |
|
4870
|
|
|
|
|
4871
|
|
|
// offset: 4; size: 2; bcWidth, specifies the width of the bitmap, in pixels |
|
4872
|
|
|
$bcWidth = self::_GetInt2d($iData, 4); |
|
4873
|
|
|
// var_dump($bcWidth); |
|
4874
|
|
|
|
|
4875
|
|
|
// offset: 6; size: 2; bcHeight, specifies the height of the bitmap, in pixels. |
|
4876
|
|
|
$bcHeight = self::_GetInt2d($iData, 6); |
|
4877
|
|
|
// var_dump($bcHeight); |
|
4878
|
|
|
$ih = imagecreatetruecolor($bcWidth, $bcHeight); |
|
4879
|
|
|
|
|
4880
|
|
|
// offset: 8; size: 2; bcPlanes, specifies the number of planes for the target device. This value must be 1 |
|
4881
|
|
|
|
|
4882
|
|
|
// offset: 10; size: 2; bcBitCount specifies the number of bits-per-pixel. This value must be 1, 4, 8, or 24 |
|
4883
|
|
|
$bcBitCount = self::_GetInt2d($iData, 10); |
|
4884
|
|
|
// var_dump($bcBitCount); |
|
4885
|
|
|
|
|
4886
|
|
|
$rgbString = substr($iData, 12); |
|
4887
|
|
|
$rgbTriples = array(); |
|
4888
|
|
|
while (strlen($rgbString) > 0) { |
|
4889
|
|
|
$rgbTriples[] = unpack('Cb/Cg/Cr', $rgbString); |
|
4890
|
|
|
$rgbString = substr($rgbString, 3); |
|
4891
|
|
|
} |
|
4892
|
|
|
$x = 0; |
|
4893
|
|
|
$y = 0; |
|
4894
|
|
|
foreach ($rgbTriples as $i => $rgbTriple) { |
|
4895
|
|
|
$color = imagecolorallocate($ih, $rgbTriple['r'], $rgbTriple['g'], $rgbTriple['b']); |
|
4896
|
|
|
imagesetpixel($ih, $x, $bcHeight - 1 - $y, $color); |
|
4897
|
|
|
$x = ($x + 1) % $bcWidth; |
|
4898
|
|
|
$y = $y + floor(($x + 1) / $bcWidth); |
|
4899
|
|
|
} |
|
4900
|
|
|
//imagepng($ih, 'image.png'); |
|
4901
|
|
|
|
|
4902
|
|
|
$drawing = new PHPExcel_Worksheet_Drawing(); |
|
4903
|
|
|
$drawing->setPath($filename); |
|
|
|
|
|
|
4904
|
|
|
$drawing->setWorksheet($this->_phpSheet); |
|
4905
|
|
|
|
|
4906
|
|
|
break; |
|
4907
|
|
|
|
|
4908
|
|
|
case 0x02: // Windows metafile or Macintosh PICT format |
|
4909
|
|
|
case 0x0e: // native format |
|
4910
|
|
|
default; |
|
|
|
|
|
|
4911
|
|
|
break; |
|
4912
|
|
|
|
|
4913
|
|
|
} |
|
4914
|
|
|
|
|
4915
|
|
|
// _getSplicedRecordData() takes care of moving current position in data stream |
|
4916
|
|
|
} |
|
4917
|
|
|
|
|
4918
|
|
|
|
|
4919
|
|
|
/** |
|
4920
|
|
|
* Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record |
|
4921
|
|
|
* When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented. |
|
4922
|
|
|
* In this case, we must treat the CONTINUE record as a MSODRAWING record |
|
4923
|
|
|
*/ |
|
4924
|
|
|
private function _readContinue() |
|
4925
|
|
|
{ |
|
4926
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4927
|
|
|
$recordData = substr($this->_data, $this->_pos + 4, $length); |
|
4928
|
|
|
|
|
4929
|
|
|
// check if we are reading drawing data |
|
4930
|
|
|
// this is in case a free CONTINUE record occurs in other circumstances we are unaware of |
|
4931
|
|
|
if ($this->_drawingData == '') { |
|
4932
|
|
|
// move stream pointer to next record |
|
4933
|
|
|
$this->_pos += 4 + $length; |
|
4934
|
|
|
|
|
4935
|
|
|
return; |
|
4936
|
|
|
} |
|
4937
|
|
|
|
|
4938
|
|
|
// check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data |
|
4939
|
|
|
if ($length < 4) { |
|
4940
|
|
|
// move stream pointer to next record |
|
4941
|
|
|
$this->_pos += 4 + $length; |
|
4942
|
|
|
|
|
4943
|
|
|
return; |
|
4944
|
|
|
} |
|
4945
|
|
|
|
|
4946
|
|
|
// dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record |
|
4947
|
|
|
// look inside CONTINUE record to see if it looks like a part of an Escher stream |
|
4948
|
|
|
// we know that Escher stream may be split at least at |
|
4949
|
|
|
// 0xF003 MsofbtSpgrContainer |
|
4950
|
|
|
// 0xF004 MsofbtSpContainer |
|
4951
|
|
|
// 0xF00D MsofbtClientTextbox |
|
4952
|
|
|
$validSplitPoints = array(0xF003, 0xF004, 0xF00D); // add identifiers if we find more |
|
4953
|
|
|
|
|
4954
|
|
|
$splitPoint = self::_GetInt2d($recordData, 2); |
|
4955
|
|
|
if (in_array($splitPoint, $validSplitPoints)) { |
|
4956
|
|
|
// get spliced record data (and move pointer to next record) |
|
4957
|
|
|
$splicedRecordData = $this->_getSplicedRecordData(); |
|
4958
|
|
|
$this->_drawingData .= $splicedRecordData['recordData']; |
|
4959
|
|
|
|
|
4960
|
|
|
return; |
|
4961
|
|
|
} |
|
4962
|
|
|
|
|
4963
|
|
|
// move stream pointer to next record |
|
4964
|
|
|
$this->_pos += 4 + $length; |
|
4965
|
|
|
|
|
4966
|
|
|
} |
|
4967
|
|
|
|
|
4968
|
|
|
|
|
4969
|
|
|
/** |
|
4970
|
|
|
* Reads a record from current position in data stream and continues reading data as long as CONTINUE |
|
4971
|
|
|
* records are found. Splices the record data pieces and returns the combined string as if record data |
|
4972
|
|
|
* is in one piece. |
|
4973
|
|
|
* Moves to next current position in data stream to start of next record different from a CONtINUE record |
|
4974
|
|
|
* |
|
4975
|
|
|
* @return array |
|
4976
|
|
|
*/ |
|
4977
|
|
|
private function _getSplicedRecordData() |
|
4978
|
|
|
{ |
|
4979
|
|
|
$data = ''; |
|
4980
|
|
|
$spliceOffsets = array(); |
|
4981
|
|
|
|
|
4982
|
|
|
$i = 0; |
|
4983
|
|
|
$spliceOffsets[0] = 0; |
|
4984
|
|
|
|
|
4985
|
|
|
do { |
|
4986
|
|
|
++$i; |
|
4987
|
|
|
|
|
4988
|
|
|
// offset: 0; size: 2; identifier |
|
4989
|
|
|
$identifier = self::_GetInt2d($this->_data, $this->_pos); |
|
4990
|
|
|
// offset: 2; size: 2; length |
|
4991
|
|
|
$length = self::_GetInt2d($this->_data, $this->_pos + 2); |
|
4992
|
|
|
$data .= substr($this->_data, $this->_pos + 4, $length); |
|
4993
|
|
|
|
|
4994
|
|
|
$spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length; |
|
4995
|
|
|
|
|
4996
|
|
|
$this->_pos += 4 + $length; |
|
4997
|
|
|
$nextIdentifier = self::_GetInt2d($this->_data, $this->_pos); |
|
4998
|
|
|
} |
|
4999
|
|
|
while ($nextIdentifier == self::XLS_Type_CONTINUE); |
|
5000
|
|
|
|
|
5001
|
|
|
$splicedData = array( |
|
5002
|
|
|
'recordData' => $data, |
|
5003
|
|
|
'spliceOffsets' => $spliceOffsets, |
|
5004
|
|
|
); |
|
5005
|
|
|
|
|
5006
|
|
|
return $splicedData; |
|
5007
|
|
|
|
|
5008
|
|
|
} |
|
5009
|
|
|
|
|
5010
|
|
|
|
|
5011
|
|
|
/** |
|
5012
|
|
|
* Convert formula structure into human readable Excel formula like 'A3+A5*5' |
|
5013
|
|
|
* |
|
5014
|
|
|
* @param string $formulaStructure The complete binary data for the formula |
|
5015
|
|
|
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas |
|
5016
|
|
|
* @return string Human readable formula |
|
5017
|
|
|
*/ |
|
5018
|
|
|
private function _getFormulaFromStructure($formulaStructure, $baseCell = 'A1') |
|
5019
|
|
|
{ |
|
5020
|
|
|
// offset: 0; size: 2; size of the following formula data |
|
5021
|
|
|
$sz = self::_GetInt2d($formulaStructure, 0); |
|
5022
|
|
|
|
|
5023
|
|
|
// offset: 2; size: sz |
|
5024
|
|
|
$formulaData = substr($formulaStructure, 2, $sz); |
|
5025
|
|
|
|
|
5026
|
|
|
// for debug: dump the formula data |
|
5027
|
|
|
//echo '<xmp>'; |
|
5028
|
|
|
//echo 'size: ' . $sz . "\n"; |
|
5029
|
|
|
//echo 'the entire formula data: '; |
|
5030
|
|
|
//Debug::dump($formulaData); |
|
5031
|
|
|
//echo "\n----\n"; |
|
5032
|
|
|
|
|
5033
|
|
|
// offset: 2 + sz; size: variable (optional) |
|
5034
|
|
|
if (strlen($formulaStructure) > 2 + $sz) { |
|
5035
|
|
|
$additionalData = substr($formulaStructure, 2 + $sz); |
|
5036
|
|
|
|
|
5037
|
|
|
// for debug: dump the additional data |
|
5038
|
|
|
//echo 'the entire additional data: '; |
|
5039
|
|
|
//Debug::dump($additionalData); |
|
5040
|
|
|
//echo "\n----\n"; |
|
5041
|
|
|
|
|
5042
|
|
|
} else { |
|
5043
|
|
|
$additionalData = ''; |
|
5044
|
|
|
} |
|
5045
|
|
|
|
|
5046
|
|
|
return $this->_getFormulaFromData($formulaData, $additionalData, $baseCell); |
|
5047
|
|
|
} |
|
5048
|
|
|
|
|
5049
|
|
|
|
|
5050
|
|
|
/** |
|
5051
|
|
|
* Take formula data and additional data for formula and return human readable formula |
|
5052
|
|
|
* |
|
5053
|
|
|
* @param string $formulaData The binary data for the formula itself |
|
5054
|
|
|
* @param string $additionalData Additional binary data going with the formula |
|
5055
|
|
|
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas |
|
5056
|
|
|
* @return string Human readable formula |
|
5057
|
|
|
*/ |
|
5058
|
|
|
private function _getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1') |
|
5059
|
|
|
{ |
|
5060
|
|
|
// start parsing the formula data |
|
5061
|
|
|
$tokens = array(); |
|
5062
|
|
|
|
|
5063
|
|
|
while (strlen($formulaData) > 0 and $token = $this->_getNextToken($formulaData, $baseCell)) { |
|
|
|
|
|
|
5064
|
|
|
$tokens[] = $token; |
|
5065
|
|
|
$formulaData = substr($formulaData, $token['size']); |
|
5066
|
|
|
|
|
5067
|
|
|
// for debug: dump the token |
|
5068
|
|
|
//var_dump($token); |
|
5069
|
|
|
} |
|
5070
|
|
|
|
|
5071
|
|
|
$formulaString = $this->_createFormulaFromTokens($tokens, $additionalData); |
|
5072
|
|
|
|
|
5073
|
|
|
return $formulaString; |
|
5074
|
|
|
} |
|
5075
|
|
|
|
|
5076
|
|
|
|
|
5077
|
|
|
/** |
|
5078
|
|
|
* Take array of tokens together with additional data for formula and return human readable formula |
|
5079
|
|
|
* |
|
5080
|
|
|
* @param array $tokens |
|
5081
|
|
|
* @param array $additionalData Additional binary data going with the formula |
|
5082
|
|
|
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas |
|
|
|
|
|
|
5083
|
|
|
* @return string Human readable formula |
|
5084
|
|
|
*/ |
|
5085
|
|
|
private function _createFormulaFromTokens($tokens, $additionalData) |
|
5086
|
|
|
{ |
|
5087
|
|
|
// empty formula? |
|
5088
|
|
|
if (empty($tokens)) { |
|
5089
|
|
|
return ''; |
|
5090
|
|
|
} |
|
5091
|
|
|
|
|
5092
|
|
|
$formulaStrings = array(); |
|
5093
|
|
|
foreach ($tokens as $token) { |
|
5094
|
|
|
// initialize spaces |
|
5095
|
|
|
$space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen |
|
5096
|
|
|
$space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen |
|
5097
|
|
|
$space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis |
|
5098
|
|
|
$space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis |
|
5099
|
|
|
$space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis |
|
5100
|
|
|
$space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis |
|
5101
|
|
|
|
|
5102
|
|
|
switch ($token['name']) { |
|
5103
|
|
|
case 'tAdd': // addition |
|
5104
|
|
|
case 'tConcat': // addition |
|
5105
|
|
|
case 'tDiv': // division |
|
5106
|
|
|
case 'tEQ': // equality |
|
5107
|
|
|
case 'tGE': // greater than or equal |
|
5108
|
|
|
case 'tGT': // greater than |
|
5109
|
|
|
case 'tIsect': // intersection |
|
5110
|
|
|
case 'tLE': // less than or equal |
|
5111
|
|
|
case 'tList': // less than or equal |
|
5112
|
|
|
case 'tLT': // less than |
|
5113
|
|
|
case 'tMul': // multiplication |
|
5114
|
|
|
case 'tNE': // multiplication |
|
5115
|
|
|
case 'tPower': // power |
|
5116
|
|
|
case 'tRange': // range |
|
5117
|
|
|
case 'tSub': // subtraction |
|
5118
|
|
|
$op2 = array_pop($formulaStrings); |
|
5119
|
|
|
$op1 = array_pop($formulaStrings); |
|
5120
|
|
|
$formulaStrings[] = "$op1$space1$space0{$token['data']}$op2"; |
|
5121
|
|
|
unset($space0, $space1); |
|
5122
|
|
|
break; |
|
5123
|
|
|
case 'tUplus': // unary plus |
|
5124
|
|
View Code Duplication |
case 'tUminus': // unary minus |
|
5125
|
|
|
$op = array_pop($formulaStrings); |
|
5126
|
|
|
$formulaStrings[] = "$space1$space0{$token['data']}$op"; |
|
5127
|
|
|
unset($space0, $space1); |
|
5128
|
|
|
break; |
|
5129
|
|
View Code Duplication |
case 'tPercent': // percent sign |
|
5130
|
|
|
$op = array_pop($formulaStrings); |
|
5131
|
|
|
$formulaStrings[] = "$op$space1$space0{$token['data']}"; |
|
5132
|
|
|
unset($space0, $space1); |
|
5133
|
|
|
break; |
|
5134
|
|
|
case 'tAttrVolatile': // indicates volatile function |
|
5135
|
|
|
case 'tAttrIf': |
|
5136
|
|
|
case 'tAttrSkip': |
|
5137
|
|
|
case 'tAttrChoose': |
|
5138
|
|
|
// token is only important for Excel formula evaluator |
|
5139
|
|
|
// do nothing |
|
5140
|
|
|
break; |
|
5141
|
|
|
case 'tAttrSpace': // space / carriage return |
|
5142
|
|
|
// space will be used when next token arrives, do not alter formulaString stack |
|
5143
|
|
|
switch ($token['data']['spacetype']) { |
|
5144
|
|
|
case 'type0': |
|
5145
|
|
|
$space0 = str_repeat(' ', $token['data']['spacecount']); |
|
5146
|
|
|
break; |
|
5147
|
|
|
case 'type1': |
|
5148
|
|
|
$space1 = str_repeat("\n", $token['data']['spacecount']); |
|
5149
|
|
|
break; |
|
5150
|
|
|
case 'type2': |
|
5151
|
|
|
$space2 = str_repeat(' ', $token['data']['spacecount']); |
|
5152
|
|
|
break; |
|
5153
|
|
|
case 'type3': |
|
5154
|
|
|
$space3 = str_repeat("\n", $token['data']['spacecount']); |
|
5155
|
|
|
break; |
|
5156
|
|
|
case 'type4': |
|
5157
|
|
|
$space4 = str_repeat(' ', $token['data']['spacecount']); |
|
5158
|
|
|
break; |
|
5159
|
|
|
case 'type5': |
|
5160
|
|
|
$space5 = str_repeat("\n", $token['data']['spacecount']); |
|
5161
|
|
|
break; |
|
5162
|
|
|
} |
|
5163
|
|
|
break; |
|
5164
|
|
|
case 'tAttrSum': // SUM function with one parameter |
|
5165
|
|
|
$op = array_pop($formulaStrings); |
|
5166
|
|
|
$formulaStrings[] = "{$space1}{$space0}SUM($op)"; |
|
5167
|
|
|
unset($space0, $space1); |
|
5168
|
|
|
break; |
|
5169
|
|
|
case 'tFunc': // function with fixed number of arguments |
|
5170
|
|
|
case 'tFuncV': // function with variable number of arguments |
|
5171
|
|
|
if ($token['data']['function'] != '') { |
|
5172
|
|
|
// normal function |
|
5173
|
|
|
$ops = array(); // array of operators |
|
5174
|
|
View Code Duplication |
for ($i = 0; $i < $token['data']['args']; ++$i) { |
|
5175
|
|
|
$ops[] = array_pop($formulaStrings); |
|
5176
|
|
|
} |
|
5177
|
|
|
$ops = array_reverse($ops); |
|
5178
|
|
|
$formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ")"; |
|
5179
|
|
|
unset($space0, $space1); |
|
5180
|
|
|
} else { |
|
5181
|
|
|
// add-in function |
|
5182
|
|
|
$ops = array(); // array of operators |
|
5183
|
|
View Code Duplication |
for ($i = 0; $i < $token['data']['args'] - 1; ++$i) { |
|
5184
|
|
|
$ops[] = array_pop($formulaStrings); |
|
5185
|
|
|
} |
|
5186
|
|
|
$ops = array_reverse($ops); |
|
5187
|
|
|
$function = array_pop($formulaStrings); |
|
5188
|
|
|
$formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ")"; |
|
5189
|
|
|
unset($space0, $space1); |
|
5190
|
|
|
} |
|
5191
|
|
|
break; |
|
5192
|
|
|
case 'tParen': // parenthesis |
|
5193
|
|
|
$expression = array_pop($formulaStrings); |
|
5194
|
|
|
$formulaStrings[] = "$space3$space2($expression$space5$space4)"; |
|
5195
|
|
|
unset($space2, $space3, $space4, $space5); |
|
5196
|
|
|
break; |
|
5197
|
|
|
case 'tArray': // array constant |
|
5198
|
|
|
$constantArray = self::_readBIFF8ConstantArray($additionalData); |
|
|
|
|
|
|
5199
|
|
|
$formulaStrings[] = $space1 . $space0 . $constantArray['value']; |
|
5200
|
|
|
$additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data |
|
5201
|
|
|
unset($space0, $space1); |
|
5202
|
|
|
break; |
|
5203
|
|
|
case 'tMemArea': |
|
5204
|
|
|
// bite off chunk of additional data |
|
5205
|
|
|
$cellRangeAddressList = $this->_readBIFF8CellRangeAddressList($additionalData); |
|
|
|
|
|
|
5206
|
|
|
$additionalData = substr($additionalData, $cellRangeAddressList['size']); |
|
5207
|
|
|
$formulaStrings[] = "$space1$space0{$token['data']}"; |
|
5208
|
|
|
unset($space0, $space1); |
|
5209
|
|
|
break; |
|
5210
|
|
|
case 'tArea': // cell range address |
|
5211
|
|
|
case 'tBool': // boolean |
|
5212
|
|
|
case 'tErr': // error code |
|
5213
|
|
|
case 'tInt': // integer |
|
5214
|
|
|
case 'tMemErr': |
|
5215
|
|
|
case 'tMemFunc': |
|
5216
|
|
|
case 'tMissArg': |
|
5217
|
|
|
case 'tName': |
|
5218
|
|
|
case 'tNameX': |
|
5219
|
|
|
case 'tNum': // number |
|
5220
|
|
|
case 'tRef': // single cell reference |
|
5221
|
|
|
case 'tRef3d': // 3d cell reference |
|
5222
|
|
|
case 'tArea3d': // 3d cell range reference |
|
5223
|
|
|
case 'tRefN': |
|
5224
|
|
|
case 'tAreaN': |
|
5225
|
|
|
case 'tStr': // string |
|
5226
|
|
|
$formulaStrings[] = "$space1$space0{$token['data']}"; |
|
5227
|
|
|
unset($space0, $space1); |
|
5228
|
|
|
break; |
|
5229
|
|
|
} |
|
5230
|
|
|
} |
|
5231
|
|
|
$formulaString = $formulaStrings[0]; |
|
5232
|
|
|
|
|
5233
|
|
|
// for debug: dump the human readable formula |
|
5234
|
|
|
//echo '----' . "\n"; |
|
5235
|
|
|
//echo 'Formula: ' . $formulaString; |
|
5236
|
|
|
|
|
5237
|
|
|
return $formulaString; |
|
5238
|
|
|
} |
|
5239
|
|
|
|
|
5240
|
|
|
|
|
5241
|
|
|
/** |
|
5242
|
|
|
* Fetch next token from binary formula data |
|
5243
|
|
|
* |
|
5244
|
|
|
* @param string Formula data |
|
5245
|
|
|
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas |
|
5246
|
|
|
* @return array |
|
5247
|
|
|
* @throws Exception |
|
5248
|
|
|
*/ |
|
5249
|
|
|
private function _getNextToken($formulaData, $baseCell = 'A1') |
|
5250
|
|
|
{ |
|
5251
|
|
|
// offset: 0; size: 1; token id |
|
5252
|
|
|
$id = ord($formulaData[0]); // token id |
|
5253
|
|
|
$name = false; // initialize token name |
|
5254
|
|
|
|
|
5255
|
|
|
switch ($id) { |
|
5256
|
|
|
case 0x03: $name = 'tAdd'; $size = 1; $data = '+'; break; |
|
5257
|
|
|
case 0x04: $name = 'tSub'; $size = 1; $data = '-'; break; |
|
5258
|
|
|
case 0x05: $name = 'tMul'; $size = 1; $data = '*'; break; |
|
5259
|
|
|
case 0x06: $name = 'tDiv'; $size = 1; $data = '/'; break; |
|
5260
|
|
|
case 0x07: $name = 'tPower'; $size = 1; $data = '^'; break; |
|
5261
|
|
|
case 0x08: $name = 'tConcat'; $size = 1; $data = '&'; break; |
|
5262
|
|
|
case 0x09: $name = 'tLT'; $size = 1; $data = '<'; break; |
|
5263
|
|
|
case 0x0A: $name = 'tLE'; $size = 1; $data = '<='; break; |
|
5264
|
|
|
case 0x0B: $name = 'tEQ'; $size = 1; $data = '='; break; |
|
5265
|
|
|
case 0x0C: $name = 'tGE'; $size = 1; $data = '>='; break; |
|
5266
|
|
|
case 0x0D: $name = 'tGT'; $size = 1; $data = '>'; break; |
|
5267
|
|
|
case 0x0E: $name = 'tNE'; $size = 1; $data = '<>'; break; |
|
5268
|
|
|
case 0x0F: $name = 'tIsect'; $size = 1; $data = ' '; break; |
|
5269
|
|
|
case 0x10: $name = 'tList'; $size = 1; $data = ','; break; |
|
5270
|
|
|
case 0x11: $name = 'tRange'; $size = 1; $data = ':'; break; |
|
5271
|
|
|
case 0x12: $name = 'tUplus'; $size = 1; $data = '+'; break; |
|
5272
|
|
|
case 0x13: $name = 'tUminus'; $size = 1; $data = '-'; break; |
|
5273
|
|
|
case 0x14: $name = 'tPercent'; $size = 1; $data = '%'; break; |
|
5274
|
|
|
case 0x15: // parenthesis |
|
5275
|
|
|
$name = 'tParen'; |
|
5276
|
|
|
$size = 1; |
|
5277
|
|
|
$data = null; |
|
5278
|
|
|
break; |
|
5279
|
|
|
case 0x16: // missing argument |
|
5280
|
|
|
$name = 'tMissArg'; |
|
5281
|
|
|
$size = 1; |
|
5282
|
|
|
$data = ''; |
|
5283
|
|
|
break; |
|
5284
|
|
|
case 0x17: // string |
|
5285
|
|
|
$name = 'tStr'; |
|
5286
|
|
|
// offset: 1; size: var; Unicode string, 8-bit string length |
|
5287
|
|
|
$string = self::_readUnicodeStringShort(substr($formulaData, 1)); |
|
5288
|
|
|
$size = 1 + $string['size']; |
|
5289
|
|
|
$data = self::_UTF8toExcelDoubleQuoted($string['value']); |
|
5290
|
|
|
break; |
|
5291
|
|
|
case 0x19: // Special attribute |
|
5292
|
|
|
// offset: 1; size: 1; attribute type flags: |
|
5293
|
|
|
switch (ord($formulaData[1])) { |
|
5294
|
|
|
case 0x01: |
|
5295
|
|
|
$name = 'tAttrVolatile'; |
|
5296
|
|
|
$size = 4; |
|
5297
|
|
|
$data = null; |
|
5298
|
|
|
break; |
|
5299
|
|
|
case 0x02: |
|
5300
|
|
|
$name = 'tAttrIf'; |
|
5301
|
|
|
$size = 4; |
|
5302
|
|
|
$data = null; |
|
5303
|
|
|
break; |
|
5304
|
|
|
case 0x04: |
|
5305
|
|
|
$name = 'tAttrChoose'; |
|
5306
|
|
|
// offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1) |
|
5307
|
|
|
$nc = self::_GetInt2d($formulaData, 2); |
|
5308
|
|
|
// offset: 4; size: 2 * $nc |
|
5309
|
|
|
// offset: 4 + 2 * $nc; size: 2 |
|
5310
|
|
|
$size = 2 * $nc + 6; |
|
5311
|
|
|
$data = null; |
|
5312
|
|
|
break; |
|
5313
|
|
|
case 0x08: |
|
5314
|
|
|
$name = 'tAttrSkip'; |
|
5315
|
|
|
$size = 4; |
|
5316
|
|
|
$data = null; |
|
5317
|
|
|
break; |
|
5318
|
|
|
case 0x10: |
|
5319
|
|
|
$name = 'tAttrSum'; |
|
5320
|
|
|
$size = 4; |
|
5321
|
|
|
$data = null; |
|
5322
|
|
|
break; |
|
5323
|
|
|
case 0x40: |
|
5324
|
|
|
case 0x41: |
|
5325
|
|
|
$name = 'tAttrSpace'; |
|
5326
|
|
|
$size = 4; |
|
5327
|
|
|
// offset: 2; size: 2; space type and position |
|
5328
|
|
|
switch (ord($formulaData[2])) { |
|
5329
|
|
|
case 0x00: |
|
5330
|
|
|
$spacetype = 'type0'; |
|
5331
|
|
|
break; |
|
5332
|
|
|
case 0x01: |
|
5333
|
|
|
$spacetype = 'type1'; |
|
5334
|
|
|
break; |
|
5335
|
|
|
case 0x02: |
|
5336
|
|
|
$spacetype = 'type2'; |
|
5337
|
|
|
break; |
|
5338
|
|
|
case 0x03: |
|
5339
|
|
|
$spacetype = 'type3'; |
|
5340
|
|
|
break; |
|
5341
|
|
|
case 0x04: |
|
5342
|
|
|
$spacetype = 'type4'; |
|
5343
|
|
|
break; |
|
5344
|
|
|
case 0x05: |
|
5345
|
|
|
$spacetype = 'type5'; |
|
5346
|
|
|
break; |
|
5347
|
|
|
default: |
|
5348
|
|
|
throw new Exception('Unrecognized space type in tAttrSpace token'); |
|
5349
|
|
|
break; |
|
|
|
|
|
|
5350
|
|
|
} |
|
5351
|
|
|
// offset: 3; size: 1; number of inserted spaces/carriage returns |
|
5352
|
|
|
$spacecount = ord($formulaData[3]); |
|
5353
|
|
|
|
|
5354
|
|
|
$data = array('spacetype' => $spacetype, 'spacecount' => $spacecount); |
|
5355
|
|
|
break; |
|
5356
|
|
|
default: |
|
5357
|
|
|
throw new Exception('Unrecognized attribute flag in tAttr token'); |
|
5358
|
|
|
break; |
|
|
|
|
|
|
5359
|
|
|
} |
|
5360
|
|
|
break; |
|
5361
|
|
|
case 0x1C: // error code |
|
5362
|
|
|
// offset: 1; size: 1; error code |
|
5363
|
|
|
$name = 'tErr'; |
|
5364
|
|
|
$size = 2; |
|
5365
|
|
|
$data = self::_mapErrorCode(ord($formulaData[1])); |
|
5366
|
|
|
break; |
|
5367
|
|
|
case 0x1D: // boolean |
|
5368
|
|
|
// offset: 1; size: 1; 0 = false, 1 = true; |
|
5369
|
|
|
$name = 'tBool'; |
|
5370
|
|
|
$size = 2; |
|
5371
|
|
|
$data = ord($formulaData[1]) ? 'TRUE' : 'FALSE'; |
|
5372
|
|
|
break; |
|
5373
|
|
|
case 0x1E: // integer |
|
5374
|
|
|
// offset: 1; size: 2; unsigned 16-bit integer |
|
5375
|
|
|
$name = 'tInt'; |
|
5376
|
|
|
$size = 3; |
|
5377
|
|
|
$data = self::_GetInt2d($formulaData, 1); |
|
5378
|
|
|
break; |
|
5379
|
|
|
case 0x1F: // number |
|
5380
|
|
|
// offset: 1; size: 8; |
|
5381
|
|
|
$name = 'tNum'; |
|
5382
|
|
|
$size = 9; |
|
5383
|
|
|
$data = self::_extractNumber(substr($formulaData, 1)); |
|
5384
|
|
|
$data = str_replace(',', '.', (string)$data); // in case non-English locale |
|
5385
|
|
|
break; |
|
5386
|
|
|
case 0x20: // array constant |
|
5387
|
|
|
case 0x40: |
|
5388
|
|
|
case 0x60: |
|
5389
|
|
|
// offset: 1; size: 7; not used |
|
5390
|
|
|
$name = 'tArray'; |
|
5391
|
|
|
$size = 8; |
|
5392
|
|
|
$data = null; |
|
5393
|
|
|
break; |
|
5394
|
|
|
case 0x21: // function with fixed number of arguments |
|
5395
|
|
|
case 0x41: |
|
5396
|
|
|
case 0x61: |
|
5397
|
|
|
$name = 'tFunc'; |
|
5398
|
|
|
$size = 3; |
|
5399
|
|
|
// offset: 1; size: 2; index to built-in sheet function |
|
5400
|
|
|
switch (self::_GetInt2d($formulaData, 1)) { |
|
5401
|
|
|
case 2: $function = 'ISNA'; $args = 1; break; |
|
|
|
|
|
|
5402
|
|
|
case 3: $function = 'ISERROR'; $args = 1; break; |
|
|
|
|
|
|
5403
|
|
|
case 10: $function = 'NA'; $args = 0; break; |
|
|
|
|
|
|
5404
|
|
|
case 15: $function = 'SIN'; $args = 1; break; |
|
|
|
|
|
|
5405
|
|
|
case 16: $function = 'COS'; $args = 1; break; |
|
|
|
|
|
|
5406
|
|
|
case 17: $function = 'TAN'; $args = 1; break; |
|
|
|
|
|
|
5407
|
|
|
case 18: $function = 'ATAN'; $args = 1; break; |
|
|
|
|
|
|
5408
|
|
|
case 19: $function = 'PI'; $args = 0; break; |
|
|
|
|
|
|
5409
|
|
|
case 20: $function = 'SQRT'; $args = 1; break; |
|
|
|
|
|
|
5410
|
|
|
case 21: $function = 'EXP'; $args = 1; break; |
|
|
|
|
|
|
5411
|
|
|
case 22: $function = 'LN'; $args = 1; break; |
|
|
|
|
|
|
5412
|
|
|
case 23: $function = 'LOG10'; $args = 1; break; |
|
|
|
|
|
|
5413
|
|
|
case 24: $function = 'ABS'; $args = 1; break; |
|
|
|
|
|
|
5414
|
|
|
case 25: $function = 'INT'; $args = 1; break; |
|
|
|
|
|
|
5415
|
|
|
case 26: $function = 'SIGN'; $args = 1; break; |
|
|
|
|
|
|
5416
|
|
|
case 27: $function = 'ROUND'; $args = 2; break; |
|
|
|
|
|
|
5417
|
|
|
case 30: $function = 'REPT'; $args = 2; break; |
|
|
|
|
|
|
5418
|
|
|
case 31: $function = 'MID'; $args = 3; break; |
|
|
|
|
|
|
5419
|
|
|
case 32: $function = 'LEN'; $args = 1; break; |
|
|
|
|
|
|
5420
|
|
|
case 33: $function = 'VALUE'; $args = 1; break; |
|
|
|
|
|
|
5421
|
|
|
case 34: $function = 'TRUE'; $args = 0; break; |
|
|
|
|
|
|
5422
|
|
|
case 35: $function = 'FALSE'; $args = 0; break; |
|
|
|
|
|
|
5423
|
|
|
case 38: $function = 'NOT'; $args = 1; break; |
|
|
|
|
|
|
5424
|
|
|
case 39: $function = 'MOD'; $args = 2; break; |
|
|
|
|
|
|
5425
|
|
|
case 40: $function = 'DCOUNT'; $args = 3; break; |
|
|
|
|
|
|
5426
|
|
|
case 41: $function = 'DSUM'; $args = 3; break; |
|
|
|
|
|
|
5427
|
|
|
case 42: $function = 'DAVERAGE'; $args = 3; break; |
|
|
|
|
|
|
5428
|
|
|
case 43: $function = 'DMIN'; $args = 3; break; |
|
|
|
|
|
|
5429
|
|
|
case 44: $function = 'DMAX'; $args = 3; break; |
|
|
|
|
|
|
5430
|
|
|
case 45: $function = 'DSTDEV'; $args = 3; break; |
|
|
|
|
|
|
5431
|
|
|
case 48: $function = 'TEXT'; $args = 2; break; |
|
|
|
|
|
|
5432
|
|
|
case 61: $function = 'MIRR'; $args = 3; break; |
|
|
|
|
|
|
5433
|
|
|
case 63: $function = 'RAND'; $args = 0; break; |
|
|
|
|
|
|
5434
|
|
|
case 65: $function = 'DATE'; $args = 3; break; |
|
|
|
|
|
|
5435
|
|
|
case 66: $function = 'TIME'; $args = 3; break; |
|
|
|
|
|
|
5436
|
|
|
case 67: $function = 'DAY'; $args = 1; break; |
|
|
|
|
|
|
5437
|
|
|
case 68: $function = 'MONTH'; $args = 1; break; |
|
|
|
|
|
|
5438
|
|
|
case 69: $function = 'YEAR'; $args = 1; break; |
|
|
|
|
|
|
5439
|
|
|
case 71: $function = 'HOUR'; $args = 1; break; |
|
|
|
|
|
|
5440
|
|
|
case 72: $function = 'MINUTE'; $args = 1; break; |
|
|
|
|
|
|
5441
|
|
|
case 73: $function = 'SECOND'; $args = 1; break; |
|
|
|
|
|
|
5442
|
|
|
case 74: $function = 'NOW'; $args = 0; break; |
|
|
|
|
|
|
5443
|
|
|
case 75: $function = 'AREAS'; $args = 1; break; |
|
|
|
|
|
|
5444
|
|
|
case 76: $function = 'ROWS'; $args = 1; break; |
|
|
|
|
|
|
5445
|
|
|
case 77: $function = 'COLUMNS'; $args = 1; break; |
|
|
|
|
|
|
5446
|
|
|
case 83: $function = 'TRANSPOSE'; $args = 1; break; |
|
|
|
|
|
|
5447
|
|
|
case 86: $function = 'TYPE'; $args = 1; break; |
|
|
|
|
|
|
5448
|
|
|
case 97: $function = 'ATAN2'; $args = 2; break; |
|
|
|
|
|
|
5449
|
|
|
case 98: $function = 'ASIN'; $args = 1; break; |
|
|
|
|
|
|
5450
|
|
|
case 99: $function = 'ACOS'; $args = 1; break; |
|
|
|
|
|
|
5451
|
|
|
case 105: $function = 'ISREF'; $args = 1; break; |
|
5452
|
|
|
case 111: $function = 'CHAR'; $args = 1; break; |
|
5453
|
|
|
case 112: $function = 'LOWER'; $args = 1; break; |
|
5454
|
|
|
case 113: $function = 'UPPER'; $args = 1; break; |
|
5455
|
|
|
case 114: $function = 'PROPER'; $args = 1; break; |
|
5456
|
|
|
case 117: $function = 'EXACT'; $args = 2; break; |
|
5457
|
|
|
case 118: $function = 'TRIM'; $args = 1; break; |
|
5458
|
|
|
case 119: $function = 'REPLACE'; $args = 4; break; |
|
5459
|
|
|
case 121: $function = 'CODE'; $args = 1; break; |
|
5460
|
|
|
case 126: $function = 'ISERR'; $args = 1; break; |
|
5461
|
|
|
case 127: $function = 'ISTEXT'; $args = 1; break; |
|
5462
|
|
|
case 128: $function = 'ISNUMBER'; $args = 1; break; |
|
5463
|
|
|
case 129: $function = 'ISBLANK'; $args = 1; break; |
|
5464
|
|
|
case 130: $function = 'T'; $args = 1; break; |
|
5465
|
|
|
case 131: $function = 'N'; $args = 1; break; |
|
5466
|
|
|
case 140: $function = 'DATEVALUE'; $args = 1; break; |
|
5467
|
|
|
case 141: $function = 'TIMEVALUE'; $args = 1; break; |
|
5468
|
|
|
case 142: $function = 'SLN'; $args = 3; break; |
|
5469
|
|
|
case 143: $function = 'SYD'; $args = 4; break; |
|
5470
|
|
|
case 162: $function = 'CLEAN'; $args = 1; break; |
|
5471
|
|
|
case 163: $function = 'MDETERM'; $args = 1; break; |
|
5472
|
|
|
case 164: $function = 'MINVERSE'; $args = 1; break; |
|
5473
|
|
|
case 165: $function = 'MMULT'; $args = 2; break; |
|
5474
|
|
|
case 184: $function = 'FACT'; $args = 1; break; |
|
5475
|
|
|
case 189: $function = 'DPRODUCT'; $args = 3; break; |
|
5476
|
|
|
case 190: $function = 'ISNONTEXT'; $args = 1; break; |
|
5477
|
|
|
case 195: $function = 'DSTDEVP'; $args = 3; break; |
|
5478
|
|
|
case 196: $function = 'DVARP'; $args = 3; break; |
|
5479
|
|
|
case 198: $function = 'ISLOGICAL'; $args = 1; break; |
|
5480
|
|
|
case 199: $function = 'DCOUNTA'; $args = 3; break; |
|
5481
|
|
|
case 207: $function = 'REPLACEB'; $args = 4; break; |
|
5482
|
|
|
case 210: $function = 'MIDB'; $args = 3; break; |
|
5483
|
|
|
case 211: $function = 'LENB'; $args = 1; break; |
|
5484
|
|
|
case 212: $function = 'ROUNDUP'; $args = 2; break; |
|
5485
|
|
|
case 213: $function = 'ROUNDDOWN'; $args = 2; break; |
|
5486
|
|
|
case 214: $function = 'ASC'; $args = 1; break; |
|
5487
|
|
|
case 215: $function = 'DBCS'; $args = 1; break; |
|
5488
|
|
|
case 221: $function = 'TODAY'; $args = 0; break; |
|
5489
|
|
|
case 229: $function = 'SINH'; $args = 1; break; |
|
5490
|
|
|
case 230: $function = 'COSH'; $args = 1; break; |
|
5491
|
|
|
case 231: $function = 'TANH'; $args = 1; break; |
|
5492
|
|
|
case 232: $function = 'ASINH'; $args = 1; break; |
|
5493
|
|
|
case 233: $function = 'ACOSH'; $args = 1; break; |
|
5494
|
|
|
case 234: $function = 'ATANH'; $args = 1; break; |
|
5495
|
|
|
case 235: $function = 'DGET'; $args = 3; break; |
|
5496
|
|
|
case 244: $function = 'INFO'; $args = 1; break; |
|
5497
|
|
|
case 252: $function = 'FREQUENCY'; $args = 2; break; |
|
5498
|
|
|
case 261: $function = 'ERROR.TYPE'; $args = 1; break; |
|
5499
|
|
|
case 271: $function = 'GAMMALN'; $args = 1; break; |
|
5500
|
|
|
case 273: $function = 'BINOMDIST'; $args = 4; break; |
|
5501
|
|
|
case 274: $function = 'CHIDIST'; $args = 2; break; |
|
5502
|
|
|
case 275: $function = 'CHIINV'; $args = 2; break; |
|
5503
|
|
|
case 276: $function = 'COMBIN'; $args = 2; break; |
|
5504
|
|
|
case 277: $function = 'CONFIDENCE'; $args = 3; break; |
|
5505
|
|
|
case 278: $function = 'CRITBINOM'; $args = 3; break; |
|
5506
|
|
|
case 279: $function = 'EVEN'; $args = 1; break; |
|
5507
|
|
|
case 280: $function = 'EXPONDIST'; $args = 3; break; |
|
5508
|
|
|
case 281: $function = 'FDIST'; $args = 3; break; |
|
5509
|
|
|
case 282: $function = 'FINV'; $args = 3; break; |
|
5510
|
|
|
case 283: $function = 'FISHER'; $args = 1; break; |
|
5511
|
|
|
case 284: $function = 'FISHERINV'; $args = 1; break; |
|
5512
|
|
|
case 285: $function = 'FLOOR'; $args = 2; break; |
|
5513
|
|
|
case 286: $function = 'GAMMADIST'; $args = 4; break; |
|
5514
|
|
|
case 287: $function = 'GAMMAINV'; $args = 3; break; |
|
5515
|
|
|
case 288: $function = 'CEILING'; $args = 2; break; |
|
5516
|
|
|
case 289: $function = 'HYPGEOMDIST'; $args = 4; break; |
|
5517
|
|
|
case 290: $function = 'LOGNORMDIST'; $args = 3; break; |
|
5518
|
|
|
case 291: $function = 'LOGINV'; $args = 3; break; |
|
5519
|
|
|
case 292: $function = 'NEGBINOMDIST'; $args = 3; break; |
|
5520
|
|
|
case 293: $function = 'NORMDIST'; $args = 4; break; |
|
5521
|
|
|
case 294: $function = 'NORMSDIST'; $args = 1; break; |
|
5522
|
|
|
case 295: $function = 'NORMINV'; $args = 3; break; |
|
5523
|
|
|
case 296: $function = 'NORMSINV'; $args = 1; break; |
|
5524
|
|
|
case 297: $function = 'STANDARDIZE'; $args = 3; break; |
|
5525
|
|
|
case 298: $function = 'ODD'; $args = 1; break; |
|
5526
|
|
|
case 299: $function = 'PERMUT'; $args = 2; break; |
|
5527
|
|
|
case 300: $function = 'POISSON'; $args = 3; break; |
|
5528
|
|
|
case 301: $function = 'TDIST'; $args = 3; break; |
|
5529
|
|
|
case 302: $function = 'WEIBULL'; $args = 4; break; |
|
5530
|
|
|
case 303: $function = 'SUMXMY2'; $args = 2; break; |
|
5531
|
|
|
case 304: $function = 'SUMX2MY2'; $args = 2; break; |
|
5532
|
|
|
case 305: $function = 'SUMX2PY2'; $args = 2; break; |
|
5533
|
|
|
case 306: $function = 'CHITEST'; $args = 2; break; |
|
5534
|
|
|
case 307: $function = 'CORREL'; $args = 2; break; |
|
5535
|
|
|
case 308: $function = 'COVAR'; $args = 2; break; |
|
5536
|
|
|
case 309: $function = 'FORECAST'; $args = 3; break; |
|
5537
|
|
|
case 310: $function = 'FTEST'; $args = 2; break; |
|
5538
|
|
|
case 311: $function = 'INTERCEPT'; $args = 2; break; |
|
5539
|
|
|
case 312: $function = 'PEARSON'; $args = 2; break; |
|
5540
|
|
|
case 313: $function = 'RSQ'; $args = 2; break; |
|
5541
|
|
|
case 314: $function = 'STEYX'; $args = 2; break; |
|
5542
|
|
|
case 315: $function = 'SLOPE'; $args = 2; break; |
|
5543
|
|
|
case 316: $function = 'TTEST'; $args = 4; break; |
|
5544
|
|
|
case 325: $function = 'LARGE'; $args = 2; break; |
|
5545
|
|
|
case 326: $function = 'SMALL'; $args = 2; break; |
|
5546
|
|
|
case 327: $function = 'QUARTILE'; $args = 2; break; |
|
5547
|
|
|
case 328: $function = 'PERCENTILE'; $args = 2; break; |
|
5548
|
|
|
case 331: $function = 'TRIMMEAN'; $args = 2; break; |
|
5549
|
|
|
case 332: $function = 'TINV'; $args = 2; break; |
|
5550
|
|
|
case 337: $function = 'POWER'; $args = 2; break; |
|
5551
|
|
|
case 342: $function = 'RADIANS'; $args = 1; break; |
|
5552
|
|
|
case 343: $function = 'DEGREES'; $args = 1; break; |
|
5553
|
|
|
case 346: $function = 'COUNTIF'; $args = 2; break; |
|
5554
|
|
|
case 347: $function = 'COUNTBLANK'; $args = 1; break; |
|
5555
|
|
|
case 350: $function = 'ISPMT'; $args = 4; break; |
|
5556
|
|
|
case 351: $function = 'DATEDIF'; $args = 3; break; |
|
5557
|
|
|
case 352: $function = 'DATESTRING'; $args = 1; break; |
|
5558
|
|
|
case 353: $function = 'NUMBERSTRING'; $args = 2; break; |
|
5559
|
|
|
case 360: $function = 'PHONETIC'; $args = 1; break; |
|
5560
|
|
|
case 368: $function = 'BAHTTEXT'; $args = 1; break; |
|
5561
|
|
|
default: |
|
5562
|
|
|
throw new Exception('Unrecognized function in formula'); |
|
5563
|
|
|
break; |
|
|
|
|
|
|
5564
|
|
|
} |
|
5565
|
|
|
$data = array('function' => $function, 'args' => $args); |
|
5566
|
|
|
break; |
|
5567
|
|
|
case 0x22: // function with variable number of arguments |
|
5568
|
|
|
case 0x42: |
|
5569
|
|
|
case 0x62: |
|
5570
|
|
|
$name = 'tFuncV'; |
|
5571
|
|
|
$size = 4; |
|
5572
|
|
|
// offset: 1; size: 1; number of arguments |
|
5573
|
|
|
$args = ord($formulaData[1]); |
|
5574
|
|
|
// offset: 2: size: 2; index to built-in sheet function |
|
5575
|
|
|
$index = self::_GetInt2d($formulaData, 2); |
|
5576
|
|
|
switch ($index) { |
|
5577
|
|
|
case 0: $function = 'COUNT'; break; |
|
|
|
|
|
|
5578
|
|
|
case 1: $function = 'IF'; break; |
|
|
|
|
|
|
5579
|
|
|
case 4: $function = 'SUM'; break; |
|
|
|
|
|
|
5580
|
|
|
case 5: $function = 'AVERAGE'; break; |
|
|
|
|
|
|
5581
|
|
|
case 6: $function = 'MIN'; break; |
|
|
|
|
|
|
5582
|
|
|
case 7: $function = 'MAX'; break; |
|
|
|
|
|
|
5583
|
|
|
case 8: $function = 'ROW'; break; |
|
|
|
|
|
|
5584
|
|
|
case 9: $function = 'COLUMN'; break; |
|
|
|
|
|
|
5585
|
|
|
case 11: $function = 'NPV'; break; |
|
|
|
|
|
|
5586
|
|
|
case 12: $function = 'STDEV'; break; |
|
|
|
|
|
|
5587
|
|
|
case 13: $function = 'DOLLAR'; break; |
|
|
|
|
|
|
5588
|
|
|
case 14: $function = 'FIXED'; break; |
|
|
|
|
|
|
5589
|
|
|
case 28: $function = 'LOOKUP'; break; |
|
|
|
|
|
|
5590
|
|
|
case 29: $function = 'INDEX'; break; |
|
|
|
|
|
|
5591
|
|
|
case 36: $function = 'AND'; break; |
|
|
|
|
|
|
5592
|
|
|
case 37: $function = 'OR'; break; |
|
|
|
|
|
|
5593
|
|
|
case 46: $function = 'VAR'; break; |
|
|
|
|
|
|
5594
|
|
|
case 49: $function = 'LINEST'; break; |
|
|
|
|
|
|
5595
|
|
|
case 50: $function = 'TREND'; break; |
|
|
|
|
|
|
5596
|
|
|
case 51: $function = 'LOGEST'; break; |
|
|
|
|
|
|
5597
|
|
|
case 52: $function = 'GROWTH'; break; |
|
|
|
|
|
|
5598
|
|
|
case 56: $function = 'PV'; break; |
|
|
|
|
|
|
5599
|
|
|
case 57: $function = 'FV'; break; |
|
|
|
|
|
|
5600
|
|
|
case 58: $function = 'NPER'; break; |
|
|
|
|
|
|
5601
|
|
|
case 59: $function = 'PMT'; break; |
|
|
|
|
|
|
5602
|
|
|
case 60: $function = 'RATE'; break; |
|
|
|
|
|
|
5603
|
|
|
case 62: $function = 'IRR'; break; |
|
|
|
|
|
|
5604
|
|
|
case 64: $function = 'MATCH'; break; |
|
|
|
|
|
|
5605
|
|
|
case 70: $function = 'WEEKDAY'; break; |
|
|
|
|
|
|
5606
|
|
|
case 78: $function = 'OFFSET'; break; |
|
|
|
|
|
|
5607
|
|
|
case 82: $function = 'SEARCH'; break; |
|
|
|
|
|
|
5608
|
|
|
case 100: $function = 'CHOOSE'; break; |
|
5609
|
|
|
case 101: $function = 'HLOOKUP'; break; |
|
5610
|
|
|
case 102: $function = 'VLOOKUP'; break; |
|
5611
|
|
|
case 109: $function = 'LOG'; break; |
|
5612
|
|
|
case 115: $function = 'LEFT'; break; |
|
5613
|
|
|
case 116: $function = 'RIGHT'; break; |
|
5614
|
|
|
case 120: $function = 'SUBSTITUTE'; break; |
|
5615
|
|
|
case 124: $function = 'FIND'; break; |
|
5616
|
|
|
case 125: $function = 'CELL'; break; |
|
5617
|
|
|
case 144: $function = 'DDB'; break; |
|
5618
|
|
|
case 148: $function = 'INDIRECT'; break; |
|
5619
|
|
|
case 167: $function = 'IPMT'; break; |
|
5620
|
|
|
case 168: $function = 'PPMT'; break; |
|
5621
|
|
|
case 169: $function = 'COUNTA'; break; |
|
5622
|
|
|
case 183: $function = 'PRODUCT'; break; |
|
5623
|
|
|
case 193: $function = 'STDEVP'; break; |
|
5624
|
|
|
case 194: $function = 'VARP'; break; |
|
5625
|
|
|
case 197: $function = 'TRUNC'; break; |
|
5626
|
|
|
case 204: $function = 'USDOLLAR'; break; |
|
5627
|
|
|
case 205: $function = 'FINDB'; break; |
|
5628
|
|
|
case 206: $function = 'SEARCHB'; break; |
|
5629
|
|
|
case 208: $function = 'LEFTB'; break; |
|
5630
|
|
|
case 209: $function = 'RIGHTB'; break; |
|
5631
|
|
|
case 216: $function = 'RANK'; break; |
|
5632
|
|
|
case 219: $function = 'ADDRESS'; break; |
|
5633
|
|
|
case 220: $function = 'DAYS360'; break; |
|
5634
|
|
|
case 222: $function = 'VDB'; break; |
|
5635
|
|
|
case 227: $function = 'MEDIAN'; break; |
|
5636
|
|
|
case 228: $function = 'SUMPRODUCT'; break; |
|
5637
|
|
|
case 247: $function = 'DB'; break; |
|
5638
|
|
|
case 255: $function = ''; break; |
|
5639
|
|
|
case 269: $function = 'AVEDEV'; break; |
|
5640
|
|
|
case 270: $function = 'BETADIST'; break; |
|
5641
|
|
|
case 272: $function = 'BETAINV'; break; |
|
5642
|
|
|
case 317: $function = 'PROB'; break; |
|
5643
|
|
|
case 318: $function = 'DEVSQ'; break; |
|
5644
|
|
|
case 319: $function = 'GEOMEAN'; break; |
|
5645
|
|
|
case 320: $function = 'HARMEAN'; break; |
|
5646
|
|
|
case 321: $function = 'SUMSQ'; break; |
|
5647
|
|
|
case 322: $function = 'KURT'; break; |
|
5648
|
|
|
case 323: $function = 'SKEW'; break; |
|
5649
|
|
|
case 324: $function = 'ZTEST'; break; |
|
5650
|
|
|
case 329: $function = 'PERCENTRANK'; break; |
|
5651
|
|
|
case 330: $function = 'MODE'; break; |
|
5652
|
|
|
case 336: $function = 'CONCATENATE'; break; |
|
5653
|
|
|
case 344: $function = 'SUBTOTAL'; break; |
|
5654
|
|
|
case 345: $function = 'SUMIF'; break; |
|
5655
|
|
|
case 354: $function = 'ROMAN'; break; |
|
5656
|
|
|
case 358: $function = 'GETPIVOTDATA'; break; |
|
5657
|
|
|
case 359: $function = 'HYPERLINK'; break; |
|
5658
|
|
|
case 361: $function = 'AVERAGEA'; break; |
|
5659
|
|
|
case 362: $function = 'MAXA'; break; |
|
5660
|
|
|
case 363: $function = 'MINA'; break; |
|
5661
|
|
|
case 364: $function = 'STDEVPA'; break; |
|
5662
|
|
|
case 365: $function = 'VARPA'; break; |
|
5663
|
|
|
case 366: $function = 'STDEVA'; break; |
|
5664
|
|
|
case 367: $function = 'VARA'; break; |
|
5665
|
|
|
default: |
|
5666
|
|
|
throw new Exception('Unrecognized function in formula'); |
|
5667
|
|
|
break; |
|
|
|
|
|
|
5668
|
|
|
} |
|
5669
|
|
|
$data = array('function' => $function, 'args' => $args); |
|
5670
|
|
|
break; |
|
5671
|
|
|
case 0x23: // index to defined name |
|
5672
|
|
|
case 0x43: |
|
5673
|
|
|
case 0x63: |
|
5674
|
|
|
$name = 'tName'; |
|
5675
|
|
|
$size = 5; |
|
5676
|
|
|
// offset: 1; size: 2; one-based index to definedname record |
|
5677
|
|
|
$definedNameIndex = self::_GetInt2d($formulaData, 1) - 1; |
|
5678
|
|
|
// offset: 2; size: 2; not used |
|
5679
|
|
|
$data = $this->_definedname[$definedNameIndex]['name']; |
|
5680
|
|
|
break; |
|
5681
|
|
|
case 0x24: // single cell reference e.g. A5 |
|
5682
|
|
|
case 0x44: |
|
5683
|
|
|
case 0x64: |
|
5684
|
|
|
$name = 'tRef'; |
|
5685
|
|
|
$size = 5; |
|
5686
|
|
|
$data = $this->_readBIFF8CellAddress(substr($formulaData, 1, 4)); |
|
5687
|
|
|
break; |
|
5688
|
|
|
case 0x25: // cell range reference to cells in the same sheet (2d) |
|
5689
|
|
|
case 0x45: |
|
5690
|
|
|
case 0x65: |
|
5691
|
|
|
$name = 'tArea'; |
|
5692
|
|
|
$size = 9; |
|
5693
|
|
|
$data = $this->_readBIFF8CellRangeAddress(substr($formulaData, 1, 8)); |
|
5694
|
|
|
break; |
|
5695
|
|
|
case 0x26: // Constant reference sub-expression |
|
5696
|
|
|
case 0x46: |
|
5697
|
|
View Code Duplication |
case 0x66: |
|
5698
|
|
|
$name = 'tMemArea'; |
|
5699
|
|
|
// offset: 1; size: 4; not used |
|
5700
|
|
|
// offset: 5; size: 2; size of the following subexpression |
|
5701
|
|
|
$subSize = self::_GetInt2d($formulaData, 5); |
|
5702
|
|
|
$size = 7 + $subSize; |
|
5703
|
|
|
$data = $this->_getFormulaFromData(substr($formulaData, 7, $subSize)); |
|
5704
|
|
|
break; |
|
5705
|
|
|
case 0x27: // Deleted constant reference sub-expression |
|
5706
|
|
|
case 0x47: |
|
5707
|
|
View Code Duplication |
case 0x67: |
|
5708
|
|
|
$name = 'tMemErr'; |
|
5709
|
|
|
// offset: 1; size: 4; not used |
|
5710
|
|
|
// offset: 5; size: 2; size of the following subexpression |
|
5711
|
|
|
$subSize = self::_GetInt2d($formulaData, 5); |
|
5712
|
|
|
$size = 7 + $subSize; |
|
5713
|
|
|
$data = $this->_getFormulaFromData(substr($formulaData, 7, $subSize)); |
|
5714
|
|
|
break; |
|
5715
|
|
|
case 0x29: // Variable reference sub-expression |
|
5716
|
|
|
case 0x49: |
|
5717
|
|
View Code Duplication |
case 0x69: |
|
5718
|
|
|
$name = 'tMemFunc'; |
|
5719
|
|
|
// offset: 1; size: 2; size of the following sub-expression |
|
5720
|
|
|
$subSize = self::_GetInt2d($formulaData, 1); |
|
5721
|
|
|
$size = 3 + $subSize; |
|
5722
|
|
|
$data = $this->_getFormulaFromData(substr($formulaData, 3, $subSize)); |
|
5723
|
|
|
break; |
|
5724
|
|
|
|
|
5725
|
|
|
case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places |
|
5726
|
|
|
case 0x4C: |
|
5727
|
|
|
case 0x6C: |
|
5728
|
|
|
$name = 'tRefN'; |
|
5729
|
|
|
$size = 5; |
|
5730
|
|
|
$data = $this->_readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell); |
|
5731
|
|
|
break; |
|
5732
|
|
|
|
|
5733
|
|
|
case 0x2D: // Relative 2d range reference |
|
5734
|
|
|
case 0x4D: |
|
5735
|
|
|
case 0x6D: |
|
5736
|
|
|
$name = 'tAreaN'; |
|
5737
|
|
|
$size = 9; |
|
5738
|
|
|
$data = $this->_readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell); |
|
5739
|
|
|
break; |
|
5740
|
|
|
|
|
5741
|
|
|
case 0x39: // External name |
|
5742
|
|
|
case 0x59: |
|
5743
|
|
|
case 0x79: |
|
5744
|
|
|
$name = 'tNameX'; |
|
5745
|
|
|
$size = 7; |
|
5746
|
|
|
// offset: 1; size: 2; index to REF entry in EXTERNSHEET record |
|
5747
|
|
|
// offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record |
|
5748
|
|
|
$index = self::_GetInt2d($formulaData, 3); |
|
5749
|
|
|
// assume index is to EXTERNNAME record |
|
5750
|
|
|
$data = $this->_externalNames[$index - 1]['name']; |
|
5751
|
|
|
// offset: 5; size: 2; not used |
|
5752
|
|
|
break; |
|
5753
|
|
|
|
|
5754
|
|
|
case 0x3A: // 3d reference to cell |
|
5755
|
|
|
case 0x5A: |
|
5756
|
|
View Code Duplication |
case 0x7A: |
|
5757
|
|
|
$name = 'tRef3d'; |
|
5758
|
|
|
$size = 7; |
|
5759
|
|
|
|
|
5760
|
|
|
try { |
|
5761
|
|
|
// offset: 1; size: 2; index to REF entry |
|
5762
|
|
|
$sheetRange = $this->_readSheetRangeByRefIndex(self::_GetInt2d($formulaData, 1)); |
|
5763
|
|
|
// offset: 3; size: 4; cell address |
|
5764
|
|
|
$cellAddress = $this->_readBIFF8CellAddress(substr($formulaData, 3, 4)); |
|
5765
|
|
|
|
|
5766
|
|
|
$data = "$sheetRange!$cellAddress"; |
|
5767
|
|
|
} catch (Exception $e) { |
|
5768
|
|
|
// deleted sheet reference |
|
5769
|
|
|
$data = '#REF!'; |
|
5770
|
|
|
} |
|
5771
|
|
|
|
|
5772
|
|
|
break; |
|
5773
|
|
|
case 0x3B: // 3d reference to cell range |
|
5774
|
|
|
case 0x5B: |
|
5775
|
|
View Code Duplication |
case 0x7B: |
|
5776
|
|
|
$name = 'tArea3d'; |
|
5777
|
|
|
$size = 11; |
|
5778
|
|
|
|
|
5779
|
|
|
try { |
|
5780
|
|
|
// offset: 1; size: 2; index to REF entry |
|
5781
|
|
|
$sheetRange = $this->_readSheetRangeByRefIndex(self::_GetInt2d($formulaData, 1)); |
|
5782
|
|
|
// offset: 3; size: 8; cell address |
|
5783
|
|
|
$cellRangeAddress = $this->_readBIFF8CellRangeAddress(substr($formulaData, 3, 8)); |
|
5784
|
|
|
|
|
5785
|
|
|
$data = "$sheetRange!$cellRangeAddress"; |
|
5786
|
|
|
} catch (Exception $e) { |
|
5787
|
|
|
// deleted sheet reference |
|
5788
|
|
|
$data = '#REF!'; |
|
5789
|
|
|
} |
|
5790
|
|
|
|
|
5791
|
|
|
break; |
|
5792
|
|
|
// Unknown cases // don't know how to deal with |
|
5793
|
|
|
default: |
|
5794
|
|
|
throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula'); |
|
5795
|
|
|
break; |
|
|
|
|
|
|
5796
|
|
|
} |
|
5797
|
|
|
|
|
5798
|
|
|
return array( |
|
5799
|
|
|
'id' => $id, |
|
5800
|
|
|
'name' => $name, |
|
5801
|
|
|
'size' => $size, |
|
5802
|
|
|
'data' => $data, |
|
5803
|
|
|
); |
|
5804
|
|
|
} |
|
5805
|
|
|
|
|
5806
|
|
|
|
|
5807
|
|
|
/** |
|
5808
|
|
|
* Reads a cell address in BIFF8 e.g. 'A2' or '$A$2' |
|
5809
|
|
|
* section 3.3.4 |
|
5810
|
|
|
* |
|
5811
|
|
|
* @param string $cellAddressStructure |
|
5812
|
|
|
* @return string |
|
5813
|
|
|
*/ |
|
5814
|
|
|
private function _readBIFF8CellAddress($cellAddressStructure) |
|
5815
|
|
|
{ |
|
5816
|
|
|
// offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) |
|
5817
|
|
|
$row = self::_GetInt2d($cellAddressStructure, 0) + 1; |
|
5818
|
|
|
|
|
5819
|
|
|
// offset: 2; size: 2; index to column or column offset + relative flags |
|
5820
|
|
|
|
|
5821
|
|
|
// bit: 7-0; mask 0x00FF; column index |
|
5822
|
|
|
$column = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($cellAddressStructure, 2)); |
|
5823
|
|
|
|
|
5824
|
|
|
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) |
|
5825
|
|
|
if (!(0x4000 & self::_GetInt2d($cellAddressStructure, 2))) { |
|
5826
|
|
|
$column = '$' . $column; |
|
5827
|
|
|
} |
|
5828
|
|
|
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) |
|
5829
|
|
|
if (!(0x8000 & self::_GetInt2d($cellAddressStructure, 2))) { |
|
5830
|
|
|
$row = '$' . $row; |
|
5831
|
|
|
} |
|
5832
|
|
|
|
|
5833
|
|
|
return $column . $row; |
|
5834
|
|
|
} |
|
5835
|
|
|
|
|
5836
|
|
|
|
|
5837
|
|
|
/** |
|
5838
|
|
|
* Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column |
|
5839
|
|
|
* to indicate offsets from a base cell |
|
5840
|
|
|
* section 3.3.4 |
|
5841
|
|
|
* |
|
5842
|
|
|
* @param string $cellAddressStructure |
|
5843
|
|
|
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas |
|
5844
|
|
|
* @return string |
|
5845
|
|
|
*/ |
|
5846
|
|
|
private function _readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1') |
|
5847
|
|
|
{ |
|
5848
|
|
|
list($baseCol, $baseRow) = PHPExcel_Cell::coordinateFromString($baseCell); |
|
5849
|
|
|
$baseCol = PHPExcel_Cell::columnIndexFromString($baseCol) - 1; |
|
5850
|
|
|
|
|
5851
|
|
|
// offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) |
|
5852
|
|
|
$rowIndex = self::_GetInt2d($cellAddressStructure, 0); |
|
5853
|
|
|
$row = self::_GetInt2d($cellAddressStructure, 0) + 1; |
|
5854
|
|
|
|
|
5855
|
|
|
// offset: 2; size: 2; index to column or column offset + relative flags |
|
5856
|
|
|
|
|
5857
|
|
|
// bit: 7-0; mask 0x00FF; column index |
|
5858
|
|
|
$colIndex = 0x00FF & self::_GetInt2d($cellAddressStructure, 2); |
|
5859
|
|
|
|
|
5860
|
|
|
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) |
|
5861
|
|
View Code Duplication |
if (!(0x4000 & self::_GetInt2d($cellAddressStructure, 2))) { |
|
5862
|
|
|
$column = PHPExcel_Cell::stringFromColumnIndex($colIndex); |
|
5863
|
|
|
$column = '$' . $column; |
|
5864
|
|
|
} else { |
|
5865
|
|
|
$colIndex = ($colIndex <= 127) ? $colIndex : $colIndex - 256; |
|
5866
|
|
|
$column = PHPExcel_Cell::stringFromColumnIndex($baseCol + $colIndex); |
|
5867
|
|
|
} |
|
5868
|
|
|
|
|
5869
|
|
|
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) |
|
5870
|
|
|
if (!(0x8000 & self::_GetInt2d($cellAddressStructure, 2))) { |
|
5871
|
|
|
$row = '$' . $row; |
|
5872
|
|
|
} else { |
|
5873
|
|
|
$rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536; |
|
5874
|
|
|
$row = $baseRow + $rowIndex; |
|
5875
|
|
|
} |
|
5876
|
|
|
|
|
5877
|
|
|
return $column . $row; |
|
5878
|
|
|
} |
|
5879
|
|
|
|
|
5880
|
|
|
|
|
5881
|
|
|
/** |
|
5882
|
|
|
* Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1' |
|
5883
|
|
|
* always fixed range |
|
5884
|
|
|
* section 2.5.14 |
|
5885
|
|
|
* |
|
5886
|
|
|
* @param string $subData |
|
5887
|
|
|
* @return string |
|
5888
|
|
|
* @throws Exception |
|
5889
|
|
|
*/ |
|
5890
|
|
|
private function _readBIFF5CellRangeAddressFixed($subData) |
|
5891
|
|
|
{ |
|
5892
|
|
|
// offset: 0; size: 2; index to first row |
|
5893
|
|
|
$fr = self::_GetInt2d($subData, 0) + 1; |
|
5894
|
|
|
|
|
5895
|
|
|
// offset: 2; size: 2; index to last row |
|
5896
|
|
|
$lr = self::_GetInt2d($subData, 2) + 1; |
|
5897
|
|
|
|
|
5898
|
|
|
// offset: 4; size: 1; index to first column |
|
5899
|
|
|
$fc = ord($subData{4}); |
|
5900
|
|
|
|
|
5901
|
|
|
// offset: 5; size: 1; index to last column |
|
5902
|
|
|
$lc = ord($subData{5}); |
|
5903
|
|
|
|
|
5904
|
|
|
// check values |
|
5905
|
|
|
if ($fr > $lr || $fc > $lc) { |
|
5906
|
|
|
throw new Exception('Not a cell range address'); |
|
5907
|
|
|
} |
|
5908
|
|
|
|
|
5909
|
|
|
// column index to letter |
|
5910
|
|
|
$fc = PHPExcel_Cell::stringFromColumnIndex($fc); |
|
5911
|
|
|
$lc = PHPExcel_Cell::stringFromColumnIndex($lc); |
|
5912
|
|
|
|
|
5913
|
|
|
if ($fr == $lr and $fc == $lc) { |
|
|
|
|
|
|
5914
|
|
|
return "$fc$fr"; |
|
5915
|
|
|
} |
|
5916
|
|
|
return "$fc$fr:$lc$lr"; |
|
5917
|
|
|
} |
|
5918
|
|
|
|
|
5919
|
|
|
|
|
5920
|
|
|
/** |
|
5921
|
|
|
* Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1' |
|
5922
|
|
|
* always fixed range |
|
5923
|
|
|
* section 2.5.14 |
|
5924
|
|
|
* |
|
5925
|
|
|
* @param string $subData |
|
5926
|
|
|
* @return string |
|
5927
|
|
|
* @throws Exception |
|
5928
|
|
|
*/ |
|
5929
|
|
|
private function _readBIFF8CellRangeAddressFixed($subData) |
|
5930
|
|
|
{ |
|
5931
|
|
|
// offset: 0; size: 2; index to first row |
|
5932
|
|
|
$fr = self::_GetInt2d($subData, 0) + 1; |
|
5933
|
|
|
|
|
5934
|
|
|
// offset: 2; size: 2; index to last row |
|
5935
|
|
|
$lr = self::_GetInt2d($subData, 2) + 1; |
|
5936
|
|
|
|
|
5937
|
|
|
// offset: 4; size: 2; index to first column |
|
5938
|
|
|
$fc = self::_GetInt2d($subData, 4); |
|
5939
|
|
|
|
|
5940
|
|
|
// offset: 6; size: 2; index to last column |
|
5941
|
|
|
$lc = self::_GetInt2d($subData, 6); |
|
5942
|
|
|
|
|
5943
|
|
|
// check values |
|
5944
|
|
|
if ($fr > $lr || $fc > $lc) { |
|
5945
|
|
|
throw new Exception('Not a cell range address'); |
|
5946
|
|
|
} |
|
5947
|
|
|
|
|
5948
|
|
|
// column index to letter |
|
5949
|
|
|
$fc = PHPExcel_Cell::stringFromColumnIndex($fc); |
|
5950
|
|
|
$lc = PHPExcel_Cell::stringFromColumnIndex($lc); |
|
5951
|
|
|
|
|
5952
|
|
|
if ($fr == $lr and $fc == $lc) { |
|
|
|
|
|
|
5953
|
|
|
return "$fc$fr"; |
|
5954
|
|
|
} |
|
5955
|
|
|
return "$fc$fr:$lc$lr"; |
|
5956
|
|
|
} |
|
5957
|
|
|
|
|
5958
|
|
|
|
|
5959
|
|
|
/** |
|
5960
|
|
|
* Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6' |
|
5961
|
|
|
* there are flags indicating whether column/row index is relative |
|
5962
|
|
|
* section 3.3.4 |
|
5963
|
|
|
* |
|
5964
|
|
|
* @param string $subData |
|
5965
|
|
|
* @return string |
|
5966
|
|
|
*/ |
|
5967
|
|
|
private function _readBIFF8CellRangeAddress($subData) |
|
5968
|
|
|
{ |
|
5969
|
|
|
// todo: if cell range is just a single cell, should this funciton |
|
5970
|
|
|
// not just return e.g. 'A1' and not 'A1:A1' ? |
|
5971
|
|
|
|
|
5972
|
|
|
// offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767)) |
|
5973
|
|
|
$fr = self::_GetInt2d($subData, 0) + 1; |
|
5974
|
|
|
|
|
5975
|
|
|
// offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767)) |
|
5976
|
|
|
$lr = self::_GetInt2d($subData, 2) + 1; |
|
5977
|
|
|
|
|
5978
|
|
|
// offset: 4; size: 2; index to first column or column offset + relative flags |
|
5979
|
|
|
|
|
5980
|
|
|
// bit: 7-0; mask 0x00FF; column index |
|
5981
|
|
|
$fc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($subData, 4)); |
|
5982
|
|
|
|
|
5983
|
|
|
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) |
|
5984
|
|
|
if (!(0x4000 & self::_GetInt2d($subData, 4))) { |
|
5985
|
|
|
$fc = '$' . $fc; |
|
5986
|
|
|
} |
|
5987
|
|
|
|
|
5988
|
|
|
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) |
|
5989
|
|
|
if (!(0x8000 & self::_GetInt2d($subData, 4))) { |
|
5990
|
|
|
$fr = '$' . $fr; |
|
5991
|
|
|
} |
|
5992
|
|
|
|
|
5993
|
|
|
// offset: 6; size: 2; index to last column or column offset + relative flags |
|
5994
|
|
|
|
|
5995
|
|
|
// bit: 7-0; mask 0x00FF; column index |
|
5996
|
|
|
$lc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($subData, 6)); |
|
5997
|
|
|
|
|
5998
|
|
|
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) |
|
5999
|
|
|
if (!(0x4000 & self::_GetInt2d($subData, 6))) { |
|
6000
|
|
|
$lc = '$' . $lc; |
|
6001
|
|
|
} |
|
6002
|
|
|
|
|
6003
|
|
|
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) |
|
6004
|
|
|
if (!(0x8000 & self::_GetInt2d($subData, 6))) { |
|
6005
|
|
|
$lr = '$' . $lr; |
|
6006
|
|
|
} |
|
6007
|
|
|
|
|
6008
|
|
|
return "$fc$fr:$lc$lr"; |
|
6009
|
|
|
} |
|
6010
|
|
|
|
|
6011
|
|
|
|
|
6012
|
|
|
/** |
|
6013
|
|
|
* Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column |
|
6014
|
|
|
* to indicate offsets from a base cell |
|
6015
|
|
|
* section 3.3.4 |
|
6016
|
|
|
* |
|
6017
|
|
|
* @param string $subData |
|
6018
|
|
|
* @param string $baseCell Base cell |
|
6019
|
|
|
* @return string Cell range address |
|
6020
|
|
|
*/ |
|
6021
|
|
|
private function _readBIFF8CellRangeAddressB($subData, $baseCell = 'A1') |
|
6022
|
|
|
{ |
|
6023
|
|
|
list($baseCol, $baseRow) = PHPExcel_Cell::coordinateFromString($baseCell); |
|
6024
|
|
|
$baseCol = PHPExcel_Cell::columnIndexFromString($baseCol) - 1; |
|
6025
|
|
|
|
|
6026
|
|
|
// TODO: if cell range is just a single cell, should this funciton |
|
6027
|
|
|
// not just return e.g. 'A1' and not 'A1:A1' ? |
|
6028
|
|
|
|
|
6029
|
|
|
// offset: 0; size: 2; first row |
|
6030
|
|
|
$frIndex = self::_GetInt2d($subData, 0); // adjust below |
|
6031
|
|
|
|
|
6032
|
|
|
// offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767) |
|
6033
|
|
|
$lrIndex = self::_GetInt2d($subData, 2); // adjust below |
|
6034
|
|
|
|
|
6035
|
|
|
// offset: 4; size: 2; first column with relative/absolute flags |
|
6036
|
|
|
|
|
6037
|
|
|
// bit: 7-0; mask 0x00FF; column index |
|
6038
|
|
|
$fcIndex = 0x00FF & self::_GetInt2d($subData, 4); |
|
6039
|
|
|
|
|
6040
|
|
|
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) |
|
6041
|
|
View Code Duplication |
if (!(0x4000 & self::_GetInt2d($subData, 4))) { |
|
6042
|
|
|
// absolute column index |
|
6043
|
|
|
$fc = PHPExcel_Cell::stringFromColumnIndex($fcIndex); |
|
6044
|
|
|
$fc = '$' . $fc; |
|
6045
|
|
|
} else { |
|
6046
|
|
|
// column offset |
|
6047
|
|
|
$fcIndex = ($fcIndex <= 127) ? $fcIndex : $fcIndex - 256; |
|
6048
|
|
|
$fc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $fcIndex); |
|
6049
|
|
|
} |
|
6050
|
|
|
|
|
6051
|
|
|
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) |
|
6052
|
|
View Code Duplication |
if (!(0x8000 & self::_GetInt2d($subData, 4))) { |
|
6053
|
|
|
// absolute row index |
|
6054
|
|
|
$fr = $frIndex + 1; |
|
6055
|
|
|
$fr = '$' . $fr; |
|
6056
|
|
|
} else { |
|
6057
|
|
|
// row offset |
|
6058
|
|
|
$frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536; |
|
6059
|
|
|
$fr = $baseRow + $frIndex; |
|
6060
|
|
|
} |
|
6061
|
|
|
|
|
6062
|
|
|
// offset: 6; size: 2; last column with relative/absolute flags |
|
6063
|
|
|
|
|
6064
|
|
|
// bit: 7-0; mask 0x00FF; column index |
|
6065
|
|
|
$lcIndex = 0x00FF & self::_GetInt2d($subData, 6); |
|
6066
|
|
|
$lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256; |
|
6067
|
|
|
$lc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $lcIndex); |
|
6068
|
|
|
|
|
6069
|
|
|
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) |
|
6070
|
|
View Code Duplication |
if (!(0x4000 & self::_GetInt2d($subData, 6))) { |
|
6071
|
|
|
// absolute column index |
|
6072
|
|
|
$lc = PHPExcel_Cell::stringFromColumnIndex($lcIndex); |
|
6073
|
|
|
$lc = '$' . $lc; |
|
6074
|
|
|
} else { |
|
6075
|
|
|
// column offset |
|
6076
|
|
|
$lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256; |
|
6077
|
|
|
$lc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $lcIndex); |
|
6078
|
|
|
} |
|
6079
|
|
|
|
|
6080
|
|
|
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) |
|
6081
|
|
View Code Duplication |
if (!(0x8000 & self::_GetInt2d($subData, 6))) { |
|
6082
|
|
|
// absolute row index |
|
6083
|
|
|
$lr = $lrIndex + 1; |
|
6084
|
|
|
$lr = '$' . $lr; |
|
6085
|
|
|
} else { |
|
6086
|
|
|
// row offset |
|
6087
|
|
|
$lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536; |
|
6088
|
|
|
$lr = $baseRow + $lrIndex; |
|
6089
|
|
|
} |
|
6090
|
|
|
|
|
6091
|
|
|
return "$fc$fr:$lc$lr"; |
|
6092
|
|
|
} |
|
6093
|
|
|
|
|
6094
|
|
|
|
|
6095
|
|
|
/** |
|
6096
|
|
|
* Read BIFF8 cell range address list |
|
6097
|
|
|
* section 2.5.15 |
|
6098
|
|
|
* |
|
6099
|
|
|
* @param string $subData |
|
6100
|
|
|
* @return array |
|
6101
|
|
|
*/ |
|
6102
|
|
View Code Duplication |
private function _readBIFF8CellRangeAddressList($subData) |
|
6103
|
|
|
{ |
|
6104
|
|
|
$cellRangeAddresses = array(); |
|
6105
|
|
|
|
|
6106
|
|
|
// offset: 0; size: 2; number of the following cell range addresses |
|
6107
|
|
|
$nm = self::_GetInt2d($subData, 0); |
|
6108
|
|
|
|
|
6109
|
|
|
$offset = 2; |
|
6110
|
|
|
// offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses |
|
6111
|
|
|
for ($i = 0; $i < $nm; ++$i) { |
|
6112
|
|
|
$cellRangeAddresses[] = $this->_readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8)); |
|
6113
|
|
|
$offset += 8; |
|
6114
|
|
|
} |
|
6115
|
|
|
|
|
6116
|
|
|
return array( |
|
6117
|
|
|
'size' => 2 + 8 * $nm, |
|
6118
|
|
|
'cellRangeAddresses' => $cellRangeAddresses, |
|
6119
|
|
|
); |
|
6120
|
|
|
} |
|
6121
|
|
|
|
|
6122
|
|
|
|
|
6123
|
|
|
/** |
|
6124
|
|
|
* Read BIFF5 cell range address list |
|
6125
|
|
|
* section 2.5.15 |
|
6126
|
|
|
* |
|
6127
|
|
|
* @param string $subData |
|
6128
|
|
|
* @return array |
|
6129
|
|
|
*/ |
|
6130
|
|
View Code Duplication |
private function _readBIFF5CellRangeAddressList($subData) |
|
6131
|
|
|
{ |
|
6132
|
|
|
$cellRangeAddresses = array(); |
|
6133
|
|
|
|
|
6134
|
|
|
// offset: 0; size: 2; number of the following cell range addresses |
|
6135
|
|
|
$nm = self::_GetInt2d($subData, 0); |
|
6136
|
|
|
|
|
6137
|
|
|
$offset = 2; |
|
6138
|
|
|
// offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses |
|
6139
|
|
|
for ($i = 0; $i < $nm; ++$i) { |
|
6140
|
|
|
$cellRangeAddresses[] = $this->_readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6)); |
|
6141
|
|
|
$offset += 6; |
|
6142
|
|
|
} |
|
6143
|
|
|
|
|
6144
|
|
|
return array( |
|
6145
|
|
|
'size' => 2 + 6 * $nm, |
|
6146
|
|
|
'cellRangeAddresses' => $cellRangeAddresses, |
|
6147
|
|
|
); |
|
6148
|
|
|
} |
|
6149
|
|
|
|
|
6150
|
|
|
|
|
6151
|
|
|
/** |
|
6152
|
|
|
* Get a sheet range like Sheet1:Sheet3 from REF index |
|
6153
|
|
|
* Note: If there is only one sheet in the range, one gets e.g Sheet1 |
|
6154
|
|
|
* It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets, |
|
6155
|
|
|
* in which case an exception is thrown |
|
6156
|
|
|
* |
|
6157
|
|
|
* @param int $index |
|
6158
|
|
|
* @return string|false |
|
6159
|
|
|
* @throws Exception |
|
6160
|
|
|
*/ |
|
6161
|
|
|
private function _readSheetRangeByRefIndex($index) |
|
6162
|
|
|
{ |
|
6163
|
|
|
if (isset($this->_ref[$index])) { |
|
6164
|
|
|
|
|
6165
|
|
|
$type = $this->_externalBooks[$this->_ref[$index]['externalBookIndex']]['type']; |
|
6166
|
|
|
|
|
6167
|
|
|
switch ($type) { |
|
6168
|
|
|
case 'internal': |
|
6169
|
|
|
// check if we have a deleted 3d reference |
|
6170
|
|
|
if ($this->_ref[$index]['firstSheetIndex'] == 0xFFFF or $this->_ref[$index]['lastSheetIndex'] == 0xFFFF) { |
|
|
|
|
|
|
6171
|
|
|
throw new Exception('Deleted sheet reference'); |
|
6172
|
|
|
} |
|
6173
|
|
|
|
|
6174
|
|
|
// we have normal sheet range (collapsed or uncollapsed) |
|
6175
|
|
|
$firstSheetName = $this->_sheets[$this->_ref[$index]['firstSheetIndex']]['name']; |
|
6176
|
|
|
$lastSheetName = $this->_sheets[$this->_ref[$index]['lastSheetIndex']]['name']; |
|
6177
|
|
|
|
|
6178
|
|
|
if ($firstSheetName == $lastSheetName) { |
|
6179
|
|
|
// collapsed sheet range |
|
6180
|
|
|
$sheetRange = $firstSheetName; |
|
6181
|
|
|
} else { |
|
6182
|
|
|
$sheetRange = "$firstSheetName:$lastSheetName"; |
|
6183
|
|
|
} |
|
6184
|
|
|
|
|
6185
|
|
|
// escape the single-quotes |
|
6186
|
|
|
$sheetRange = str_replace("'", "''", $sheetRange); |
|
6187
|
|
|
|
|
6188
|
|
|
// if there are special characters, we need to enclose the range in single-quotes |
|
6189
|
|
|
// todo: check if we have identified the whole set of special characters |
|
6190
|
|
|
// it seems that the following characters are not accepted for sheet names |
|
6191
|
|
|
// and we may assume that they are not present: []*/:\? |
|
6192
|
|
|
if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/", $sheetRange)) { |
|
6193
|
|
|
$sheetRange = "'$sheetRange'"; |
|
6194
|
|
|
} |
|
6195
|
|
|
|
|
6196
|
|
|
return $sheetRange; |
|
6197
|
|
|
break; |
|
|
|
|
|
|
6198
|
|
|
|
|
6199
|
|
|
default: |
|
6200
|
|
|
// TODO: external sheet support |
|
6201
|
|
|
throw new Exception('Excel5 reader only supports internal sheets in fomulas'); |
|
6202
|
|
|
break; |
|
|
|
|
|
|
6203
|
|
|
} |
|
6204
|
|
|
} |
|
6205
|
|
|
return false; |
|
6206
|
|
|
} |
|
6207
|
|
|
|
|
6208
|
|
|
|
|
6209
|
|
|
/** |
|
6210
|
|
|
* read BIFF8 constant value array from array data |
|
6211
|
|
|
* returns e.g. array('value' => '{1,2;3,4}', 'size' => 40} |
|
6212
|
|
|
* section 2.5.8 |
|
6213
|
|
|
* |
|
6214
|
|
|
* @param string $arrayData |
|
6215
|
|
|
* @return array |
|
6216
|
|
|
*/ |
|
6217
|
|
|
private static function _readBIFF8ConstantArray($arrayData) |
|
6218
|
|
|
{ |
|
6219
|
|
|
// offset: 0; size: 1; number of columns decreased by 1 |
|
6220
|
|
|
$nc = ord($arrayData[0]); |
|
6221
|
|
|
|
|
6222
|
|
|
// offset: 1; size: 2; number of rows decreased by 1 |
|
6223
|
|
|
$nr = self::_GetInt2d($arrayData, 1); |
|
6224
|
|
|
$size = 3; // initialize |
|
6225
|
|
|
$arrayData = substr($arrayData, 3); |
|
6226
|
|
|
|
|
6227
|
|
|
// offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values |
|
6228
|
|
|
$matrixChunks = array(); |
|
6229
|
|
|
for ($r = 1; $r <= $nr + 1; ++$r) { |
|
6230
|
|
|
$items = array(); |
|
6231
|
|
|
for ($c = 1; $c <= $nc + 1; ++$c) { |
|
6232
|
|
|
$constant = self::_readBIFF8Constant($arrayData); |
|
6233
|
|
|
$items[] = $constant['value']; |
|
6234
|
|
|
$arrayData = substr($arrayData, $constant['size']); |
|
6235
|
|
|
$size += $constant['size']; |
|
6236
|
|
|
} |
|
6237
|
|
|
$matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"' |
|
6238
|
|
|
} |
|
6239
|
|
|
$matrix = '{' . implode(';', $matrixChunks) . '}'; |
|
6240
|
|
|
|
|
6241
|
|
|
return array( |
|
6242
|
|
|
'value' => $matrix, |
|
6243
|
|
|
'size' => $size, |
|
6244
|
|
|
); |
|
6245
|
|
|
} |
|
6246
|
|
|
|
|
6247
|
|
|
|
|
6248
|
|
|
/** |
|
6249
|
|
|
* read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value' |
|
6250
|
|
|
* section 2.5.7 |
|
6251
|
|
|
* returns e.g. array('value' => '5', 'size' => 9) |
|
6252
|
|
|
* |
|
6253
|
|
|
* @param string $valueData |
|
6254
|
|
|
* @return array |
|
6255
|
|
|
*/ |
|
6256
|
|
|
private static function _readBIFF8Constant($valueData) |
|
6257
|
|
|
{ |
|
6258
|
|
|
// offset: 0; size: 1; identifier for type of constant |
|
6259
|
|
|
$identifier = ord($valueData[0]); |
|
6260
|
|
|
|
|
6261
|
|
|
switch ($identifier) { |
|
6262
|
|
|
case 0x00: // empty constant (what is this?) |
|
6263
|
|
|
$value = ''; |
|
6264
|
|
|
$size = 9; |
|
6265
|
|
|
break; |
|
6266
|
|
|
case 0x01: // number |
|
6267
|
|
|
// offset: 1; size: 8; IEEE 754 floating-point value |
|
6268
|
|
|
$value = self::_extractNumber(substr($valueData, 1, 8)); |
|
6269
|
|
|
$size = 9; |
|
6270
|
|
|
break; |
|
6271
|
|
|
case 0x02: // string value |
|
6272
|
|
|
// offset: 1; size: var; Unicode string, 16-bit string length |
|
6273
|
|
|
$string = self::_readUnicodeStringLong(substr($valueData, 1)); |
|
6274
|
|
|
$value = '"' . $string['value'] . '"'; |
|
6275
|
|
|
$size = 1 + $string['size']; |
|
6276
|
|
|
break; |
|
6277
|
|
|
case 0x04: // boolean |
|
6278
|
|
|
// offset: 1; size: 1; 0 = FALSE, 1 = TRUE |
|
6279
|
|
|
if (ord($valueData[1])) { |
|
6280
|
|
|
$value = 'TRUE'; |
|
6281
|
|
|
} else { |
|
6282
|
|
|
$value = 'FALSE'; |
|
6283
|
|
|
} |
|
6284
|
|
|
$size = 9; |
|
6285
|
|
|
break; |
|
6286
|
|
|
case 0x10: // error code |
|
6287
|
|
|
// offset: 1; size: 1; error code |
|
6288
|
|
|
$value = self::_mapErrorCode(ord($valueData[1])); |
|
6289
|
|
|
$size = 9; |
|
6290
|
|
|
break; |
|
6291
|
|
|
} |
|
6292
|
|
|
return array( |
|
6293
|
|
|
'value' => $value, |
|
|
|
|
|
|
6294
|
|
|
'size' => $size, |
|
|
|
|
|
|
6295
|
|
|
); |
|
6296
|
|
|
} |
|
6297
|
|
|
|
|
6298
|
|
|
|
|
6299
|
|
|
/** |
|
6300
|
|
|
* Extract RGB color |
|
6301
|
|
|
* OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4 |
|
6302
|
|
|
* |
|
6303
|
|
|
* @param string $rgb Encoded RGB value (4 bytes) |
|
6304
|
|
|
* @return array |
|
6305
|
|
|
*/ |
|
6306
|
|
|
private static function _readRGB($rgb) |
|
6307
|
|
|
{ |
|
6308
|
|
|
// offset: 0; size 1; Red component |
|
6309
|
|
|
$r = ord($rgb{0}); |
|
6310
|
|
|
|
|
6311
|
|
|
// offset: 1; size: 1; Green component |
|
6312
|
|
|
$g = ord($rgb{1}); |
|
6313
|
|
|
|
|
6314
|
|
|
// offset: 2; size: 1; Blue component |
|
6315
|
|
|
$b = ord($rgb{2}); |
|
6316
|
|
|
|
|
6317
|
|
|
// HEX notation, e.g. 'FF00FC' |
|
6318
|
|
|
$rgb = sprintf('%02X%02X%02X', $r, $g, $b); |
|
6319
|
|
|
|
|
6320
|
|
|
return array('rgb' => $rgb); |
|
6321
|
|
|
} |
|
6322
|
|
|
|
|
6323
|
|
|
|
|
6324
|
|
|
/** |
|
6325
|
|
|
* Read byte string (8-bit string length) |
|
6326
|
|
|
* OpenOffice documentation: 2.5.2 |
|
6327
|
|
|
* |
|
6328
|
|
|
* @param string $subData |
|
6329
|
|
|
* @return array |
|
6330
|
|
|
*/ |
|
6331
|
|
View Code Duplication |
private function _readByteStringShort($subData) |
|
6332
|
|
|
{ |
|
6333
|
|
|
// offset: 0; size: 1; length of the string (character count) |
|
6334
|
|
|
$ln = ord($subData[0]); |
|
6335
|
|
|
|
|
6336
|
|
|
// offset: 1: size: var; character array (8-bit characters) |
|
6337
|
|
|
$value = $this->_decodeCodepage(substr($subData, 1, $ln)); |
|
6338
|
|
|
|
|
6339
|
|
|
return array( |
|
6340
|
|
|
'value' => $value, |
|
6341
|
|
|
'size' => 1 + $ln, // size in bytes of data structure |
|
6342
|
|
|
); |
|
6343
|
|
|
} |
|
6344
|
|
|
|
|
6345
|
|
|
|
|
6346
|
|
|
/** |
|
6347
|
|
|
* Read byte string (16-bit string length) |
|
6348
|
|
|
* OpenOffice documentation: 2.5.2 |
|
6349
|
|
|
* |
|
6350
|
|
|
* @param string $subData |
|
6351
|
|
|
* @return array |
|
6352
|
|
|
*/ |
|
6353
|
|
View Code Duplication |
private function _readByteStringLong($subData) |
|
6354
|
|
|
{ |
|
6355
|
|
|
// offset: 0; size: 2; length of the string (character count) |
|
6356
|
|
|
$ln = self::_GetInt2d($subData, 0); |
|
6357
|
|
|
|
|
6358
|
|
|
// offset: 2: size: var; character array (8-bit characters) |
|
6359
|
|
|
$value = $this->_decodeCodepage(substr($subData, 2)); |
|
6360
|
|
|
|
|
6361
|
|
|
//return $string; |
|
6362
|
|
|
return array( |
|
6363
|
|
|
'value' => $value, |
|
6364
|
|
|
'size' => 2 + $ln, // size in bytes of data structure |
|
6365
|
|
|
); |
|
6366
|
|
|
} |
|
6367
|
|
|
|
|
6368
|
|
|
|
|
6369
|
|
|
/** |
|
6370
|
|
|
* Extracts an Excel Unicode short string (8-bit string length) |
|
6371
|
|
|
* OpenOffice documentation: 2.5.3 |
|
6372
|
|
|
* function will automatically find out where the Unicode string ends. |
|
6373
|
|
|
* |
|
6374
|
|
|
* @param string $subData |
|
6375
|
|
|
* @return array |
|
6376
|
|
|
*/ |
|
6377
|
|
View Code Duplication |
private static function _readUnicodeStringShort($subData) |
|
6378
|
|
|
{ |
|
6379
|
|
|
$value = ''; |
|
6380
|
|
|
|
|
6381
|
|
|
// offset: 0: size: 1; length of the string (character count) |
|
6382
|
|
|
$characterCount = ord($subData[0]); |
|
6383
|
|
|
|
|
6384
|
|
|
$string = self::_readUnicodeString(substr($subData, 1), $characterCount); |
|
6385
|
|
|
|
|
6386
|
|
|
// add 1 for the string length |
|
6387
|
|
|
$string['size'] += 1; |
|
6388
|
|
|
|
|
6389
|
|
|
return $string; |
|
6390
|
|
|
} |
|
6391
|
|
|
|
|
6392
|
|
|
|
|
6393
|
|
|
/** |
|
6394
|
|
|
* Extracts an Excel Unicode long string (16-bit string length) |
|
6395
|
|
|
* OpenOffice documentation: 2.5.3 |
|
6396
|
|
|
* this function is under construction, needs to support rich text, and Asian phonetic settings |
|
6397
|
|
|
* |
|
6398
|
|
|
* @param string $subData |
|
6399
|
|
|
* @return array |
|
6400
|
|
|
*/ |
|
6401
|
|
View Code Duplication |
private static function _readUnicodeStringLong($subData) |
|
6402
|
|
|
{ |
|
6403
|
|
|
$value = ''; |
|
6404
|
|
|
|
|
6405
|
|
|
// offset: 0: size: 2; length of the string (character count) |
|
6406
|
|
|
$characterCount = self::_GetInt2d($subData, 0); |
|
6407
|
|
|
|
|
6408
|
|
|
$string = self::_readUnicodeString(substr($subData, 2), $characterCount); |
|
6409
|
|
|
|
|
6410
|
|
|
// add 2 for the string length |
|
6411
|
|
|
$string['size'] += 2; |
|
6412
|
|
|
|
|
6413
|
|
|
return $string; |
|
6414
|
|
|
} |
|
6415
|
|
|
|
|
6416
|
|
|
|
|
6417
|
|
|
/** |
|
6418
|
|
|
* Read Unicode string with no string length field, but with known character count |
|
6419
|
|
|
* this function is under construction, needs to support rich text, and Asian phonetic settings |
|
6420
|
|
|
* OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3 |
|
6421
|
|
|
* |
|
6422
|
|
|
* @param string $subData |
|
6423
|
|
|
* @param int $characterCount |
|
6424
|
|
|
* @return array |
|
6425
|
|
|
*/ |
|
6426
|
|
|
private static function _readUnicodeString($subData, $characterCount) |
|
6427
|
|
|
{ |
|
6428
|
|
|
$value = ''; |
|
6429
|
|
|
|
|
6430
|
|
|
// offset: 0: size: 1; option flags |
|
6431
|
|
|
|
|
6432
|
|
|
// bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit) |
|
6433
|
|
|
$isCompressed = !((0x01 & ord($subData[0])) >> 0); |
|
6434
|
|
|
|
|
6435
|
|
|
// bit: 2; mask: 0x04; Asian phonetic settings |
|
6436
|
|
|
$hasAsian = (0x04) & ord($subData[0]) >> 2; |
|
6437
|
|
|
|
|
6438
|
|
|
// bit: 3; mask: 0x08; Rich-Text settings |
|
6439
|
|
|
$hasRichText = (0x08) & ord($subData[0]) >> 3; |
|
6440
|
|
|
|
|
6441
|
|
|
// offset: 1: size: var; character array |
|
6442
|
|
|
// this offset assumes richtext and Asian phonetic settings are off which is generally wrong |
|
6443
|
|
|
// needs to be fixed |
|
6444
|
|
|
$value = self::_encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed); |
|
6445
|
|
|
|
|
6446
|
|
|
return array( |
|
6447
|
|
|
'value' => $value, |
|
6448
|
|
|
'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags |
|
6449
|
|
|
); |
|
6450
|
|
|
} |
|
6451
|
|
|
|
|
6452
|
|
|
|
|
6453
|
|
|
/** |
|
6454
|
|
|
* Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas. |
|
6455
|
|
|
* Example: hello"world --> "hello""world" |
|
6456
|
|
|
* |
|
6457
|
|
|
* @param string $value UTF-8 encoded string |
|
6458
|
|
|
* @return string |
|
6459
|
|
|
*/ |
|
6460
|
|
|
private static function _UTF8toExcelDoubleQuoted($value) |
|
6461
|
|
|
{ |
|
6462
|
|
|
return '"' . str_replace('"', '""', $value) . '"'; |
|
6463
|
|
|
} |
|
6464
|
|
|
|
|
6465
|
|
|
|
|
6466
|
|
|
/** |
|
6467
|
|
|
* Reads first 8 bytes of a string and return IEEE 754 float |
|
6468
|
|
|
* |
|
6469
|
|
|
* @param string $data Binary string that is at least 8 bytes long |
|
6470
|
|
|
* @return float |
|
6471
|
|
|
*/ |
|
6472
|
|
|
private static function _extractNumber($data) |
|
6473
|
|
|
{ |
|
6474
|
|
|
$rknumhigh = self::_GetInt4d($data, 4); |
|
6475
|
|
|
$rknumlow = self::_GetInt4d($data, 0); |
|
6476
|
|
|
$sign = ($rknumhigh & 0x80000000) >> 31; |
|
6477
|
|
|
$exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023; |
|
6478
|
|
|
$mantissa = (0x100000 | ($rknumhigh & 0x000fffff)); |
|
6479
|
|
|
$mantissalow1 = ($rknumlow & 0x80000000) >> 31; |
|
6480
|
|
|
$mantissalow2 = ($rknumlow & 0x7fffffff); |
|
6481
|
|
|
$value = $mantissa / pow( 2 , (20 - $exp)); |
|
6482
|
|
|
|
|
6483
|
|
|
if ($mantissalow1 != 0) { |
|
6484
|
|
|
$value += 1 / pow (2 , (21 - $exp)); |
|
6485
|
|
|
} |
|
6486
|
|
|
|
|
6487
|
|
|
$value += $mantissalow2 / pow (2 , (52 - $exp)); |
|
6488
|
|
|
if ($sign) { |
|
6489
|
|
|
$value *= -1; |
|
6490
|
|
|
} |
|
6491
|
|
|
|
|
6492
|
|
|
return $value; |
|
6493
|
|
|
} |
|
6494
|
|
|
|
|
6495
|
|
|
|
|
6496
|
|
|
private static function _GetIEEE754($rknum) |
|
6497
|
|
|
{ |
|
6498
|
|
|
if (($rknum & 0x02) != 0) { |
|
6499
|
|
|
$value = $rknum >> 2; |
|
6500
|
|
|
} else { |
|
6501
|
|
|
// changes by mmp, info on IEEE754 encoding from |
|
6502
|
|
|
// research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html |
|
6503
|
|
|
// The RK format calls for using only the most significant 30 bits |
|
6504
|
|
|
// of the 64 bit floating point value. The other 34 bits are assumed |
|
6505
|
|
|
// to be 0 so we use the upper 30 bits of $rknum as follows... |
|
6506
|
|
|
$sign = ($rknum & 0x80000000) >> 31; |
|
6507
|
|
|
$exp = ($rknum & 0x7ff00000) >> 20; |
|
6508
|
|
|
$mantissa = (0x100000 | ($rknum & 0x000ffffc)); |
|
6509
|
|
|
$value = $mantissa / pow( 2 , (20- ($exp - 1023))); |
|
6510
|
|
|
if ($sign) { |
|
6511
|
|
|
$value = -1 * $value; |
|
6512
|
|
|
} |
|
6513
|
|
|
//end of changes by mmp |
|
6514
|
|
|
} |
|
6515
|
|
|
if (($rknum & 0x01) != 0) { |
|
6516
|
|
|
$value /= 100; |
|
6517
|
|
|
} |
|
6518
|
|
|
return $value; |
|
6519
|
|
|
} |
|
6520
|
|
|
|
|
6521
|
|
|
|
|
6522
|
|
|
/** |
|
6523
|
|
|
* Get UTF-8 string from (compressed or uncompressed) UTF-16 string |
|
6524
|
|
|
* |
|
6525
|
|
|
* @param string $string |
|
6526
|
|
|
* @param bool $compressed |
|
6527
|
|
|
* @return string |
|
6528
|
|
|
*/ |
|
6529
|
|
|
private static function _encodeUTF16($string, $compressed = '') |
|
6530
|
|
|
{ |
|
6531
|
|
|
if ($compressed) { |
|
6532
|
|
|
$string = self::_uncompressByteString($string); |
|
6533
|
|
|
} |
|
6534
|
|
|
|
|
6535
|
|
|
return PHPExcel_Shared_String::ConvertEncoding($string, 'UTF-8', 'UTF-16LE'); |
|
6536
|
|
|
} |
|
6537
|
|
|
|
|
6538
|
|
|
|
|
6539
|
|
|
/** |
|
6540
|
|
|
* Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8. |
|
6541
|
|
|
* |
|
6542
|
|
|
* @param string $string |
|
6543
|
|
|
* @return string |
|
6544
|
|
|
*/ |
|
6545
|
|
|
private static function _uncompressByteString($string) |
|
6546
|
|
|
{ |
|
6547
|
|
|
$uncompressedString = ''; |
|
6548
|
|
|
$strLen = strlen($string); |
|
6549
|
|
|
for ($i = 0; $i < $strLen; ++$i) { |
|
6550
|
|
|
$uncompressedString .= $string[$i] . "\0"; |
|
6551
|
|
|
} |
|
6552
|
|
|
|
|
6553
|
|
|
return $uncompressedString; |
|
6554
|
|
|
} |
|
6555
|
|
|
|
|
6556
|
|
|
|
|
6557
|
|
|
/** |
|
6558
|
|
|
* Convert string to UTF-8. Only used for BIFF5. |
|
6559
|
|
|
* |
|
6560
|
|
|
* @param string $string |
|
6561
|
|
|
* @return string |
|
6562
|
|
|
*/ |
|
6563
|
|
|
private function _decodeCodepage($string) |
|
6564
|
|
|
{ |
|
6565
|
|
|
return PHPExcel_Shared_String::ConvertEncoding($string, 'UTF-8', $this->_codepage); |
|
6566
|
|
|
} |
|
6567
|
|
|
|
|
6568
|
|
|
|
|
6569
|
|
|
/** |
|
6570
|
|
|
* Read 16-bit unsigned integer |
|
6571
|
|
|
* |
|
6572
|
|
|
* @param string $data |
|
6573
|
|
|
* @param int $pos |
|
6574
|
|
|
* @return int |
|
6575
|
|
|
*/ |
|
6576
|
|
|
public static function _GetInt2d($data, $pos) |
|
6577
|
|
|
{ |
|
6578
|
|
|
return ord($data[$pos]) | (ord($data[$pos+1]) << 8); |
|
6579
|
|
|
} |
|
6580
|
|
|
|
|
6581
|
|
|
|
|
6582
|
|
|
/** |
|
6583
|
|
|
* Read 32-bit signed integer |
|
6584
|
|
|
* |
|
6585
|
|
|
* @param string $data |
|
6586
|
|
|
* @param int $pos |
|
6587
|
|
|
* @return int |
|
6588
|
|
|
*/ |
|
6589
|
|
View Code Duplication |
public static function _GetInt4d($data, $pos) |
|
6590
|
|
|
{ |
|
6591
|
|
|
// FIX: represent numbers correctly on 64-bit system |
|
6592
|
|
|
// http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 |
|
6593
|
|
|
// Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems |
|
6594
|
|
|
$_or_24 = ord($data[$pos + 3]); |
|
6595
|
|
|
if ($_or_24 >= 128) { |
|
6596
|
|
|
// negative number |
|
6597
|
|
|
$_ord_24 = -abs((256 - $_or_24) << 24); |
|
6598
|
|
|
} else { |
|
6599
|
|
|
$_ord_24 = ($_or_24 & 127) << 24; |
|
6600
|
|
|
} |
|
6601
|
|
|
return ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | $_ord_24; |
|
6602
|
|
|
} |
|
6603
|
|
|
|
|
6604
|
|
|
|
|
6605
|
|
|
/** |
|
6606
|
|
|
* Read color |
|
6607
|
|
|
* |
|
6608
|
|
|
* @param int $color Indexed color |
|
6609
|
|
|
* @param array $palette Color palette |
|
6610
|
|
|
* @return array RGB color value, example: array('rgb' => 'FF0000') |
|
6611
|
|
|
*/ |
|
6612
|
|
|
private static function _readColor($color,$palette,$version) |
|
6613
|
|
|
{ |
|
6614
|
|
|
if ($color <= 0x07 || $color >= 0x40) { |
|
6615
|
|
|
// special built-in color |
|
6616
|
|
|
return self::_mapBuiltInColor($color); |
|
6617
|
|
|
} elseif (isset($palette) && isset($palette[$color - 8])) { |
|
6618
|
|
|
// palette color, color index 0x08 maps to pallete index 0 |
|
6619
|
|
|
return $palette[$color - 8]; |
|
6620
|
|
|
} else { |
|
6621
|
|
|
// default color table |
|
6622
|
|
|
if ($version == self::XLS_BIFF8) { |
|
6623
|
|
|
return self::_mapColor($color); |
|
6624
|
|
|
} else { |
|
6625
|
|
|
// BIFF5 |
|
6626
|
|
|
return self::_mapColorBIFF5($color); |
|
6627
|
|
|
} |
|
6628
|
|
|
} |
|
6629
|
|
|
|
|
6630
|
|
|
return $color; |
|
|
|
|
|
|
6631
|
|
|
} |
|
6632
|
|
|
|
|
6633
|
|
|
|
|
6634
|
|
|
/** |
|
6635
|
|
|
* Map border style |
|
6636
|
|
|
* OpenOffice documentation: 2.5.11 |
|
6637
|
|
|
* |
|
6638
|
|
|
* @param int $index |
|
6639
|
|
|
* @return string |
|
6640
|
|
|
*/ |
|
6641
|
|
View Code Duplication |
private static function _mapBorderStyle($index) |
|
6642
|
|
|
{ |
|
6643
|
|
|
switch ($index) { |
|
6644
|
|
|
case 0x00: return PHPExcel_Style_Border::BORDER_NONE; |
|
6645
|
|
|
case 0x01: return PHPExcel_Style_Border::BORDER_THIN; |
|
6646
|
|
|
case 0x02: return PHPExcel_Style_Border::BORDER_MEDIUM; |
|
6647
|
|
|
case 0x03: return PHPExcel_Style_Border::BORDER_DASHED; |
|
6648
|
|
|
case 0x04: return PHPExcel_Style_Border::BORDER_DOTTED; |
|
6649
|
|
|
case 0x05: return PHPExcel_Style_Border::BORDER_THICK; |
|
6650
|
|
|
case 0x06: return PHPExcel_Style_Border::BORDER_DOUBLE; |
|
6651
|
|
|
case 0x07: return PHPExcel_Style_Border::BORDER_HAIR; |
|
6652
|
|
|
case 0x08: return PHPExcel_Style_Border::BORDER_MEDIUMDASHED; |
|
6653
|
|
|
case 0x09: return PHPExcel_Style_Border::BORDER_DASHDOT; |
|
6654
|
|
|
case 0x0A: return PHPExcel_Style_Border::BORDER_MEDIUMDASHDOT; |
|
6655
|
|
|
case 0x0B: return PHPExcel_Style_Border::BORDER_DASHDOTDOT; |
|
6656
|
|
|
case 0x0C: return PHPExcel_Style_Border::BORDER_MEDIUMDASHDOTDOT; |
|
6657
|
|
|
case 0x0D: return PHPExcel_Style_Border::BORDER_SLANTDASHDOT; |
|
6658
|
|
|
default: return PHPExcel_Style_Border::BORDER_NONE; |
|
|
|
|
|
|
6659
|
|
|
} |
|
6660
|
|
|
} |
|
6661
|
|
|
|
|
6662
|
|
|
|
|
6663
|
|
|
/** |
|
6664
|
|
|
* Get fill pattern from index |
|
6665
|
|
|
* OpenOffice documentation: 2.5.12 |
|
6666
|
|
|
* |
|
6667
|
|
|
* @param int $index |
|
6668
|
|
|
* @return string |
|
6669
|
|
|
*/ |
|
6670
|
|
|
private static function _mapFillPattern($index) |
|
6671
|
|
|
{ |
|
6672
|
|
|
switch ($index) { |
|
6673
|
|
|
case 0x00: return PHPExcel_Style_Fill::FILL_NONE; |
|
6674
|
|
|
case 0x01: return PHPExcel_Style_Fill::FILL_SOLID; |
|
6675
|
|
|
case 0x02: return PHPExcel_Style_Fill::FILL_PATTERN_MEDIUMGRAY; |
|
6676
|
|
|
case 0x03: return PHPExcel_Style_Fill::FILL_PATTERN_DARKGRAY; |
|
6677
|
|
|
case 0x04: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTGRAY; |
|
6678
|
|
|
case 0x05: return PHPExcel_Style_Fill::FILL_PATTERN_DARKHORIZONTAL; |
|
6679
|
|
|
case 0x06: return PHPExcel_Style_Fill::FILL_PATTERN_DARKVERTICAL; |
|
6680
|
|
|
case 0x07: return PHPExcel_Style_Fill::FILL_PATTERN_DARKDOWN; |
|
6681
|
|
|
case 0x08: return PHPExcel_Style_Fill::FILL_PATTERN_DARKUP; |
|
6682
|
|
|
case 0x09: return PHPExcel_Style_Fill::FILL_PATTERN_DARKGRID; |
|
6683
|
|
|
case 0x0A: return PHPExcel_Style_Fill::FILL_PATTERN_DARKTRELLIS; |
|
6684
|
|
|
case 0x0B: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTHORIZONTAL; |
|
6685
|
|
|
case 0x0C: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTVERTICAL; |
|
6686
|
|
|
case 0x0D: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTDOWN; |
|
6687
|
|
|
case 0x0E: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTUP; |
|
6688
|
|
|
case 0x0F: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTGRID; |
|
6689
|
|
|
case 0x10: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTTRELLIS; |
|
6690
|
|
|
case 0x11: return PHPExcel_Style_Fill::FILL_PATTERN_GRAY125; |
|
6691
|
|
|
case 0x12: return PHPExcel_Style_Fill::FILL_PATTERN_GRAY0625; |
|
6692
|
|
|
default: return PHPExcel_Style_Fill::FILL_NONE; |
|
|
|
|
|
|
6693
|
|
|
} |
|
6694
|
|
|
} |
|
6695
|
|
|
|
|
6696
|
|
|
|
|
6697
|
|
|
/** |
|
6698
|
|
|
* Map error code, e.g. '#N/A' |
|
6699
|
|
|
* |
|
6700
|
|
|
* @param int $subData |
|
6701
|
|
|
* @return string |
|
6702
|
|
|
*/ |
|
6703
|
|
|
private static function _mapErrorCode($subData) |
|
6704
|
|
|
{ |
|
6705
|
|
|
switch ($subData) { |
|
6706
|
|
|
case 0x00: return '#NULL!'; break; |
|
|
|
|
|
|
6707
|
|
|
case 0x07: return '#DIV/0!'; break; |
|
|
|
|
|
|
6708
|
|
|
case 0x0F: return '#VALUE!'; break; |
|
|
|
|
|
|
6709
|
|
|
case 0x17: return '#REF!'; break; |
|
|
|
|
|
|
6710
|
|
|
case 0x1D: return '#NAME?'; break; |
|
|
|
|
|
|
6711
|
|
|
case 0x24: return '#NUM!'; break; |
|
|
|
|
|
|
6712
|
|
|
case 0x2A: return '#N/A'; break; |
|
|
|
|
|
|
6713
|
|
|
default: return false; |
|
|
|
|
|
|
6714
|
|
|
} |
|
6715
|
|
|
} |
|
6716
|
|
|
|
|
6717
|
|
|
|
|
6718
|
|
|
/** |
|
6719
|
|
|
* Map built-in color to RGB value |
|
6720
|
|
|
* |
|
6721
|
|
|
* @param int $color Indexed color |
|
6722
|
|
|
* @return array |
|
6723
|
|
|
*/ |
|
6724
|
|
|
private static function _mapBuiltInColor($color) |
|
6725
|
|
|
{ |
|
6726
|
|
|
switch ($color) { |
|
6727
|
|
|
case 0x00: return array('rgb' => '000000'); |
|
6728
|
|
|
case 0x01: return array('rgb' => 'FFFFFF'); |
|
6729
|
|
|
case 0x02: return array('rgb' => 'FF0000'); |
|
6730
|
|
|
case 0x03: return array('rgb' => '00FF00'); |
|
6731
|
|
|
case 0x04: return array('rgb' => '0000FF'); |
|
6732
|
|
|
case 0x05: return array('rgb' => 'FFFF00'); |
|
6733
|
|
|
case 0x06: return array('rgb' => 'FF00FF'); |
|
6734
|
|
|
case 0x07: return array('rgb' => '00FFFF'); |
|
6735
|
|
|
case 0x40: return array('rgb' => '000000'); // system window text color |
|
6736
|
|
|
case 0x41: return array('rgb' => 'FFFFFF'); // system window background color |
|
6737
|
|
|
default: return array('rgb' => '000000'); |
|
|
|
|
|
|
6738
|
|
|
} |
|
6739
|
|
|
} |
|
6740
|
|
|
|
|
6741
|
|
|
|
|
6742
|
|
|
/** |
|
6743
|
|
|
* Map color array from BIFF5 built-in color index |
|
6744
|
|
|
* |
|
6745
|
|
|
* @param int $subData |
|
6746
|
|
|
* @return array |
|
6747
|
|
|
*/ |
|
6748
|
|
|
private static function _mapColorBIFF5($subData) |
|
6749
|
|
|
{ |
|
6750
|
|
|
switch ($subData) { |
|
6751
|
|
|
case 0x08: return array('rgb' => '000000'); |
|
6752
|
|
|
case 0x09: return array('rgb' => 'FFFFFF'); |
|
6753
|
|
|
case 0x0A: return array('rgb' => 'FF0000'); |
|
6754
|
|
|
case 0x0B: return array('rgb' => '00FF00'); |
|
6755
|
|
|
case 0x0C: return array('rgb' => '0000FF'); |
|
6756
|
|
|
case 0x0D: return array('rgb' => 'FFFF00'); |
|
6757
|
|
|
case 0x0E: return array('rgb' => 'FF00FF'); |
|
6758
|
|
|
case 0x0F: return array('rgb' => '00FFFF'); |
|
6759
|
|
|
case 0x10: return array('rgb' => '800000'); |
|
6760
|
|
|
case 0x11: return array('rgb' => '008000'); |
|
6761
|
|
|
case 0x12: return array('rgb' => '000080'); |
|
6762
|
|
|
case 0x13: return array('rgb' => '808000'); |
|
6763
|
|
|
case 0x14: return array('rgb' => '800080'); |
|
6764
|
|
|
case 0x15: return array('rgb' => '008080'); |
|
6765
|
|
|
case 0x16: return array('rgb' => 'C0C0C0'); |
|
6766
|
|
|
case 0x17: return array('rgb' => '808080'); |
|
6767
|
|
|
case 0x18: return array('rgb' => '8080FF'); |
|
6768
|
|
|
case 0x19: return array('rgb' => '802060'); |
|
6769
|
|
|
case 0x1A: return array('rgb' => 'FFFFC0'); |
|
6770
|
|
|
case 0x1B: return array('rgb' => 'A0E0F0'); |
|
6771
|
|
|
case 0x1C: return array('rgb' => '600080'); |
|
6772
|
|
|
case 0x1D: return array('rgb' => 'FF8080'); |
|
6773
|
|
|
case 0x1E: return array('rgb' => '0080C0'); |
|
6774
|
|
|
case 0x1F: return array('rgb' => 'C0C0FF'); |
|
6775
|
|
|
case 0x20: return array('rgb' => '000080'); |
|
6776
|
|
|
case 0x21: return array('rgb' => 'FF00FF'); |
|
6777
|
|
|
case 0x22: return array('rgb' => 'FFFF00'); |
|
6778
|
|
|
case 0x23: return array('rgb' => '00FFFF'); |
|
6779
|
|
|
case 0x24: return array('rgb' => '800080'); |
|
6780
|
|
|
case 0x25: return array('rgb' => '800000'); |
|
6781
|
|
|
case 0x26: return array('rgb' => '008080'); |
|
6782
|
|
|
case 0x27: return array('rgb' => '0000FF'); |
|
6783
|
|
|
case 0x28: return array('rgb' => '00CFFF'); |
|
6784
|
|
|
case 0x29: return array('rgb' => '69FFFF'); |
|
6785
|
|
|
case 0x2A: return array('rgb' => 'E0FFE0'); |
|
6786
|
|
|
case 0x2B: return array('rgb' => 'FFFF80'); |
|
6787
|
|
|
case 0x2C: return array('rgb' => 'A6CAF0'); |
|
6788
|
|
|
case 0x2D: return array('rgb' => 'DD9CB3'); |
|
6789
|
|
|
case 0x2E: return array('rgb' => 'B38FEE'); |
|
6790
|
|
|
case 0x2F: return array('rgb' => 'E3E3E3'); |
|
6791
|
|
|
case 0x30: return array('rgb' => '2A6FF9'); |
|
6792
|
|
|
case 0x31: return array('rgb' => '3FB8CD'); |
|
6793
|
|
|
case 0x32: return array('rgb' => '488436'); |
|
6794
|
|
|
case 0x33: return array('rgb' => '958C41'); |
|
6795
|
|
|
case 0x34: return array('rgb' => '8E5E42'); |
|
6796
|
|
|
case 0x35: return array('rgb' => 'A0627A'); |
|
6797
|
|
|
case 0x36: return array('rgb' => '624FAC'); |
|
6798
|
|
|
case 0x37: return array('rgb' => '969696'); |
|
6799
|
|
|
case 0x38: return array('rgb' => '1D2FBE'); |
|
6800
|
|
|
case 0x39: return array('rgb' => '286676'); |
|
6801
|
|
|
case 0x3A: return array('rgb' => '004500'); |
|
6802
|
|
|
case 0x3B: return array('rgb' => '453E01'); |
|
6803
|
|
|
case 0x3C: return array('rgb' => '6A2813'); |
|
6804
|
|
|
case 0x3D: return array('rgb' => '85396A'); |
|
6805
|
|
|
case 0x3E: return array('rgb' => '4A3285'); |
|
6806
|
|
|
case 0x3F: return array('rgb' => '424242'); |
|
6807
|
|
|
default: return array('rgb' => '000000'); |
|
|
|
|
|
|
6808
|
|
|
} |
|
6809
|
|
|
} |
|
6810
|
|
|
|
|
6811
|
|
|
|
|
6812
|
|
|
/** |
|
6813
|
|
|
* Map color array from BIFF8 built-in color index |
|
6814
|
|
|
* |
|
6815
|
|
|
* @param int $subData |
|
6816
|
|
|
* @return array |
|
6817
|
|
|
*/ |
|
6818
|
|
|
private static function _mapColor($subData) |
|
6819
|
|
|
{ |
|
6820
|
|
|
switch ($subData) { |
|
6821
|
|
|
case 0x08: return array('rgb' => '000000'); |
|
6822
|
|
|
case 0x09: return array('rgb' => 'FFFFFF'); |
|
6823
|
|
|
case 0x0A: return array('rgb' => 'FF0000'); |
|
6824
|
|
|
case 0x0B: return array('rgb' => '00FF00'); |
|
6825
|
|
|
case 0x0C: return array('rgb' => '0000FF'); |
|
6826
|
|
|
case 0x0D: return array('rgb' => 'FFFF00'); |
|
6827
|
|
|
case 0x0E: return array('rgb' => 'FF00FF'); |
|
6828
|
|
|
case 0x0F: return array('rgb' => '00FFFF'); |
|
6829
|
|
|
case 0x10: return array('rgb' => '800000'); |
|
6830
|
|
|
case 0x11: return array('rgb' => '008000'); |
|
6831
|
|
|
case 0x12: return array('rgb' => '000080'); |
|
6832
|
|
|
case 0x13: return array('rgb' => '808000'); |
|
6833
|
|
|
case 0x14: return array('rgb' => '800080'); |
|
6834
|
|
|
case 0x15: return array('rgb' => '008080'); |
|
6835
|
|
|
case 0x16: return array('rgb' => 'C0C0C0'); |
|
6836
|
|
|
case 0x17: return array('rgb' => '808080'); |
|
6837
|
|
|
case 0x18: return array('rgb' => '9999FF'); |
|
6838
|
|
|
case 0x19: return array('rgb' => '993366'); |
|
6839
|
|
|
case 0x1A: return array('rgb' => 'FFFFCC'); |
|
6840
|
|
|
case 0x1B: return array('rgb' => 'CCFFFF'); |
|
6841
|
|
|
case 0x1C: return array('rgb' => '660066'); |
|
6842
|
|
|
case 0x1D: return array('rgb' => 'FF8080'); |
|
6843
|
|
|
case 0x1E: return array('rgb' => '0066CC'); |
|
6844
|
|
|
case 0x1F: return array('rgb' => 'CCCCFF'); |
|
6845
|
|
|
case 0x20: return array('rgb' => '000080'); |
|
6846
|
|
|
case 0x21: return array('rgb' => 'FF00FF'); |
|
6847
|
|
|
case 0x22: return array('rgb' => 'FFFF00'); |
|
6848
|
|
|
case 0x23: return array('rgb' => '00FFFF'); |
|
6849
|
|
|
case 0x24: return array('rgb' => '800080'); |
|
6850
|
|
|
case 0x25: return array('rgb' => '800000'); |
|
6851
|
|
|
case 0x26: return array('rgb' => '008080'); |
|
6852
|
|
|
case 0x27: return array('rgb' => '0000FF'); |
|
6853
|
|
|
case 0x28: return array('rgb' => '00CCFF'); |
|
6854
|
|
|
case 0x29: return array('rgb' => 'CCFFFF'); |
|
6855
|
|
|
case 0x2A: return array('rgb' => 'CCFFCC'); |
|
6856
|
|
|
case 0x2B: return array('rgb' => 'FFFF99'); |
|
6857
|
|
|
case 0x2C: return array('rgb' => '99CCFF'); |
|
6858
|
|
|
case 0x2D: return array('rgb' => 'FF99CC'); |
|
6859
|
|
|
case 0x2E: return array('rgb' => 'CC99FF'); |
|
6860
|
|
|
case 0x2F: return array('rgb' => 'FFCC99'); |
|
6861
|
|
|
case 0x30: return array('rgb' => '3366FF'); |
|
6862
|
|
|
case 0x31: return array('rgb' => '33CCCC'); |
|
6863
|
|
|
case 0x32: return array('rgb' => '99CC00'); |
|
6864
|
|
|
case 0x33: return array('rgb' => 'FFCC00'); |
|
6865
|
|
|
case 0x34: return array('rgb' => 'FF9900'); |
|
6866
|
|
|
case 0x35: return array('rgb' => 'FF6600'); |
|
6867
|
|
|
case 0x36: return array('rgb' => '666699'); |
|
6868
|
|
|
case 0x37: return array('rgb' => '969696'); |
|
6869
|
|
|
case 0x38: return array('rgb' => '003366'); |
|
6870
|
|
|
case 0x39: return array('rgb' => '339966'); |
|
6871
|
|
|
case 0x3A: return array('rgb' => '003300'); |
|
6872
|
|
|
case 0x3B: return array('rgb' => '333300'); |
|
6873
|
|
|
case 0x3C: return array('rgb' => '993300'); |
|
6874
|
|
|
case 0x3D: return array('rgb' => '993366'); |
|
6875
|
|
|
case 0x3E: return array('rgb' => '333399'); |
|
6876
|
|
|
case 0x3F: return array('rgb' => '333333'); |
|
6877
|
|
|
default: return array('rgb' => '000000'); |
|
|
|
|
|
|
6878
|
|
|
} |
|
6879
|
|
|
} |
|
6880
|
|
|
|
|
6881
|
|
|
|
|
6882
|
|
|
private function _parseRichText($is = '') { |
|
6883
|
|
|
$value = new PHPExcel_RichText(); |
|
6884
|
|
|
|
|
6885
|
|
|
$value->createText($is); |
|
6886
|
|
|
|
|
6887
|
|
|
return $value; |
|
6888
|
|
|
} |
|
6889
|
|
|
|
|
6890
|
|
|
} |
|
6891
|
|
|
|
This check marks private properties in classes that are never used. Those properties can be removed.