1<?php
2
3require_once DOKU_PLUGIN . 'odt/ODT/ODTDocument.php';
4
5/**
6 * ODTImport:
7 * Class containing static code for importing ODT or CSS code.
8 *
9 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
10 */
11class ODTImport
12{
13    static public $trace_dump = NULL;
14    static protected $internalRegs = array('heading1' => array('element' => 'h1', 'attributes' => NULL),
15                                    'heading2' => array('element' => 'h2', 'attributes' => NULL),
16                                    'heading3' => array('element' => 'h3', 'attributes' => NULL),
17                                    'heading4' => array('element' => 'h4', 'attributes' => NULL),
18                                    'heading5' => array('element' => 'h5', 'attributes' => NULL),
19                                    'horizontal line' => array('element' => 'hr', 'attributes' => NULL),
20                                    'body' => array('element' => 'p', 'attributes' => NULL),
21                                    'emphasis' => array('element' => 'em', 'attributes' => NULL, 'compare' => true),
22                                    'strong' => array('element' => 'strong', 'attributes' => NULL, 'compare' => true),
23                                    'underline' => array('element' => 'u', 'attributes' => NULL, 'compare' => true),
24                                    'monospace' => array('element' => 'code', 'attributes' => NULL),
25                                    'del' => array('element' => 'del', 'attributes' => NULL, 'compare' => true),
26                                    'preformatted' => array('element' => 'pre', 'attributes' => NULL),
27                                    'source code' => array('element' => 'pre', 'attributes' => 'class="code"'),
28                                    'source file' => array('element' => 'pre', 'attributes' => 'class="file"'),
29                                   );
30    static protected $table_styles = array('table' => array('element' => 'table', 'attributes' => NULL),
31                                    'table header' => array('element' => 'th', 'attributes' => NULL),
32                                    'table cell' => array('element' => 'td', 'attributes' => NULL)
33                                   );
34    static protected $link_styles = array(
35                                   'internet link' => array('element' => 'a',
36                                                            'attributes' => NULL,
37                                                            'pseudo-class' => 'link'),
38                                   'visited internet link' => array('element' => 'a',
39                                                                    'attributes' => NULL,
40                                                                    'pseudo-class' => 'visited'),
41                                   'local link' => array('element' => 'a',
42                                                         'attributes' => 'class="wikilink1"',
43                                                         'pseudo-class' => 'link'),
44                                   'visited local link' => array('element' => 'a',
45                                                                 'attributes' => 'class="wikilink1"',
46                                                                 'pseudo-class' => 'visited'),
47                                  );
48
49    /**
50     * Import CSS code.
51     * This is the CSS code import for the new API.
52     * That means in this function the CSS code is only parsed and stored
53     * but not immediately imported as styles like in the old API.
54     *
55     * The function can be called multiple times.
56     * All CSS code is handled like being appended.
57     *
58     * @param string $cssCode The CSS code to be imported
59     */
60    static protected function importCSSCodeInternal (ODTInternalParams $params, $isFile, $CSSSource, $mediaSel=NULL, $lengthCallback=NULL, $URLCallback=NULL) {
61        if ($params->import == NULL) {
62            // No CSS imported yet. Create object.
63            $params->import = new cssimportnew();
64            if ( $params->import == NULL ) {
65                return;
66            }
67            $params->import->setMedia ($mediaSel);
68        }
69
70        if ($isFile == false) {
71            $params->import->importFromString($CSSSource);
72        } else {
73            $params->import->importFromFile($CSSSource);
74        }
75
76        // Call adjustLengthValues to make our callback function being called for every
77        // length value imported. This gives us the chance to convert it once from
78        // pixel to points.
79        if ($lengthCallback != NULL) {
80            $params->import->adjustLengthValues ($lengthCallback);
81        }
82
83        // Call replaceURLPrefixes to make the callers (renderer/page.php) callback
84        // function being called for every URL to convert it to an absolute path.
85        if ($URLCallback != NULL) {
86            $params->import->replaceURLPrefixes ($URLCallback);
87        }
88    }
89
90    /**
91     * Import CSS code from a file.
92     *
93     * @param ODTInternalParams $params Common params
94     * @param string $CSSTemplate String containing the path and file name of the CSS file to import
95     * @param string $media_sel String containing the media selector to use for import (e.g. 'print' or 'screen')
96     * @param callable $callback Callback for adjusting length values
97     */
98    static public function importCSSFromFile (ODTInternalParams $params, $CSSTemplate, $media_sel=NULL, $lengthCallback=NULL, $URLCallback=NULL, $registrations=NULL, $importStyles=true, $listAlign='right') {
99        self::importCSSCodeInternal ($params, true, $CSSTemplate, $media_sel, $lengthCallback, $URLCallback);
100        if ($importStyles) {
101            self::import_styles_from_css ($params, $media_sel, $registrations, $listAlign);
102        }
103    }
104
105    /**
106     * Import CSS code for styles from a string.
107     *
108     * @param string $cssCode The CSS code to import
109     * @param string $mediaSel The media selector to use e.g. 'print'
110     * @param string $mediaPath Local path to media files
111     */
112    static public function importCSSFromString(ODTInternalParams $params, $cssCode, $media_sel=NULL, $lengthCallback=NULL, $URLCallback=NULL, $registrations=NULL, $importStyles=true, $listAlign='right')
113    {
114        self::importCSSCodeInternal ($params, false, $cssCode, $media_sel, $lengthCallback, $URLCallback);
115        if ($importStyles) {
116            self::import_styles_from_css ($params, $media_sel, $registrations, $listAlign);
117        }
118    }
119
120    static protected function importQuotationStyles(ODTInternalParams $params, cssdocument $htmlStack) {
121        // Reset stack to saved root so next importStyle
122        // will have the same conditions
123        $htmlStack->restoreToRoot ();
124
125        $disabled = array();
126        $disabled ['margin']         = 1;
127        $disabled ['margin-left']    = 1;
128        $disabled ['margin-right']   = 1;
129        $disabled ['margin-top']     = 1;
130        $disabled ['margin-bottom']  = 1;
131        $disabled ['padding']        = 1;
132        $disabled ['padding-left']   = 1;
133        $disabled ['padding-right']  = 1;
134        $disabled ['padding-top']    = 1;
135        $disabled ['padding-bottom'] = 1;
136
137        for ($level = 1 ; $level < 6 ; $level++) {
138            // Push our element to import on the stack
139            $htmlStack->open('blockquote');
140            $toMatch = $htmlStack->getCurrentElement();
141
142            $properties = array();
143            $params->import->getPropertiesForElement($properties, $toMatch, $params->units);
144            if (count($properties) == 0) {
145                // Nothing found. Go to next, DO NOT change existing style!
146                continue;
147            }
148
149            // Adjust values for ODT
150            ODTUtility::adjustValuesForODT ($properties, $params->units);
151
152            $name = $params->styleset->getStyleName('table quotation'.$level);
153            $style = $params->styleset->getStyle($name);
154            if ($style != NULL ) {
155                if ($level == 1) {
156                    $style->importProperties($properties);
157                } else {
158                    $style->importProperties($properties, $disabled);
159                }
160            }
161
162            $name = $params->styleset->getStyleName('cell quotation'.$level);
163            $style = $params->styleset->getStyle($name);
164            if ($style != NULL ) {
165                $style->importProperties($properties);
166            }
167        }
168
169        // Reset stack to saved root so next importStyle
170        // will have the same conditions
171        $htmlStack->restoreToRoot ();
172    }
173
174    static protected function setListStyleImage (ODTInternalParams $params, $style, $level, $file) {
175        $odt_file = $params->document->addFileAsPicture($file);
176
177        if ( $odt_file != NULL ) {
178            $style->setPropertyForLevel($level, 'list-level-style', 'image');
179            $style->setPropertyForLevel($level, 'href', $odt_file);
180            $style->setPropertyForLevel($level, 'type', 'simple');
181            $style->setPropertyForLevel($level, 'show', 'embed');
182            $style->setPropertyForLevel($level, 'actuate', 'onLoad');
183            $style->setPropertyForLevel($level, 'vertical-pos', 'middle');
184            $style->setPropertyForLevel($level, 'vertical-rel', 'line');
185
186            list($width, $height) = ODTUtility::getImageSize($file);
187            if (empty($width) || empty($height)) {
188                $width = '0.5';
189                $height = $width;
190            }
191            $style->setPropertyForLevel($level, 'width', $width.'cm');
192            $style->setPropertyForLevel($level, 'height', $height.'cm');
193
194            // ??? Wie berechnen...
195            $text_indent = ODTUnits::getDigits($style->getPropertyFromLevel($level, 'text-indent'));
196            $margin_left = ODTUnits::getDigits($style->getPropertyFromLevel($level, 'margin_left'));
197            $tab_stop_position =
198                ODTUnits::getDigits($style->getPropertyFromLevel($level, 'list-tab-stop-position'));
199            $minimum = $margin_left + $text_indent + $width;
200            if ($minimum > $tab_stop_position) {
201                $inc = abs($text_indent);
202                if ($inc == 0 ) {
203                    $inc = 0.5;
204                }
205                while ($minimum > $tab_stop_position) {
206                    $tab_stop_position += $inc;
207                }
208            }
209            $style->setPropertyForLevel($level, 'list-tab-stop-position', $tab_stop_position.'cm');
210        }
211    }
212
213    static protected function importOrderedListStyles(ODTInternalParams $params, cssdocument $htmlStack, $listAlign='right') {
214        $name = $params->styleset->getStyleName('numbering');
215        $style = $params->styleset->getStyle($name);
216        if ($style == NULL ) {
217            return;
218        }
219
220        // Workaround for ODT format, see end of loop
221        $name = $params->styleset->getStyleName('numbering first');
222        $firstStyle = $params->styleset->getStyle($name);
223        $name = $params->styleset->getStyleName('numbering last');
224        $lastStyle = $params->styleset->getStyle($name);
225
226        // Reset stack to saved root so next importStyle
227        // will have the same conditions
228        $htmlStack->restoreToRoot ();
229
230        for ($level = 1 ; $level < 11 ; $level++) {
231            // Push our element to import on the stack
232            $htmlStack->open('ol');
233            $toMatch = $htmlStack->getCurrentElement();
234
235            $properties = array();
236            $params->import->getPropertiesForElement($properties, $toMatch, $params->units);
237            if (count($properties) == 0) {
238                // Nothing found. Return, DO NOT change existing style!
239                return;
240            }
241
242            // Push list item element to import on the stack
243            // (Required to get left margin)
244            $htmlStack->open('li');
245            $toMatch = $htmlStack->getCurrentElement();
246
247            $li_properties = array();
248            $params->import->getPropertiesForElement($li_properties, $toMatch, $params->units);
249
250            // Adjust values for ODT
251            ODTUtility::adjustValuesForODT ($properties, $params->units);
252
253            if ($properties ['list-style-type'] !== NULL) {
254                $prefix = NULL;
255                $suffix = '.';
256                $numbering = trim($properties ['list-style-type'],'"');
257                switch ($numbering) {
258                    case 'decimal':
259                        $numbering = '1';
260                        break;
261                    case 'decimal-leading-zero':
262                        $numbering = '1';
263                        $prefix = '0';
264                        break;
265                    case 'lower-alpha':
266                    case 'lower-latin':
267                        $numbering = 'a';
268                        break;
269                    case 'lower-roman':
270                        $numbering = 'i';
271                        break;
272                    case 'none':
273                        $numbering = '';
274                        $suffix = '';
275                        break;
276                    case 'upper-alpha':
277                    case 'upper-latin':
278                        $numbering = 'A';
279                        break;
280                    case 'upper-roman':
281                        $numbering = 'I';
282                        break;
283                }
284                $style->setPropertyForLevel($level, 'num-format', $numbering);
285                if ($prefix !== NULL ) {
286                    $style->setPropertyForLevel($level, 'num-prefix', $prefix);
287                }
288                $style->setPropertyForLevel($level, 'num-suffix', $suffix);
289
290                // Padding is not inherited so we will only get it for the list root!
291                if ($level == 1 ) {
292                    $paddingLeft = 0;
293                    if ($properties ['padding-left'] !== NULL) {
294                        $paddingLeft = $params->units->toCentimeters($properties ['padding-left'], 'y');
295                        $paddingLeft = substr($paddingLeft, 0, -2);
296                    }
297                }
298                $marginLeft = 1;
299                if ($li_properties ['margin-left'] !== NULL) {
300                    $marginLeft = $params->units->toCentimeters($li_properties ['margin-left'], 'y');
301                    $marginLeft = substr($marginLeft, 0, -2);
302                }
303                // Set list params.
304                $params->document->setOrderedListParams($level, $listAlign, $paddingLeft, $marginLeft);
305            }
306            if ($properties ['list-style-image'] !== NULL && $properties ['list-style-image'] != 'none') {
307                // It is assumed that the CSS already contains absolute path values only!
308                // (see replaceURLPrefixes)
309                $file = $properties ['list-style-image'];
310
311                $this->setListStyleImage ($params, $style, $level, $file);
312            }
313
314            // Workaround for ODT format:
315            // We can not set margins on the list itself.
316            // So we use extra paragraph styles for the first and last
317            // list items to set a margin.
318            if ($level == 1 &&
319                ($properties ['margin-top'] != NULL ||
320                 $properties ['margin-bottom'] != NULL)) {
321                $set = array ();
322                $disabled = array ();
323                // Delete left and right margins as setting them
324                // would destroy list item indentation
325                $set ['margin-left'] = NULL;
326                $set ['margin-right'] = NULL;
327                $set ['margin-top'] = $properties ['margin-top'];
328                $set ['margin-bottom'] = '0pt';
329                $firstStyle->importProperties($set, $disabled);
330                $set ['margin-bottom'] = $properties ['margin-bottom'];
331                $set ['margin-top'] = '0pt';
332                $lastStyle->importProperties($set, $disabled);
333            }
334
335            // Import properties for list paragraph style once.
336            // Margins MUST be ignored! See extra handling above.
337            if ($level == 1) {
338                $disabled = array();
339                $disabled ['margin-left'] = 1;
340                $disabled ['margin-right'] = 1;
341                $disabled ['margin-top'] = 1;
342                $disabled ['margin-bottom'] = 1;
343
344                $name = $params->styleset->getStyleName('numbering content');
345                $paragraphStyle = $params->styleset->getStyle($name);
346                $paragraphStyle->importProperties($properties, $disabled);
347            }
348        }
349
350        // Reset stack to saved root so next importStyle
351        // will have the same conditions
352        $htmlStack->restoreToRoot ();
353    }
354
355    static protected function importUnorderedListStyles(ODTInternalParams $params, cssdocument $htmlStack, $listAlign='right') {
356        $name = $params->styleset->getStyleName('list');
357        $style = $params->styleset->getStyle($name);
358        if ($style == NULL ) {
359            return;
360        }
361
362        // Workaround for ODT format, see end of loop
363        $name = $params->styleset->getStyleName('list first');
364        $firstStyle = $params->styleset->getStyle($name);
365        $name = $params->styleset->getStyleName('list last');
366        $lastStyle = $params->styleset->getStyle($name);
367
368        // Reset stack to saved root so next importStyle
369        // will have the same conditions
370        $htmlStack->restoreToRoot ();
371
372        for ($level = 1 ; $level < 11 ; $level++) {
373            // Push our element to import on the stack
374            $htmlStack->open('ul');
375            $toMatch = $htmlStack->getCurrentElement();
376
377            $properties = array();
378            $params->import->getPropertiesForElement($properties, $toMatch, $params->units);
379            if (count($properties) == 0) {
380                // Nothing found. Return, DO NOT change existing style!
381                return;
382            }
383
384            // Push list item element to import on the stack
385            // (Required to get left margin)
386            $htmlStack->open('li');
387            $toMatch = $htmlStack->getCurrentElement();
388
389            $li_properties = array();
390            $params->import->getPropertiesForElement($li_properties, $toMatch, $params->units);
391
392            // Adjust values for ODT
393            ODTUtility::adjustValuesForODT ($properties, $params->units);
394
395            if ($properties ['list-style-type'] !== NULL) {
396                switch ($properties ['list-style-type']) {
397                    case 'disc':
398                    case 'bullet':
399                        $sign = '•';
400                        break;
401                    case 'circle':
402                        $sign = '∘';
403                        break;
404                    case 'square':
405                        $sign = '▪';
406                        break;
407                    case 'none':
408                        $sign = ' ';
409                        break;
410                    case 'blackcircle':
411                        $sign = '●';
412                        break;
413                    case 'heavycheckmark':
414                        $sign = '✔';
415                        break;
416                    case 'ballotx':
417                        $sign = '✗';
418                        break;
419                    case 'heavyrightarrow':
420                        $sign = '➔';
421                        break;
422                    case 'lightedrightarrow':
423                        $sign = '➢';
424                        break;
425                    default:
426                        $sign = trim($properties ['list-style-type'],'"');
427                        break;
428                }
429                $style->setPropertyForLevel($level, 'text-bullet-char', $sign);
430
431                // Padding is not inherited so we will only get it for the list root!
432                if ($level == 1 ) {
433                    $paddingLeft = 0;
434                    if ($properties ['padding-left'] !== NULL) {
435                        $paddingLeft = $params->units->toCentimeters($properties ['padding-left'], 'y');
436                        $paddingLeft = substr($paddingLeft, 0, -2);
437                    }
438                }
439                $marginLeft = 1;
440                if ($li_properties ['margin-left'] !== NULL) {
441                    $marginLeft = $params->units->toCentimeters($li_properties ['margin-left'], 'y');
442                    $marginLeft = substr($marginLeft, 0, -2);
443                }
444                // Set list params.
445                $params->document->setUnorderedListParams($level, $listAlign, $paddingLeft, $marginLeft);
446            }
447            if ($properties ['list-style-image'] !== NULL && $properties ['list-style-image'] != 'none') {
448                // It is assumed that the CSS already contains absolute path values only!
449                // (see replaceURLPrefixes)
450                $file = $properties ['list-style-image'];
451                /*$file = substr($file, 4);
452                $file = trim($file, "()'");
453                if ($media_path [strlen($media_path)-1] != '/') {
454                    $media_path .= '/';
455                }
456                $file = $media_path.$file;*/
457
458                $this->setListStyleImage ($params, $style, $level, $file);
459            }
460
461            // Workaround for ODT format:
462            // We can not set margins on the list itself.
463            // So we use extra paragraph styles for the first and last
464            // list items to set a margin.
465            if ($level == 1 &&
466                ($properties ['margin-top'] != NULL ||
467                 $properties ['margin-bottom'] != NULL)) {
468                $set = array ();
469                $disabled = array ();
470                // Delete left and right margins as setting them
471                // would destroy list item indentation
472                $set ['margin-left'] = NULL;
473                $set ['margin-right'] = NULL;
474                $set ['margin-top'] = $properties ['margin-top'];
475                $set ['margin-bottom'] = '0pt';
476                $firstStyle->importProperties($set, $disabled);
477                $set ['margin-bottom'] = $properties ['margin-bottom'];
478                $set ['margin-top'] = '0pt';
479                $lastStyle->importProperties($set, $disabled);
480            }
481
482            // Import properties for list paragraph style once.
483            // Margins MUST be ignored! See extra handling above.
484            if ($level == 1) {
485                $disabled = array();
486                $disabled ['margin-left'] = 1;
487                $disabled ['margin-right'] = 1;
488                $disabled ['margin-top'] = 1;
489                $disabled ['margin-bottom'] = 1;
490
491                $name = $params->styleset->getStyleName('list content');
492                $paragraphStyle = $params->styleset->getStyle($name);
493                $paragraphStyle->importProperties($properties, $disabled);
494            }
495        }
496
497        // Reset stack to saved root so next importStyle
498        // will have the same conditions
499        $htmlStack->restoreToRoot ();
500    }
501
502
503    static protected function importTableStyles(ODTInternalParams $params, cssdocument $htmlStack) {
504        foreach (self::$table_styles as $style_type => $elementParams) {
505            $name = $params->styleset->getStyleName($style_type);
506            $style = $params->styleset->getStyle($name);
507            if ( $style != NULL ) {
508                $element = $elementParams ['element'];
509                $attributes = $elementParams ['attributes'];
510
511                // Push our element to import on the stack
512                $htmlStack->open($element, $attributes);
513                $toMatch = $htmlStack->getCurrentElement();
514
515                $properties = array();
516                $params->import->getPropertiesForElement($properties, $toMatch, $params->units);
517                if (count($properties) == 0) {
518                    // Nothing found. Back to top, DO NOT change existing style!
519                    continue;
520                }
521
522                // We have found something.
523                // First clear the existing layout properties of the style.
524                $style->clearLayoutProperties();
525
526                // Adjust values for ODT
527                ODTUtility::adjustValuesForODT ($properties, $params->units);
528
529                // If the style imported is a table adjust some properties
530                if ($style->getFamily() == 'table') {
531                    // Move 'width' to 'rel-width' if it is relative
532                    $width = $properties ['width'];
533                    if ($width != NULL) {
534                        if ($properties ['align'] == NULL) {
535                            // If width is set but align not, changing the width
536                            // will not work. So we set it here if not done by the user.
537                            $properties ['align'] = 'center';
538                        }
539                    }
540                    if ($width [strlen($width)-1] == '%') {
541                        $properties ['rel-width'] = $width;
542                        unset ($properties ['width']);
543                    }
544
545                    // Convert property 'border-model' to ODT
546                    if ( !empty ($properties ['border-collapse']) ) {
547                        $properties ['border-model'] = $properties ['border-collapse'];
548                        unset ($properties ['border-collapse']);
549                        if ( $properties ['border-model'] == 'collapse' ) {
550                            $properties ['border-model'] = 'collapsing';
551                        } else {
552                            $properties ['border-model'] = 'separating';
553                        }
554                    }
555                }
556
557                // Inherit properties for table header paragraph style from
558                // the properties of the 'th' element
559                if ($element == 'th') {
560                    $name = $params->styleset->getStyleName('table heading');
561                    $paragraphStyle = $params->styleset->getStyle($name);
562
563                    // Do not set borders on our paragraph styles in the table.
564                    // Otherwise we will have double borders. Around the cell and
565                    // around the text in the cell!
566                    $disabled = array();
567                    $disabled ['border']        = 1;
568                    $disabled ['border-top']    = 1;
569                    $disabled ['border-right']  = 1;
570                    $disabled ['border-bottom'] = 1;
571                    $disabled ['border-left']   = 1;
572                    // Do not set background/background-color
573                    $disabled ['background-color'] = 1;
574
575                    $paragraphStyle->clearLayoutProperties();
576                    $paragraphStyle->importProperties($properties, $disabled);
577                }
578                // Inherit properties for table content paragraph style from
579                // the properties of the 'td' element
580                if ($element == 'td') {
581                    $name = $params->styleset->getStyleName('table content');
582                    $paragraphStyle = $params->styleset->getStyle($name);
583
584                    // Do not set borders on our paragraph styles in the table.
585                    // Otherwise we will have double borders. Around the cell and
586                    // around the text in the cell!
587                    $disabled = array();
588                    $disabled ['border']        = 1;
589                    $disabled ['border-top']    = 1;
590                    $disabled ['border-right']  = 1;
591                    $disabled ['border-bottom'] = 1;
592                    $disabled ['border-left']   = 1;
593                    // Do not set background/background-color
594                    $disabled ['background-color'] = 1;
595
596                    $paragraphStyle->clearLayoutProperties();
597                    $paragraphStyle->importProperties($properties, $disabled);
598                }
599                $disabled = array();
600                $style->importProperties($properties, $disabled);
601
602                // Reset stack to saved root so next importStyle
603                // will have the same conditions
604                $htmlStack->restoreToRoot ();
605            }
606        }
607    }
608
609    static protected function importLinkStyles(ODTInternalParams $params, cssdocument $htmlStack) {
610        foreach (self::$link_styles as $style_type => $elementParams) {
611            $name = $params->styleset->getStyleName($style_type);
612            $style = $params->styleset->getStyle($name);
613            if ( $name != NULL && $style != NULL ) {
614                $element = $elementParams ['element'];
615                $attributes = $elementParams ['attributes'];
616                $pseudo_class = $elementParams ['pseudo-class'];
617
618                // Push our element to import on the stack
619                $htmlStack->open($element, $attributes, $pseudo_class, NULL);
620                $toMatch = $htmlStack->getCurrentElement();
621
622                $properties = array();
623                $params->import->getPropertiesForElement($properties, $toMatch, $params->units);
624                if (count($properties) == 0) {
625                    // Nothing found. Back to top, DO NOT change existing style!
626                    continue;
627                }
628
629                // We have found something.
630                // First clear the existing layout properties of the style.
631                $style->clearLayoutProperties();
632
633                // Adjust values for ODT
634                ODTUtility::adjustValuesForODT ($properties, $params->units);
635
636                $disabled = array();
637                $style->importProperties($properties, $disabled);
638
639                // Reset stack to saved root so next importStyle
640                // will have the same conditions
641                $htmlStack->restoreToRoot ();
642            }
643        }
644    }
645
646    static protected function importStyle(ODTInternalParams $params, cssdocument $htmlStack, $style_type, $element, $attributes=NULL, array $plain=NULL) {
647        $name = $params->styleset->getStyleName($style_type);
648        $style = $params->styleset->getStyle($name);
649        if ( $style != NULL ) {
650            // Push our element to import on the stack
651            $htmlStack->open($element, $attributes);
652            $toMatch = $htmlStack->getCurrentElement();
653
654            $properties = array();
655            $params->import->getPropertiesForElement($properties, $toMatch, $params->units);
656            if (count($properties) == 0) {
657                // Nothing found. Return, DO NOT change existing style!
658                return;
659            }
660            if ($plain != NULL)
661            {
662                $diff = array_diff ($properties, $plain);
663                if (count($diff) == 0) {
664                    // Workaround for some elements, e.g. 'em' and 'del':
665                    // They may have default values from the browser only.
666                    // In that case do not import the style otherwise
667                    // 'em' and 'del' will look like plain text.
668
669                    // Reset stack to saved root so next importStyle
670                    // will have the same conditions
671                    $htmlStack->restoreToRoot ();
672                    return;
673                }
674            }
675
676            // We have found something.
677            // First clear the existing layout properties of the style.
678            $style->clearLayoutProperties();
679
680            // Adjust values for ODT
681            ODTUtility::adjustValuesForODT ($properties, $params->units);
682
683            // In all paragraph styles set the ODT specific attribute join-border = false
684            if ($style->getFamily() == 'paragraph') {
685                $properties ['join-border'] = 'false';
686            }
687
688            $disabled = array();
689            if ($style_type == 'horizontal line') {
690                // Do not use margin and padding on horizontal line paragraph style!
691                $disabled ['margin'] = 1;
692                $disabled ['margin-top'] = 1;
693                $disabled ['margin-right'] = 1;
694                $disabled ['margin-bottom'] = 1;
695                $disabled ['margin-left'] = 1;
696                $disabled ['padding'] = 1;
697                $disabled ['padding-top'] = 1;
698                $disabled ['padding-right'] = 1;
699                $disabled ['padding-bottom'] = 1;
700                $disabled ['padding-left'] = 1;
701            }
702            $style->importProperties($properties, $disabled);
703
704            // Reset stack to saved root so next importStyle
705            // will have the same conditions
706            $htmlStack->restoreToRoot ();
707        }
708    }
709
710    static public function import_styles_from_css (ODTInternalParams $params, $media_sel=NULL, $registrations=NULL, $listAlign='right') {
711        if ( $params->import != NULL ) {
712            $save = $params->import->getMedia ();
713            $params->import->setMedia ($media_sel);
714
715            // Make a copy of the stack to be sure we do not leave anything behind after import.
716            $stack = clone $params->htmlStack;
717            $stack->restoreToRoot ();
718
719            self::import_styles_from_css_internal ($params, $stack, $registrations, $listAlign);
720
721            $params->import->setMedia ($save);
722        }
723    }
724
725    static public function set_page_properties (ODTInternalParams $params, ODTPageLayoutStyle $pageStyle, $media_sel=NULL) {
726        if ( $params->import != NULL ) {
727            if ($media_sel != NULL ) {
728                $save = $params->import->getMedia ();
729                $params->import->setMedia ($media_sel);
730            }
731
732            $stack = clone $params->htmlStack;
733            $stack->restoreToRoot ();
734
735            // Set background-color of page
736            // It is assumed that the last element of the "root" elements hold the backround-color.
737            // For DokuWiki this is <div class="page group">, see renderer/page.php, function 'load_css()'
738            $stack->restoreToRoot ();
739            $properties = array();
740            $params->import->getPropertiesForElement($properties, $stack->getCurrentElement(), $params->units);
741            ODTUtility::adjustValuesForODT ($properties, $params->units);
742            if (!empty($properties ['background-color'])) {
743                if ($pageStyle != NULL) {
744                    $pageStyle->setProperty('background-color', $properties ['background-color']);
745                }
746            }
747
748            if ($media_sel != NULL ) {
749                $params->import->setMedia ($save);
750            }
751        }
752    }
753
754    static protected function importParagraphDefaultStyle(ODTInternalParams $params) {
755        // This function MUST be called at the end of import_styles_from_css_internal
756        // ==> the 'body' paragraph style must have alread been imported!
757
758        // Get standard text style ('body')
759        $styleName = $params->styleset->getStyleName('body');
760        $body = $params->styleset->getStyle($styleName);
761
762        // Copy body paragraph properties to the paragraph default styles
763        // But not margins and paddings:
764        // That would also influence the margin and paddings in the
765        // Table of Contents or in lists
766        $disabled = array();
767        $disabled ['margin'] = 1;
768        $disabled ['margin-top'] = 1;
769        $disabled ['margin-right'] = 1;
770        $disabled ['margin-bottom'] = 1;
771        $disabled ['margin-left'] = 1;
772        $disabled ['padding'] = 1;
773        $disabled ['padding-top'] = 1;
774        $disabled ['padding-right'] = 1;
775        $disabled ['padding-bottom'] = 1;
776        $disabled ['padding-left'] = 1;
777
778        $default = $params->styleset->getDefaultStyle ('paragraph');
779        if ($default != NULL && $body != NULL) {
780            ODTParagraphStyle::copyLayoutProperties ($body, $default, $disabled);
781        }
782    }
783
784    static protected function importFootnoteStyle(ODTInternalParams $params) {
785        // This function MUST be called at the end of import_styles_from_css_internal
786        // ==> the 'body' paragraph style must have alread been imported!
787
788        // Get standard text style ('body')
789        $styleName = $params->styleset->getStyleName('body');
790        $body = $params->styleset->getStyle($styleName);
791
792        // Copy body paragraph properties to the footnote style
793        // But not margins and paddings.
794        $disabled = array();
795        $disabled ['margin'] = 1;
796        $disabled ['margin-top'] = 1;
797        $disabled ['margin-right'] = 1;
798        $disabled ['margin-bottom'] = 1;
799        $disabled ['margin-left'] = 1;
800        $disabled ['padding'] = 1;
801        $disabled ['padding-top'] = 1;
802        $disabled ['padding-right'] = 1;
803        $disabled ['padding-bottom'] = 1;
804        $disabled ['padding-left'] = 1;
805
806        $styleName = $params->styleset->getStyleName (footnote);
807        $footnote = $params->styleset->getStyle($styleName);
808        if ($footnote != NULL && $body != NULL) {
809            ODTParagraphStyle::copyLayoutProperties ($body, $footnote, $disabled);
810        }
811    }
812
813    static protected function import_styles_from_css_internal(ODTInternalParams $params, $htmlStack, $registrations=NULL, $listAlign='right') {
814        // Import page layout
815        $name = $params->styleset->getStyleName('first page');
816        $first_page = $params->styleset->getStyle($name);
817        if ($first_page != NULL) {
818            self::set_page_properties ($params, $first_page, $htmlStack, NULL);
819        }
820
821        // Import styles which only require a simple import based on element name and attributes
822
823        // Get style of plain text paragraph for comparison
824        // See importStyle()
825        $htmlStack->restoreToRoot ();
826        $htmlStack->open('p');
827        $toMatch = $htmlStack->getCurrentElement();
828        $properties = array();
829        $params->import->getPropertiesForElement($properties, $toMatch, $params->units);
830        $htmlStack->restoreToRoot ();
831
832        $toImport = array_merge (self::$internalRegs, $registrations);
833        foreach ($toImport as $style => $element) {
834            if ($element ['compare']) {
835                self::importStyle($params, $htmlStack,
836                                  $style,
837                                  $element ['element'],
838                                  $element ['attributes'],
839                                  $properties);
840            } else {
841                self::importStyle($params, $htmlStack,
842                                  $style,
843                                  $element ['element'],
844                                  $element ['attributes'],
845                                  NULL);
846            }
847        }
848
849        // Import table styles
850        self::importTableStyles($params, $htmlStack);
851
852        // Import link styles (require extra pseudo class handling)
853        self::importLinkStyles($params, $htmlStack);
854
855        // Import list styles and list paragraph styles
856        self::importUnorderedListStyles($params, $htmlStack, $listAlign);
857        self::importOrderedListStyles($params, $htmlStack, $listAlign);
858
859        self::importParagraphDefaultStyle($params);
860        self::importFootnoteStyle($params);
861
862        self::importQuotationStyles($params, $htmlStack);
863    }
864
865    static public function importODTStyles(ODTInternalParams $params, $template=NULL, $tempDir=NULL){
866        if ($template == NULL || $tempDir == NULL) {
867            return;
868        }
869
870        // Temp dir
871        if (is_dir($tempDir)) { io_rmdir($tempDir,true); }
872        io_mkdir_p($tempDir);
873
874        // Extract template
875        try {
876            $ZIPextract = new \splitbrain\PHPArchive\Zip();
877            $ZIPextract->open($template);
878            $ZIPextract->extract($tempDir);
879            $ZIPextract->close();
880        } catch (\splitbrain\PHPArchive\ArchiveIOException $e) {
881            throw new Exception(' Error extracting the zip archive:'.$template.' to '.$tempDir);
882        }
883
884        // Import styles from ODT template
885        $params->styleset->importFromODTFile($tempDir.'/content.xml', 'office:automatic-styles', true);
886        $params->styleset->importFromODTFile($tempDir.'/styles.xml', 'office:automatic-styles', true);
887        $params->styleset->importFromODTFile($tempDir.'/styles.xml', 'office:styles', true);
888        $params->styleset->importFromODTFile($tempDir.'/styles.xml', 'office:master-styles', true);
889
890        // Cleanup temp dir.
891        io_rmdir($tempDir,true);
892    }
893}
894