├── index.php ├── GetWangYiYunInfo.php ├── 网易云音乐热门评论_2019-04-03.txt ├── QueryList.php └── phpQuery.php /index.php: -------------------------------------------------------------------------------- 1 | getHtmlInfo($this->url); 25 | } 26 | 27 | /** 28 | * 获取云音乐热歌榜的榜单歌曲名以及链接 29 | * @param $url 30 | */ 31 | private function getHtmlInfo($url) 32 | { 33 | // 设置请求头 34 | $headers = array( 35 | 'Host:music.163.com', 36 | 'Refere:http://music.163.com/', 37 | // 模拟浏览器设置 User-Agent ,否则取到的数据不完整 38 | 'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' 39 | ); 40 | $htmlInfo = $this->httpGet($url,$headers); 41 | $rules = array( 42 | 'a' => array('.f-hide>li>a','href'), 43 | 'text' => array('.f-hide>li>a','text') 44 | ); 45 | $data = \QL\QueryList::Query($htmlInfo,$rules); 46 | $this->makeData($data); 47 | } 48 | 49 | /** 50 | * 修改获得的object类型以及拼接歌曲完整链接 51 | * @param $data 52 | */ 53 | private function makeData($data) 54 | { 55 | $tmp = ''; 56 | foreach ($data->data as $k=>$v){ 57 | $tmp[$k]['text'] = $v['text']; 58 | $songId = explode('/song?id=',$v['a']); 59 | $tmp[$k]['songId'] = $songId[1]; 60 | } 61 | $this->getComment($tmp); 62 | } 63 | 64 | /** 65 | * 获取评论 66 | * @param $data 67 | */ 68 | private function getComment($data) 69 | { 70 | // 设置请求头 71 | $headers = array( 72 | 'Accept:*/*', 73 | 'Accept-Language:zh-CN,zh;q=0.9', 74 | 'Connection:keep-alive', 75 | 'Content-Type:application/x-www-form-urlencoded', 76 | 'Host:music.163.com', 77 | 'Origin:https://music.163.com', 78 | // 模拟浏览器设置 User-Agent ,否则取到的数据不完整 79 | 'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' 80 | ); 81 | for ($i=0; $i<$this->music_num; $i++){ 82 | // 拼接歌曲的url 83 | $url = 'https://music.163.com/weapi/v1/resource/comments/R_SO_4_'.$data[$i]['songId'].'?csrf_token='; 84 | // 拼接加密 params 用到的第一个参数 85 | $first_param = '{"rid":"R_SO_4_'.$data[$i]['songId'].'","offset":"0","total":"true","limit":"20","csrf_token":""}'; 86 | $params = array('params' => $this->aesGetParams($first_param), 'encSecKey' => $this->getEncSecKey()); 87 | $htmlInfo = $this->httpPost($url,$headers,http_build_query($params)); 88 | // 记录评论 89 | $this->saveComment(json_decode($htmlInfo,true),$data[$i]['text']); 90 | // 没有设置代理IP,间隔2秒执行 91 | sleep(2); 92 | } 93 | } 94 | 95 | /** 96 | * 加密获取params 97 | * @param $param // 待加密的明文信息数据 98 | * @param string $method // 加密算法 99 | * @param string $key // key 100 | * @param string $options // options 是以下标记的按位或: OPENSSL_RAW_DATA 、 OPENSSL_ZERO_PADDING 101 | * @param string $iv // 非 NULL 的初始化向量 102 | * @return string 103 | * 104 | * $key 在加密 params 中第一次用的是固定的第四个参数 0CoJUm6Qyw8W8jud,在第二次加密中用的是 js 中随机生成的16位字符串 105 | */ 106 | private function aesGetParams($param,$method = 'AES-128-CBC',$key = 'JK1M5sQAEcAZ46af',$options = '0',$iv = '0102030405060708') 107 | { 108 | $firstEncrypt = openssl_encrypt($param,$method,'0CoJUm6Qyw8W8jud',$options,$iv); 109 | $secondEncrypt = openssl_encrypt($firstEncrypt,$method,$key,$options,$iv); 110 | return $secondEncrypt; 111 | } 112 | 113 | /** 114 | * encSecKey 在 js 中有 res 方法加密。 115 | * 其中三个参数分别为上面随机生成的16为字符串,第二个参数 $second_param,第三个参数 $third_param 都是固定写死的,这边使用抄下来的一个固定 encSecKey 116 | * @return bool 117 | */ 118 | private function getEncSecKey() 119 | { 120 | $getEncSecKey = '2a98b8ea60e8e0dd0369632b14574cf8d4b7a606349669b2609509978e1b5f96ed8fbe53a90c0bb74497cd2eb965508bff5bfa065394a52ea362539444f18f423f46aded5ed9a1788d110875fb976386aa4f5d784321433549434bccea5f08d1888995bdd2eb015b2236f5af15099e3afbb05aa817c92bfe3214671e818ea16b'; 121 | 122 | return $getEncSecKey; 123 | } 124 | 125 | /** 126 | * curl 发送 get 请求 127 | * @param $url 128 | * @param $header 129 | * @return mixed 130 | */ 131 | private function httpGet($url,$header) 132 | { 133 | $ch = curl_init(); 134 | curl_setopt($ch, CURLOPT_URL, $url); 135 | curl_setopt($ch, CURLOPT_HEADER, 0); // true获取响应头的信息 136 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 137 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 138 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查 139 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转 140 | curl_setopt($ch, CURLOPT_AUTOREFERER, 1); // 自动设置Referer 141 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); // 设置等待时间 142 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 设置cURL允许执行的最长秒数 143 | $res = curl_exec($ch); 144 | curl_close($ch); 145 | return $res; 146 | } 147 | 148 | /** 149 | * curl 发送 post 请求 150 | * @param $url 151 | * @param $header 152 | * @param $data 153 | * @return mixed 154 | */ 155 | private function httpPost($url,$header,$data) 156 | { 157 | $ch = curl_init(); 158 | curl_setopt($ch, CURLOPT_URL, $url); 159 | curl_setopt($ch, CURLOPT_POST,true); 160 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 161 | curl_setopt($ch, CURLOPT_HEADER, 0); // 0不带头文件,1带头文件(返回值中带有头文件) 162 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 163 | curl_setopt($ch, CURLOPT_POSTFIELDS , $data); 164 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查 165 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转 166 | curl_setopt($ch, CURLOPT_AUTOREFERER, 1); // 自动设置Referer 167 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); //设置等待时间 168 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数 169 | $content = curl_exec($ch); 170 | curl_close($ch); 171 | return $content; 172 | } 173 | 174 | /** 175 | * 写入文件 176 | * @param $songName 177 | * @param $data 178 | */ 179 | private function saveComment($data,$songName) 180 | { 181 | // 读写方式打开文件,将文件指针指向文件末尾。如果文件不存在,则创建。 182 | $myFile = fopen(iconv('UTF-8', 'GB18030', "网易云音乐热门评论_".date("Y-m-d",time()).".txt"), "a+"); 183 | 184 | $hotCommentsLength = count($data['hotComments']); 185 | for ($i=0;$i<$hotCommentsLength;$i++){ 186 | $text = "歌名:".$songName.PHP_EOL; 187 | $text .= "评论:".$data['hotComments'][$i]['content'].PHP_EOL; 188 | $text .= "时间:".date("Y-m-d H:i:s",$data['hotComments'][$i]['time'] / 1000).PHP_EOL; 189 | $text .= "用户名:".$data['hotComments'][$i]['user']['nickname'].PHP_EOL; 190 | $text .= "点赞数:".$data['hotComments'][$i]['likedCount'].PHP_EOL; 191 | $text .= "******************************************************".PHP_EOL; 192 | // 写入文件,第一个参数要写入的文件名,第二个参数是被写的字符串 193 | fwrite($myFile,$text); 194 | } 195 | 196 | // 关闭打开的文件 197 | fclose($myFile); 198 | } 199 | } -------------------------------------------------------------------------------- /网易云音乐热门评论_2019-04-03.txt: -------------------------------------------------------------------------------- 1 | 歌名:绿色 2 | 评论:我曾经最爱的 绿色 3 | 时间:2019-02-14 08:31:49 4 | 用户名:陈雪凝Sherly 5 | 点赞数:347604 6 | ****************************************************** 7 | 歌名:绿色 8 | 评论:生活不允许普通人内向 ​​​ 9 | 时间:2019-02-14 12:20:01 10 | 用户名:薛阗甛 11 | 点赞数:190535 12 | ****************************************************** 13 | 歌名:绿色 14 | 评论:陈雪凝我非常喜欢你,我只是你众多粉丝里面的一个学生党,自从2016听到你的白山茶我非常喜欢,你唱的歌有一种吸引人的魅力,还有你的声音非常好听,长的也很可爱,像我这样安静的女生一定会喜欢你唱的歌,所以愿你2019里每天开心,愿你难过的时候,想到我们这些雪花在你身后支持着你,新的一年里加油哦 15 | 时间:2019-02-14 07:53:18 16 | 用户名:丧茶的酒馆 17 | 点赞数:157345 18 | ****************************************************** 19 | 歌名:绿色 20 | 评论:我那时候的确太喜欢你,可谁让我们遇见的太早了呢,当初我们都太骄傲,爱自己的自尊心胜过对方,总觉得错一个人,没什么大不了的,毕竟前路漫长,还有很多时间可以浪费,总觉得自己一定会遇见很多喜欢的人,可当我们真正离开过后,才发现喜欢从来不是一个人的事,当你连回忆都吝于给我时,我才懂了绿色 21 | 时间:2019-02-14 12:05:31 22 | 用户名:木子镜 23 | 点赞数:73909 24 | ****************************************************** 25 | 歌名:绿色 26 | 评论:你不努力可能连自己喜欢的学校都考不了 你喜欢的人那么优秀 他也是努力来的 只有自己优秀起来 他的眼里就发现这个女孩子可以哦 不管结局怎么样 你为你自己努力了 为他努力了 多努力为以后的路更好走 后来发现 那些快乐都不是真的快乐 再后来 你会后悔 为什么当初不再努力一点 27 | 时间:2019-02-14 09:07:33 28 | 用户名:怡欧妮1 29 | 点赞数:73295 30 | ****************************************************** 31 | 歌名:绿色 32 | 评论:幸好你给的失望够多 我的离开也算值得 33 | 时间:2019-02-14 11:02:33 34 | 用户名:我怀疑这奇遇只是恶作剧 35 | 点赞数:55180 36 | ****************************************************** 37 | 歌名:绿色 38 | 评论:一天一天期待我们再次相遇 39 | 一点一点整理好扰乱的心绪 40 | 当深夜里吵醒了梦呓 41 | 依旧回荡静谧的甜蜜 42 | 时间:2019-02-14 15:16:35 43 | 用户名:谢长欣Xavier 44 | 点赞数:44760 45 | ****************************************************** 46 | 歌名:绿色 47 | 评论:若不是你突然闯进我生活 48 | 又怎么会有故事 49 | 怎么会有满船清梦💚 50 | 时间:2019-02-14 09:35:26 51 | 用户名:BINNiNG- 52 | 点赞数:44554 53 | ****************************************************** 54 | 歌名:绿色 55 | 评论:陈雪凝我非常喜欢你,我只是你众多粉丝里面的一个学生党,自从2016听到你的白山茶我非常 56 | 喜欢,你唱的歌有一种吸引人的魅力,还有你的声音非常好听,长的也很可爱,像我这样安静的女生一定会喜欢你唱的歌,所以愿你2019里每天开心,愿你难过的时候,想到我们这些雪花在你身后支持着你,新的一年里加油 57 | 时间:2019-02-14 08:34:56 58 | 用户名:陈雪凝-凝 59 | 点赞数:43523 60 | ****************************************************** 61 | 歌名:绿色 62 | 评论:在一起的第1999天,分开的第一天,祝你幸福[憨笑] 63 | 时间:2019-02-25 08:28:44 64 | 用户名:oo高先森 65 | 点赞数:38251 66 | ****************************************************** 67 | 歌名:绿色 68 | 评论:期待了好久的绿色 今天终于发出来啦 这首歌是汽水熬夜写的又在凌晨发布 真的很心疼很心疼她。今年的我即将高考 是汽水给了我动力 汽水你那么优秀 能不能等等我啊 我一定会去看你的巡演 你要等我啊。 69 | 时间:2019-02-14 08:38:51 70 | 用户名:怡欧妮1 71 | 点赞数:26743 72 | ****************************************************** 73 | 歌名:绿色 74 | 评论:你说你喜欢我为什么还要背着我去找她💔 75 | 时间:2019-02-14 08:03:54 76 | 用户名:奺扄 77 | 点赞数:23073 78 | ****************************************************** 79 | 歌名:绿色 80 | 评论: 💚祝大家情人节快乐💚 81 | 时间:2019-02-14 11:03:59 82 | 用户名:辛浩辰_xhc 83 | 点赞数:15911 84 | ****************************************************** 85 | 歌名:绿色 86 | 评论:晚风拂柳,笛声消残, 87 | 知其了,何为声悲,泪已嫣然落下 88 | 忽而春风起,绿色开满地。 89 | 时间:2019-02-14 20:21:06 90 | 用户名:清风九许 91 | 点赞数:14649 92 | ****************************************************** 93 | 歌名:绿色 94 | 评论:绿色是你的保护色 95 | 时间:2019-02-14 11:04:27 96 | 用户名:芒果豆沙 97 | 点赞数:12466 98 | ****************************************************** 99 | 歌名:我曾 100 | 评论:怎么被自己感动到了。 101 | 时间:2019-02-03 00:06:10 102 | 用户名:隔壁老樊c 103 | 点赞数:223537 104 | ****************************************************** 105 | 歌名:我曾 106 | 评论: 107 | 有时关不上冰箱的门 脚趾撞到了桌腿 临出门找不到想要的东西 突然忍不住掉泪 你觉得小题大作 只有我自己知道为什么 108 | 时间:2019-02-03 02:15:36 109 | 用户名:小忯 110 | 点赞数:127138 111 | ****************************************************** 112 | 歌名:我曾 113 | 评论:我曾渴望长大 我曾羡慕着成年后的世界 我现在成年了 我真的好累 114 | 时间:2019-02-03 01:37:48 115 | 用户名:詹嘉亮 116 | 点赞数:82555 117 | ****************************************************** 118 | 歌名:我曾 119 | 评论:总不能还没努力就向生活妥协吧 120 | 时间:2019-02-03 00:24:03 121 | 用户名:博文Fighting 122 | 点赞数:66824 123 | ****************************************************** 124 | 歌名:我曾 125 | 评论:只要以后他的合法丈夫是我 现在的苦 我统统接下 126 | 时间:2019-02-08 00:57:18 127 | 用户名:而迷 128 | 点赞数:65165 129 | ****************************************************** 130 | 歌名:我曾 131 | 评论:2019年了,我今年还要喜欢梁小姐,你要在南京等我哟,我真的不该欺骗和隐瞒你,但是好好喜欢你,好喜欢好喜欢,我也不知道还能喜欢你多久,得了肿瘤嘛基因病治不好那种,现在瘤体慢慢长起来了。我只想好好陪着你,你真的对我很重要,早就知道自己得了绝症,在这个世界还有很多遗憾,最舍不得还是你。 132 | 时间:2019-02-04 21:49:45 133 | 用户名:引山洪ii 134 | 点赞数:61572 135 | ****************************************************** 136 | 歌名:我曾 137 | 评论:马航调查团队解散,四年,我还在等你,可是他们不找了,他们找了四年,连你的一丝希望都没有给我带来,如今他们解散,留给我的只有一笔钱,可是金钱是冰冷的,我想要的是牵着你的手,感受你手心的温暖啊,宝贝,你去哪了,我希望你只是穿越了,在某一年某一天突然回来,告诉我飞机晚点了…没关系的啊! 138 | 时间:2019-02-16 10:33:04 139 | 用户名:念捻不忘 140 | 点赞数:53927 141 | ****************************************************** 142 | 歌名:我曾 143 | 评论:我曾以为了无牵挂是句空话 144 | 我曾轻笑茕茕孑立只属于旁人 145 | 后来我被梦想推向了断崖 被妄想侵蚀了孤勇 146 | 又被坦诚刺穿了胸口 147 | 她说人生是笑的有滋有味 148 | 我说还有哭的五味杂陈 149 | 时间:2019-02-03 02:30:48 150 | 用户名:是白空 151 | 点赞数:31746 152 | ****************************************************** 153 | 歌名:我曾 154 | 评论:我多想让过去重来 再给我一次机会 155 | 时间:2019-02-03 09:26:30 156 | 用户名:帐号已注销 157 | 点赞数:22319 158 | ****************************************************** 159 | 歌名:我曾 160 | 评论:我曾把所有希望的放在你身上 你带给了我绝望 161 | 我曾无条件的去相信你的谎言 你带给了我欺骗 162 | 我曾 163 | 我曾爱过你[心碎] 164 | 时间:2019-02-03 01:26:05 165 | 用户名:小攀哟 166 | 点赞数:16362 167 | ****************************************************** 168 | 歌名:我曾 169 | 评论:我曾爱你 孤注一掷 170 | 时间:2019-02-03 02:12:04 171 | 用户名:操昏 172 | 点赞数:15574 173 | ****************************************************** 174 | 歌名:我曾 175 | 评论:晚安,我记得你以前很少熬夜的,早点睡吧,祝你能有个甜甜的梦。如果你哭了,幺妻琉零零洱妻妖零零琉我陪你聊天。我只是想说我每晚都失眠,我现在过的并不好……只是希望看到评论的你们都幸福 176 | 以后的以后我要开个烘焙店,它的名字叫做“很久以前”[猫] 177 | 时间:2019-03-24 00:00:51 178 | 用户名:小白兔的故事你还在听吗 179 | 点赞数:14825 180 | ****************************************************** 181 | 歌名:我曾 182 | 评论:破镜能够重圆吗?若非血脉相连的至亲骨肉,谁又能合上这割心的裂缝? 183 | 你我本自陌生缘起,终以陌路缘灭。 184 | 时间:2019-03-28 00:08:25 185 | 用户名:一人之森 186 | 点赞数:12038 187 | ****************************************************** 188 | 歌名:我曾 189 | 评论:关不上冰箱的门是因为想见你太着急了 脚趾撞到桌脚是因为一直想你分心了 出门找不到想要的东西是因为想快点见到你太高兴了 这些其实都是因为他 而他却不知道 190 | 时间:2019-02-03 08:34:43 191 | 用户名:你找到幸运了吗 192 | 点赞数:11264 193 | ****************************************************** 194 | 歌名:我曾 195 | 评论:猫不能下水,所以老鼠也是不错的食物,鱼不能上岸,小虾也能饱腹,人生要珍惜眼前的美好,并不是谁都替代不了,只是接受不了这样的现实罢了,猫吃老鼠也香,鱼吃虾也快活,其实你不去爱她也能幸福 196 | 时间:2019-03-03 17:01:44 197 | 用户名:一只林叔叔 198 | 点赞数:10434 199 | ****************************************************** 200 | 歌名:出山 201 | 评论:不说别的,粥大爷厉害!!!! 202 | 203 | 不是说好了十一发么[奸笑] 204 | 205 | 有能耐你忍着呀[奸笑] 206 | 207 | 呵,女人啊[奸笑] 208 | 209 | 嘴上说着不要,心里却急的像小猫乱抓一样[奸笑] 210 | 211 | 这样的女人不正是我所爱的嘛[奸笑] 212 | 时间:2018-09-28 13:40:43 213 | 用户名:花粥迷弟二狗子 214 | 点赞数:525052 215 | ****************************************************** 216 | 歌名:出山 217 | 评论:前两天刚好在北京过中秋,我就去胜男家里找她玩,然后在附近吃了串,相谈甚欢。 218 | 为了记录这次短暂的会面,我拉着她赶在我离开北京之前录完了这首歌,所以,夸就完事了! 219 | 不要吝啬你们赞美的词藻!今天我就是这个峡谷里最酷的粥! 220 | 221 | --花粥《出山》封面简介 222 | 时间:2018-09-28 14:30:01 223 | 用户名:花粥迷弟二狗子 224 | 点赞数:180440 225 | ****************************************************** 226 | 歌名:出山 227 | 评论:哇,那几句京腔好好听鸭[色] 228 | 时间:2018-09-28 13:48:53 229 | 用户名:Mrrrrr大昕 230 | 点赞数:119413 231 | ****************************************************** 232 | 歌名:出山 233 | 评论:人间荒唐市侩,不如山中做怪。 234 | 时间:2018-09-28 15:06:19 235 | 用户名:Therrier 236 | 点赞数:100615 237 | ****************************************************** 238 | 歌名:出山 239 | 评论:她可能没网费了 240 | 时间:2018-12-21 23:06:54 241 | 用户名:这是肖邦 242 | 点赞数:72350 243 | ****************************************************** 244 | 歌名:出山 245 | 评论:我走过山时,山不说话, 246 | 247 | 我路过海时,海不回答, 248 | 249 | 小毛驴滴滴答答,倚天剑伴我走天涯。 250 | 251 | 大家都说我因为爱着杨过大侠, 252 | 253 | 才在峨嵋山上出了家, 254 | 255 | 其实我只是爱上了峨嵋山上的云和霞, 256 | 257 | 像极了十六岁那年的烟花。 258 | 时间:2018-09-30 03:05:12 259 | 用户名:歌与猫呐 260 | 点赞数:67250 261 | ****************************************************** 262 | 歌名:出山 263 | 评论:像两只山间女鬼翘腿点烟谈论人间俗世 264 | 时间:2018-11-12 16:23:25 265 | 用户名:五花肉超好吃 266 | 点赞数:61038 267 | ****************************************************** 268 | 歌名:出山 269 | 评论:手握日月摘星辰,世间无我这般人。 270 | 天上地下宙宇中,无人可敌我称尊。 271 | 霸天绝地何人挡,贸然一人弑天殇。 272 | 血路凋零残花亡,残躯镇世仍为皇。 273 | ——唐家三少《神印王座》 274 | 时间:2018-09-28 19:03:03 275 | 用户名:大大-大师兄 276 | 点赞数:37173 277 | ****************************************************** 278 | 歌名:出山 279 | 评论:南京老鸭粉丝汤配蛋炒饭真是绝了! 280 | 时间:2018-09-28 13:34:44 281 | 用户名:战强 282 | 点赞数:33125 283 | ****************************************************** 284 | 歌名:出山 285 | 评论:唱副歌的时候,胜男一不小心唱成了戏腔。其实本来没那个安排,但当我第一次听她唱戏腔的时候,我在棚里激动的差点要窜起来,真的很合适。----------花粥和他的朋友们 286 | 时间:2018-10-06 08:37:03 287 | 用户名:野原堃之助 288 | 点赞数:32627 289 | ****************************************************** 290 | 歌名:出山 291 | 评论:尚未佩妥剑 转眼便江湖 愿历尽千帆 归来仍少年 292 | 时间:2018-09-28 19:18:22 293 | 用户名:赴见 294 | 点赞数:23073 295 | ****************************************************** 296 | 歌名:出山 297 | 评论:老哥666 298 | 时间:2018-12-23 17:27:01 299 | 用户名:北辰new 300 | 点赞数:18043 301 | ****************************************************** 302 | 歌名:出山 303 | 评论:憋唆话,要上网没钱呢, 304 | 时间:2018-12-24 06:59:05 305 | 用户名:潇洒你冰gg 306 | 点赞数:17598 307 | ****************************************************** 308 | 歌名:出山 309 | 评论:有人迷途知返,便是苦尽甘来。 310 | 时间:2018-09-28 19:08:07 311 | 用户名:桃迹 312 | 点赞数:14231 313 | ****************************************************** 314 | 歌名:出山 315 | 评论:这条评论我想让它的赞到88888 316 | 时间:2018-12-25 16:12:39 317 | 用户名:此花我柚美如画 318 | 点赞数:6121 319 | ****************************************************** 320 | -------------------------------------------------------------------------------- /QueryList.php: -------------------------------------------------------------------------------- 1 | array('.unit h1','text'))); 20 | print_r($hj->data); 21 | //回调函数1 22 | function callfun1($content,$key) 23 | { 24 | return '回调函数1:'.$key.'-'.$content; 25 | } 26 | class HJ{ 27 | //回调函数2 28 | static public function callfun2($content,$key) 29 | { 30 | return '回调函数2:'.$key.'-'.$content; 31 | } 32 | } 33 | //获取CSDN文章页下面的文章标题和内容 34 | $url = 'http://www.csdn.net/article/2014-06-05/2820091-build-or-buy-a-mobile-game-backend'; 35 | $rules = array( 36 | 'title'=>array('h1','text','','callfun1'), //获取纯文本格式的标题,并调用回调函数1 37 | 'summary'=>array('.summary','text','-input strong'), //获取纯文本的文章摘要,但保strong标签并去除input标签 38 | 'content'=>array('.news_content','html','div a -.copyright'), //获取html格式的文章内容,但过滤掉div和a标签,去除类名为copyright的元素 39 | 'callback'=>array('HJ','callfun2') //调用回调函数2作为全局回调函数 40 | ); 41 | $rang = '.left'; 42 | $hj = QueryList::Query($url,$rules,$rang); 43 | print_r($hj->data); 44 | //继续获取右边相关热门文章列表的标题以及链接地址 45 | $hj->setQuery(array('title'=>array('','text'),'url'=>array('a','href')),'#con_two_2 li'); 46 | //输出数据 47 | echo $hj->getData(); 48 | 49 | */ 50 | class QueryList 51 | { 52 | public $data; 53 | public $html; 54 | private $page; 55 | private $pqHtml; 56 | private $outputEncoding = false; 57 | private $inputEncoding = false; 58 | private $htmlEncoding; 59 | public static $logger = null; 60 | public static $instances; 61 | public function __construct() { 62 | } 63 | /** 64 | * 静态方法,访问入口 65 | * @param string $page 要抓取的网页URL地址(支持https);或者是html源代码 66 | * @param array $rules 【选择器数组】说明:格式array("名称"=>array("选择器","类型"[,"标签过滤列表"][,"回调函数"]),.......[,"callback"=>"全局回调函数"]); 67 | * 【选择器】说明:可以为任意的jQuery选择器语法 68 | * 【类型】说明:值 "text" ,"html" ,"HTML标签属性" , 69 | * 【标签过滤列表】:可选,要过滤的选择器名,多个用空格隔开,当标签名前面添加减号(-)时(此时标签可以为任意的元素选择器),表示移除该标签以及标签内容,否则当【类型】值为text时表示需要保留的HTML标签,为html时表示要过滤掉的HTML标签 70 | * 【回调函数】/【全局回调函数】:可选,字符串(函数名) 或 数组(array("类名","类的静态方法")),回调函数应有俩个参数,第一个参数是选择到的内容,第二个参数是选择器数组下标,回调函数会覆盖全局回调函数 71 | * 72 | * @param string $range 【块选择器】:指 先按照规则 选出 几个大块 ,然后再分别再在块里面 进行相关的选择 73 | * @param string $outputEncoding【输出编码格式】指要以什么编码输出(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则不改变原字符串编码 74 | * @param string $inputEncoding 【输入编码格式】明确指定输入的页面编码格式(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则自动识别 75 | * @param bool|false $removeHead 【是否移除页面头部区域】 乱码终极解决方案 76 | * @return mixed 77 | */ 78 | public static function Query($page,array $rules, $range = '', $outputEncoding = null, $inputEncoding = null,$removeHead = false) 79 | { 80 | return self::getInstance()->_query($page, $rules, $range, $outputEncoding, $inputEncoding,$removeHead); 81 | } 82 | /** 83 | * 运行QueryList扩展 84 | * @param $class 85 | * @param array $args 86 | * @return mixed 87 | * @throws Exception 88 | */ 89 | public static function run($class,$args = array()) 90 | { 91 | $extension = self::getInstance("QL\\Ext\\{$class}"); 92 | return $extension->run($args); 93 | } 94 | /** 95 | * 日志设置 96 | * @param $handler 97 | */ 98 | public static function setLog($handler) 99 | { 100 | if(class_exists('Monolog\Logger')) 101 | { 102 | if(is_string($handler)) 103 | { 104 | $handler = new StreamHandler($handler,Logger::INFO); 105 | } 106 | self::$logger = new Logger('QueryList'); 107 | self::$logger->pushHandler($handler); 108 | }else{ 109 | throw new Exception("You need to install the package [monolog/monolog]"); 110 | 111 | } 112 | 113 | } 114 | /** 115 | * 获取任意实例 116 | * @return mixed 117 | * @throws Exception 118 | */ 119 | public static function getInstance() 120 | { 121 | $args = func_get_args(); 122 | count($args) || $args = array('QL\QueryList'); 123 | $key = md5(serialize($args)); 124 | $className = array_shift($args); 125 | if(!class_exists($className)) { 126 | throw new Exception("no class {$className}"); 127 | } 128 | if(!isset(self::$instances[$key])) { 129 | $rc = new ReflectionClass($className); 130 | self::$instances[$key] = $rc->newInstanceArgs($args); 131 | } 132 | return self::$instances[$key]; 133 | } 134 | /** 135 | * 获取目标页面源码(主要用于调试) 136 | * @param bool|true $rel 137 | * @return string 138 | */ 139 | public function getHtml($rel = true) 140 | { 141 | return $rel?$this->qpHtml:$this->html; 142 | } 143 | /** 144 | * 获取采集结果数据 145 | * @param callback $callback 146 | * @return array 147 | */ 148 | public function getData($callback = null) 149 | { 150 | if(is_callable($callback)){ 151 | return array_map($callback,$this->data); 152 | } 153 | return $this->data; 154 | } 155 | /** 156 | * 重新设置选择器 157 | * @param $rules 158 | * @param string $range 159 | * @param string $outputEncoding 160 | * @param string $inputEncoding 161 | * @param bool|false $removeHead 162 | * @return QueryList 163 | */ 164 | public function setQuery(array $rules, $range = '',$outputEncoding = null, $inputEncoding = null,$removeHead = false) 165 | { 166 | return $this->_query($this->html,$rules, $range, $outputEncoding, $inputEncoding,$removeHead); 167 | } 168 | private function _query($page,array $rules, $range, $outputEncoding, $inputEncoding,$removeHead) 169 | { 170 | $this->data = array(); 171 | $this->page = $page; 172 | $this->html = $this->_isURL($this->page)?$this->_request($this->page):$this->page; 173 | $outputEncoding && $this->outputEncoding = $outputEncoding; 174 | $inputEncoding && $this->inputEncoding = $inputEncoding; 175 | $removeHead && $this->html = $this->_removeHead($this->html); 176 | $this->pqHtml = ''; 177 | if(empty($this->html)){ 178 | $this->_log('The received content is empty!','error'); 179 | trigger_error('The received content is empty!',E_USER_NOTICE); 180 | } 181 | //获取编码格式 182 | $this->htmlEncoding = $this->inputEncoding?$this->inputEncoding:$this->_getEncode($this->html); 183 | // $this->html = $this->_removeTags($this->html,array('script','style')); 184 | $this->regArr = $rules; 185 | $this->regRange = $range; 186 | $this->_getList(); 187 | $this->_log(); 188 | return $this; 189 | } 190 | private function _getList() 191 | { 192 | $this->inputEncoding && phpQuery::$defaultCharset = $this->inputEncoding; 193 | $document = phpQuery::newDocumentHTML($this->html); 194 | $this->qpHtml = $document->htmlOuter(); 195 | if (!empty($this->regRange)) { 196 | $robj = pq($document)->find($this->regRange); 197 | $i = 0; 198 | foreach ($robj as $item) { 199 | while (list($key, $reg_value) = each($this->regArr)) { 200 | if($key=='callback')continue; 201 | $tags = isset($reg_value[2])?$reg_value[2]:''; 202 | $iobj = pq($item)->find($reg_value[0]); 203 | switch ($reg_value[1]) { 204 | case 'text': 205 | $this->data[$i][$key] = $this->_allowTags(pq($iobj)->html(),$tags); 206 | break; 207 | case 'html': 208 | $this->data[$i][$key] = $this->_stripTags(pq($iobj)->html(),$tags); 209 | break; 210 | default: 211 | $this->data[$i][$key] = pq($iobj)->attr($reg_value[1]); 212 | break; 213 | } 214 | if(isset($reg_value[3])){ 215 | $this->data[$i][$key] = call_user_func($reg_value[3],$this->data[$i][$key],$key); 216 | }else if(isset($this->regArr['callback'])){ 217 | $this->data[$i][$key] = call_user_func($this->regArr['callback'],$this->data[$i][$key],$key); 218 | } 219 | } 220 | //重置数组指针 221 | reset($this->regArr); 222 | $i++; 223 | } 224 | } else { 225 | while (list($key, $reg_value) = each($this->regArr)) { 226 | if($key=='callback')continue; 227 | $document = phpQuery::newDocumentHTML($this->html); 228 | $tags = isset($reg_value[2])?$reg_value[2]:''; 229 | $lobj = pq($document)->find($reg_value[0]); 230 | $i = 0; 231 | foreach ($lobj as $item) { 232 | switch ($reg_value[1]) { 233 | case 'text': 234 | $this->data[$i][$key] = $this->_allowTags(pq($item)->html(),$tags); 235 | break; 236 | case 'html': 237 | $this->data[$i][$key] = $this->_stripTags(pq($item)->html(),$tags); 238 | break; 239 | default: 240 | $this->data[$i][$key] = pq($item)->attr($reg_value[1]); 241 | break; 242 | } 243 | if(isset($reg_value[3])){ 244 | $this->data[$i][$key] = call_user_func($reg_value[3],$this->data[$i][$key],$key); 245 | }else if(isset($this->regArr['callback'])){ 246 | $this->data[$i][$key] = call_user_func($this->regArr['callback'],$this->data[$i][$key],$key); 247 | } 248 | $i++; 249 | } 250 | } 251 | } 252 | if ($this->outputEncoding) { 253 | //编码转换 254 | $this->data = $this->_arrayConvertEncoding($this->data, $this->outputEncoding, $this->htmlEncoding); 255 | } 256 | phpQuery::$documents = array(); 257 | } 258 | /** 259 | * URL请求 260 | * @param $url 261 | * @return string 262 | */ 263 | private function _request($url) 264 | { 265 | if(function_exists('curl_init')){ 266 | $ch = curl_init(); 267 | curl_setopt($ch, CURLOPT_URL, $url); 268 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 269 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 270 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 271 | curl_setopt($ch, CURLOPT_AUTOREFERER, true); 272 | curl_setopt($ch, CURLOPT_REFERER, $url); 273 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 274 | curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36'); 275 | $result = curl_exec($ch); 276 | curl_close($ch); 277 | }elseif(version_compare(PHP_VERSION, '5.0.0')>=0){ 278 | $opts = array( 279 | 'http' => array( 280 | 'header' => "Referer:{$url}" 281 | ) 282 | ); 283 | $result = file_get_contents($url,false,stream_context_create($opts)); 284 | }else{ 285 | $result = file_get_contents($url); 286 | } 287 | return $result; 288 | } 289 | /** 290 | * 移除页面head区域代码 291 | * @param $html 292 | * @return mixed 293 | */ 294 | private function _removeHead($html) 295 | { 296 | return preg_replace('/.+<\/head>/is','',$html); 297 | } 298 | /** 299 | * 获取文件编码 300 | * @param $string 301 | * @return string 302 | */ 303 | private function _getEncode($string) 304 | { 305 | return mb_detect_encoding($string, array('ASCII', 'GB2312', 'GBK', 'UTF-8')); 306 | } 307 | /** 308 | * 转换数组值的编码格式 309 | * @param array $arr 310 | * @param string $toEncoding 311 | * @param string $fromEncoding 312 | * @return array 313 | */ 314 | private function _arrayConvertEncoding($arr, $toEncoding, $fromEncoding) 315 | { 316 | eval('$arr = '.iconv($fromEncoding, $toEncoding.'//IGNORE', var_export($arr,TRUE)).';'); 317 | return $arr; 318 | } 319 | /** 320 | * 简单的判断一下参数是否为一个URL链接 321 | * @param string $str 322 | * @return boolean 323 | */ 324 | private function _isURL($str) 325 | { 326 | if (preg_match('/^http(s)?:\\/\\/.+/', $str)) { 327 | return true; 328 | } 329 | return false; 330 | } 331 | /** 332 | * 去除特定的html标签 333 | * @param string $html 334 | * @param string $tags_str 多个标签名之间用空格隔开 335 | * @return string 336 | */ 337 | private function _stripTags($html,$tags_str) 338 | { 339 | $tagsArr = $this->_tag($tags_str); 340 | $html = $this->_removeTags($html,$tagsArr[1]); 341 | $p = array(); 342 | foreach ($tagsArr[0] as $tag) { 343 | $p[]="/(<(?:\/".$tag."|".$tag.")[^>]*>)/i"; 344 | } 345 | $html = preg_replace($p,"",trim($html)); 346 | return $html; 347 | } 348 | /** 349 | * 保留特定的html标签 350 | * @param string $html 351 | * @param string $tags_str 多个标签名之间用空格隔开 352 | * @return string 353 | */ 354 | private function _allowTags($html,$tags_str) 355 | { 356 | $tagsArr = $this->_tag($tags_str); 357 | $html = $this->_removeTags($html,$tagsArr[1]); 358 | $allow = ''; 359 | foreach ($tagsArr[0] as $tag) { 360 | $allow .= "<$tag> "; 361 | } 362 | return strip_tags(trim($html),$allow); 363 | } 364 | private function _tag($tags_str) 365 | { 366 | $tagArr = preg_split("/\s+/",$tags_str,-1,PREG_SPLIT_NO_EMPTY); 367 | $tags = array(array(),array()); 368 | foreach($tagArr as $tag) 369 | { 370 | if(preg_match('/-(.+)/', $tag,$arr)) 371 | { 372 | array_push($tags[1], $arr[1]); 373 | }else{ 374 | array_push($tags[0], $tag); 375 | } 376 | } 377 | return $tags; 378 | } 379 | /** 380 | * 移除特定的html标签 381 | * @param string $html 382 | * @param array $tags 标签数组 383 | * @return string 384 | */ 385 | private function _removeTags($html,$tags) 386 | { 387 | $tag_str = ''; 388 | if(count($tags)) 389 | { 390 | foreach ($tags as $tag) { 391 | $tag_str .= $tag_str?','.$tag:$tag; 392 | } 393 | phpQuery::$defaultCharset = $this->inputEncoding?$this->inputEncoding:$this->htmlEncoding; 394 | $doc = phpQuery::newDocumentHTML($html); 395 | pq($doc)->find($tag_str)->remove(); 396 | $html = pq($doc)->htmlOuter(); 397 | $doc->unloadDocument(); 398 | } 399 | return $html; 400 | } 401 | /** 402 | * 打印日志 403 | * @param string $message 404 | * @param string $level 405 | */ 406 | private function _log($message = '',$level = 'info') 407 | { 408 | if(!is_null(self::$logger)) 409 | { 410 | $url = $this->_isURL($this->page)?$this->page:'[html]'; 411 | $count = count($this->data); 412 | $level = empty($level)?($count?'info':'warning'):$level; 413 | $message = empty($message)?($count?'Get data successfully':'Get data failed'):$message; 414 | self::$logger->$level($message,array( 415 | 'page' => $url, 416 | 'count' => $count 417 | )); 418 | } 419 | } 420 | } 421 | /* 422 | class Autoload 423 | { 424 | public static function load($className) 425 | { 426 | $files = array( 427 | sprintf('%s/extensions/%s.php',__DIR__,$className), 428 | sprintf('%s/extensions/vendors/%s.php',__DIR__,$className) 429 | ); 430 | foreach ($files as $file) { 431 | if(is_file($file)){ 432 | require $file; 433 | return true; 434 | } 435 | } 436 | return false; 437 | } 438 | } 439 | spl_autoload_register(array('Autoload','load')); 440 | */ -------------------------------------------------------------------------------- /phpQuery.php: -------------------------------------------------------------------------------- 1 | 11 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 12 | * @package phpQuery 13 | */ 14 | 15 | // class names for instanceof 16 | // TODO move them as class constants into phpQuery 17 | define('DOMDOCUMENT', 'DOMDocument'); 18 | define('DOMELEMENT', 'DOMElement'); 19 | define('DOMNODELIST', 'DOMNodeList'); 20 | define('DOMNODE', 'DOMNode'); 21 | 22 | /** 23 | * DOMEvent class. 24 | * 25 | * Based on 26 | * @link http://developer.mozilla.org/En/DOM:event 27 | * @author Tobiasz Cudnik 28 | * @package phpQuery 29 | * @todo implement ArrayAccess ? 30 | */ 31 | class DOMEvent { 32 | /** 33 | * Returns a boolean indicating whether the event bubbles up through the DOM or not. 34 | * 35 | * @var unknown_type 36 | */ 37 | public $bubbles = true; 38 | /** 39 | * Returns a boolean indicating whether the event is cancelable. 40 | * 41 | * @var unknown_type 42 | */ 43 | public $cancelable = true; 44 | /** 45 | * Returns a reference to the currently registered target for the event. 46 | * 47 | * @var unknown_type 48 | */ 49 | public $currentTarget; 50 | /** 51 | * Returns detail about the event, depending on the type of event. 52 | * 53 | * @var unknown_type 54 | * @link http://developer.mozilla.org/en/DOM/event.detail 55 | */ 56 | public $detail; // ??? 57 | /** 58 | * Used to indicate which phase of the event flow is currently being evaluated. 59 | * 60 | * NOT IMPLEMENTED 61 | * 62 | * @var unknown_type 63 | * @link http://developer.mozilla.org/en/DOM/event.eventPhase 64 | */ 65 | public $eventPhase; // ??? 66 | /** 67 | * The explicit original target of the event (Mozilla-specific). 68 | * 69 | * NOT IMPLEMENTED 70 | * 71 | * @var unknown_type 72 | */ 73 | public $explicitOriginalTarget; // moz only 74 | /** 75 | * The original target of the event, before any retargetings (Mozilla-specific). 76 | * 77 | * NOT IMPLEMENTED 78 | * 79 | * @var unknown_type 80 | */ 81 | public $originalTarget; // moz only 82 | /** 83 | * Identifies a secondary target for the event. 84 | * 85 | * @var unknown_type 86 | */ 87 | public $relatedTarget; 88 | /** 89 | * Returns a reference to the target to which the event was originally dispatched. 90 | * 91 | * @var unknown_type 92 | */ 93 | public $target; 94 | /** 95 | * Returns the time that the event was created. 96 | * 97 | * @var unknown_type 98 | */ 99 | public $timeStamp; 100 | /** 101 | * Returns the name of the event (case-insensitive). 102 | */ 103 | public $type; 104 | public $runDefault = true; 105 | public $data = null; 106 | public function __construct($data) { 107 | foreach($data as $k => $v) { 108 | $this->$k = $v; 109 | } 110 | if (! $this->timeStamp) 111 | $this->timeStamp = time(); 112 | } 113 | /** 114 | * Cancels the event (if it is cancelable). 115 | * 116 | */ 117 | public function preventDefault() { 118 | $this->runDefault = false; 119 | } 120 | /** 121 | * Stops the propagation of events further along in the DOM. 122 | * 123 | */ 124 | public function stopPropagation() { 125 | $this->bubbles = false; 126 | } 127 | } 128 | 129 | 130 | /** 131 | * DOMDocumentWrapper class simplifies work with DOMDocument. 132 | * 133 | * Know bug: 134 | * - in XHTML fragments,
changes to
135 | * 136 | * @todo check XML catalogs compatibility 137 | * @author Tobiasz Cudnik 138 | * @package phpQuery 139 | */ 140 | class DOMDocumentWrapper { 141 | /** 142 | * @var DOMDocument 143 | */ 144 | public $document; 145 | public $id; 146 | /** 147 | * @todo Rewrite as method and quess if null. 148 | * @var unknown_type 149 | */ 150 | public $contentType = ''; 151 | public $xpath; 152 | public $uuid = 0; 153 | public $data = array(); 154 | public $dataNodes = array(); 155 | public $events = array(); 156 | public $eventsNodes = array(); 157 | public $eventsGlobal = array(); 158 | /** 159 | * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28 160 | * @var unknown_type 161 | */ 162 | public $frames = array(); 163 | /** 164 | * Document root, by default equals to document itself. 165 | * Used by documentFragments. 166 | * 167 | * @var DOMNode 168 | */ 169 | public $root; 170 | public $isDocumentFragment; 171 | public $isXML = false; 172 | public $isXHTML = false; 173 | public $isHTML = false; 174 | public $charset; 175 | public function __construct($markup = null, $contentType = null, $newDocumentID = null) { 176 | if (isset($markup)) 177 | $this->load($markup, $contentType, $newDocumentID); 178 | $this->id = $newDocumentID 179 | ? $newDocumentID 180 | : md5(microtime()); 181 | } 182 | public function load($markup, $contentType = null, $newDocumentID = null) { 183 | // phpQuery::$documents[$id] = $this; 184 | $this->contentType = strtolower($contentType); 185 | if ($markup instanceof DOMDOCUMENT) { 186 | $this->document = $markup; 187 | $this->root = $this->document; 188 | $this->charset = $this->document->encoding; 189 | // TODO isDocumentFragment 190 | $loaded = true; 191 | } else { 192 | $loaded = $this->loadMarkup($markup); 193 | } 194 | if ($loaded) { 195 | // $this->document->formatOutput = true; 196 | $this->document->preserveWhiteSpace = true; 197 | $this->xpath = new DOMXPath($this->document); 198 | $this->afterMarkupLoad(); 199 | return true; 200 | // remember last loaded document 201 | // return phpQuery::selectDocument($id); 202 | } 203 | return false; 204 | } 205 | protected function afterMarkupLoad() { 206 | if ($this->isXHTML) { 207 | $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml"); 208 | } 209 | } 210 | protected function loadMarkup($markup) { 211 | $loaded = false; 212 | if ($this->contentType) { 213 | self::debug("Load markup for content type {$this->contentType}"); 214 | // content determined by contentType 215 | list($contentType, $charset) = $this->contentTypeToArray($this->contentType); 216 | switch($contentType) { 217 | case 'text/html': 218 | phpQuery::debug("Loading HTML, content type '{$this->contentType}'"); 219 | $loaded = $this->loadMarkupHTML($markup, $charset); 220 | break; 221 | case 'text/xml': 222 | case 'application/xhtml+xml': 223 | phpQuery::debug("Loading XML, content type '{$this->contentType}'"); 224 | $loaded = $this->loadMarkupXML($markup, $charset); 225 | break; 226 | default: 227 | // for feeds or anything that sometimes doesn't use text/xml 228 | if (strpos('xml', $this->contentType) !== false) { 229 | phpQuery::debug("Loading XML, content type '{$this->contentType}'"); 230 | $loaded = $this->loadMarkupXML($markup, $charset); 231 | } else 232 | phpQuery::debug("Could not determine document type from content type '{$this->contentType}'"); 233 | } 234 | } else { 235 | // content type autodetection 236 | if ($this->isXML($markup)) { 237 | phpQuery::debug("Loading XML, isXML() == true"); 238 | $loaded = $this->loadMarkupXML($markup); 239 | if (! $loaded && $this->isXHTML) { 240 | phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true'); 241 | $loaded = $this->loadMarkupHTML($markup); 242 | } 243 | } else { 244 | phpQuery::debug("Loading HTML, isXML() == false"); 245 | $loaded = $this->loadMarkupHTML($markup); 246 | } 247 | } 248 | return $loaded; 249 | } 250 | protected function loadMarkupReset() { 251 | $this->isXML = $this->isXHTML = $this->isHTML = false; 252 | } 253 | protected function documentCreate($charset, $version = '1.0') { 254 | if (! $version) 255 | $version = '1.0'; 256 | $this->document = new DOMDocument($version, $charset); 257 | $this->charset = $this->document->encoding; 258 | // $this->document->encoding = $charset; 259 | $this->document->formatOutput = true; 260 | $this->document->preserveWhiteSpace = true; 261 | } 262 | protected function loadMarkupHTML($markup, $requestedCharset = null) { 263 | if (phpQuery::$debug) 264 | phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250)); 265 | $this->loadMarkupReset(); 266 | $this->isHTML = true; 267 | if (!isset($this->isDocumentFragment)) 268 | $this->isDocumentFragment = self::isDocumentFragmentHTML($markup); 269 | $charset = null; 270 | $documentCharset = $this->charsetFromHTML($markup); 271 | $addDocumentCharset = false; 272 | if ($documentCharset) { 273 | $charset = $documentCharset; 274 | $markup = $this->charsetFixHTML($markup); 275 | } else if ($requestedCharset) { 276 | $charset = $requestedCharset; 277 | } 278 | if (! $charset) 279 | $charset = phpQuery::$defaultCharset; 280 | // HTTP 1.1 says that the default charset is ISO-8859-1 281 | // @see http://www.w3.org/International/O-HTTP-charset 282 | if (! $documentCharset) { 283 | $documentCharset = 'ISO-8859-1'; 284 | $addDocumentCharset = true; 285 | } 286 | // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' 287 | // Worse, some pages can have mixed encodings... we'll try not to worry about that 288 | $requestedCharset = strtoupper($requestedCharset); 289 | $documentCharset = strtoupper($documentCharset); 290 | phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset"); 291 | if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) { 292 | phpQuery::debug("CHARSET CONVERT"); 293 | // Document Encoding Conversion 294 | // http://code.google.com/p/phpquery/issues/detail?id=86 295 | if (function_exists('mb_detect_encoding')) { 296 | $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO'); 297 | $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets)); 298 | if (! $docEncoding) 299 | $docEncoding = $documentCharset; // ok trust the document 300 | phpQuery::debug("DETECTED '$docEncoding'"); 301 | // Detected does not match what document says... 302 | if ($docEncoding !== $documentCharset) { 303 | // Tricky.. 304 | } 305 | if ($docEncoding !== $requestedCharset) { 306 | phpQuery::debug("CONVERT $docEncoding => $requestedCharset"); 307 | $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding); 308 | $markup = $this->charsetAppendToHTML($markup, $requestedCharset); 309 | $charset = $requestedCharset; 310 | } 311 | } else { 312 | phpQuery::debug("TODO: charset conversion without mbstring..."); 313 | } 314 | } 315 | $return = false; 316 | if ($this->isDocumentFragment) { 317 | phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'"); 318 | $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); 319 | } else { 320 | if ($addDocumentCharset) { 321 | phpQuery::debug("Full markup load (HTML), appending charset: '$charset'"); 322 | $markup = $this->charsetAppendToHTML($markup, $charset); 323 | } 324 | phpQuery::debug("Full markup load (HTML), documentCreate('$charset')"); 325 | $this->documentCreate($charset); 326 | $return = phpQuery::$debug === 2 327 | ? $this->document->loadHTML($markup) 328 | : @$this->document->loadHTML($markup); 329 | if ($return) 330 | $this->root = $this->document; 331 | } 332 | if ($return && ! $this->contentType) 333 | $this->contentType = 'text/html'; 334 | return $return; 335 | } 336 | protected function loadMarkupXML($markup, $requestedCharset = null) { 337 | if (phpQuery::$debug) 338 | phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250)); 339 | $this->loadMarkupReset(); 340 | $this->isXML = true; 341 | // check agains XHTML in contentType or markup 342 | $isContentTypeXHTML = $this->isXHTML(); 343 | $isMarkupXHTML = $this->isXHTML($markup); 344 | if ($isContentTypeXHTML || $isMarkupXHTML) { 345 | self::debug('Full markup load (XML), XHTML detected'); 346 | $this->isXHTML = true; 347 | } 348 | // determine document fragment 349 | if (! isset($this->isDocumentFragment)) 350 | $this->isDocumentFragment = $this->isXHTML 351 | ? self::isDocumentFragmentXHTML($markup) 352 | : self::isDocumentFragmentXML($markup); 353 | // this charset will be used 354 | $charset = null; 355 | // charset from XML declaration @var string 356 | $documentCharset = $this->charsetFromXML($markup); 357 | if (! $documentCharset) { 358 | if ($this->isXHTML) { 359 | // this is XHTML, try to get charset from content-type meta header 360 | $documentCharset = $this->charsetFromHTML($markup); 361 | if ($documentCharset) { 362 | phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'"); 363 | $this->charsetAppendToXML($markup, $documentCharset); 364 | $charset = $documentCharset; 365 | } 366 | } 367 | if (! $documentCharset) { 368 | // if still no document charset... 369 | $charset = $requestedCharset; 370 | } 371 | } else if ($requestedCharset) { 372 | $charset = $requestedCharset; 373 | } 374 | if (! $charset) { 375 | $charset = phpQuery::$defaultCharset; 376 | } 377 | if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) { 378 | // TODO place for charset conversion 379 | // $charset = $requestedCharset; 380 | } 381 | $return = false; 382 | if ($this->isDocumentFragment) { 383 | phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'"); 384 | $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); 385 | } else { 386 | // FIXME ??? 387 | if ($isContentTypeXHTML && ! $isMarkupXHTML) 388 | if (! $documentCharset) { 389 | phpQuery::debug("Full markup load (XML), appending charset '$charset'"); 390 | $markup = $this->charsetAppendToXML($markup, $charset); 391 | } 392 | // see http://pl2.php.net/manual/en/book.dom.php#78929 393 | // LIBXML_DTDLOAD (>= PHP 5.1) 394 | // does XML ctalogues works with LIBXML_NONET 395 | // $this->document->resolveExternals = true; 396 | // TODO test LIBXML_COMPACT for performance improvement 397 | // create document 398 | $this->documentCreate($charset); 399 | if (phpversion() < 5.1) { 400 | $this->document->resolveExternals = true; 401 | $return = phpQuery::$debug === 2 402 | ? $this->document->loadXML($markup) 403 | : @$this->document->loadXML($markup); 404 | } else { 405 | /** @link http://pl2.php.net/manual/en/libxml.constants.php */ 406 | $libxmlStatic = phpQuery::$debug === 2 407 | ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET 408 | : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR; 409 | $return = $this->document->loadXML($markup, $libxmlStatic); 410 | // if (! $return) 411 | // $return = $this->document->loadHTML($markup); 412 | } 413 | if ($return) 414 | $this->root = $this->document; 415 | } 416 | if ($return) { 417 | if (! $this->contentType) { 418 | if ($this->isXHTML) 419 | $this->contentType = 'application/xhtml+xml'; 420 | else 421 | $this->contentType = 'text/xml'; 422 | } 423 | return $return; 424 | } else { 425 | throw new Exception("Error loading XML markup"); 426 | } 427 | } 428 | protected function isXHTML($markup = null) { 429 | if (! isset($markup)) { 430 | return strpos($this->contentType, 'xhtml') !== false; 431 | } 432 | // XXX ok ? 433 | return strpos($markup, "doctype) && is_object($dom->doctype) 436 | // ? $dom->doctype->publicId 437 | // : self::$defaultDoctype; 438 | } 439 | protected function isXML($markup) { 440 | // return strpos($markup, ']+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', 464 | $markup, $matches 465 | ); 466 | if (! isset($matches[0])) 467 | return array(null, null); 468 | // get attr 'content' 469 | preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches); 470 | if (! isset($matches[0])) 471 | return array(null, null); 472 | return $this->contentTypeToArray($matches[2]); 473 | } 474 | protected function charsetFromHTML($markup) { 475 | $contentType = $this->contentTypeFromHTML($markup); 476 | return $contentType[1]; 477 | } 478 | protected function charsetFromXML($markup) { 479 | $matches; 480 | // find declaration 481 | preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i', 482 | $markup, $matches 483 | ); 484 | return isset($matches[2]) 485 | ? strtolower($matches[2]) 486 | : null; 487 | } 488 | /** 489 | * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug. 490 | * 491 | * @link http://code.google.com/p/phpquery/issues/detail?id=80 492 | * @param $html 493 | */ 494 | protected function charsetFixHTML($markup) { 495 | $matches = array(); 496 | // find meta tag 497 | preg_match('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', 498 | $markup, $matches, PREG_OFFSET_CAPTURE 499 | ); 500 | if (! isset($matches[0])) 501 | return; 502 | $metaContentType = $matches[0][0]; 503 | $markup = substr($markup, 0, $matches[0][1]) 504 | .substr($markup, $matches[0][1]+strlen($metaContentType)); 505 | $headStart = stripos($markup, ''); 506 | $markup = substr($markup, 0, $headStart+6).$metaContentType 507 | .substr($markup, $headStart+6); 508 | return $markup; 509 | } 510 | protected function charsetAppendToHTML($html, $charset, $xhtml = false) { 511 | // remove existing meta[type=content-type] 512 | $html = preg_replace('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html); 513 | $meta = ''; 517 | if (strpos($html, ')@s', 523 | "{$meta}", 524 | $html 525 | ); 526 | } 527 | } else { 528 | return preg_replace( 529 | '@)@s', 530 | ''.$meta, 531 | $html 532 | ); 533 | } 534 | } 535 | protected function charsetAppendToXML($markup, $charset) { 536 | $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>'; 537 | return $declaration.$markup; 538 | } 539 | public static function isDocumentFragmentHTML($markup) { 540 | return stripos($markup, 'documentFragmentCreate($node, $sourceCharset); 568 | // if ($fake === false) 569 | // throw new Exception("Error loading documentFragment markup"); 570 | // else 571 | // $return = array_merge($return, 572 | // $this->import($fake->root->childNodes) 573 | // ); 574 | // } else { 575 | // $return[] = $this->document->importNode($node, true); 576 | // } 577 | // } 578 | // return $return; 579 | // } else { 580 | // // string markup 581 | // $fake = $this->documentFragmentCreate($source, $sourceCharset); 582 | // if ($fake === false) 583 | // throw new Exception("Error loading documentFragment markup"); 584 | // else 585 | // return $this->import($fake->root->childNodes); 586 | // } 587 | if (is_array($source) || $source instanceof DOMNODELIST) { 588 | // dom nodes 589 | self::debug('Importing nodes to document'); 590 | foreach($source as $node) 591 | $return[] = $this->document->importNode($node, true); 592 | } else { 593 | // string markup 594 | $fake = $this->documentFragmentCreate($source, $sourceCharset); 595 | if ($fake === false) 596 | throw new Exception("Error loading documentFragment markup"); 597 | else 598 | return $this->import($fake->root->childNodes); 599 | } 600 | return $return; 601 | } 602 | /** 603 | * Creates new document fragment. 604 | * 605 | * @param $source 606 | * @return DOMDocumentWrapper 607 | */ 608 | protected function documentFragmentCreate($source, $charset = null) { 609 | $fake = new DOMDocumentWrapper(); 610 | $fake->contentType = $this->contentType; 611 | $fake->isXML = $this->isXML; 612 | $fake->isHTML = $this->isHTML; 613 | $fake->isXHTML = $this->isXHTML; 614 | $fake->root = $fake->document; 615 | if (! $charset) 616 | $charset = $this->charset; 617 | // $fake->documentCreate($this->charset); 618 | if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) 619 | $source = array($source); 620 | if (is_array($source) || $source instanceof DOMNODELIST) { 621 | // dom nodes 622 | // load fake document 623 | if (! $this->documentFragmentLoadMarkup($fake, $charset)) 624 | return false; 625 | $nodes = $fake->import($source); 626 | foreach($nodes as $node) 627 | $fake->root->appendChild($node); 628 | } else { 629 | // string markup 630 | $this->documentFragmentLoadMarkup($fake, $charset, $source); 631 | } 632 | return $fake; 633 | } 634 | /** 635 | * 636 | * @param $document DOMDocumentWrapper 637 | * @param $markup 638 | * @return $document 639 | */ 640 | private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) { 641 | // TODO error handling 642 | // TODO copy doctype 643 | // tempolary turn off 644 | $fragment->isDocumentFragment = false; 645 | if ($fragment->isXML) { 646 | if ($fragment->isXHTML) { 647 | // add FAKE element to set default namespace 648 | $fragment->loadMarkupXML('' 649 | .'' 651 | .''.$markup.''); 652 | $fragment->root = $fragment->document->firstChild->nextSibling; 653 | } else { 654 | $fragment->loadMarkupXML(''.$markup.''); 655 | $fragment->root = $fragment->document->firstChild; 656 | } 657 | } else { 658 | $markup2 = phpQuery::$defaultDoctype.''; 660 | $noBody = strpos($markup, 'loadMarkupHTML($markup2); 668 | // TODO resolv body tag merging issue 669 | $fragment->root = $noBody 670 | ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling 671 | : $fragment->document->firstChild->nextSibling->firstChild->nextSibling; 672 | } 673 | if (! $fragment->root) 674 | return false; 675 | $fragment->isDocumentFragment = true; 676 | return true; 677 | } 678 | protected function documentFragmentToMarkup($fragment) { 679 | phpQuery::debug('documentFragmentToMarkup'); 680 | $tmp = $fragment->isDocumentFragment; 681 | $fragment->isDocumentFragment = false; 682 | $markup = $fragment->markup(); 683 | if ($fragment->isXML) { 684 | $markup = substr($markup, 0, strrpos($markup, '')); 685 | if ($fragment->isXHTML) { 686 | $markup = substr($markup, strpos($markup, '')+6); 689 | } 690 | } else { 691 | $markup = substr($markup, strpos($markup, '')+6); 692 | $markup = substr($markup, 0, strrpos($markup, '')); 693 | } 694 | $fragment->isDocumentFragment = $tmp; 695 | if (phpQuery::$debug) 696 | phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150)); 697 | return $markup; 698 | } 699 | /** 700 | * Return document markup, starting with optional $nodes as root. 701 | * 702 | * @param $nodes DOMNode|DOMNodeList 703 | * @return string 704 | */ 705 | public function markup($nodes = null, $innerMarkup = false) { 706 | if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT) 707 | $nodes = null; 708 | if (isset($nodes)) { 709 | $markup = ''; 710 | if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) ) 711 | $nodes = array($nodes); 712 | if ($this->isDocumentFragment && ! $innerMarkup) 713 | foreach($nodes as $i => $node) 714 | if ($node->isSameNode($this->root)) { 715 | // var_dump($node); 716 | $nodes = array_slice($nodes, 0, $i) 717 | + phpQuery::DOMNodeListToArray($node->childNodes) 718 | + array_slice($nodes, $i+1); 719 | } 720 | if ($this->isXML && ! $innerMarkup) { 721 | self::debug("Getting outerXML with charset '{$this->charset}'"); 722 | // we need outerXML, so we can benefit from 723 | // $node param support in saveXML() 724 | foreach($nodes as $node) 725 | $markup .= $this->document->saveXML($node); 726 | } else { 727 | $loop = array(); 728 | if ($innerMarkup) 729 | foreach($nodes as $node) { 730 | if ($node->childNodes) 731 | foreach($node->childNodes as $child) 732 | $loop[] = $child; 733 | else 734 | $loop[] = $node; 735 | } 736 | else 737 | $loop = $nodes; 738 | self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment"); 739 | $fake = $this->documentFragmentCreate($loop); 740 | $markup = $this->documentFragmentToMarkup($fake); 741 | } 742 | if ($this->isXHTML) { 743 | self::debug("Fixing XHTML"); 744 | $markup = self::markupFixXHTML($markup); 745 | } 746 | self::debug("Markup: ".substr($markup, 0, 250)); 747 | return $markup; 748 | } else { 749 | if ($this->isDocumentFragment) { 750 | // documentFragment, html only... 751 | self::debug("Getting markup, DocumentFragment detected"); 752 | // return $this->markup( 753 | //// $this->document->getElementsByTagName('body')->item(0) 754 | // $this->document->root, true 755 | // ); 756 | $markup = $this->documentFragmentToMarkup($this); 757 | // no need for markupFixXHTML, as it's done thought markup($nodes) method 758 | return $markup; 759 | } else { 760 | self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'"); 761 | $markup = $this->isXML 762 | ? $this->document->saveXML() 763 | : $this->document->saveHTML(); 764 | if ($this->isXHTML) { 765 | self::debug("Fixing XHTML"); 766 | $markup = self::markupFixXHTML($markup); 767 | } 768 | self::debug("Markup: ".substr($markup, 0, 250)); 769 | return $markup; 770 | } 771 | } 772 | } 773 | protected static function markupFixXHTML($markup) { 774 | $markup = self::expandEmptyTag('script', $markup); 775 | $markup = self::expandEmptyTag('select', $markup); 776 | $markup = self::expandEmptyTag('textarea', $markup); 777 | return $markup; 778 | } 779 | public static function debug($text) { 780 | phpQuery::debug($text); 781 | } 782 | /** 783 | * expandEmptyTag 784 | * 785 | * @param $tag 786 | * @param $xml 787 | * @return unknown_type 788 | * @author mjaque at ilkebenson dot com 789 | * @link http://php.net/manual/en/domdocument.savehtml.php#81256 790 | */ 791 | public static function expandEmptyTag($tag, $xml){ 792 | $indice = 0; 793 | while ($indice< strlen($xml)){ 794 | $pos = strpos($xml, "<$tag ", $indice); 795 | if ($pos){ 796 | $posCierre = strpos($xml, ">", $pos); 797 | if ($xml[$posCierre-1] == "/"){ 798 | $xml = substr_replace($xml, ">", $posCierre-1, 2); 799 | } 800 | $indice = $posCierre; 801 | } 802 | else break; 803 | } 804 | return $xml; 805 | } 806 | } 807 | 808 | /** 809 | * Event handling class. 810 | * 811 | * @author Tobiasz Cudnik 812 | * @package phpQuery 813 | * @static 814 | */ 815 | abstract class phpQueryEvents { 816 | /** 817 | * Trigger a type of event on every matched element. 818 | * 819 | * @param DOMNode|phpQueryObject|string $document 820 | * @param unknown_type $type 821 | * @param unknown_type $data 822 | * 823 | * @TODO exclusive events (with !) 824 | * @TODO global events (test) 825 | * @TODO support more than event in $type (space-separated) 826 | */ 827 | public static function trigger($document, $type, $data = array(), $node = null) { 828 | // trigger: function(type, data, elem, donative, extra) { 829 | $documentID = phpQuery::getDocumentID($document); 830 | $namespace = null; 831 | if (strpos($type, '.') !== false) 832 | list($name, $namespace) = explode('.', $type); 833 | else 834 | $name = $type; 835 | if (! $node) { 836 | if (self::issetGlobal($documentID, $type)) { 837 | $pq = phpQuery::getDocument($documentID); 838 | // TODO check add($pq->document) 839 | $pq->find('*')->add($pq->document) 840 | ->trigger($type, $data); 841 | } 842 | } else { 843 | if (isset($data[0]) && $data[0] instanceof DOMEvent) { 844 | $event = $data[0]; 845 | $event->relatedTarget = $event->target; 846 | $event->target = $node; 847 | $data = array_slice($data, 1); 848 | } else { 849 | $event = new DOMEvent(array( 850 | 'type' => $type, 851 | 'target' => $node, 852 | 'timeStamp' => time(), 853 | )); 854 | } 855 | $i = 0; 856 | while($node) { 857 | // TODO whois 858 | phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on " 859 | ."node \n");//.phpQueryObject::whois($node)."\n"); 860 | $event->currentTarget = $node; 861 | $eventNode = self::getNode($documentID, $node); 862 | if (isset($eventNode->eventHandlers)) { 863 | foreach($eventNode->eventHandlers as $eventType => $handlers) { 864 | $eventNamespace = null; 865 | if (strpos($type, '.') !== false) 866 | list($eventName, $eventNamespace) = explode('.', $eventType); 867 | else 868 | $eventName = $eventType; 869 | if ($name != $eventName) 870 | continue; 871 | if ($namespace && $eventNamespace && $namespace != $eventNamespace) 872 | continue; 873 | foreach($handlers as $handler) { 874 | phpQuery::debug("Calling event handler\n"); 875 | $event->data = $handler['data'] 876 | ? $handler['data'] 877 | : null; 878 | $params = array_merge(array($event), $data); 879 | $return = phpQuery::callbackRun($handler['callback'], $params); 880 | if ($return === false) { 881 | $event->bubbles = false; 882 | } 883 | } 884 | } 885 | } 886 | // to bubble or not to bubble... 887 | if (! $event->bubbles) 888 | break; 889 | $node = $node->parentNode; 890 | $i++; 891 | } 892 | } 893 | } 894 | /** 895 | * Binds a handler to one or more events (like click) for each matched element. 896 | * Can also bind custom events. 897 | * 898 | * @param DOMNode|phpQueryObject|string $document 899 | * @param unknown_type $type 900 | * @param unknown_type $data Optional 901 | * @param unknown_type $callback 902 | * 903 | * @TODO support '!' (exclusive) events 904 | * @TODO support more than event in $type (space-separated) 905 | * @TODO support binding to global events 906 | */ 907 | public static function add($document, $node, $type, $data, $callback = null) { 908 | phpQuery::debug("Binding '$type' event"); 909 | $documentID = phpQuery::getDocumentID($document); 910 | // if (is_null($callback) && is_callable($data)) { 911 | // $callback = $data; 912 | // $data = null; 913 | // } 914 | $eventNode = self::getNode($documentID, $node); 915 | if (! $eventNode) 916 | $eventNode = self::setNode($documentID, $node); 917 | if (!isset($eventNode->eventHandlers[$type])) 918 | $eventNode->eventHandlers[$type] = array(); 919 | $eventNode->eventHandlers[$type][] = array( 920 | 'callback' => $callback, 921 | 'data' => $data, 922 | ); 923 | } 924 | /** 925 | * Enter description here... 926 | * 927 | * @param DOMNode|phpQueryObject|string $document 928 | * @param unknown_type $type 929 | * @param unknown_type $callback 930 | * 931 | * @TODO namespace events 932 | * @TODO support more than event in $type (space-separated) 933 | */ 934 | public static function remove($document, $node, $type = null, $callback = null) { 935 | $documentID = phpQuery::getDocumentID($document); 936 | $eventNode = self::getNode($documentID, $node); 937 | if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) { 938 | if ($callback) { 939 | foreach($eventNode->eventHandlers[$type] as $k => $handler) 940 | if ($handler['callback'] == $callback) 941 | unset($eventNode->eventHandlers[$type][$k]); 942 | } else { 943 | unset($eventNode->eventHandlers[$type]); 944 | } 945 | } 946 | } 947 | protected static function getNode($documentID, $node) { 948 | foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) { 949 | if ($node->isSameNode($eventNode)) 950 | return $eventNode; 951 | } 952 | } 953 | protected static function setNode($documentID, $node) { 954 | phpQuery::$documents[$documentID]->eventsNodes[] = $node; 955 | return phpQuery::$documents[$documentID]->eventsNodes[ 956 | count(phpQuery::$documents[$documentID]->eventsNodes)-1 957 | ]; 958 | } 959 | protected static function issetGlobal($documentID, $type) { 960 | return isset(phpQuery::$documents[$documentID]) 961 | ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal) 962 | : false; 963 | } 964 | } 965 | 966 | 967 | interface ICallbackNamed { 968 | function hasName(); 969 | function getName(); 970 | } 971 | /** 972 | * Callback class introduces currying-like pattern. 973 | * 974 | * Example: 975 | * function foo($param1, $param2, $param3) { 976 | * var_dump($param1, $param2, $param3); 977 | * } 978 | * $fooCurried = new Callback('foo', 979 | * 'param1 is now statically set', 980 | * new CallbackParam, new CallbackParam 981 | * ); 982 | * phpQuery::callbackRun($fooCurried, 983 | * array('param2 value', 'param3 value' 984 | * ); 985 | * 986 | * Callback class is supported in all phpQuery methods which accepts callbacks. 987 | * 988 | * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures 989 | * @author Tobiasz Cudnik 990 | * 991 | * @TODO??? return fake forwarding function created via create_function 992 | * @TODO honor paramStructure 993 | */ 994 | class Callback 995 | implements ICallbackNamed { 996 | public $callback = null; 997 | public $params = null; 998 | protected $name; 999 | public function __construct($callback, $param1 = null, $param2 = null, 1000 | $param3 = null) { 1001 | $params = func_get_args(); 1002 | $params = array_slice($params, 1); 1003 | if ($callback instanceof Callback) { 1004 | // TODO implement recurention 1005 | } else { 1006 | $this->callback = $callback; 1007 | $this->params = $params; 1008 | } 1009 | } 1010 | public function getName() { 1011 | return 'Callback: '.$this->name; 1012 | } 1013 | public function hasName() { 1014 | return isset($this->name) && $this->name; 1015 | } 1016 | public function setName($name) { 1017 | $this->name = $name; 1018 | return $this; 1019 | } 1020 | // TODO test me 1021 | // public function addParams() { 1022 | // $params = func_get_args(); 1023 | // return new Callback($this->callback, $this->params+$params); 1024 | // } 1025 | } 1026 | /** 1027 | * Shorthand for new Callback(create_function(...), ...); 1028 | * 1029 | * @author Tobiasz Cudnik 1030 | */ 1031 | class CallbackBody extends Callback { 1032 | public function __construct($paramList, $code, $param1 = null, $param2 = null, 1033 | $param3 = null) { 1034 | $params = func_get_args(); 1035 | $params = array_slice($params, 2); 1036 | $this->callback = create_function($paramList, $code); 1037 | $this->params = $params; 1038 | } 1039 | } 1040 | /** 1041 | * Callback type which on execution returns reference passed during creation. 1042 | * 1043 | * @author Tobiasz Cudnik 1044 | */ 1045 | class CallbackReturnReference extends Callback 1046 | implements ICallbackNamed { 1047 | protected $reference; 1048 | public function __construct(&$reference, $name = null){ 1049 | $this->reference =& $reference; 1050 | $this->callback = array($this, 'callback'); 1051 | } 1052 | public function callback() { 1053 | return $this->reference; 1054 | } 1055 | public function getName() { 1056 | return 'Callback: '.$this->name; 1057 | } 1058 | public function hasName() { 1059 | return isset($this->name) && $this->name; 1060 | } 1061 | } 1062 | /** 1063 | * Callback type which on execution returns value passed during creation. 1064 | * 1065 | * @author Tobiasz Cudnik 1066 | */ 1067 | class CallbackReturnValue extends Callback 1068 | implements ICallbackNamed { 1069 | protected $value; 1070 | protected $name; 1071 | public function __construct($value, $name = null){ 1072 | $this->value =& $value; 1073 | $this->name = $name; 1074 | $this->callback = array($this, 'callback'); 1075 | } 1076 | public function callback() { 1077 | return $this->value; 1078 | } 1079 | public function __toString() { 1080 | return $this->getName(); 1081 | } 1082 | public function getName() { 1083 | return 'Callback: '.$this->name; 1084 | } 1085 | public function hasName() { 1086 | return isset($this->name) && $this->name; 1087 | } 1088 | } 1089 | /** 1090 | * CallbackParameterToReference can be used when we don't really want a callback, 1091 | * only parameter passed to it. CallbackParameterToReference takes first 1092 | * parameter's value and passes it to reference. 1093 | * 1094 | * @author Tobiasz Cudnik 1095 | */ 1096 | class CallbackParameterToReference extends Callback { 1097 | /** 1098 | * @param $reference 1099 | * @TODO implement $paramIndex; 1100 | * param index choose which callback param will be passed to reference 1101 | */ 1102 | public function __construct(&$reference){ 1103 | $this->callback =& $reference; 1104 | } 1105 | } 1106 | //class CallbackReference extends Callback { 1107 | // /** 1108 | // * 1109 | // * @param $reference 1110 | // * @param $paramIndex 1111 | // * @todo implement $paramIndex; param index choose which callback param will be passed to reference 1112 | // */ 1113 | // public function __construct(&$reference, $name = null){ 1114 | // $this->callback =& $reference; 1115 | // } 1116 | //} 1117 | class CallbackParam {} 1118 | 1119 | /** 1120 | * Class representing phpQuery objects. 1121 | * 1122 | * @author Tobiasz Cudnik 1123 | * @package phpQuery 1124 | * @method phpQueryObject clone() clone() 1125 | * @method phpQueryObject empty() empty() 1126 | * @method phpQueryObject next() next($selector = null) 1127 | * @method phpQueryObject prev() prev($selector = null) 1128 | * @property Int $length 1129 | */ 1130 | class phpQueryObject 1131 | implements Iterator, Countable, ArrayAccess { 1132 | public $documentID = null; 1133 | /** 1134 | * DOMDocument class. 1135 | * 1136 | * @var DOMDocument 1137 | */ 1138 | public $document = null; 1139 | public $charset = null; 1140 | /** 1141 | * 1142 | * @var DOMDocumentWrapper 1143 | */ 1144 | public $documentWrapper = null; 1145 | /** 1146 | * XPath interface. 1147 | * 1148 | * @var DOMXPath 1149 | */ 1150 | public $xpath = null; 1151 | /** 1152 | * Stack of selected elements. 1153 | * @TODO refactor to ->nodes 1154 | * @var array 1155 | */ 1156 | public $elements = array(); 1157 | /** 1158 | * @access private 1159 | */ 1160 | protected $elementsBackup = array(); 1161 | /** 1162 | * @access private 1163 | */ 1164 | protected $previous = null; 1165 | /** 1166 | * @access private 1167 | * @TODO deprecate 1168 | */ 1169 | protected $root = array(); 1170 | /** 1171 | * Indicated if doument is just a fragment (no tag). 1172 | * 1173 | * Every document is realy a full document, so even documentFragments can 1174 | * be queried against , but getDocument(id)->htmlOuter() will return 1175 | * only contents of . 1176 | * 1177 | * @var bool 1178 | */ 1179 | public $documentFragment = true; 1180 | /** 1181 | * Iterator interface helper 1182 | * @access private 1183 | */ 1184 | protected $elementsInterator = array(); 1185 | /** 1186 | * Iterator interface helper 1187 | * @access private 1188 | */ 1189 | protected $valid = false; 1190 | /** 1191 | * Iterator interface helper 1192 | * @access private 1193 | */ 1194 | protected $current = null; 1195 | /** 1196 | * Enter description here... 1197 | * 1198 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1199 | */ 1200 | public function __construct($documentID) { 1201 | // if ($documentID instanceof self) 1202 | // var_dump($documentID->getDocumentID()); 1203 | $id = $documentID instanceof self 1204 | ? $documentID->getDocumentID() 1205 | : $documentID; 1206 | // var_dump($id); 1207 | if (! isset(phpQuery::$documents[$id] )) { 1208 | // var_dump(phpQuery::$documents); 1209 | throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first."); 1210 | } 1211 | $this->documentID = $id; 1212 | $this->documentWrapper =& phpQuery::$documents[$id]; 1213 | $this->document =& $this->documentWrapper->document; 1214 | $this->xpath =& $this->documentWrapper->xpath; 1215 | $this->charset =& $this->documentWrapper->charset; 1216 | $this->documentFragment =& $this->documentWrapper->isDocumentFragment; 1217 | // TODO check $this->DOM->documentElement; 1218 | // $this->root = $this->document->documentElement; 1219 | $this->root =& $this->documentWrapper->root; 1220 | // $this->toRoot(); 1221 | $this->elements = array($this->root); 1222 | } 1223 | /** 1224 | * 1225 | * @access private 1226 | * @param $attr 1227 | * @return unknown_type 1228 | */ 1229 | public function __get($attr) { 1230 | switch($attr) { 1231 | // FIXME doesnt work at all ? 1232 | case 'length': 1233 | return $this->size(); 1234 | break; 1235 | default: 1236 | return $this->$attr; 1237 | } 1238 | } 1239 | /** 1240 | * Saves actual object to $var by reference. 1241 | * Useful when need to break chain. 1242 | * @param phpQueryObject $var 1243 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1244 | */ 1245 | public function toReference(&$var) { 1246 | return $var = $this; 1247 | } 1248 | public function documentFragment($state = null) { 1249 | if ($state) { 1250 | phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state; 1251 | return $this; 1252 | } 1253 | return $this->documentFragment; 1254 | } 1255 | /** 1256 | * @access private 1257 | * @TODO documentWrapper 1258 | */ 1259 | protected function isRoot( $node) { 1260 | // return $node instanceof DOMDOCUMENT || $node->tagName == 'html'; 1261 | return $node instanceof DOMDOCUMENT 1262 | || ($node instanceof DOMELEMENT && $node->tagName == 'html') 1263 | || $this->root->isSameNode($node); 1264 | } 1265 | /** 1266 | * @access private 1267 | */ 1268 | protected function stackIsRoot() { 1269 | return $this->size() == 1 && $this->isRoot($this->elements[0]); 1270 | } 1271 | /** 1272 | * Enter description here... 1273 | * NON JQUERY METHOD 1274 | * 1275 | * Watch out, it doesn't creates new instance, can be reverted with end(). 1276 | * 1277 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1278 | */ 1279 | public function toRoot() { 1280 | $this->elements = array($this->root); 1281 | return $this; 1282 | // return $this->newInstance(array($this->root)); 1283 | } 1284 | /** 1285 | * Saves object's DocumentID to $var by reference. 1286 | * 1287 | * $myDocumentId; 1288 | * phpQuery::newDocument('
') 1289 | * ->getDocumentIDRef($myDocumentId) 1290 | * ->find('div')->... 1291 | * 1292 | * 1293 | * @param unknown_type $domId 1294 | * @see phpQuery::newDocument 1295 | * @see phpQuery::newDocumentFile 1296 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1297 | */ 1298 | public function getDocumentIDRef(&$documentID) { 1299 | $documentID = $this->getDocumentID(); 1300 | return $this; 1301 | } 1302 | /** 1303 | * Returns object with stack set to document root. 1304 | * 1305 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1306 | */ 1307 | public function getDocument() { 1308 | return phpQuery::getDocument($this->getDocumentID()); 1309 | } 1310 | /** 1311 | * 1312 | * @return DOMDocument 1313 | */ 1314 | public function getDOMDocument() { 1315 | return $this->document; 1316 | } 1317 | /** 1318 | * Get object's Document ID. 1319 | * 1320 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1321 | */ 1322 | public function getDocumentID() { 1323 | return $this->documentID; 1324 | } 1325 | /** 1326 | * Unloads whole document from memory. 1327 | * CAUTION! None further operations will be possible on this document. 1328 | * All objects refering to it will be useless. 1329 | * 1330 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1331 | */ 1332 | public function unloadDocument() { 1333 | phpQuery::unloadDocuments($this->getDocumentID()); 1334 | } 1335 | public function isHTML() { 1336 | return $this->documentWrapper->isHTML; 1337 | } 1338 | public function isXHTML() { 1339 | return $this->documentWrapper->isXHTML; 1340 | } 1341 | public function isXML() { 1342 | return $this->documentWrapper->isXML; 1343 | } 1344 | /** 1345 | * Enter description here... 1346 | * 1347 | * @link http://docs.jquery.com/Ajax/serialize 1348 | * @return string 1349 | */ 1350 | public function serialize() { 1351 | return phpQuery::param($this->serializeArray()); 1352 | } 1353 | /** 1354 | * Enter description here... 1355 | * 1356 | * @link http://docs.jquery.com/Ajax/serializeArray 1357 | * @return array 1358 | */ 1359 | public function serializeArray($submit = null) { 1360 | $source = $this->filter('form, input, select, textarea') 1361 | ->find('input, select, textarea') 1362 | ->andSelf() 1363 | ->not('form'); 1364 | $return = array(); 1365 | // $source->dumpDie(); 1366 | foreach($source as $input) { 1367 | $input = phpQuery::pq($input); 1368 | if ($input->is('[disabled]')) 1369 | continue; 1370 | if (!$input->is('[name]')) 1371 | continue; 1372 | if ($input->is('[type=checkbox]') && !$input->is('[checked]')) 1373 | continue; 1374 | // jquery diff 1375 | if ($submit && $input->is('[type=submit]')) { 1376 | if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit)) 1377 | continue; 1378 | else if (is_string($submit) && $input->attr('name') != $submit) 1379 | continue; 1380 | } 1381 | $return[] = array( 1382 | 'name' => $input->attr('name'), 1383 | 'value' => $input->val(), 1384 | ); 1385 | } 1386 | return $return; 1387 | } 1388 | /** 1389 | * @access private 1390 | */ 1391 | protected function debug($in) { 1392 | if (! phpQuery::$debug ) 1393 | return; 1394 | print('
');
1395 | 		print_r($in);
1396 | 		// file debug
1397 | //		file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
1398 | 		// quite handy debug trace
1399 | //		if ( is_array($in))
1400 | //			print_r(array_slice(debug_backtrace(), 3));
1401 | 		print("
\n"); 1402 | } 1403 | /** 1404 | * @access private 1405 | */ 1406 | protected function isRegexp($pattern) { 1407 | return in_array( 1408 | $pattern[ mb_strlen($pattern)-1 ], 1409 | array('^','*','$') 1410 | ); 1411 | } 1412 | /** 1413 | * Determines if $char is really a char. 1414 | * 1415 | * @param string $char 1416 | * @return bool 1417 | * @todo rewrite me to charcode range ! ;) 1418 | * @access private 1419 | */ 1420 | protected function isChar($char) { 1421 | return extension_loaded('mbstring') && phpQuery::$mbstringSupport 1422 | ? mb_eregi('\w', $char) 1423 | : preg_match('@\w@', $char); 1424 | } 1425 | /** 1426 | * @access private 1427 | */ 1428 | protected function parseSelector($query) { 1429 | // clean spaces 1430 | // TODO include this inside parsing ? 1431 | $query = trim( 1432 | preg_replace('@\s+@', ' ', 1433 | preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) 1434 | ) 1435 | ); 1436 | $queries = array(array()); 1437 | if (! $query) 1438 | return $queries; 1439 | $return =& $queries[0]; 1440 | $specialChars = array('>',' '); 1441 | // $specialCharsMapping = array('/' => '>'); 1442 | $specialCharsMapping = array(); 1443 | $strlen = mb_strlen($query); 1444 | $classChars = array('.', '-'); 1445 | $pseudoChars = array('-'); 1446 | $tagChars = array('*', '|', '-'); 1447 | // split multibyte string 1448 | // http://code.google.com/p/phpquery/issues/detail?id=76 1449 | $_query = array(); 1450 | for ($i=0; $i<$strlen; $i++) 1451 | $_query[] = mb_substr($query, $i, 1); 1452 | $query = $_query; 1453 | // it works, but i dont like it... 1454 | $i = 0; 1455 | while( $i < $strlen) { 1456 | $c = $query[$i]; 1457 | $tmp = ''; 1458 | // TAG 1459 | if ($this->isChar($c) || in_array($c, $tagChars)) { 1460 | while(isset($query[$i]) 1461 | && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) { 1462 | $tmp .= $query[$i]; 1463 | $i++; 1464 | } 1465 | $return[] = $tmp; 1466 | // IDs 1467 | } else if ( $c == '#') { 1468 | $i++; 1469 | while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) { 1470 | $tmp .= $query[$i]; 1471 | $i++; 1472 | } 1473 | $return[] = '#'.$tmp; 1474 | // SPECIAL CHARS 1475 | } else if (in_array($c, $specialChars)) { 1476 | $return[] = $c; 1477 | $i++; 1478 | // MAPPED SPECIAL MULTICHARS 1479 | // } else if ( $c.$query[$i+1] == '//') { 1480 | // $return[] = ' '; 1481 | // $i = $i+2; 1482 | // MAPPED SPECIAL CHARS 1483 | } else if ( isset($specialCharsMapping[$c])) { 1484 | $return[] = $specialCharsMapping[$c]; 1485 | $i++; 1486 | // COMMA 1487 | } else if ( $c == ',') { 1488 | $queries[] = array(); 1489 | $return =& $queries[ count($queries)-1 ]; 1490 | $i++; 1491 | while( isset($query[$i]) && $query[$i] == ' ') 1492 | $i++; 1493 | // CLASSES 1494 | } else if ($c == '.') { 1495 | while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { 1496 | $tmp .= $query[$i]; 1497 | $i++; 1498 | } 1499 | $return[] = $tmp; 1500 | // ~ General Sibling Selector 1501 | } else if ($c == '~') { 1502 | $spaceAllowed = true; 1503 | $tmp .= $query[$i++]; 1504 | while( isset($query[$i]) 1505 | && ($this->isChar($query[$i]) 1506 | || in_array($query[$i], $classChars) 1507 | || $query[$i] == '*' 1508 | || ($query[$i] == ' ' && $spaceAllowed) 1509 | )) { 1510 | if ($query[$i] != ' ') 1511 | $spaceAllowed = false; 1512 | $tmp .= $query[$i]; 1513 | $i++; 1514 | } 1515 | $return[] = $tmp; 1516 | // + Adjacent sibling selectors 1517 | } else if ($c == '+') { 1518 | $spaceAllowed = true; 1519 | $tmp .= $query[$i++]; 1520 | while( isset($query[$i]) 1521 | && ($this->isChar($query[$i]) 1522 | || in_array($query[$i], $classChars) 1523 | || $query[$i] == '*' 1524 | || ($spaceAllowed && $query[$i] == ' ') 1525 | )) { 1526 | if ($query[$i] != ' ') 1527 | $spaceAllowed = false; 1528 | $tmp .= $query[$i]; 1529 | $i++; 1530 | } 1531 | $return[] = $tmp; 1532 | // ATTRS 1533 | } else if ($c == '[') { 1534 | $stack = 1; 1535 | $tmp .= $c; 1536 | while( isset($query[++$i])) { 1537 | $tmp .= $query[$i]; 1538 | if ( $query[$i] == '[') { 1539 | $stack++; 1540 | } else if ( $query[$i] == ']') { 1541 | $stack--; 1542 | if (! $stack ) 1543 | break; 1544 | } 1545 | } 1546 | $return[] = $tmp; 1547 | $i++; 1548 | // PSEUDO CLASSES 1549 | } else if ($c == ':') { 1550 | $stack = 1; 1551 | $tmp .= $query[$i++]; 1552 | while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { 1553 | $tmp .= $query[$i]; 1554 | $i++; 1555 | } 1556 | // with arguments ? 1557 | if ( isset($query[$i]) && $query[$i] == '(') { 1558 | $tmp .= $query[$i]; 1559 | $stack = 1; 1560 | while( isset($query[++$i])) { 1561 | $tmp .= $query[$i]; 1562 | if ( $query[$i] == '(') { 1563 | $stack++; 1564 | } else if ( $query[$i] == ')') { 1565 | $stack--; 1566 | if (! $stack ) 1567 | break; 1568 | } 1569 | } 1570 | $return[] = $tmp; 1571 | $i++; 1572 | } else { 1573 | $return[] = $tmp; 1574 | } 1575 | } else { 1576 | $i++; 1577 | } 1578 | } 1579 | foreach($queries as $k => $q) { 1580 | if (isset($q[0])) { 1581 | if (isset($q[0][0]) && $q[0][0] == ':') 1582 | array_unshift($queries[$k], '*'); 1583 | if ($q[0] != '>') 1584 | array_unshift($queries[$k], ' '); 1585 | } 1586 | } 1587 | return $queries; 1588 | } 1589 | /** 1590 | * Return matched DOM nodes. 1591 | * 1592 | * @param int $index 1593 | * @return array|DOMElement Single DOMElement or array of DOMElement. 1594 | */ 1595 | public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { 1596 | $return = isset($index) 1597 | ? (isset($this->elements[$index]) ? $this->elements[$index] : null) 1598 | : $this->elements; 1599 | // pass thou callbacks 1600 | $args = func_get_args(); 1601 | $args = array_slice($args, 1); 1602 | foreach($args as $callback) { 1603 | if (is_array($return)) 1604 | foreach($return as $k => $v) 1605 | $return[$k] = phpQuery::callbackRun($callback, array($v)); 1606 | else 1607 | $return = phpQuery::callbackRun($callback, array($return)); 1608 | } 1609 | return $return; 1610 | } 1611 | /** 1612 | * Return matched DOM nodes. 1613 | * jQuery difference. 1614 | * 1615 | * @param int $index 1616 | * @return array|string Returns string if $index != null 1617 | * @todo implement callbacks 1618 | * @todo return only arrays ? 1619 | * @todo maybe other name... 1620 | */ 1621 | public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { 1622 | if (!is_null($index) && is_int($index)) 1623 | $return = $this->eq($index)->text(); 1624 | else { 1625 | $return = array(); 1626 | for($i = 0; $i < $this->size(); $i++) { 1627 | $return[] = $this->eq($i)->text(); 1628 | } 1629 | } 1630 | // pass thou callbacks 1631 | $args = func_get_args(); 1632 | $args = array_slice($args, 1); 1633 | foreach($args as $callback) { 1634 | $return = phpQuery::callbackRun($callback, array($return)); 1635 | } 1636 | return $return; 1637 | } 1638 | /** 1639 | * Return matched DOM nodes. 1640 | * jQuery difference. 1641 | * 1642 | * @param int $index 1643 | * @return array|string Returns string if $index != null 1644 | * @todo implement callbacks 1645 | * @todo return only arrays ? 1646 | * @todo maybe other name... 1647 | */ 1648 | public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { 1649 | if (!is_null($index) && is_int($index)) 1650 | $return = $this->eq($index)->text(); 1651 | else { 1652 | $return = array(); 1653 | for($i = 0; $i < $this->size(); $i++) { 1654 | $return[] = $this->eq($i)->text(); 1655 | } 1656 | // pass thou callbacks 1657 | $args = func_get_args(); 1658 | $args = array_slice($args, 1); 1659 | } 1660 | foreach($args as $callback) { 1661 | if (is_array($return)) 1662 | foreach($return as $k => $v) 1663 | $return[$k] = phpQuery::callbackRun($callback, array($v)); 1664 | else 1665 | $return = phpQuery::callbackRun($callback, array($return)); 1666 | } 1667 | return $return; 1668 | } 1669 | /** 1670 | * Returns new instance of actual class. 1671 | * 1672 | * @param array $newStack Optional. Will replace old stack with new and move old one to history.c 1673 | */ 1674 | public function newInstance($newStack = null) { 1675 | $class = get_class($this); 1676 | // support inheritance by passing old object to overloaded constructor 1677 | $new = $class != 'phpQuery' 1678 | ? new $class($this, $this->getDocumentID()) 1679 | : new phpQueryObject($this->getDocumentID()); 1680 | $new->previous = $this; 1681 | if (is_null($newStack)) { 1682 | $new->elements = $this->elements; 1683 | if ($this->elementsBackup) 1684 | $this->elements = $this->elementsBackup; 1685 | } else if (is_string($newStack)) { 1686 | $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack(); 1687 | } else { 1688 | $new->elements = $newStack; 1689 | } 1690 | return $new; 1691 | } 1692 | /** 1693 | * Enter description here... 1694 | * 1695 | * In the future, when PHP will support XLS 2.0, then we would do that this way: 1696 | * contains(tokenize(@class, '\s'), "something") 1697 | * @param unknown_type $class 1698 | * @param unknown_type $node 1699 | * @return boolean 1700 | * @access private 1701 | */ 1702 | protected function matchClasses($class, $node) { 1703 | // multi-class 1704 | if ( mb_strpos($class, '.', 1)) { 1705 | $classes = explode('.', substr($class, 1)); 1706 | $classesCount = count( $classes ); 1707 | $nodeClasses = explode(' ', $node->getAttribute('class') ); 1708 | $nodeClassesCount = count( $nodeClasses ); 1709 | if ( $classesCount > $nodeClassesCount ) 1710 | return false; 1711 | $diff = count( 1712 | array_diff( 1713 | $classes, 1714 | $nodeClasses 1715 | ) 1716 | ); 1717 | if (! $diff ) 1718 | return true; 1719 | // single-class 1720 | } else { 1721 | return in_array( 1722 | // strip leading dot from class name 1723 | substr($class, 1), 1724 | // get classes for element as array 1725 | explode(' ', $node->getAttribute('class') ) 1726 | ); 1727 | } 1728 | } 1729 | /** 1730 | * @access private 1731 | */ 1732 | protected function runQuery($XQuery, $selector = null, $compare = null) { 1733 | if ($compare && ! method_exists($this, $compare)) 1734 | return false; 1735 | $stack = array(); 1736 | if (! $this->elements) 1737 | $this->debug('Stack empty, skipping...'); 1738 | // var_dump($this->elements[0]->nodeType); 1739 | // element, document 1740 | foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) { 1741 | $detachAfter = false; 1742 | // to work on detached nodes we need temporary place them somewhere 1743 | // thats because context xpath queries sucks ;] 1744 | $testNode = $stackNode; 1745 | while ($testNode) { 1746 | if (! $testNode->parentNode && ! $this->isRoot($testNode)) { 1747 | $this->root->appendChild($testNode); 1748 | $detachAfter = $testNode; 1749 | break; 1750 | } 1751 | $testNode = isset($testNode->parentNode) 1752 | ? $testNode->parentNode 1753 | : null; 1754 | } 1755 | // XXX tmp ? 1756 | $xpath = $this->documentWrapper->isXHTML 1757 | ? $this->getNodeXpath($stackNode, 'html') 1758 | : $this->getNodeXpath($stackNode); 1759 | // FIXME pseudoclasses-only query, support XML 1760 | $query = $XQuery == '//' && $xpath == '/html[1]' 1761 | ? '//*' 1762 | : $xpath.$XQuery; 1763 | $this->debug("XPATH: {$query}"); 1764 | // run query, get elements 1765 | $nodes = $this->xpath->query($query); 1766 | $this->debug("QUERY FETCHED"); 1767 | if (! $nodes->length ) 1768 | $this->debug('Nothing found'); 1769 | $debug = array(); 1770 | foreach($nodes as $node) { 1771 | $matched = false; 1772 | if ( $compare) { 1773 | phpQuery::$debug ? 1774 | $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") 1775 | : null; 1776 | $phpQueryDebug = phpQuery::$debug; 1777 | phpQuery::$debug = false; 1778 | // TODO ??? use phpQuery::callbackRun() 1779 | if (call_user_func_array(array($this, $compare), array($selector, $node))) 1780 | $matched = true; 1781 | phpQuery::$debug = $phpQueryDebug; 1782 | } else { 1783 | $matched = true; 1784 | } 1785 | if ( $matched) { 1786 | if (phpQuery::$debug) 1787 | $debug[] = $this->whois( $node ); 1788 | $stack[] = $node; 1789 | } 1790 | } 1791 | if (phpQuery::$debug) { 1792 | $this->debug("Matched ".count($debug).": ".implode(', ', $debug)); 1793 | } 1794 | if ($detachAfter) 1795 | $this->root->removeChild($detachAfter); 1796 | } 1797 | $this->elements = $stack; 1798 | } 1799 | /** 1800 | * Enter description here... 1801 | * 1802 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1803 | */ 1804 | public function find($selectors, $context = null, $noHistory = false) { 1805 | if (!$noHistory) 1806 | // backup last stack /for end()/ 1807 | $this->elementsBackup = $this->elements; 1808 | // allow to define context 1809 | // TODO combine code below with phpQuery::pq() context guessing code 1810 | // as generic function 1811 | if ($context) { 1812 | if (! is_array($context) && $context instanceof DOMELEMENT) 1813 | $this->elements = array($context); 1814 | else if (is_array($context)) { 1815 | $this->elements = array(); 1816 | foreach ($context as $c) 1817 | if ($c instanceof DOMELEMENT) 1818 | $this->elements[] = $c; 1819 | } else if ( $context instanceof self ) 1820 | $this->elements = $context->elements; 1821 | } 1822 | $queries = $this->parseSelector($selectors); 1823 | $this->debug(array('FIND', $selectors, $queries)); 1824 | $XQuery = ''; 1825 | // remember stack state because of multi-queries 1826 | $oldStack = $this->elements; 1827 | // here we will be keeping found elements 1828 | $stack = array(); 1829 | foreach($queries as $selector) { 1830 | $this->elements = $oldStack; 1831 | $delimiterBefore = false; 1832 | foreach($selector as $s) { 1833 | // TAG 1834 | $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport 1835 | ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*' 1836 | : preg_match('@^[\w|\||-]+$@', $s) || $s == '*'; 1837 | if ($isTag) { 1838 | if ($this->isXML()) { 1839 | // namespace support 1840 | if (mb_strpos($s, '|') !== false) { 1841 | $ns = $tag = null; 1842 | list($ns, $tag) = explode('|', $s); 1843 | $XQuery .= "$ns:$tag"; 1844 | } else if ($s == '*') { 1845 | $XQuery .= "*"; 1846 | } else { 1847 | $XQuery .= "*[local-name()='$s']"; 1848 | } 1849 | } else { 1850 | $XQuery .= $s; 1851 | } 1852 | // ID 1853 | } else if ($s[0] == '#') { 1854 | if ($delimiterBefore) 1855 | $XQuery .= '*'; 1856 | $XQuery .= "[@id='".substr($s, 1)."']"; 1857 | // ATTRIBUTES 1858 | } else if ($s[0] == '[') { 1859 | if ($delimiterBefore) 1860 | $XQuery .= '*'; 1861 | // strip side brackets 1862 | $attr = trim($s, ']['); 1863 | $execute = false; 1864 | // attr with specifed value 1865 | if (mb_strpos($s, '=')) { 1866 | $value = null; 1867 | list($attr, $value) = explode('=', $attr); 1868 | $value = trim($value, "'\""); 1869 | if ($this->isRegexp($attr)) { 1870 | // cut regexp character 1871 | $attr = substr($attr, 0, -1); 1872 | $execute = true; 1873 | $XQuery .= "[@{$attr}]"; 1874 | } else { 1875 | $XQuery .= "[@{$attr}='{$value}']"; 1876 | } 1877 | // attr without specified value 1878 | } else { 1879 | $XQuery .= "[@{$attr}]"; 1880 | } 1881 | if ($execute) { 1882 | $this->runQuery($XQuery, $s, 'is'); 1883 | $XQuery = ''; 1884 | if (! $this->length()) 1885 | break; 1886 | } 1887 | // CLASSES 1888 | } else if ($s[0] == '.') { 1889 | // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]"); 1890 | // thx wizDom ;) 1891 | if ($delimiterBefore) 1892 | $XQuery .= '*'; 1893 | $XQuery .= '[@class]'; 1894 | $this->runQuery($XQuery, $s, 'matchClasses'); 1895 | $XQuery = ''; 1896 | if (! $this->length() ) 1897 | break; 1898 | // ~ General Sibling Selector 1899 | } else if ($s[0] == '~') { 1900 | $this->runQuery($XQuery); 1901 | $XQuery = ''; 1902 | $this->elements = $this 1903 | ->siblings( 1904 | substr($s, 1) 1905 | )->elements; 1906 | if (! $this->length() ) 1907 | break; 1908 | // + Adjacent sibling selectors 1909 | } else if ($s[0] == '+') { 1910 | // TODO /following-sibling:: 1911 | $this->runQuery($XQuery); 1912 | $XQuery = ''; 1913 | $subSelector = substr($s, 1); 1914 | $subElements = $this->elements; 1915 | $this->elements = array(); 1916 | foreach($subElements as $node) { 1917 | // search first DOMElement sibling 1918 | $test = $node->nextSibling; 1919 | while($test && ! ($test instanceof DOMELEMENT)) 1920 | $test = $test->nextSibling; 1921 | if ($test && $this->is($subSelector, $test)) 1922 | $this->elements[] = $test; 1923 | } 1924 | if (! $this->length() ) 1925 | break; 1926 | // PSEUDO CLASSES 1927 | } else if ($s[0] == ':') { 1928 | // TODO optimization for :first :last 1929 | if ($XQuery) { 1930 | $this->runQuery($XQuery); 1931 | $XQuery = ''; 1932 | } 1933 | if (! $this->length()) 1934 | break; 1935 | $this->pseudoClasses($s); 1936 | if (! $this->length()) 1937 | break; 1938 | // DIRECT DESCENDANDS 1939 | } else if ($s == '>') { 1940 | $XQuery .= '/'; 1941 | $delimiterBefore = 2; 1942 | // ALL DESCENDANDS 1943 | } else if ($s == ' ') { 1944 | $XQuery .= '//'; 1945 | $delimiterBefore = 2; 1946 | // ERRORS 1947 | } else { 1948 | phpQuery::debug("Unrecognized token '$s'"); 1949 | } 1950 | $delimiterBefore = $delimiterBefore === 2; 1951 | } 1952 | // run query if any 1953 | if ($XQuery && $XQuery != '//') { 1954 | $this->runQuery($XQuery); 1955 | $XQuery = ''; 1956 | } 1957 | foreach($this->elements as $node) 1958 | if (! $this->elementsContainsNode($node, $stack)) 1959 | $stack[] = $node; 1960 | } 1961 | $this->elements = $stack; 1962 | return $this->newInstance(); 1963 | } 1964 | /** 1965 | * @todo create API for classes with pseudoselectors 1966 | * @access private 1967 | */ 1968 | protected function pseudoClasses($class) { 1969 | // TODO clean args parsing ? 1970 | $class = ltrim($class, ':'); 1971 | $haveArgs = mb_strpos($class, '('); 1972 | if ($haveArgs !== false) { 1973 | $args = substr($class, $haveArgs+1, -1); 1974 | $class = substr($class, 0, $haveArgs); 1975 | } 1976 | switch($class) { 1977 | case 'even': 1978 | case 'odd': 1979 | $stack = array(); 1980 | foreach($this->elements as $i => $node) { 1981 | if ($class == 'even' && ($i%2) == 0) 1982 | $stack[] = $node; 1983 | else if ( $class == 'odd' && $i % 2 ) 1984 | $stack[] = $node; 1985 | } 1986 | $this->elements = $stack; 1987 | break; 1988 | case 'eq': 1989 | $k = intval($args); 1990 | if ($k < 0) { 1991 | $this->elements = array( $this->elements[count($this->elements)+$k] ); 1992 | } else { 1993 | $this->elements = isset($this->elements[$k]) 1994 | ? array($this->elements[$k]) 1995 | : array(); 1996 | } 1997 | break; 1998 | case 'gt': 1999 | $this->elements = array_slice($this->elements, $args+1); 2000 | break; 2001 | case 'lt': 2002 | $this->elements = array_slice($this->elements, 0, $args+1); 2003 | break; 2004 | case 'first': 2005 | if (isset($this->elements[0])) 2006 | $this->elements = array($this->elements[0]); 2007 | break; 2008 | case 'last': 2009 | if ($this->elements) 2010 | $this->elements = array($this->elements[count($this->elements)-1]); 2011 | break; 2012 | /*case 'parent': 2013 | $stack = array(); 2014 | foreach($this->elements as $node) { 2015 | if ( $node->childNodes->length ) 2016 | $stack[] = $node; 2017 | } 2018 | $this->elements = $stack; 2019 | break;*/ 2020 | case 'contains': 2021 | $text = trim($args, "\"'"); 2022 | $stack = array(); 2023 | foreach($this->elements as $node) { 2024 | if (mb_stripos($node->textContent, $text) === false) 2025 | continue; 2026 | $stack[] = $node; 2027 | } 2028 | $this->elements = $stack; 2029 | break; 2030 | case 'not': 2031 | $selector = self::unQuote($args); 2032 | $this->elements = $this->not($selector)->stack(); 2033 | break; 2034 | case 'slice': 2035 | // TODO jQuery difference ? 2036 | $args = explode(',', 2037 | str_replace(', ', ',', trim($args, "\"'")) 2038 | ); 2039 | $start = $args[0]; 2040 | $end = isset($args[1]) 2041 | ? $args[1] 2042 | : null; 2043 | if ($end > 0) 2044 | $end = $end-$start; 2045 | $this->elements = array_slice($this->elements, $start, $end); 2046 | break; 2047 | case 'has': 2048 | $selector = trim($args, "\"'"); 2049 | $stack = array(); 2050 | foreach($this->stack(1) as $el) { 2051 | if ($this->find($selector, $el, true)->length) 2052 | $stack[] = $el; 2053 | } 2054 | $this->elements = $stack; 2055 | break; 2056 | case 'submit': 2057 | case 'reset': 2058 | $this->elements = phpQuery::merge( 2059 | $this->map(array($this, 'is'), 2060 | "input[type=$class]", new CallbackParam() 2061 | ), 2062 | $this->map(array($this, 'is'), 2063 | "button[type=$class]", new CallbackParam() 2064 | ) 2065 | ); 2066 | break; 2067 | // $stack = array(); 2068 | // foreach($this->elements as $node) 2069 | // if ($node->is('input[type=submit]') || $node->is('button[type=submit]')) 2070 | // $stack[] = $el; 2071 | // $this->elements = $stack; 2072 | case 'input': 2073 | $this->elements = $this->map( 2074 | array($this, 'is'), 2075 | 'input', new CallbackParam() 2076 | )->elements; 2077 | break; 2078 | case 'password': 2079 | case 'checkbox': 2080 | case 'radio': 2081 | case 'hidden': 2082 | case 'image': 2083 | case 'file': 2084 | $this->elements = $this->map( 2085 | array($this, 'is'), 2086 | "input[type=$class]", new CallbackParam() 2087 | )->elements; 2088 | break; 2089 | case 'parent': 2090 | $this->elements = $this->map( 2091 | function ($node) { 2092 | return $node instanceof DOMELEMENT && $node->childNodes->length 2093 | ? $node : null; 2094 | } 2095 | )->elements; 2096 | break; 2097 | case 'empty': 2098 | $this->elements = $this->map( 2099 | function ($node) { 2100 | return $node instanceof DOMELEMENT && $node->childNodes->length 2101 | ? null : $node; 2102 | } 2103 | )->elements; 2104 | break; 2105 | case 'disabled': 2106 | case 'selected': 2107 | case 'checked': 2108 | $this->elements = $this->map( 2109 | array($this, 'is'), 2110 | "[$class]", new CallbackParam() 2111 | )->elements; 2112 | break; 2113 | case 'enabled': 2114 | $this->elements = $this->map( 2115 | function ($node) { 2116 | return pq($node)->not(":disabled") ? $node : null; 2117 | } 2118 | )->elements; 2119 | break; 2120 | case 'header': 2121 | $this->elements = $this->map( 2122 | function ($node) { 2123 | $isHeader = isset($node->tagName) && in_array($node->tagName, array( 2124 | "h1", "h2", "h3", "h4", "h5", "h6", "h7" 2125 | )); 2126 | return $isHeader 2127 | ? $node 2128 | : null; 2129 | } 2130 | )->elements; 2131 | // $this->elements = $this->map( 2132 | // create_function('$node', '$node = pq($node); 2133 | // return $node->is("h1") 2134 | // || $node->is("h2") 2135 | // || $node->is("h3") 2136 | // || $node->is("h4") 2137 | // || $node->is("h5") 2138 | // || $node->is("h6") 2139 | // || $node->is("h7") 2140 | // ? $node 2141 | // : null;') 2142 | // )->elements; 2143 | break; 2144 | case 'only-child': 2145 | $this->elements = $this->map( 2146 | function ($node) { 2147 | return pq($node)->siblings()->size() == 0 ? $node : null; 2148 | } 2149 | )->elements; 2150 | break; 2151 | case 'first-child': 2152 | $this->elements = $this->map( 2153 | function ($node) { 2154 | return pq($node)->prevAll()->size() == 0 ? $node : null; 2155 | } 2156 | )->elements; 2157 | break; 2158 | case 'last-child': 2159 | $this->elements = $this->map( 2160 | function ($node) { 2161 | return pq($node)->nextAll()->size() == 0 ? $node : null; 2162 | } 2163 | )->elements; 2164 | break; 2165 | case 'nth-child': 2166 | $param = trim($args, "\"'"); 2167 | if (! $param) 2168 | break; 2169 | // nth-child(n+b) to nth-child(1n+b) 2170 | if ($param{0} == 'n') 2171 | $param = '1'.$param; 2172 | // :nth-child(index/even/odd/equation) 2173 | if ($param == 'even' || $param == 'odd') 2174 | $mapped = $this->map( 2175 | function ($node, $param) { 2176 | $index = pq($node)->prevAll()->size()+1; 2177 | if ($param == "even" && ($index%2) == 0) 2178 | return $node; 2179 | else if ($param == "odd" && $index%2 == 1) 2180 | return $node; 2181 | else 2182 | return null; 2183 | }, 2184 | new CallbackParam(), $param 2185 | ); 2186 | else if (mb_strlen($param) > 1 && preg_match('/^(\d*)n([-+]?)(\d*)/', $param) === 1) 2187 | // an+b 2188 | $mapped = $this->map( 2189 | function ($node, $param) { 2190 | $prevs = pq($node)->prevAll()->size(); 2191 | $index = 1+$prevs; 2192 | 2193 | preg_match("/^(\d*)n([-+]?)(\d*)/", $param, $matches); 2194 | $a = intval($matches[1]); 2195 | $b = intval($matches[3]); 2196 | if( $matches[2] === "-" ) { 2197 | $b = -$b; 2198 | } 2199 | 2200 | if ($a > 0) { 2201 | return ($index-$b)%$a == 0 2202 | ? $node 2203 | : null; 2204 | phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs"); 2205 | return $a*floor($index/$a)+$b-1 == $prevs 2206 | ? $node 2207 | : null; 2208 | } else if ($a == 0) 2209 | return $index == $b 2210 | ? $node 2211 | : null; 2212 | else 2213 | // negative value 2214 | return $index <= $b 2215 | ? $node 2216 | : null; 2217 | // if (! $b) 2218 | // return $index%$a == 0 2219 | // ? $node 2220 | // : null; 2221 | // else 2222 | // return ($index-$b)%$a == 0 2223 | // ? $node 2224 | // : null; 2225 | }, 2226 | new CallbackParam(), $param 2227 | ); 2228 | else 2229 | // index 2230 | $mapped = $this->map( 2231 | function ($node, $index) { 2232 | $prevs = pq($node)->prevAll()->size(); 2233 | if ($prevs && $prevs == $index-1) 2234 | return $node; 2235 | else if (! $prevs && $index == 1) 2236 | return $node; 2237 | else 2238 | return null; 2239 | }, 2240 | new CallbackParam(), $param 2241 | ); 2242 | $this->elements = $mapped->elements; 2243 | break; 2244 | default: 2245 | $this->debug("Unknown pseudoclass '{$class}', skipping..."); 2246 | } 2247 | } 2248 | /** 2249 | * @access private 2250 | */ 2251 | protected function __pseudoClassParam($paramsString) { 2252 | // TODO; 2253 | } 2254 | /** 2255 | * Enter description here... 2256 | * 2257 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2258 | */ 2259 | public function is($selector, $nodes = null) { 2260 | phpQuery::debug(array("Is:", $selector)); 2261 | if (! $selector) 2262 | return false; 2263 | $oldStack = $this->elements; 2264 | $returnArray = false; 2265 | if ($nodes && is_array($nodes)) { 2266 | $this->elements = $nodes; 2267 | } else if ($nodes) 2268 | $this->elements = array($nodes); 2269 | $this->filter($selector, true); 2270 | $stack = $this->elements; 2271 | $this->elements = $oldStack; 2272 | if ($nodes) 2273 | return $stack ? $stack : null; 2274 | return (bool)count($stack); 2275 | } 2276 | /** 2277 | * Enter description here... 2278 | * jQuery difference. 2279 | * 2280 | * Callback: 2281 | * - $index int 2282 | * - $node DOMNode 2283 | * 2284 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2285 | * @link http://docs.jquery.com/Traversing/filter 2286 | */ 2287 | public function filterCallback($callback, $_skipHistory = false) { 2288 | if (! $_skipHistory) { 2289 | $this->elementsBackup = $this->elements; 2290 | $this->debug("Filtering by callback"); 2291 | } 2292 | $newStack = array(); 2293 | foreach($this->elements as $index => $node) { 2294 | $result = phpQuery::callbackRun($callback, array($index, $node)); 2295 | if (is_null($result) || (! is_null($result) && $result)) 2296 | $newStack[] = $node; 2297 | } 2298 | $this->elements = $newStack; 2299 | return $_skipHistory 2300 | ? $this 2301 | : $this->newInstance(); 2302 | } 2303 | /** 2304 | * Enter description here... 2305 | * 2306 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2307 | * @link http://docs.jquery.com/Traversing/filter 2308 | */ 2309 | public function filter($selectors, $_skipHistory = false) { 2310 | if ($selectors instanceof Callback OR $selectors instanceof Closure) 2311 | return $this->filterCallback($selectors, $_skipHistory); 2312 | if (! $_skipHistory) 2313 | $this->elementsBackup = $this->elements; 2314 | $notSimpleSelector = array(' ', '>', '~', '+', '/'); 2315 | if (! is_array($selectors)) 2316 | $selectors = $this->parseSelector($selectors); 2317 | if (! $_skipHistory) 2318 | $this->debug(array("Filtering:", $selectors)); 2319 | $finalStack = array(); 2320 | foreach($selectors as $selector) { 2321 | $stack = array(); 2322 | if (! $selector) 2323 | break; 2324 | // avoid first space or / 2325 | if (in_array($selector[0], $notSimpleSelector)) 2326 | $selector = array_slice($selector, 1); 2327 | // PER NODE selector chunks 2328 | foreach($this->stack() as $node) { 2329 | $break = false; 2330 | foreach($selector as $s) { 2331 | if (!($node instanceof DOMELEMENT)) { 2332 | // all besides DOMElement 2333 | if ( $s[0] == '[') { 2334 | $attr = trim($s, '[]'); 2335 | if ( mb_strpos($attr, '=')) { 2336 | list( $attr, $val ) = explode('=', $attr); 2337 | if ($attr == 'nodeType' && $node->nodeType != $val) 2338 | $break = true; 2339 | } 2340 | } else 2341 | $break = true; 2342 | } else { 2343 | // DOMElement only 2344 | // ID 2345 | if ( $s[0] == '#') { 2346 | if ( $node->getAttribute('id') != substr($s, 1) ) 2347 | $break = true; 2348 | // CLASSES 2349 | } else if ( $s[0] == '.') { 2350 | if (! $this->matchClasses( $s, $node ) ) 2351 | $break = true; 2352 | // ATTRS 2353 | } else if ( $s[0] == '[') { 2354 | // strip side brackets 2355 | $attr = trim($s, '[]'); 2356 | if (mb_strpos($attr, '=')) { 2357 | list($attr, $val) = explode('=', $attr); 2358 | $val = self::unQuote($val); 2359 | if ($attr == 'nodeType') { 2360 | if ($val != $node->nodeType) 2361 | $break = true; 2362 | } else if ($this->isRegexp($attr)) { 2363 | $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport 2364 | ? quotemeta(trim($val, '"\'')) 2365 | : preg_quote(trim($val, '"\''), '@'); 2366 | // switch last character 2367 | switch( substr($attr, -1)) { 2368 | // quotemeta used insted of preg_quote 2369 | // http://code.google.com/p/phpquery/issues/detail?id=76 2370 | case '^': 2371 | $pattern = '^'.$val; 2372 | break; 2373 | case '*': 2374 | $pattern = '.*'.$val.'.*'; 2375 | break; 2376 | case '$': 2377 | $pattern = '.*'.$val.'$'; 2378 | break; 2379 | } 2380 | // cut last character 2381 | $attr = substr($attr, 0, -1); 2382 | $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport 2383 | ? mb_ereg_match($pattern, $node->getAttribute($attr)) 2384 | : preg_match("@{$pattern}@", $node->getAttribute($attr)); 2385 | if (! $isMatch) 2386 | $break = true; 2387 | } else if ($node->getAttribute($attr) != $val) 2388 | $break = true; 2389 | } else if (! $node->hasAttribute($attr)) 2390 | $break = true; 2391 | // PSEUDO CLASSES 2392 | } else if ( $s[0] == ':') { 2393 | // skip 2394 | // TAG 2395 | } else if (trim($s)) { 2396 | if ($s != '*') { 2397 | // TODO namespaces 2398 | if (isset($node->tagName)) { 2399 | if ($node->tagName != $s) 2400 | $break = true; 2401 | } else if ($s == 'html' && ! $this->isRoot($node)) 2402 | $break = true; 2403 | } 2404 | // AVOID NON-SIMPLE SELECTORS 2405 | } else if (in_array($s, $notSimpleSelector)) { 2406 | $break = true; 2407 | $this->debug(array('Skipping non simple selector', $selector)); 2408 | } 2409 | } 2410 | if ($break) 2411 | break; 2412 | } 2413 | // if element passed all chunks of selector - add it to new stack 2414 | if (! $break ) 2415 | $stack[] = $node; 2416 | } 2417 | $tmpStack = $this->elements; 2418 | $this->elements = $stack; 2419 | // PER ALL NODES selector chunks 2420 | foreach($selector as $s) 2421 | // PSEUDO CLASSES 2422 | if ($s[0] == ':') 2423 | $this->pseudoClasses($s); 2424 | foreach($this->elements as $node) 2425 | // XXX it should be merged without duplicates 2426 | // but jQuery doesnt do that 2427 | $finalStack[] = $node; 2428 | $this->elements = $tmpStack; 2429 | } 2430 | $this->elements = $finalStack; 2431 | if ($_skipHistory) { 2432 | return $this; 2433 | } else { 2434 | $this->debug("Stack length after filter(): ".count($finalStack)); 2435 | return $this->newInstance(); 2436 | } 2437 | } 2438 | /** 2439 | * 2440 | * @param $value 2441 | * @return unknown_type 2442 | * @TODO implement in all methods using passed parameters 2443 | */ 2444 | protected static function unQuote($value) { 2445 | return $value[0] == '\'' || $value[0] == '"' 2446 | ? substr($value, 1, -1) 2447 | : $value; 2448 | } 2449 | /** 2450 | * Enter description here... 2451 | * 2452 | * @link http://docs.jquery.com/Ajax/load 2453 | * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2454 | * @todo Support $selector 2455 | */ 2456 | public function load($url, $data = null, $callback = null) { 2457 | if ($data && ! is_array($data)) { 2458 | $callback = $data; 2459 | $data = null; 2460 | } 2461 | if (mb_strpos($url, ' ') !== false) { 2462 | $matches = null; 2463 | if (extension_loaded('mbstring') && phpQuery::$mbstringSupport) 2464 | mb_ereg('^([^ ]+) (.*)$', $url, $matches); 2465 | else 2466 | preg_match('^([^ ]+) (.*)$', $url, $matches); 2467 | $url = $matches[1]; 2468 | $selector = $matches[2]; 2469 | // FIXME this sucks, pass as callback param 2470 | $this->_loadSelector = $selector; 2471 | } 2472 | $ajax = array( 2473 | 'url' => $url, 2474 | 'type' => $data ? 'POST' : 'GET', 2475 | 'data' => $data, 2476 | 'complete' => $callback, 2477 | 'success' => array($this, '__loadSuccess') 2478 | ); 2479 | phpQuery::ajax($ajax); 2480 | return $this; 2481 | } 2482 | /** 2483 | * @access private 2484 | * @param $html 2485 | * @return unknown_type 2486 | */ 2487 | public function __loadSuccess($html) { 2488 | if ($this->_loadSelector) { 2489 | $html = phpQuery::newDocument($html)->find($this->_loadSelector); 2490 | unset($this->_loadSelector); 2491 | } 2492 | foreach($this->stack(1) as $node) { 2493 | phpQuery::pq($node, $this->getDocumentID()) 2494 | ->markup($html); 2495 | } 2496 | } 2497 | /** 2498 | * Enter description here... 2499 | * 2500 | * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2501 | * @todo 2502 | */ 2503 | public function css() { 2504 | // TODO 2505 | return $this; 2506 | } 2507 | /** 2508 | * @todo 2509 | * 2510 | */ 2511 | public function show(){ 2512 | // TODO 2513 | return $this; 2514 | } 2515 | /** 2516 | * @todo 2517 | * 2518 | */ 2519 | public function hide(){ 2520 | // TODO 2521 | return $this; 2522 | } 2523 | /** 2524 | * Trigger a type of event on every matched element. 2525 | * 2526 | * @param unknown_type $type 2527 | * @param unknown_type $data 2528 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2529 | * @TODO support more than event in $type (space-separated) 2530 | */ 2531 | public function trigger($type, $data = array()) { 2532 | foreach($this->elements as $node) 2533 | phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node); 2534 | return $this; 2535 | } 2536 | /** 2537 | * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions. 2538 | * 2539 | * @param unknown_type $type 2540 | * @param unknown_type $data 2541 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2542 | * @TODO 2543 | */ 2544 | public function triggerHandler($type, $data = array()) { 2545 | // TODO; 2546 | } 2547 | /** 2548 | * Binds a handler to one or more events (like click) for each matched element. 2549 | * Can also bind custom events. 2550 | * 2551 | * @param unknown_type $type 2552 | * @param unknown_type $data Optional 2553 | * @param unknown_type $callback 2554 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2555 | * @TODO support '!' (exclusive) events 2556 | * @TODO support more than event in $type (space-separated) 2557 | */ 2558 | public function bind($type, $data, $callback = null) { 2559 | // TODO check if $data is callable, not using is_callable 2560 | if (! isset($callback)) { 2561 | $callback = $data; 2562 | $data = null; 2563 | } 2564 | foreach($this->elements as $node) 2565 | phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback); 2566 | return $this; 2567 | } 2568 | /** 2569 | * Enter description here... 2570 | * 2571 | * @param unknown_type $type 2572 | * @param unknown_type $callback 2573 | * @return unknown 2574 | * @TODO namespace events 2575 | * @TODO support more than event in $type (space-separated) 2576 | */ 2577 | public function unbind($type = null, $callback = null) { 2578 | foreach($this->elements as $node) 2579 | phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback); 2580 | return $this; 2581 | } 2582 | /** 2583 | * Enter description here... 2584 | * 2585 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2586 | */ 2587 | public function change($callback = null) { 2588 | if ($callback) 2589 | return $this->bind('change', $callback); 2590 | return $this->trigger('change'); 2591 | } 2592 | /** 2593 | * Enter description here... 2594 | * 2595 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2596 | */ 2597 | public function submit($callback = null) { 2598 | if ($callback) 2599 | return $this->bind('submit', $callback); 2600 | return $this->trigger('submit'); 2601 | } 2602 | /** 2603 | * Enter description here... 2604 | * 2605 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2606 | */ 2607 | public function click($callback = null) { 2608 | if ($callback) 2609 | return $this->bind('click', $callback); 2610 | return $this->trigger('click'); 2611 | } 2612 | /** 2613 | * Enter description here... 2614 | * 2615 | * @param String|phpQuery 2616 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2617 | */ 2618 | public function wrapAllOld($wrapper) { 2619 | $wrapper = pq($wrapper)->_clone(); 2620 | if (! $wrapper->length() || ! $this->length() ) 2621 | return $this; 2622 | $wrapper->insertBefore($this->elements[0]); 2623 | $deepest = $wrapper->elements[0]; 2624 | while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) 2625 | $deepest = $deepest->firstChild; 2626 | pq($deepest)->append($this); 2627 | return $this; 2628 | } 2629 | /** 2630 | * Enter description here... 2631 | * 2632 | * TODO testme... 2633 | * @param String|phpQuery 2634 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2635 | */ 2636 | public function wrapAll($wrapper) { 2637 | if (! $this->length()) 2638 | return $this; 2639 | return phpQuery::pq($wrapper, $this->getDocumentID()) 2640 | ->clone() 2641 | ->insertBefore($this->get(0)) 2642 | ->map(array($this, '___wrapAllCallback')) 2643 | ->append($this); 2644 | } 2645 | /** 2646 | * 2647 | * @param $node 2648 | * @return unknown_type 2649 | * @access private 2650 | */ 2651 | public function ___wrapAllCallback($node) { 2652 | $deepest = $node; 2653 | while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) 2654 | $deepest = $deepest->firstChild; 2655 | return $deepest; 2656 | } 2657 | /** 2658 | * Enter description here... 2659 | * NON JQUERY METHOD 2660 | * 2661 | * @param String|phpQuery 2662 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2663 | */ 2664 | public function wrapAllPHP($codeBefore, $codeAfter) { 2665 | return $this 2666 | ->slice(0, 1) 2667 | ->beforePHP($codeBefore) 2668 | ->end() 2669 | ->slice(-1) 2670 | ->afterPHP($codeAfter) 2671 | ->end(); 2672 | } 2673 | /** 2674 | * Enter description here... 2675 | * 2676 | * @param String|phpQuery 2677 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2678 | */ 2679 | public function wrap($wrapper) { 2680 | foreach($this->stack() as $node) 2681 | phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper); 2682 | return $this; 2683 | } 2684 | /** 2685 | * Enter description here... 2686 | * 2687 | * @param String|phpQuery 2688 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2689 | */ 2690 | public function wrapPHP($codeBefore, $codeAfter) { 2691 | foreach($this->stack() as $node) 2692 | phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter); 2693 | return $this; 2694 | } 2695 | /** 2696 | * Enter description here... 2697 | * 2698 | * @param String|phpQuery 2699 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2700 | */ 2701 | public function wrapInner($wrapper) { 2702 | foreach($this->stack() as $node) 2703 | phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper); 2704 | return $this; 2705 | } 2706 | /** 2707 | * Enter description here... 2708 | * 2709 | * @param String|phpQuery 2710 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2711 | */ 2712 | public function wrapInnerPHP($codeBefore, $codeAfter) { 2713 | foreach($this->stack(1) as $node) 2714 | phpQuery::pq($node, $this->getDocumentID())->contents() 2715 | ->wrapAllPHP($codeBefore, $codeAfter); 2716 | return $this; 2717 | } 2718 | /** 2719 | * Enter description here... 2720 | * 2721 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2722 | * @testme Support for text nodes 2723 | */ 2724 | public function contents() { 2725 | $stack = array(); 2726 | foreach($this->stack(1) as $el) { 2727 | // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56 2728 | // if (! isset($el->childNodes)) 2729 | // continue; 2730 | foreach($el->childNodes as $node) { 2731 | $stack[] = $node; 2732 | } 2733 | } 2734 | return $this->newInstance($stack); 2735 | } 2736 | /** 2737 | * Enter description here... 2738 | * 2739 | * jQuery difference. 2740 | * 2741 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2742 | */ 2743 | public function contentsUnwrap() { 2744 | foreach($this->stack(1) as $node) { 2745 | if (! $node->parentNode ) 2746 | continue; 2747 | $childNodes = array(); 2748 | // any modification in DOM tree breaks childNodes iteration, so cache them first 2749 | foreach($node->childNodes as $chNode ) 2750 | $childNodes[] = $chNode; 2751 | foreach($childNodes as $chNode ) 2752 | // $node->parentNode->appendChild($chNode); 2753 | $node->parentNode->insertBefore($chNode, $node); 2754 | $node->parentNode->removeChild($node); 2755 | } 2756 | return $this; 2757 | } 2758 | /** 2759 | * Enter description here... 2760 | * 2761 | * jQuery difference. 2762 | */ 2763 | public function switchWith($markup) { 2764 | $markup = pq($markup, $this->getDocumentID()); 2765 | $content = null; 2766 | foreach($this->stack(1) as $node) { 2767 | pq($node) 2768 | ->contents()->toReference($content)->end() 2769 | ->replaceWith($markup->clone()->append($content)); 2770 | } 2771 | return $this; 2772 | } 2773 | /** 2774 | * Enter description here... 2775 | * 2776 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2777 | */ 2778 | public function eq($num) { 2779 | $oldStack = $this->elements; 2780 | $this->elementsBackup = $this->elements; 2781 | $this->elements = array(); 2782 | if ( isset($oldStack[$num]) ) 2783 | $this->elements[] = $oldStack[$num]; 2784 | return $this->newInstance(); 2785 | } 2786 | /** 2787 | * Enter description here... 2788 | * 2789 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2790 | */ 2791 | public function size() { 2792 | return count($this->elements); 2793 | } 2794 | /** 2795 | * Enter description here... 2796 | * 2797 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2798 | * @deprecated Use length as attribute 2799 | */ 2800 | public function length() { 2801 | return $this->size(); 2802 | } 2803 | public function count() { 2804 | return $this->size(); 2805 | } 2806 | /** 2807 | * Enter description here... 2808 | * 2809 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2810 | * @todo $level 2811 | */ 2812 | public function end($level = 1) { 2813 | // $this->elements = array_pop( $this->history ); 2814 | // return $this; 2815 | // $this->previous->DOM = $this->DOM; 2816 | // $this->previous->XPath = $this->XPath; 2817 | return $this->previous 2818 | ? $this->previous 2819 | : $this; 2820 | } 2821 | /** 2822 | * Enter description here... 2823 | * Normal use ->clone() . 2824 | * 2825 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2826 | * @access private 2827 | */ 2828 | public function _clone() { 2829 | $newStack = array(); 2830 | //pr(array('copy... ', $this->whois())); 2831 | //$this->dumpHistory('copy'); 2832 | $this->elementsBackup = $this->elements; 2833 | foreach($this->elements as $node) { 2834 | $newStack[] = $node->cloneNode(true); 2835 | } 2836 | $this->elements = $newStack; 2837 | return $this->newInstance(); 2838 | } 2839 | /** 2840 | * Enter description here... 2841 | * 2842 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2843 | */ 2844 | public function replaceWithPHP($code) { 2845 | return $this->replaceWith(phpQuery::php($code)); 2846 | } 2847 | /** 2848 | * Enter description here... 2849 | * 2850 | * @param String|phpQuery $content 2851 | * @link http://docs.jquery.com/Manipulation/replaceWith#content 2852 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2853 | */ 2854 | public function replaceWith($content) { 2855 | return $this->after($content)->remove(); 2856 | } 2857 | /** 2858 | * Enter description here... 2859 | * 2860 | * @param String $selector 2861 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2862 | * @todo this works ? 2863 | */ 2864 | public function replaceAll($selector) { 2865 | foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node) 2866 | phpQuery::pq($node, $this->getDocumentID()) 2867 | ->after($this->_clone()) 2868 | ->remove(); 2869 | return $this; 2870 | } 2871 | /** 2872 | * Enter description here... 2873 | * 2874 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2875 | */ 2876 | public function remove($selector = null) { 2877 | $loop = $selector 2878 | ? $this->filter($selector)->elements 2879 | : $this->elements; 2880 | foreach($loop as $node) { 2881 | if (! $node->parentNode ) 2882 | continue; 2883 | if (isset($node->tagName)) 2884 | $this->debug("Removing '{$node->tagName}'"); 2885 | $node->parentNode->removeChild($node); 2886 | // Mutation event 2887 | $event = new DOMEvent(array( 2888 | 'target' => $node, 2889 | 'type' => 'DOMNodeRemoved' 2890 | )); 2891 | phpQueryEvents::trigger($this->getDocumentID(), 2892 | $event->type, array($event), $node 2893 | ); 2894 | } 2895 | return $this; 2896 | } 2897 | protected function markupEvents($newMarkup, $oldMarkup, $node) { 2898 | if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) { 2899 | $event = new DOMEvent(array( 2900 | 'target' => $node, 2901 | 'type' => 'change' 2902 | )); 2903 | phpQueryEvents::trigger($this->getDocumentID(), 2904 | $event->type, array($event), $node 2905 | ); 2906 | } 2907 | } 2908 | /** 2909 | * jQuey difference 2910 | * 2911 | * @param $markup 2912 | * @return unknown_type 2913 | * @TODO trigger change event for textarea 2914 | */ 2915 | public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) { 2916 | $args = func_get_args(); 2917 | if ($this->documentWrapper->isXML) 2918 | return call_user_func_array(array($this, 'xml'), $args); 2919 | else 2920 | return call_user_func_array(array($this, 'html'), $args); 2921 | } 2922 | /** 2923 | * jQuey difference 2924 | * 2925 | * @param $markup 2926 | * @return unknown_type 2927 | */ 2928 | public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) { 2929 | $args = func_get_args(); 2930 | if ($this->documentWrapper->isXML) 2931 | return call_user_func_array(array($this, 'xmlOuter'), $args); 2932 | else 2933 | return call_user_func_array(array($this, 'htmlOuter'), $args); 2934 | } 2935 | /** 2936 | * Enter description here... 2937 | * 2938 | * @param unknown_type $html 2939 | * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2940 | * @TODO force html result 2941 | */ 2942 | public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) { 2943 | if (isset($html)) { 2944 | // INSERT 2945 | $nodes = $this->documentWrapper->import($html); 2946 | $this->empty(); 2947 | foreach($this->stack(1) as $alreadyAdded => $node) { 2948 | // for now, limit events for textarea 2949 | if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') 2950 | $oldHtml = pq($node, $this->getDocumentID())->markup(); 2951 | foreach($nodes as $newNode) { 2952 | $node->appendChild($alreadyAdded 2953 | ? $newNode->cloneNode(true) 2954 | : $newNode 2955 | ); 2956 | } 2957 | // for now, limit events for textarea 2958 | if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') 2959 | $this->markupEvents($html, $oldHtml, $node); 2960 | } 2961 | return $this; 2962 | } else { 2963 | // FETCH 2964 | $return = $this->documentWrapper->markup($this->elements, true); 2965 | $args = func_get_args(); 2966 | foreach(array_slice($args, 1) as $callback) { 2967 | $return = phpQuery::callbackRun($callback, array($return)); 2968 | } 2969 | return $return; 2970 | } 2971 | } 2972 | /** 2973 | * @TODO force xml result 2974 | */ 2975 | public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) { 2976 | $args = func_get_args(); 2977 | return call_user_func_array(array($this, 'html'), $args); 2978 | } 2979 | /** 2980 | * Enter description here... 2981 | * @TODO force html result 2982 | * 2983 | * @return String 2984 | */ 2985 | public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { 2986 | $markup = $this->documentWrapper->markup($this->elements); 2987 | // pass thou callbacks 2988 | $args = func_get_args(); 2989 | foreach($args as $callback) { 2990 | $markup = phpQuery::callbackRun($callback, array($markup)); 2991 | } 2992 | return $markup; 2993 | } 2994 | /** 2995 | * @TODO force xml result 2996 | */ 2997 | public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { 2998 | $args = func_get_args(); 2999 | return call_user_func_array(array($this, 'htmlOuter'), $args); 3000 | } 3001 | public function __toString() { 3002 | return $this->markupOuter(); 3003 | } 3004 | /** 3005 | * Just like html(), but returns markup with VALID (dangerous) PHP tags. 3006 | * 3007 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3008 | * @todo support returning markup with PHP tags when called without param 3009 | */ 3010 | public function php($code = null) { 3011 | return $this->markupPHP($code); 3012 | } 3013 | /** 3014 | * Enter description here... 3015 | * 3016 | * @param $code 3017 | * @return unknown_type 3018 | */ 3019 | public function markupPHP($code = null) { 3020 | return isset($code) 3021 | ? $this->markup(phpQuery::php($code)) 3022 | : phpQuery::markupToPHP($this->markup()); 3023 | } 3024 | /** 3025 | * Enter description here... 3026 | * 3027 | * @param $code 3028 | * @return unknown_type 3029 | */ 3030 | public function markupOuterPHP() { 3031 | return phpQuery::markupToPHP($this->markupOuter()); 3032 | } 3033 | /** 3034 | * Enter description here... 3035 | * 3036 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3037 | */ 3038 | public function children($selector = null) { 3039 | $stack = array(); 3040 | foreach($this->stack(1) as $node) { 3041 | // foreach($node->getElementsByTagName('*') as $newNode) { 3042 | foreach($node->childNodes as $newNode) { 3043 | if ($newNode->nodeType != 1) 3044 | continue; 3045 | if ($selector && ! $this->is($selector, $newNode)) 3046 | continue; 3047 | if ($this->elementsContainsNode($newNode, $stack)) 3048 | continue; 3049 | $stack[] = $newNode; 3050 | } 3051 | } 3052 | $this->elementsBackup = $this->elements; 3053 | $this->elements = $stack; 3054 | return $this->newInstance(); 3055 | } 3056 | /** 3057 | * Enter description here... 3058 | * 3059 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3060 | */ 3061 | public function ancestors($selector = null) { 3062 | return $this->children( $selector ); 3063 | } 3064 | /** 3065 | * Enter description here... 3066 | * 3067 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3068 | */ 3069 | public function append( $content) { 3070 | return $this->insert($content, __FUNCTION__); 3071 | } 3072 | /** 3073 | * Enter description here... 3074 | * 3075 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3076 | */ 3077 | public function appendPHP( $content) { 3078 | return $this->insert("", 'append'); 3079 | } 3080 | /** 3081 | * Enter description here... 3082 | * 3083 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3084 | */ 3085 | public function appendTo( $seletor) { 3086 | return $this->insert($seletor, __FUNCTION__); 3087 | } 3088 | /** 3089 | * Enter description here... 3090 | * 3091 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3092 | */ 3093 | public function prepend( $content) { 3094 | return $this->insert($content, __FUNCTION__); 3095 | } 3096 | /** 3097 | * Enter description here... 3098 | * 3099 | * @todo accept many arguments, which are joined, arrays maybe also 3100 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3101 | */ 3102 | public function prependPHP( $content) { 3103 | return $this->insert("", 'prepend'); 3104 | } 3105 | /** 3106 | * Enter description here... 3107 | * 3108 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3109 | */ 3110 | public function prependTo( $seletor) { 3111 | return $this->insert($seletor, __FUNCTION__); 3112 | } 3113 | /** 3114 | * Enter description here... 3115 | * 3116 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3117 | */ 3118 | public function before($content) { 3119 | return $this->insert($content, __FUNCTION__); 3120 | } 3121 | /** 3122 | * Enter description here... 3123 | * 3124 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3125 | */ 3126 | public function beforePHP( $content) { 3127 | return $this->insert("", 'before'); 3128 | } 3129 | /** 3130 | * Enter description here... 3131 | * 3132 | * @param String|phpQuery 3133 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3134 | */ 3135 | public function insertBefore( $seletor) { 3136 | return $this->insert($seletor, __FUNCTION__); 3137 | } 3138 | /** 3139 | * Enter description here... 3140 | * 3141 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3142 | */ 3143 | public function after( $content) { 3144 | return $this->insert($content, __FUNCTION__); 3145 | } 3146 | /** 3147 | * Enter description here... 3148 | * 3149 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3150 | */ 3151 | public function afterPHP( $content) { 3152 | return $this->insert("", 'after'); 3153 | } 3154 | /** 3155 | * Enter description here... 3156 | * 3157 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3158 | */ 3159 | public function insertAfter( $seletor) { 3160 | return $this->insert($seletor, __FUNCTION__); 3161 | } 3162 | /** 3163 | * Internal insert method. Don't use it. 3164 | * 3165 | * @param unknown_type $target 3166 | * @param unknown_type $type 3167 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3168 | * @access private 3169 | */ 3170 | public function insert($target, $type) { 3171 | $this->debug("Inserting data with '{$type}'"); 3172 | $to = false; 3173 | switch( $type) { 3174 | case 'appendTo': 3175 | case 'prependTo': 3176 | case 'insertBefore': 3177 | case 'insertAfter': 3178 | $to = true; 3179 | } 3180 | switch(gettype($target)) { 3181 | case 'string': 3182 | $insertFrom = $insertTo = array(); 3183 | if ($to) { 3184 | // INSERT TO 3185 | $insertFrom = $this->elements; 3186 | if (phpQuery::isMarkup($target)) { 3187 | // $target is new markup, import it 3188 | $insertTo = $this->documentWrapper->import($target); 3189 | // insert into selected element 3190 | } else { 3191 | // $tagret is a selector 3192 | $thisStack = $this->elements; 3193 | $this->toRoot(); 3194 | $insertTo = $this->find($target)->elements; 3195 | $this->elements = $thisStack; 3196 | } 3197 | } else { 3198 | // INSERT FROM 3199 | $insertTo = $this->elements; 3200 | $insertFrom = $this->documentWrapper->import($target); 3201 | } 3202 | break; 3203 | case 'object': 3204 | $insertFrom = $insertTo = array(); 3205 | // phpQuery 3206 | if ($target instanceof self) { 3207 | if ($to) { 3208 | $insertTo = $target->elements; 3209 | if ($this->documentFragment && $this->stackIsRoot()) 3210 | // get all body children 3211 | // $loop = $this->find('body > *')->elements; 3212 | // TODO test it, test it hard... 3213 | // $loop = $this->newInstance($this->root)->find('> *')->elements; 3214 | $loop = $this->root->childNodes; 3215 | else 3216 | $loop = $this->elements; 3217 | // import nodes if needed 3218 | $insertFrom = $this->getDocumentID() == $target->getDocumentID() 3219 | ? $loop 3220 | : $target->documentWrapper->import($loop); 3221 | } else { 3222 | $insertTo = $this->elements; 3223 | if ( $target->documentFragment && $target->stackIsRoot() ) 3224 | // get all body children 3225 | // $loop = $target->find('body > *')->elements; 3226 | $loop = $target->root->childNodes; 3227 | else 3228 | $loop = $target->elements; 3229 | // import nodes if needed 3230 | $insertFrom = $this->getDocumentID() == $target->getDocumentID() 3231 | ? $loop 3232 | : $this->documentWrapper->import($loop); 3233 | } 3234 | // DOMNODE 3235 | } elseif ($target instanceof DOMNODE) { 3236 | // import node if needed 3237 | // if ( $target->ownerDocument != $this->DOM ) 3238 | // $target = $this->DOM->importNode($target, true); 3239 | if ( $to) { 3240 | $insertTo = array($target); 3241 | if ($this->documentFragment && $this->stackIsRoot()) 3242 | // get all body children 3243 | $loop = $this->root->childNodes; 3244 | // $loop = $this->find('body > *')->elements; 3245 | else 3246 | $loop = $this->elements; 3247 | foreach($loop as $fromNode) 3248 | // import nodes if needed 3249 | $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument) 3250 | ? $target->ownerDocument->importNode($fromNode, true) 3251 | : $fromNode; 3252 | } else { 3253 | // import node if needed 3254 | if (! $target->ownerDocument->isSameNode($this->document)) 3255 | $target = $this->document->importNode($target, true); 3256 | $insertTo = $this->elements; 3257 | $insertFrom[] = $target; 3258 | } 3259 | } 3260 | break; 3261 | } 3262 | phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes"); 3263 | foreach($insertTo as $insertNumber => $toNode) { 3264 | // we need static relative elements in some cases 3265 | switch( $type) { 3266 | case 'prependTo': 3267 | case 'prepend': 3268 | $firstChild = $toNode->firstChild; 3269 | break; 3270 | case 'insertAfter': 3271 | case 'after': 3272 | $nextSibling = $toNode->nextSibling; 3273 | break; 3274 | } 3275 | foreach($insertFrom as $fromNode) { 3276 | // clone if inserted already before 3277 | $insert = $insertNumber 3278 | ? $fromNode->cloneNode(true) 3279 | : $fromNode; 3280 | switch($type) { 3281 | case 'appendTo': 3282 | case 'append': 3283 | // $toNode->insertBefore( 3284 | // $fromNode, 3285 | // $toNode->lastChild->nextSibling 3286 | // ); 3287 | $toNode->appendChild($insert); 3288 | $eventTarget = $insert; 3289 | break; 3290 | case 'prependTo': 3291 | case 'prepend': 3292 | $toNode->insertBefore( 3293 | $insert, 3294 | $firstChild 3295 | ); 3296 | break; 3297 | case 'insertBefore': 3298 | case 'before': 3299 | if (! $toNode->parentNode) 3300 | throw new Exception("No parentNode, can't do {$type}()"); 3301 | else 3302 | $toNode->parentNode->insertBefore( 3303 | $insert, 3304 | $toNode 3305 | ); 3306 | break; 3307 | case 'insertAfter': 3308 | case 'after': 3309 | if (! $toNode->parentNode) 3310 | throw new Exception("No parentNode, can't do {$type}()"); 3311 | else 3312 | $toNode->parentNode->insertBefore( 3313 | $insert, 3314 | $nextSibling 3315 | ); 3316 | break; 3317 | } 3318 | // Mutation event 3319 | $event = new DOMEvent(array( 3320 | 'target' => $insert, 3321 | 'type' => 'DOMNodeInserted' 3322 | )); 3323 | phpQueryEvents::trigger($this->getDocumentID(), 3324 | $event->type, array($event), $insert 3325 | ); 3326 | } 3327 | } 3328 | return $this; 3329 | } 3330 | /** 3331 | * Enter description here... 3332 | * 3333 | * @return Int 3334 | */ 3335 | public function index($subject) { 3336 | $index = -1; 3337 | $subject = $subject instanceof phpQueryObject 3338 | ? $subject->elements[0] 3339 | : $subject; 3340 | foreach($this->newInstance() as $k => $node) { 3341 | if ($node->isSameNode($subject)) 3342 | $index = $k; 3343 | } 3344 | return $index; 3345 | } 3346 | /** 3347 | * Enter description here... 3348 | * 3349 | * @param unknown_type $start 3350 | * @param unknown_type $end 3351 | * 3352 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3353 | * @testme 3354 | */ 3355 | public function slice($start, $end = null) { 3356 | // $last = count($this->elements)-1; 3357 | // $end = $end 3358 | // ? min($end, $last) 3359 | // : $last; 3360 | // if ($start < 0) 3361 | // $start = $last+$start; 3362 | // if ($start > $last) 3363 | // return array(); 3364 | if ($end > 0) 3365 | $end = $end-$start; 3366 | return $this->newInstance( 3367 | array_slice($this->elements, $start, $end) 3368 | ); 3369 | } 3370 | /** 3371 | * Enter description here... 3372 | * 3373 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3374 | */ 3375 | public function reverse() { 3376 | $this->elementsBackup = $this->elements; 3377 | $this->elements = array_reverse($this->elements); 3378 | return $this->newInstance(); 3379 | } 3380 | /** 3381 | * Return joined text content. 3382 | * @return String 3383 | */ 3384 | public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) { 3385 | if (isset($text)) 3386 | return $this->html(htmlspecialchars($text)); 3387 | $args = func_get_args(); 3388 | $args = array_slice($args, 1); 3389 | $return = ''; 3390 | foreach($this->elements as $node) { 3391 | $text = $node->textContent; 3392 | if (count($this->elements) > 1 && $text) 3393 | $text .= "\n"; 3394 | foreach($args as $callback) { 3395 | $text = phpQuery::callbackRun($callback, array($text)); 3396 | } 3397 | $return .= $text; 3398 | } 3399 | return $return; 3400 | } 3401 | /** 3402 | * @return The text content of each matching element, like 3403 | * text() but returns an array with one entry per matched element. 3404 | * Read only. 3405 | */ 3406 | public function texts($attr = null) { 3407 | $results = array(); 3408 | foreach($this->elements as $node) { 3409 | $results[] = $node->textContent; 3410 | } 3411 | return $results; 3412 | } 3413 | /** 3414 | * Enter description here... 3415 | * 3416 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3417 | */ 3418 | public function plugin($class, $file = null) { 3419 | phpQuery::plugin($class, $file); 3420 | return $this; 3421 | } 3422 | /** 3423 | * Deprecated, use $pq->plugin() instead. 3424 | * 3425 | * @deprecated 3426 | * @param $class 3427 | * @param $file 3428 | * @return unknown_type 3429 | */ 3430 | public static function extend($class, $file = null) { 3431 | return $this->plugin($class, $file); 3432 | } 3433 | /** 3434 | * 3435 | * @access private 3436 | * @param $method 3437 | * @param $args 3438 | * @return unknown_type 3439 | */ 3440 | public function __call($method, $args) { 3441 | $aliasMethods = array('clone', 'empty'); 3442 | if (isset(phpQuery::$extendMethods[$method])) { 3443 | array_unshift($args, $this); 3444 | return phpQuery::callbackRun( 3445 | phpQuery::$extendMethods[$method], $args 3446 | ); 3447 | } else if (isset(phpQuery::$pluginsMethods[$method])) { 3448 | array_unshift($args, $this); 3449 | $class = phpQuery::$pluginsMethods[$method]; 3450 | $realClass = "phpQueryObjectPlugin_$class"; 3451 | $return = call_user_func_array( 3452 | array($realClass, $method), 3453 | $args 3454 | ); 3455 | // XXX deprecate ? 3456 | return is_null($return) 3457 | ? $this 3458 | : $return; 3459 | } else if (in_array($method, $aliasMethods)) { 3460 | return call_user_func_array(array($this, '_'.$method), $args); 3461 | } else 3462 | throw new Exception("Method '{$method}' doesnt exist"); 3463 | } 3464 | /** 3465 | * Safe rename of next(). 3466 | * 3467 | * Use it ONLY when need to call next() on an iterated object (in same time). 3468 | * Normaly there is no need to do such thing ;) 3469 | * 3470 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3471 | * @access private 3472 | */ 3473 | public function _next($selector = null) { 3474 | return $this->newInstance( 3475 | $this->getElementSiblings('nextSibling', $selector, true) 3476 | ); 3477 | } 3478 | /** 3479 | * Use prev() and next(). 3480 | * 3481 | * @deprecated 3482 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3483 | * @access private 3484 | */ 3485 | public function _prev($selector = null) { 3486 | return $this->prev($selector); 3487 | } 3488 | /** 3489 | * Enter description here... 3490 | * 3491 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3492 | */ 3493 | public function prev($selector = null) { 3494 | return $this->newInstance( 3495 | $this->getElementSiblings('previousSibling', $selector, true) 3496 | ); 3497 | } 3498 | /** 3499 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3500 | * @todo 3501 | */ 3502 | public function prevAll($selector = null) { 3503 | return $this->newInstance( 3504 | $this->getElementSiblings('previousSibling', $selector) 3505 | ); 3506 | } 3507 | /** 3508 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3509 | * @todo FIXME: returns source elements insted of next siblings 3510 | */ 3511 | public function nextAll($selector = null) { 3512 | return $this->newInstance( 3513 | $this->getElementSiblings('nextSibling', $selector) 3514 | ); 3515 | } 3516 | /** 3517 | * @access private 3518 | */ 3519 | protected function getElementSiblings($direction, $selector = null, $limitToOne = false) { 3520 | $stack = array(); 3521 | $count = 0; 3522 | foreach($this->stack() as $node) { 3523 | $test = $node; 3524 | while( isset($test->{$direction}) && $test->{$direction}) { 3525 | $test = $test->{$direction}; 3526 | if (! $test instanceof DOMELEMENT) 3527 | continue; 3528 | $stack[] = $test; 3529 | if ($limitToOne) 3530 | break; 3531 | } 3532 | } 3533 | if ($selector) { 3534 | $stackOld = $this->elements; 3535 | $this->elements = $stack; 3536 | $stack = $this->filter($selector, true)->stack(); 3537 | $this->elements = $stackOld; 3538 | } 3539 | return $stack; 3540 | } 3541 | /** 3542 | * Enter description here... 3543 | * 3544 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3545 | */ 3546 | public function siblings($selector = null) { 3547 | $stack = array(); 3548 | $siblings = array_merge( 3549 | $this->getElementSiblings('previousSibling', $selector), 3550 | $this->getElementSiblings('nextSibling', $selector) 3551 | ); 3552 | foreach($siblings as $node) { 3553 | if (! $this->elementsContainsNode($node, $stack)) 3554 | $stack[] = $node; 3555 | } 3556 | return $this->newInstance($stack); 3557 | } 3558 | /** 3559 | * Enter description here... 3560 | * 3561 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3562 | */ 3563 | public function not($selector = null) { 3564 | if (is_string($selector)) 3565 | phpQuery::debug(array('not', $selector)); 3566 | else 3567 | phpQuery::debug('not'); 3568 | $stack = array(); 3569 | if ($selector instanceof self || $selector instanceof DOMNODE) { 3570 | foreach($this->stack() as $node) { 3571 | if ($selector instanceof self) { 3572 | $matchFound = false; 3573 | foreach($selector->stack() as $notNode) { 3574 | if ($notNode->isSameNode($node)) 3575 | $matchFound = true; 3576 | } 3577 | if (! $matchFound) 3578 | $stack[] = $node; 3579 | } else if ($selector instanceof DOMNODE) { 3580 | if (! $selector->isSameNode($node)) 3581 | $stack[] = $node; 3582 | } else { 3583 | if (! $this->is($selector)) 3584 | $stack[] = $node; 3585 | } 3586 | } 3587 | } else { 3588 | $orgStack = $this->stack(); 3589 | $matched = $this->filter($selector, true)->stack(); 3590 | // $matched = array(); 3591 | // // simulate OR in filter() instead of AND 5y 3592 | // foreach($this->parseSelector($selector) as $s) { 3593 | // $matched = array_merge($matched, 3594 | // $this->filter(array($s))->stack() 3595 | // ); 3596 | // } 3597 | foreach($orgStack as $node) 3598 | if (! $this->elementsContainsNode($node, $matched)) 3599 | $stack[] = $node; 3600 | } 3601 | return $this->newInstance($stack); 3602 | } 3603 | /** 3604 | * Enter description here... 3605 | * 3606 | * @param string|phpQueryObject 3607 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3608 | */ 3609 | public function add($selector = null) { 3610 | if (! $selector) 3611 | return $this; 3612 | $stack = array(); 3613 | $this->elementsBackup = $this->elements; 3614 | $found = phpQuery::pq($selector, $this->getDocumentID()); 3615 | $this->merge($found->elements); 3616 | return $this->newInstance(); 3617 | } 3618 | /** 3619 | * @access private 3620 | */ 3621 | protected function merge() { 3622 | foreach(func_get_args() as $nodes) 3623 | foreach($nodes as $newNode ) 3624 | if (! $this->elementsContainsNode($newNode) ) 3625 | $this->elements[] = $newNode; 3626 | } 3627 | /** 3628 | * @access private 3629 | * TODO refactor to stackContainsNode 3630 | */ 3631 | protected function elementsContainsNode($nodeToCheck, $elementsStack = null) { 3632 | $loop = ! is_null($elementsStack) 3633 | ? $elementsStack 3634 | : $this->elements; 3635 | foreach($loop as $node) { 3636 | if ( $node->isSameNode( $nodeToCheck ) ) 3637 | return true; 3638 | } 3639 | return false; 3640 | } 3641 | /** 3642 | * Enter description here... 3643 | * 3644 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3645 | */ 3646 | public function parent($selector = null) { 3647 | $stack = array(); 3648 | foreach($this->elements as $node ) 3649 | if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) ) 3650 | $stack[] = $node->parentNode; 3651 | $this->elementsBackup = $this->elements; 3652 | $this->elements = $stack; 3653 | if ( $selector ) 3654 | $this->filter($selector, true); 3655 | return $this->newInstance(); 3656 | } 3657 | /** 3658 | * Enter description here... 3659 | * 3660 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3661 | */ 3662 | public function parents($selector = null) { 3663 | $stack = array(); 3664 | if (! $this->elements ) 3665 | $this->debug('parents() - stack empty'); 3666 | foreach($this->elements as $node) { 3667 | $test = $node; 3668 | while( $test->parentNode) { 3669 | $test = $test->parentNode; 3670 | if ($this->isRoot($test)) 3671 | break; 3672 | if (! $this->elementsContainsNode($test, $stack)) { 3673 | $stack[] = $test; 3674 | continue; 3675 | } 3676 | } 3677 | } 3678 | $this->elementsBackup = $this->elements; 3679 | $this->elements = $stack; 3680 | if ( $selector ) 3681 | $this->filter($selector, true); 3682 | return $this->newInstance(); 3683 | } 3684 | /** 3685 | * Internal stack iterator. 3686 | * 3687 | * @access private 3688 | */ 3689 | public function stack($nodeTypes = null) { 3690 | if (!isset($nodeTypes)) 3691 | return $this->elements; 3692 | if (!is_array($nodeTypes)) 3693 | $nodeTypes = array($nodeTypes); 3694 | $return = array(); 3695 | foreach($this->elements as $node) { 3696 | if (in_array($node->nodeType, $nodeTypes)) 3697 | $return[] = $node; 3698 | } 3699 | return $return; 3700 | } 3701 | // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes 3702 | protected function attrEvents($attr, $oldAttr, $oldValue, $node) { 3703 | // skip events for XML documents 3704 | if (! $this->isXHTML() && ! $this->isHTML()) 3705 | return; 3706 | $event = null; 3707 | // identify 3708 | $isInputValue = $node->tagName == 'input' 3709 | && ( 3710 | in_array($node->getAttribute('type'), 3711 | array('text', 'password', 'hidden')) 3712 | || !$node->getAttribute('type') 3713 | ); 3714 | $isRadio = $node->tagName == 'input' 3715 | && $node->getAttribute('type') == 'radio'; 3716 | $isCheckbox = $node->tagName == 'input' 3717 | && $node->getAttribute('type') == 'checkbox'; 3718 | $isOption = $node->tagName == 'option'; 3719 | if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) { 3720 | $event = new DOMEvent(array( 3721 | 'target' => $node, 3722 | 'type' => 'change' 3723 | )); 3724 | } else if (($isRadio || $isCheckbox) && $attr == 'checked' && ( 3725 | // check 3726 | (! $oldAttr && $node->hasAttribute($attr)) 3727 | // un-check 3728 | || (! $node->hasAttribute($attr) && $oldAttr) 3729 | )) { 3730 | $event = new DOMEvent(array( 3731 | 'target' => $node, 3732 | 'type' => 'change' 3733 | )); 3734 | } else if ($isOption && $node->parentNode && $attr == 'selected' && ( 3735 | // select 3736 | (! $oldAttr && $node->hasAttribute($attr)) 3737 | // un-select 3738 | || (! $node->hasAttribute($attr) && $oldAttr) 3739 | )) { 3740 | $event = new DOMEvent(array( 3741 | 'target' => $node->parentNode, 3742 | 'type' => 'change' 3743 | )); 3744 | } 3745 | if ($event) { 3746 | phpQueryEvents::trigger($this->getDocumentID(), 3747 | $event->type, array($event), $node 3748 | ); 3749 | } 3750 | } 3751 | public function attr($attr = null, $value = null) { 3752 | foreach($this->stack(1) as $node) { 3753 | if (! is_null($value)) { 3754 | $loop = $attr == '*' 3755 | ? $this->getNodeAttrs($node) 3756 | : array($attr); 3757 | foreach($loop as $a) { 3758 | $oldValue = $node->getAttribute($a); 3759 | $oldAttr = $node->hasAttribute($a); 3760 | // TODO raises an error when charset other than UTF-8 3761 | // while document's charset is also not UTF-8 3762 | @$node->setAttribute($a, $value); 3763 | $this->attrEvents($a, $oldAttr, $oldValue, $node); 3764 | } 3765 | } else if ($attr == '*') { 3766 | // jQuery difference 3767 | $return = array(); 3768 | foreach($node->attributes as $n => $v) 3769 | $return[$n] = $v->value; 3770 | return $return; 3771 | } else 3772 | return $node->hasAttribute($attr) 3773 | ? $node->getAttribute($attr) 3774 | : null; 3775 | } 3776 | return is_null($value) 3777 | ? '' : $this; 3778 | } 3779 | /** 3780 | * @return The same attribute of each matching element, like 3781 | * attr() but returns an array with one entry per matched element. 3782 | * Read only. 3783 | */ 3784 | public function attrs($attr = null) { 3785 | $results = array(); 3786 | foreach($this->stack(1) as $node) { 3787 | $results[] = $node->hasAttribute($attr) 3788 | ? $node->getAttribute($attr) 3789 | : null; 3790 | } 3791 | return $results; 3792 | } 3793 | /** 3794 | * @access private 3795 | */ 3796 | protected function getNodeAttrs($node) { 3797 | $return = array(); 3798 | foreach($node->attributes as $n => $o) 3799 | $return[] = $n; 3800 | return $return; 3801 | } 3802 | /** 3803 | * Enter description here... 3804 | * 3805 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3806 | * @todo check CDATA ??? 3807 | */ 3808 | public function attrPHP($attr, $code) { 3809 | if (! is_null($code)) { 3810 | $value = '<'.'?php '.$code.' ?'.'>'; 3811 | // TODO tempolary solution 3812 | // http://code.google.com/p/phpquery/issues/detail?id=17 3813 | // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII') 3814 | // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES'); 3815 | } 3816 | foreach($this->stack(1) as $node) { 3817 | if (! is_null($code)) { 3818 | // $attrNode = $this->DOM->createAttribute($attr); 3819 | $node->setAttribute($attr, $value); 3820 | // $attrNode->value = $value; 3821 | // $node->appendChild($attrNode); 3822 | } else if ( $attr == '*') { 3823 | // jQuery diff 3824 | $return = array(); 3825 | foreach($node->attributes as $n => $v) 3826 | $return[$n] = $v->value; 3827 | return $return; 3828 | } else 3829 | return $node->getAttribute($attr); 3830 | } 3831 | return $this; 3832 | } 3833 | /** 3834 | * Enter description here... 3835 | * 3836 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3837 | */ 3838 | public function removeAttr($attr) { 3839 | foreach($this->stack(1) as $node) { 3840 | $loop = $attr == '*' 3841 | ? $this->getNodeAttrs($node) 3842 | : array($attr); 3843 | foreach($loop as $a) { 3844 | $oldValue = $node->getAttribute($a); 3845 | $node->removeAttribute($a); 3846 | $this->attrEvents($a, $oldValue, null, $node); 3847 | } 3848 | } 3849 | return $this; 3850 | } 3851 | /** 3852 | * Return form element value. 3853 | * 3854 | * @return String Fields value. 3855 | */ 3856 | public function val($val = null) { 3857 | if (! isset($val)) { 3858 | if ($this->eq(0)->is('select')) { 3859 | $selected = $this->eq(0)->find('option[selected=selected]'); 3860 | if ($selected->is('[value]')) 3861 | return $selected->attr('value'); 3862 | else 3863 | return $selected->text(); 3864 | } else if ($this->eq(0)->is('textarea')) 3865 | return $this->eq(0)->markup(); 3866 | else 3867 | return $this->eq(0)->attr('value'); 3868 | } else { 3869 | $_val = null; 3870 | foreach($this->stack(1) as $node) { 3871 | $node = pq($node, $this->getDocumentID()); 3872 | if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) { 3873 | $isChecked = in_array($node->attr('value'), $val) 3874 | || in_array($node->attr('name'), $val); 3875 | if ($isChecked) 3876 | $node->attr('checked', 'checked'); 3877 | else 3878 | $node->removeAttr('checked'); 3879 | } else if ($node->get(0)->tagName == 'select') { 3880 | if (! isset($_val)) { 3881 | $_val = array(); 3882 | if (! is_array($val)) 3883 | $_val = array((string)$val); 3884 | else 3885 | foreach($val as $v) 3886 | $_val[] = $v; 3887 | } 3888 | foreach($node['option']->stack(1) as $option) { 3889 | $option = pq($option, $this->getDocumentID()); 3890 | $selected = false; 3891 | // XXX: workaround for string comparsion, see issue #96 3892 | // http://code.google.com/p/phpquery/issues/detail?id=96 3893 | $selected = is_null($option->attr('value')) 3894 | ? in_array($option->markup(), $_val) 3895 | : in_array($option->attr('value'), $_val); 3896 | // $optionValue = $option->attr('value'); 3897 | // $optionText = $option->text(); 3898 | // $optionTextLenght = mb_strlen($optionText); 3899 | // foreach($_val as $v) 3900 | // if ($optionValue == $v) 3901 | // $selected = true; 3902 | // else if ($optionText == $v && $optionTextLenght == mb_strlen($v)) 3903 | // $selected = true; 3904 | if ($selected) 3905 | $option->attr('selected', 'selected'); 3906 | else 3907 | $option->removeAttr('selected'); 3908 | } 3909 | } else if ($node->get(0)->tagName == 'textarea') 3910 | $node->markup($val); 3911 | else 3912 | $node->attr('value', $val); 3913 | } 3914 | } 3915 | return $this; 3916 | } 3917 | /** 3918 | * Enter description here... 3919 | * 3920 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3921 | */ 3922 | public function andSelf() { 3923 | if ( $this->previous ) 3924 | $this->elements = array_merge($this->elements, $this->previous->elements); 3925 | return $this; 3926 | } 3927 | /** 3928 | * Enter description here... 3929 | * 3930 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3931 | */ 3932 | public function addClass( $className) { 3933 | if (! $className) 3934 | return $this; 3935 | foreach($this->stack(1) as $node) { 3936 | if (! $this->is(".$className", $node)) 3937 | $node->setAttribute( 3938 | 'class', 3939 | trim($node->getAttribute('class').' '.$className) 3940 | ); 3941 | } 3942 | return $this; 3943 | } 3944 | /** 3945 | * Enter description here... 3946 | * 3947 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3948 | */ 3949 | public function addClassPHP( $className) { 3950 | foreach($this->stack(1) as $node) { 3951 | $classes = $node->getAttribute('class'); 3952 | $newValue = $classes 3953 | ? $classes.' <'.'?php '.$className.' ?'.'>' 3954 | : '<'.'?php '.$className.' ?'.'>'; 3955 | $node->setAttribute('class', $newValue); 3956 | } 3957 | return $this; 3958 | } 3959 | /** 3960 | * Enter description here... 3961 | * 3962 | * @param string $className 3963 | * @return bool 3964 | */ 3965 | public function hasClass($className) { 3966 | foreach($this->stack(1) as $node) { 3967 | if ( $this->is(".$className", $node)) 3968 | return true; 3969 | } 3970 | return false; 3971 | } 3972 | /** 3973 | * Enter description here... 3974 | * 3975 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3976 | */ 3977 | public function removeClass($className) { 3978 | foreach($this->stack(1) as $node) { 3979 | $classes = explode( ' ', $node->getAttribute('class')); 3980 | if ( in_array($className, $classes)) { 3981 | $classes = array_diff($classes, array($className)); 3982 | if ( $classes ) 3983 | $node->setAttribute('class', implode(' ', $classes)); 3984 | else 3985 | $node->removeAttribute('class'); 3986 | } 3987 | } 3988 | return $this; 3989 | } 3990 | /** 3991 | * Enter description here... 3992 | * 3993 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3994 | */ 3995 | public function toggleClass($className) { 3996 | foreach($this->stack(1) as $node) { 3997 | if ( $this->is( $node, '.'.$className )) 3998 | $this->removeClass($className); 3999 | else 4000 | $this->addClass($className); 4001 | } 4002 | return $this; 4003 | } 4004 | /** 4005 | * Proper name without underscore (just ->empty()) also works. 4006 | * 4007 | * Removes all child nodes from the set of matched elements. 4008 | * 4009 | * Example: 4010 | * pq("p")._empty() 4011 | * 4012 | * HTML: 4013 | *

Hello, Person and person

4014 | * 4015 | * Result: 4016 | * [

] 4017 | * 4018 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4019 | * @access private 4020 | */ 4021 | public function _empty() { 4022 | foreach($this->stack(1) as $node) { 4023 | // thx to 'dave at dgx dot cz' 4024 | $node->nodeValue = ''; 4025 | } 4026 | return $this; 4027 | } 4028 | /** 4029 | * Enter description here... 4030 | * 4031 | * @param array|string $callback Expects $node as first param, $index as second 4032 | * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope) 4033 | * @param array $arg1 Will ba passed as third and futher args to callback. 4034 | * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on... 4035 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4036 | */ 4037 | public function each($callback, $param1 = null, $param2 = null, $param3 = null) { 4038 | $paramStructure = null; 4039 | if (func_num_args() > 1) { 4040 | $paramStructure = func_get_args(); 4041 | $paramStructure = array_slice($paramStructure, 1); 4042 | } 4043 | foreach($this->elements as $v) 4044 | phpQuery::callbackRun($callback, array($v), $paramStructure); 4045 | return $this; 4046 | } 4047 | /** 4048 | * Run callback on actual object. 4049 | * 4050 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4051 | */ 4052 | public function callback($callback, $param1 = null, $param2 = null, $param3 = null) { 4053 | $params = func_get_args(); 4054 | $params[0] = $this; 4055 | phpQuery::callbackRun($callback, $params); 4056 | return $this; 4057 | } 4058 | /** 4059 | * Enter description here... 4060 | * 4061 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4062 | * @todo add $scope and $args as in each() ??? 4063 | */ 4064 | public function map($callback, $param1 = null, $param2 = null, $param3 = null) { 4065 | // $stack = array(); 4066 | //// foreach($this->newInstance() as $node) { 4067 | // foreach($this->newInstance() as $node) { 4068 | // $result = call_user_func($callback, $node); 4069 | // if ($result) 4070 | // $stack[] = $result; 4071 | // } 4072 | $params = func_get_args(); 4073 | array_unshift($params, $this->elements); 4074 | return $this->newInstance( 4075 | call_user_func_array(array('phpQuery', 'map'), $params) 4076 | // phpQuery::map($this->elements, $callback) 4077 | ); 4078 | } 4079 | /** 4080 | * Enter description here... 4081 | * 4082 | * @param $key 4083 | * @param $value 4084 | */ 4085 | public function data($key, $value = null) { 4086 | if (! isset($value)) { 4087 | // TODO? implement specific jQuery behavior od returning parent values 4088 | // is child which we look up doesn't exist 4089 | return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID()); 4090 | } else { 4091 | foreach($this as $node) 4092 | phpQuery::data($node, $key, $value, $this->getDocumentID()); 4093 | return $this; 4094 | } 4095 | } 4096 | /** 4097 | * Enter description here... 4098 | * 4099 | * @param $key 4100 | */ 4101 | public function removeData($key) { 4102 | foreach($this as $node) 4103 | phpQuery::removeData($node, $key, $this->getDocumentID()); 4104 | return $this; 4105 | } 4106 | // INTERFACE IMPLEMENTATIONS 4107 | 4108 | // ITERATOR INTERFACE 4109 | /** 4110 | * @access private 4111 | */ 4112 | public function rewind(){ 4113 | $this->debug('iterating foreach'); 4114 | // phpQuery::selectDocument($this->getDocumentID()); 4115 | $this->elementsBackup = $this->elements; 4116 | $this->elementsInterator = $this->elements; 4117 | $this->valid = isset( $this->elements[0] ) 4118 | ? 1 : 0; 4119 | // $this->elements = $this->valid 4120 | // ? array($this->elements[0]) 4121 | // : array(); 4122 | $this->current = 0; 4123 | } 4124 | /** 4125 | * @access private 4126 | */ 4127 | public function current(){ 4128 | return $this->elementsInterator[ $this->current ]; 4129 | } 4130 | /** 4131 | * @access private 4132 | */ 4133 | public function key(){ 4134 | return $this->current; 4135 | } 4136 | /** 4137 | * Double-function method. 4138 | * 4139 | * First: main iterator interface method. 4140 | * Second: Returning next sibling, alias for _next(). 4141 | * 4142 | * Proper functionality is choosed automagicaly. 4143 | * 4144 | * @see phpQueryObject::_next() 4145 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4146 | */ 4147 | public function next($cssSelector = null){ 4148 | // if ($cssSelector || $this->valid) 4149 | // return $this->_next($cssSelector); 4150 | $this->valid = isset( $this->elementsInterator[ $this->current+1 ] ) 4151 | ? true 4152 | : false; 4153 | if (! $this->valid && $this->elementsInterator) { 4154 | $this->elementsInterator = null; 4155 | } else if ($this->valid) { 4156 | $this->current++; 4157 | } else { 4158 | return $this->_next($cssSelector); 4159 | } 4160 | } 4161 | /** 4162 | * @access private 4163 | */ 4164 | public function valid(){ 4165 | return $this->valid; 4166 | } 4167 | // ITERATOR INTERFACE END 4168 | // ARRAYACCESS INTERFACE 4169 | /** 4170 | * @access private 4171 | */ 4172 | public function offsetExists($offset) { 4173 | return $this->find($offset)->size() > 0; 4174 | } 4175 | /** 4176 | * @access private 4177 | */ 4178 | public function offsetGet($offset) { 4179 | return $this->find($offset); 4180 | } 4181 | /** 4182 | * @access private 4183 | */ 4184 | public function offsetSet($offset, $value) { 4185 | // $this->find($offset)->replaceWith($value); 4186 | $this->find($offset)->html($value); 4187 | } 4188 | /** 4189 | * @access private 4190 | */ 4191 | public function offsetUnset($offset) { 4192 | // empty 4193 | throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML."); 4194 | } 4195 | // ARRAYACCESS INTERFACE END 4196 | /** 4197 | * Returns node's XPath. 4198 | * 4199 | * @param unknown_type $oneNode 4200 | * @return string 4201 | * @TODO use native getNodePath is avaible 4202 | * @access private 4203 | */ 4204 | protected function getNodeXpath($oneNode = null, $namespace = null) { 4205 | $return = array(); 4206 | $loop = $oneNode 4207 | ? array($oneNode) 4208 | : $this->elements; 4209 | // if ($namespace) 4210 | // $namespace .= ':'; 4211 | foreach($loop as $node) { 4212 | if ($node instanceof DOMDOCUMENT) { 4213 | $return[] = ''; 4214 | continue; 4215 | } 4216 | $xpath = array(); 4217 | while(! ($node instanceof DOMDOCUMENT)) { 4218 | $i = 1; 4219 | $sibling = $node; 4220 | while($sibling->previousSibling) { 4221 | $sibling = $sibling->previousSibling; 4222 | $isElement = $sibling instanceof DOMELEMENT; 4223 | if ($isElement && $sibling->tagName == $node->tagName) 4224 | $i++; 4225 | } 4226 | $xpath[] = $this->isXML() 4227 | ? "*[local-name()='{$node->tagName}'][{$i}]" 4228 | : "{$node->tagName}[{$i}]"; 4229 | $node = $node->parentNode; 4230 | } 4231 | $xpath = implode('/', array_reverse($xpath)); 4232 | $return[] = '/'.$xpath; 4233 | } 4234 | return $oneNode 4235 | ? $return[0] 4236 | : $return; 4237 | } 4238 | // HELPERS 4239 | public function whois($oneNode = null) { 4240 | $return = array(); 4241 | $loop = $oneNode 4242 | ? array( $oneNode ) 4243 | : $this->elements; 4244 | foreach($loop as $node) { 4245 | if (isset($node->tagName)) { 4246 | $tag = in_array($node->tagName, array('php', 'js')) 4247 | ? strtoupper($node->tagName) 4248 | : $node->tagName; 4249 | $return[] = $tag 4250 | .($node->getAttribute('id') 4251 | ? '#'.$node->getAttribute('id'):'') 4252 | .($node->getAttribute('class') 4253 | ? '.'.implode('.', explode(' ', $node->getAttribute('class'))):'') 4254 | .($node->getAttribute('name') 4255 | ? '[name="'.$node->getAttribute('name').'"]':'') 4256 | .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false 4257 | ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'') 4258 | .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false 4259 | ? '[value=PHP]':'') 4260 | .($node->getAttribute('selected') 4261 | ? '[selected]':'') 4262 | .($node->getAttribute('checked') 4263 | ? '[checked]':'') 4264 | ; 4265 | } else if ($node instanceof DOMTEXT) { 4266 | if (trim($node->textContent)) 4267 | $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15); 4268 | } else { 4269 | 4270 | } 4271 | } 4272 | return $oneNode && isset($return[0]) 4273 | ? $return[0] 4274 | : $return; 4275 | } 4276 | /** 4277 | * Dump htmlOuter and preserve chain. Usefull for debugging. 4278 | * 4279 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4280 | * 4281 | */ 4282 | public function dump() { 4283 | print 'DUMP #'.(phpQuery::$dumpCount++).' '; 4284 | $debug = phpQuery::$debug; 4285 | phpQuery::$debug = false; 4286 | // print __FILE__.':'.__LINE__."\n"; 4287 | var_dump($this->htmlOuter()); 4288 | return $this; 4289 | } 4290 | public function dumpWhois() { 4291 | print 'DUMP #'.(phpQuery::$dumpCount++).' '; 4292 | $debug = phpQuery::$debug; 4293 | phpQuery::$debug = false; 4294 | // print __FILE__.':'.__LINE__."\n"; 4295 | var_dump('whois', $this->whois()); 4296 | phpQuery::$debug = $debug; 4297 | return $this; 4298 | } 4299 | public function dumpLength() { 4300 | print 'DUMP #'.(phpQuery::$dumpCount++).' '; 4301 | $debug = phpQuery::$debug; 4302 | phpQuery::$debug = false; 4303 | // print __FILE__.':'.__LINE__."\n"; 4304 | var_dump('length', $this->length()); 4305 | phpQuery::$debug = $debug; 4306 | return $this; 4307 | } 4308 | public function dumpTree($html = true, $title = true) { 4309 | $output = $title 4310 | ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : ''; 4311 | $debug = phpQuery::$debug; 4312 | phpQuery::$debug = false; 4313 | foreach($this->stack() as $node) 4314 | $output .= $this->__dumpTree($node); 4315 | phpQuery::$debug = $debug; 4316 | print $html 4317 | ? nl2br(str_replace(' ', ' ', $output)) 4318 | : $output; 4319 | return $this; 4320 | } 4321 | private function __dumpTree($node, $intend = 0) { 4322 | $whois = $this->whois($node); 4323 | $return = ''; 4324 | if ($whois) 4325 | $return .= str_repeat(' - ', $intend).$whois."\n"; 4326 | if (isset($node->childNodes)) 4327 | foreach($node->childNodes as $chNode) 4328 | $return .= $this->__dumpTree($chNode, $intend+1); 4329 | return $return; 4330 | } 4331 | /** 4332 | * Dump htmlOuter and stop script execution. Usefull for debugging. 4333 | * 4334 | */ 4335 | public function dumpDie() { 4336 | print __FILE__.':'.__LINE__; 4337 | var_dump($this->htmlOuter()); 4338 | die(); 4339 | } 4340 | } 4341 | 4342 | 4343 | // -- Multibyte Compatibility functions --------------------------------------- 4344 | // http://svn.iphonewebdev.com/lace/lib/mb_compat.php 4345 | 4346 | /** 4347 | * mb_internal_encoding() 4348 | * 4349 | * Included for mbstring pseudo-compatability. 4350 | */ 4351 | if (!function_exists('mb_internal_encoding')) 4352 | { 4353 | function mb_internal_encoding($enc) {return true; } 4354 | } 4355 | 4356 | /** 4357 | * mb_regex_encoding() 4358 | * 4359 | * Included for mbstring pseudo-compatability. 4360 | */ 4361 | if (!function_exists('mb_regex_encoding')) 4362 | { 4363 | function mb_regex_encoding($enc) {return true; } 4364 | } 4365 | 4366 | /** 4367 | * mb_strlen() 4368 | * 4369 | * Included for mbstring pseudo-compatability. 4370 | */ 4371 | if (!function_exists('mb_strlen')) 4372 | { 4373 | function mb_strlen($str) 4374 | { 4375 | return strlen($str); 4376 | } 4377 | } 4378 | 4379 | /** 4380 | * mb_strpos() 4381 | * 4382 | * Included for mbstring pseudo-compatability. 4383 | */ 4384 | if (!function_exists('mb_strpos')) 4385 | { 4386 | function mb_strpos($haystack, $needle, $offset=0) 4387 | { 4388 | return strpos($haystack, $needle, $offset); 4389 | } 4390 | } 4391 | /** 4392 | * mb_stripos() 4393 | * 4394 | * Included for mbstring pseudo-compatability. 4395 | */ 4396 | if (!function_exists('mb_stripos')) 4397 | { 4398 | function mb_stripos($haystack, $needle, $offset=0) 4399 | { 4400 | return stripos($haystack, $needle, $offset); 4401 | } 4402 | } 4403 | 4404 | /** 4405 | * mb_substr() 4406 | * 4407 | * Included for mbstring pseudo-compatability. 4408 | */ 4409 | if (!function_exists('mb_substr')) 4410 | { 4411 | function mb_substr($str, $start, $length=0) 4412 | { 4413 | return substr($str, $start, $length); 4414 | } 4415 | } 4416 | 4417 | /** 4418 | * mb_substr_count() 4419 | * 4420 | * Included for mbstring pseudo-compatability. 4421 | */ 4422 | if (!function_exists('mb_substr_count')) 4423 | { 4424 | function mb_substr_count($haystack, $needle) 4425 | { 4426 | return substr_count($haystack, $needle); 4427 | } 4428 | } 4429 | 4430 | 4431 | /** 4432 | * Static namespace for phpQuery functions. 4433 | * 4434 | * @author Tobiasz Cudnik 4435 | * @package phpQuery 4436 | */ 4437 | abstract class phpQuery { 4438 | /** 4439 | * XXX: Workaround for mbstring problems 4440 | * 4441 | * @var bool 4442 | */ 4443 | public static $mbstringSupport = true; 4444 | public static $debug = false; 4445 | public static $documents = array(); 4446 | public static $defaultDocumentID = null; 4447 | // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'; 4448 | /** 4449 | * Applies only to HTML. 4450 | * 4451 | * @var unknown_type 4452 | */ 4453 | public static $defaultDoctype = ''; 4455 | public static $defaultCharset = 'UTF-8'; 4456 | /** 4457 | * Static namespace for plugins. 4458 | * 4459 | * @var object 4460 | */ 4461 | public static $plugins = array(); 4462 | /** 4463 | * List of loaded plugins. 4464 | * 4465 | * @var unknown_type 4466 | */ 4467 | public static $pluginsLoaded = array(); 4468 | public static $pluginsMethods = array(); 4469 | public static $pluginsStaticMethods = array(); 4470 | public static $extendMethods = array(); 4471 | /** 4472 | * @TODO implement 4473 | */ 4474 | public static $extendStaticMethods = array(); 4475 | /** 4476 | * Hosts allowed for AJAX connections. 4477 | * Dot '.' means $_SERVER['HTTP_HOST'] (if any). 4478 | * 4479 | * @var array 4480 | */ 4481 | public static $ajaxAllowedHosts = array( 4482 | '.' 4483 | ); 4484 | /** 4485 | * AJAX settings. 4486 | * 4487 | * @var array 4488 | * XXX should it be static or not ? 4489 | */ 4490 | public static $ajaxSettings = array( 4491 | 'url' => '',//TODO 4492 | 'global' => true, 4493 | 'type' => "GET", 4494 | 'timeout' => null, 4495 | 'contentType' => "application/x-www-form-urlencoded", 4496 | 'processData' => true, 4497 | // 'async' => true, 4498 | 'data' => null, 4499 | 'username' => null, 4500 | 'password' => null, 4501 | 'accepts' => array( 4502 | 'xml' => "application/xml, text/xml", 4503 | 'html' => "text/html", 4504 | 'script' => "text/javascript, application/javascript", 4505 | 'json' => "application/json, text/javascript", 4506 | 'text' => "text/plain", 4507 | '_default' => "*/*" 4508 | ) 4509 | ); 4510 | public static $lastModified = null; 4511 | public static $active = 0; 4512 | public static $dumpCount = 0; 4513 | /** 4514 | * Multi-purpose function. 4515 | * Use pq() as shortcut. 4516 | * 4517 | * In below examples, $pq is any result of pq(); function. 4518 | * 4519 | * 1. Import markup into existing document (without any attaching): 4520 | * - Import into selected document: 4521 | * pq('
') // DOESNT accept text nodes at beginning of input string ! 4522 | * - Import into document with ID from $pq->getDocumentID(): 4523 | * pq('
', $pq->getDocumentID()) 4524 | * - Import into same document as DOMNode belongs to: 4525 | * pq('
', DOMNode) 4526 | * - Import into document from phpQuery object: 4527 | * pq('
', $pq) 4528 | * 4529 | * 2. Run query: 4530 | * - Run query on last selected document: 4531 | * pq('div.myClass') 4532 | * - Run query on document with ID from $pq->getDocumentID(): 4533 | * pq('div.myClass', $pq->getDocumentID()) 4534 | * - Run query on same document as DOMNode belongs to and use node(s)as root for query: 4535 | * pq('div.myClass', DOMNode) 4536 | * - Run query on document from phpQuery object 4537 | * and use object's stack as root node(s) for query: 4538 | * pq('div.myClass', $pq) 4539 | * 4540 | * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes 4541 | * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root) 4542 | * 4543 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false 4544 | * phpQuery object or false in case of error. 4545 | */ 4546 | public static function pq($arg1, $context = null) { 4547 | if ($arg1 instanceof DOMNODE && ! isset($context)) { 4548 | foreach(phpQuery::$documents as $documentWrapper) { 4549 | $compare = $arg1 instanceof DOMDocument 4550 | ? $arg1 : $arg1->ownerDocument; 4551 | if ($documentWrapper->document->isSameNode($compare)) 4552 | $context = $documentWrapper->id; 4553 | } 4554 | } 4555 | if (! $context) { 4556 | $domId = self::$defaultDocumentID; 4557 | if (! $domId) 4558 | throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first."); 4559 | // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) 4560 | } else if (is_object($context) && $context instanceof phpQueryObject) 4561 | $domId = $context->getDocumentID(); 4562 | else if ($context instanceof DOMDOCUMENT) { 4563 | $domId = self::getDocumentID($context); 4564 | if (! $domId) { 4565 | //throw new Exception('Orphaned DOMDocument'); 4566 | $domId = self::newDocument($context)->getDocumentID(); 4567 | } 4568 | } else if ($context instanceof DOMNODE) { 4569 | $domId = self::getDocumentID($context); 4570 | if (! $domId) { 4571 | throw new Exception('Orphaned DOMNode'); 4572 | // $domId = self::newDocument($context->ownerDocument); 4573 | } 4574 | } else 4575 | $domId = $context; 4576 | if ($arg1 instanceof phpQueryObject) { 4577 | // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) { 4578 | /** 4579 | * Return $arg1 or import $arg1 stack if document differs: 4580 | * pq(pq('
')) 4581 | */ 4582 | if ($arg1->getDocumentID() == $domId) 4583 | return $arg1; 4584 | $class = get_class($arg1); 4585 | // support inheritance by passing old object to overloaded constructor 4586 | $phpQuery = $class != 'phpQuery' 4587 | ? new $class($arg1, $domId) 4588 | : new phpQueryObject($domId); 4589 | $phpQuery->elements = array(); 4590 | foreach($arg1->elements as $node) 4591 | $phpQuery->elements[] = $phpQuery->document->importNode($node, true); 4592 | return $phpQuery; 4593 | } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) { 4594 | /* 4595 | * Wrap DOM nodes with phpQuery object, import into document when needed: 4596 | * pq(array($domNode1, $domNode2)) 4597 | */ 4598 | $phpQuery = new phpQueryObject($domId); 4599 | if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1)) 4600 | $arg1 = array($arg1); 4601 | $phpQuery->elements = array(); 4602 | foreach($arg1 as $node) { 4603 | $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT 4604 | && ! $node->ownerDocument->isSameNode($phpQuery->document); 4605 | $phpQuery->elements[] = $sameDocument 4606 | ? $phpQuery->document->importNode($node, true) 4607 | : $node; 4608 | } 4609 | return $phpQuery; 4610 | } else if (self::isMarkup($arg1)) { 4611 | /** 4612 | * Import HTML: 4613 | * pq('
') 4614 | */ 4615 | $phpQuery = new phpQueryObject($domId); 4616 | return $phpQuery->newInstance( 4617 | $phpQuery->documentWrapper->import($arg1) 4618 | ); 4619 | } else { 4620 | /** 4621 | * Run CSS query: 4622 | * pq('div.myClass') 4623 | */ 4624 | $phpQuery = new phpQueryObject($domId); 4625 | // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) 4626 | if ($context && $context instanceof phpQueryObject) 4627 | $phpQuery->elements = $context->elements; 4628 | else if ($context && $context instanceof DOMNODELIST) { 4629 | $phpQuery->elements = array(); 4630 | foreach($context as $node) 4631 | $phpQuery->elements[] = $node; 4632 | } else if ($context && $context instanceof DOMNODE) 4633 | $phpQuery->elements = array($context); 4634 | return $phpQuery->find($arg1); 4635 | } 4636 | } 4637 | /** 4638 | * Sets default document to $id. Document has to be loaded prior 4639 | * to using this method. 4640 | * $id can be retrived via getDocumentID() or getDocumentIDRef(). 4641 | * 4642 | * @param unknown_type $id 4643 | */ 4644 | public static function selectDocument($id) { 4645 | $id = self::getDocumentID($id); 4646 | self::debug("Selecting document '$id' as default one"); 4647 | self::$defaultDocumentID = self::getDocumentID($id); 4648 | } 4649 | /** 4650 | * Returns document with id $id or last used as phpQueryObject. 4651 | * $id can be retrived via getDocumentID() or getDocumentIDRef(). 4652 | * Chainable. 4653 | * 4654 | * @see phpQuery::selectDocument() 4655 | * @param unknown_type $id 4656 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4657 | */ 4658 | public static function getDocument($id = null) { 4659 | if ($id) 4660 | phpQuery::selectDocument($id); 4661 | else 4662 | $id = phpQuery::$defaultDocumentID; 4663 | return new phpQueryObject($id); 4664 | } 4665 | /** 4666 | * Creates new document from markup. 4667 | * Chainable. 4668 | * 4669 | * @param unknown_type $markup 4670 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4671 | */ 4672 | public static function newDocument($markup = null, $contentType = null) { 4673 | if (! $markup) 4674 | $markup = ''; 4675 | $documentID = phpQuery::createDocumentWrapper($markup, $contentType); 4676 | return new phpQueryObject($documentID); 4677 | } 4678 | /** 4679 | * Creates new document from markup. 4680 | * Chainable. 4681 | * 4682 | * @param unknown_type $markup 4683 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4684 | */ 4685 | public static function newDocumentHTML($markup = null, $charset = null) { 4686 | $contentType = $charset 4687 | ? ";charset=$charset" 4688 | : ''; 4689 | return self::newDocument($markup, "text/html{$contentType}"); 4690 | } 4691 | /** 4692 | * Creates new document from markup. 4693 | * Chainable. 4694 | * 4695 | * @param unknown_type $markup 4696 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4697 | */ 4698 | public static function newDocumentXML($markup = null, $charset = null) { 4699 | $contentType = $charset 4700 | ? ";charset=$charset" 4701 | : ''; 4702 | return self::newDocument($markup, "text/xml{$contentType}"); 4703 | } 4704 | /** 4705 | * Creates new document from markup. 4706 | * Chainable. 4707 | * 4708 | * @param unknown_type $markup 4709 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4710 | */ 4711 | public static function newDocumentXHTML($markup = null, $charset = null) { 4712 | $contentType = $charset 4713 | ? ";charset=$charset" 4714 | : ''; 4715 | return self::newDocument($markup, "application/xhtml+xml{$contentType}"); 4716 | } 4717 | /** 4718 | * Creates new document from markup. 4719 | * Chainable. 4720 | * 4721 | * @param unknown_type $markup 4722 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4723 | */ 4724 | public static function newDocumentPHP($markup = null, $contentType = "text/html") { 4725 | // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function) 4726 | $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset); 4727 | return self::newDocument($markup, $contentType); 4728 | } 4729 | public static function phpToMarkup($php, $charset = 'utf-8') { 4730 | $regexes = array( 4731 | '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s', 4732 | '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s', 4733 | ); 4734 | foreach($regexes as $regex) 4735 | while (preg_match($regex, $php, $matches)) { 4736 | $php = preg_replace_callback( 4737 | $regex, 4738 | // create_function('$m, $charset = "'.$charset.'"', 4739 | // 'return $m[1].$m[2] 4740 | // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) 4741 | // .$m[5].$m[2];' 4742 | // ), 4743 | array('phpQuery', '_phpToMarkupCallback'), 4744 | $php 4745 | ); 4746 | } 4747 | $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s'; 4748 | //preg_match_all($regex, $php, $matches); 4749 | //var_dump($matches); 4750 | $php = preg_replace($regex, '\\1', $php); 4751 | return $php; 4752 | } 4753 | public static function _phpToMarkupCallback($php, $charset = 'utf-8') { 4754 | return $m[1].$m[2] 4755 | .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) 4756 | .$m[5].$m[2]; 4757 | } 4758 | public static function _markupToPHPCallback($m) { 4759 | return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">"; 4760 | } 4761 | /** 4762 | * Converts document markup containing PHP code generated by phpQuery::php() 4763 | * into valid (executable) PHP code syntax. 4764 | * 4765 | * @param string|phpQueryObject $content 4766 | * @return string PHP code. 4767 | */ 4768 | public static function markupToPHP($content) { 4769 | if ($content instanceof phpQueryObject) 4770 | $content = $content->markupOuter(); 4771 | /* ... to */ 4772 | $content = preg_replace_callback( 4773 | '@\s*\s*@s', 4774 | // create_function('$m', 4775 | // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";' 4776 | // ), 4777 | array('phpQuery', '_markupToPHPCallback'), 4778 | $content 4779 | ); 4780 | /* extra space added to save highlighters */ 4781 | $regexes = array( 4782 | '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^\']*)\'@s', 4783 | '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^"]*)"@s', 4784 | ); 4785 | foreach($regexes as $regex) 4786 | while (preg_match($regex, $content)) 4787 | $content = preg_replace_callback( 4788 | $regex, 4789 | function ($m) { 4790 | return $m[1].$m[2].$m[3]."", " ", "\n", " ", "{", "$", "}", '"', "[", "]"), 4794 | htmlspecialchars_decode($m[4]) 4795 | ) 4796 | ." ?>".$m[5].$m[2]; 4797 | }, 4798 | $content 4799 | ); 4800 | return $content; 4801 | } 4802 | /** 4803 | * Creates new document from file $file. 4804 | * Chainable. 4805 | * 4806 | * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources. 4807 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4808 | */ 4809 | public static function newDocumentFile($file, $contentType = null) { 4810 | $documentID = self::createDocumentWrapper( 4811 | file_get_contents($file), $contentType 4812 | ); 4813 | return new phpQueryObject($documentID); 4814 | } 4815 | /** 4816 | * Creates new document from markup. 4817 | * Chainable. 4818 | * 4819 | * @param unknown_type $markup 4820 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4821 | */ 4822 | public static function newDocumentFileHTML($file, $charset = null) { 4823 | $contentType = $charset 4824 | ? ";charset=$charset" 4825 | : ''; 4826 | return self::newDocumentFile($file, "text/html{$contentType}"); 4827 | } 4828 | /** 4829 | * Creates new document from markup. 4830 | * Chainable. 4831 | * 4832 | * @param unknown_type $markup 4833 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4834 | */ 4835 | public static function newDocumentFileXML($file, $charset = null) { 4836 | $contentType = $charset 4837 | ? ";charset=$charset" 4838 | : ''; 4839 | return self::newDocumentFile($file, "text/xml{$contentType}"); 4840 | } 4841 | /** 4842 | * Creates new document from markup. 4843 | * Chainable. 4844 | * 4845 | * @param unknown_type $markup 4846 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4847 | */ 4848 | public static function newDocumentFileXHTML($file, $charset = null) { 4849 | $contentType = $charset 4850 | ? ";charset=$charset" 4851 | : ''; 4852 | return self::newDocumentFile($file, "application/xhtml+xml{$contentType}"); 4853 | } 4854 | /** 4855 | * Creates new document from markup. 4856 | * Chainable. 4857 | * 4858 | * @param unknown_type $markup 4859 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4860 | */ 4861 | public static function newDocumentFilePHP($file, $contentType = null) { 4862 | return self::newDocumentPHP(file_get_contents($file), $contentType); 4863 | } 4864 | /** 4865 | * Reuses existing DOMDocument object. 4866 | * Chainable. 4867 | * 4868 | * @param $document DOMDocument 4869 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4870 | * @TODO support DOMDocument 4871 | */ 4872 | public static function loadDocument($document) { 4873 | // TODO 4874 | die('TODO loadDocument'); 4875 | } 4876 | /** 4877 | * Enter description here... 4878 | * 4879 | * @param unknown_type $html 4880 | * @param unknown_type $domId 4881 | * @return unknown New DOM ID 4882 | * @todo support PHP tags in input 4883 | * @todo support passing DOMDocument object from self::loadDocument 4884 | */ 4885 | protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) { 4886 | if (function_exists('domxml_open_mem')) 4887 | throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled."); 4888 | // $id = $documentID 4889 | // ? $documentID 4890 | // : md5(microtime()); 4891 | $document = null; 4892 | if ($html instanceof DOMDOCUMENT) { 4893 | if (self::getDocumentID($html)) { 4894 | // document already exists in phpQuery::$documents, make a copy 4895 | $document = clone $html; 4896 | } else { 4897 | // new document, add it to phpQuery::$documents 4898 | $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); 4899 | } 4900 | } else { 4901 | $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); 4902 | } 4903 | // $wrapper->id = $id; 4904 | // bind document 4905 | phpQuery::$documents[$wrapper->id] = $wrapper; 4906 | // remember last loaded document 4907 | phpQuery::selectDocument($wrapper->id); 4908 | return $wrapper->id; 4909 | } 4910 | /** 4911 | * Extend class namespace. 4912 | * 4913 | * @param string|array $target 4914 | * @param array $source 4915 | * @TODO support string $source 4916 | * @return unknown_type 4917 | */ 4918 | public static function extend($target, $source) { 4919 | switch($target) { 4920 | case 'phpQueryObject': 4921 | $targetRef = &self::$extendMethods; 4922 | $targetRef2 = &self::$pluginsMethods; 4923 | break; 4924 | case 'phpQuery': 4925 | $targetRef = &self::$extendStaticMethods; 4926 | $targetRef2 = &self::$pluginsStaticMethods; 4927 | break; 4928 | default: 4929 | throw new Exception("Unsupported \$target type"); 4930 | } 4931 | if (is_string($source)) 4932 | $source = array($source => $source); 4933 | foreach($source as $method => $callback) { 4934 | if (isset($targetRef[$method])) { 4935 | // throw new Exception 4936 | self::debug("Duplicate method '{$method}', can\'t extend '{$target}'"); 4937 | continue; 4938 | } 4939 | if (isset($targetRef2[$method])) { 4940 | // throw new Exception 4941 | self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}'," 4942 | ." can\'t extend '{$target}'"); 4943 | continue; 4944 | } 4945 | $targetRef[$method] = $callback; 4946 | } 4947 | return true; 4948 | } 4949 | /** 4950 | * Extend phpQuery with $class from $file. 4951 | * 4952 | * @param string $class Extending class name. Real class name can be prepended phpQuery_. 4953 | * @param string $file Filename to include. Defaults to "{$class}.php". 4954 | */ 4955 | public static function plugin($class, $file = null) { 4956 | // TODO $class checked agains phpQuery_$class 4957 | // if (strpos($class, 'phpQuery') === 0) 4958 | // $class = substr($class, 8); 4959 | if (in_array($class, self::$pluginsLoaded)) 4960 | return true; 4961 | if (! $file) 4962 | $file = $class.'.php'; 4963 | $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class); 4964 | $staticClassExists = class_exists('phpQueryPlugin_'.$class); 4965 | if (! $objectClassExists && ! $staticClassExists) 4966 | require_once($file); 4967 | self::$pluginsLoaded[] = $class; 4968 | // static methods 4969 | if (class_exists('phpQueryPlugin_'.$class)) { 4970 | $realClass = 'phpQueryPlugin_'.$class; 4971 | $vars = get_class_vars($realClass); 4972 | $loop = isset($vars['phpQueryMethods']) 4973 | && ! is_null($vars['phpQueryMethods']) 4974 | ? $vars['phpQueryMethods'] 4975 | : get_class_methods($realClass); 4976 | foreach($loop as $method) { 4977 | if ($method == '__initialize') 4978 | continue; 4979 | if (! is_callable(array($realClass, $method))) 4980 | continue; 4981 | if (isset(self::$pluginsStaticMethods[$method])) { 4982 | throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'"); 4983 | return; 4984 | } 4985 | self::$pluginsStaticMethods[$method] = $class; 4986 | } 4987 | if (method_exists($realClass, '__initialize')) 4988 | call_user_func_array(array($realClass, '__initialize'), array()); 4989 | } 4990 | // object methods 4991 | if (class_exists('phpQueryObjectPlugin_'.$class)) { 4992 | $realClass = 'phpQueryObjectPlugin_'.$class; 4993 | $vars = get_class_vars($realClass); 4994 | $loop = isset($vars['phpQueryMethods']) 4995 | && ! is_null($vars['phpQueryMethods']) 4996 | ? $vars['phpQueryMethods'] 4997 | : get_class_methods($realClass); 4998 | foreach($loop as $method) { 4999 | if (! is_callable(array($realClass, $method))) 5000 | continue; 5001 | if (isset(self::$pluginsMethods[$method])) { 5002 | throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'"); 5003 | continue; 5004 | } 5005 | self::$pluginsMethods[$method] = $class; 5006 | } 5007 | } 5008 | return true; 5009 | } 5010 | /** 5011 | * Unloades all or specified document from memory. 5012 | * 5013 | * @param mixed $documentID @see phpQuery::getDocumentID() for supported types. 5014 | */ 5015 | public static function unloadDocuments($id = null) { 5016 | if (isset($id)) { 5017 | if ($id = self::getDocumentID($id)) 5018 | unset(phpQuery::$documents[$id]); 5019 | } else { 5020 | foreach(phpQuery::$documents as $k => $v) { 5021 | unset(phpQuery::$documents[$k]); 5022 | } 5023 | } 5024 | } 5025 | /** 5026 | * Parses phpQuery object or HTML result against PHP tags and makes them active. 5027 | * 5028 | * @param phpQuery|string $content 5029 | * @deprecated 5030 | * @return string 5031 | */ 5032 | public static function unsafePHPTags($content) { 5033 | return self::markupToPHP($content); 5034 | } 5035 | public static function DOMNodeListToArray($DOMNodeList) { 5036 | $array = array(); 5037 | if (! $DOMNodeList) 5038 | return $array; 5039 | foreach($DOMNodeList as $node) 5040 | $array[] = $node; 5041 | return $array; 5042 | } 5043 | /** 5044 | * Checks if $input is HTML string, which has to start with '<'. 5045 | * 5046 | * @deprecated 5047 | * @param String $input 5048 | * @return Bool 5049 | * @todo still used ? 5050 | */ 5051 | public static function isMarkup($input) { 5052 | return ! is_array($input) && substr(trim($input), 0, 1) == '<'; 5053 | } 5054 | public static function debug($text) { 5055 | if (self::$debug) 5056 | print var_dump($text); 5057 | } 5058 | /** 5059 | * Make an AJAX request. 5060 | * 5061 | * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions 5062 | * Additional options are: 5063 | * 'document' - document for global events, @see phpQuery::getDocumentID() 5064 | * 'referer' - implemented 5065 | * 'requested_with' - TODO; not implemented (X-Requested-With) 5066 | * @return Zend_Http_Client 5067 | * @link http://docs.jquery.com/Ajax/jQuery.ajax 5068 | * 5069 | * @TODO $options['cache'] 5070 | * @TODO $options['processData'] 5071 | * @TODO $options['xhr'] 5072 | * @TODO $options['data'] as string 5073 | * @TODO XHR interface 5074 | */ 5075 | public static function ajax($options = array(), $xhr = null) { 5076 | $options = array_merge( 5077 | self::$ajaxSettings, $options 5078 | ); 5079 | $documentID = isset($options['document']) 5080 | ? self::getDocumentID($options['document']) 5081 | : null; 5082 | if ($xhr) { 5083 | // reuse existing XHR object, but clean it up 5084 | $client = $xhr; 5085 | // $client->setParameterPost(null); 5086 | // $client->setParameterGet(null); 5087 | $client->setAuth(false); 5088 | $client->setHeaders("If-Modified-Since", null); 5089 | $client->setHeaders("Referer", null); 5090 | $client->resetParameters(); 5091 | } else { 5092 | // create new XHR object 5093 | require_once('Zend/Http/Client.php'); 5094 | $client = new Zend_Http_Client(); 5095 | $client->setCookieJar(); 5096 | } 5097 | if (isset($options['timeout'])) 5098 | $client->setConfig(array( 5099 | 'timeout' => $options['timeout'], 5100 | )); 5101 | // 'maxredirects' => 0, 5102 | foreach(self::$ajaxAllowedHosts as $k => $host) 5103 | if ($host == '.' && isset($_SERVER['HTTP_HOST'])) 5104 | self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST']; 5105 | $host = parse_url($options['url'], PHP_URL_HOST); 5106 | if (! in_array($host, self::$ajaxAllowedHosts)) { 5107 | throw new Exception("Request not permitted, host '$host' not present in " 5108 | ."phpQuery::\$ajaxAllowedHosts"); 5109 | } 5110 | // JSONP 5111 | $jsre = "/=\\?(&|$)/"; 5112 | if (isset($options['dataType']) && $options['dataType'] == 'jsonp') { 5113 | $jsonpCallbackParam = $options['jsonp'] 5114 | ? $options['jsonp'] : 'callback'; 5115 | if (strtolower($options['type']) == 'get') { 5116 | if (! preg_match($jsre, $options['url'])) { 5117 | $sep = strpos($options['url'], '?') 5118 | ? '&' : '?'; 5119 | $options['url'] .= "$sep$jsonpCallbackParam=?"; 5120 | } 5121 | } else if ($options['data']) { 5122 | $jsonp = false; 5123 | foreach($options['data'] as $n => $v) { 5124 | if ($v == '?') 5125 | $jsonp = true; 5126 | } 5127 | if (! $jsonp) { 5128 | $options['data'][$jsonpCallbackParam] = '?'; 5129 | } 5130 | } 5131 | $options['dataType'] = 'json'; 5132 | } 5133 | if (isset($options['dataType']) && $options['dataType'] == 'json') { 5134 | $jsonpCallback = 'json_'.md5(microtime()); 5135 | $jsonpData = $jsonpUrl = false; 5136 | if ($options['data']) { 5137 | foreach($options['data'] as $n => $v) { 5138 | if ($v == '?') 5139 | $jsonpData = $n; 5140 | } 5141 | } 5142 | if (preg_match($jsre, $options['url'])) 5143 | $jsonpUrl = true; 5144 | if ($jsonpData !== false || $jsonpUrl) { 5145 | // remember callback name for httpData() 5146 | $options['_jsonp'] = $jsonpCallback; 5147 | if ($jsonpData !== false) 5148 | $options['data'][$jsonpData] = $jsonpCallback; 5149 | if ($jsonpUrl) 5150 | $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']); 5151 | } 5152 | } 5153 | $client->setUri($options['url']); 5154 | $client->setMethod(strtoupper($options['type'])); 5155 | if (isset($options['referer']) && $options['referer']) 5156 | $client->setHeaders('Referer', $options['referer']); 5157 | $client->setHeaders(array( 5158 | // 'content-type' => $options['contentType'], 5159 | 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko' 5160 | .'/2008122010 Firefox/3.0.5', 5161 | // TODO custom charset 5162 | 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 5163 | // 'Connection' => 'keep-alive', 5164 | // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 5165 | 'Accept-Language' => 'en-us,en;q=0.5', 5166 | )); 5167 | if ($options['username']) 5168 | $client->setAuth($options['username'], $options['password']); 5169 | if (isset($options['ifModified']) && $options['ifModified']) 5170 | $client->setHeaders("If-Modified-Since", 5171 | self::$lastModified 5172 | ? self::$lastModified 5173 | : "Thu, 01 Jan 1970 00:00:00 GMT" 5174 | ); 5175 | $client->setHeaders("Accept", 5176 | isset($options['dataType']) 5177 | && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ]) 5178 | ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*" 5179 | : self::$ajaxSettings['accepts']['_default'] 5180 | ); 5181 | // TODO $options['processData'] 5182 | if ($options['data'] instanceof phpQueryObject) { 5183 | $serialized = $options['data']->serializeArray($options['data']); 5184 | $options['data'] = array(); 5185 | foreach($serialized as $r) 5186 | $options['data'][ $r['name'] ] = $r['value']; 5187 | } 5188 | if (strtolower($options['type']) == 'get') { 5189 | $client->setParameterGet($options['data']); 5190 | } else if (strtolower($options['type']) == 'post') { 5191 | $client->setEncType($options['contentType']); 5192 | $client->setParameterPost($options['data']); 5193 | } 5194 | if (self::$active == 0 && $options['global']) 5195 | phpQueryEvents::trigger($documentID, 'ajaxStart'); 5196 | self::$active++; 5197 | // beforeSend callback 5198 | if (isset($options['beforeSend']) && $options['beforeSend']) 5199 | phpQuery::callbackRun($options['beforeSend'], array($client)); 5200 | // ajaxSend event 5201 | if ($options['global']) 5202 | phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options)); 5203 | if (phpQuery::$debug) { 5204 | self::debug("{$options['type']}: {$options['url']}\n"); 5205 | self::debug("Options:
".var_export($options, true)."
\n"); 5206 | // if ($client->getCookieJar()) 5207 | // self::debug("Cookies:
".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."
\n"); 5208 | } 5209 | // request 5210 | $response = $client->request(); 5211 | if (phpQuery::$debug) { 5212 | self::debug('Status: '.$response->getStatus().' / '.$response->getMessage()); 5213 | self::debug($client->getLastRequest()); 5214 | self::debug($response->getHeaders()); 5215 | } 5216 | if ($response->isSuccessful()) { 5217 | // XXX tempolary 5218 | self::$lastModified = $response->getHeader('Last-Modified'); 5219 | $data = self::httpData($response->getBody(), $options['dataType'], $options); 5220 | if (isset($options['success']) && $options['success']) 5221 | phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options)); 5222 | if ($options['global']) 5223 | phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options)); 5224 | } else { 5225 | if (isset($options['error']) && $options['error']) 5226 | phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage())); 5227 | if ($options['global']) 5228 | phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options)); 5229 | } 5230 | if (isset($options['complete']) && $options['complete']) 5231 | phpQuery::callbackRun($options['complete'], array($client, $response->getStatus())); 5232 | if ($options['global']) 5233 | phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options)); 5234 | if ($options['global'] && ! --self::$active) 5235 | phpQueryEvents::trigger($documentID, 'ajaxStop'); 5236 | return $client; 5237 | // if (is_null($domId)) 5238 | // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false; 5239 | // return new phpQueryAjaxResponse($response, $domId); 5240 | } 5241 | protected static function httpData($data, $type, $options) { 5242 | if (isset($options['dataFilter']) && $options['dataFilter']) 5243 | $data = self::callbackRun($options['dataFilter'], array($data, $type)); 5244 | if (is_string($data)) { 5245 | if ($type == "json") { 5246 | if (isset($options['_jsonp']) && $options['_jsonp']) { 5247 | $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data); 5248 | } 5249 | $data = self::parseJSON($data); 5250 | } 5251 | } 5252 | return $data; 5253 | } 5254 | /** 5255 | * Enter description here... 5256 | * 5257 | * @param array|phpQuery $data 5258 | * 5259 | */ 5260 | public static function param($data) { 5261 | return http_build_query($data, null, '&'); 5262 | } 5263 | public static function get($url, $data = null, $callback = null, $type = null) { 5264 | if (!is_array($data)) { 5265 | $callback = $data; 5266 | $data = null; 5267 | } 5268 | // TODO some array_values on this shit 5269 | return phpQuery::ajax(array( 5270 | 'type' => 'GET', 5271 | 'url' => $url, 5272 | 'data' => $data, 5273 | 'success' => $callback, 5274 | 'dataType' => $type, 5275 | )); 5276 | } 5277 | public static function post($url, $data = null, $callback = null, $type = null) { 5278 | if (!is_array($data)) { 5279 | $callback = $data; 5280 | $data = null; 5281 | } 5282 | return phpQuery::ajax(array( 5283 | 'type' => 'POST', 5284 | 'url' => $url, 5285 | 'data' => $data, 5286 | 'success' => $callback, 5287 | 'dataType' => $type, 5288 | )); 5289 | } 5290 | public static function getJSON($url, $data = null, $callback = null) { 5291 | if (!is_array($data)) { 5292 | $callback = $data; 5293 | $data = null; 5294 | } 5295 | // TODO some array_values on this shit 5296 | return phpQuery::ajax(array( 5297 | 'type' => 'GET', 5298 | 'url' => $url, 5299 | 'data' => $data, 5300 | 'success' => $callback, 5301 | 'dataType' => 'json', 5302 | )); 5303 | } 5304 | public static function ajaxSetup($options) { 5305 | self::$ajaxSettings = array_merge( 5306 | self::$ajaxSettings, 5307 | $options 5308 | ); 5309 | } 5310 | public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) { 5311 | $loop = is_array($host1) 5312 | ? $host1 5313 | : func_get_args(); 5314 | foreach($loop as $host) { 5315 | if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) { 5316 | phpQuery::$ajaxAllowedHosts[] = $host; 5317 | } 5318 | } 5319 | } 5320 | public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) { 5321 | $loop = is_array($url1) 5322 | ? $url1 5323 | : func_get_args(); 5324 | foreach($loop as $url) 5325 | phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST)); 5326 | } 5327 | /** 5328 | * Returns JSON representation of $data. 5329 | * 5330 | * @static 5331 | * @param mixed $data 5332 | * @return string 5333 | */ 5334 | public static function toJSON($data) { 5335 | if (function_exists('json_encode')) 5336 | return json_encode($data); 5337 | require_once('Zend/Json/Encoder.php'); 5338 | return Zend_Json_Encoder::encode($data); 5339 | } 5340 | /** 5341 | * Parses JSON into proper PHP type. 5342 | * 5343 | * @static 5344 | * @param string $json 5345 | * @return mixed 5346 | */ 5347 | public static function parseJSON($json) { 5348 | if (function_exists('json_decode')) { 5349 | $return = json_decode(trim($json), true); 5350 | // json_decode and UTF8 issues 5351 | if (isset($return)) 5352 | return $return; 5353 | } 5354 | require_once('Zend/Json/Decoder.php'); 5355 | return Zend_Json_Decoder::decode($json); 5356 | } 5357 | /** 5358 | * Returns source's document ID. 5359 | * 5360 | * @param $source DOMNode|phpQueryObject 5361 | * @return string 5362 | */ 5363 | public static function getDocumentID($source) { 5364 | if ($source instanceof DOMDOCUMENT) { 5365 | foreach(phpQuery::$documents as $id => $document) { 5366 | if ($source->isSameNode($document->document)) 5367 | return $id; 5368 | } 5369 | } else if ($source instanceof DOMNODE) { 5370 | foreach(phpQuery::$documents as $id => $document) { 5371 | if ($source->ownerDocument->isSameNode($document->document)) 5372 | return $id; 5373 | } 5374 | } else if ($source instanceof phpQueryObject) 5375 | return $source->getDocumentID(); 5376 | else if (is_string($source) && isset(phpQuery::$documents[$source])) 5377 | return $source; 5378 | } 5379 | /** 5380 | * Get DOMDocument object related to $source. 5381 | * Returns null if such document doesn't exist. 5382 | * 5383 | * @param $source DOMNode|phpQueryObject|string 5384 | * @return string 5385 | */ 5386 | public static function getDOMDocument($source) { 5387 | if ($source instanceof DOMDOCUMENT) 5388 | return $source; 5389 | $source = self::getDocumentID($source); 5390 | return $source 5391 | ? self::$documents[$id]['document'] 5392 | : null; 5393 | } 5394 | 5395 | // UTILITIES 5396 | // http://docs.jquery.com/Utilities 5397 | 5398 | /** 5399 | * 5400 | * @return unknown_type 5401 | * @link http://docs.jquery.com/Utilities/jQuery.makeArray 5402 | */ 5403 | public static function makeArray($obj) { 5404 | $array = array(); 5405 | if (is_object($object) && $object instanceof DOMNODELIST) { 5406 | foreach($object as $value) 5407 | $array[] = $value; 5408 | } else if (is_object($object) && ! ($object instanceof Iterator)) { 5409 | foreach(get_object_vars($object) as $name => $value) 5410 | $array[0][$name] = $value; 5411 | } else { 5412 | foreach($object as $name => $value) 5413 | $array[0][$name] = $value; 5414 | } 5415 | return $array; 5416 | } 5417 | public static function inArray($value, $array) { 5418 | return in_array($value, $array); 5419 | } 5420 | /** 5421 | * 5422 | * @param $object 5423 | * @param $callback 5424 | * @return unknown_type 5425 | * @link http://docs.jquery.com/Utilities/jQuery.each 5426 | */ 5427 | public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) { 5428 | $paramStructure = null; 5429 | if (func_num_args() > 2) { 5430 | $paramStructure = func_get_args(); 5431 | $paramStructure = array_slice($paramStructure, 2); 5432 | } 5433 | if (is_object($object) && ! ($object instanceof Iterator)) { 5434 | foreach(get_object_vars($object) as $name => $value) 5435 | phpQuery::callbackRun($callback, array($name, $value), $paramStructure); 5436 | } else { 5437 | foreach($object as $name => $value) 5438 | phpQuery::callbackRun($callback, array($name, $value), $paramStructure); 5439 | } 5440 | } 5441 | /** 5442 | * 5443 | * @link http://docs.jquery.com/Utilities/jQuery.map 5444 | */ 5445 | public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) { 5446 | $result = array(); 5447 | $paramStructure = null; 5448 | if (func_num_args() > 2) { 5449 | $paramStructure = func_get_args(); 5450 | $paramStructure = array_slice($paramStructure, 2); 5451 | } 5452 | foreach($array as $v) { 5453 | $vv = phpQuery::callbackRun($callback, array($v), $paramStructure); 5454 | // $callbackArgs = $args; 5455 | // foreach($args as $i => $arg) { 5456 | // $callbackArgs[$i] = $arg instanceof CallbackParam 5457 | // ? $v 5458 | // : $arg; 5459 | // } 5460 | // $vv = call_user_func_array($callback, $callbackArgs); 5461 | if (is_array($vv)) { 5462 | foreach($vv as $vvv) 5463 | $result[] = $vvv; 5464 | } else if ($vv !== null) { 5465 | $result[] = $vv; 5466 | } 5467 | } 5468 | return $result; 5469 | } 5470 | /** 5471 | * 5472 | * @param $callback Callback 5473 | * @param $params 5474 | * @param $paramStructure 5475 | * @return unknown_type 5476 | */ 5477 | public static function callbackRun($callback, $params = array(), $paramStructure = null) { 5478 | if (! $callback) 5479 | return; 5480 | if ($callback instanceof CallbackParameterToReference) { 5481 | // TODO support ParamStructure to select which $param push to reference 5482 | if (isset($params[0])) 5483 | $callback->callback = $params[0]; 5484 | return true; 5485 | } 5486 | if ($callback instanceof Callback) { 5487 | $paramStructure = $callback->params; 5488 | $callback = $callback->callback; 5489 | } 5490 | if (! $paramStructure) 5491 | return call_user_func_array($callback, $params); 5492 | $p = 0; 5493 | foreach($paramStructure as $i => $v) { 5494 | $paramStructure[$i] = $v instanceof CallbackParam 5495 | ? $params[$p++] 5496 | : $v; 5497 | } 5498 | return call_user_func_array($callback, $paramStructure); 5499 | } 5500 | /** 5501 | * Merge 2 phpQuery objects. 5502 | * @param array $one 5503 | * @param array $two 5504 | * @protected 5505 | * @todo node lists, phpQueryObject 5506 | */ 5507 | public static function merge($one, $two) { 5508 | $elements = $one->elements; 5509 | foreach($two->elements as $node) { 5510 | $exists = false; 5511 | foreach($elements as $node2) { 5512 | if ($node2->isSameNode($node)) 5513 | $exists = true; 5514 | } 5515 | if (! $exists) 5516 | $elements[] = $node; 5517 | } 5518 | return $elements; 5519 | // $one = $one->newInstance(); 5520 | // $one->elements = $elements; 5521 | // return $one; 5522 | } 5523 | /** 5524 | * 5525 | * @param $array 5526 | * @param $callback 5527 | * @param $invert 5528 | * @return unknown_type 5529 | * @link http://docs.jquery.com/Utilities/jQuery.grep 5530 | */ 5531 | public static function grep($array, $callback, $invert = false) { 5532 | $result = array(); 5533 | foreach($array as $k => $v) { 5534 | $r = call_user_func_array($callback, array($v, $k)); 5535 | if ($r === !(bool)$invert) 5536 | $result[] = $v; 5537 | } 5538 | return $result; 5539 | } 5540 | public static function unique($array) { 5541 | return array_unique($array); 5542 | } 5543 | /** 5544 | * 5545 | * @param $function 5546 | * @return unknown_type 5547 | * @TODO there are problems with non-static methods, second parameter pass it 5548 | * but doesnt verify is method is really callable 5549 | */ 5550 | public static function isFunction($function) { 5551 | return is_callable($function); 5552 | } 5553 | public static function trim($str) { 5554 | return trim($str); 5555 | } 5556 | /* PLUGINS NAMESPACE */ 5557 | /** 5558 | * 5559 | * @param $url 5560 | * @param $callback 5561 | * @param $param1 5562 | * @param $param2 5563 | * @param $param3 5564 | * @return phpQueryObject 5565 | */ 5566 | public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) { 5567 | if (self::plugin('WebBrowser')) { 5568 | $params = func_get_args(); 5569 | return self::callbackRun(array(self::$plugins, 'browserGet'), $params); 5570 | } else { 5571 | self::debug('WebBrowser plugin not available...'); 5572 | } 5573 | } 5574 | /** 5575 | * 5576 | * @param $url 5577 | * @param $data 5578 | * @param $callback 5579 | * @param $param1 5580 | * @param $param2 5581 | * @param $param3 5582 | * @return phpQueryObject 5583 | */ 5584 | public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) { 5585 | if (self::plugin('WebBrowser')) { 5586 | $params = func_get_args(); 5587 | return self::callbackRun(array(self::$plugins, 'browserPost'), $params); 5588 | } else { 5589 | self::debug('WebBrowser plugin not available...'); 5590 | } 5591 | } 5592 | /** 5593 | * 5594 | * @param $ajaxSettings 5595 | * @param $callback 5596 | * @param $param1 5597 | * @param $param2 5598 | * @param $param3 5599 | * @return phpQueryObject 5600 | */ 5601 | public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) { 5602 | if (self::plugin('WebBrowser')) { 5603 | $params = func_get_args(); 5604 | return self::callbackRun(array(self::$plugins, 'browser'), $params); 5605 | } else { 5606 | self::debug('WebBrowser plugin not available...'); 5607 | } 5608 | } 5609 | /** 5610 | * 5611 | * @param $code 5612 | * @return string 5613 | */ 5614 | public static function php($code) { 5615 | return self::code('php', $code); 5616 | } 5617 | /** 5618 | * 5619 | * @param $type 5620 | * @param $code 5621 | * @return string 5622 | */ 5623 | public static function code($type, $code) { 5624 | return "<$type>"; 5625 | } 5626 | 5627 | public static function __callStatic($method, $params) { 5628 | return call_user_func_array( 5629 | array(phpQuery::$plugins, $method), 5630 | $params 5631 | ); 5632 | } 5633 | protected static function dataSetupNode($node, $documentID) { 5634 | // search are return if alredy exists 5635 | foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) { 5636 | if ($node->isSameNode($dataNode)) 5637 | return $dataNode; 5638 | } 5639 | // if doesn't, add it 5640 | phpQuery::$documents[$documentID]->dataNodes[] = $node; 5641 | return $node; 5642 | } 5643 | protected static function dataRemoveNode($node, $documentID) { 5644 | // search are return if alredy exists 5645 | foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) { 5646 | if ($node->isSameNode($dataNode)) { 5647 | unset(self::$documents[$documentID]->dataNodes[$k]); 5648 | unset(self::$documents[$documentID]->data[ $dataNode->dataID ]); 5649 | } 5650 | } 5651 | } 5652 | public static function data($node, $name, $data, $documentID = null) { 5653 | if (! $documentID) 5654 | // TODO check if this works 5655 | $documentID = self::getDocumentID($node); 5656 | $document = phpQuery::$documents[$documentID]; 5657 | $node = self::dataSetupNode($node, $documentID); 5658 | if (! isset($node->dataID)) 5659 | $node->dataID = ++phpQuery::$documents[$documentID]->uuid; 5660 | $id = $node->dataID; 5661 | if (! isset($document->data[$id])) 5662 | $document->data[$id] = array(); 5663 | if (! is_null($data)) 5664 | $document->data[$id][$name] = $data; 5665 | if ($name) { 5666 | if (isset($document->data[$id][$name])) 5667 | return $document->data[$id][$name]; 5668 | } else 5669 | return $id; 5670 | } 5671 | public static function removeData($node, $name, $documentID) { 5672 | if (! $documentID) 5673 | // TODO check if this works 5674 | $documentID = self::getDocumentID($node); 5675 | $document = phpQuery::$documents[$documentID]; 5676 | $node = self::dataSetupNode($node, $documentID); 5677 | $id = $node->dataID; 5678 | if ($name) { 5679 | if (isset($document->data[$id][$name])) 5680 | unset($document->data[$id][$name]); 5681 | $name = null; 5682 | foreach($document->data[$id] as $name) 5683 | break; 5684 | if (! $name) 5685 | self::removeData($node, $name, $documentID); 5686 | } else { 5687 | self::dataRemoveNode($node, $documentID); 5688 | } 5689 | } 5690 | } 5691 | /** 5692 | * Plugins static namespace class. 5693 | * 5694 | * @author Tobiasz Cudnik 5695 | * @package phpQuery 5696 | * @todo move plugin methods here (as statics) 5697 | */ 5698 | class phpQueryPlugins { 5699 | public function __call($method, $args) { 5700 | if (isset(phpQuery::$extendStaticMethods[$method])) { 5701 | $return = call_user_func_array( 5702 | phpQuery::$extendStaticMethods[$method], 5703 | $args 5704 | ); 5705 | } else if (isset(phpQuery::$pluginsStaticMethods[$method])) { 5706 | $class = phpQuery::$pluginsStaticMethods[$method]; 5707 | $realClass = "phpQueryPlugin_$class"; 5708 | $return = call_user_func_array( 5709 | array($realClass, $method), 5710 | $args 5711 | ); 5712 | return isset($return) 5713 | ? $return 5714 | : $this; 5715 | } else 5716 | throw new Exception("Method '{$method}' doesnt exist"); 5717 | } 5718 | } 5719 | /** 5720 | * Shortcut to phpQuery::pq($arg1, $context) 5721 | * Chainable. 5722 | * 5723 | * @see phpQuery::pq() 5724 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5725 | * @author Tobiasz Cudnik 5726 | * @package phpQuery 5727 | */ 5728 | function pq($arg1, $context = null) { 5729 | $args = func_get_args(); 5730 | return call_user_func_array( 5731 | array('phpQuery', 'pq'), 5732 | $args 5733 | ); 5734 | } 5735 | // add plugins dir and Zend framework to include path 5736 | set_include_path( 5737 | get_include_path() 5738 | .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/' 5739 | .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/' 5740 | ); 5741 | // why ? no __call nor __get for statics in php... 5742 | // XXX __callStatic will be available in PHP 5.3 5743 | phpQuery::$plugins = new phpQueryPlugins(); 5744 | // include bootstrap file (personal library config) 5745 | if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php')) 5746 | require_once dirname(__FILE__).'/phpQuery/bootstrap.php'; 5747 | --------------------------------------------------------------------------------