1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
<?php
/**
***********************************************************************************************
* @copyright 2004-2016 The Admidio Team
* @see http://www.admidio.org/
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License v2.0 only
***********************************************************************************************
*/
/**
* @class HtmlElement
* @brief This @b abstract @b class parses html elements
*
* This abstract class is designed to parse html elements.
* It is only allowed to use extensions of this class.
* Create a html object and add your elements programmatically .
* Calling as parent instance just define the element you need and add all inline elements
* or child elements. Also it is possible to define attributes and value for each added
* element. Content data can be passed as string or as array.
* The class supports also reading the data from assoc arrays and bi dimensional arrays.
* @par Testarray with data
* @code
* // Example content arrays
* $dataArray = array('Data 1', 'Data 2', 'Data 3');
* @endcode
* @par Example_1: @b unorderedlist
* @code
* // create as parent instance
* parent::HtmlElement('ul','class', 'unordered'); // Parameters( element, attribute, value, nesting (true/false ))
* // we want to have further attributes for the element and set an id, for example
* HtmlElement::addAttribute('id','mainelement');
* // set a list element with content as string
* HtmlElement::addElement('li', 'list 1');
* // if you need attributes for your setted element then first define the element, set the attributes and after that
* // pass the content.
* // Example: Arrays are also supported for content values.
* HtmlElement::addElement('li');
* HtmlElement::addAttribute('class', 'from array');
* HtmlElement::addData($dataArray);
* // As result you get 3 <li> elements with same class and content from the array
* // Next example defines a list element with data list, data terms and data descriptions. Therefor we use method addParentElement();
* // This method logs the selected elements because the endtags must be set later.
* HtmlElement::addParentElement('li');
* HtmlElement::addAttribute('class', 'link_1');
* HtmlElement::addParentElement('dl');
* HtmlElement::addAttribute('class', 'datalist_1');
* // now the elements with start and endtags
* HtmlElement::addElement('dt', 'term');
* HtmlElement::addElement('dd', 'description');
* // finally set the endtags for all opened parent elements
* HtmlElement::closeParentElement('dl');
* HtmlElement::closeParentElement('li');
* // Repeat with next list elements
* HtmlElement::addParentElement('li');
* HtmlElement::addParentElement('dl');
* HtmlElement::addElement('dt', 'term2');
* HtmlElement::addElement('dd', 'description2');
* HtmlElement::closeParentElement('dl');
* HtmlElement::closeParentElement('li');
* $htmlList = HtmlElement::getHtmlElement();
* echo $htmlList;
* @endcode
* @par Example_2 Nested Div Elements using nesting mode
* @code
* // Creating block elements with nested divs.
* // Example using nesting mode for html elements
* // Setting mode to true you are allowed to set the main element ('div' in this example) further times
* // Default false it is not possible to set the main element again
*
* parent::HtmlElement ('div', 'class', 'pagewrap', true);
* // now we can nest a second div element with a paragaph.
* // Because of div is the parent of the paragraph element, we must tell the class using method addParentElement();
* HtmlElement::addParentElement('div');
* // We want to set an Id for the div element, for example
* HtmlElement::addAttribute('id', 'Paragraphs', 'div');
* // Define a paragrph
* HtmlElement::addElement('p', 'Hello World');
* // Nested div element must be closed !
* HtmlElement::closeParentElement('div');
* // Get the block element
* $htmlBlock = HtmlElement::getHtmlElement();
* echo $htmlBlock;
* @endcode
* @par Example_3 Hyperlinks
* @code
* parent::HtmlElement();
* HtmlElement::addElement('a');
* HtmlElement::addAttribute('href', 'http://www.admidio.org');
* HtmlElement::addData('Admidio Homepage');
* $hyperlink = HtmlElement::getHtmlElement();
* echo $hyperlink;
* @endcode
* @par Example_4 Form element
* @code
* // Create a form element
* parent::HtmlElement('form', 'name', 'testform');
* HtmlElement::addAttribute('action', 'test.php');
* HtmlElement::addAttribute('method', 'post');
* HtmlElement::addAttribute('enctype', 'text/html');
* // add an input field with label
* HtmlElement::addElement('input');
* HtmlElement::addAttribute('type', 'text');
* HtmlElement::addAttribute('name', 'input');
* HtmlElement::addHtml('Inputfield:');
* // pass a whitespace because element has no content
* HtmlElement::addData(' ', true); // true for self closing element (default: false)
* // add a checkbox
* HtmlElement::addElement('input');
* HtmlElement::addAttribute('type', 'checkbox');
* HtmlElement::addAttribute('name', 'checkbox');
* HtmlElement::addHtml('Checkbox:');
* // pass a whitespace because element has no content
* HtmlElement::addData(' ', true); // true for self closing element (default: false)
* // add a submit button
* HtmlElement::addElement('input');
* HtmlElement::addAttribute('type', 'submit');
* HtmlElement::addAttribute('value', 'submit');
* // pass a whitespace because element has no content
* HtmlElement::addData(' ', true);
*
* echo HtmlElement::getHtmlElement();
* @endcode
*/
abstract class HtmlElement {
protected $nesting; ///< Flag enables nesting of main elements, e.g div blocks ( Default : false )
protected $mainElement; ///< String with main element as string
protected $mainElementAttributes; ///< String with attributes of the main element
protected $mainElementWritten; ///< Flag if the main element was written in the html string
protected $currentElement; ///< Internal pointer showing to actual element or child element
protected $currentElementAttributes; ///< Attributes of the current element
protected $currentElementDataWritten; ///< Flag if an element is added but the data is not added
protected $htmlString; ///< String with prepared html
protected $parentFlag; ///< Flag for setted parent Element
protected $arrParentElements; ///< Array with opened child elements
/**
* Constructor initializing all class variables
*
* @param string $element The html element to be defined
* @param bool $nesting Enables nesting of main elements ( Default: False )
*/
public function __construct($element, $nesting = false)
{
$this->nesting = $nesting;
$this->mainElement = $element;
$this->mainElementAttributes = array();
$this->mainElementWritten = false;
$this->currentElement = $element;
$this->currentElementAttributes = array();
$this->currentElementDataWritten = true;
$this->htmlString = '';
$this->parentFlag = false;
$this->arrParentElements = array();
}
/**
* Add attributes to the selected element. If that attribute is already added
* than the new value will be attached to the current value.
* @param string $attrKey Name of the html attribute
* @param string $attrValue Value of the attribute
* @param string $element Optional the element for which the attribute should be set,
* if this is not the current element
*/
public function addAttribute($attrKey, $attrValue, $element = null)
{
if ($element === null)
{
$element = $this->currentElement;
}
$selectedElementAttributes = 'currentElementAttributes';
if ($element === $this->mainElement)
{
$selectedElementAttributes = 'mainElementAttributes';
}
if (array_key_exists($attrKey, $this->{$selectedElementAttributes}))
{
$this->{$selectedElementAttributes}[$attrKey] = $this->{$selectedElementAttributes}[$attrKey] . ' ' . $attrValue;
}
else
{
$this->{$selectedElementAttributes}[$attrKey] = $attrValue;
}
}
/**
* Set attributes from associative array.
* @param string[] $arrAttributes An array that contains all attribute names as array key
* and all attribute content as array value
*/
protected function setAttributesFromArray(array $arrAttributes)
{
foreach ($arrAttributes as $key => $value)
{
$this->addAttribute($key, $value);
}
}
/**
* Add data to current element
* @param string|string[] $data Content for the element as string, or array
* @param bool $selfClosing Element has self closing tag ( default: false)
*/
public function addData($data, $selfClosing = false)
{
if ($selfClosing)
{
$startTag = '<' . $this->currentElement . $this->getCurrentElementAttributesString();
$endTag = '/>';
}
else
{
$startTag = '<' . $this->currentElement . $this->getCurrentElementAttributesString() . '>';
$endTag = '</' . $this->currentElement . '>';
}
if (is_array($data))
{
// data is an array
foreach ($data as $value)
{
$this->htmlString .= $startTag . $value . $endTag;
}
}
else
{
// data is a string
$this->htmlString .= $startTag . $data . $endTag;
}
$this->currentElementAttributes = array();
// set flag that the data of the current element is written to html string
$this->currentElementDataWritten = true;
}
/**
* @par Add new child element.
* This method defines the next child element to be written in the output string.
* If a parent element was defined before, the syntax with all setted attributes is written first from internal buffer to the string.
* After that, the new element is defined.
* The method determines that the element has @b no @b own @b child @b elements and has a closing tag.
* If you need a parent element like a \<div\> with some \<p\> elements, use method addParentElement(); instead and then add the paragraph elements.
* If nesting mode is active you are allowed to set the main element called with object instance again. Dafault: false
*
* @param string $childElement valid child tags for element object
* @param string $attrKey Attribute name
* @param string $attrValue Value for the attribute
* @param string $data content values can be passed as string, array, bidimensional Array and assoc. Array. ( Default: no data )
* @param bool $selfClosing Element has self closing tag ( default: false)
*/
public function addElement($childElement, $attrKey = '', $attrValue = '', $data = '', $selfClosing = false)
{
// if previous current element was not written to html string and the same child element is set
// than this could be a call of parent class so do not reinitialize the current element
if (!$this->currentElementDataWritten && $childElement === $this->currentElement)
{
return;
}
$this->currentElementDataWritten = false;
if ($attrKey !== '' || $attrValue !== '')
{
$this->addAttribute($attrKey, $attrValue);
}
// check if parent element is set, then write first the tag and attributes for the previous element
if ($this->parentFlag)
{
// Main element attributes are set in own variable, so in nesting mode main element can be set again
if ($this->currentElement === $this->mainElement)
{
$this->currentElementAttributes = $this->mainElementAttributes;
}
$this->htmlString .= '<' . $this->currentElement . $this->getCurrentElementAttributesString() . '>';
$this->currentElement = $childElement;
$this->currentElementAttributes = array();
$this->parentFlag = false;
}
// If first child is set start writing the html beginning with main element and attributes
if ($this->currentElement === $this->mainElement && $this->mainElement !== '' && !$this->mainElementWritten)
{
$this->htmlString .= '<' . $this->mainElement . $this->getMainElementAttributesString() . '>';
$this->mainElementWritten = true;
}
// If nesting is enabled, main element can be set again
if ($childElement === $this->mainElement && $this->nesting)
{
// now set as current position
$this->currentElement = $childElement;
// clear attribute buffer
$this->currentElementAttributes = array();
}
if ($childElement !== $this->mainElement)
{
// now set as current position
$this->currentElement = $childElement;
// clear attribute buffer
$this->currentElementAttributes = array();
}
// add content if exists
if ($data !== '')
{
$this->addData($data, $selfClosing);
}
}
/**
* Add any string to the html output. If the main element wasn't written to the
* html string than this will be done before your string will be added.
* @param string $string Text as string in current string position
*/
public function addHtml($string = '')
{
// If first child is set start writing the html beginning with main element and attributes
if ($this->currentElement === $this->mainElement && $this->mainElement !== '' && !$this->mainElementWritten)
{
$this->htmlString .= '<' . $this->mainElement . $this->getMainElementAttributesString() . '>';
$this->mainElementWritten = true;
}
$this->htmlString .= $string;
}
/**
* @par Add a parent element that has own child's.
* This method is needed if an element can have several child elements and the closing tag must be set after own child elements.
* It logs the setted element in an array. Each time you define a new parent element, the function checks the log array, if the element already was set.
* If the current element already was defined, then the function determines that the still opened tag must be closed first until it can be set again.
* The method closeParentElement(); is called automatically to close the previous element.
* By default it is not allowed to define several elements from same type. If needed use option @b nesting @b mode @b true!
*
* @param string $parentElement Parent element to be set
* @param string $attrKey Attribute name
* @param string $attrValue Value for the attribute
*/
public function addParentElement($parentElement, $attrKey = '', $attrValue = '')
{
// Only possible for child elements of the main element or nesting mode is active!
if (!$this->nesting && $this->currentElement === $this->mainElement)
{
return;
}
// check if already parent element is set, then write first the tag and attributes for the previous element
if ($this->parentFlag)
{
$this->htmlString .= '<' . $this->currentElement . $this->getCurrentElementAttributesString() . '>';
//$this->currentElementAttributes = array();
}
else
{
// set Flag
$this->parentFlag = true;
if ($this->currentElement === $this->mainElement && $this->nesting && !$this->mainElementWritten)
{
$this->htmlString .= '<' . $this->currentElement . $this->getMainElementAttributesString() . '>';
$this->mainElementAttributes = array();
}
}
if (!in_array($parentElement, $this->arrParentElements, true))
{
// If currently not defined and element has own child elements then log in array to define endtags later
$this->arrParentElements[] = $parentElement;
}
elseif ($this->nesting)
{
// in nesting mode always log elements
$this->arrParentElements[] = $parentElement;
}
else
{
// already set and we need the endtag first before setting again
$this->closeParentElement($parentElement);
$this->arrParentElements[] = $parentElement;
}
// set parent element to current element
$this->currentElement = $parentElement;
// initialize attributes because parent element should not get attributes of previous element
$this->currentElementAttributes = array();
// save attribute for parent element
if ($attrKey !== '')
{
$this->addAttribute($attrKey, $attrValue);
}
//$this->mainElementAttributes = array();
}
/**
* @par Close parent element.
* This method sets the endtag of the selected element and removes the entry from log array.
* If nesting mode is not used, the methods looks for the entry in the array and determines
* that all setted elements after the selected element must be closed as well.
* All end tags to position are closed automatically starting with last setted element tag.
* @param string $parentElement Parent element to be closed
* @return bool
*/
public function closeParentElement($parentElement)
{
// count entries in array
$totalCount = count($this->arrParentElements);
if ($totalCount === 0)
{
return false;
}
$position = null;
if (!$this->nesting && in_array($parentElement, $this->arrParentElements, true))
{
// find position in log array
foreach ($this->arrParentElements as $key => $value)
{
if ($value === $parentElement)
{
$position = $key;
break;
}
}
// if last position set Endtag in string and remove from array
if ($position === $totalCount)
{
$this->htmlString .= '</' . $this->arrParentElements[$totalCount] . '>';
unset($this->arrParentElements[$totalCount]);
}
else
{
// all elements setted later must also be closed and removed from array
for ($i = $totalCount - 1; $i >= $position; --$i)
{
$this->htmlString .= '</' . $this->arrParentElements[$i] . '>';
unset($this->arrParentElements[$i]);
}
}
}
else
{
// close last tag and delete whitespaces in log array
$this->htmlString .= '</' . $this->arrParentElements[$totalCount - 1] . '>';
unset($this->arrParentElements[$totalCount - 1]);
}
$this->arrParentElements = array_values($this->arrParentElements);
return true;
}
/**
* Create a valid html compatible string with all attributes and their values of the given element.
* @param string[] $elementAttributes
* @return string Returns a string with all attributes and values.
*/
private function getElementAttributesString(array $elementAttributes)
{
$string = ' ';
foreach ($elementAttributes as $key => $value)
{
if ($key === $value)
{
$string .= $key . ' ';
}
else
{
$string .= $key . '="' . $value . '" ';
}
}
return $string;
}
/**
* Create a valid html compatible string with all attributes and their values of the last added element.
* @return string Returns a string with all attributes and values.
*/
private function getCurrentElementAttributesString()
{
return $this->getElementAttributesString($this->currentElementAttributes);
}
/**
* Create a valid html compatible string with all attributes and their values of the main element.
* @return string Returns a string with all attributes and values.
*/
private function getMainElementAttributesString()
{
return $this->getElementAttributesString($this->mainElementAttributes);
}
/**
* Return the element as string
* @return string Returns the parsed html as string
*/
public function getHtmlElement()
{
$this->htmlString .= '</' . $this->mainElement . '>';
return $this->htmlString;
}
}