├── 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, ">$tag>", $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>$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 |
--------------------------------------------------------------------------------