%PDF- %PDF-
Direktori : /www/varak.net/www.varak.net/texy/libs/ |
Current File : //www/varak.net/www.varak.net/texy/libs/TexyHtml.php |
<?php /** * Texy! - web text markup-language * -------------------------------- * * Copyright (c) 2004, 2008 David Grudl (http://davidgrudl.com) * * This source file is subject to the GNU GPL license that is bundled * with this package in the file license.txt. * * For more information please see http://texy.info * * @copyright Copyright (c) 2004, 2008 David Grudl * @license GNU GENERAL PUBLIC LICENSE version 2 or 3 * @link http://texy.info * @package Texy * @version $Id: TexyHtml.php 218 2008-09-05 05:35:57Z David Grudl $ */ /** * HTML helper. * * usage: * $anchor = TexyHtml::el('a')->href($link)->setText('Texy'); * $el->class = 'myclass'; * * echo $el->startTag(), $el->endTag(); * * @property mixed element's attributes * @author David Grudl * @copyright Copyright (c) 2004, 2008 David Grudl * @package Texy */ class TexyHtml extends TexyObject implements ArrayAccess, /* Countable, */ IteratorAggregate { /** @var string element's name */ private $name; /** @var bool is element empty? */ private $isEmpty; /** @var array element's attributes */ public $attrs = array(); /** @var array of TexyHtml | string nodes */ protected $children = array(); /** @var bool use XHTML syntax? */ public static $xhtml = TRUE; /** @var array empty elements */ public static $emptyElements = array('img'=>1,'hr'=>1,'br'=>1,'input'=>1,'meta'=>1,'area'=>1, 'base'=>1,'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'embed'=>1); /** @var array %inline; elements; replaced elements + br have value '1' */ public static $inlineElements = array('ins'=>0,'del'=>0,'tt'=>0,'i'=>0,'b'=>0,'big'=>0,'small'=>0,'em'=>0, 'strong'=>0,'dfn'=>0,'code'=>0,'samp'=>0,'kbd'=>0,'var'=>0,'cite'=>0,'abbr'=>0,'acronym'=>0, 'sub'=>0,'sup'=>0,'q'=>0,'span'=>0,'bdo'=>0,'a'=>0,'object'=>1,'img'=>1,'br'=>1,'script'=>1, 'map'=>0,'input'=>1,'select'=>1,'textarea'=>1,'label'=>0,'button'=>1, 'u'=>0,'s'=>0,'strike'=>0,'font'=>0,'applet'=>1,'basefont'=>0, // transitional 'embed'=>1,'wbr'=>0,'nobr'=>0,'canvas'=>1, // proprietary ); /** @var array elements with optional end tag in HTML */ public static $optionalEnds = array('body'=>1,'head'=>1,'html'=>1,'colgroup'=>1,'dd'=>1, 'dt'=>1,'li'=>1,'option'=>1,'p'=>1,'tbody'=>1,'td'=>1,'tfoot'=>1,'th'=>1,'thead'=>1,'tr'=>1); /** @see http://www.w3.org/TR/xhtml1/prohibitions.html */ public static $prohibits = array( 'a' => array('a','button'), 'img' => array('pre'), 'object' => array('pre'), 'big' => array('pre'), 'small' => array('pre'), 'sub' => array('pre'), 'sup' => array('pre'), 'input' => array('button'), 'select' => array('button'), 'textarea' => array('button'), 'label' => array('button', 'label'), 'button' => array('button'), 'form' => array('button', 'form'), 'fieldset' => array('button'), 'iframe' => array('button'), 'isindex' => array('button'), ); /** * Static factory. * @param string element name (or NULL) * @param array|string element's attributes (or textual content) * @return TexyHtml */ public static function el($name = NULL, $attrs = NULL) { $el = new self; $el->setName($name); if (is_array($attrs)) { $el->attrs = $attrs; } elseif ($attrs !== NULL) { $el->setText($attrs); } return $el; } /** * Changes element's name. * @param string * @param bool Is element empty? * @return TexyHtml provides a fluent interface * @throws InvalidArgumentException */ final public function setName($name, $empty = NULL) { if ($name !== NULL && !is_string($name)) { throw new InvalidArgumentException("Name must be string or NULL."); } $this->name = $name; $this->isEmpty = $empty === NULL ? isset(self::$emptyElements[$name]) : (bool) $empty; return $this; } /** * Returns element's name. * @return string */ final public function getName() { return $this->name; } /** * Is element empty? * @return bool */ final public function isEmpty() { return $this->isEmpty; } /** * Overloaded setter for element's attribute. * @param string property name * @param mixed property value * @return void */ final public function __set($name, $value) { $this->attrs[$name] = $value; } /** * Overloaded getter for element's attribute. * @param string property name * @return mixed property value */ final public function &__get($name) { return $this->attrs[$name]; } /** * Overloaded setter for element's attribute. * @param string attribute name * @param array value * @return TexyHtml provides a fluent interface */ /* final public function __call($m, $args) { if (count($args) !== 1) { throw new InvalidArgumentException("Just one argument is required."); } $this->attrs[$m] = $args[0]; return $this; } */ /** * Special setter for element's attribute. * @param string path * @param array query * @return TexyHtml provides a fluent interface */ final public function href($path, $query = NULL) { if ($query) { $query = http_build_query($query, NULL, '&'); if ($query !== '') $path .= '?' . $query; } $this->attrs['href'] = $path; return $this; } /** * Sets element's textual content. * @param string * @return TexyHtml provides a fluent interface */ final public function setText($text) { if (is_scalar($text)) { $this->removeChildren(); $this->children = array($text); } elseif ($text !== NULL) { throw new InvalidArgumentException('Content must be scalar.'); } return $this; } /** * Gets element's textual content. * @return string */ final public function getText() { $s = ''; foreach ($this->children as $child) { if (is_object($child)) return FALSE; $s .= $child; } return $s; } /** * Adds new element's child. * @param TexyHtml|string child node * @return TexyHtml provides a fluent interface */ final public function add($child) { return $this->insert(NULL, $child); } /** * Creates and adds a new TexyHtml child. * @param string elements's name * @param array|string element's attributes (or textual content) * @return TexyHtml created element */ final public function create($name, $attrs = NULL) { $this->insert(NULL, $child = self::el($name, $attrs)); return $child; } /** * Inserts child node. * @param int * @param TexyHtml node * @param bool * @return TexyHtml provides a fluent interface * @throws Exception */ public function insert($index, $child, $replace = FALSE) { if ($child instanceof TexyHtml || is_string($child)) { if ($index === NULL) { // append $this->children[] = $child; } else { // insert or replace array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child)); } } else { throw new /*::*/InvalidArgumentException('Child node must be scalar or TexyHtml object.'); } return $this; } /** * Inserts (replaces) child node (ArrayAccess implementation). * @param int * @param TexyHtml node * @return void */ final public function offsetSet($index, $child) { $this->insert($index, $child, TRUE); } /** * Returns child node (ArrayAccess implementation). * @param int index * @return mixed */ final public function offsetGet($index) { return $this->children[$index]; } /** * Exists child node? (ArrayAccess implementation). * @param int index * @return bool */ final public function offsetExists($index) { return isset($this->children[$index]); } /** * Removes child node (ArrayAccess implementation). * @param int index * @return void */ public function offsetUnset($index) { if (isset($this->children[$index])) { array_splice($this->children, (int) $index, 1); } } /** * Required by the Countable interface. * @return int */ final public function count() { return count($this->children); } /** * Removed all children. * @return void */ public function removeChildren() { $this->children = array(); } /** * Required by the IteratorAggregate interface. * @return ArrayIterator */ final public function getIterator() { return new ArrayIterator($this->children); } /** * Returns all of children. * return array */ final public function getChildren() { return $this->children; } /** * Renders element's start tag, content and end tag to internal string representation. * @param Texy * @return string */ final public function toString(Texy $texy) { $ct = $this->getContentType(); $s = $texy->protect($this->startTag(), $ct); // empty elements are finished now if ($this->isEmpty) { return $s; } // add content foreach ($this->children as $child) { if (is_object($child)) { $s .= $child->toString($texy); } else { $s .= $child; } } // add end tag return $s . $texy->protect($this->endTag(), $ct); } /** * Renders to final HTML. * @param Texy * @return string */ final public function toHtml(Texy $texy) { return $texy->stringToHtml($this->toString($texy)); } /** * Renders to final text. * @param Texy * @return string */ final public function toText(Texy $texy) { return $texy->stringToText($this->toString($texy)); } /** * Returns element's start tag. * @return string */ public function startTag() { if (!$this->name) { return ''; } $s = '<' . $this->name; if (is_array($this->attrs)) { foreach ($this->attrs as $key => $value) { // skip NULLs and false boolean attributes if ($value === NULL || $value === FALSE) continue; // true boolean attribute if ($value === TRUE) { // in XHTML must use unminimized form if (self::$xhtml) $s .= ' ' . $key . '="' . $key . '"'; // in HTML should use minimized form else $s .= ' ' . $key; continue; } elseif (is_array($value)) { // prepare into temporary array $tmp = NULL; foreach ($value as $k => $v) { // skip NULLs & empty string; composite 'style' vs. 'others' if ($v == NULL) continue; if (is_string($k)) $tmp[] = $k . ':' . $v; else $tmp[] = $v; } if (!$tmp) continue; $value = implode($key === 'style' ? ';' : ' ', $tmp); } else { $value = (string) $value; } // add new attribute $value = str_replace(array('&', '"', '<', '>', '@'), array('&', '"', '<', '>', '@'), $value); $s .= ' ' . $key . '="' . Texy::freezeSpaces($value) . '"'; } } // finish start tag if (self::$xhtml && $this->isEmpty) { return $s . ' />'; } return $s . '>'; } /** * Returns element's end tag. * @return string */ public function endTag() { if ($this->name && !$this->isEmpty) { return '</' . $this->name . '>'; } return ''; } /** * Clones all children too. */ public function __clone() { foreach ($this->children as $key => $value) { if (is_object($value)) { $this->children[$key] = clone $value; } } } /** * @return int */ final public function getContentType() { if (!isset(self::$inlineElements[$this->name])) return Texy::CONTENT_BLOCK; return self::$inlineElements[$this->name] ? Texy::CONTENT_REPLACED : Texy::CONTENT_MARKUP; } /** * @param array * @return void */ final public function validateAttrs($dtd) { if (isset($dtd[$this->name])) { $allowed = $dtd[$this->name][0]; if (is_array($allowed)) { foreach ($this->attrs as $attr => $foo) { if (!isset($allowed[$attr])) unset($this->attrs[$attr]); } } } } public function validateChild($child, $dtd) { if (isset($dtd[$this->name])) { if ($child instanceof TexyHtml) $child = $child->name; return isset($dtd[$this->name][1][$child]); } else { return TRUE; // unknown element } } /** * Parses text as single line. * @param Texy * @param string * @return void */ final public function parseLine(Texy $texy, $s) { // TODO! // special escape sequences $s = str_replace(array('\)', '\*'), array(')', '*'), $s); $parser = new TexyLineParser($texy, $this); $parser->parse($s); return $parser; } /** * Parses text as block. * @param Texy * @param string * @param bool * @return void */ final public function parseBlock(Texy $texy, $s, $indented = FALSE) { $parser = new TexyBlockParser($texy, $this, $indented); $parser->parse($s); } }