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