红得发紫的jQuery框架是专门用于页面Javascript程序设计的,它通过一种优雅的方式让我们轻松自如地操作页面的所有元素而无须担心浏 览器版本以及兼容性等问题。受到jQuery的启发,一种试图让Web开发者在PHP中直接采用jQuery方式操纵和生成HTML/XML元素的 QueryPath计划开始了,库的发开者是 Matt Butcher 。
QueryPath可以很方便地读入和生成HTML/XML,使用jQuery类似的语法和函数遍历文档对象,支持远程URL文件的读取和分析。支 持标准的CSS3 Selector和XPath,这意味着我们可以使PHP像jQuery一样,随心所欲地玩转任何HTML!当然PHP5本身就带了不少DOM解析 库,QueryPath本身也是在这些库上的二次加工,省下了我们不少的工作量。
QueryPath官方网站: http://querypath.org/
QueryPath使用一个qp()工厂函数,为各种需求生成不同的类,一个最简单的例子如下:
require 'QueryPath/QueryPath.php'; $html = qp('a.html'); $html->find('title')->text('hello world'); $html->find('.myInput')->attr('value', 'hello world'); $html->find('body')->css('background-color', 'red');
如果你对jQuery熟悉的话,几乎可以没有任何学习成本就能很快上 手。我立刻就使用QueryPath来写了几个测试例程,分析和遍历了一个常去的网站的内容结构,抓取了和分析了不少有用的资料。感觉对于那些做网站采集 程序的家伙们来说,QueryPath是不是又要让他们更加如鱼得水(抑或是无恶不作)了,LOL。
使用过程中发现QueryPath库尚处于基本的开发状态,不少问题。但对于程序员来说,遇到问题读一下库的源代码,基本上就能搞定。下面列出两个 常用问题的解决,与大家分享。
1.当QueryPath查询到一个节点并操作完成后,它本身并不回到根节点,而是停留在节点上,不知道这样描述正确否,这样导致的结果是下一步的 查询将很可能找不到数据。解决的方法是用top()函数回到根节点。如上面的例子可以直接这样用,用top回到根:
$html->top()->find('.myInput')->attr('value', 'hello world');
支持的方法有top(), prev(), next(), child(),不用解释,很好理解。
2.无法读取远程url,或不能识别非"html"的扩展名的html文件。QueryPath直接分析文件和url的扩展名,不为"html"的直接当做xml处理,寒一个。解决的办法是用url的传递参数在最后面伪装一 个".html"的参数,告诉QueryPath这是HTML文件。解决方法:
$qp('http://www.acwind.net/index.php?=.html');//伪装一个后缀名,搞定。
希望开发者能在将来的版本中改进,能自动识别文件类型那该多好。
详解:
PHP 也曾在 Web 开发领域造成轰动。由于易于开发和以 Web 为中心的模型,PHP 使 Web 站点从小小的主页变成像 Yahoo! 这样强大的站点。但是,通过 PHP 来使用这三种技术 — 尤其是 XML — 有时候会比较复杂。在本文中,了解 QueryPath,这是一个 PHP 库,它在设计时考虑了两个目标:
- 简单性,使 HTML、XML 和 HTTP 变得容易使用
- 健壮性,为使用这些技术提供丰富的工具
本文探索如何构建 QueryPath 对象、遍历 XML 和 HTML、操纵 XML 和 HTML 以及使用 QueryPath 访问 Web 服务(使用 Twitter 作为示例服务)。
对于 QueryPath 的典型使用,有四个最重要的概念:
- QueryPath 对象与一个 XML 或 HTML 文档相关联。
- QueryPath 可以查询文档,识别文档中的一组匹配项。
- QueryPath 可以操纵文档。可以添加新的部分,修改已有的部分,删除不想要的部分。
- QueryPath 方法可以链接在一起,在一个简洁的序列中执行很多操作。只需几行代码,就可以装载、解析、查询、修改和写入文档。
清单 1 中的代码展示了所有这些要点。
<?php require 'QueryPath/QueryPath.php'; qp('sample.html')->find('title')->text('Hello World')->writeHTML(); ?>
以上例子需要一个库,即
QueryPath/QueryPath.php
。除非还要装载 QueryPath 扩展,否者只需包括这个库就可以使用 QueryPath。
|
例子中接下来一行代码是一个 QueryPath 链,它做以下事情。
-
创建一个新的
QueryPath
对象,该对象指向 sample.html 文档。当qp()
运行时,它将创建一个新的QueryPath
对象,后者随即装载和解析文档。 -
使用
find()
方法,它使用 CSS 3 选择器title
搜索整个文档,寻找所有<title/>
元素。在一个有效的 HTML 文档中,该搜索只能在文档的头部找到一个匹配的
<title/>
元素。 -
标题的文本值被设为
Hello World
。当执行到这里时,标题的子节点将被 CDATA(字符数据)字符串Hello World
替换。任何已有的内容将被破坏。 -
使用
writeHTML()
方法将整个文档写到标准输出中。
以上例子实际上还可以缩短一点,因为
qp()
工厂函数带有一个 CSS 选择器作为可选的第二个参数。清单 2 显示了缩短后的版本。
<?php require 'QueryPath/QueryPath.php'; qp('sample.html', 'title')->text('Hello World')->writeHTML(); ?> |
假设
sample.html
是一个最基本的 HTML 文档,以上代码(
清 单 1
或清单 2)的结果看上去将如清单 3 所示。加粗的行包含我们设置的标题。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <title>Hello World</title> </head> <body> </body> </html> |
这些简单的例子展示了 QueryPath 可以执行的一些常见的任务。接下来几个小节探索一些方法。然后,您将把这些构建块装配起来,创建一个简单的 Web 服务客户机。
QueryPath 库中最常用的函数是
qp()
工厂函数。实际上,它执行创建新的 QueryPath 对象的任务。它被用于传统的构造函数。
如果您熟悉面向对象设计模式,那么可能会意识到
qp()
是工厂模式的一个变种。 QueryPath 不是用构造器方法定义一个工厂类,而是使用一个函数。这种方法除了可以节省键盘输入外(在链接方法时比较重要),还可以使 QueryPath 更贴近 jQuery,减少 jQuery 熟悉者的学习曲线。
一个
QueryPath
对象与一个 XML 或 HTML 文档相关联。当构造
QueryPath
对象时,文档被绑定到该对象。
qp()
函数带有 3 个参数,这 3 个参数都是可选的:
|
qp()
支持将很多类型的数据作为第一个参数,从而方便构建 QueryPath 对象。QueryPath 可以以一个文件名或 URL 开始,然后装载一个文档。如果传递的是一个 XML 或 HTML 字符串,QueryPath 将解析该内容。当然,它可以接受另外两种常用的 XML 文档的对象表示:DOM 和 SimpleXML。清单 4 展示
qp()
函数如何解析包含 XML 的字符串。
清单 4. 从 XML 字符串构建 QueryPath 对象
<?php require 'QueryPath/QueryPath.php'; $xml = '<?xml version="1.0"?><doc><item/></doc>'; $qp = qp($xml); ?> |
当清单 4 中的代码运行时,
$qp
将引用一个
QueryPath
对象,该对象在内部指向 XML 解析后的表示。前面的例子传入的是一个文件名。如果 PHP 被配置为允许 HTTP/HTTPS 流包装器(在大多数 PHP V5 发行版中是标准配置),那么甚至可以装载远程 HTTP URL,如下所示。
<?php require 'QueryPath/QueryPath.php'; $qp = qp('http://example.com/file.xml'); ?> |
这样便可以使用 QueryPath 访问 Web 服务。(可以使用第 3 个参数
qp()
传递流上下文,以便对连接设置进行调整)。当创建新文档时,有一个添加样板 HTML 的快捷方式,如下所示。
清单 6. 使用
QueryPath::HTML_STUB
常量
<?php require 'QueryPath/QueryPath.php'; $qp = qp(QueryPath::HTML_STUB); ?> |
QueryPath::HTML_STUB
常量定义一个基本的 HTML 文档,如下所示。
<?xml version="1.0"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>Untitled</title> </head> <body></body> </html> |
以这个框架文档为基础,可以更快地生成 HTML。
至此,您知道了如何创建新的指向文档的 QueryPath 对象,并且看到了一个简单的 CSS 选择器。下一小节讨论如何使用 QueryPath 遍历文档。
打开文档后,需要在文档中查找感兴趣的内容。QueryPath 的设计使得这一任务变得很容易。为了简化遍历需求,QueryPath 提供了一些用于遍历的方法。大多数方法使用 CSS3 选择器查找所需的节点。
图 1 总结了常用的遍历函数。下面一一描述每个函数。虽然还有一些遍历函数没有提到,但这里覆盖了大多数常见的需求。
方法 描述 是否带 CSS 选择器
find()
|
选择与选择器匹配的任何元素(在当前选择的节点下) | 是 |
xpath()
|
选择与给定 XPath 查询匹配的元素 | 否(使用 XPath 查询) |
top()
|
选择文档元素(根元素) | 否 |
parents()
|
选择任何祖先元素 | 是 |
parent()
|
选择直接父元素 | 是 |
siblings()
|
选择所有同胞(sibling)元素(包括之前和之后的元素) | 是 |
next()
|
选择后一个同胞元素 | 是 |
nextAll()
|
选择当前元素之后的所有同胞元素 | 是 |
prev()
|
选择前一个同胞元素 | 是 |
prevAll()
|
选择当前元素之前的所有同胞元素 | 是 |
children()
|
选择当前元素的直接子元素 | 是 |
deepest()
|
选择当前元素下最深的节点 | 否 |
QueryPath 中的很多方法可以以查询作为参数,进一步指定应该选择什么项。如表 1 中第三列所示,几乎所有这些方法都带有一个作为可选参数的 CSS3 选择器。(
xpath()
函数则带有一个 XPath 查询,而不是 CSS3 选择器)。只有
top()
和
deepest()
不使用查询作为参数。
可以通过另一个简单的例子了解如何进行遍历。假设有一个像下面这样的 XML 文档。
<?xml version="1.0"?> <root> <child id="one"/> <child id="two"/> <child id="three"/> <ignore/> </root> |
<root/>
元素有 4 个子元素:其中有 3 个名为
<child/>
,还有一个名为
<ignore/>
。可以用一个 QueryPath 查询选择
<root/>
的所有 4 个子元素。
<?php require 'QueryPath/QueryPath.php'; $xml = '<?xml version="1.0"?> <root> <child id="one"/> <child id="two"/> <child id="three"/> <ignore/> </root>'; $qp = qp($xml, 'root')->children(); print $qp->size(); ?> |
children()
方法将选择
<root/>
元素的所有直接子元素。最后一行打印
QueryPath
对象中匹配项的数量,最终打印的结果为
4
。
假设只需选择 3 个
<child/>
元素,而不需要选择
<ignore/>
元素。 清单 10 显示了如何实现这一点。
<?php require 'QueryPath/QueryPath.php'; $xml = '<?xml version="1.0"?> <root> <child id="one"/> <child id="two"/> <child id="three"/> <ignore/> </root>'; $qp = qp($xml, 'root')->children('child'); print $qp->size(); ?> |
最后的
print
语句将打印 QueryPath 当前选择的项的数量。它将返回
3
。在内部,QueryPath 跟踪这 3 个元素。它们被存储为当前上下文。如果执行进一步的查询,那么查询将从这 3 个元素开始。如果试图附加数据,那么数据将被附加到这 3 个元素后。
|
CSS 选择器是 CSS 语句的一部分,用于选择将应用某种样式的元素。CSS 选择器还可以在样式表上下文之外使用。QueryPath 使用选择器作为查询语言,并支持 CSS3 选择器 标 准 中描述的特性集。
CSS 选择器在 QueryPath 中扮演很重要的角色。您已经看到,有 10 个函数使用 CSS 选择器作为参数。到目前为止使用的选择器是简单的标记名查询。CSS3 选择器要比前面的例子强大得多。对 CSS3 选择器的详细描述超出了本文的范围,但表 2 提供了一些常见的选择器模式的例子。
选择器模式 描述 示例匹配项
p
|
找到标记名为
<p/>
的元素
|
<p> |
.container
|
找到
class
属性被设为
container
的元素
|
<div class="container"/> |
#menu
|
找到
id
属性被设为
menu
的元素。基于 ID 的搜索以这种方式进行
|
<div id="menu"/> |
[type="inline"]
|
找到
type
属性的值为
inline
的元素
|
<code type="inline"/> |
tr > th
|
找到直接父元素为
<tr>
的
<th>
元素
|
<tr><th/></tr> |
table td
|
找到祖先(例如父亲或祖父)中有
<table>
元素的
<td>
元素
|
<table><tr><td/></tr></table> |
li:first
|
获取第一个名为
<li/>
的元素。支持的伪类包括
:last
、
:even
和
:odd
|
<li/> |
RDF|seq
|
找到
<RDF:seq>
元素。 QueryPath 包括用于 XML 名称空间的 CSS3 选择器。名称空间支持延伸到属性和元素
|
<RDF:seq> |
这些常见的选择器模式可以加以组合,形成复杂的选择器,例如
div.content ul>li:first |
。 这个选择器将搜索 class 为
content
的任何
<div/>
。 在
div
中,它将搜索所有无序列表(
<ul>
),返回每个列表的第一个列表项(
<li>
)。
您了解了遍历文档的两个方面:QueryPath 提供的方法和 CSS3 选择器支持。第三个方面是迭代选择的项。
QueryPath 对象是 可遍历的(traversable) 。在 PHP 中,这意味着对象可以当做迭代器。标准的 PHP 循环结构可以遍历 QueryPath 对象选择的元素。还记得吗, 清 单 10 中的例子是一个简单的查询,它从一个 XML 文档中检索 3 个元素。接下来的例子将以这个例子为基础。
如果要单独处理每个项,应该怎么办?很容易,因为 QueryPath 可以用作迭代器。清单 11 显示了一个例子。
<?php require 'QueryPath/QueryPath.php'; $xml = '<?xml version="1.0"?> <root> <child id="one"/> <child id="two"/> <child id="three"/> <ignore/> </root>'; $qp = qp($xml, 'root')->children('child'); foreach ($qp as $child) { print $child->attr('id') . PHP_EOL; } ?> |
当
foreach
循环迭代时,它将每个匹配项赋给
$child
变量。但是,
$child
不是真正的元素,它是指向当前元素的一个
QueryPath
对象。您可以任意使用所有常见的 QueryPath 方法。
为了使 API 与 jQuery 的 API 类似, QueryPath 提供一些可同时作为 accessor 和 mutator — 或 getter 和 setter 的方法。取决于参数,同一个方法可以检索(access)数据,或者更改(mutate)数据。
attr()
函数就是一个例子。
qp()->attr('name')
检索
name
属性的值。
qp()->attr('name', 'value')
将
name
属性的值设为
value
。还有一些方法,包括
text()
、
html()
和
xml()
,作为 accessor 和 mutator 同时执行两种任务。
由于每个迭代的项包装在一个 QueryPath 对象中,所以可以通过
$child
任意使用所有标准的 QueryPath 方法。上面的例子使用了
attr()
函数,这是一个元素中的属性的 accessor 和 mutator。
attr()
方法检索名为
id
的属性的值。下面显示以上代码的输出。
one two three |
您已经了解了如何使用 QueryPath 方法、CSS3 选择器和迭代技术遍历文档。下一节探索如何用 QueryPath 修改文档。
除了使用 QueryPath 搜索文档外,还可以使用它添加、修改和移除文档中的数据。在清单 1 中可以大致了解 QueryPath 的功能。为了方便,下面再重复一遍。
<?php require 'QueryPath/QueryPath.php'; qp('sample.html')->find('title')->text('Hello World')->writeHTML(); ?> |
在这个例子中,
text()
函数用于修改
<title/>
元素的内容。QueryPath 提供了十几个用于更改文档的方法。图 2 展示一些常用的修改方法如何工作。这些方法都是添加或替换数据。绿色的标记表示当前被选中的元素。
每个方法以字符串数据(通常是以 HTML 或 XML 片段的形式)作为参数,并将数据插入到文档中。随后立即可以访问和进一步操纵新插入的数据。
实际上有两类方法。第一类方法使用任意的 XML 片段,如下所示。
append()
|
将数 据添加为当前选中元素的 最后一个 子元素 |
prepend()
|
将 数据添加为当前选中元素的 第一个 子元素 |
after()
|
将 数据直接插在当前选中元素 之后 |
before()
|
将数据直接插在当前选择的元素 之前 |
html()
|
替换 HTML 文档中当前元素的子内容 |
xml()
|
替换 XML 文档中当前元素的子内容 |
以上方法需要一个参数,该参数包含一个格式良好的 XML 或 HTML 数据的字符串。清单 14 有一个使用
html()
方法的例子。
<?php require 'QueryPath/QueryPath.php'; qp($file)->find('div.content')->html('<ul><li>One</li></ul>'); ?> |
图 2
中没有给出
remove()
方法(难以清楚地表示移除)。
remove()
方法移除文档中的元素。如果不带参数调用,该方法将移除当前选中的元素。但是,和很多其他的 QueryPath 方法一样,
remove()
可以使用一个 CSS3 选择器作为可选参数。如果提供了一个选择器,那么只移除与选择器匹配的项。
图 2 中的第二类方法则操纵元素中的属性。下面介绍两个这样的例子。
attr()
|
获取或设 置每个选中的元素上给定属性的值 |
addClass()
|
为当前选中的每 个元素添加一个类 |
还有其他一些与属性相关的方法。例如
removeClass()
方法,该方法以一个类名作为参数,它将移除元素中的一个类。
removeAttr()
以一个属性名作为参数,它将从所有当前选中的元素中移除具有该名称的属性。
现在可以将所有这些基本功能组合到一起,形成有趣的东西。
Twitter 是一个流行的微博客服务,通过它可以发布短消息,同时还可以跟随其他 Twitter 用户的微博客。 Twitter 提供了一个简单的 Web 服务,用于公布该平台的很多特性。
下面的例子使用 QueryPath 在 Twitter 服务器上执行搜索,并以 HTML 格式打印结果。可以将一个工具添加到已有的 Web 站点,以显示最近的关于一个感兴趣的话题的 Twitter 活动。
Twitter 的搜索服务器侦听一个标准的 HTTP 服务器,当被请求时,以 Atom XML 格式返回搜索结果。我们的例子将搜索最近 5 个提到 QueryPath 的贴子。为了运行这种搜索,并以 Atom 格式返回内容,只需在 URL 中编写必要的信息:
http://search.twitter.com/search.
atom
?
rpp=5
&
q=QueryPath
。
加粗的 3 个部分表示针对这个应用程序进行了调整的参数。
.atom
|
提供这个扩 展名是为了告诉服务器需要返回 Atom XML 内容 |
rpp=5
|
RPP 指定每页显示的结果数。我们想要返回 5 条结果。默认情况下,将返回 5 条最近的结果 |
q=QueryPath
|
这 是查询。Twitter 支持更复杂的搜索查询,但对于这个简单的例子只需要这样的查询。 |
当装载这个 URL 时,Twitter 将返回一个 Atom 格式的 XML 文档。下面的清单 15 显示一个经过大量简化的返回文档。这里只显示最关心的信息(只显示一个条目)。
<?xml version="1.0" encoding="UTF-8"?> <feed> <entry> <content type="html"> Last night I added XSD schema validation and XSL Transformation (XSLT) support to <b>QueryPath</b> (as extensions). Will commit them today. </content> <link type="image/png" rel="image" href="http://example.com/img.jpg"/> <author> <name>technosophos (M Butcher)</name> <uri>http://twitter.com/technosophos</uri> </author> </entry> </feed> |
清单 16 显示了执行搜索的简要的 QueryPath 代码,处理返回的 XML,并创建一个文档。
<?php require 'QueryPath/QueryPath.php'; $url = 'http://search.twitter.com/search.atom?rpp=5&q=QueryPath'; $out = qp(QueryPath::HTML_STUB, 'body')->append('<ul/>')->find('ul'); foreach (qp($url, 'entry') as $result) { $title = $result->children('content')->text(); $img = $result->siblings('link[rel="image"]')->attr('href'); $author = $result->parent()->find('author>name')->text(); $out->append("<li><img src='$img'/> <em>$author</em><br/>$title</li>"); } $out->writeHTML(); ?> |
如果使用 Web 浏览器执行以上代码,可以看到图 3 所示的结果。
图 3. QueryPath 显示 Twitter 搜索结果
清 单 16 中的代码有 14 行,其中只有 9 行代码做实际的工作。以上代码是如何产生图 3 中的视图的呢?
$url
变量存放前面提到的 Twitter URL。
$out
变量指向用于将 HTML 写到客户机的 QueryPath 对象。 从一个基本的文档 (
QueryPath::HTML_STUB
)开始,添加一个无序列表,并(使用
find()
) 选择这个新列表。
foreach
循环是脚本中最重要的一行:
foreach (qp($url, 'entry') as $result)
。这里创建 一个新的
QueryPath
对象。由于传递了一个 URL,QueryPath 将检索远程 Atom 文档,并解析结果。另外,由于传递了选择器
entry
,QueryPath 将选择文档中的所有条目。回头查看一下
清 单 15
,看看这是文档中的哪个部分。返回的文档中将有 5 个条目(因为 URL 中这样设置了
rpp
标志)。这 5 个条目中的每个条目看上去都应该与清单 15 中的
<entry/>
类似。
循环获取了 3 个数据部分:
$title
|
条目的内 容 |
$img
|
发帖用户的头像的 URL |
$author
|
发 帖用户的用户名 |
为了获取每块数据,可以使用不同的 QueryPath 方法。例如,可以使用
$result->children('content')->text();
获得
$title
。
|
循环中首先选择标记名为
content
的所有子元素,然后从发现的节点中获得 CDATA 文本。每个条目将有一个
<content/>
元素。
现在需要获得图像 URL。在前面的链中,选择了
<content/>
元素,所以这就是起点。现在需要搜索
<content/>
的同胞,找到形如
<link rel="image"/>
的元素。 为此,使用
siblings()
函数,并提供一个选择器作为参数。然后使用
attr()
函数获得元素的
href
属性的值。
最后,从
<link/>
元素跳回到它的父元素,接着使用
find('author>name')
, 获得作者的用户名。(请查看
表 2
)。在这里,可以使用
text()
获得作者的用户名的文本。
在
foreach
循环的每次迭代的最后,构建一个 HTML 片段,并使用
append()
将这个片段插入到
$out
QueryPath 中。
迭代完从 Twitter 返回的结果后,可以在脚本的最后将 HTML 文档写到浏览器:
$out->writeHTML();
。
这样就完成了。在大约十几行代码中,您完成了与一个远程服务的交互。可以通过这种方式,使用 QueryPath 访问任何使用 HTTP 和 XML 或 HTML 的 Web 服务。QueryPath 附带的例子展示了如何设置连接参数、对 SPARQL 端点执行 SPARQL 查询以及解析复杂的、多名称空间的文档。QueryPath 为使用 Web 服务带来巨大的潜力。
在本文中,您探索了 QueryPath 库的基础。您学习了如何创建
QueryPath
对象、遍历文档和操纵内容。您还构建了一个小型的例子脚本,该脚本使用流行的 Twitter 微博客服务的 Web 服务 API。
本文只是初步发掘 QueryPath 库的一些可能的应用。例如,本文只提到数据库 API,可使用该 API 将 RDBMS 支持集成到 QueryPath。想象一下,运行一个 SQL
SELECT
语句,并将结果直接合并到一个符合自己的规范的 HTML 表格中。或者再想象一下,构建一个 XML 导入器,用于解析数据并将数据直接插入到数据库中。
QueryPath 还有一些特性这里没有提到。通过映射器和过滤器,可以让 QueryPath 运行定制的函数来转换或过滤 QueryPath 数据。通过
QPTPL
扩展,可以将数据插入到预定义的纯 HTML 模板中。QueryPath 还支持用户定义的扩展。通过编写一个简单的类定义,可以将自己的方法添加到 QueryPath 中。
>>phpQuery—基于 jQuery的PHP实现