Location: PHPKode > scripts > HoinP > hoinp/hop_element.php
«Copyright 2007 Max Barel hide@address.com»
Release : 0.4 ($Rev: 47 $)
$Date: 2007-09-09 15:43:54 +0200 (Dim, 09 sep 2007) $

This file is part of HoP.

HoP is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

HoP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with HoP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

//XHTML element.
class Hop_element extends Hop_node {
	const INDENT = "\t";		//define the indentation string
	protected static $n_el = 0;	//element count in the pool added for time optimization
	public static $pool;		//a "flat" container object for all Hop_elements.
	//Use it to direct access to an object : Hop_element::$pool->object_id
	//See also the commodity function in Hop class
	//mandatory properties values
	public $type;				//string, element type eg. 'div','p'
	public $id;				//id of this object, automatic generation if not given
	//optional properties
	public $attributes = null;	//stdclass object: html attributes container
	public $auto_id = false;	//should 'id' html attribute be reflecting the object id
	public $auto_name = false;	//should 'name' html attribute be reflecting the object id (eg. for input element)
	public $empty_el = false;	//is this element type empty (otional empty should be set to false)
	public $nl = 1;				//newlines when rendering: 0 for inline (e.g. <span>),
								// 1 for newline before the opening tag (one line elements: <p> or <legend>),
								// 2 for newline before the closing tag (container elements :  <body>, <div>)

	public static $comment_tag = true;	//optional comment at closing tag for readability. Global to the whole rendering
	function __construct($type, $options = null) {
		$this->type = $type; // set the element type
		//process options
		if (is_string($options)) $options = array('id' => $options); // a string option default to the object id
		elseif ($options and !is_object($options)) $options = (object) $options; //cast to object for uniform processing
		if (isset($options->content) ) {
			//content should be handled by setter for correct parent linking
			unset ($options->content);

		if ($options) foreach ($options as $k => $v) $this->$k = $v; //make other options properies of the element
		//if (isset($this->$k)) 
		//cast attributes to object for easy access (might be array or simple string)
		if ($this->attributes and !is_object($this->attributes)) $this->attributes = (object) $this->attributes;
		// if ($this->attributes and !is_object($this->attributes)) settype($this->attributes, 'object');

		//if no id (from options) generate one, for the safeness of the pool. The element will then be difficult to address.
		if (!$this->id or !is_string($this->id)) $this->id = $type;
		self::$n_el ++;
		if (isset(self::$pool->{$this->id})) $this->id = "{$type}_" . self::$n_el;
		//add self to the pool
		self::$pool->{$this->id} = $this; 
	function __clone() {
		parent::__clone(); //called before renaming for easier access by name : $parent->child
		self::$n_el ++;
		$this->id = "{$this->id}_clone_" . self::$n_el;
		self::$pool->{$this->id} = $this;
		if ($this->attributes) $this->attributes = clone $this->attributes;

	protected function set_container($node) {
		//redefined to add a reverse link from parent to child through an id named property
		parent::set_container($node); //=> $this->container = $node;
		// if (is_a($node, __CLASS__))
		$node->{$this->id} = $this; //cannot be done at node level becaus nodes have no id
	function cut() {
		return parent::cut();
	//rendering function.
	function render($indent = '') {
		$html_string = '';
		if ($this->auto_id and empty($this->attributes->id) ) $this->set_attr('id', $this->id);
		if ($this->auto_name and empty($this->attributes->name) ) $this->set_attr('name', $this->id);
		if ($this->nl) $html_string .= "\n$indent";
		$html_string .= "<$this->type";
		if ($this->attributes) {
			//cast in case it has been set to a string
			if (!is_object($this->attributes)) $this->attributes = (object) $this->attributes;
			foreach ($this->attributes as $k => $value) {
				if ($k == 'raw' or $k == 'scalar') $html_string .= " $value"; //for raw formatted attributes
				else $html_string .= " $k=\"$value\"";
		if ($this->empty_el) {
			$html_string .= ' />';
		} else {
			$html_string .= '>';
			$html_string .=  parent::render(self::INDENT . $indent);
			if ($this->nl > 1) $html_string .= "\n$indent";
			$html_string .= "</$this->type>";
			//optional, for code documentation
			if ($this->nl > 1 and self::$comment_tag and isset($this->attributes->id)) $html_string .= "<!-- {$this->attributes->id} -->";
		return $html_string;

	//properties setter
	function auto_id($val=true) { $this->auto_id = $val; return $this; }
	function auto_name($val=true) { $this->auto_name = $val; return $this; }
	function empty_el($val=true) { $this->empty_el = $val; return $this; }
	function nl($val=1) { $this->nl = $val; return $this; }
	//commodity function to set an attribute
	function set_attr($attr, $value=null) {
		$this->attributes->$attr = htmlspecialchars($value); //htmlspecialchars htmlentities
		return $this;
	//commodity function to get an attribute
	function get_attr($attr) {
		return @$this->attributes->$attr;
	//remove an attribute
	function remove_attr($attr) {
		unset ($this->attributes->$attr);
		return $this;

	//generic attibute setter
	private function __call($setter, $params) {
		if (ereg('(set|get|remove)_(.*)', $setter, $e_res)) {
			switch ($e_res[1]) {
				case 'set': return $this->set_attr($e_res[2], $params[0]);
				case 'get': return $this->get_attr($e_res[2]);
				case 'remove': return $this->remove_attr($e_res[2]);
		} else throw new Exception("invalide function call : $setter");

	//specific class methods
	public function add_class($class = null) {
		if ($class) $this->set_attr('class', $this->get_attr('class') . " $class");
		return $this;
	public function remove_class($class = null) {
		if ($class) $this->set_attr('class', trim(ereg_replace(" *$class", '', $this->get_attr('class'))));
		return $this;
Return current item: HoinP