You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
7.7 KiB
248 lines
7.7 KiB
<?php
|
|
class cls_query
|
|
{
|
|
private static $content;
|
|
public static $debug = false;
|
|
|
|
public static function init($content)
|
|
{
|
|
self::$content = $content;
|
|
}
|
|
|
|
public static function query($query, $attr = "html")
|
|
{
|
|
$nodes = self::get_nodes($query);
|
|
$datas = self::get_datas($nodes, $attr);
|
|
return $datas;
|
|
}
|
|
|
|
protected static function is_char($char) {
|
|
return extension_loaded('mbstring') ? mb_eregi('\w', $char) : preg_match('@\w@', $char);
|
|
}
|
|
|
|
/**
|
|
* 从xpath中得到节点
|
|
*
|
|
* @param mixed $xpath
|
|
* @return void
|
|
* @author seatle <seatle@foxmail.com>
|
|
* @created time :2015-08-08 15:52
|
|
*/
|
|
private static function get_nodes($query)
|
|
{
|
|
// 把一到多个空格 替换成 一个空格
|
|
// 把 > 和 ~ 符号两边的空格去掉,因为没有用这两个符号,所以这里可以不这么做
|
|
// ul>li.className
|
|
$query = trim(
|
|
preg_replace('@\s+@', ' ',
|
|
preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query)
|
|
)
|
|
);
|
|
|
|
$nodes = array();
|
|
if (! $query)
|
|
{
|
|
return $nodes;
|
|
}
|
|
|
|
$query_arr = explode(" ", $query);
|
|
foreach ($query_arr as $k=>$v)
|
|
{
|
|
$path = $k == 0 ? $v : $path.' '.$v;
|
|
$node = array("path"=>(string)$path, "name"=>"", "id"=>"", "class"=>"", "other"=>array());
|
|
// 如果存在内容选择器
|
|
if (preg_match('@(.*?)\[(.*?)=[\'|"](.*?)[\'|"]\]@', $v, $matches) && !empty($matches[2]) && !empty($matches[3]))
|
|
{
|
|
// 把选择器过滤掉 [rel='topic']
|
|
$v = $matches[1];
|
|
$node['other'] = array(
|
|
'key'=>$matches[2],
|
|
'val'=>$matches[3],
|
|
);
|
|
}
|
|
|
|
// 如果存在 id
|
|
$id_arr = explode("#", $v);
|
|
$class_arr = explode(".", $v);
|
|
if (count($id_arr) === 2)
|
|
{
|
|
$node['name'] = $id_arr[0];
|
|
$node['id'] = $id_arr[1];
|
|
}
|
|
// 如果存在 class
|
|
elseif (count($class_arr) === 2)
|
|
{
|
|
$node['name'] = $class_arr[0];
|
|
$node['class'] = $class_arr[1];
|
|
}
|
|
// 如果没有样式
|
|
else
|
|
{
|
|
$node['name'] = $v;
|
|
}
|
|
$nodes[] = $node;
|
|
}
|
|
//print_r($nodes);
|
|
//exit;
|
|
return $nodes;
|
|
}
|
|
|
|
public static function get_datas($nodes, $attr = "html")
|
|
{
|
|
if (empty(self::$content))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$node_datas = array();
|
|
$count = count($nodes);
|
|
// 循环所有节点
|
|
foreach ($nodes as $i=>$node)
|
|
{
|
|
$is_last = $count == $i+1 ? true : false;
|
|
// 第一次
|
|
if ($i == 0)
|
|
{
|
|
$datas = array();
|
|
$datas = self::get_node_datas($node, self::$content, $attr, $is_last);
|
|
// 如果第一次都取不到数据,直接跳出循环
|
|
if(!$datas)
|
|
{
|
|
break;
|
|
}
|
|
$node_datas[$nodes[$i]['path']] = $datas;
|
|
}
|
|
else
|
|
{
|
|
$datas = array();
|
|
// 循环上一个节点的数组
|
|
foreach ($node_datas[$nodes[$i-1]['path']] as $v)
|
|
{
|
|
$datas = array_merge( $datas, self::get_node_datas($node, trim($v), $attr, $is_last) );
|
|
}
|
|
$node_datas[$nodes[$i]['path']] = $datas;
|
|
// 删除上一个节点,防止内存溢出,或者缓存到本地,再次使用?!
|
|
unset($node_datas[$nodes[$i-1]['path']]);
|
|
}
|
|
}
|
|
//print_r($datas);exit;
|
|
// 从数组中弹出最后一个元素
|
|
$node_datas = array_pop($node_datas);
|
|
//print_r($node_datas);
|
|
//exit;
|
|
return $node_datas;
|
|
}
|
|
|
|
/**
|
|
* 从节点中获取内容
|
|
* $regex = '@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i';
|
|
*
|
|
* @param mixed $node
|
|
* @param mixed $content
|
|
* @return void
|
|
* @author seatle <seatle@foxmail.com>
|
|
* @created time :2015-08-08 15:52
|
|
*/
|
|
private static function get_node_datas($node, $content, $attr = "html", $is_last = false)
|
|
{
|
|
$node_datas = $datas = array();
|
|
|
|
if (!empty($node['id']))
|
|
{
|
|
if ($node['name'])
|
|
$regex = '@<'.$node['name'].'[^>]+id\\s*=\\s*["|\']+?'.$node['id'].'\\s*[^>]+?>(.*?)</'.$node['name'].'>@is';
|
|
else
|
|
$regex = '@id\\s*=\\s*["|\']+?'.$node['id'].'\\s*[^>]+?>(.*?)<@is';
|
|
}
|
|
elseif (!empty($node['class']))
|
|
{
|
|
if ($node['name'])
|
|
$regex = '@<'.$node['name'].'[^>]+class\\s*=\\s*["|\']+?'.$node['class'].'\\s*[^>]+?>(.*?)</'.$node['name'].'>@is';
|
|
else
|
|
$regex = '@class\\s*=\\s*["|\']+?'.$node['class'].'\\s*[^>]+?>(.*?)<@is';
|
|
}
|
|
else
|
|
{
|
|
// 这里为是么是*,0次到多次,因为有可能是 <li>
|
|
$regex = '@<'.$node['name'].'[^>]*?>(.*?)</'.$node['name'].'>@is';
|
|
}
|
|
self::log("regex --- " . $regex);;
|
|
preg_match_all($regex, $content, $matches);
|
|
$all_datas = empty($matches[0]) ? array() : $matches[0];
|
|
$html_datas = empty($matches[1]) ? array() : $matches[1];
|
|
|
|
// 过滤掉选择器对不上的
|
|
foreach ($all_datas as $i=>$data)
|
|
{
|
|
// 如果有设置其他选择器,验证一下选择器
|
|
if (!empty($node['other']))
|
|
{
|
|
$regex = '@'.$node['other']['key'].'=[\'|"]'.$node['other']['val'].'[\'|"]@is';
|
|
self::log("regex other --- " . $regex);
|
|
// 过滤器对不上的,跳过
|
|
if (!preg_match($regex, $data, $matches))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
// 获取节点的html内容
|
|
if ($attr != "html" && $is_last)
|
|
{
|
|
$regex = '@'.$attr.'=[\'|"](.*?)[\'|"]@is';
|
|
preg_match($regex, $data, $matches);
|
|
$node_datas[] = empty($matches[1]) ? '' : trim($matches[1]);
|
|
}
|
|
// 获取节点属性名的值
|
|
else
|
|
{
|
|
$node_datas[] = trim($html_datas[$i]);
|
|
}
|
|
}
|
|
//echo " 11111 ========================================= \n";
|
|
//print_r($node_datas);
|
|
//echo " 22222 ========================================= \n\n\n";
|
|
return $node_datas;
|
|
}
|
|
|
|
/**
|
|
* 记录日志
|
|
* @param string $msg
|
|
* @return void
|
|
*/
|
|
private static function log($msg)
|
|
{
|
|
$msg = "[".date("Y-m-d H:i:s")."] " . $msg . "\n";
|
|
if (self::$debug)
|
|
{
|
|
echo $msg;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//$xpath = "ul.top-nav-dropdown li";
|
|
//$xpath = "i.zg-icon";
|
|
//print_r($nodes);
|
|
//exit;
|
|
// [^>]+ 不是>的字符重复一次到多次, ? 表示不贪婪
|
|
// \s 表示空白字符
|
|
// * 表示0次或者多次
|
|
// + 表示1次或者多次
|
|
//
|
|
// 后向引用,表示表达式中,从左往右数,第一个左括号对应的括号内的内容。
|
|
// \\0 表示整个表达式
|
|
// \\1表示第1个表达式
|
|
// \\2表示第2个表达式
|
|
// $regex = '@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i';
|
|
//preg_match_all($regex, $content, $matches);
|
|
//print_r($matches);
|
|
//exit;
|
|
|
|
// 用法
|
|
//$content = file_get_contents("./test.html");
|
|
//$query = "ul#top-nav-profile-dropdown li a";
|
|
//$query = "div#zh-profile-following-topic a.link[href='/topic/19550937']";
|
|
//cls_query::init($content);
|
|
//$list = cls_query::query($query, "href");
|
|
//print_r($list);
|
|
|
|
|