使用 Symfony 的 CssSelector 组件将 CSS 选择器转换为 XPath 表达式
2025-4-07 19:29:40
为了实现类似 jQuery 的 CSS 选择器语法(如 p
、.container .para1
),我们需要引入一个工具将 CSS 选择器转换为 XPath 表达式。PHP 中可以使用 Symfony 的 CssSelector
组件来完成这一任务。
以下是重写后的 Query
类,支持 CSS 选择器风格的查询:
1. 引入 Symfony CssSelector
首先,确保安装了 Symfony 的 CssSelector
组件。可以通过 Composer 安装:
composer require symfony/css-selector
2. 修改 Query
类以支持 CSS 选择器
下面是修改后的完整代码:
<?php
require_once 'vendor/autoload.php'; // 确保加载 Composer 自动加载文件
use SymfonyComponentCssSelectorCssSelectorConverter;
class Query {
private $dom;
private $xpath;
private $nodes = [];
public function __construct($html = null) {
$this->dom = new DOMDocument();
libxml_use_internal_errors(true); // 防止 HTML 解析错误抛出异常
if ($html) {
$this->dom->loadHTML($html);
}
$this->xpath = new DOMXPath($this->dom);
libxml_clear_errors();
}
// 查找节点,支持 CSS 选择器
public function find($selector) {
$converter = new CssSelectorConverter();
$xpathExpression = $converter->toXPath($selector);
$this->nodes = $this->xpath->query($xpathExpression);
return $this; // 支持链式调用
}
// 获取所有匹配节点的文本内容
public function text() {
$texts = [];
foreach ($this->nodes as $node) {
$texts[] = $node->nodeValue;
}
return $texts;
}
// 获取第一个匹配节点的文本内容
public function firstText() {
if ($this->nodes->length > 0) {
return $this->nodes->item(0)->nodeValue;
}
return null;
}
// 获取或设置 HTML 内容
public function html($newHtml = null) {
if ($newHtml !== null) {
foreach ($this->nodes as $node) {
// 清空节点现有内容
while ($node->firstChild) {
$node->removeChild($node->firstChild);
}
// 添加新的 HTML 内容
$fragment = $this->dom->createDocumentFragment();
$fragment->appendXML($newHtml);
$node->appendChild($fragment);
}
return $this;
}
$htmls = [];
foreach ($this->nodes as $node) {
$htmls[] = $this->dom->saveHTML($node);
}
return $htmls;
}
// 获取或设置属性值
public function attr($attribute, $value = null) {
if ($value !== null) {
foreach ($this->nodes as $node) {
$node->setAttribute($attribute, $value);
}
return $this;
}
$attrs = [];
foreach ($this->nodes as $node) {
if ($node->hasAttribute($attribute)) {
$attrs[] = $node->getAttribute($attribute);
}
}
return $attrs;
}
// 向匹配节点追加内容
public function append($html) {
foreach ($this->nodes as $node) {
$fragment = $this->dom->createDocumentFragment();
$fragment->appendXML($html);
$node->appendChild($fragment);
}
return $this;
}
// 删除匹配的节点
public function remove() {
foreach ($this->nodes as $node) {
if ($node->parentNode) {
$node->parentNode->removeChild($node);
}
}
return $this;
}
// 输出完整的 HTML
public function saveHTML() {
return $this->dom->saveHTML();
}
}
3. 使用示例
假设我们有以下 HTML 内容:
$html = <<<HTML
<html>
<body>
<div class="container">
<p id="para1" class="text">Hello, World!</p>
<p id="para2" class="text">This is a test.</p>
<a href="https://example.com" title="Example Link">Click Me</a>
</div>
</body>
</html>
HTML;
$query = new Query($html);
// 示例 1: 查找所有 p 标签并获取它们的文本内容
$texts = $query->find('p')->text();
echo "All paragraph texts:n";
print_r($texts);
// 示例 2: 查找第一个 p 标签的文本内容
$firstText = $query->find('p')->firstText();
echo "First paragraph text: $firstTextn";
// 示例 3: 通过类名查找段落
$textsByClass = $query->find('.container .text')->text();
echo "Texts by class selector:n";
print_r($textsByClass);
// 示例 4: 通过 ID 查找段落
$textById = $query->find('#para1')->text();
echo "Text by ID selector:n";
print_r($textById);
// 示例 5: 修改第一个 p 标签的内容
$query->find('p#para1')->html('<strong>New Content</strong>');
echo "Modified HTML:n";
echo $query->saveHTML();
// 示例 6: 获取所有链接的 href 属性
$hrefs = $query->find('a')->attr('href');
echo "All href attributes:n";
print_r($hrefs);
// 示例 7: 删除第二个 p 标签
$query->find('p#para2')->remove();
echo "HTML after removing second paragraph:n";
echo $query->saveHTML();
4. 结果解释
示例 1: 查找所有 p
标签
All paragraph texts:
Array
(
[0] => Hello, World!
[1] => This is a test.
)
示例 2: 查找第一个 p
标签
First paragraph text: Hello, World!
示例 3: 通过类名 .container .text
查找
Texts by class selector:
Array
(
[0] => Hello, World!
[1] => This is a test.
)
示例 4: 通过 ID #para1
查找
Text by ID selector:
Array
(
[0] => Hello, World!
)
示例 5: 修改第一个段落内容
修改后:
<p id="para1" class="text"><strong>New Content</strong></p>
示例 6: 获取所有链接的 href
属性
All href attributes:
Array
(
[0] => https://example.com
)
示例 7: 删除第二个段落
删除后:
<div class="container">
<p id="para1" class="text"><strong>New Content</strong></p>
<a href="https://example.com" title="Example Link">Click Me</a>
</div>
5. 总结
通过引入 Symfony 的 CssSelector
组件,我们可以轻松地将 CSS 选择器转换为 XPath 表达式,从而实现类似 jQuery 的查询方式。这个改进让 Query
类更加灵活和易用,支持以下功能:
- CSS 选择器:如
p
、.class
、#id
、.container .text
。 - 文本提取:
text()
和firstText()
。 - HTML 操作:
html()
。 - 属性操作:
attr()
。 - 节点添加/删除:
append()
和remove()
。
这种封装方式极大地简化了 PHP 中的 DOM 操作!