Files
dokuwiki-plugins/lib/plugins/odt/ODT/ODTState.php
Trevor Batley bce7dd054a add contents
2025-10-09 15:04:29 +11:00

359 lines
10 KiB
PHP

<?php
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTStateElement.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTRoot.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementSpan.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementParagraph.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementList.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementListItem.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementListHeader.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTable.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTableColumn.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTableRow.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTableCell.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTableHeaderCell.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementFrame.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementTextBox.php';
require_once DOKU_PLUGIN.'odt/ODT/elements/ODTElementNote.php';
require_once DOKU_PLUGIN.'odt/ODT/css/cssdocument.php';
/**
* ODTState: class for maintaining the ODT state stack.
*
* In general this is a setter/getter class for ODT states.
* The intention is to get rid of some global state variables.
* Especially the global error-prone $in_paragraph which easily causes
* a document to become invalid if once set wrong. Now each state/element
* can set their own instance of $in_paragraph which hopefully makes it use
* a bit safer. E.g. for a new table-cell or list-item it can be set to false
* because they allow creation of a new paragraph. On leave() we throw the
* current state variables away and are safe back from where we came from.
* So we also don't need to worry about correct re-initialization of global
* variables anymore.
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author LarsDW223
*/
class ODTState
{
// The ODT document to which this state belongs
protected $document = NULL;
protected $stack = array();
protected $size = 0;
protected $element_counter = array();
/**
* Constructor. Set initial 'root' state.
*/
public function __construct() {
// Stack for maintaining our ODT elements
$this->stack [$this->size] = new ODTElementRoot();
$this->size++;
}
/**
* Get current list item.
* If the function returns NULL then that means that we are
* currently not in a list item.
*
* @return ODTStateElement|NULL
*/
public function getCurrentListItem() {
return $this->findClosestWithClass ('list-item');
}
/**
* Get current frame.
* If the function returns NULL then that means that we are
* currently not in a frame.
*
* @return ODTStateElement|NULL
*/
public function getCurrentFrame() {
return $this->findClosestWithClass ('frame');
}
/**
* Get current list.
* If the function returns NULL then that means that we are
* currently not in a list.
*
* @return ODTStateElement|NULL
*/
public function getCurrentList() {
return $this->findClosestWithClass ('list');
}
/**
* Get current paragraph.
* If the function returns NULL then that means that we are
* currently not in a paragraph.
*
* @return ODTStateElement|NULL
*/
public function getCurrentParagraph() {
// Only search for the paragraph if the current element tells
// us that we are in one. Otherwise we may find the paragraph
// around this current element which might lead to double
// closing of a paragraph == invalid/broken ODT document!
if ($this->getInParagraph()) {
return $this->findClosestWithClass ('paragraph');
}
return NULL;
}
/**
* Get current table.
* If the function returns NULL then that means that we are
* currently not in a table.
*
* @return ODTStateElement|NULL
*/
public function getCurrentTable() {
return $this->findClosestWithClass ('table');
}
/**
* Enter a new state with element name $element and class $clazz.
* E.g. 'text:p' and 'paragraph'.
*
* @param string $element
* @param string $clazz
*/
public function enter(ODTStateElement $element, $attributes=NULL) {
if ($element == NULL ) {
return;
}
$name = $element->getElementName();
// Increase the counter for that element
if ($this->element_counter [$name] == NULL ) {
$this->element_counter [$name] = 1;
} else {
$this->element_counter [$name]++;
}
$element->setCount($this->element_counter [$name]);
// Get the current element
$previous = $this->stack [$this->size-1];
// Add new element to stack
$this->stack [$this->size] = $element;
$this->size++;
// Set the elements style object
if ($this->document != NULL) {
$styleObj = $this->document->getStyle($element->getStyleName());
$element->setStyle($styleObj);
}
// Let the element find its parent
$element->determineParent ($previous);
}
/**
* Get current element on top of the stack.
*
* @return ODTStateElement
*/
public function getCurrent() {
return $this->stack [$this->size-1];
}
/**
* Leave current state. All data of the curent state is thrown away.
*/
public function leave() {
// We always will keep the initial state.
// That means we do nothing if size is 0. This would be a fault anyway.
if ($this->size > 1) {
unset ($this->stack [$this->size-1]);
$this->size--;
}
}
/**
* Reset the state stack/go back to the initial state.
* All states except the root state will be discarded.
*/
public function reset() {
// Throw away any states except the initial state.
// Reset size to 1.
for ($reset = 1 ; $reset < $this->size ; $reset++) {
unset ($this->stack [$reset]);
}
$this->size = 1;
}
/**
* Find the closest state with class $clazz.
*
* @param string $clazz
* @return ODTStateEntry|NULL
*/
public function findClosestWithClass($clazz) {
for ($search = $this->size-1 ; $search > 0 ; $search--) {
if ($this->stack [$search]->getClass() == $clazz) {
return $this->stack [$search];
}
}
// Nothing found.
return NULL;
}
/**
* Find the closest state with class $clazz, return $index.
*
* @param string $clazz
* @param integer|false &$index Index of the found element or false
* @return ODTStateEntry|NULL
*/
public function findClosestWithClassGetIndex($clazz, &$index) {
$index = false;
for ($search = $this->size-1 ; $search > 0 ; $search--) {
if ($this->stack [$search]->getClass() == $clazz) {
$index = $search;
return $this->stack [$search];
}
}
// Nothing found.
return NULL;
}
/**
* toString() function. Only for creating debug dumps.
*
* @return string
*/
public function toString () {
$indent = '';
$string = "Stackdump:\n";
for ($search = 0 ; $search < $this->size ; $search++) {
$string .= $indent . $this->stack [$search]->getElementName().";\n";
$indent .= ' ';
}
return $string;
}
/**
* Find the closest state with class $clazz.
*
* @param string $clazz
* @return ODTStateEntry|NULL
*/
public function countClass($clazz) {
$count = 0;
for ($search = $this->size-1 ; $search > 0 ; $search--) {
if ($this->stack [$search]->getClass() == $clazz) {
$count++;
}
}
return $count;
}
/**
* Find the closest element with element name $name.
*
* @param string $name
* @return ODTStateElement|NULL
*/
public function findClosestWithName($name) {
for ($search = $this->size-1 ; $search > 0 ; $search--) {
if ($this->stack [$search]->getElementName() == $name) {
return $this->stack [$search];
}
}
// Nothing found.
return NULL;
}
/**
* Are we in a table row?
*
* @return bool
*/
public function getInTableRow() {
$this->findClosestWithClassGetIndex('table-row', $tableRowIndex);
$this->findClosestWithClassGetIndex('table', $tableIndex);
if ($tableRowIndex > $tableIndex) {
return true;
}
return false;
}
/**
* Are we in a table cell?
*
* @return bool
*/
public function getInTableCell() {
$this->findClosestWithClassGetIndex('table-cell', $tableCellIndex);
$this->findClosestWithClassGetIndex('table-row', $tableRowIndex);
if ($tableCellIndex > $tableRowIndex) {
return true;
}
return false;
}
/**
* Are we in a list item?
*
* @return bool
*/
public function getInListItem() {
$this->findClosestWithClassGetIndex('list-item', $listItemIndex);
$this->findClosestWithClassGetIndex('list', $listIndex);
if ($listItemIndex > $listIndex) {
return true;
}
return false;
}
/**
* Are we in list content?
*
* @return bool
*/
public function getInListContent() {
// listContentOpen == paragraphOpen,
// so we can simply call getInParagraph()
return $this->getInParagraph();
}
/**
* Are we in a paragraph?
*
* @return bool
*/
public function getInParagraph() {
// Ask the current element
if ($this->size > 0) {
return $this->stack [$this->size-1]->getInParagraph();
} else {
return false;
}
}
/**
* Set the ODTDocument to which this state belongs.
*
* @param ODTDocument $doc
*/
public function setDocument($doc) {
$this->document = $doc;
}
public function getHTMLElement() {
// Ask the current element
if ($this->size > 0) {
return $this->stack [$this->size-1]->getHTMLElement();
} else {
return NULL;
}
}
public function getElementCount($element) {
return $this->element_counter [$element]++;
}
}