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