1<?php
2
3require_once DOKU_PLUGIN.'odt/ODT/elements/ODTStateElement.php';
4require_once DOKU_PLUGIN.'odt/ODT/elements/ODTRoot.php';
5require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementSpan.php';
6require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementParagraph.php';
7require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementList.php';
8require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementListItem.php';
9require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementListHeader.php';
10require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTable.php';
11require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTableColumn.php';
12require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTableRow.php';
13require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTableCell.php';
14require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTableHeaderCell.php';
15require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementFrame.php';
16require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTextBox.php';
17require_once DOKU_PLUGIN.'odt/ODT/css/cssdocument.php';
18
19/**
20 * ODTState: class for maintaining the ODT state stack.
21 *
22 * In general this is a setter/getter class for ODT states.
23 * The intention is to get rid of some global state variables.
24 * Especially the global error-prone $in_paragraph which easily causes
25 * a document to become invalid if once set wrong. Now each state/element
26 * can set their own instance of $in_paragraph which hopefully makes it use
27 * a bit safer. E.g. for a new table-cell or list-item it can be set to false
28 * because they allow creation of a new paragraph. On leave() we throw the
29 * current state variables away and are safe back from where we came from.
30 * So we also don't need to worry about correct re-initialization of global
31 * variables anymore.
32 *
33 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
34 * @author  LarsDW223
35 */
36class ODTState
37{
38    // The ODT document to which this state belongs
39    protected $document = NULL;
40    protected $stack = array();
41    protected $size = 0;
42    protected $element_counter = array();
43
44    /**
45     * Constructor. Set initial 'root' state.
46     */
47    public function __construct() {
48        // Stack for maintaining our ODT elements
49        $this->stack [$this->size] = new ODTElementRoot();
50        $this->size++;
51    }
52
53    /**
54     * Get current list item.
55     * If the function returns NULL then that means that we are
56     * currently not in a list item.
57     *
58     * @return ODTStateElement|NULL
59     */
60    public function getCurrentListItem() {
61        return $this->findClosestWithClass ('list-item');
62    }
63
64    /**
65     * Get current frame.
66     * If the function returns NULL then that means that we are
67     * currently not in a frame.
68     *
69     * @return ODTStateElement|NULL
70     */
71    public function getCurrentFrame() {
72        return $this->findClosestWithClass ('frame');
73    }
74
75    /**
76     * Get current list.
77     * If the function returns NULL then that means that we are
78     * currently not in a list.
79     *
80     * @return ODTStateElement|NULL
81     */
82    public function getCurrentList() {
83        return $this->findClosestWithClass ('list');
84    }
85
86    /**
87     * Get current paragraph.
88     * If the function returns NULL then that means that we are
89     * currently not in a paragraph.
90     *
91     * @return ODTStateElement|NULL
92     */
93    public function getCurrentParagraph() {
94        // Only search for the paragraph if the current element tells
95        // us that we are in one. Otherwise we may find the paragraph
96        // around this current element which might lead to double
97        // closing of a paragraph == invalid/broken ODT document!
98        if ($this->getInParagraph()) {
99            return $this->findClosestWithClass ('paragraph');
100        }
101        return NULL;
102    }
103
104    /**
105     * Get current table.
106     * If the function returns NULL then that means that we are
107     * currently not in a table.
108     *
109     * @return ODTStateElement|NULL
110     */
111    public function getCurrentTable() {
112        return $this->findClosestWithClass ('table');
113    }
114
115    /**
116     * Enter a new state with element name $element and class $clazz.
117     * E.g. 'text:p' and 'paragraph'.
118     *
119     * @param string $element
120     * @param string $clazz
121     */
122    public function enter(ODTStateElement $element, $attributes=NULL) {
123        if ($element == NULL ) {
124            return;
125        }
126        $name = $element->getElementName();
127
128        // Increase the counter for that element
129        if ($this->element_counter [$name] == NULL ) {
130            $this->element_counter [$name] = 1;
131        } else {
132            $this->element_counter [$name]++;
133        }
134        $element->setCount($this->element_counter [$name]);
135
136        // Get the current element
137        $previous = $this->stack [$this->size-1];
138
139        // Add new element to stack
140        $this->stack [$this->size] = $element;
141        $this->size++;
142
143        // Set the elements style object
144        if ($this->document != NULL) {
145            $styleObj = $this->document->getStyle($element->getStyleName());
146            $element->setStyle($styleObj);
147        }
148
149        // Let the element find its parent
150        $element->determineParent ($previous);
151    }
152
153    /**
154     * Get current element on top of the stack.
155     *
156     * @return ODTStateElement
157     */
158    public function getCurrent() {
159        return $this->stack [$this->size-1];
160    }
161
162    /**
163     * Leave current state. All data of the curent state is thrown away.
164     */
165    public function leave() {
166        // We always will keep the initial state.
167        // That means we do nothing if size is 0. This would be a fault anyway.
168        if ($this->size > 1) {
169            unset ($this->stack [$this->size-1]);
170            $this->size--;
171        }
172    }
173
174    /**
175     * Reset the state stack/go back to the initial state.
176     * All states except the root state will be discarded.
177     */
178    public function reset() {
179        // Throw away any states except the initial state.
180        // Reset size to 1.
181        for ($reset = 1 ; $reset < $this->size ; $reset++) {
182            unset ($this->stack [$reset]);
183        }
184        $this->size = 1;
185    }
186
187    /**
188     * Find the closest state with class $clazz.
189     *
190     * @param string $clazz
191     * @return ODTStateEntry|NULL
192     */
193    public function findClosestWithClass($clazz) {
194        for ($search = $this->size-1 ; $search > 0 ; $search--) {
195            if ($this->stack [$search]->getClass() == $clazz) {
196                return $this->stack [$search];
197            }
198        }
199        // Nothing found.
200        return NULL;
201    }
202
203    /**
204     * Find the closest state with class $clazz, return $index.
205     *
206     * @param string $clazz
207     * @param integer|false &$index Index of the found element or false
208     * @return ODTStateEntry|NULL
209     */
210    public function findClosestWithClassGetIndex($clazz, &$index) {
211        $index = false;
212        for ($search = $this->size-1 ; $search > 0 ; $search--) {
213            if ($this->stack [$search]->getClass() == $clazz) {
214                $index = $search;
215                return $this->stack [$search];
216            }
217        }
218        // Nothing found.
219        return NULL;
220    }
221
222    /**
223     * toString() function. Only for creating debug dumps.
224     *
225     * @return string
226     */
227    public function toString () {
228        $indent = '';
229        $string = 'Stackdump:';
230        for ($search = 0 ; $search < $this->size ; $search++) {
231            $string .= $indent . $this->stack [$search]->getElementName().';';
232            $indent .= '    ';
233        }
234        return $string;
235    }
236
237    /**
238     * Find the closest state with class $clazz.
239     *
240     * @param string $clazz
241     * @return ODTStateEntry|NULL
242     */
243    public function countClass($clazz) {
244        $count = 0;
245        for ($search = $this->size-1 ; $search > 0 ; $search--) {
246            if ($this->stack [$search]->getClass() == $clazz) {
247                $count++;
248            }
249        }
250        return $count;
251    }
252
253    /**
254     * Find the closest element with element name $name.
255     *
256     * @param string $name
257     * @return ODTStateElement|NULL
258     */
259    public function findClosestWithName($name) {
260        for ($search = $this->size-1 ; $search > 0 ; $search--) {
261            if ($this->stack [$search]->getElementName() == $name) {
262                return $this->stack [$search];
263            }
264        }
265        // Nothing found.
266        return NULL;
267    }
268
269    /**
270     * Are we in a table row?
271     *
272     * @return bool
273     */
274    public function getInTableRow() {
275        $this->findClosestWithClassGetIndex('table-row', $tableRowIndex);
276        $this->findClosestWithClassGetIndex('table', $tableIndex);
277        if ($tableRowIndex > $tableIndex) {
278            return true;
279        }
280        return false;
281    }
282
283    /**
284     * Are we in a table cell?
285     *
286     * @return bool
287     */
288    public function getInTableCell() {
289        $this->findClosestWithClassGetIndex('table-cell', $tableCellIndex);
290        $this->findClosestWithClassGetIndex('table-row', $tableRowIndex);
291        if ($tableCellIndex > $tableRowIndex) {
292            return true;
293        }
294        return false;
295    }
296
297    /**
298     * Are we in a list item?
299     *
300     * @return bool
301     */
302    public function getInListItem() {
303        $this->findClosestWithClassGetIndex('list-item', $listItemIndex);
304        $this->findClosestWithClassGetIndex('list', $listIndex);
305        if ($listItemIndex > $listIndex) {
306            return true;
307        }
308        return false;
309    }
310
311    /**
312     * Are we in list content?
313     *
314     * @return bool
315     */
316    public function getInListContent() {
317        // listContentOpen == paragraphOpen,
318        // so we can simply call getInParagraph()
319        return $this->getInParagraph();
320    }
321
322    /**
323     * Are we in a paragraph?
324     *
325     * @return bool
326     */
327    public function getInParagraph() {
328        // Ask the current element
329        if ($this->size > 0) {
330            return $this->stack [$this->size-1]->getInParagraph();
331        } else {
332            return false;
333        }
334    }
335
336    /**
337     * Set the ODTDocument to which this state belongs.
338     *
339     * @param ODTDocument $doc
340     */
341    public function setDocument($doc) {
342        $this->document = $doc;
343    }
344
345    public function getHTMLElement() {
346        // Ask the current element
347        if ($this->size > 0) {
348            return $this->stack [$this->size-1]->getHTMLElement();
349        } else {
350            return NULL;
351        }
352    }
353
354    public function getElementCount($element) {
355        return $this->element_counter [$element]++;
356    }
357}
358