Public feed #599413

Callback and udata references pr

public php v1 · immutable
Raw Edit
·published 2007-07-02 05:03 UTC
rendered paste body
<?/* * Put in the public domain by Matthew Mondor in 2007. *//* * Simple HTML 4.01 parsing class for PHP *//* * TODO (see XXX comments) * - Optimize * - Fix the non-working "function pointers/references" mess [1] and [2] * - Fix udata which for some reason doesn't work properly [3] */class HTMLParser{	private $str = '';	private $str_len = 0;	private $str_i = 0;	private $callback = NULL;	private $udata = NULL;	/* XXX [3] */	private $state = 0;	private $tag = NULL;	private $tag_len = 0;	private $attribute = NULL;	private $attributes = array();	private $last_free = NULL;	/* XXX [3] */	public function HTMLParser($str, $function, &$udata)	{		$this->str = $str;		$this->str_len = strlen($str);		$this->callback = $function;		$this->udata = $udata;	}	public function parse()	{		$this->state = 0;		for ($this->str_i = 0; $this->str_i < $this->str_len; ) {			/*			 * XXX [3]			 * PHP can't even handle pure references			 * to functions, and we don't want it to deal with			 * strings evaluation at a great performance loss.			 * if (($x = $this->state()) !== NULL)		 	 * 	return $x;			 * Apparently the choice to use functions/methods for			 * the state would have been a good performance choice			 * with JavaScript but not with PHP.			 */			switch ($this->state) {			case 0:				$x = $this->state_0();				break;			case 1:				$x = $this->state_1();				break;			case 2:				$x = $this->state_2();				break;			case 3:				$x = $this->state_3();				break;			case 4:				$x = $this->state_4();				break;			case 5:				$x = $this->state_5();				break;			}			if ($x !== NULL)				return $x;		}		/* Send any remaining data */		if ($this->last_free < $this->str_i) {			$s = trim(substr($this->str, $this->last_free,			    $this->str_i - $this->last_free));			if (strlen($s) > 0) {				$o = array(				    'type' => 'content',				    'content' => $s);				/*				 * XXX [2] [3]				 * Would ideally be like:				 * ... $this->callback(...) ...				 * but it doesn't work.				 * We must call the user-provided callback				 * function and must pass to it the persistent				 * udata object it provided.				 */				if (($r = po_callback($o, $this->udata))				    != NULL)					return $r;			}		}		/* NOTREACHED */		return NULL;	}	public function dbg($n)	{		print("DBG{$n}: state = {$this->state}, pos = {$this->str_i}, " .		    "len = {$this->str_len}\n");	}	private function state_0()	{		$this->last_free = $this->str_i;		/* Free-run until '<', switch to state 1 if found */		for (; $this->str_i < $this->str_len; $this->str_i++) {			if ($this->str{$this->str_i} == '<') {				if ($this->last_free < $this->str_i) {					$s = trim(substr($this->str,					    $this->last_free, $this->str_i -					    $this->last_free));					if (strlen($s) > 0) {						$o = array(						    'type' => 'content',						    'content' => $s);						/* XXX [2] [3] */						if (($r = po_callback($o,						    $this->udata)) != NULL)							return $r;					}				}				$this->state = 1;				return NULL;			}		}		return NULL;	}	private function state_1()	{		/*		 * Skip comment if any;		 * error if reaching EOF before comment ends		 */		if ($this->str_i <= $this->str_len - 4 &&		    ($i = strpos($this->str, '<!--', $this->str_i)) !== FALSE		    && $i == $this->str_i) {			/* Was a comment, switch back to state 0 */			$this->str_i += 4;			if (($i = strpos($this->str, '-->', $this->str_i))			    === FALSE)				return "Expected '-->' but reached EOF";			$this->str_i = $i + 3;			$this->state = 0;			return NULL;		}		/* Not a comment, switch to state 2 */		$this->state = 2;		return NULL;	}	private function state_2()	{		/* Tag must start with '<' */		if ($this->str{$this->str_i} != '<')			return "Expected '<' at position {$this->str_i}";		/* Tag start, switch to state 3 */		$this->str_i++;		$this->state = 3;		return NULL;	}	private function state_3()	{		/* Load tag name */		for ($this->tag = '', $this->tag_len = 0;		    $this->str_i < $this->str_len &&		    !ctype_space($this->str{$this->str_i}) &&		    $this->str{$this->str_i} != '>';		    $this->str_i++, $this->tag_len++)			$this->tag .= $this->str{$this->str_i};		if ($this->tag_len == 0)			return "Expected tag name at position {$this->str_i}";		/* Non-empty tag name, switch to state 4 */		$this->attributes = array();		$this->state = 4;		return NULL;	}	private function state_4()	{		/* Skip whitespaces */		for (; $this->str_i < $this->str_len &&		     ctype_space($this->str{$this->str_i}); $this->str_i++) ;		/*		 * Upon end of tag, call the user function with the tag		 * and switch to state 0.		 */		if ($this->str{$this->str_i} == '>') {			$this->str_i++;			$o = array(			    'type' => 'tag',			    'tag' => array(			    	'name' => strtolower($this->tag),				'attributes' => $this->attributes));			/* XXX [2] [3] */			if (($r = po_callback($o, $this->udata)) != NULL)				return $r;			$this->state = 0;			return NULL;		}		/* Load attribute name */		for ($this->attribute = '';		    $this->str_i < $this->str_len &&		    !ctype_space($this->str{$this->str_i}) &&		    $this->str{$this->str_i} != '>';		    $this->str_i++)			$this->attribute .= $this->str{$this->str_i};		if (strlen($this->attribute) == 0)			return 'Expected attribute name at position ' .			 "{$this->str_i}";		/* Skip whitespaces */		for (; $this->str_i < $this->str_len &&		     ctype_space($this->str{$this->str_i}); $this->str_i++) ;		/*		 * Nonempty attribute name, is it assigned a value?		 * Switch to state 4 if empty, or 5 to assign it.		 */		if ($this->str{$this->str_i} != '=') {			$this->attributes[strtolower($this->attribute)] = NULL;			$this->state = 4;			return NULL;		}		$this->str_i++;		$this->state = 5;		return NULL;	}	private function state_5()	{		/* Skip whitespaces */		for (; $this->str_i < $this->str_len &&		     ctype_space($this->str{$this->str_i}); $this->str_i++) ;		/* Assignment must start with '"' */		if ($this->str{$this->str_i} != '"')			return "Expected '\"' at position {$this->str_i}";		$this->str_i++;		/*		 * Find ending '"', error if EOF;  Assign attribute and switch		 * back to state 4.		 */		if (($i = strpos($this->str, '"', $this->str_i)) === FALSE) {			--$this->str_i;			return "Expected closing '\"' to match position " .			    "{$this->str_i} but reached EOF";		}		if ($i > $this->str_i) {			$this->attributes[strtolower($this->attribute)] =			    substr($this->str, $this->str_i,			    ($i - $this->str_i));			$this->str_i = $i + 1;		} else {			$this->attributes[strtolower($this->attribute)] = NULL;			$this->str_i++;		}		$this->state = 4;		return NULL;	}}/* * Test the parser *//* XXX [2] [3] */function po_callback(&$o, &$udata){	print_r($o);	/*	if ($o['type'] == 'content')		print($o['content']);	else {		print("<{$o['tag']['name']}");		$a = $o['tag']['attributes'];		foreach($a as $k => $v) {			if ($v != NULL)				print(" {$k}=\"{$v}\"");			else				print(" {$k}");		}		print('>');		$udata['tags']++;	}	*/	$udata['tags']++;	return NULL;}/* * XXX [3] * Interestingly, the user data object does not appear to properly be * reference-passed in a way that its contents remain consistent accross * calls.  We attempt to pass a reference to it to the HTMLParser system. * This also appears to work fine with JavaScript automatically (as objects * are passed by reference)... */$p_udata = array('tags' => 0);/* XXX [2] [3] */$po = new HTMLParser(file_get_contents('bookmarks.html'), 'po_callback',    $p_udata);print($po->parse() . "\n");/* XXX [3] */print("Tags: {$p_udata['tags']}\n");?>