Download res/simple_html_dom.php from druvx13/ultimate-manga-scraper: direct link, hf CLI and curl.
- Browser
- Download file 65.7 kB
-
https://huggingface.co/druvx13/ultimate-manga-scraper/resolve/main/res/simple_html_dom.php
- Command line
-
hf download hf://druvx13/ultimate-manga-scraper/res/simple_html_dom.php
-
curl -L -o simple_html_dom.php https://huggingface.co/druvx13/ultimate-manga-scraper/resolve/main/res/simple_html_dom.php
65.7 kB
| /** | |
| * Website: http://sourceforge.net/projects/simplehtmldom/ | |
| * Additional projects that may be used: http://sourceforge.net/projects/debugobject/ | |
| * Acknowledge: Jose Solorzano (https://sourceforge.net/projects/php-html/) | |
| * Contributions by: | |
| * Yousuke Kumakura (Attribute filters) | |
| * Vadim Voituk (Negative indexes supports of "find" method) | |
| * Antcs (Constructor with automatically load contents either text or file/url) | |
| * | |
| * all affected sections have comments starting with "PaperG" | |
| * | |
| * Paperg - Added case insensitive testing of the value of the selector. | |
| * Paperg - Added tag_start for the starting index of tags - NOTE: This works but not accurately. | |
| * This tag_start gets counted AFTER \r\n have been crushed out, and after the remove_noice calls so it will not reflect the REAL position of the tag in the source, | |
| * it will almost always be smaller by some amount. | |
| * We use this to determine how far into the file the tag in question is. This "percentage will never be accurate as the $dom->size is the "real" number of bytes the dom was created from. | |
| * but for most purposes, it's a really good estimation. | |
| * Paperg - Added the forceTagsClosed to the dom constructor. Forcing tags closed is great for malformed html, but it CAN lead to parsing errors. | |
| * Allow the user to tell us how much they trust the html. | |
| * Paperg add the text and plaintext to the selectors for the find syntax. plaintext implies text in the innertext of a node. text implies that the tag is a text node. | |
| * This allows for us to find tags based on the text they contain. | |
| * Create find_ancestor_tag to see if a tag is - at any level - inside of another specific tag. | |
| * Paperg: added parse_charset so that we know about the character set of the source document. | |
| * NOTE: If the user's system has a routine called get_last_retrieve_url_contents_content_type availalbe, we will assume it's returning the content-type header from the | |
| * last transfer or curl_exec, and we will parse that and use it in preference to any other method of charset detection. | |
| * | |
| * Found infinite loop in the case of broken html in restore_noise. Rewrote to protect from that. | |
| * PaperG (John Schlick) Added get_display_size for "IMG" tags. | |
| * | |
| * Licensed under The MIT License | |
| * Redistributions of files must retain the above copyright notice. | |
| * | |
| * @author S.C. Chen <me578022@gmail.com> | |
| * @author John Schlick | |
| * @author Rus Carroll | |
| * @version Rev. 1.7 (214) | |
| * @package PlaceLocalInclude | |
| * @subpackage ums_simple_html_dom | |
| */ | |
| /** | |
| * All of the Defines for the classes below. | |
| * @author S.C. Chen <me578022@gmail.com> | |
| */ | |
| define('HDOM_TYPE_ELEMENT', 1); | |
| define('HDOM_TYPE_COMMENT', 2); | |
| define('HDOM_TYPE_TEXT', 3); | |
| define('HDOM_TYPE_ENDTAG', 4); | |
| define('HDOM_TYPE_ROOT', 5); | |
| define('HDOM_TYPE_UNKNOWN', 6); | |
| define('HDOM_QUOTE_DOUBLE', 0); | |
| define('HDOM_QUOTE_SINGLE', 1); | |
| define('HDOM_QUOTE_NO', 3); | |
| define('HDOM_INFO_BEGIN', 0); | |
| define('HDOM_INFO_END', 1); | |
| define('HDOM_INFO_QUOTE', 2); | |
| define('HDOM_INFO_SPACE', 3); | |
| define('HDOM_INFO_TEXT', 4); | |
| define('HDOM_INFO_INNER', 5); | |
| define('HDOM_INFO_OUTER', 6); | |
| define('HDOM_INFO_ENDSPACE',7); | |
| define('DEFAULT_TARGET_CHARSET', 'UTF-8'); | |
| define('DEFAULT_BR_TEXT', "\r\n"); | |
| define('DEFAULT_SPAN_TEXT', " "); | |
| define('MAX_FILE_SIZE', 10485760); | |
| /** Contents between curly braces "{" and "}" are interpreted as text */ | |
| define('HDOM_SMARTY_AS_TEXT', 1); | |
| // helper functions | |
| // ----------------------------------------------------------------------------- | |
| // get html dom from file | |
| // $maxlen is defined in the code as PHP_STREAM_COPY_ALL which is defined as -1. | |
| function ums_file_get_html($url, $use_include_path = false, $context=null, $offset = 0, $maxLen=-1, $lowercase = true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT) | |
| { | |
| // Ensure maximum length is greater than zero | |
| if($maxLen <= 0) { $maxLen = MAX_FILE_SIZE; } | |
| // We DO force the tags to be terminated. | |
| $dom = new ums_simple_html_dom(null, $lowercase, $forceTagsClosed, $target_charset, $stripRN, $defaultBRText, $defaultSpanText); | |
| // For sourceforge users: uncomment the next line and comment the retrieve_url_contents line 2 lines down if it is not already done. | |
| global $wp_filesystem; | |
| if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base') ){ | |
| include_once(ABSPATH . 'wp-admin/includes/file.php');$creds = request_filesystem_credentials( site_url() ); | |
| wp_filesystem($creds); | |
| } | |
| $contents = $wp_filesystem->get_contents($url); | |
| // Paperg - use our own mechanism for getting the contents as we want to control the timeout. | |
| if (empty($contents) || strlen($contents) > $maxLen) | |
| { | |
| return false; | |
| } | |
| // The second parameter can force the selectors to all be lowercase. | |
| $dom->load($contents, $lowercase, $stripRN); | |
| return $dom; | |
| } | |
| // get html dom from string | |
| function ums_str_get_html($str, $lowercase=true, $forceTagsClosed=true, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT) | |
| { | |
| $dom = new ums_simple_html_dom(null, $lowercase, $forceTagsClosed, $target_charset, $stripRN, $defaultBRText, $defaultSpanText); | |
| if (empty($str) || strlen($str) > MAX_FILE_SIZE) | |
| { | |
| $dom->clear(); | |
| return false; | |
| } | |
| $dom->load($str, $lowercase, $stripRN); | |
| return $dom; | |
| } | |
| // dump html dom tree | |
| function ums_dump_html_tree($node, $show_attr=true, $deep=0) | |
| { | |
| $node->dump($node); | |
| } | |
| /** | |
| * simple html dom node | |
| * PaperG - added ability for "find" routine to lowercase the value of the selector. | |
| * PaperG - added $tag_start to track the start position of the tag in the total byte index | |
| * | |
| * @package PlaceLocalInclude | |
| */ | |
| class ums_simple_html_dom_node | |
| { | |
| /** | |
| * Node type | |
| * | |
| * Default is {@see HDOM_TYPE_TEXT} | |
| * | |
| * @var int | |
| */ | |
| public $nodetype = HDOM_TYPE_TEXT; | |
| /** | |
| * Tag name | |
| * | |
| * Default is 'text' | |
| * | |
| * @var string | |
| */ | |
| public $tag = 'text'; | |
| /** | |
| * List of attributes | |
| * | |
| * @var array | |
| */ | |
| public $attr = array(); | |
| /** | |
| * List of child node objects | |
| * | |
| * @var array | |
| */ | |
| public $children = array(); | |
| public $nodes = array(); | |
| /** | |
| * The parent node object | |
| * | |
| * @var object|null | |
| */ | |
| public $parent = null; | |
| // The "info" array - see HDOM_INFO_... for what each element contains. | |
| public $_ = array(); | |
| /** | |
| * Start position of the tag in the document | |
| * | |
| * @var int | |
| */ | |
| public $tag_start = 0; | |
| /** | |
| * The DOM object | |
| * | |
| * @var object|null | |
| */ | |
| private $dom = null; | |
| /** | |
| * Construct new node object | |
| * | |
| * Adds itself to the list of DOM Nodes {@see ums_simple_html_dom::$nodes} | |
| */ | |
| function __construct($dom) | |
| { | |
| $this->dom = $dom; | |
| $dom->nodes[] = $this; | |
| } | |
| function __destruct() | |
| { | |
| $this->clear(); | |
| } | |
| function __toString() | |
| { | |
| return $this->outertext(); | |
| } | |
| // clean up memory due to php5 circular references memory leak... | |
| function clear() | |
| { | |
| $this->dom = null; | |
| $this->nodes = null; | |
| $this->parent = null; | |
| $this->children = null; | |
| } | |
| // dump node's tree | |
| function dump($show_attr=true, $deep=0) | |
| { | |
| $lead = str_repeat(' ', $deep); | |
| echo $lead.$this->tag; | |
| if ($show_attr && count($this->attr)>0) | |
| { | |
| echo '('; | |
| foreach ($this->attr as $k=>$v) | |
| echo "[$k]=>\"".$this->$k.'", '; | |
| echo ')'; | |
| } | |
| echo "\n"; | |
| if ($this->nodes) | |
| { | |
| foreach ($this->nodes as $c) | |
| { | |
| $c->dump($show_attr, $deep+1); | |
| } | |
| } | |
| } | |
| // Debugging function to dump a single dom node with a bunch of information about it. | |
| function dump_node($echo=true) | |
| { | |
| $string = $this->tag; | |
| if (count($this->attr)>0) | |
| { | |
| $string .= '('; | |
| foreach ($this->attr as $k=>$v) | |
| { | |
| $string .= "[$k]=>\"".$this->$k.'", '; | |
| } | |
| $string .= ')'; | |
| } | |
| if (count($this->_)>0) | |
| { | |
| $string .= ' $_ ('; | |
| foreach ($this->_ as $k=>$v) | |
| { | |
| if (is_array($v)) | |
| { | |
| $string .= "[$k]=>("; | |
| foreach ($v as $k2=>$v2) | |
| { | |
| $string .= "[$k2]=>\"".$v2.'", '; | |
| } | |
| $string .= ")"; | |
| } else { | |
| $string .= "[$k]=>\"".$v.'", '; | |
| } | |
| } | |
| $string .= ")"; | |
| } | |
| if (isset($this->text)) | |
| { | |
| $string .= " text: (" . $this->text . ")"; | |
| } | |
| $string .= " HDOM_INNER_INFO: '"; | |
| if (isset($node->_[HDOM_INFO_INNER])) | |
| { | |
| $string .= $node->_[HDOM_INFO_INNER] . "'"; | |
| } | |
| else | |
| { | |
| $string .= ' NULL '; | |
| } | |
| $string .= " children: " . count($this->children); | |
| $string .= " nodes: " . count($this->nodes); | |
| $string .= " tag_start: " . $this->tag_start; | |
| $string .= "\n"; | |
| if ($echo) | |
| { | |
| echo $string; | |
| return; | |
| } | |
| else | |
| { | |
| return $string; | |
| } | |
| } | |
| /** | |
| * Return or set parent node | |
| * | |
| * @param object|null $parent (optional) The parent node, `null` to return | |
| * the current parent node. | |
| * @return object|null The parent node | |
| */ | |
| function parent($parent=null) | |
| { | |
| // I am SURE that this doesn't work properly. | |
| // It fails to unset the current node from it's current parents nodes or children list first. | |
| if ($parent !== null) | |
| { | |
| $this->parent = $parent; | |
| $this->parent->nodes[] = $this; | |
| $this->parent->children[] = $this; | |
| } | |
| return $this->parent; | |
| } | |
| /** | |
| * @return bool True if the node has at least one child node | |
| */ | |
| function has_child() | |
| { | |
| return !empty($this->children); | |
| } | |
| /** | |
| * Get child node at specified index | |
| * | |
| * @param int $idx The index of the child node to return, `-1` to return all | |
| * child nodes. | |
| * @return object|array|null The child node at the specified index, all child | |
| * nodes or null if the index is invalid. | |
| */ | |
| function children($idx=-1) | |
| { | |
| if ($idx===-1) | |
| { | |
| return $this->children; | |
| } | |
| if (isset($this->children[$idx])) | |
| { | |
| return $this->children[$idx]; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Get first child node | |
| * | |
| * @return object|null The first child node or null if the current node has | |
| * no child nodes. | |
| * | |
| * @todo Use `empty()` instead of `count()` to improve performance on large | |
| * arrays. | |
| */ | |
| function first_child() | |
| { | |
| if (count($this->children)>0) | |
| { | |
| return $this->children[0]; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Get last child node | |
| * | |
| * @return object|null The last child node or null if the current node has | |
| * no child nodes. | |
| * | |
| * @todo Use `end()` to slightly improve performance on large arrays. | |
| */ | |
| function last_child() | |
| { | |
| if (($count=count($this->children))>0) | |
| { | |
| return $this->children[$count-1]; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Get next sibling node | |
| * | |
| * @return object|null The sibling node or null if the current node has no | |
| * sibling nodes. | |
| */ | |
| function next_sibling() | |
| { | |
| if ($this->parent===null) | |
| { | |
| return null; | |
| } | |
| $idx = 0; | |
| $count = count($this->parent->children); | |
| while ($idx<$count && $this!==$this->parent->children[$idx]) | |
| { | |
| ++$idx; | |
| } | |
| if (++$idx>=$count) | |
| { | |
| return null; | |
| } | |
| return $this->parent->children[$idx]; | |
| } | |
| /** | |
| * Get previous sibling node | |
| * | |
| * @return object|null The sibling node or null if the current node has no | |
| * sibling nodes. | |
| */ | |
| function prev_sibling() | |
| { | |
| if ($this->parent===null) return null; | |
| $idx = 0; | |
| $count = count($this->parent->children); | |
| while ($idx<$count && $this!==$this->parent->children[$idx]) | |
| ++$idx; | |
| if (--$idx<0) return null; | |
| return $this->parent->children[$idx]; | |
| } | |
| /** | |
| * Traverse ancestors to the first matching tag. | |
| * | |
| * @param string $tag Tag to find | |
| * @return object|null First matching node in the DOM tree or null if no | |
| * match was found. | |
| * | |
| * @todo Null is returned implicitly by calling ->parent on the root node. | |
| * This behaviour could change at any time, rendering this function invalid. | |
| */ | |
| function find_ancestor_tag($tag) | |
| { | |
| global $debug_object; | |
| if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } | |
| // Start by including ourselves in the comparison. | |
| $returnDom = $this; | |
| while (!is_null($returnDom)) | |
| { | |
| if (is_object($debug_object)) { $debug_object->debug_log(2, "Current tag is: " . $returnDom->tag); } | |
| if ($returnDom->tag == $tag) | |
| { | |
| break; | |
| } | |
| $returnDom = $returnDom->parent; | |
| } | |
| return $returnDom; | |
| } | |
| /** | |
| * Get node's inner text (everything inside the opening and closing tags) | |
| * | |
| * @return string | |
| */ | |
| function innertext() | |
| { | |
| if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER]; | |
| if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); | |
| $ret = ''; | |
| foreach ($this->nodes as $n) | |
| $ret .= $n->outertext(); | |
| return $ret; | |
| } | |
| /** | |
| * Get node's outer text (everything including the opening and closing tags) | |
| * | |
| * @return string | |
| */ | |
| function outertext() | |
| { | |
| global $debug_object; | |
| if (is_object($debug_object)) | |
| { | |
| $text = ''; | |
| if ($this->tag == 'text') | |
| { | |
| if (!empty($this->text)) | |
| { | |
| $text = " with text: " . $this->text; | |
| } | |
| } | |
| $debug_object->debug_log(1, 'Innertext of tag: ' . $this->tag . $text); | |
| } | |
| if ($this->tag==='root') return $this->innertext(); | |
| // trigger callback | |
| if ($this->dom && $this->dom->callback!==null) | |
| { | |
| call_user_func_array($this->dom->callback, array($this)); | |
| } | |
| if (isset($this->_[HDOM_INFO_OUTER])) return $this->_[HDOM_INFO_OUTER]; | |
| if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); | |
| // render begin tag | |
| if ($this->dom && $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]) | |
| { | |
| $ret = $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]->makeup(); | |
| } else { | |
| $ret = ""; | |
| } | |
| // render inner text | |
| if (isset($this->_[HDOM_INFO_INNER])) | |
| { | |
| // If it's a br tag... don't return the HDOM_INNER_INFO that we may or may not have added. | |
| if ($this->tag != "br") | |
| { | |
| $ret .= $this->_[HDOM_INFO_INNER]; | |
| } | |
| } else { | |
| if ($this->nodes) | |
| { | |
| foreach ($this->nodes as $n) | |
| { | |
| $ret .= $this->convert_text($n->outertext()); | |
| } | |
| } | |
| } | |
| // render end tag | |
| if (isset($this->_[HDOM_INFO_END]) && $this->_[HDOM_INFO_END]!=0) | |
| $ret .= '</'.$this->tag.'>'; | |
| return $ret; | |
| } | |
| /** | |
| * Get node's plain text (everything excluding all tags) | |
| * | |
| * @return string | |
| */ | |
| function text() | |
| { | |
| if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER]; | |
| switch ($this->nodetype) | |
| { | |
| case HDOM_TYPE_TEXT: return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); | |
| case HDOM_TYPE_COMMENT: return ''; | |
| case HDOM_TYPE_UNKNOWN: return ''; | |
| } | |
| if (strcasecmp($this->tag, 'script')===0) return ''; | |
| if (strcasecmp($this->tag, 'style')===0) return ''; | |
| $ret = ''; | |
| // In rare cases, (always node type 1 or HDOM_TYPE_ELEMENT - observed for some span tags, and some p tags) $this->nodes is set to NULL. | |
| // NOTE: This indicates that there is a problem where it's set to NULL without a clear happening. | |
| // WHY is this happening? | |
| if (!is_null($this->nodes)) | |
| { | |
| foreach ($this->nodes as $n) | |
| { | |
| // Start paragraph after a blank line | |
| if ($n->tag == 'p') | |
| { | |
| $ret .= "\n\n"; | |
| } | |
| $ret .= $this->convert_text($n->text()); | |
| // If this node is a span... add a space at the end of it so multiple spans don't run into each other. This is plaintext after all. | |
| if ($n->tag == "span") | |
| { | |
| $ret .= $this->dom->default_span_text; | |
| } | |
| } | |
| } | |
| return trim($ret); | |
| } | |
| /** | |
| * Get node's xml text (inner text as a CDATA section) | |
| * | |
| * @return string | |
| */ | |
| function xmltext() | |
| { | |
| $ret = $this->innertext(); | |
| $ret = str_ireplace('<![CDATA[', '', $ret); | |
| $ret = str_replace(']]>', '', $ret); | |
| return $ret; | |
| } | |
| // build node's text with tag | |
| function makeup() | |
| { | |
| // text, comment, unknown | |
| if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]); | |
| $ret = '<'.$this->tag; | |
| $i = -1; | |
| foreach ($this->attr as $key=>$val) | |
| { | |
| ++$i; | |
| // skip removed attribute | |
| if ($val===null || $val===false) | |
| continue; | |
| $ret .= $this->_[HDOM_INFO_SPACE][$i][0]; | |
| //no value attr: nowrap, checked selected... | |
| if ($val===true) | |
| $ret .= $key; | |
| else { | |
| switch ($this->_[HDOM_INFO_QUOTE][$i]) | |
| { | |
| case HDOM_QUOTE_DOUBLE: $quote = '"'; break; | |
| case HDOM_QUOTE_SINGLE: $quote = '\''; break; | |
| default: $quote = ''; | |
| } | |
| $ret .= $key.$this->_[HDOM_INFO_SPACE][$i][1].'='.$this->_[HDOM_INFO_SPACE][$i][2].$quote.$val.$quote; | |
| } | |
| } | |
| $ret = $this->dom->restore_noise($ret); | |
| return $ret . $this->_[HDOM_INFO_ENDSPACE] . '>'; | |
| } | |
| // find elements by css selector | |
| //PaperG - added ability for find to lowercase the value of the selector. | |
| function find($selector, $idx=null, $lowercase=false) | |
| { | |
| $selectors = $this->parse_selector($selector); | |
| if (($count=count($selectors))===0) return array(); | |
| $found_keys = array(); | |
| // find each selector | |
| for ($c=0; $c<$count; ++$c) | |
| { | |
| // The change on the below line was documented on the sourceforge code tracker id 2788009 | |
| // used to be: if (($levle=count($selectors[0]))===0) return array(); | |
| if (($levle=count($selectors[$c]))===0) return array(); | |
| if (!isset($this->_[HDOM_INFO_BEGIN])) return array(); | |
| $head = array($this->_[HDOM_INFO_BEGIN]=>1); | |
| // handle descendant selectors, no recursive! | |
| for ($l=0; $l<$levle; ++$l) | |
| { | |
| $ret = array(); | |
| foreach ($head as $k=>$v) | |
| { | |
| $n = ($k===-1) ? $this->dom->root : $this->dom->nodes[$k]; | |
| //PaperG - Pass this optional parameter on to the seek function. | |
| $n->seek($selectors[$c][$l], $ret, $lowercase); | |
| } | |
| $head = $ret; | |
| } | |
| foreach ($head as $k=>$v) | |
| { | |
| if (!isset($found_keys[$k])) | |
| { | |
| $found_keys[$k] = 1; | |
| } | |
| } | |
| } | |
| // sort keys | |
| ksort($found_keys); | |
| $found = array(); | |
| foreach ($found_keys as $k=>$v) | |
| $found[] = $this->dom->nodes[$k]; | |
| // return nth-element or array | |
| if (is_null($idx)) return $found; | |
| else if ($idx<0) $idx = count($found) + $idx; | |
| return (isset($found[$idx])) ? $found[$idx] : null; | |
| } | |
| // seek for given conditions | |
| // PaperG - added parameter to allow for case insensitive testing of the value of a selector. | |
| protected function seek($selector, &$ret, $lowercase=false) | |
| { | |
| global $debug_object; | |
| if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } | |
| list($tag, $key, $val, $exp, $no_key) = $selector; | |
| // xpath index | |
| if ($tag && $key && is_numeric($key)) | |
| { | |
| $count = 0; | |
| foreach ($this->children as $c) | |
| { | |
| if ($tag==='*' || $tag===$c->tag) { | |
| if (++$count==$key) { | |
| $ret[$c->_[HDOM_INFO_BEGIN]] = 1; | |
| return; | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| $end = (!empty($this->_[HDOM_INFO_END])) ? $this->_[HDOM_INFO_END] : 0; | |
| if ($end==0) { | |
| $parent = $this->parent; | |
| while (!isset($parent->_[HDOM_INFO_END]) && $parent!==null) { | |
| $end -= 1; | |
| $parent = $parent->parent; | |
| } | |
| $end += $parent->_[HDOM_INFO_END]; | |
| } | |
| for ($i=$this->_[HDOM_INFO_BEGIN]+1; $i<$end; ++$i) { | |
| $node = $this->dom->nodes[$i]; | |
| $pass = true; | |
| if ($tag==='*' && !$key) { | |
| if (in_array($node, $this->children, true)) | |
| $ret[$i] = 1; | |
| continue; | |
| } | |
| // compare tag | |
| if ($tag && $tag!=$node->tag && $tag!=='*') {$pass=false;} | |
| // compare key | |
| if ($pass && $key) { | |
| if ($no_key) { | |
| if (isset($node->attr[$key])) $pass=false; | |
| } else { | |
| if (($key != "plaintext") && !isset($node->attr[$key])) $pass=false; | |
| } | |
| } | |
| // compare value | |
| if ($pass && $key && $val && $val!=='*') { | |
| // If they have told us that this is a "plaintext" search then we want the plaintext of the node - right? | |
| if ($key == "plaintext") { | |
| // $node->plaintext actually returns $node->text(); | |
| $nodeKeyValue = $node->text(); | |
| } else { | |
| // this is a normal search, we want the value of that attribute of the tag. | |
| $nodeKeyValue = $node->attr[$key]; | |
| } | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, "testing node: " . $node->tag . " for attribute: " . $key . $exp . $val . " where nodes value is: " . $nodeKeyValue);} | |
| //PaperG - If lowercase is set, do a case insensitive test of the value of the selector. | |
| if ($lowercase) { | |
| $check = $this->match($exp, strtolower($val), strtolower($nodeKeyValue)); | |
| } else { | |
| $check = $this->match($exp, $val, $nodeKeyValue); | |
| } | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, "after match: " . ($check ? "true" : "false"));} | |
| // handle multiple class | |
| if (!$check && strcasecmp($key, 'class')===0) { | |
| foreach (explode(' ',$node->attr[$key]) as $k) { | |
| // Without this, there were cases where leading, trailing, or double spaces lead to our comparing blanks - bad form. | |
| if (!empty($k)) { | |
| if ($lowercase) { | |
| $check = $this->match($exp, strtolower($val), strtolower($k)); | |
| } else { | |
| $check = $this->match($exp, $val, $k); | |
| } | |
| if ($check) break; | |
| } | |
| } | |
| } | |
| if (!$check) $pass = false; | |
| } | |
| if ($pass) $ret[$i] = 1; | |
| unset($node); | |
| } | |
| // It's passed by reference so this is actually what this function returns. | |
| if (is_object($debug_object)) {$debug_object->debug_log(1, "EXIT - ret: ", $ret);} | |
| } | |
| protected function match($exp, $pattern, $value) { | |
| global $debug_object; | |
| if (is_object($debug_object)) {$debug_object->debug_log_entry(1);} | |
| switch ($exp) { | |
| case '=': | |
| return ($value===$pattern); | |
| case '!=': | |
| return ($value!==$pattern); | |
| case '^=': | |
| return preg_match("/^".preg_quote($pattern,'/')."/", $value); | |
| case '$=': | |
| return preg_match("/".preg_quote($pattern,'/')."$/", $value); | |
| case '*=': | |
| if ($pattern[0]=='/') { | |
| return preg_match($pattern, $value); | |
| } | |
| return preg_match("/".$pattern."/i", $value); | |
| } | |
| return false; | |
| } | |
| protected function parse_selector($selector_string) { | |
| global $debug_object; | |
| if (is_object($debug_object)) {$debug_object->debug_log_entry(1);} | |
| // pattern of CSS selectors, modified from mootools | |
| // Paperg: Add the colon to the attrbute, so that it properly finds <tag attr:ibute="something" > like google does. | |
| // Note: if you try to look at this attribute, yo MUST use getAttribute since $dom->x:y will fail the php syntax check. | |
| // Notice the \[ starting the attbute? and the @? following? This implies that an attribute can begin with an @ sign that is not captured. | |
| // This implies that an html attribute specifier may start with an @ sign that is NOT captured by the expression. | |
| // farther study is required to determine of this should be documented or removed. | |
| // $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; | |
| $pattern = "/([\w:\*-]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w:-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is"; | |
| preg_match_all($pattern, trim($selector_string).' ', $matches, PREG_SET_ORDER); | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, "Matches Array: ", $matches);} | |
| $selectors = array(); | |
| $result = array(); | |
| //print_r($matches); | |
| foreach ($matches as $m) { | |
| $m[0] = trim($m[0]); | |
| if ($m[0]==='' || $m[0]==='/' || $m[0]==='//') continue; | |
| // for browser generated xpath | |
| if ($m[1]==='tbody') continue; | |
| list($tag, $key, $val, $exp, $no_key) = array($m[1], null, null, '=', false); | |
| if (!empty($m[2])) {$key='id'; $val=$m[2];} | |
| if (!empty($m[3])) {$key='class'; $val=$m[3];} | |
| if (!empty($m[4])) {$key=$m[4];} | |
| if (!empty($m[5])) {$exp=$m[5];} | |
| if (!empty($m[6])) {$val=$m[6];} | |
| if($tag === null) | |
| { | |
| $tag = ''; | |
| } | |
| if($key === null) | |
| { | |
| $key = ''; | |
| } | |
| // convert to lowercase | |
| if ($this->dom->lowercase) {$tag=strtolower($tag); $key=strtolower($key);} | |
| //elements that do NOT have the specified attribute | |
| if (isset($key[0]) && $key[0]==='!') {$key=substr($key, 1); $no_key=true;} | |
| $result[] = array($tag, $key, $val, $exp, $no_key); | |
| if (trim($m[7])===',') { | |
| $selectors[] = $result; | |
| $result = array(); | |
| } | |
| } | |
| if (count($result)>0) | |
| $selectors[] = $result; | |
| return $selectors; | |
| } | |
| function __get($name) | |
| { | |
| if (isset($this->attr[$name])) | |
| { | |
| return $this->convert_text($this->attr[$name]); | |
| } | |
| switch ($name) | |
| { | |
| case 'outertext': return $this->outertext(); | |
| case 'innertext': return $this->innertext(); | |
| case 'plaintext': return $this->text(); | |
| case 'xmltext': return $this->xmltext(); | |
| default: return array_key_exists($name, $this->attr); | |
| } | |
| } | |
| function __set($name, $value) | |
| { | |
| global $debug_object; | |
| if (is_object($debug_object)) {$debug_object->debug_log_entry(1);} | |
| switch ($name) | |
| { | |
| case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value; | |
| case 'innertext': | |
| if (isset($this->_[HDOM_INFO_TEXT])) return $this->_[HDOM_INFO_TEXT] = $value; | |
| return $this->_[HDOM_INFO_INNER] = $value; | |
| } | |
| if (!isset($this->attr[$name])) | |
| { | |
| $this->_[HDOM_INFO_SPACE][] = array(' ', '', ''); | |
| $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE; | |
| } | |
| $this->attr[$name] = $value; | |
| } | |
| function __isset($name) | |
| { | |
| switch ($name) | |
| { | |
| case 'outertext': return true; | |
| case 'innertext': return true; | |
| case 'plaintext': return true; | |
| } | |
| //no value attr: nowrap, checked selected... | |
| return (array_key_exists($name, $this->attr)) ? true : isset($this->attr[$name]); | |
| } | |
| function __unset($name) { | |
| if (isset($this->attr[$name])) | |
| unset($this->attr[$name]); | |
| } | |
| // PaperG - Function to convert the text from one character set to another if the two sets are not the same. | |
| function convert_text($text) | |
| { | |
| global $debug_object; | |
| if (is_object($debug_object)) {$debug_object->debug_log_entry(1);} | |
| $converted_text = $text; | |
| $sourceCharset = ""; | |
| $targetCharset = ""; | |
| if ($this->dom) | |
| { | |
| $sourceCharset = strtoupper($this->dom->_charset); | |
| $targetCharset = strtoupper($this->dom->_target_charset); | |
| } | |
| if (is_object($debug_object)) {$debug_object->debug_log(3, "source charset: " . $sourceCharset . " target charaset: " . $targetCharset);} | |
| if (!empty($sourceCharset) && !empty($targetCharset) && (strcasecmp($sourceCharset, $targetCharset) != 0)) | |
| { | |
| // Check if the reported encoding could have been incorrect and the text is actually already UTF-8 | |
| if ((strcasecmp($targetCharset, 'UTF-8') == 0) && ($this->is_utf8($text))) | |
| { | |
| $converted_text = $text; | |
| } | |
| else | |
| { | |
| $converted_text = iconv($sourceCharset, $targetCharset, $text); | |
| } | |
| } | |
| // Lets make sure that we don't have that silly BOM issue with any of the utf-8 text we output. | |
| if ($targetCharset == 'UTF-8') | |
| { | |
| if (substr($converted_text, 0, 3) == "\xef\xbb\xbf") | |
| { | |
| $converted_text = substr($converted_text, 3); | |
| } | |
| if (substr($converted_text, -3) == "\xef\xbb\xbf") | |
| { | |
| $converted_text = substr($converted_text, 0, -3); | |
| } | |
| } | |
| return $converted_text; | |
| } | |
| /** | |
| * Returns true if $string is valid UTF-8 and false otherwise. | |
| * | |
| * @param mixed $str String to be tested | |
| * @return boolean | |
| */ | |
| static function is_utf8($str) | |
| { | |
| $c=0; $b=0; | |
| $bits=0; | |
| $len=strlen($str); | |
| for($i=0; $i<$len; $i++) | |
| { | |
| $c=ord($str[$i]); | |
| if($c > 128) | |
| { | |
| if(($c >= 254)) return false; | |
| elseif($c >= 252) $bits=6; | |
| elseif($c >= 248) $bits=5; | |
| elseif($c >= 240) $bits=4; | |
| elseif($c >= 224) $bits=3; | |
| elseif($c >= 192) $bits=2; | |
| else return false; | |
| if(($i+$bits) > $len) return false; | |
| while($bits > 1) | |
| { | |
| $i++; | |
| $b=ord($str[$i]); | |
| if($b < 128 || $b > 191) return false; | |
| $bits--; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| /* | |
| function is_utf8($string) | |
| { | |
| //this is buggy | |
| return (utf8_encode(utf8_decode($string)) == $string); | |
| } | |
| */ | |
| /** | |
| * Function to try a few tricks to determine the displayed size of an img on the page. | |
| * NOTE: This will ONLY work on an IMG tag. Returns FALSE on all other tag types. | |
| * | |
| * @author John Schlick | |
| * @version April 19 2012 | |
| * @return array an array containing the 'height' and 'width' of the image on the page or -1 if we can't figure it out. | |
| */ | |
| function get_display_size() | |
| { | |
| global $debug_object; | |
| $width = -1; | |
| $height = -1; | |
| if ($this->tag !== 'img') | |
| { | |
| return false; | |
| } | |
| // See if there is aheight or width attribute in the tag itself. | |
| if (isset($this->attr['width'])) | |
| { | |
| $width = $this->attr['width']; | |
| } | |
| if (isset($this->attr['height'])) | |
| { | |
| $height = $this->attr['height']; | |
| } | |
| // Now look for an inline style. | |
| if (isset($this->attr['style'])) | |
| { | |
| // Thanks to user gnarf from stackoverflow for this regular expression. | |
| $attributes = array(); | |
| preg_match_all("/([\w-]+)\s*:\s*([^;]+)\s*;?/", $this->attr['style'], $matches, PREG_SET_ORDER); | |
| foreach ($matches as $match) { | |
| $attributes[$match[1]] = $match[2]; | |
| } | |
| // If there is a width in the style attributes: | |
| if (isset($attributes['width']) && $width == -1) | |
| { | |
| // check that the last two characters are px (pixels) | |
| if (strtolower(substr($attributes['width'], -2)) == 'px') | |
| { | |
| $proposed_width = substr($attributes['width'], 0, -2); | |
| // Now make sure that it's an integer and not something stupid. | |
| if (filter_var($proposed_width, FILTER_VALIDATE_INT)) | |
| { | |
| $width = $proposed_width; | |
| } | |
| } | |
| } | |
| // If there is a width in the style attributes: | |
| if (isset($attributes['height']) && $height == -1) | |
| { | |
| // check that the last two characters are px (pixels) | |
| if (strtolower(substr($attributes['height'], -2)) == 'px') | |
| { | |
| $proposed_height = substr($attributes['height'], 0, -2); | |
| // Now make sure that it's an integer and not something stupid. | |
| if (filter_var($proposed_height, FILTER_VALIDATE_INT)) | |
| { | |
| $height = $proposed_height; | |
| } | |
| } | |
| } | |
| } | |
| // Future enhancement: | |
| // Look in the tag to see if there is a class or id specified that has a height or width attribute to it. | |
| // Far future enhancement | |
| // Look at all the parent tags of this image to see if they specify a class or id that has an img selector that specifies a height or width | |
| // Note that in this case, the class or id will have the img subselector for it to apply to the image. | |
| // ridiculously far future development | |
| // If the class or id is specified in a SEPARATE css file thats not on the page, go get it and do what we were just doing for the ones on the page. | |
| $result = array('height' => $height, | |
| 'width' => $width); | |
| return $result; | |
| } | |
| // camel naming conventions | |
| function getAllAttributes() {return $this->attr;} | |
| function getAttribute($name) {return $this->__get($name);} | |
| function setAttribute($name, $value) {$this->__set($name, $value);} | |
| function hasAttribute($name) {return $this->__isset($name);} | |
| function removeAttribute($name) {$this->__set($name, null);} | |
| function getElementById($id) {return $this->find("#$id", 0);} | |
| function getElementsById($id, $idx=null) {return $this->find("#$id", $idx);} | |
| function getElementByTagName($name) {return $this->find($name, 0);} | |
| function getElementsByTagName($name, $idx=null) {return $this->find($name, $idx);} | |
| function parentNode() {return $this->parent();} | |
| function childNodes($idx=-1) {return $this->children($idx);} | |
| function firstChild() {return $this->first_child();} | |
| function lastChild() {return $this->last_child();} | |
| function nextSibling() {return $this->next_sibling();} | |
| function previousSibling() {return $this->prev_sibling();} | |
| function hasChildNodes() {return $this->has_child();} | |
| function nodeName() {return $this->tag;} | |
| function appendChild($node) {$node->parent($this); return $node;} | |
| } | |
| /** | |
| * simple html dom parser | |
| * Paperg - in the find routine: allow us to specify that we want case insensitive testing of the value of the selector. | |
| * Paperg - change $size from protected to public so we can easily access it | |
| * Paperg - added ForceTagsClosed in the constructor which tells us whether we trust the html or not. Default is to NOT trust it. | |
| * | |
| * @package PlaceLocalInclude | |
| */ | |
| class ums_simple_html_dom | |
| { | |
| /** | |
| * The root node of the document | |
| * | |
| * @var object | |
| */ | |
| public $root = null; | |
| /** | |
| * List of nodes in the current DOM | |
| * | |
| * @var array | |
| */ | |
| public $nodes = array(); | |
| /** | |
| * Callback function to run for each element in the DOM. | |
| * | |
| * @var callable|null | |
| */ | |
| public $callback = null; | |
| /** | |
| * Indicates how tags and attributes are matched | |
| * | |
| * @var bool When set to **true** tags and attributes will be converted to | |
| * lowercase before matching. | |
| */ | |
| public $lowercase = false; | |
| /** | |
| * Original document size | |
| * | |
| * Holds the original document size. | |
| * | |
| * @var int | |
| */ | |
| public $original_size; | |
| /** | |
| * Current document size | |
| * | |
| * Holds the current document size. The document size is determined by the | |
| * string length of ({@see ums_simple_html_dom::$doc}). | |
| * | |
| * _Note_: Using this variable is more efficient than calling `strlen($doc)` | |
| * | |
| * @var int | |
| * */ | |
| public $size; | |
| /** | |
| * Current position in the document | |
| * | |
| * @var int | |
| */ | |
| protected $pos; | |
| /** | |
| * The document | |
| * | |
| * @var string | |
| */ | |
| protected $doc; | |
| /** | |
| * Current character | |
| * | |
| * Holds the current character at position {@see ums_simple_html_dom::$pos} in | |
| * the document {@see ums_simple_html_dom::$doc} | |
| * | |
| * _Note_: Using this variable is more efficient than calling `substr($doc, $pos, 1)` | |
| * | |
| * @var string | |
| */ | |
| protected $char; | |
| protected $cursor; | |
| /** | |
| * Parent node of the next node detected by the parser | |
| * | |
| * @var object | |
| */ | |
| protected $parent; | |
| protected $noise = array(); | |
| /** | |
| * Tokens considered blank in HTML | |
| * | |
| * @var string | |
| */ | |
| protected $token_blank = " \t\r\n"; | |
| /** | |
| * Tokens to identify the equal sign for attributes, stopping either at the | |
| * closing tag ("/" i.e. "<html />") or the end of an opening tag (">" i.e. | |
| * "<html>") | |
| * | |
| * @var string | |
| */ | |
| protected $token_equal = ' =/>'; | |
| /** | |
| * Tokens to identify the end of a tag name. A tag name either ends on the | |
| * ending slash ("/" i.e. "<html/>") or whitespace ("\s\r\n\t") | |
| * | |
| * @var string | |
| */ | |
| protected $token_slash = " />\r\n\t"; | |
| /** | |
| * Tokens to identify the end of an attribute | |
| * | |
| * @var string | |
| */ | |
| protected $token_attr = ' >'; | |
| // Note that this is referenced by a child node, and so it needs to be public for that node to see this information. | |
| public $_charset = ''; | |
| public $_target_charset = ''; | |
| /** | |
| * Innertext for <br> elements | |
| * | |
| * @var string | |
| */ | |
| protected $default_br_text = ""; | |
| /** | |
| * Suffix for <span> elements | |
| * | |
| * @var string | |
| */ | |
| public $default_span_text = ""; | |
| /** | |
| * Defines a list of self-closing tags (Void elements) according to the HTML | |
| * Specification | |
| * | |
| * _Remarks_: | |
| * - Use `isset()` instead of `in_array()` on array elements to boost | |
| * performance about 30% | |
| * - Sort elements by name for better readability! | |
| * | |
| * @link https://www.w3.org/TR/html HTML Specification | |
| * @link https://www.w3.org/TR/html/syntax.html#void-elements Void elements | |
| */ | |
| protected $self_closing_tags = array( | |
| 'area'=>1, | |
| 'base'=>1, | |
| 'br'=>1, | |
| 'col'=>1, | |
| 'embed'=>1, | |
| 'hr'=>1, | |
| 'img'=>1, | |
| 'input'=>1, | |
| 'link'=>1, | |
| 'meta'=>1, | |
| 'param'=>1, | |
| 'source'=>1, | |
| 'track'=>1, | |
| 'wbr'=>1 | |
| ); | |
| /** | |
| * Defines a list of tags which - if closed - close all optional closing | |
| * elements within if they haven't been closed yet. (So, an element where | |
| * neither opening nor closing tag is omissible consistently closes every | |
| * optional closing element within) | |
| * | |
| * _Remarks_: | |
| * - Use `isset()` instead of `in_array()` on array elements to boost | |
| * performance about 30% | |
| * - Sort elements by name for better readability! | |
| */ | |
| protected $block_tags = array( | |
| 'body'=>1, | |
| 'div'=>1, | |
| 'form'=>1, | |
| 'root'=>1, | |
| 'span'=>1, | |
| 'table'=>1 | |
| ); | |
| /** | |
| * Defines elements whose end tag is omissible. | |
| * | |
| * * key = Name of an element whose end tag is omissible. | |
| * * value = Names of elements whose end tag is omissible, that are closed | |
| * by the current element. | |
| * | |
| * _Remarks_: | |
| * - Use `isset()` instead of `in_array()` on array elements to boost | |
| * performance about 30% | |
| * - Sort elements by name for better readability! | |
| * | |
| * **Example** | |
| * | |
| * An `li` element???s end tag may be omitted if the `li` element is immediately | |
| * followed by another `li` element. To do that, add following element to the | |
| * array: | |
| * | |
| * ```php | |
| * 'li' => array('li'), | |
| * ``` | |
| * | |
| * With this, the following two examples are considered equal. Note that the | |
| * second example is missing the closing tags on `li` elements. | |
| * | |
| * ```html | |
| * <ul><li>First Item</li><li>Second Item</li></ul> | |
| * ``` | |
| * | |
| * <ul><li>First Item</li><li>Second Item</li></ul> | |
| * | |
| * ```html | |
| * <ul><li>First Item<li>Second Item</ul> | |
| * ``` | |
| * | |
| * <ul><li>First Item<li>Second Item</ul> | |
| * | |
| * @var array A two-dimensional array where the key is the name of an | |
| * element whose end tag is omissible and the value is an array of elements | |
| * whose end tag is omissible, that are closed by the current element. | |
| * | |
| * @link https://www.w3.org/TR/html/syntax.html#optional-tags Optional tags | |
| * | |
| * @todo The implementation of optional closing tags doesn't work in all cases | |
| * because it only consideres elements who close other optional closing | |
| * tags, not taking into account that some (non-blocking) tags should close | |
| * these optional closing tags. For example, the end tag for "p" is omissible | |
| * and can be closed by an "address" element, whose end tag is NOT omissible. | |
| * Currently a "p" element without closing tag stops at the next "p" element | |
| * or blocking tag, even if it contains other elements. | |
| * | |
| * @todo Known sourceforge issue #2977341 | |
| * B tags that are not closed cause us to return everything to the end of | |
| * the document. | |
| */ | |
| protected $optional_closing_tags = array( | |
| 'b'=>array('b'=>1), // Not optional, see https://www.w3.org/TR/html/textlevel-semantics.html#the-b-element | |
| 'dd'=>array('dd'=>1, 'dt'=>1), | |
| 'dl'=>array('dd'=>1, 'dt'=>1), // Not optional, see https://www.w3.org/TR/html/grouping-content.html#the-dl-element | |
| 'dt'=>array('dd'=>1, 'dt'=>1), | |
| 'li'=>array('li'=>1), | |
| 'optgroup'=>array('optgroup'=>1, 'option'=>1), | |
| 'option'=>array('optgroup'=>1, 'option'=>1), | |
| 'p'=>array('p'=>1), | |
| 'rp'=>array('rp'=>1, 'rt'=>1), | |
| 'rt'=>array('rp'=>1, 'rt'=>1), | |
| 'td'=>array('td'=>1, 'th'=>1), | |
| 'th'=>array('td'=>1, 'th'=>1), | |
| 'tr'=>array('td'=>1, 'th'=>1, 'tr'=>1), | |
| ); | |
| function __construct($str=null, $lowercase=true, $forceTagsClosed=true, $target_charset=DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT, $options=0) | |
| { | |
| if ($str) | |
| { | |
| global $wp_filesystem; | |
| if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base') ){ | |
| include_once(ABSPATH . 'wp-admin/includes/file.php');$creds = request_filesystem_credentials( site_url() ); | |
| wp_filesystem($creds); | |
| } | |
| if (preg_match("/^http:\/\//i",$str) || $wp_filesystem->is_file($str)) | |
| { | |
| $this->load_file($str); | |
| } | |
| else | |
| { | |
| $this->load($str, $lowercase, $stripRN, $defaultBRText, $defaultSpanText, $options); | |
| } | |
| } | |
| // Forcing tags to be closed implies that we don't trust the html, but it can lead to parsing errors if we SHOULD trust the html. | |
| if (!$forceTagsClosed) { | |
| $this->optional_closing_array=array(); | |
| } | |
| $this->_target_charset = $target_charset; | |
| } | |
| function __destruct() | |
| { | |
| $this->clear(); | |
| } | |
| // load html from string | |
| function load($str, $lowercase=true, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT, $options=0) | |
| { | |
| global $debug_object; | |
| // prepare | |
| $this->prepare($str, $lowercase, $defaultBRText, $defaultSpanText); | |
| // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037 | |
| // Script tags removal now preceeds style tag removal. | |
| // strip out <script> tags | |
| $this->remove_noise("'<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>'is"); | |
| $this->remove_noise("'<\s*script\s*>(.*?)<\s*/\s*script\s*>'is"); | |
| // strip out the \r \n's if we are told to. | |
| if ($stripRN) { | |
| $this->doc = str_replace("\r", " ", $this->doc); | |
| $this->doc = str_replace("\n", " ", $this->doc); | |
| // set the length of content since we have changed it. | |
| $this->size = strlen($this->doc); | |
| } | |
| // strip out cdata | |
| $this->remove_noise("'<!\[CDATA\[(.*?)\]\]>'is", true); | |
| // strip out comments | |
| $this->remove_noise("'<!--(.*?)-->'is"); | |
| // strip out <style> tags | |
| $this->remove_noise("'<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>'is"); | |
| $this->remove_noise("'<\s*style\s*>(.*?)<\s*/\s*style\s*>'is"); | |
| // strip out preformatted tags | |
| $this->remove_noise("'<\s*(?:code)[^>]*>(.*?)<\s*/\s*(?:code)\s*>'is"); | |
| // strip out server side scripts | |
| $this->remove_noise("'(<\?)(.*?)(\?>)'s", true); | |
| if($options & HDOM_SMARTY_AS_TEXT) { // Strip Smarty scripts | |
| $this->remove_noise("'(\{\w)(.*?)(\})'s", true); | |
| } | |
| // parsing | |
| $this->parse(); | |
| // end | |
| $this->root->_[HDOM_INFO_END] = $this->cursor; | |
| $this->parse_charset(); | |
| // make load function chainable | |
| return $this; | |
| } | |
| // load html from file | |
| function load_file() | |
| { | |
| $args = func_get_args(); | |
| if(count($args) == 0) | |
| { | |
| return false; | |
| } | |
| global $wp_filesystem; | |
| if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base') ){ | |
| include_once(ABSPATH . 'wp-admin/includes/file.php');$creds = request_filesystem_credentials( site_url() ); | |
| wp_filesystem($creds); | |
| } | |
| $doc = $wp_filesystem->get_contents($args[0]); | |
| if($doc !== false) { | |
| $this->load($doc, true); | |
| } else { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Set the callback function | |
| * | |
| * @param callable $function_name Callback function to run for each element | |
| * in the DOM. | |
| * @return void | |
| */ | |
| function set_callback($function_name) | |
| { | |
| $this->callback = $function_name; | |
| } | |
| /** | |
| * Remove callback function | |
| * | |
| * @return void | |
| */ | |
| function remove_callback() | |
| { | |
| $this->callback = null; | |
| } | |
| // save dom as string | |
| function save($filepath='') | |
| { | |
| $ret = $this->root->innertext(); | |
| if ($filepath!=='') | |
| { | |
| global $wp_filesystem; | |
| if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base') ){ | |
| include_once(ABSPATH . 'wp-admin/includes/file.php');$creds = request_filesystem_credentials( site_url() ); | |
| wp_filesystem($creds); | |
| } | |
| $wp_filesystem->put_contents($filepath, $ret); | |
| } | |
| return $ret; | |
| } | |
| // find dom node by css selector | |
| // Paperg - allow us to specify that we want case insensitive testing of the value of the selector. | |
| function find($selector, $idx=null, $lowercase=false) | |
| { | |
| return $this->root->find($selector, $idx, $lowercase); | |
| } | |
| // clean up memory due to php5 circular references memory leak... | |
| function clear() | |
| { | |
| foreach ($this->nodes as $n) {$n->clear(); $n = null;} | |
| // This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear. | |
| if (isset($this->children)) foreach ($this->children as $n) {$n->clear(); $n = null;} | |
| if (isset($this->parent)) {$this->parent->clear(); unset($this->parent);} | |
| if (isset($this->root)) {$this->root->clear(); unset($this->root);} | |
| unset($this->doc); | |
| unset($this->noise); | |
| } | |
| function dump($show_attr=true) | |
| { | |
| $this->root->dump($show_attr); | |
| } | |
| // prepare HTML data and init everything | |
| protected function prepare($str, $lowercase=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT) | |
| { | |
| $this->clear(); | |
| $this->doc = trim($str); | |
| $this->size = strlen($this->doc); | |
| $this->original_size = $this->size; // Save the original size of the html that we got in. It might be useful to someone. | |
| $this->pos = 0; | |
| $this->cursor = 1; | |
| $this->noise = array(); | |
| $this->nodes = array(); | |
| $this->lowercase = $lowercase; | |
| $this->default_br_text = $defaultBRText; | |
| $this->default_span_text = $defaultSpanText; | |
| $this->root = new ums_simple_html_dom_node($this); | |
| $this->root->tag = 'root'; | |
| $this->root->_[HDOM_INFO_BEGIN] = -1; | |
| $this->root->nodetype = HDOM_TYPE_ROOT; | |
| $this->parent = $this->root; | |
| if ($this->size>0) $this->char = $this->doc[0]; | |
| } | |
| /** | |
| * Parse HTML content | |
| * | |
| * @return bool True on success | |
| */ | |
| protected function parse() | |
| { | |
| while (true) { | |
| // Read next tag if there is no text between current position and the | |
| // next opening tag. | |
| if (($s = $this->copy_until_char('<'))==='') | |
| { | |
| if($this->read_tag()) { | |
| continue; | |
| } else { | |
| return true; | |
| } | |
| } | |
| // Add a text node for text between tags | |
| $node = new ums_simple_html_dom_node($this); | |
| ++$this->cursor; | |
| $node->_[HDOM_INFO_TEXT] = $s; | |
| $this->link_nodes($node, false); | |
| } | |
| } | |
| // PAPERG - dkchou - added this to try to identify the character set of the page we have just parsed so we know better how to spit it out later. | |
| // NOTE: IF you provide a routine called get_last_retrieve_url_contents_content_type which returns the CURLINFO_CONTENT_TYPE from the last curl_exec | |
| // (or the content_type header from the last transfer), we will parse THAT, and if a charset is specified, we will use it over any other mechanism. | |
| protected function parse_charset() | |
| { | |
| global $debug_object; | |
| $charset = null; | |
| if (function_exists('get_last_retrieve_url_contents_content_type')) | |
| { | |
| $contentTypeHeader = get_last_retrieve_url_contents_content_type(); | |
| $success = preg_match('/charset=(.+)/', $contentTypeHeader, $matches); | |
| if ($success) | |
| { | |
| $charset = $matches[1]; | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, 'header content-type found charset of: ' . $charset);} | |
| } | |
| } | |
| if (empty($charset)) | |
| { | |
| $el = $this->root->find('meta[http-equiv=Content-Type]',0, true); | |
| if (!empty($el)) | |
| { | |
| $fullvalue = $el->content; | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, 'meta content-type tag found' . $fullvalue);} | |
| if (!empty($fullvalue)) | |
| { | |
| $success = preg_match('/charset=(.+)/i', $fullvalue, $matches); | |
| if ($success) | |
| { | |
| $charset = $matches[1]; | |
| } | |
| else | |
| { | |
| // If there is a meta tag, and they don't specify the character set, research says that it's typically ISO-8859-1 | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, 'meta content-type tag couldn\'t be parsed. using iso-8859 default.');} | |
| $charset = 'ISO-8859-1'; | |
| } | |
| } | |
| } | |
| } | |
| // If we couldn't find a charset above, then lets try to detect one based on the text we got... | |
| if (empty($charset)) | |
| { | |
| // Use this in case mb_detect_charset isn't installed/loaded on this machine. | |
| $charset = false; | |
| if (function_exists('mb_detect_encoding')) | |
| { | |
| // Have php try to detect the encoding from the text given to us. | |
| $charset = mb_detect_encoding($this->doc . "ascii", $encoding_list = array( "UTF-8", "CP1252" ) ); | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, 'mb_detect found: ' . $charset);} | |
| } | |
| // and if this doesn't work... then we need to just wrongheadedly assume it's UTF-8 so that we can move on - cause this will usually give us most of what we need... | |
| if ($charset === false) | |
| { | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, 'since mb_detect failed - using default of utf-8');} | |
| $charset = 'UTF-8'; | |
| } | |
| } | |
| // Since CP1252 is a superset, if we get one of it's subsets, we want it instead. | |
| if ((strtolower($charset) == strtolower('ISO-8859-1')) || (strtolower($charset) == strtolower('Latin1')) || (strtolower($charset) == strtolower('Latin-1'))) | |
| { | |
| if (is_object($debug_object)) {$debug_object->debug_log(2, 'replacing ' . $charset . ' with CP1252 as its a superset');} | |
| $charset = 'CP1252'; | |
| } | |
| if (is_object($debug_object)) {$debug_object->debug_log(1, 'EXIT - ' . $charset);} | |
| return $this->_charset = $charset; | |
| } | |
| /** | |
| * Parse tag from current document position. | |
| * | |
| * @return bool True if a tag was found, false otherwise | |
| */ | |
| protected function read_tag() | |
| { | |
| // Set end position if no further tags found | |
| if ($this->char!=='<') | |
| { | |
| $this->root->_[HDOM_INFO_END] = $this->cursor; | |
| return false; | |
| } | |
| $begin_tag_pos = $this->pos; | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| // end tag | |
| if ($this->char==='/') | |
| { | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| // Skip whitespace in end tags (i.e. in "</ html>") | |
| $this->skip($this->token_blank); | |
| $tag = $this->copy_until_char('>'); | |
| // Skip attributes in end tags | |
| if (($pos = strpos($tag, ' '))!==false) | |
| $tag = substr($tag, 0, $pos); | |
| $parent_lower = strtolower($this->parent->tag); | |
| $tag_lower = strtolower($tag); | |
| // The end tag is supposed to close the parent tag. Handle situations | |
| // when it doesn't | |
| if ($parent_lower!==$tag_lower) | |
| { | |
| // Parent tag does not have to be closed necessarily (optional closing tag) | |
| // Current tag is a block tag, so it may close an ancestor | |
| if (isset($this->optional_closing_tags[$parent_lower]) && isset($this->block_tags[$tag_lower])) | |
| { | |
| $this->parent->_[HDOM_INFO_END] = 0; | |
| $org_parent = $this->parent; | |
| // Traverse ancestors to find a matching opening tag | |
| // Stop at root node | |
| while (($this->parent->parent) && strtolower($this->parent->tag)!==$tag_lower) | |
| $this->parent = $this->parent->parent; | |
| // If we don't have a match add current tag as text node | |
| if (strtolower($this->parent->tag)!==$tag_lower) { | |
| $this->parent = $org_parent; // restore origonal parent | |
| if ($this->parent->parent) $this->parent = $this->parent->parent; | |
| $this->parent->_[HDOM_INFO_END] = $this->cursor; | |
| return $this->as_text_node($tag); | |
| } | |
| } | |
| // Grandparent exists and current tag is a block tag, so our parent doesn't have an end tag | |
| else if (($this->parent->parent) && isset($this->block_tags[$tag_lower])) | |
| { | |
| $this->parent->_[HDOM_INFO_END] = 0; // No end tag | |
| $org_parent = $this->parent; | |
| // Traverse ancestors to find a matching opening tag | |
| // Stop at root node | |
| while (($this->parent->parent) && strtolower($this->parent->tag)!==$tag_lower) | |
| $this->parent = $this->parent->parent; | |
| // If we don't have a match add current tag as text node | |
| if (strtolower($this->parent->tag)!==$tag_lower) | |
| { | |
| $this->parent = $org_parent; // restore origonal parent | |
| $this->parent->_[HDOM_INFO_END] = $this->cursor; | |
| return $this->as_text_node($tag); | |
| } | |
| } | |
| // Grandparent exists and current tag closes it | |
| else if (($this->parent->parent) && strtolower($this->parent->parent->tag)===$tag_lower) | |
| { | |
| $this->parent->_[HDOM_INFO_END] = 0; | |
| $this->parent = $this->parent->parent; | |
| } | |
| else // Random tag, add as text node | |
| return $this->as_text_node($tag); | |
| } | |
| // Set end position of parent tag to current cursor position | |
| $this->parent->_[HDOM_INFO_END] = $this->cursor; | |
| if ($this->parent->parent) $this->parent = $this->parent->parent; | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| return true; | |
| } | |
| // start tag | |
| $node = new ums_simple_html_dom_node($this); | |
| $node->_[HDOM_INFO_BEGIN] = $this->cursor; | |
| ++$this->cursor; | |
| $tag = $this->copy_until($this->token_slash); // Get tag name | |
| $node->tag_start = $begin_tag_pos; | |
| // doctype, cdata & comments... | |
| // <!DOCTYPE html> | |
| // <![CDATA[ ... ]]> | |
| // <!-- Comment --> | |
| if (isset($tag[0]) && $tag[0]==='!') { | |
| $node->_[HDOM_INFO_TEXT] = '<' . $tag . $this->copy_until_char('>'); | |
| if (isset($tag[2]) && $tag[1]==='-' && $tag[2]==='-') { // Comment ("<!--") | |
| $node->nodetype = HDOM_TYPE_COMMENT; | |
| $node->tag = 'comment'; | |
| } else { // Could be doctype or CDATA but we don't care | |
| $node->nodetype = HDOM_TYPE_UNKNOWN; | |
| $node->tag = 'unknown'; | |
| } | |
| if ($this->char==='>') $node->_[HDOM_INFO_TEXT].='>'; | |
| $this->link_nodes($node, true); | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| return true; | |
| } | |
| // The start tag cannot contain another start tag, if so add as text | |
| // i.e. "<<html>" | |
| if ($pos=strpos($tag, '<')!==false) { | |
| $tag = '<' . substr($tag, 0, -1); | |
| $node->_[HDOM_INFO_TEXT] = $tag; | |
| $this->link_nodes($node, false); | |
| $this->char = $this->doc[--$this->pos]; // prev | |
| return true; | |
| } | |
| // Handle invalid tag names (i.e. "<html#doc>") | |
| if (!preg_match("/^\w[\w:-]*$/", $tag)) { | |
| $node->_[HDOM_INFO_TEXT] = '<' . $tag . $this->copy_until('<>'); | |
| // Next char is the beginning of a new tag, don't touch it. | |
| if ($this->char==='<') { | |
| $this->link_nodes($node, false); | |
| return true; | |
| } | |
| // Next char closes current tag, add and be done with it. | |
| if ($this->char==='>') $node->_[HDOM_INFO_TEXT].='>'; | |
| $this->link_nodes($node, false); | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| return true; | |
| } | |
| // begin tag, add new node | |
| $node->nodetype = HDOM_TYPE_ELEMENT; | |
| $tag_lower = strtolower($tag); | |
| $node->tag = ($this->lowercase) ? $tag_lower : $tag; | |
| // handle optional closing tags | |
| if (isset($this->optional_closing_tags[$tag_lower]) ) | |
| { | |
| // Traverse ancestors to close all optional closing tags | |
| while (isset($this->optional_closing_tags[$tag_lower][strtolower($this->parent->tag)])) | |
| { | |
| $this->parent->_[HDOM_INFO_END] = 0; | |
| $this->parent = $this->parent->parent; | |
| } | |
| $node->parent = $this->parent; | |
| } | |
| $guard = 0; // prevent infinity loop | |
| $space = array($this->copy_skip($this->token_blank), '', ''); // [0] Space between tag and first attribute | |
| // attributes | |
| do | |
| { | |
| // Everything until the first equal sign should be the attribute name | |
| $name = $this->copy_until($this->token_equal); | |
| if ($name==='' && $this->char!==null && $space[0]==='') | |
| { | |
| break; | |
| } | |
| if ($guard===$this->pos) // Escape infinite loop | |
| { | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| continue; | |
| } | |
| $guard = $this->pos; | |
| // handle endless '<' | |
| if ($this->pos>=$this->size-1 && $this->char!=='>') { // Out of bounds before the tag ended | |
| $node->nodetype = HDOM_TYPE_TEXT; | |
| $node->_[HDOM_INFO_END] = 0; | |
| $node->_[HDOM_INFO_TEXT] = '<'.$tag . $space[0] . $name; | |
| $node->tag = 'text'; | |
| $this->link_nodes($node, false); | |
| return true; | |
| } | |
| // handle mismatch '<' | |
| if ($this->doc[$this->pos-1]=='<') { // Attributes cannot start after opening tag | |
| $node->nodetype = HDOM_TYPE_TEXT; | |
| $node->tag = 'text'; | |
| $node->attr = array(); | |
| $node->_[HDOM_INFO_END] = 0; | |
| $node->_[HDOM_INFO_TEXT] = substr($this->doc, $begin_tag_pos, $this->pos-$begin_tag_pos-1); | |
| $this->pos -= 2; | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| $this->link_nodes($node, false); | |
| return true; | |
| } | |
| if ($name!=='/' && $name!=='') { // this is a attribute name | |
| $space[1] = $this->copy_skip($this->token_blank); // [1] Whitespace after attribute name | |
| $name = $this->restore_noise($name); // might be a noisy name | |
| if ($this->lowercase) $name = strtolower($name); | |
| if ($this->char==='=') { // attribute with value | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| $this->parse_attr($node, $name, $space); // get attribute value | |
| } | |
| else { | |
| //no value attr: nowrap, checked selected... | |
| $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO; | |
| $node->attr[$name] = true; | |
| if ($this->char!='>') $this->char = $this->doc[--$this->pos]; // prev | |
| } | |
| $node->_[HDOM_INFO_SPACE][] = $space; | |
| $space = array($this->copy_skip($this->token_blank), '', ''); // prepare for next attribute | |
| } | |
| else // no more attributes | |
| break; | |
| } while ($this->char!=='>' && $this->char!=='/'); // go until the tag ended | |
| $this->link_nodes($node, true); | |
| $node->_[HDOM_INFO_ENDSPACE] = $space[0]; | |
| // handle empty tags (i.e. "<div/>") | |
| if ($this->copy_until_char('>')==='/') | |
| { | |
| $node->_[HDOM_INFO_ENDSPACE] .= '/'; | |
| $node->_[HDOM_INFO_END] = 0; | |
| } | |
| else | |
| { | |
| // reset parent | |
| if (!isset($this->self_closing_tags[strtolower($node->tag)])) $this->parent = $node; | |
| } | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| // If it's a BR tag, we need to set it's text to the default text. | |
| // This way when we see it in plaintext, we can generate formatting that the user wants. | |
| // since a br tag never has sub nodes, this works well. | |
| if ($node->tag == "br") | |
| { | |
| $node->_[HDOM_INFO_INNER] = $this->default_br_text; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Parse attribute from current document position | |
| * | |
| * @param object $node Node for the attributes | |
| * @param string $name Name of the current attribute | |
| * @param array $space Array for spacing information | |
| * @return void | |
| */ | |
| protected function parse_attr($node, $name, &$space) | |
| { | |
| // Per sourceforge: http://sourceforge.net/tracker/?func=detail&aid=3061408&group_id=218559&atid=1044037 | |
| // If the attribute is already defined inside a tag, only pay attention to the first one as opposed to the last one. | |
| // https://stackoverflow.com/a/26341866 | |
| if (isset($node->attr[$name])) | |
| { | |
| return; | |
| } | |
| $space[2] = $this->copy_skip($this->token_blank); // [2] Whitespace between "=" and the value | |
| switch ($this->char) { | |
| case '"': // value is anything between double quotes | |
| $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE; | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| $node->attr[$name] = $this->restore_noise($this->copy_until_char('"')); | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| break; | |
| case '\'': // value is anything between single quotes | |
| $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_SINGLE; | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| $node->attr[$name] = $this->restore_noise($this->copy_until_char('\'')); | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| break; | |
| default: // value is anything until the first space or end tag | |
| $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO; | |
| $node->attr[$name] = $this->restore_noise($this->copy_until($this->token_attr)); | |
| } | |
| // PaperG: Attributes should not have \r or \n in them, that counts as html whitespace. | |
| $node->attr[$name] = str_replace("\r", "", $node->attr[$name]); | |
| $node->attr[$name] = str_replace("\n", "", $node->attr[$name]); | |
| // PaperG: If this is a "class" selector, lets get rid of the preceeding and trailing space since some people leave it in the multi class case. | |
| if ($name == "class") { | |
| $node->attr[$name] = trim($node->attr[$name]); | |
| } | |
| } | |
| /** | |
| * Link node to parent node | |
| * | |
| * @param object $node Node to link to parent | |
| * @param bool $is_child True if the node is a child of parent | |
| * @return void | |
| */ | |
| // link node's parent | |
| protected function link_nodes(&$node, $is_child) | |
| { | |
| $node->parent = $this->parent; | |
| $this->parent->nodes[] = $node; | |
| if ($is_child) | |
| { | |
| $this->parent->children[] = $node; | |
| } | |
| } | |
| /** | |
| * Add tag as text node to current node | |
| * | |
| * @param string $tag Tag name | |
| * @return bool True on success | |
| */ | |
| protected function as_text_node($tag) | |
| { | |
| $node = new ums_simple_html_dom_node($this); | |
| ++$this->cursor; | |
| $node->_[HDOM_INFO_TEXT] = '</' . $tag . '>'; | |
| $this->link_nodes($node, false); | |
| $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| return true; | |
| } | |
| /** | |
| * Seek from the current document position to the first occurrence of a | |
| * character not defined by the provided string. Update the current document | |
| * position to the new position. | |
| * | |
| * @param string $chars A string containing every allowed character. | |
| * @return void | |
| */ | |
| protected function skip($chars) | |
| { | |
| $this->pos += strspn($this->doc, $chars, $this->pos); | |
| $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| } | |
| /** | |
| * Copy substring from the current document position to the first occurrence | |
| * of a character not defined by the provided string. | |
| * | |
| * @param string $chars A string containing every allowed character. | |
| * @return string Substring from the current document position to the first | |
| * occurrence of a character not defined by the provided string. | |
| */ | |
| protected function copy_skip($chars) | |
| { | |
| $pos = $this->pos; | |
| $len = strspn($this->doc, $chars, $pos); | |
| $this->pos += $len; | |
| $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| if ($len===0) return ''; | |
| return substr($this->doc, $pos, $len); | |
| } | |
| /** | |
| * Copy substring from the current document position to the first occurrence | |
| * of any of the provided characters. | |
| * | |
| * @param string $chars A string containing every character to stop at. | |
| * @return string Substring from the current document position to the first | |
| * occurrence of any of the provided characters. | |
| */ | |
| protected function copy_until($chars) | |
| { | |
| $pos = $this->pos; | |
| $len = strcspn($this->doc, $chars, $pos); | |
| $this->pos += $len; | |
| $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null; // next | |
| return substr($this->doc, $pos, $len); | |
| } | |
| /** | |
| * Copy substring from the current document position to the first occurrence | |
| * of the provided string. | |
| * | |
| * @param string $char The string to stop at. | |
| * @return string Substring from the current document position to the first | |
| * occurrence of the provided string. | |
| */ | |
| protected function copy_until_char($char) | |
| { | |
| if ($this->char===null) return ''; | |
| if (($pos = strpos($this->doc, $char, $this->pos))===false) { | |
| $ret = substr($this->doc, $this->pos, $this->size-$this->pos); | |
| $this->char = null; | |
| $this->pos = $this->size; | |
| return $ret; | |
| } | |
| if ($pos===$this->pos) return ''; | |
| $pos_old = $this->pos; | |
| $this->char = $this->doc[$pos]; | |
| $this->pos = $pos; | |
| return substr($this->doc, $pos_old, $pos-$pos_old); | |
| } | |
| /** | |
| * Remove noise from HTML content | |
| * | |
| * Noise is stored to {@see ums_simple_html_dom::$noise} | |
| * | |
| * @param string $pattern The regex pattern used for finding noise | |
| * @param bool $remove_tag True to remove the entire match. Default is false | |
| * to only remove the captured data. | |
| */ | |
| protected function remove_noise($pattern, $remove_tag=false) | |
| { | |
| global $debug_object; | |
| if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } | |
| $count = preg_match_all($pattern, $this->doc, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE); | |
| for ($i=$count-1; $i>-1; --$i) | |
| { | |
| $key = '___noise___'.sprintf('% 5d', count($this->noise)+1000); | |
| if (is_object($debug_object)) { $debug_object->debug_log(2, 'key is: ' . $key); } | |
| $idx = ($remove_tag) ? 0 : 1; // 0 = entire match, 1 = submatch | |
| $this->noise[$key] = $matches[$i][$idx][0]; | |
| $this->doc = substr_replace($this->doc, $key, $matches[$i][$idx][1], strlen($matches[$i][$idx][0])); | |
| } | |
| // reset the length of content | |
| $this->size = strlen($this->doc); | |
| if ($this->size>0) | |
| { | |
| $this->char = $this->doc[0]; | |
| } | |
| } | |
| /** | |
| * Restore noise to HTML content | |
| * | |
| * Noise is restored from {@see ums_simple_html_dom::$noise} | |
| * | |
| * @param string $text A subset of HTML containing noise | |
| * @return string The same content with noise restored | |
| */ | |
| function restore_noise($text) | |
| { | |
| global $debug_object; | |
| if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } | |
| while (($pos=strpos($text, '___noise___'))!==false) | |
| { | |
| // Sometimes there is a broken piece of markup, and we don't GET the pos+11 etc... token which indicates a problem outside of us... | |
| if (strlen($text) > $pos+15) | |
| { // todo: "___noise___1000" (or any number with four or more digits) in the DOM causes an infinite loop which could be utilized by malicious software | |
| $key = '___noise___'.$text[$pos+11].$text[$pos+12].$text[$pos+13].$text[$pos+14].$text[$pos+15]; | |
| if (is_object($debug_object)) { $debug_object->debug_log(2, 'located key of: ' . $key); } | |
| if (isset($this->noise[$key])) | |
| { | |
| $text = substr($text, 0, $pos).$this->noise[$key].substr($text, $pos+16); | |
| } | |
| else | |
| { | |
| // do this to prevent an infinite loop. | |
| $text = substr($text, 0, $pos).'UNDEFINED NOISE FOR KEY: '.$key . substr($text, $pos+16); | |
| } | |
| } | |
| else | |
| { | |
| // There is no valid key being given back to us... We must get rid of the ___noise___ or we will have a problem. | |
| $text = substr($text, 0, $pos).'NO NUMERIC NOISE KEY' . substr($text, $pos+11); | |
| } | |
| } | |
| return $text; | |
| } | |
| // Sometimes we NEED one of the noise elements. | |
| function search_noise($text) | |
| { | |
| global $debug_object; | |
| if (is_object($debug_object)) { $debug_object->debug_log_entry(1); } | |
| foreach($this->noise as $noiseElement) | |
| { | |
| if (strpos($noiseElement, $text)!==false) | |
| { | |
| return $noiseElement; | |
| } | |
| } | |
| } | |
| function __toString() | |
| { | |
| return $this->root->innertext(); | |
| } | |
| function __get($name) | |
| { | |
| switch ($name) | |
| { | |
| case 'outertext': | |
| return $this->root->innertext(); | |
| case 'innertext': | |
| return $this->root->innertext(); | |
| case 'plaintext': | |
| return $this->root->text(); | |
| case 'charset': | |
| return $this->_charset; | |
| case 'target_charset': | |
| return $this->_target_charset; | |
| } | |
| } | |
| // camel naming conventions | |
| function childNodes($idx=-1) {return $this->root->childNodes($idx);} | |
| function firstChild() {return $this->root->first_child();} | |
| function lastChild() {return $this->root->last_child();} | |
| function createElement($name, $value=null) {return ums_str_get_html("<$name>$value</$name>")->first_child();} | |
| function createTextNode($value) {return end(ums_str_get_html($value)->nodes);} | |
| function getElementById($id) {return $this->find("#$id", 0);} | |
| function getElementsById($id, $idx=null) {return $this->find("#$id", $idx);} | |
| function getElementByTagName($name) {return $this->find($name, 0);} | |
| function getElementsByTagName($name, $idx=-1) {return $this->find($name, $idx);} | |
| function loadFile() {$args = func_get_args();$this->load_file($args);} | |
| } | |