Compare commits

...
20 Commits
Author SHA1 Message Date
Kevin DecherfandGitHub a93488d727 Merge pull request #113 from Kdecherf/fix/loadHtml
Change visibility of Readability::loadHtml() and prevent DOM reset in init()
2026-07-11 21:40:14 +02:00
Kevin Decherf ac24ef54a4 Fix php-cs-fixer
Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2026-07-11 21:37:25 +02:00
Kevin Decherf 76547fef78 Prevent Readability::init() from resetting DOM if already set
Letting init() resetting the DOM may lead to unexpected behaviors in
libraries like Graby.

Related to https://github.com/j0k3r/graby/pull/392

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2026-07-11 21:37:25 +02:00
Kevin Decherf 228bc7ee1d Change visibility of Readability::loadHtml() to public
This change is required for usecases where we want to load the DOM
without going through the whole init() process

Related to https://github.com/j0k3r/graby/pull/392

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2026-07-11 21:37:25 +02:00
Jérémy BenoistandGitHub 35b87585e7 Merge pull request #128 from j0k3r/dependabot/composer/phpstan/phpstan-phpunit-2.0.18
Update phpstan/phpstan-phpunit requirement from 2.0.16 to 2.0.18
2026-07-10 09:45:01 +02:00
dependabot[bot]andGitHub 4230b2d7ca Update phpstan/phpstan-phpunit requirement from 2.0.16 to 2.0.18
Updates the requirements on [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) to permit the latest version.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/2.0.16...2.0.18)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-phpunit
  dependency-version: 2.0.18
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 07:26:54 +00:00
Jérémy BenoistandGitHub 85be584f94 Merge pull request #130 from j0k3r/dependabot/composer/friendsofphp/php-cs-fixer-3.95.12
Update friendsofphp/php-cs-fixer requirement from 3.95.11 to 3.95.12
2026-07-10 09:25:25 +02:00
Jérémy BenoistandGitHub 03a960daf0 Merge pull request #129 from j0k3r/dependabot/composer/rector/rector-2.5.5
Update rector/rector requirement from 2.5.2 to 2.5.5
2026-07-10 09:25:14 +02:00
Jérémy BenoistandGitHub 6f62bcf662 Merge pull request #127 from j0k3r/dependabot/composer/phpstan/phpstan-2.2.5
Update phpstan/phpstan requirement from 2.2.3 to 2.2.5
2026-07-10 09:24:55 +02:00
dependabot[bot]andGitHub 3e3114a492 Update friendsofphp/php-cs-fixer requirement from 3.95.11 to 3.95.12
Updates the requirements on [friendsofphp/php-cs-fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) to permit the latest version.
- [Release notes](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases)
- [Changelog](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/compare/v3.95.11...v3.95.12)

