futureplc / html-dom-document
用于处理HTML5文档的DOMDocument的替代品。
Requires
- php: ^8.3
- ext-dom: *
- symfony/css-selector: ^7.0
Requires (Dev)
- ext-libxml: *
- friendsofphp/php-cs-fixer: ^3.21.1
- phpunit/phpunit: ^11.3.1
- spatie/ray: ^1.28
This package is auto-updated.
Last update: 2024-09-02 06:28:19 UTC
README
HTMLDocument包的主要目的是作为一个替代品来替换PHP的核心DOMDocument
和相关DOM类。
⚠️ 如果您只需要爬取DOM而不需要就地操作,请考虑使用Symfony DOM Crawler组件。
虽然PHP内置的DOM相关类是解析XML的好方法,但当尝试解析现代HTML5标记时,它们会迅速崩溃。此包使操作更加直观,并处理了一些幕后的问题。
此包提供了一系列类,以向后兼容的方式替换DOM类,但具有更紧密的接口,并捆绑了额外的实用程序,以便轻松处理HTML。这些类将返回等效的HTML*
类的实例,而不是DOM*
类
DOMDocument
->HTMLDocument
DOMElement
->HTMLElement
DOMNode
->HTMLElement
DOMText
->HTMLText
DOMNodeList
->HTMLNodeList
DOMXPath
->HTMLXPath
安装
您可以通过Composer安装此包
composer require futureplc/html-dom-document
功能
合理的返回值
由于PHP使用虚假返回类型的传统,每次操作都需要检查联合类型,这真是令人烦恼。我们已经通过确保有合理的默认值来解决这个问题。
- 如果返回值期望
DOMNodeList
或false
,则在没有返回值的情况下返回一个空的DOMNodeList
- 如果返回值可以是
string
或false
,则失败时抛出异常或返回一个空字符串 - 不再区分
DOMNode
和DOMElement
;我们有一个单一的HTMLElement
类来处理这两种情况的组合
您会在整个接口中注意到这种理念 - 如果有合理的类型可以返回,我们将确保您得到它,而不是处理联合。
轻松创建HTML文档和元素
DOMDocument
通常具有简短、过时的接口,需要大量的设置和重复操作才能完成基本和常用任务,例如从纯HTML字符串创建DOMElement
类。
所有旧的DOMDocument
风格方法仍然有效,因此您可以将此包作为现有DOMDocument
实现的替代品。但是,我们已经添加了新的方法来创建HTML文档和元素,而无需通常用于某些操作的冗长性。
$dom = new HTMLDocument(); $dom->loadHTML($html); $dom = HTMLDocument::fromHTML($html); $dom = HTMLDocument::loadFromFile($filePath); $element = HTMLElement::fromNode($domNode); $element = HTMLElement::fromHTML($html); $element = $dom->createElement('p', 'This is a paragraph.'); $element = $dom->createElementFromNode($domNode); $element = $dom->createElementFromHTML('<p>This is a paragraph.</p>');
支持HTML5的附加行为
允许DOMDocument解析任何HTML字符串的大多数自定义行为来自一系列“中间件”类,它们在加载之前和再次作为纯HTML字符串发布之前操作HTML。
这些中间件执行各种操作,例如
- 如果没有
<!doctype>
,则假定HTML5行为,通过添加一个 - 忽略LibXML错误(因为LibXML会对某些HTML5标签提出异议,尽管它可以正确解析它们)
- 将
<template>
和<script>
标签作为字面量处理,以便它们的内容不会受到整个文档的影响
如果您使用HTMLDocument
类,这些功能将默认启用,但根据需要您可以禁用它们。
- 在加载HTML之前,不带任何参数调用
->withoutMiddleware()
将导致没有任何中间件应用,实际上只应用了额外的实用方法,而没有应用任何额外的HTML5支持。 - 使用中间件类的名称调用
->withoutMiddleware(MiddlewareName::class)
,将禁用该特定的中间件。
从DOMDocument
获取纯HTML字符串可能会有些棘手,如果您需要特定元素,我们添加了一些选项以简化操作。
$html = (string) $dom; // Cast the HTMLDocument to a string $html = $dom->saveHTML(); $html = (string) $element; // Cast the HTMLElement to a string $html = $element->saveHTML(); $html = $element->getInnerHTML(); // Gets the HTML of the element without the wrapping node $html = $element->getOuterHTML(); // Gets the HTML of the element with the wrapping node
检查HTML5
如果您需要知道是否正在处理HTML5文档,isHTML5()
方法将告诉您。
$dom->isHtml5(); // true
空元素
如果您正在使用HTML5,您可能想知道一个给定的节点是否是“空元素”,这意味着它不需要关闭标签。这可以通过isVoidElement()
方法进行检查。
$element->isVoidElement(); // true
通常在保存HTML时,DOMDocument
会将空元素输出为<example></example>
,但此包将它们输出为<example>
,即使是自定义元素,也保持它们原始的输入方式。
处理属性
HTMLElement
类有一系列方法来帮助您处理元素上的属性。
$element->getAttributes(); // Returns an array of all attributes $element->getAttribute('class'); // Returns the value of the class attribute $element->setAttribute('class', 'foo'); // Sets the class attribute to "foo" $element->addAttribute('class', 'foo'); // Adds the "foo" value as a space-separated value to the class attribute, appending it if the attribute already exists $element->removeAttribute('ref'); // Removes the ref attribute entirely $element->removeAttribute('ref', 'noreferrer'); // Removes the "noreferrer" value from the ref attribute if it exists - if the attribute is now empty, it will be removed entirely $element->toggleAttribute('checked'); // Toggles the "checked" attribute
由于我们经常在HTML中使用CSS类,因此也有一些方法可以帮助处理。
$element->getClassList(); // Returns an array of CSS classes $element->setClassList(['foo', 'bar']); // Sets the CSS classes $element->hasClass('foo'); // Returns true if the element has the class "foo" $element->addClass('baz'); // Adds the class "baz" $element->removeClass('bar'); // Removes the class "bar"
删除文档的部分
有一些实用的工具可以快速删除文档的特定部分。
$element->wihoutSelector('p'); // Removes all child `<p>` element $element->withoutComments(); // Removes all HTML comments
实用方法
有一些额外的实用方法可以帮助从PHP数组构建属性字符串。
Utility::attribute()
将单个键/值对转换为HTML属性,无论值是字符串、数组还是布尔值。布尔值可以用于有条件地添加属性。
Utility::attribute('class', ['foo', 'bar']); // class="foo bar" Utility::attribute('id', 'baz'); // id="baz" Utility::attribute('required', true); // disabled
Utility::attributes()
将更进一步,将键/值对数组转换为HTML属性字符串。
Utility::attributes([ 'class' => ['foo', 'bar'], 'id' => 'baz', 'required' => true, 'checked' => false, ]); // class="foo bar" id="baz" required
Utility::nodeMapRecursive()
允许对文档中的每个节点(包括所有子节点)运行回调。您可以使用此回调检查节点、修改节点、用另一个节点完全替换节点或从文档中删除节点。
此功能也适用于HTMLElement
和HTMLDocument
对象,通过mapRecursive
方法实现。
$dom = HTMLDocument::fromHTML('<p><span>foo</span></p>'); // Make sure every element has a class of "bar" $dom->mapRecursive(function ($node) { if ($node instanceof HTMLElement) { $node->setAttribute('class', 'bar'); } }); // <p class="bar"><span class="bar">foo</span></p>
Utility::countRootNodes()
将告诉您文档中有多少根节点。
Utility::countRootNodes('<p>foo</p>'); // 1 Utility::countRootNodes('<p>foo</p><p>bar</p>'); // 2
如果您正在处理包含多个根节点的源HTML,您可以使用Utility::wrap($html)
和Utility::unwrap($html)
方法确保有一个根节点或删除根节点。
处理CSS类
HTMLElement
类有几种方法可以帮助您处理CSS类。
$element->setClassList(['foo', 'bar']); $element->getClassList(); // ['foo', 'bar'] $element->hasClass('foo'); // true $element->addClass('foo'); // ['foo', 'bar', 'baz'] $element->removeClass('baz'); // ['foo', 'bar']
切换布尔属性
如果您需要切换某些布尔属性的开/关,则可以使用toggleAttribute()
方法。
$element = HTMLElement::fromString('<input type="checkbox">'); $element->toggleAttribute('checked'); // <input type="checkbox" checked> $element->toggleAttribute('checked'); // <input type="checkbox">
基于CSS选择器和XPath进行查询
大多数处理HTML的人都知道如何使用大多数CSS选择器,但许多人从未接触过XPath。我们已向HTMLDocument
和HTMLElement
类添加了方便的querySelector()
和querySelectorAll()
方法,允许您直接使用CSS选择器获取所需元素,这是由Symfony CSS Selector包提供的。
$dom->querySelector('head > title'); // Returns the first `<title>` element $dom->querySelectorAll('.foo'); // Returns all elements with the class `foo`
如果您仍然需要使用XPath,则在HTMLDocument
和HTMLElement
类上都有方便的xpath()
方法。
$dom->xpath('//a'); // Returns all `<a>` elements
处理文本节点
如果要在文本中更改某些内容为另一个节点,处理文本节点可能会很棘手。在HTMLText
上的replaceTextWithNode()
方法允许您做到这一点。
如果您使用Utility::nodeMapRecursive()
函数,这将特别有用,该函数将遍历文本节点。
$textNode->replaceTextWithNode('example', HTMLElement::fromHTML('<strong>example</strong>'));
其他说明
HTMLDocument
还有一些比 DOMDocument
更多的好处
- 具有 XML 风格命名空间的标签会被保留,而
DOMDocument
通常只会保留标签名的最后部分。这在处理边侧包含(edge-side-includes)等标准以及包含如<esi:include src="..." />
的标记时很有用 - 以
@
开头的属性会被保留,而DOMDocument
通常会移除它们。当处理包含 Alpine.js 或 Vue.js 标记的 HTML 时(例如<button @click="doSomething">
)非常有用 - 输入 HTML 中的任何空标签也将被输出为空标签
缺点
由于所有额外的检查和数据类型转换,这个包比本机的 DOMDocument
类稍微慢一些。然而,在大多数情况下,这种差异微不足道,额外功能和易用性的好处远远超过了性能损失,除非您一次处理数百万个大型 HTML 文档。
测试
composer test
变更日志
请参阅 变更日志 了解最近有哪些变化。
贡献
请参阅 贡献指南 了解详细信息。
安全漏洞
请查看 我们的安全策略 了解如何报告安全漏洞。
致谢
许可证
MIT 许可证(MIT)。请参阅 许可证文件 了解更多信息。