Public feed
#599413
<?/* * 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");?>