---
updated-dependencies:
- dependency-name: friendsofphp/php-cs-fixer
  dependency-version: 3.95.12
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 05:35:20 +00:00
dependabot[bot]andGitHub f98247ed14 Update rector/rector requirement from 2.5.2 to 2.5.5
Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version.
- [Release notes](https://github.com/rectorphp/rector/releases)
- [Commits](https://github.com/rectorphp/rector/compare/2.5.2...2.5.5)

---
updated-dependencies:
- dependency-name: rector/rector
  dependency-version: 2.5.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 05:34:54 +00:00
dependabot[bot]andGitHub 5a6e525ff5 Update phpstan/phpstan requirement from 2.2.3 to 2.2.5
Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) to permit the latest version.
- [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-version: 2.2.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 05:34:41 +00:00
Jérémy BenoistandGitHub 0de328760a Merge pull request #126 from j0k3r/dependabot/composer/phpstan/phpstan-2.2.3 2026-07-03 07:56:04 +02:00
dependabot[bot]andGitHub 5aa9da6843 Update phpstan/phpstan requirement from 2.2.2 to 2.2.3
Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) to permit the latest version.
- [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-version: 2.2.3
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-03 05:34:19 +00:00
Jérémy BenoistandGitHub 1bb7eec83c Merge pull request #125 from j0k3r/dependabot/composer/rector/rector-2.5.2
Update rector/rector requirement from 2.4.6 to 2.5.2
2026-06-26 08:58:17 +02:00
Jérémy BenoistandGitHub 049bd07074 Merge pull request #124 from j0k3r/dependabot/composer/friendsofphp/php-cs-fixer-3.95.11
Update friendsofphp/php-cs-fixer requirement from 3.95.8 to 3.95.11
2026-06-26 08:58:05 +02:00
dependabot[bot]andGitHub ca1b105f40 Update rector/rector requirement from 2.4.6 to 2.5.2
Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version.
- [Release notes](https://github.com/rectorphp/rector/releases)
- [Commits](https://github.com/rectorphp/rector/compare/2.4.6...2.5.2)

---
updated-dependencies:
- dependency-name: rector/rector
  dependency-version: 2.5.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-26 05:34:17 +00:00
dependabot[bot]andGitHub 5ad159ebb1 Update friendsofphp/php-cs-fixer requirement from 3.95.8 to 3.95.11
Updates the requirements on [friendsofphp/php-cs-fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) to permit the latest version.
- [Release notes](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases)
- [Changelog](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/compare/v3.95.8...v3.95.11)

---
updated-dependencies:
- dependency-name: friendsofphp/php-cs-fixer
  dependency-version: 3.95.11
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-26 05:34:09 +00:00
Kevin DecherfandGitHub 1312ffbced Merge pull request #123 from Kdecherf/fix/singletag-type-error
Fix a TypeError when tidy is disabled
2026-06-23 14:12:03 +02:00
Kevin Decherf 266e36c187 Fix a TypeError when tidy is disabled
A logic tries to replace a node with its child node if it is the only
element of the node. However in some cases, especially when tidy is
disabled, children items other than DOMElement may survive (e.g.
DOMComment), leading to a TypeError.

Fixes #122

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2026-06-22 15:51:06 +02:00
3 changed files with 95 additions and 76 deletions
+4 -4
View File
@@ -30,12 +30,12 @@
"masterminds/html5": "^2.7" "masterminds/html5": "^2.7"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "3.95.8", "friendsofphp/php-cs-fixer": "3.95.12",
"monolog/monolog": "^1.24|^2.1", "monolog/monolog": "^1.24|^2.1",
"symfony/phpunit-bridge": "^4.4|^5.3|^6.0|^7.0", "symfony/phpunit-bridge": "^4.4|^5.3|^6.0|^7.0",
"phpstan/phpstan": "2.2.2", "phpstan/phpstan": "2.2.5",
"phpstan/phpstan-phpunit": "2.0.16", "phpstan/phpstan-phpunit": "2.0.18",
"rector/rector": "2.4.6" "rector/rector": "2.5.5"
}, },
"suggest": { "suggest": {
"ext-tidy": "Used to clean up given HTML and to avoid problems with bad HTML structure." "ext-tidy": "Used to clean up given HTML and to avoid problems with bad HTML structure."
+78 -72
View File
@@ -213,7 +213,9 @@ class Readability implements LoggerAwareInterface
*/ */
public function init(): bool public function init(): bool
{ {
$this->loadHtml(); if (!isset($this->dom)) {
$this->loadHtml();
}
if (!isset($this->dom->documentElement)) { if (!isset($this->dom->documentElement)) {
return false; return false;
@@ -751,6 +753,76 @@ class Readability implements LoggerAwareInterface
$this->flags &= ~$flag; $this->flags &= ~$flag;
} }
/**
* Load HTML in a DOMDocument.
* Apply Pre filters
* Cleanup HTML using Tidy (or not).
*/
public function loadHtml(): void
{
$this->original_html = $this->html;
$this->logger->debug('Parsing URL: ' . $this->url);
if ($this->url) {
$host = parse_url($this->url, \PHP_URL_HOST);
if (null !== $host) {
$this->domainRegExp = '/' . strtr((string) preg_replace('/www\d*\./', '', $host), ['.' => '\.']) . '/';
}
}
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
mb_regex_encoding('UTF-8');
// HACK: dirty cleanup to replace some stuff; shouldn't use regexps with HTML but well...
if (!$this->flagIsActive(self::FLAG_DISABLE_PREFILTER)) {
foreach ($this->pre_filters as $search => $replace) {
$this->html = preg_replace($search, $replace, $this->html);
}
unset($search, $replace);
}
if ('' === trim($this->html)) {
$this->html = '<html></html>';
}
/*
* Use tidy (if it exists).
* This fixes problems with some sites which would otherwise trouble DOMDocument's HTML parsing.
* Although sometimes it makes matters worse, which is why there is an option to disable it.
*/
if ($this->useTidy) {
$this->logger->debug('Tidying document');
$tidy = tidy_repair_string($this->html, $this->tidy_config, 'UTF8');
if (false !== $tidy && $this->html !== $tidy) {
$this->tidied = true;
$this->html = $tidy;
$this->html = preg_replace('/[\r\n]+/is', "\n", $this->html);
}
unset($tidy);
}
$this->html = self::entitizeNonAscii((string) $this->html);
if ('html5lib' === $this->parser || 'html5' === $this->parser) {
$this->dom = (new HTML5())->loadHTML($this->html);
}
if ('libxml' === $this->parser) {
libxml_use_internal_errors(true);
$this->dom = new \DOMDocument();
$this->dom->preserveWhiteSpace = false;
$this->dom->loadHTML($this->html, \LIBXML_NOBLANKS | \LIBXML_COMPACT | \LIBXML_NOERROR);
libxml_use_internal_errors(false);
}
$this->dom->registerNodeClass(\DOMElement::class, JSLikeHTMLElement::class);
}
/** /**
* Get the article title as an H1. * Get the article title as an H1.
* *
@@ -1003,7 +1075,11 @@ class Readability implements LoggerAwareInterface
} }
if ($this->hasSingleTagInsideElement($node, 'p') && $this->getLinkDensity($node) < 0.25) { if ($this->hasSingleTagInsideElement($node, 'p') && $this->getLinkDensity($node) < 0.25) {
$newNode = $node->childNodes->item(0); // In some cases when tidy is disabled the first item may not be a DOMElement so we apply a filter
$newNode = array_values(array_filter(
iterator_to_array($node->childNodes),
static fn ($childNode) => $childNode instanceof \DOMElement
))[0];
$node->parentNode->replaceChild($newNode, $node); $node->parentNode->replaceChild($newNode, $node);
$nodesToScore[] = $newNode; $nodesToScore[] = $newNode;
} }
@@ -1371,76 +1447,6 @@ class Readability implements LoggerAwareInterface
} }
} }
/**
* Load HTML in a DOMDocument.
* Apply Pre filters
* Cleanup HTML using Tidy (or not).
*/
private function loadHtml(): void
{
$this->original_html = $this->html;
$this->logger->debug('Parsing URL: ' . $this->url);
if ($this->url) {
$host = parse_url($this->url, \PHP_URL_HOST);
if (null !== $host) {
$this->domainRegExp = '/' . strtr((string) preg_replace('/www\d*\./', '', $host), ['.' => '\.']) . '/';
}
}
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
mb_regex_encoding('UTF-8');
// HACK: dirty cleanup to replace some stuff; shouldn't use regexps with HTML but well...
if (!$this->flagIsActive(self::FLAG_DISABLE_PREFILTER)) {
foreach ($this->pre_filters as $search => $replace) {
$this->html = preg_replace($search, $replace, $this->html);
}
unset($search, $replace);
}
if ('' === trim($this->html)) {
$this->html = '<html></html>';
}
/*
* Use tidy (if it exists).
* This fixes problems with some sites which would otherwise trouble DOMDocument's HTML parsing.
* Although sometimes it makes matters worse, which is why there is an option to disable it.
*/
if ($this->useTidy) {
$this->logger->debug('Tidying document');
$tidy = tidy_repair_string($this->html, $this->tidy_config, 'UTF8');
if (false !== $tidy && $this->html !== $tidy) {
$this->tidied = true;
$this->html = $tidy;
$this->html = preg_replace('/[\r\n]+/is', "\n", $this->html);
}
unset($tidy);
}
$this->html = self::entitizeNonAscii((string) $this->html);
if ('html5lib' === $this->parser || 'html5' === $this->parser) {
$this->dom = (new HTML5())->loadHTML($this->html);
}
if ('libxml' === $this->parser) {
libxml_use_internal_errors(true);
$this->dom = new \DOMDocument();
$this->dom->preserveWhiteSpace = false;
$this->dom->loadHTML($this->html, \LIBXML_NOBLANKS | \LIBXML_COMPACT | \LIBXML_NOERROR);
libxml_use_internal_errors(false);
}
$this->dom->registerNodeClass(\DOMElement::class, JSLikeHTMLElement::class);
}
private function getAncestors(\DOMElement $node, int $maxDepth = 0): array private function getAncestors(\DOMElement $node, int $maxDepth = 0): array
{ {
$ancestors = []; $ancestors = [];
+13
View File
@@ -507,6 +507,19 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
$this->assertSame($expected, $method->invoke($readability, $node, $tag)); $this->assertSame($expected, $method->invoke($readability, $node, $tag));
} }
public function testDivToPElementWithComment(): void
{
$text = str_repeat('Padded real article text to reach decent length for content scoring threshold here. ', 8);
$html = '<div><!-- comment --><code>some code snippet</code> ' . $text . '</div>';
$readability = $this->getReadability($html, 'http://0.0.0.0', 'libxml', false);
$res = $readability->init();
$this->assertTrue($res);
$this->assertStringContainsString('some code snippet', $readability->getContent()->getInnerHtml());
$this->assertStringContainsString('Padded real article text', $readability->getContent()->getInnerHtml());
}
public function testKeepFootnotes(): void public function testKeepFootnotes(): void
{ {
// from https://www.schreibdichte.de/blog/feed-aggregator-und-spaeter-lesen-dienst-im-team // from https://www.schreibdichte.de/blog/feed-aggregator-und-spaeter-lesen-dienst-im-team