18588f1ef5896f62ed0f271053cb4f0bc5367c37
[fa-stable.git] / reporting / includes / ExcelWriterXML_Sheet.php
1 <?php
2 /**
3  * File contains the class files for ExcelWriterXML_Sheet
4  * @package ExcelWriterXML
5  */
6
7 /**
8  * Class for generating sheets within the Excel document
9  * @link http://msdn.microsoft.com/en-us/library/aa140066(office.10).aspx
10  * @author Robert F Greer
11  * @version 1.0
12  * @package ExcelWriterXML
13  * @uses ExcelWriterXML_Style::alignHorizontal()
14  * @uses ExcelWriterXML_Style::alignRotate()
15  * @uses ExcelWriterXML_Style::alignShrinktofit()
16  * @uses ExcelWriterXML_Style::alignVertical()
17  * @uses ExcelWriterXML_Style::alignVerticaltext()
18  * @uses ExcelWriterXML_Style::alignWraptext()
19  * @uses ExcelWriterXML_Style::bgColor()
20  * @uses ExcelWriterXML_Style::bgPattern()
21  * @uses ExcelWriterXML_Style::bgPatternColor()
22  * @uses ExcelWriterXML_Style::border()
23  * @uses ExcelWriterXML_Style::checkColor()
24  * @uses ExcelWriterXML_Style::fontBold()
25  * @uses ExcelWriterXML_Style::fontColor()
26  * @uses ExcelWriterXML_Style::fontFamily()
27  * @uses ExcelWriterXML_Style::fontItalic()
28  * @uses ExcelWriterXML_Style::fontName()
29  * @uses ExcelWriterXML_Style::fontOutline()
30  * @uses ExcelWriterXML_Style::fontShadow()
31  * @uses ExcelWriterXML_Style::fontStrikethrough()
32  * @uses ExcelWriterXML_Style::fontUnderline()
33  * @uses ExcelWriterXML_Style::getErrors()
34  * @uses ExcelWriterXML_Style::getID()
35  * @uses ExcelWriterXML_Style::getStyleXML()
36  * @uses ExcelWriterXML_Style::name()
37  * @uses ExcelWriterXML_Style::numberFormat()
38  * @uses ExcelWriterXML_Style::numberFormatDate()
39  * @uses ExcelWriterXML_Style::numberFormatDatetime()
40  * @uses ExcelWriterXML_Style::numberFormatTime()
41  */
42 class ExcelWriterXML_Sheet 
43 {
44         // Private Variables
45         var $id;
46         var $cells = array();
47         var $colWidth = array();
48         var $rowHeight = array();
49         var $URLs = array();
50         var $mergeCells = array();
51         var $comments = array();
52         var $formatErrors = array();
53         var $displayRightToLeft = false;
54         /////////////////////
55         
56         // Public Variables
57         /////////////////////
58         
59         // Constructor
60         /**
61      * Constructor for a new Sheet
62      * @param string $id The name of the sheet to be referenced within the
63      * spreadsheet
64      */
65         function ExcelWriterXML_Sheet($id)
66         {
67                 $this->id = $id;
68         }
69         
70         /**
71          * Function to get the named value of the Sheet
72          * @return string Name of the Sheet
73          */
74         function getID()
75         {
76                 return $this->id;
77         }
78         
79         /**
80          * Adds a format error.  When the document is generated if there are any
81          * errors they will be listed on a seperate sheet.
82          * @param string $function The name of the function that was called
83          * @param string $message Details of the error
84          */
85         function _addError($function, $message)
86         {
87                 $tmp = array(
88                         'sheet'         => $this->id,
89                         'function'      => $function,
90                         'message'       => $message,
91                 );
92                 $this->formatErrors[] = $tmp;
93         }
94         
95         /**
96          * Returns any errors found in the sheet
97          * @return mixed Array of errors if they exist, otherwise false
98          */
99         function getErrors()
100         {
101                 return($this->formatErrors);
102         }
103
104         /**
105      * Converts a MySQL type datetime field to a value that can be used within
106      * Excel.
107      * If the passed value is not valid then the passed string is sent back.
108      * @param string $datetime Value must in in the format "yyyy-mm-dd hh:ii:ss"
109      * @return string Value in the Excel format "yyyy-mm-ddThh:ii:ss.000"
110      */
111         function convertMysqlDatetime($datetime)
112         {
113                 $datetime = trim($datetime);
114                 $pattern = "/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/";
115                 if (preg_match($pattern, $datetime, $matches)) 
116                 {
117                         $datetime = $matches[0];
118                         list($date,$time) = explode(' ',$datetime);
119                         return($date.'T'.$time.'.000');
120                 }
121                 else
122                 {
123                         return($datetime);
124                 }
125         }
126         
127         /**
128      * Converts a MySQL type date field to a value that can be used within Excel
129      * If the passed value is not valid then the passed string is sent back.
130      * @param string $datetime Value must in in the format "yyyy-mm-dd hh:ii:ss"
131      * or "yyyy-mm-dd"
132      * @return string Value in the Excel format "yyyy-mm-ddT00:00:00.000"
133      */
134         function convertMysqlDate($datetime)
135         {
136                 $datetime = trim($datetime);
137                 $pattern1 = "/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/";
138                 $pattern2 = "/[0-9]{4}-[0-9]{2}-[0-9]{2}/";
139                 if (preg_match($pattern1, $datetime, $matches)) 
140                 {
141                         $datetime = $matches[0];
142                         list($date,$time) = explode(' ',$datetime);
143                         return($date.'T'.$time.'.000');
144                 }
145                 elseif (preg_match($pattern2, $datetime, $matches)) 
146                 {
147                         $date = $matches[0];
148                         return($date.'T00:00:00.000');
149                 }
150                 else
151                 {
152                         return($datetime);
153                 }
154         }
155         
156         /**
157      * Converts a MySQL type time field to a value that can be used within Excel
158      * If the passed value is not valid then the passed string is sent back.
159      * @param string $datetime Value must in in the format "yyyy-mm-dd hh:ii:ss"
160      * or "hh:ii:ss"
161      * @return string Value in the Excel format "1899-12-31Thh:ii:ss.000"
162      */
163         function convertMysqlTime($datetime)
164         {
165                 $datetime = trim($datetime);
166                 $pattern1 = "/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/";
167                 $pattern2 = "/[0-9]{2}:[0-9]{2}:[0-9]{2}/";
168                 if (preg_match($pattern1, $datetime, $matches)) 
169                 {
170                         $datetime = $matches[0];
171                         list($date,$time) = explode(' ',$datetime);
172                         return($date.'T'.$time.'.000');
173                 }
174                 elseif (preg_match($pattern2, $datetime, $matches)) 
175                 {
176                         $time = $matches[0];
177                         return('1899-12-31T'.$time.'.000');
178                 }
179                 else
180                 {
181                         return($datetime);
182                 }
183         }
184         
185
186         /**
187      * Writes a formula to a cell
188      * From MS
189      * Specifies the formula stored in this cell. All formulas are persisted in
190      * R1C1 notation because they are significantly easier to parse and generate
191      * than A1-style formulas. The formula is calculated upon reload unless
192      * calculation is set to manual. Recalculation of the formula overrides the
193      * value in this cell's Value attribute.
194      * @see writeFormula()
195      * @param string $dataType Type of data that the formula should generate,
196      * "String" "Number" "DateTime"
197      * @param integer $row Row, based upon a "1" based array
198      * @param integer $column Column, based upon a "1" based array
199      * @param string $data Formula data to be written to a cell
200      * @param mixed $style Named style, or style reference to be applied to the
201      * cell
202      */
203         function writeFormula($dataType,$row,$column,$data,$style = null)
204         {
205                 if ($dataType != 'String'
206                         && $dataType != 'Number'
207                         && $dataType != 'DateTime')
208                 {
209                         $this->_addError(__FUNCTION__,'('.$row.','.$column.') DataType for formula was not valid "'.$dataType.'"');
210                         $halign = 'Automatic';
211                 }
212
213                 $this->_writeData('String',$row,$column,'',$style,$data);
214         }
215
216         /**
217      * Writes a string to a cell
218      * @see writeData()
219      * @param integer $row Row, based upon a "1" based array
220      * @param integer $column Column, based upon a "1" based array
221      * @param string $data String data to be written to a cell
222      * @param mixed $style Named style, or style reference to be applied to the
223      * cell
224      */
225         function writeString($row,$column,$data,$style = null)
226         {
227                 $this->_writeData('String',$row,$column,$data,$style);
228         }
229         
230         /**
231      * Writes a number to a cell.
232      * If the data is not numeric then the function will write the data as a
233      * string.
234      * @see writeData()
235      * @param integer $row Row, based upon a "1" based array
236      * @param integer $column Column, based upon a "1" based array
237      * @param mixed $data Number data to be written to a cell
238      * @param mixed $style Named style, or style reference to be applied to the
239      * cell
240      */
241         function writeNumber($row,$column,$data,$style = null)
242         {
243                 if (!is_numeric($data))
244                 {
245                         $this->_writeData('String',$row,$column,$data,$style);
246                         $this->_addError(__FUNCTION__,'('.$row.','.$column.') Tried to write non-numeric data to type Number "'.$data.'"');
247                 }
248                 else
249                 {               
250                         $this->_writeData('Number',$row,$column,$data,$style);
251                 }
252         }
253         
254         /**
255      * Writes a Date/Time to a cell.
256      * If data is not valid the function will write the passed value as a
257      * string.
258      * @see writeData()
259      * @param integer $row Row, based upon a "1" based array
260      * @param integer $column Column, based upon a "1" based array
261      * @param string $data Date or Time data to be written to a cell.  This must
262      * be in the format "yyyy-mm-ddThh:ii:ss.000" for Excel to recognize it.
263      * @param mixed $style Named style, or style reference to be applied to the
264      * cell
265      */
266         function writeDateTime($row,$column,$data,$style = null)
267         {
268                 $pattern = "/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.000/";
269                 if (preg_match($pattern, $data, $matches)) 
270                 {
271                         $data = $matches[0];
272                         $this->_writeData('DateTime',$row,$column,$data,$style);
273                 }
274                 else
275                 {
276                         $this->_writeData('String',$row,$column,$data,$style);
277                         $this->_addError(__FUNCTION__,'('.$row.','.$column.') Tried to write invalid datetime data to type DateTime "'.$data.'"');
278                 }
279         }
280         function _writeData($type,$row,$column,$data,$style = null,$formula = null)
281         {
282                 if ($style != null)
283                 {
284                         if (gettype($style) == 'object')
285                         {
286                                 if (get_class($style) == 'ExcelWriterXML_Style')
287                                 {
288                                         $styleID = $style->getID();
289                                 }
290                                 else
291                                 {
292                                         $this->_addError(__FUNCTION__,'('.$row.','.$column.') StyleID supplied was an object, but not a style object "'.get_class($style).'"');
293                                         $styleID = null;
294                                 }
295                         }
296                         else
297                         {
298                                 $styleID = $style;
299                         }
300                 }
301                 else
302                 {
303                         $styleID = null;
304                 }
305                 
306                 $cell = array(
307                         'type'          => $type,
308                         'style'         => $styleID,
309                         'data'          => $data,
310                         'formula'       => $formula,
311                 );
312                 $this->cells[$row][$column] = $cell;
313         }
314         
315         /**
316          * Displays the sheet in Right to Left format
317          */
318         function displayRightToLeft()
319         {
320                 $this->displayRightToLeft = true;
321         }
322
323         /**
324      * Called by the ExcelWriterXML class to get the XML data for this object
325      * @return string Contains only the XML data for the sheet
326      */
327         function getSheetXML()
328         {
329                 ksort($this->cells);
330                 
331                 $displayRightToLeft = ($this->displayRightToLeft) ? 'ss:RightToLeft="1"' : '';
332                 
333                 $xml = '<Worksheet ss:Name="'.$this->id.'" '.$displayRightToLeft.'>'."\r";
334                 $xml .= '       <Table>'."\r";
335                 foreach($this->colWidth as $colIndex => $colWidth)
336                 {
337                           $xml .= '             <Column ss:Index="'.$colIndex.'" ss:AutoFitWidth="0" ss:Width="'.$colWidth.'"/>'."\r";
338                 }
339                 foreach($this->cells as $row => $rowData)
340                 {
341                         ksort($rowData);
342                         if (isset($this->rowHeight[$row]))
343                         {
344                                 $rowHeight = 'ss:AutoFitHeight="0" ss:Height="'.$this->rowHeight[$row].'"';
345                         }
346                         else
347                         {
348                                 $rowHeight = '';
349                         }
350                         $xml .= '               <Row ss:Index="'.$row.'" '.$rowHeight.' >'."\r";
351                         foreach($rowData as $column => $cell)
352                         {
353                                 if (!empty($cell['formula'])) 
354                                         $formula = 'ss:Formula="'.$cell['formula'].'"';
355                                 else 
356                                         $formula = '';
357                                 if (!empty($cell['style'])) 
358                                         $style = 'ss:StyleID="'.$cell['style'].'"';
359                                 else 
360                                         $style = '';
361                                 if (empty($this->URLs[$row][$column])) 
362                                         $URL = '';
363                                 else 
364                                         $URL = 'ss:HRef="'.htmlspecialchars($this->URLs[$row][$column]).'"';
365                                 if (empty($this->mergeCells[$row][$column])) 
366                                         $mergeCell = '';
367                                 else 
368                                         $mergeCell = 'ss:MergeAcross="'.$this->mergeCells[$row][$column]['width'].'" ss:MergeDown="'.$this->mergeCells[$row][$column]['height'].'"';
369                                 if (empty($this->comments[$row][$column])) 
370                                         $comment = '';
371                                 else
372                                 {
373                                         $comment = '                                    <Comment ss:Author="'.$this->comments[$row][$column]['author'].'">'."\r";
374                                         $comment .= '                                   <ss:Data xmlns="http://www.w3.org/TR/REC-html40">'."\r";
375                                         $comment .= '                                   <B><Font html:Face="Tahoma" x:CharSet="1" html:Size="8" html:Color="#000000">'.htmlspecialchars($this->comments[$row][$column]['author']).':</Font></B>'."\r";
376                                         $comment .= '                                   <Font html:Face="Tahoma" x:CharSet="1" html:Size="8" html:Color="#000000">'.htmlspecialchars($this->comments[$row][$column]['comment']).'</Font>'."\r";
377                                         $comment .= '                                   </ss:Data>'."\r";
378                                         $comment .= '                                   </Comment>'."\r";
379                                 }
380                                 $type = $cell['type'];
381                                 $data = $cell['data'];
382                                 
383                                 $xml .= '                       <Cell '.$style.' ss:Index="'.$column.'" '.$URL.' '.$mergeCell.' '.$formula.'>'."\r";
384                                 $xml .= '                               <Data ss:Type="'.$type.'">';
385                                 $xml .= htmlspecialchars($data);
386                                 $xml .= '</Data>'."\r";
387                                 $xml .= $comment;
388                                 $xml .= '                       </Cell>'."\r";
389                         }
390                         $xml .= '               </Row>'."\r";
391                 }
392                 $xml .= '       </Table>'."\r";
393                 $xml .= '</Worksheet>'."\r";
394                 return($xml);
395         }
396
397         /**
398      * Alias for function columnWidth()
399      */
400         function cellWidth( $row, $col,$width = 48) { $this->columnWidth($col,$width); }
401
402         /**
403      * Sets the width of a cell.
404      * Sets  the width of the column that the cell resides in.
405      * Cell width of zero effectively hides the column
406      * @param integer $row Row, based upon a "1" based array
407      * @param integer $col Column, based upon a "1" based array
408      * @param mixed $width Width of the cell/column, default is 48
409      */
410         function columnWidth( $col,$width = 48) { $this->colWidth[$col] = $width; }
411
412         /**
413      * Alias for function rowHeight()
414      */
415         function cellHeight( $row, $col,$height = 12.5) { $this->rowHeight($row,$height); }
416
417         /**
418      * Sets the height of a cell.
419      * Sets  the height of the column that the cell resides in.
420      * Cell height of zero effectively hides the row
421      * @param integer $row Row, based upon a "1" based array
422      * @param integer $col Column, based upon a "1" based array
423      * @param mixed $height Height of the cell/column, default is 12.5
424      */
425         function rowHeight( $row,$height = 12.5) { $this->rowHeight[$row] = $height; }
426
427         /**
428      * Makes the target cell a link to a URL
429      * @param integer $row Row, based upon a "1" based array
430      * @param integer $col Column, based upon a "1" based array
431      * @param string $URL The URL that the link should point to
432      */
433         function addURL( $row, $col,$URL) { $this->URLs[$row][$col] = $URL; }
434         
435         /**
436      * Merges 2 or more cells.
437      * The function acts like a bounding box, with the row and column defining
438      * the upper left corner, and the width and height extending the box.
439      * If width or height are zero (or ommitted) then the function does nothing.
440      * @param integer $row Row, based upon a "1" based array
441      * @param integer $col Column, based upon a "1" based array
442      * @param integer $width Number of cells to the right to merge with
443      * @param integer $height Number of cells down to merge with
444      */
445         function cellMerge($row,$col, $width = 0, $height = 0)
446         {
447                 if ($width < 0 || $height < 0)
448                 {
449                         $this->_addError(__FUNCTION__,'('.$row.','.$col.') Tried to merge cells with width/height < 0 "(w='.$width.',h='.$height.')"');
450                         return;
451                 }
452                 
453                 $this->mergeCells[$row][$col] = array(
454                         'width'         => $width,
455                         'height'        => $height,
456                 );
457                 /* I don't think this code is necessary
458                 if (!isset($cells[$row][$col]))
459                 {
460                         $this->writeString($row,$col,'');
461                 }
462                 */
463         }
464         
465         /**
466      * Adds a comment to a cell
467      * @param integer $row Row, based upon a "1" based array
468      * @param integer $col Column, based upon a "1" based array
469      * @param string $comment The comment to be displayed on the cell
470      * @param string $author The comment will show a bold header displaying the
471      * author
472      */
473         function addComment( $row, $col,$comment,$author = 'SYSTEM')
474         {
475                 $this->comments[$row][$col] = array(
476                         'comment'       => $comment,
477                         'author'        => $author,
478                 );
479         }
480 }
481 ?>