1<?php
2
3require_once DOKU_PLUGIN . 'odt/ODT/ODTUnits.php';
4require_once DOKU_PLUGIN . 'odt/ODT/ODTDocument.php';
5
6/**
7 * ODTTable:
8 * Class containing static code for handling tables.
9 *
10 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
11 */
12class ODTTable
13{
14    /**
15     * Open/start a table
16     *
17     * @param int $maxcols maximum number of columns
18     * @param int $numrows NOT IMPLEMENTED
19     */
20    public static function tableOpen(ODTInternalParams $params, $maxcols = NULL, $numrows = NULL, $tableStyleName=NULL, $element=NULL, $attributes=NULL){
21        if (!isset($element)) {
22            $element = 'table';
23        }
24        $elementObj = $params->elementObj;
25
26        // Close any open paragraph.
27        $params->document->paragraphClose();
28
29        // Do additional actions if the parent element is a list.
30        // In this case we need to finish the list and re-open it later
31        // after the table has been closed! --> tables may not be part of a list item in ODT!
32
33        $interrupted = false;
34        if (!isset($tableStyleName)) {
35            $tableStyleName = $params->document->getStyleName('table');
36        }
37
38        $list_item = $params->document->state->getCurrentListItem();
39        if (isset($list_item)) {
40            // We are in a list item. Query indentation settings.
41            $list = $list_item->getList();
42            if (isset($list)) {
43                $list_style_name = $list->getStyleName();
44                $list_style = $params->document->getStyle($list_style_name);
45                if (isset($list_style)) {
46                    // The list level stored in the list item/from the parser
47                    // might not be correct. Count 'list' states to get level.
48                    $level = $params->document->state->countClass('list');
49
50                    // Create a table style for indenting the table.
51                    // We try to achieve this by substracting the list indentation
52                    // from the width of the table and right align it!
53                    // (if not done yet, the name must be unique!)
54                    $count = $params->document->state->getElementCount('table')+1;
55                    $style_name = 'Table'.$count.'_Indentation_Level'.$level;
56                    if (!$params->document->styleExists($style_name)) {
57                        $style_obj = clone $params->document->getStyle($tableStyleName);
58                        $style_obj->setProperty('style-name', $style_name);
59                        if (isset($style_obj)) {
60                            $max = $params->document->getAbsWidthMindMargins();
61                            $indent = 0 + ODTUnits::getDigits($list_style->getPropertyFromLevel($level, 'margin-left'));
62                            $style_obj->setProperty('margin-left', ($indent).'cm');
63                            if ($style_obj->getProperty('width') == NULL && $style_obj->getProperty('rel-width')) {
64                                $style_obj->setProperty('width', ($max-$indent).'cm');
65                            }
66                            $style_obj->setProperty('align', 'left');
67                            $params->document->addAutomaticStyle($style_obj);
68                        }
69                    }
70                    $tableStyleName = $style_name;
71                }
72            }
73
74            // Close all open lists and remember their style (may be nested!)
75            $lists = array();
76            $first = true;
77            $iterations = 0;
78            $list = $params->document->state->getCurrentList();
79            while (isset($list))
80            {
81                // Close list items
82                if ($first == true) {
83                    $first = false;
84                    $params->document->listContentClose();
85                }
86                $params->document->listItemClose();
87
88                // Now we are in the list state!
89                // Get the lists style name before closing it.
90                $lists [] = $list->getStyleName();
91                // Reset saved last paragraph position to -1 to prevent change of the paragraph style
92                $list->setListLastParagraphPosition(-1);
93                $params->document->listClose();
94
95                if (!isset($params->document->state) ||
96                    $params->document->state->getCurrent()->getElementName() == 'root') {
97                    break;
98                }
99
100                // List has been closed (and removed from stack). Get next.
101                $list = $params->document->state->getCurrentList();
102
103                // Just to prevent endless loops in case of an error!
104                $iterations++;
105                if ($iterations == 50) {
106                    $params->content .= 'Error: ENDLESS LOOP!';
107                    break;
108                }
109            }
110
111            $interrupted = true;
112        }
113
114        if (!isset($elementObj)) {
115            $properties = array();
116            ODTUtility::openHTMLElement ($params, $properties, $element, $attributes);
117        }
118
119        $table = new ODTElementTable($tableStyleName, $maxcols, $numrows);
120        $params->document->state->enter($table);
121        if ($interrupted == true) {
122            // Set marker that list has been interrupted
123            $table->setListInterrupted(true);
124
125            // Save the lists array as temporary data
126            // in THIS state because this is the state that we get back
127            // to in table_close!!!
128            // (we closed the ODT list, we can't access its state info anymore!
129            //  So we use the table state to save the style name!)
130            $table->setTemp($lists);
131        }
132        $table->setHTMLElement ($element);
133
134        $params->content .= $table->getOpeningTag();
135    }
136
137    /**
138     * Close/finish a table
139     */
140    public static function tableClose(ODTInternalParams $params){
141        $table = $params->document->state->getCurrentTable();
142        if (!isset($table)) {
143            // ??? Error. Not table found.
144            return;
145        }
146
147        if ($params->document->state->getInTableRow()) {
148            // If we are still inside a table row then close it first,
149            // to prevent an error or broken document.
150            $params->document->tableRowClose();
151        }
152
153        $interrupted = $table->getListInterrupted();
154        $lists = NULL;
155        if ($interrupted) {
156            $lists = $table->getTemp();
157        }
158
159        // Eventually adjust table width.
160        $table->adjustWidth ($params);
161
162        // Close the table.
163        ODTUtility::closeHTMLElement ($params, $params->document->state->getHTMLElement());
164        $params->content .= $table->getClosingTag($params->content);
165        $params->document->state->leave();
166
167        // Do additional actions required if we interrupted a list,
168        // see table_open()
169        if ($interrupted) {
170            // Re-open list(s) with original style!
171            // (in revers order of lists array)
172            $max = count($lists);
173            for ($index = $max ; $index > 0 ; $index--) {
174                $params->document->listOpen(true, $lists [$index-1]);
175
176                // If this is not the most inner list then we need to open
177                // a list item too!
178                if ($index > 0) {
179                    $params->document->listItemOpen($max-$index);
180                }
181            }
182
183            // DO NOT set marker that list is not interrupted anymore, yet!
184            // The renderer will still call listcontent_close and listitem_close!
185            // The marker will only be reset on the next call from the renderer to listitem_open!!!
186            //$table->setListInterrupted(false);
187        }
188    }
189
190    /**
191     * @param array $properties
192     */
193    public static function tableAddColumn (ODTInternalParams $params, $styleNameSet=NULL, &$styleNameGet=NULL){
194        // Create new column
195        $column = new ODTElementTableColumn();
196        $params->document->state->enter($column);
197
198        if (isset($styleNameSet)) {
199            // Change automatically assigned style name.
200            $column->setStyleName($styleNameSet);
201        }
202
203        // Return style name to caller.
204        $styleNameGet = $column->getStyleName();
205
206        // Never create any new document content here!!!
207        // Columns have already been added on table open or are
208        // re-written on table close.
209        $params->document->state->leave();
210    }
211
212    /**
213     * Open a table row
214     */
215    public static function tableRowOpen(ODTInternalParams $params, $styleName=NULL, $element=NULL, $attributes=NULL){
216        if ($params->document->state->getInTableRow()) {
217            // If we are still inside a table row then close it first,
218            // to prevent an error or broken document.
219            $params->document->tableRowClose();
220        }
221
222        if (!isset($element)) {
223            $element = 'tr';
224        }
225
226        if (!isset($params->elementObj)) {
227            $properties = array();
228            ODTUtility::openHTMLElement ($params, $properties, $element, $attributes);
229        }
230
231        $row = new ODTElementTableRow($styleName);
232        $params->document->state->enter($row);
233        $params->content .= $row->getOpeningTag();
234        $row->setHTMLElement ($element);
235    }
236
237    /**
238     * Close a table row
239     */
240    public static function tableRowClose(ODTInternalParams $params){
241        if ($params->document->state->getInTableCell()) {
242            // If we are still inside a table cell then close it first,
243            // to prevent an error or broken document.
244            $params->document->tableCellClose();
245        }
246
247        ODTUtility::closeHTMLElement ($params, $params->document->state->getHTMLElement());
248        $params->document->closeCurrentElement();
249    }
250
251    /**
252     * Open a table header cell
253     *
254     * @param int    $colspan
255     * @param int    $rowspan
256     * @param string $align left|center|right
257     */
258    public static function tableHeaderOpen(ODTInternalParams $params, $colspan = 1, $rowspan = 1, $align = "left", $cellStyle=NULL, $paragraphStyle=NULL, $element=NULL, $attributes=NULL){
259        if (!isset($element)) {
260            $element = 'th';
261        }
262        // Are style names given? If not, use defaults.
263        if (empty($cellStyle)) {
264            $cellStyle = $params->document->getStyleName('table header');
265        }
266        if (empty($paragraphStyle)) {
267            $paragraphStyle = $params->document->getStyleName('table heading');
268        }
269
270        if (!isset($params->elementObj)) {
271            $properties = array();
272            ODTUtility::openHTMLElement ($params, $properties, $element, $attributes);
273        }
274
275        // ODT has no element for the table header.
276        // We mark the state with a differnt class to be able
277        // to differ between a normal cell and a header cell.
278        $header_cell = new ODTElementTableHeaderCell
279            ($cellStyle, $colspan, $rowspan);
280        $params->document->state->enter($header_cell);
281        $header_cell->setHTMLElement ($element);
282
283        // Encode table (header) cell.
284        $params->content .= $header_cell->getOpeningTag();
285
286        // Open new paragraph with table heading style.
287        $params->document->paragraphOpen($paragraphStyle);
288    }
289
290    /**
291     * Close a table header cell
292     */
293    public static function tableHeaderClose(ODTInternalParams $params){
294        $params->document->paragraphClose();
295
296        ODTUtility::closeHTMLElement ($params, $params->document->state->getHTMLElement());
297        $params->document->closeCurrentElement();
298    }
299
300    /**
301     * Open a table cell
302     *
303     * @param int    $colspan
304     * @param int    $rowspan
305     * @param string $align left|center|right
306     */
307    public static function tableCellOpen(ODTInternalParams $params, $colspan = 1, $rowspan = 1, $align = "left", $cellStyle=NULL, $paragraphStyle=NULL, $element=NULL, $attributes=NULL){
308        if (!isset($element)) {
309            $element = 'td';
310        }
311
312        if ($params->document->state->getInTableCell()) {
313            // If we are still inside a table cell then close it first,
314            // to prevent an error or broken document.
315            $params->document->tableCellClose();
316        }
317
318        // Are style names given? If not, use defaults.
319        if (empty($cellStyle)) {
320            $cellStyle = $params->document->getStyleName('table cell');
321        }
322        if (empty($paragraphStyle)) {
323            // Open paragraph with required alignment.
324            if (!$align) $align = "left";
325            $paragraphStyle = $params->document->getStyleName('tablealign '.$align);
326        }
327
328        if (!isset($params->elementObj)) {
329            $properties = array();
330            ODTUtility::openHTMLElement ($params, $properties, $element, $attributes);
331        }
332
333        $cell = new ODTElementTableCell
334            ($cellStyle, $colspan, $rowspan);
335        $params->document->state->enter($cell);
336        $cell->setHTMLElement ($element);
337
338        // Encode table cell.
339        $params->content .= $cell->getOpeningTag();
340
341        // Open paragraph.
342        $params->document->paragraphOpen($paragraphStyle);
343    }
344
345    /**
346     * Close a table cell
347     */
348    public static function tableCellClose(ODTInternalParams $params){
349        $params->document->paragraphClose();
350
351        ODTUtility::closeHTMLElement ($params, $params->document->state->getHTMLElement());
352        $params->document->closeCurrentElement();
353    }
354
355    /**
356     * This function opens a new table using the style as set in the imported CSS $import.
357     * So, the function requires the helper class 'helper_plugin_odt_cssimport'.
358     * The CSS style is selected by the element type 'td' and the specified classes in $classes.
359     *
360     * This function calls _odtTableOpenUseProperties. See the function description for supported properties.
361     *
362     * The table should be closed by calling 'table_close()'.
363     *
364     * @author LarsDW223
365     *
366     * @param cssimportnew $import
367     * @param $classes
368     * @param null $baseURL
369     * @param null $element
370     * @param null $maxcols
371     * @param null $numrows
372     */
373    public static function tableOpenUseCSS(ODTInternalParams $params, $maxcols=NULL, $numrows=NULL, $element=NULL, $attributes=NULL){
374        if (!isset($element)) {
375            $element = 'table';
376        }
377
378        $properties = array();
379        ODTUtility::openHTMLElement ($params, $properties, $element, $attributes);
380        $params->elementObj = $params->htmlStack->getCurrentElement();
381
382        self::tableOpenUseProperties($params, $properties, $maxcols, $numrows);
383    }
384
385    /**
386     * This function opens a new table using the style as set in the assoziative array $properties.
387     * The parameters in the array should be named as the CSS property names e.g. 'width'.
388     *
389     * The currently supported properties are:
390     * width, border-collapse, background-color
391     *
392     * The table must be closed by calling 'table_close'.
393     *
394     * @author LarsDW223
395     *
396     * @param array $properties
397     * @param null $maxcols
398     * @param null $numrows
399     */
400    public static function tableOpenUseProperties (ODTInternalParams $params, $properties, $maxcols = 0, $numrows = 0){
401        $elementObj = $params->elementObj;
402
403        // Eventually adjust table width.
404        if ( !empty ($properties ['width']) ) {
405            if ( $properties ['width'] [strlen($properties ['width'])-1] != '%' ) {
406                // Width has got an absolute value.
407                // Some units are not supported by ODT for table width (e.g. 'px').
408                // So we better convert it to points.
409                $properties ['width'] = $params->document->toPoints($properties ['width'], 'x');
410            }
411        }
412
413        // Create style.
414        // FIXME: fix disabled_props, ask state for current max width...
415        $style_obj = ODTTableStyle::createTableTableStyle($properties, NULL, 17);
416        $params->document->addAutomaticStyle($style_obj);
417        $style_name = $style_obj->getProperty('style-name');
418
419        // Open the table referencing our style.
420        $params->elementObj = $elementObj;
421        self::tableOpen($params, $maxcols, $numrows, $style_name);
422    }
423
424    /**
425     * @param array $properties
426     */
427    public static function tableAddColumnUseProperties (ODTInternalParams $params, array $properties = NULL){
428        // Add column and set/query assigned style name
429        $styleName = $properties ['style-name'];
430        $styleNameGet = '';
431        self::tableAddColumn ($params, $styleName, $styleNameGet);
432
433        // Overwrite/Create column style for actual column
434        $properties ['style-name'] = $styleNameGet;
435        $style_obj = ODTTableColumnStyle::createTableColumnStyle ($properties);
436        $params->document->addAutomaticStyle($style_obj);
437    }
438
439    /**
440     * @param helper_plugin_odt_cssimport $import
441     * @param $classes
442     * @param null $baseURL
443     * @param null $element
444     * @param int $colspan
445     * @param int $rowspan
446     */
447    public static function tableHeaderOpenUseCSS(ODTInternalParams $params, $colspan = 1, $rowspan = 1, $element=NULL, $attributes=NULL){
448        if (!isset($element)) {
449            $element = 'th';
450        }
451
452        $properties = array();
453        ODTUtility::openHTMLElement ($params, $properties, $element, $attributes);
454        $params->elementObj = $params->htmlStack->getCurrentElement();
455
456        self::tableHeaderOpenUseProperties($params, $properties, $colspan, $rowspan);
457    }
458
459    /**
460     * @param null $properties
461     * @param int $colspan
462     * @param int $rowspan
463     */
464    public static function tableHeaderOpenUseProperties (ODTInternalParams $params, $properties = NULL, $colspan = 1, $rowspan = 1){
465        // Open cell, second parameter MUST BE true to indicate we are in the header.
466        self::tableCellOpenUsePropertiesInternal ($params, $properties, true, $colspan, $rowspan);
467    }
468
469    /**
470     * This function opens a new table row using the style as set in the imported CSS $import.
471     * So, the function requires the helper class 'helper_plugin_odt_cssimport'.
472     * The CSS style is selected by the element type 'td' and the specified classes in $classes.
473     *
474     * This function calls _odtTableRowOpenUseProperties. See the function description for supported properties.
475     *
476     * The row should be closed by calling 'tablerow_close()'.
477     *
478     * @author LarsDW223
479     * @param helper_plugin_odt_cssimport $import
480     * @param $classes
481     * @param null $baseURL
482     * @param null $element
483     */
484    public static function tableRowOpenUseCSS(ODTInternalParams $params, $element=NULL, $attributes=NULL){
485        if (!isset($element)) {
486            $element = 'tr';
487        }
488
489        $properties = array();
490        ODTUtility::openHTMLElement ($params, $properties, $element, $attributes);
491        $params->elementObj = $params->htmlStack->getCurrentElement();
492
493        self::tableRowOpenUseProperties($params, $properties);
494    }
495
496    /**
497     * @param array $properties
498     */
499    public static function tableRowOpenUseProperties (ODTInternalParams $params, $properties){
500        // Create style.
501        $style_obj = ODTTableRowStyle::createTableRowStyle ($properties);
502        $params->document->addAutomaticStyle($style_obj);
503        $style_name = $style_obj->getProperty('style-name');
504
505        // Open table row with our new style.
506        self::tableRowOpen($params, $style_name);
507    }
508
509    /**
510     * This function opens a new table cell using the style as set in the imported CSS $import.
511     * So, the function requires the helper class 'helper_plugin_odt_cssimport'.
512     * The CSS style is selected by the element type 'td' and the specified classes in $classes.
513     *
514     * This function calls _odtTableCellOpenUseProperties. See the function description for supported properties.
515     *
516     * The cell should be closed by calling 'tablecell_close()'.
517     *
518     * @author LarsDW223
519     *
520     * @param helper_plugin_odt_cssimport $import
521     * @param $classes
522     * @param null $baseURL
523     * @param null $element
524     */
525    public static function tableCellOpenUseCSS(ODTInternalParams $params, $element=NULL, $attributes=NULL, $colspan = 1, $rowspan = 1){
526        if (!isset($element)) {
527            $element = 'td';
528        }
529
530        $properties = array();
531        ODTUtility::openHTMLElement ($params, $properties, $element, $attributes);
532        $params->elementObj = $params->htmlStack->getCurrentElement();
533
534        self::tableCellOpenUseProperties($params, $properties, $colspan, $rowspan);
535    }
536
537    /**
538     * @param $properties
539     */
540    public static function tableCellOpenUseProperties (ODTInternalParams $params, $properties = NULL, $colspan = 1, $rowspan = 1){
541        self::tableCellOpenUsePropertiesInternal ($params, $properties, false, $colspan, $rowspan);
542    }
543
544    /**
545     * @param $properties
546     * @param bool $inHeader
547     * @param int $colspan
548     * @param int $rowspan
549     */
550    static protected function tableCellOpenUsePropertiesInternal (ODTInternalParams $params, $properties, $inHeader = false, $colspan = 1, $rowspan = 1){
551        $disabled = array ();
552
553        // Create style name. (Re-enable background-color!)
554        $style_obj = ODTTableCellStyle::createTableCellStyle ($properties);
555        $params->document->addAutomaticStyle($style_obj);
556        $style_name = $style_obj->getProperty('style-name');
557
558        // Create a paragraph style for the paragraph within the cell.
559        // Disable properties that belong to the table cell style.
560        $disabled ['border'] = 1;
561        $disabled ['border-left'] = 1;
562        $disabled ['border-right'] = 1;
563        $disabled ['border-top'] = 1;
564        $disabled ['border-bottom'] = 1;
565        $disabled ['background-color'] = 1;
566        $disabled ['background-image'] = 1;
567        $disabled ['vertical-align'] = 1;
568        $style_obj = ODTParagraphStyle::createParagraphStyle ($properties, $disabled);
569        $params->document->addAutomaticStyle($style_obj);
570        $style_name_paragraph = $style_obj->getProperty('style-name');
571
572        // Open header or normal cell.
573        if ($inHeader) {
574            self::tableHeaderOpen($params, $colspan, $rowspan, NULL, $style_name, $style_name_paragraph);
575        } else {
576            self::tableCellOpen($params, $colspan, $rowspan, NULL, $style_name, $style_name_paragraph);
577        }
578
579        // There might be properties in the table header cell/normal cell which in ODT belong to the
580        // column, e.g. 'width'. So eventually adjust column style.
581        self::adjustColumnStyle($params, $properties);
582    }
583
584    static protected function adjustColumnStyle(ODTInternalParams $params, array $properties) {
585        $table = $params->document->state->getCurrentTable();
586        if (!isset($table)) {
587            // ??? Error. Not table found.
588            return;
589        }
590        $curr_column = $table->getTableCurrentColumn();
591        $table_column_styles = $table->getTableColumnStyles();
592        $style_name = $table_column_styles [$curr_column-1];
593        $style_obj = $params->document->getStyle($style_name);
594
595        if (isset($style_obj)) {
596            if (!empty($properties ['width'])) {
597                $width = $properties ['width'];
598                $length = strlen ($width);
599                $width = $params->document->toPoints($width, 'x');
600                $style_obj->setProperty('column-width', $width);
601            }
602        } else {
603            self::tableAddColumnUseProperties ($params, $properties);
604        }
605    }
606}
607