1<?php
2/**
3 * ODTTextListStyle: class for ODT text list styles.
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author LarsDW223
7 */
8
9require_once DOKU_INC.'lib/plugins/odt/ODT/XMLUtil.php';
10require_once DOKU_INC.'lib/plugins/odt/ODT/styles/ODTStyle.php';
11require_once DOKU_INC.'lib/plugins/odt/ODT/styles/ODTTextStyle.php';
12
13/**
14 * The ODTTextListStyle class
15 */
16class ODTTextListStyle extends ODTStyle
17{
18    static $list_fields = array(
19        // Fields belonging to "text:list-style"
20        'style-name'                         => array ('style:name',                              'style', false),
21        'style-display-name'                 => array ('style:display-name',                      'style', false),
22        'consecutive-numbering'              => array ('text:consecutive-numbering',              'style', true),
23    );
24
25    static $style_number_fields = array(
26        // Fields belonging to "text:list-level-style-number"
27        'level'                              => array ('text:level',                              'style-attr', true),
28        'text-style-name'                    => array ('text:style-name',                         'style-attr', true),
29        'num-format'                         => array ('style:num-format',                        'style-attr', true),
30        'num-letter-sync'                    => array ('style:num-letter-sync',                   'style-attr', true),
31        'num-prefix'                         => array ('style:num-prefix',                        'style-attr', true),
32        'num-suffix'                         => array ('style:num-suffix',                        'style-attr', true),
33        'display-levels'                     => array ('text:display-levels',                     'style-attr', true),
34        'start-value'                        => array ('text:start-value',                        'style-attr', true),
35    );
36
37    static $style_bullet_fields = array(
38        // Fields belonging to "text:list-level-style-bullet"
39        'level'                              => array ('text:level',                              'style-attr', true),
40        'text-style-name'                    => array ('text:style-name',                         'style-attr', true),
41        'text-bullet-char'                   => array ('text:bullet-char',                        'style-attr', true),
42        'num-prefix'                         => array ('style:num-prefix',                        'style-attr', true),
43        'num-suffix'                         => array ('style:num-suffix',                        'style-attr', true),
44        'text-bullet-relative-size'          => array ('text:bullet-relative-size',               'style-attr', true),
45    );
46
47    static $style_image_fields = array(
48        // Fields belonging to "text:list-level-style-image"
49        'level'                              => array ('text:level',                              'style-attr',  true),
50        'type'                               => array ('xlink:type',                              'style-attr',  true),
51        'href'                               => array ('xlink:href',                              'style-attr',  true),
52        'show'                               => array ('xlink:show',                              'style-attr',  true),
53        'actuate'                            => array ('xlink:actuate',                           'style-attr',  true),
54        'binary-data'                        => array ('office:binary-data',                      'style-attr',  true),
55        'base64Binary'                       => array ('base64Binary',                            'style-attr',  true),
56    );
57
58    static $list_level_props_fields = array(
59        // Fields belonging to "style-list-level-properties"
60        'text-align'                         => array ('fo:text-align',                           'level-list', true),
61        'text-space-before'                  => array ('text:space-before',                       'level-list', true),
62        'text-min-label-width'               => array ('text:min-label-width',                    'level-list', true),
63        'text-min-label-distance'            => array ('text:min-label-distance',                 'level-list', true),
64        'font-name'                          => array ('style:font-name',                         'level-list', true),
65        'width'                              => array ('fo:width',                                'level-list', true),
66        'height'                             => array ('fo:height',                               'level-list', true),
67        'vertical-rel'                       => array ('style:vertical-rel',                      'level-list', true),
68        'vertical-pos'                       => array ('style:vertical-pos',                      'level-list', true),
69        'list-level-position-and-space-mode' => array ('text:list-level-position-and-space-mode', 'level-list', true),
70    );
71
72    static $label_align_fields = array(
73        // Fields belonging to "style:list-level-label-alignment"
74        'label-followed-by'                  => array ('text:label-followed-by',                  'level-label', true),
75        'list-tab-stop-position'             => array ('text:list-tab-stop-position',             'level-label', true),
76        'text-indent'                        => array ('fo:text-indent',                          'level-label', true),
77        'margin-left'                        => array ('fo:margin-left',                          'level-label', true),
78    );
79    protected $list_level_styles = array();
80
81    /**
82     * Get the element name for the ODT XML encoding of the style.
83     */
84    public function getElementName() {
85        return 'text:list-style';
86    }
87
88    /**
89     * Set style properties by importing values from a properties array.
90     * Properties might be disabled by setting them in $disabled.
91     * The style must have been previously created.
92     *
93     * @param  $properties Properties to be imported
94     * @param  $disabled Properties to be ignored
95     */
96    public function importProperties($properties, $disabled) {
97        $this->importPropertiesInternal(ODTTextStyle::getTextProperties (), $properties, $disabled);
98        $this->importPropertiesInternal(self::$list_fields, $properties, $disabled);
99    }
100
101    /**
102     * Check if a style is a common style.
103     *
104     * @return bool Is common style
105     */
106    public function mustBeCommonStyle() {
107        return false;
108    }
109
110    /**
111     * Set a property.
112     * For a TextListStyle we can only set the style main properties here.
113     * All properties specific for a level need to be set using setPropertyForLevel().
114     *
115     * @param $property The name of the property to set
116     * @param $value    New value to set
117     */
118    public function setProperty($property, $value) {
119        if (array_key_exists ($property, self::$list_fields)) {
120            $this->setPropertyInternal
121                ($property, self::$list_fields [$property][0], $value, self::$list_fields [$property][1]);
122            return;
123        }
124    }
125
126    /**
127     * Create new style by importing ODT style definition.
128     *
129     * @param  $xmlCode Style definition in ODT XML format
130     * @return ODTStyle New specific style
131     */
132    static public function importODTStyle($xmlCode) {
133        $style = new ODTTextListStyle();
134        $attrs = 0;
135
136        $open = XMLUtil::getElementOpenTag('text:list-style', $xmlCode);
137        if (!empty($open)) {
138            // This properties are stored in the properties of ODTStyle
139            $attrs += $style->importODTStyleInternal(self::$list_fields, $open);
140            $content = XMLUtil::getElementContent('text:list-style', $xmlCode);
141        }
142
143        $pos = 0;
144        $end = 0;
145        $max = strlen ($content);
146        $text_fields = ODTTextStyle::getTextProperties ();
147        while ($pos < $max)
148        {
149            // Get XML code for next level.
150            $level = XMLUtil::getNextElement($element, substr($content, $pos), $end);
151            $level_content = XMLUtil::getNextElementContent($element, $level, $ignore);
152            if (!empty($level)) {
153                $list_style_properties = array();
154                $list_level_properties = array();
155                $label_properties = array();
156                $text_properties = array();
157                $properties = array();
158                switch ($element) {
159                    case 'text:list-level-style-number':
160                        $attrs += $style->importODTStyleInternal(self::$style_number_fields, $level, $list_style_properties);
161                        $list_level_style = 'number';
162                    break;
163                    case 'text:list-level-style-bullet':
164                        $attrs += $style->importODTStyleInternal(self::$style_bullet_fields, $level, $list_style_properties);
165                        $list_level_style = 'bullet';
166                    break;
167                    case 'text:list-level-style-image':
168                        $attrs += $style->importODTStyleInternal(self::$style_image_fields, $level, $list_style_properties);
169                        $list_level_style = 'image';
170                    break;
171                }
172
173                $temp_content = XMLUtil::getElement('style:text-properties', $level_content);
174                $attrs += $style->importODTStyleInternal($text_fields, $temp_content, $text_properties);
175                $temp_content = XMLUtil::getElementOpenTag('style:list-level-properties', $level_content);
176                $attrs += $style->importODTStyleInternal(self::$list_level_props_fields, $temp_content, $list_level_properties);
177                $temp_content = XMLUtil::getElement('style:list-level-label-alignment', $level_content);
178                $attrs += $style->importODTStyleInternal(self::$label_align_fields, $temp_content, $label_properties);
179
180                // Assign properties array to our level array
181                $level_number = $style->getPropertyInternal('level', $list_style_properties);
182                $properties ['list-style'] = $list_style_properties;
183                $properties ['list-level'] = $list_level_properties;
184                $properties ['label'] = $label_properties;
185                $properties ['text'] = $text_properties;
186                $style->list_level_styles [$level_number] = $properties;
187
188                // Set special property 'list-level-style' to remember element to encode
189                // on call to toString()!
190                $style->setPropertyForLevel($level_number, 'list-level-style', $list_level_style);
191            }
192
193            $pos += $end;
194        }
195
196        // If style has no meaningfull content then throw it away
197        if ( $attrs == 0 ) {
198            return NULL;
199        }
200
201        return $style;
202    }
203
204    /**
205     * Encode current style values in a string and return it.
206     *
207     * @return string ODT XML encoded style
208     */
209    public function toString() {
210        $style = '';
211        $levels = '';
212
213        // The style properties are stored in the properties of ODTStyle
214        foreach ($this->properties as $property => $items) {
215            $style .= $items ['odt_property'].'="'.$items ['value'].'" ';
216        }
217
218        // The level properties are stored in our level properties
219        $level_number = 0;
220        foreach ($this->list_level_styles as $key => $properties) {
221            $level_number++;
222            $element = $this->getPropertyFromLevel($level_number, 'list-level-style');
223            switch ($element) {
224                case 'number':
225                    $fields = self::$style_number_fields;
226                break;
227                case 'bullet':
228                    $fields = self::$style_bullet_fields;
229                break;
230                case 'image':
231                    $fields = self::$style_image_fields;
232                break;
233            }
234            $element = 'text:list-level-style-'.$element;
235
236            $style_attr = '';
237            foreach ($this->list_level_styles [$level_number]['list-style'] as $property => $items) {
238                // Only add fields/properties which are allowed for the specific list-level-style
239                if ($property != 'list-level-style' && array_key_exists ($property, $fields)) {
240                    $style_attr .= $items ['odt_property'].'="'.$items ['value'].'" ';
241                }
242            }
243            $level_list = '';
244            foreach ($this->list_level_styles [$level_number]['list-level'] as $property => $items) {
245                $level_list .= $items ['odt_property'].'="'.$items ['value'].'" ';
246            }
247            $level_label = '';
248            foreach ($this->list_level_styles [$level_number]['label'] as $property => $items) {
249                $level_label .= $items ['odt_property'].'="'.$items ['value'].'" ';
250            }
251            $text = '';
252            foreach ($this->list_level_styles [$level_number]['text'] as $property => $items) {
253                $text .= $items ['odt_property'].'="'.$items ['value'].'" ';
254            }
255
256            $levels .= '    <'.$element.' '.$style_attr.">\n";
257            if (!empty($level_list)) {
258                if (empty($level_label)) {
259                    $levels .= '        <style:list-level-properties '.$level_list."/>\n";
260                } else {
261                    $levels .= '        <style:list-level-properties '.$level_list.">\n";
262                    $levels .= '            <style:list-level-label-alignment '.$level_label."/>\n";
263                    $levels .= "        </style:list-level-properties>\n";
264                }
265            }
266            if (!empty($text)) {
267                $levels .= '        <style:text-properties '.$text.'/>'."\n";
268            }
269            $levels .= "    </".$element.">\n";
270        }
271
272        // Build style.
273        $element = $this->getElementName();
274        $style  = '<'.$element.' '.$style.">\n";
275        if ( !empty($levels) ) {
276            $style .= $levels;
277        }
278        $style .= '</'.$element.">\n";
279        return $style;
280    }
281
282    /**
283     * Get the value of a property for text outline level $level.
284     *
285     * @param  $level      The text outline level (usually 1 to 10)
286     * @param  $property   The property name
287     * @return string      The current value of the property
288     */
289    public function getPropertyFromLevel($level, $property) {
290        if ($property == 'list-level-style') {
291            // Property 'list-level-style' is a special property just to remember
292            // which element needs to be encoded on a call to toString().
293            // It may not be included in the output of toString()!!!
294            return $this->getPropertyInternal('list-level-style', $this->list_level_styles [$level]['list-style']);
295        }
296        $text_fields = ODTTextStyle::getTextProperties ();
297        if (array_key_exists ($property, $text_fields)) {
298            return $this->getPropertyInternal($property, $this->list_level_styles [$level]['text']);
299        }
300        $element = $this->getPropertyInternal('list-level-style', $this->list_level_styles [$level]['list-style']);
301        switch ($element) {
302            case 'number':
303                $fields = self::$style_number_fields;
304            break;
305            case 'bullet':
306                $fields = self::$style_bullet_fields;
307            break;
308            case 'image':
309                $fields = self::$style_image_fields;
310            break;
311        }
312        if (array_key_exists ($property, $fields)) {
313            return $this->getPropertyInternal($property, $this->list_level_styles [$level]['list-style']);
314        }
315        if (array_key_exists ($property, self::$list_level_props_fields)) {
316            return $this->getPropertyInternal($property, $this->list_level_styles [$level]['list-level']);
317        }
318        if (array_key_exists ($property, self::$label_align_fields)) {
319            return $this->getPropertyInternal($property, $this->list_level_styles [$level]['label']);
320        }
321    }
322
323    /**
324     * Set a property for a specific level.
325     *
326     * @param $level    The level for which to set the property (1 to 10)
327     * @param $property The name of the property to set
328     * @param $value    New value to set
329     */
330    public function setPropertyForLevel($level, $property, $value) {
331        if ($property == 'list-level-style') {
332            // Property 'list-level-style' is a special property just to remember
333            // which element needs to be encoded on a call to toString().
334            // It may not be included in the output of toString()!!!
335            $this->setPropertyInternal
336                ($property, 'list-level-style', $value, 'list-level-style', $this->list_level_styles [$level]['list-style']);
337        } else {
338            // First check fields/properties common to each list-level-style
339            $text_fields = ODTTextStyle::getTextProperties ();
340            if (array_key_exists ($property, $text_fields)) {
341                $this->setPropertyInternal
342                    ($property, $text_fields [$property][0], $value, $text_fields [$property][1], $this->list_level_styles [$level]['text']);
343                return;
344            }
345            if (array_key_exists ($property, self::$list_level_props_fields)) {
346                $this->setPropertyInternal
347                    ($property, self::$list_level_props_fields [$property][0], $value, self::$list_level_props_fields [$property][1], $this->list_level_styles [$level]['list-level']);
348                return;
349            }
350            if (array_key_exists ($property, self::$label_align_fields)) {
351                $this->setPropertyInternal
352                    ($property, self::$label_align_fields [$property][0], $value, self::$label_align_fields [$property][1], $this->list_level_styles [$level]['label']);
353                return;
354            }
355
356            // Now check fields specific to the list-level-style.
357            $element = $this->getPropertyFromLevel ($level, 'list-level-style');
358            switch ($element) {
359                case 'number':
360                    $fields = self::$style_number_fields;
361                break;
362                case 'bullet':
363                    $fields = self::$style_bullet_fields;
364                break;
365                case 'image':
366                    $fields = self::$style_image_fields;
367                break;
368            }
369            if (array_key_exists ($property, $fields)) {
370                $this->setPropertyInternal
371                    ($property, $fields [$property][0], $value, $fields [$property][1], $this->list_level_styles [$level]['list-style']);
372            }
373        }
374    }
375}
376
377