mirror of
https://github.com/j0k3r/php-readability.git
synced 2026-09-27 14:36:23 +00:00
Compare commits
58
Commits
2.0.0
...
a8b08d8cb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8b08d8cb2 | ||
|
|
7db754debe | ||
|
|
18bfe842f8 | ||
|
|
63a0db9109 | ||
|
|
63d6ef1a47 | ||
|
|
116b6c839a | ||
|
|
a35f00ebb3 | ||
|
|
0f9f971556 | ||
|
|
fd1eaf61a7 | ||
|
|
3e40d78a67 | ||
|
|
03533f5e4f | ||
|
|
009b4ab9b8 | ||
|
|
f9e73fb49a | ||
|
|
3042990efc | ||
|
|
8b89d70b1a | ||
|
|
3e9b15db46 | ||
|
|
7413a38ff0 | ||
|
|
a18cd0f2a9 | ||
|
|
efbbc86df9 | ||
|
|
541fab34a0 | ||
|
|
90869d877e | ||
|
|
c7208f6ad2 | ||
|
|
4258559b8a | ||
|
|
1ac761d708 | ||
|
|
d3053fbce4 | ||
|
|
4c929754e9 | ||
|
|
1d7cdf3a12 | ||
|
|
f825dcf55a | ||
|
|
9a9373de4b | ||
|
|
d454c3a462 | ||
|
|
8b1ef07401 | ||
|
|
5885dbbe78 | ||
|
|
6947999782 | ||
|
|
da755013aa | ||
|
|
5b9551d1e3 | ||
|
|
c7b10dcc45 | ||
|
|
80adfe870b | ||
|
|
cb6b6ac577 | ||
|
|
677f3f096e | ||
|
|
29122763db | ||
|
|
89d3b74259 | ||
|
|
e792644fe8 | ||
|
|
648d8c605b | ||
|
|
f28191a728 | ||
|
|
2103853a1b | ||
|
|
7f4c6cfcbd | ||
|
|
38870cdff1 | ||
|
|
9bdd3b6b2e | ||
|
|
f14428e4c0 | ||
|
|
23f824a1ce | ||
|
|
2a57124528 | ||
|
|
0975574bdb | ||
|
|
9ed89bde92 | ||
|
|
2c6c6d5987 | ||
|
|
c5407ec07c | ||
|
|
7cd8476d38 | ||
|
|
82083c872b | ||
|
|
6689f19956 |
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "composer"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
@@ -11,7 +11,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
coding-standards:
|
coding-standards:
|
||||||
name: "CS Fixer & PHPStan"
|
name: "CS Fixer & PHPStan"
|
||||||
runs-on: "ubuntu-20.04"
|
runs-on: "ubuntu-22.04"
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: "actions/checkout@v2"
|
uses: "actions/checkout@v4"
|
||||||
|
|
||||||
- name: "Install PHP"
|
- name: "Install PHP"
|
||||||
uses: "shivammathur/setup-php@v2"
|
uses: "shivammathur/setup-php@v2"
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: "Install dependencies with Composer"
|
- name: "Install dependencies with Composer"
|
||||||
uses: "ramsey/composer-install@v1"
|
uses: "ramsey/composer-install@v3"
|
||||||
with:
|
with:
|
||||||
composer-options: "--optimize-autoloader --prefer-dist"
|
composer-options: "--optimize-autoloader --prefer-dist"
|
||||||
|
|
||||||
|
|||||||
@@ -14,20 +14,22 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
phpunit:
|
phpunit:
|
||||||
name: "PHPUnit (PHP ${{ matrix.php }})"
|
name: "PHPUnit (PHP ${{ matrix.php }})"
|
||||||
runs-on: "ubuntu-20.04"
|
runs-on: "ubuntu-22.04"
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php:
|
php:
|
||||||
- "7.2"
|
|
||||||
- "7.3"
|
|
||||||
- "7.4"
|
- "7.4"
|
||||||
- "8.0"
|
- "8.0"
|
||||||
- "8.1"
|
- "8.1"
|
||||||
|
- "8.2"
|
||||||
|
- "8.3"
|
||||||
|
- "8.4"
|
||||||
|
- "8.5"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: "actions/checkout@v2"
|
uses: "actions/checkout@v4"
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ jobs:
|
|||||||
run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update"
|
run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update"
|
||||||
|
|
||||||
- name: "Install dependencies with Composer"
|
- name: "Install dependencies with Composer"
|
||||||
uses: "ramsey/composer-install@v1"
|
uses: "ramsey/composer-install@v3"
|
||||||
with:
|
with:
|
||||||
composer-options: "--optimize-autoloader --prefer-dist"
|
composer-options: "--optimize-autoloader --prefer-dist"
|
||||||
|
|
||||||
@@ -58,16 +60,16 @@ jobs:
|
|||||||
|
|
||||||
phpunit-coverage:
|
phpunit-coverage:
|
||||||
name: "PHPUnit coverage (PHP ${{ matrix.php }})"
|
name: "PHPUnit coverage (PHP ${{ matrix.php }})"
|
||||||
runs-on: "ubuntu-20.04"
|
runs-on: "ubuntu-22.04"
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php:
|
php:
|
||||||
- "7.4"
|
- "8.0"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: "actions/checkout@v2"
|
uses: "actions/checkout@v4"
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ jobs:
|
|||||||
run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update"
|
run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update"
|
||||||
|
|
||||||
- name: "Install dependencies with Composer"
|
- name: "Install dependencies with Composer"
|
||||||
uses: "ramsey/composer-install@v1"
|
uses: "ramsey/composer-install@v3"
|
||||||
with:
|
with:
|
||||||
composer-options: "--optimize-autoloader --prefer-dist"
|
composer-options: "--optimize-autoloader --prefer-dist"
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ jobs:
|
|||||||
run: "php vendor/bin/simple-phpunit -v --coverage-clover build/logs/clover.xml"
|
run: "php vendor/bin/simple-phpunit -v --coverage-clover build/logs/clover.xml"
|
||||||
|
|
||||||
- name: "Retrieve Coveralls phar"
|
- name: "Retrieve Coveralls phar"
|
||||||
run: "wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.2/php-coveralls.phar"
|
run: "wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.7.0/php-coveralls.phar"
|
||||||
|
|
||||||
- name: "Enable Coveralls phar"
|
- name: "Enable Coveralls phar"
|
||||||
run: "chmod +x php-coveralls.phar"
|
run: "chmod +x php-coveralls.phar"
|
||||||
@@ -109,16 +111,16 @@ jobs:
|
|||||||
|
|
||||||
phpunit-lowest:
|
phpunit-lowest:
|
||||||
name: "PHPUnit lowest deps (PHP ${{ matrix.php }})"
|
name: "PHPUnit lowest deps (PHP ${{ matrix.php }})"
|
||||||
runs-on: "ubuntu-20.04"
|
runs-on: "ubuntu-22.04"
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php:
|
php:
|
||||||
- "7.2"
|
- "7.4"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: "actions/checkout@v2"
|
uses: "actions/checkout@v4"
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
@@ -137,7 +139,7 @@ jobs:
|
|||||||
run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update"
|
run: "composer remove friendsofphp/php-cs-fixer --dev --no-progress --no-update"
|
||||||
|
|
||||||
- name: "Install dependencies with Composer"
|
- name: "Install dependencies with Composer"
|
||||||
uses: "ramsey/composer-install@v1"
|
uses: "ramsey/composer-install@v3"
|
||||||
with:
|
with:
|
||||||
composer-options: "--optimize-autoloader --prefer-dist"
|
composer-options: "--optimize-autoloader --prefer-dist"
|
||||||
dependency-versions: "lowest"
|
dependency-versions: "lowest"
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ composer.lock
|
|||||||
.php_cs.cache
|
.php_cs.cache
|
||||||
.php-cs-fixer.cache
|
.php-cs-fixer.cache
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
phpstan.neon
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ return (new PhpCsFixer\Config())
|
|||||||
'strict_comparison' => true,
|
'strict_comparison' => true,
|
||||||
'strict_param' => true,
|
'strict_param' => true,
|
||||||
'concat_space' => ['spacing' => 'one'],
|
'concat_space' => ['spacing' => 'one'],
|
||||||
|
// Pulled in by @Symfony:risky but we still support PHP 7.4
|
||||||
|
'modernize_strpos' => false,
|
||||||
|
// Pulled in by @Symfony, we cannot add property types until we bump PHP to ≥ 7.4
|
||||||
|
'no_null_property_initialization' => false,
|
||||||
|
// @Symfony:risky started removing them but we will probably want to go the opposite direction.
|
||||||
|
'declare_strict_types' => false,
|
||||||
])
|
])
|
||||||
->setFinder($finder)
|
->setFinder($finder)
|
||||||
;
|
;
|
||||||
|
|||||||
+15
-11
@@ -24,18 +24,18 @@
|
|||||||
"role": "Developer (original JS version)"
|
"role": "Developer (original JS version)"
|
||||||
}],
|
}],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.2.0",
|
"php": ">=7.4.0",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"psr/log": "^1.0",
|
"psr/log": "^1.0.1 || ^2.0 || ^3.0",
|
||||||
"masterminds/html5": "^2.7"
|
"masterminds/html5": "^2.7"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"friendsofphp/php-cs-fixer": "^3.0",
|
"friendsofphp/php-cs-fixer": "3.95.8",
|
||||||
"monolog/monolog": "^1.24|^2.1",
|
"monolog/monolog": "^1.24|^2.1",
|
||||||
"symfony/phpunit-bridge": "^4.4|^5.3|^6.0",
|
"symfony/phpunit-bridge": "^4.4|^5.3|^6.0|^7.0",
|
||||||
"phpstan/phpstan": "^1.3",
|
"phpstan/phpstan": "2.2.2",
|
||||||
"phpstan/phpstan-phpunit": "^1.0",
|
"phpstan/phpstan-phpunit": "2.0.16",
|
||||||
"rector/rector": "^0.12.15"
|
"rector/rector": "2.4.6"
|
||||||
},
|
},
|
||||||
"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."
|
||||||
@@ -46,9 +46,13 @@
|
|||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": { "Tests\\Readability\\": "tests/" }
|
"psr-4": { "Tests\\Readability\\": "tests/" }
|
||||||
},
|
},
|
||||||
"config":{
|
"config": {
|
||||||
"platform": {
|
"lock": false
|
||||||
"php": "7.2.34"
|
},
|
||||||
}
|
"scripts": {
|
||||||
|
"fix": "php-cs-fixer fix --verbose --diff",
|
||||||
|
"phpstan": "phpstan analyze --memory-limit 512M",
|
||||||
|
"rector": "rector process",
|
||||||
|
"test": "simple-phpunit -v"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ parameters:
|
|||||||
|
|
||||||
# https://github.com/phpstan/phpstan/issues/694#issuecomment-350724288
|
# https://github.com/phpstan/phpstan/issues/694#issuecomment-350724288
|
||||||
bootstrapFiles:
|
bootstrapFiles:
|
||||||
- vendor/bin/.phpunit/phpunit-8.5-0/vendor/autoload.php
|
- vendor/bin/.phpunit/phpunit/vendor/autoload.php
|
||||||
|
|
||||||
checkMissingIterableValueType: false
|
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
- vendor/phpstan/phpstan-phpunit/extension.neon
|
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||||
+9
-23
@@ -2,31 +2,17 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Rector\Core\Configuration\Option;
|
use Rector\Config\RectorConfig;
|
||||||
use Rector\Core\ValueObject\PhpVersion;
|
|
||||||
use Rector\Set\ValueObject\LevelSetList;
|
use Rector\Set\ValueObject\LevelSetList;
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
return RectorConfig::configure()
|
||||||
$parameters = $containerConfigurator->parameters();
|
->withPaths([
|
||||||
|
|
||||||
// paths to refactor; solid alternative to CLI arguments
|
|
||||||
$parameters->set(Option::PATHS, [
|
|
||||||
__DIR__ . '/src',
|
__DIR__ . '/src',
|
||||||
__DIR__ . '/tests',
|
__DIR__ . '/tests',
|
||||||
]);
|
])
|
||||||
|
->withBootstrapFiles([
|
||||||
// Path to phpstan with extensions, that PHPSTan in Rector uses to determine types
|
__DIR__ . '/vendor/bin/.phpunit/phpunit/vendor/autoload.php',
|
||||||
$parameters->set(Option::PHPSTAN_FOR_RECTOR_PATH, __DIR__ . '/phpstan.neon');
|
|
||||||
|
|
||||||
$parameters->set(Option::BOOTSTRAP_FILES, [
|
|
||||||
__DIR__ . '/vendor/bin/.phpunit/phpunit-8.5-0/vendor/autoload.php',
|
|
||||||
__DIR__ . '/vendor/autoload.php',
|
__DIR__ . '/vendor/autoload.php',
|
||||||
]);
|
])
|
||||||
|
->withSets([LevelSetList::UP_TO_PHP_74])
|
||||||
// Define what rule sets will be applied
|
;
|
||||||
$containerConfigurator->import(LevelSetList::UP_TO_PHP_72);
|
|
||||||
|
|
||||||
// is your PHP version different from the one your refactor to?
|
|
||||||
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_72);
|
|
||||||
};
|
|
||||||
|
|||||||
+13
-12
@@ -39,11 +39,11 @@ class JSLikeHTMLElement extends \DOMElement
|
|||||||
/**
|
/**
|
||||||
* Used for setting innerHTML like it's done in JavaScript:.
|
* Used for setting innerHTML like it's done in JavaScript:.
|
||||||
*
|
*
|
||||||
* @code
|
* ```php
|
||||||
* $div->innerHTML = '<h2>Chapter 2</h2><p>The story begins...</p>';
|
* $div->innerHTML = '<h2>Chapter 2</h2><p>The story begins...</p>';
|
||||||
* @endcode
|
* ```
|
||||||
*/
|
*/
|
||||||
public function __set($name, $value)
|
public function __set(string $name, string $value): void
|
||||||
{
|
{
|
||||||
if ('innerHTML' !== $name) {
|
if ('innerHTML' !== $name) {
|
||||||
$trace = debug_backtrace();
|
$trace = debug_backtrace();
|
||||||
@@ -79,14 +79,13 @@ class JSLikeHTMLElement extends \DOMElement
|
|||||||
} else {
|
} else {
|
||||||
// $value is probably ill-formed
|
// $value is probably ill-formed
|
||||||
$f = new \DOMDocument();
|
$f = new \DOMDocument();
|
||||||
$value = mb_convert_encoding($value, 'HTML-ENTITIES', 'UTF-8');
|
|
||||||
|
|
||||||
// Using <htmlfragment> will generate a warning, but so will bad HTML
|
// Using <htmlfragment> will generate a warning, but so will bad HTML
|
||||||
// (and by this point, bad HTML is what we've got).
|
// (and by this point, bad HTML is what we've got).
|
||||||
// We use it (and suppress the warning) because an HTML fragment will
|
// We use it (and suppress the warning) because an HTML fragment will
|
||||||
// be wrapped around <html><body> tags which we don't really want to keep.
|
// be wrapped around <html><body> tags which we don't really want to keep.
|
||||||
// Note: despite the warning, if loadHTML succeeds it will return true.
|
// Note: despite the warning, if loadHTML succeeds it will return true.
|
||||||
$result = $f->loadHTML('<htmlfragment>' . $value . '</htmlfragment>');
|
$result = $f->loadHTML('<meta charset="utf-8"><htmlfragment>' . $value . '</htmlfragment>');
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
$import = $f->getElementsByTagName('htmlfragment')->item(0);
|
$import = $f->getElementsByTagName('htmlfragment')->item(0);
|
||||||
@@ -105,11 +104,11 @@ class JSLikeHTMLElement extends \DOMElement
|
|||||||
/**
|
/**
|
||||||
* Used for getting innerHTML like it's done in JavaScript:.
|
* Used for getting innerHTML like it's done in JavaScript:.
|
||||||
*
|
*
|
||||||
* @code
|
* ```php
|
||||||
* $string = $div->innerHTML;
|
* $string = $div->innerHTML;
|
||||||
* @endcode
|
* ```
|
||||||
*/
|
*/
|
||||||
public function __get($name)
|
public function __get(string $name): string
|
||||||
{
|
{
|
||||||
if ('innerHTML' === $name) {
|
if ('innerHTML' === $name) {
|
||||||
$inner = '';
|
$inner = '';
|
||||||
@@ -125,20 +124,22 @@ class JSLikeHTMLElement extends \DOMElement
|
|||||||
|
|
||||||
$trace = debug_backtrace();
|
$trace = debug_backtrace();
|
||||||
trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], \E_USER_NOTICE);
|
trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], \E_USER_NOTICE);
|
||||||
|
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString()
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return '[' . $this->tagName . ']';
|
return '[' . $this->tagName . ']';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInnerHtml()
|
public function getInnerHtml(): string
|
||||||
{
|
{
|
||||||
return $this->__get('innerHTML');
|
return $this->__get('innerHTML');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setInnerHtml($value)
|
public function setInnerHtml(string $value): void
|
||||||
{
|
{
|
||||||
return $this->__set('innerHTML', $value);
|
$this->__set('innerHTML', $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+128
-92
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace Readability;
|
namespace Readability;
|
||||||
|
|
||||||
use DOMElement;
|
|
||||||
use Masterminds\HTML5;
|
use Masterminds\HTML5;
|
||||||
use Psr\Log\LoggerAwareInterface;
|
use Psr\Log\LoggerAwareInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
@@ -89,7 +88,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
'enclose-text' => true,
|
'enclose-text' => true,
|
||||||
'merge-divs' => true,
|
'merge-divs' => true,
|
||||||
// 'merge-spans' => true,
|
// 'merge-spans' => true,
|
||||||
'input-encoding' => '????',
|
'input-encoding' => 'utf8',
|
||||||
'output-encoding' => 'utf8',
|
'output-encoding' => 'utf8',
|
||||||
'hide-comments' => true,
|
'hide-comments' => true,
|
||||||
];
|
];
|
||||||
@@ -108,10 +107,6 @@ class Readability implements LoggerAwareInterface
|
|||||||
protected $useTidy;
|
protected $useTidy;
|
||||||
// raw HTML filters
|
// raw HTML filters
|
||||||
protected $pre_filters = [
|
protected $pre_filters = [
|
||||||
// remove obvious scripts
|
|
||||||
'!<script[^>]*>(.*?)</script>!is' => '',
|
|
||||||
// remove obvious styles
|
|
||||||
'!<style[^>]*>(.*?)</style>!is' => '',
|
|
||||||
// remove spans as we redefine styles and they're probably special-styled
|
// remove spans as we redefine styles and they're probably special-styled
|
||||||
'!</?span[^>]*>!is' => '',
|
'!</?span[^>]*>!is' => '',
|
||||||
// HACK: firewall-filtered content
|
// HACK: firewall-filtered content
|
||||||
@@ -119,7 +114,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
// HACK: replace linebreaks plus br's with p's
|
// HACK: replace linebreaks plus br's with p's
|
||||||
'!(<br[^>]*>[ \r\n\s]*){2,}!i' => '</p><p>',
|
'!(<br[^>]*>[ \r\n\s]*){2,}!i' => '</p><p>',
|
||||||
// replace noscripts
|
// replace noscripts
|
||||||
//'!</?noscript>!is' => '',
|
// '!</?noscript>!is' => '',
|
||||||
// replace fonts to spans
|
// replace fonts to spans
|
||||||
'!<(/?)font[^>]*>!is' => '<\\1span>',
|
'!<(/?)font[^>]*>!is' => '<\\1span>',
|
||||||
];
|
];
|
||||||
@@ -130,8 +125,8 @@ class Readability implements LoggerAwareInterface
|
|||||||
// replace empty tags that break layouts
|
// replace empty tags that break layouts
|
||||||
'!<(?:a|div|p|figure)[^>]+/>!is' => '',
|
'!<(?:a|div|p|figure)[^>]+/>!is' => '',
|
||||||
// remove all attributes on text tags
|
// remove all attributes on text tags
|
||||||
//'!<(\s*/?\s*(?:blockquote|br|hr|code|div|article|span|footer|aside|p|pre|dl|li|ul|ol)) [^>]+>!is' => "<\\1>",
|
// '!<(\s*/?\s*(?:blockquote|br|hr|code|div|article|span|footer|aside|p|pre|dl|li|ul|ol)) [^>]+>!is' => "<\\1>",
|
||||||
//single newlines cleanup
|
// single newlines cleanup
|
||||||
"/\n+/" => "\n",
|
"/\n+/" => "\n",
|
||||||
// modern web...
|
// modern web...
|
||||||
'!<pre[^>]*>\s*<code!is' => '<pre',
|
'!<pre[^>]*>\s*<code!is' => '<pre',
|
||||||
@@ -147,7 +142,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
* @param string $parser Which parser to use for turning raw HTML into a DOMDocument
|
* @param string $parser Which parser to use for turning raw HTML into a DOMDocument
|
||||||
* @param bool $useTidy Use tidy
|
* @param bool $useTidy Use tidy
|
||||||
*/
|
*/
|
||||||
public function __construct(string $html, string $url = null, string $parser = 'libxml', bool $useTidy = true)
|
public function __construct(string $html, ?string $url = null, string $parser = 'libxml', bool $useTidy = true)
|
||||||
{
|
{
|
||||||
$this->url = $url;
|
$this->url = $url;
|
||||||
$this->html = $html;
|
$this->html = $html;
|
||||||
@@ -165,7 +160,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Get article title element.
|
* Get article title element.
|
||||||
*
|
*
|
||||||
* @return DOMElement
|
* @return \DOMElement
|
||||||
*/
|
*/
|
||||||
public function getTitle()
|
public function getTitle()
|
||||||
{
|
{
|
||||||
@@ -175,7 +170,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Get article content element.
|
* Get article content element.
|
||||||
*
|
*
|
||||||
* @return DOMElement
|
* @return \DOMElement
|
||||||
*/
|
*/
|
||||||
public function getContent()
|
public function getContent()
|
||||||
{
|
{
|
||||||
@@ -284,7 +279,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Run any post-process modifications to article content as necessary.
|
* Run any post-process modifications to article content as necessary.
|
||||||
*/
|
*/
|
||||||
public function postProcessContent(DOMElement $articleContent): void
|
public function postProcessContent(\DOMElement $articleContent): void
|
||||||
{
|
{
|
||||||
if ($this->convertLinksToFootnotes && !preg_match('/\bwiki/', $this->url)) {
|
if ($this->convertLinksToFootnotes && !preg_match('/\bwiki/', $this->url)) {
|
||||||
$this->addFootnotes($articleContent);
|
$this->addFootnotes($articleContent);
|
||||||
@@ -296,7 +291,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
*
|
*
|
||||||
* @see http://www.roughtype.com/archives/2010/05/experiments_in.php
|
* @see http://www.roughtype.com/archives/2010/05/experiments_in.php
|
||||||
*/
|
*/
|
||||||
public function addFootnotes(DOMElement $articleContent): void
|
public function addFootnotes(\DOMElement $articleContent): void
|
||||||
{
|
{
|
||||||
$footnotesWrapper = $this->dom->createElement('footer');
|
$footnotesWrapper = $this->dom->createElement('footer');
|
||||||
$footnotesWrapper->setAttribute('class', 'readability-footnotes');
|
$footnotesWrapper->setAttribute('class', 'readability-footnotes');
|
||||||
@@ -307,8 +302,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
$articleLinks = $articleContent->getElementsByTagName('a');
|
$articleLinks = $articleContent->getElementsByTagName('a');
|
||||||
$linkCount = 0;
|
$linkCount = 0;
|
||||||
|
|
||||||
for ($i = 0; $i < $articleLinks->length; ++$i) {
|
foreach ($articleLinks as $articleLink) {
|
||||||
$articleLink = $articleLinks->item($i);
|
|
||||||
$footnoteLink = $articleLink->cloneNode(true);
|
$footnoteLink = $articleLink->cloneNode(true);
|
||||||
$refLink = $this->dom->createElement('a');
|
$refLink = $this->dom->createElement('a');
|
||||||
$footnote = $this->dom->createElement('li');
|
$footnote = $this->dom->createElement('li');
|
||||||
@@ -339,7 +333,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
$articleLink->setAttribute('style', 'color: inherit; text-decoration: none;');
|
$articleLink->setAttribute('style', 'color: inherit; text-decoration: none;');
|
||||||
$articleLink->setAttribute('name', 'readabilityLink-' . $linkCount);
|
$articleLink->setAttribute('name', 'readabilityLink-' . $linkCount);
|
||||||
$footnote->setInnerHtml('<small><sup><a href="#readabilityLink-' . $linkCount . '" title="Jump to Link in Article">^</a></sup></small> ');
|
$footnote->setInnerHtml('<small><sup><a href="#readabilityLink-' . $linkCount . '" title="Jump to Link in Article">^</a></sup></small> ');
|
||||||
$footnoteLink->setInnerHtml(('' !== $footnoteLink->getAttribute('title') ? $footnoteLink->getAttribute('title') : $linkText));
|
$footnoteLink->setInnerHtml('' !== $footnoteLink->getAttribute('title') ? $footnoteLink->getAttribute('title') : $linkText);
|
||||||
$footnoteLink->setAttribute('name', 'readabilityFootnoteLink-' . $linkCount);
|
$footnoteLink->setAttribute('name', 'readabilityFootnoteLink-' . $linkCount);
|
||||||
$footnote->appendChild($footnoteLink);
|
$footnote->appendChild($footnoteLink);
|
||||||
|
|
||||||
@@ -360,12 +354,15 @@ class Readability implements LoggerAwareInterface
|
|||||||
*/
|
*/
|
||||||
public function prepArticle(\DOMNode $articleContent): void
|
public function prepArticle(\DOMNode $articleContent): void
|
||||||
{
|
{
|
||||||
if (!$articleContent instanceof DOMElement) {
|
if (!$articleContent instanceof \DOMElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger->debug($this->lightClean ? 'Light clean enabled.' : 'Standard clean enabled.');
|
$this->logger->debug($this->lightClean ? 'Light clean enabled.' : 'Standard clean enabled.');
|
||||||
|
|
||||||
|
$this->clean($articleContent, 'style');
|
||||||
|
$this->clean($articleContent, 'script');
|
||||||
|
|
||||||
$this->cleanStyles($articleContent);
|
$this->cleanStyles($articleContent);
|
||||||
$this->killBreaks($articleContent);
|
$this->killBreaks($articleContent);
|
||||||
|
|
||||||
@@ -385,8 +382,8 @@ class Readability implements LoggerAwareInterface
|
|||||||
|
|
||||||
// Remove service data-candidate attribute.
|
// Remove service data-candidate attribute.
|
||||||
$elems = $xpath->query('.//*[@data-candidate]', $articleContent);
|
$elems = $xpath->query('.//*[@data-candidate]', $articleContent);
|
||||||
for ($i = $elems->length - 1; $i >= 0; --$i) {
|
foreach ($elems as $elem) {
|
||||||
$elems->item($i)->removeAttribute('data-candidate');
|
$elem->removeAttribute('data-candidate');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean out junk from the article content.
|
// Clean out junk from the article content.
|
||||||
@@ -457,7 +454,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
* Get the inner text of a node.
|
* Get the inner text of a node.
|
||||||
* This also strips out any excess whitespace to be found.
|
* This also strips out any excess whitespace to be found.
|
||||||
*
|
*
|
||||||
* @param DOMElement $e
|
* @param \DOMElement $e
|
||||||
* @param bool $normalizeSpaces (default: true)
|
* @param bool $normalizeSpaces (default: true)
|
||||||
* @param bool $flattenLines (default: false)
|
* @param bool $flattenLines (default: false)
|
||||||
*/
|
*/
|
||||||
@@ -483,7 +480,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Remove the style attribute on every $e and under.
|
* Remove the style attribute on every $e and under.
|
||||||
*/
|
*/
|
||||||
public function cleanStyles(DOMElement $e): void
|
public function cleanStyles(\DOMElement $e): void
|
||||||
{
|
{
|
||||||
if (\is_object($e)) {
|
if (\is_object($e)) {
|
||||||
$elems = $e->getElementsByTagName('*');
|
$elems = $e->getElementsByTagName('*');
|
||||||
@@ -516,17 +513,18 @@ class Readability implements LoggerAwareInterface
|
|||||||
* This is the amount of text that is inside a link divided by the total text in the node.
|
* This is the amount of text that is inside a link divided by the total text in the node.
|
||||||
* Can exclude external references to differentiate between simple text and menus/infoblocks.
|
* Can exclude external references to differentiate between simple text and menus/infoblocks.
|
||||||
*/
|
*/
|
||||||
public function getLinkDensity(DOMElement $e, bool $excludeExternal = false): float
|
public function getLinkDensity(\DOMElement $e, bool $excludeExternal = false): float
|
||||||
{
|
{
|
||||||
$links = $e->getElementsByTagName('a');
|
$links = $e->getElementsByTagName('a');
|
||||||
$textLength = mb_strlen($this->getInnerText($e, true, true));
|
$textLength = mb_strlen($this->getInnerText($e, true, true));
|
||||||
$linkLength = 0;
|
$linkLength = 0;
|
||||||
|
|
||||||
for ($dRe = $this->domainRegExp, $i = 0, $il = $links->length; $i < $il; ++$i) {
|
$dRe = $this->domainRegExp;
|
||||||
if ($excludeExternal && $dRe && !preg_match($dRe, $links->item($i)->getAttribute('href'))) {
|
foreach ($links as $link) {
|
||||||
|
if ($excludeExternal && $dRe && !preg_match($dRe, $link->getAttribute('href'))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$linkLength += mb_strlen($this->getInnerText($links->item($i)));
|
$linkLength += mb_strlen($this->getInnerText($link));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($textLength > 0 && $linkLength > 0) {
|
if ($textLength > 0 && $linkLength > 0) {
|
||||||
@@ -539,7 +537,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Get an element relative weight.
|
* Get an element relative weight.
|
||||||
*/
|
*/
|
||||||
public function getWeight(DOMElement $e): int
|
public function getWeight(\DOMElement $e): int
|
||||||
{
|
{
|
||||||
if (!$this->flagIsActive(self::FLAG_WEIGHT_ATTRIBUTES)) {
|
if (!$this->flagIsActive(self::FLAG_WEIGHT_ATTRIBUTES)) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -557,7 +555,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Remove extraneous break tags from a node.
|
* Remove extraneous break tags from a node.
|
||||||
*/
|
*/
|
||||||
public function killBreaks(DOMElement $node): void
|
public function killBreaks(\DOMElement $node): void
|
||||||
{
|
{
|
||||||
$html = $node->getInnerHTML();
|
$html = $node->getInnerHTML();
|
||||||
$html = preg_replace($this->regexps['killBreaks'], '<br />', $html);
|
$html = preg_replace($this->regexps['killBreaks'], '<br />', $html);
|
||||||
@@ -570,7 +568,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
*
|
*
|
||||||
* Updated 2012-09-18 to preserve youtube/vimeo iframes
|
* Updated 2012-09-18 to preserve youtube/vimeo iframes
|
||||||
*/
|
*/
|
||||||
public function clean(DOMElement $e, string $tag): void
|
public function clean(\DOMElement $e, string $tag): void
|
||||||
{
|
{
|
||||||
$targetList = $e->getElementsByTagName($tag);
|
$targetList = $e->getElementsByTagName($tag);
|
||||||
$isEmbed = ('audio' === $tag || 'video' === $tag || 'iframe' === $tag || 'object' === $tag || 'embed' === $tag);
|
$isEmbed = ('audio' === $tag || 'video' === $tag || 'iframe' === $tag || 'object' === $tag || 'embed' === $tag);
|
||||||
@@ -588,7 +586,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then check the elements inside this element for the same.
|
// Then check the elements inside this element for the same.
|
||||||
if (preg_match($this->regexps['media'], $targetList->item($y)->getInnerHTML())) {
|
if (preg_match($this->regexps['media'], $currentItem->getInnerHTML())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -602,7 +600,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
* "Fishy" is an algorithm based on content length, classnames,
|
* "Fishy" is an algorithm based on content length, classnames,
|
||||||
* link density, number of images & embeds, etc.
|
* link density, number of images & embeds, etc.
|
||||||
*/
|
*/
|
||||||
public function cleanConditionally(DOMElement $e, string $tag): void
|
public function cleanConditionally(\DOMElement $e, string $tag): void
|
||||||
{
|
{
|
||||||
if (!$this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) {
|
if (!$this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) {
|
||||||
return;
|
return;
|
||||||
@@ -642,15 +640,15 @@ class Readability implements LoggerAwareInterface
|
|||||||
$embedCount = 0;
|
$embedCount = 0;
|
||||||
$embeds = $node->getElementsByTagName('embed');
|
$embeds = $node->getElementsByTagName('embed');
|
||||||
|
|
||||||
for ($ei = 0, $il = $embeds->length; $ei < $il; ++$ei) {
|
foreach ($embeds as $embed) {
|
||||||
if (preg_match($this->regexps['media'], $embeds->item($ei)->getAttribute('src'))) {
|
if (preg_match($this->regexps['media'], $embed->getAttribute('src'))) {
|
||||||
++$embedCount;
|
++$embedCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$embeds = $node->getElementsByTagName('iframe');
|
$embeds = $node->getElementsByTagName('iframe');
|
||||||
for ($ei = 0, $il = $embeds->length; $ei < $il; ++$ei) {
|
foreach ($embeds as $embed) {
|
||||||
if (preg_match($this->regexps['media'], $embeds->item($ei)->getAttribute('src'))) {
|
if (preg_match($this->regexps['media'], $embed->getAttribute('src'))) {
|
||||||
++$embedCount;
|
++$embedCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,10 +668,10 @@ class Readability implements LoggerAwareInterface
|
|||||||
$this->logger->debug(' content length less than 6 chars, 0 embeds and either 0 images or more than 2 images');
|
$this->logger->debug(' content length less than 6 chars, 0 embeds and either 0 images or more than 2 images');
|
||||||
$toRemove = true;
|
$toRemove = true;
|
||||||
} elseif (!$isList && $weight < 25 && $linkDensity > 0.25) {
|
} elseif (!$isList && $weight < 25 && $linkDensity > 0.25) {
|
||||||
$this->logger->debug(' weight is ' . $weight . ' < 25 and link density is ' . sprintf('%.2f', $linkDensity) . ' > 0.25');
|
$this->logger->debug(' weight is ' . $weight . ' < 25 and link density is ' . \sprintf('%.2f', $linkDensity) . ' > 0.25');
|
||||||
$toRemove = true;
|
$toRemove = true;
|
||||||
} elseif ($a > 2 && ($weight >= 25 && $linkDensity > 0.5)) {
|
} elseif ($a > 2 && ($weight >= 25 && $linkDensity > 0.5)) {
|
||||||
$this->logger->debug(' more than 2 links and weight is ' . $weight . ' > 25 but link density is ' . sprintf('%.2f', $linkDensity) . ' > 0.5');
|
$this->logger->debug(' more than 2 links and weight is ' . $weight . ' > 25 but link density is ' . \sprintf('%.2f', $linkDensity) . ' > 0.5');
|
||||||
$toRemove = true;
|
$toRemove = true;
|
||||||
} elseif ($embedCount > 3) {
|
} elseif ($embedCount > 3) {
|
||||||
$this->logger->debug(' more than 3 embeds');
|
$this->logger->debug(' more than 3 embeds');
|
||||||
@@ -693,10 +691,10 @@ class Readability implements LoggerAwareInterface
|
|||||||
$this->logger->debug(' content length less than 10 chars and 0 images, or more than 2 images');
|
$this->logger->debug(' content length less than 10 chars and 0 images, or more than 2 images');
|
||||||
$toRemove = true;
|
$toRemove = true;
|
||||||
} elseif (!$isList && $weight < 25 && $linkDensity > 0.2) {
|
} elseif (!$isList && $weight < 25 && $linkDensity > 0.2) {
|
||||||
$this->logger->debug(' weight is ' . $weight . ' lower than 0 and link density is ' . sprintf('%.2f', $linkDensity) . ' > 0.2');
|
$this->logger->debug(' weight is ' . $weight . ' lower than 0 and link density is ' . \sprintf('%.2f', $linkDensity) . ' > 0.2');
|
||||||
$toRemove = true;
|
$toRemove = true;
|
||||||
} elseif ($weight >= 25 && $linkDensity > 0.5) {
|
} elseif ($weight >= 25 && $linkDensity > 0.5) {
|
||||||
$this->logger->debug(' weight above 25 but link density is ' . sprintf('%.2f', $linkDensity) . ' > 0.5');
|
$this->logger->debug(' weight above 25 but link density is ' . \sprintf('%.2f', $linkDensity) . ' > 0.5');
|
||||||
$toRemove = true;
|
$toRemove = true;
|
||||||
} elseif ((1 === $embedCount && $contentLength < 75) || $embedCount > 1) {
|
} elseif ((1 === $embedCount && $contentLength < 75) || $embedCount > 1) {
|
||||||
$this->logger->debug(' 1 embed and content length smaller than 75 chars, or more than one embed');
|
$this->logger->debug(' 1 embed and content length smaller than 75 chars, or more than one embed');
|
||||||
@@ -715,14 +713,15 @@ class Readability implements LoggerAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Clean out spurious headers from an Element. Checks things like classnames and link density.
|
* Clean out spurious headers from an Element. Checks things like classnames and link density.
|
||||||
*/
|
*/
|
||||||
public function cleanHeaders(DOMElement $e): void
|
public function cleanHeaders(\DOMElement $e): void
|
||||||
{
|
{
|
||||||
for ($headerIndex = 1; $headerIndex < 3; ++$headerIndex) {
|
for ($headerIndex = 1; $headerIndex < 3; ++$headerIndex) {
|
||||||
$headers = $e->getElementsByTagName('h' . $headerIndex);
|
$headers = $e->getElementsByTagName('h' . $headerIndex);
|
||||||
|
|
||||||
for ($i = $headers->length - 1; $i >= 0; --$i) {
|
for ($i = $headers->length - 1; $i >= 0; --$i) {
|
||||||
if ($this->getWeight($headers->item($i)) < 0 || $this->getLinkDensity($headers->item($i)) > 0.33) {
|
$header = $headers->item($i);
|
||||||
$headers->item($i)->parentNode->removeChild($headers->item($i));
|
if ($this->getWeight($header) < 0 || $this->getLinkDensity($header) > 0.33) {
|
||||||
|
$header->parentNode->removeChild($header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -741,7 +740,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
*/
|
*/
|
||||||
public function addFlag(int $flag): void
|
public function addFlag(int $flag): void
|
||||||
{
|
{
|
||||||
$this->flags = $this->flags | $flag;
|
$this->flags |= $flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -749,13 +748,13 @@ class Readability implements LoggerAwareInterface
|
|||||||
*/
|
*/
|
||||||
public function removeFlag(int $flag): void
|
public function removeFlag(int $flag): void
|
||||||
{
|
{
|
||||||
$this->flags = $this->flags & ~$flag;
|
$this->flags &= ~$flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the article title as an H1.
|
* Get the article title as an H1.
|
||||||
*
|
*
|
||||||
* @return DOMElement
|
* @return \DOMElement
|
||||||
*/
|
*/
|
||||||
protected function getArticleTitle()
|
protected function getArticleTitle()
|
||||||
{
|
{
|
||||||
@@ -814,12 +813,14 @@ class Readability implements LoggerAwareInterface
|
|||||||
// Remove all style tags in head.
|
// Remove all style tags in head.
|
||||||
$styleTags = $this->dom->getElementsByTagName('style');
|
$styleTags = $this->dom->getElementsByTagName('style');
|
||||||
for ($i = $styleTags->length - 1; $i >= 0; --$i) {
|
for ($i = $styleTags->length - 1; $i >= 0; --$i) {
|
||||||
$styleTags->item($i)->parentNode->removeChild($styleTags->item($i));
|
$styleTag = $styleTags->item($i);
|
||||||
|
$styleTag->parentNode->removeChild($styleTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
$linkTags = $this->dom->getElementsByTagName('link');
|
$linkTags = $this->dom->getElementsByTagName('link');
|
||||||
for ($i = $linkTags->length - 1; $i >= 0; --$i) {
|
for ($i = $linkTags->length - 1; $i >= 0; --$i) {
|
||||||
$linkTags->item($i)->parentNode->removeChild($linkTags->item($i));
|
$linkTag = $linkTags->item($i);
|
||||||
|
$linkTag->parentNode->removeChild($linkTag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,7 +828,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
* Initialize a node with the readability object. Also checks the
|
* Initialize a node with the readability object. Also checks the
|
||||||
* className/id for special names to add to its score.
|
* className/id for special names to add to its score.
|
||||||
*/
|
*/
|
||||||
protected function initializeNode(DOMElement $node): void
|
protected function initializeNode(\DOMElement $node): void
|
||||||
{
|
{
|
||||||
if (!isset($node->tagName)) {
|
if (!isset($node->tagName)) {
|
||||||
return;
|
return;
|
||||||
@@ -895,11 +896,9 @@ class Readability implements LoggerAwareInterface
|
|||||||
* Using a variety of metrics (content score, classname, element types), find the content that is
|
* Using a variety of metrics (content score, classname, element types), find the content that is
|
||||||
* most likely to be the stuff a user wants to read. Then return it wrapped up in a div.
|
* most likely to be the stuff a user wants to read. Then return it wrapped up in a div.
|
||||||
*
|
*
|
||||||
* @param DOMElement $page
|
* @return \DOMElement|false
|
||||||
*
|
|
||||||
* @return DOMElement|false
|
|
||||||
*/
|
*/
|
||||||
protected function grabArticle(DOMElement $page = null)
|
protected function grabArticle(?\DOMElement $page = null)
|
||||||
{
|
{
|
||||||
if (!$page) {
|
if (!$page) {
|
||||||
$page = $this->dom;
|
$page = $this->dom;
|
||||||
@@ -935,9 +934,9 @@ class Readability implements LoggerAwareInterface
|
|||||||
// Remove unlikely candidates
|
// Remove unlikely candidates
|
||||||
$unlikelyMatchString = $node->getAttribute('class') . ' ' . $node->getAttribute('id') . ' ' . $node->getAttribute('style');
|
$unlikelyMatchString = $node->getAttribute('class') . ' ' . $node->getAttribute('id') . ' ' . $node->getAttribute('style');
|
||||||
|
|
||||||
if (mb_strlen($unlikelyMatchString) > 3 && // don't process "empty" strings
|
if (mb_strlen($unlikelyMatchString) > 3 // don't process "empty" strings
|
||||||
preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString) &&
|
&& preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString)
|
||||||
!preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString)
|
&& !preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString)
|
||||||
) {
|
) {
|
||||||
$this->logger->debug('Removing unlikely candidate (using conf) ' . $node->getNodePath() . ' by "' . $unlikelyMatchString . '"');
|
$this->logger->debug('Removing unlikely candidate (using conf) ' . $node->getNodePath() . ' by "' . $unlikelyMatchString . '"');
|
||||||
$node->parentNode->removeChild($node);
|
$node->parentNode->removeChild($node);
|
||||||
@@ -1019,15 +1018,15 @@ class Readability implements LoggerAwareInterface
|
|||||||
* A score is determined by things like number of commas, class names, etc.
|
* A score is determined by things like number of commas, class names, etc.
|
||||||
* Maybe eventually link density.
|
* Maybe eventually link density.
|
||||||
*/
|
*/
|
||||||
for ($pt = 0, $scored = \count($nodesToScore); $pt < $scored; ++$pt) {
|
foreach ($nodesToScore as $nodeToScore) {
|
||||||
$ancestors = $this->getAncestors($nodesToScore[$pt], 5);
|
$ancestors = $this->getAncestors($nodeToScore, 5);
|
||||||
|
|
||||||
// No parent node? Move on...
|
// No parent node? Move on...
|
||||||
if (0 === \count($ancestors)) {
|
if (0 === \count($ancestors)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$innerText = $this->getInnerText($nodesToScore[$pt]);
|
$innerText = $this->getInnerText($nodeToScore);
|
||||||
|
|
||||||
// If this paragraph is less than MIN_PARAGRAPH_LENGTH (default:20) characters, don't even count it.
|
// If this paragraph is less than MIN_PARAGRAPH_LENGTH (default:20) characters, don't even count it.
|
||||||
if (mb_strlen($innerText) < self::MIN_PARAGRAPH_LENGTH) {
|
if (mb_strlen($innerText) < self::MIN_PARAGRAPH_LENGTH) {
|
||||||
@@ -1041,7 +1040,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
// For every SCORE_CHARS_IN_PARAGRAPH (default:100) characters in this paragraph, add another point. Up to 3 points.
|
// For every SCORE_CHARS_IN_PARAGRAPH (default:100) characters in this paragraph, add another point. Up to 3 points.
|
||||||
$contentScore += min(floor(mb_strlen($innerText) / self::SCORE_CHARS_IN_PARAGRAPH), 3);
|
$contentScore += min(floor(mb_strlen($innerText) / self::SCORE_CHARS_IN_PARAGRAPH), 3);
|
||||||
// For every SCORE_WORDS_IN_PARAGRAPH (default:20) words in this paragraph, add another point. Up to 3 points.
|
// For every SCORE_WORDS_IN_PARAGRAPH (default:20) words in this paragraph, add another point. Up to 3 points.
|
||||||
//$contentScore += min(floor($this->getWordCount($innerText) / self::SCORE_WORDS_IN_PARAGRAPH), 3);
|
// $contentScore += min(floor($this->getWordCount($innerText) / self::SCORE_WORDS_IN_PARAGRAPH), 3);
|
||||||
|
|
||||||
foreach ($ancestors as $level => $ancestor) {
|
foreach ($ancestors as $level => $ancestor) {
|
||||||
if (!$ancestor->nodeName || !$ancestor->parentNode) {
|
if (!$ancestor->nodeName || !$ancestor->parentNode) {
|
||||||
@@ -1080,11 +1079,6 @@ class Readability implements LoggerAwareInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$candidates = $xpath->query('.//*[not(self::body) and (@class or @id or @style) and ((number(@readability) < 40) or not(@readability))]', $page->documentElement);
|
|
||||||
|
|
||||||
for ($c = $candidates->length - 1; $c >= 0; --$c) {
|
|
||||||
$node = $candidates->item($c);
|
|
||||||
}
|
|
||||||
unset($candidates);
|
unset($candidates);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1122,9 +1116,11 @@ class Readability implements LoggerAwareInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$topCandidates = array_filter($topCandidates, function ($v, $idx) {
|
$topCandidates = array_filter(
|
||||||
return 0 === $idx || null !== $v;
|
$topCandidates,
|
||||||
}, \ARRAY_FILTER_USE_BOTH);
|
static fn ($v, $idx) => 0 === $idx || null !== $v,
|
||||||
|
\ARRAY_FILTER_USE_BOTH
|
||||||
|
);
|
||||||
$topCandidate = $topCandidates[0];
|
$topCandidate = $topCandidates[0];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1212,7 +1208,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
if (0 === strcasecmp($tagName, 'td') || 0 === strcasecmp($tagName, 'tr')) {
|
if (0 === strcasecmp($tagName, 'td') || 0 === strcasecmp($tagName, 'tr')) {
|
||||||
$up = $topCandidate;
|
$up = $topCandidate;
|
||||||
|
|
||||||
if ($up->parentNode instanceof DOMElement) {
|
if ($up->parentNode instanceof \DOMElement) {
|
||||||
$up = $up->parentNode;
|
$up = $up->parentNode;
|
||||||
|
|
||||||
if (0 === strcasecmp($up->tagName, 'table')) {
|
if (0 === strcasecmp($up->tagName, 'table')) {
|
||||||
@@ -1233,11 +1229,6 @@ class Readability implements LoggerAwareInterface
|
|||||||
$parentOfTopCandidate = $topCandidate->parentNode;
|
$parentOfTopCandidate = $topCandidate->parentNode;
|
||||||
$siblingNodes = $parentOfTopCandidate->childNodes;
|
$siblingNodes = $parentOfTopCandidate->childNodes;
|
||||||
|
|
||||||
if (0 === $siblingNodes->length) {
|
|
||||||
$siblingNodes = new \stdClass();
|
|
||||||
$siblingNodes->length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($s = 0, $sl = $siblingNodes->length; $s < $sl; ++$s) {
|
for ($s = 0, $sl = $siblingNodes->length; $s < $sl; ++$s) {
|
||||||
$siblingNode = $siblingNodes->item($s);
|
$siblingNode = $siblingNodes->item($s);
|
||||||
$siblingNodeName = $siblingNode->nodeName;
|
$siblingNodeName = $siblingNode->nodeName;
|
||||||
@@ -1293,8 +1284,8 @@ class Readability implements LoggerAwareInterface
|
|||||||
|
|
||||||
// To ensure a node does not interfere with readability styles, remove its classnames & ids.
|
// To ensure a node does not interfere with readability styles, remove its classnames & ids.
|
||||||
// Now done via RegExp post_filter.
|
// Now done via RegExp post_filter.
|
||||||
//$nodeToAppend->removeAttribute('class');
|
// $nodeToAppend->removeAttribute('class');
|
||||||
//$nodeToAppend->removeAttribute('id');
|
// $nodeToAppend->removeAttribute('id');
|
||||||
// Append sibling and subtract from our list as appending removes a node.
|
// Append sibling and subtract from our list as appending removes a node.
|
||||||
$articleContent->appendChild($nodeToAppend);
|
$articleContent->appendChild($nodeToAppend);
|
||||||
}
|
}
|
||||||
@@ -1341,7 +1332,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
* Get an element weight by attribute.
|
* Get an element weight by attribute.
|
||||||
* Uses regular expressions to tell if this element looks good or bad.
|
* Uses regular expressions to tell if this element looks good or bad.
|
||||||
*/
|
*/
|
||||||
protected function weightAttribute(DOMElement $element, string $attribute): int
|
protected function weightAttribute(\DOMElement $element, string $attribute): int
|
||||||
{
|
{
|
||||||
if (!$element->hasAttribute($attribute)) {
|
if (!$element->hasAttribute($attribute)) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1392,7 +1383,10 @@ class Readability implements LoggerAwareInterface
|
|||||||
$this->logger->debug('Parsing URL: ' . $this->url);
|
$this->logger->debug('Parsing URL: ' . $this->url);
|
||||||
|
|
||||||
if ($this->url) {
|
if ($this->url) {
|
||||||
$this->domainRegExp = '/' . strtr((string) preg_replace('/www\d*\./', '', (string) parse_url($this->url, \PHP_URL_HOST)), ['.' => '\.']) . '/';
|
$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_internal_encoding('UTF-8');
|
||||||
@@ -1428,7 +1422,7 @@ class Readability implements LoggerAwareInterface
|
|||||||
unset($tidy);
|
unset($tidy);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->html = mb_convert_encoding((string) $this->html, 'HTML-ENTITIES', 'UTF-8');
|
$this->html = self::entitizeNonAscii((string) $this->html);
|
||||||
|
|
||||||
if ('html5lib' === $this->parser || 'html5' === $this->parser) {
|
if ('html5lib' === $this->parser || 'html5' === $this->parser) {
|
||||||
$this->dom = (new HTML5())->loadHTML($this->html);
|
$this->dom = (new HTML5())->loadHTML($this->html);
|
||||||
@@ -1444,14 +1438,14 @@ class Readability implements LoggerAwareInterface
|
|||||||
libxml_use_internal_errors(false);
|
libxml_use_internal_errors(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->dom->registerNodeClass(DOMElement::class, \Readability\JSLikeHTMLElement::class);
|
$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 = [];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
while ($node->parentNode instanceof DOMElement) {
|
while ($node->parentNode instanceof \DOMElement) {
|
||||||
$ancestors[] = $node->parentNode;
|
$ancestors[] = $node->parentNode;
|
||||||
if (++$i === $maxDepth) {
|
if (++$i === $maxDepth) {
|
||||||
break;
|
break;
|
||||||
@@ -1466,21 +1460,45 @@ class Readability implements LoggerAwareInterface
|
|||||||
{
|
{
|
||||||
return \XML_TEXT_NODE === $node->nodeType
|
return \XML_TEXT_NODE === $node->nodeType
|
||||||
|| \in_array(strtoupper($node->nodeName), $this->phrasingElements, true)
|
|| \in_array(strtoupper($node->nodeName), $this->phrasingElements, true)
|
||||||
|| (\in_array(strtoupper($node->nodeName), ['A', 'DEL', 'INS'], true) && !\in_array(false, array_map(function ($c) {
|
|| (
|
||||||
return $this->isPhrasingContent($c);
|
\in_array(strtoupper($node->nodeName), ['A', 'DEL', 'INS'], true)
|
||||||
}, iterator_to_array($node->childNodes)), true));
|
&& !\in_array(
|
||||||
|
false,
|
||||||
|
array_map(
|
||||||
|
fn ($c) => $this->isPhrasingContent($c),
|
||||||
|
iterator_to_array($node->childNodes)
|
||||||
|
),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function hasSingleTagInsideElement(DOMElement $node, string $tag): bool
|
/**
|
||||||
|
* Checks if `$node` has only whitespace and a single element with `$tag` for the tag name.
|
||||||
|
* Returns false if `$node` contains non-empty text nodes
|
||||||
|
* or if it contains no element with given tag or more than 1 element.
|
||||||
|
*/
|
||||||
|
private function hasSingleTagInsideElement(\DOMElement $node, string $tag): bool
|
||||||
{
|
{
|
||||||
if (1 !== $node->childNodes->length || $node->childNodes->item(0)->nodeName !== $tag) {
|
$childNodes = iterator_to_array($node->childNodes);
|
||||||
|
$children = array_filter($childNodes, static fn ($childNode) => $childNode instanceof \DOMElement);
|
||||||
|
|
||||||
|
// array_first() has been added in PHP 8.5, failing back for older versions
|
||||||
|
if (!\function_exists('array_first')) {
|
||||||
|
$firstChild = $children ? $children[array_key_first($children)] : null;
|
||||||
|
} else {
|
||||||
|
$firstChild = array_first($children);
|
||||||
|
}
|
||||||
|
|
||||||
|
// There should be exactly 1 element child with given tag
|
||||||
|
if (1 !== \count($children) || $firstChild->nodeName !== $tag) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$a = array_filter(iterator_to_array($node->childNodes), function ($childNode) {
|
$a = array_filter(
|
||||||
return $childNode instanceof \DOMText &&
|
$childNodes,
|
||||||
preg_match($this->regexps['hasContent'], $this->getInnerText($childNode));
|
fn ($childNode) => $childNode instanceof \DOMText && preg_match($this->regexps['hasContent'], $this->getInnerText($childNode))
|
||||||
});
|
);
|
||||||
|
|
||||||
return 0 === \count($a);
|
return 0 === \count($a);
|
||||||
}
|
}
|
||||||
@@ -1491,11 +1509,29 @@ class Readability implements LoggerAwareInterface
|
|||||||
* Tidy must be configured to not clean the input for this function to
|
* Tidy must be configured to not clean the input for this function to
|
||||||
* work as expected, see $this->tidy_config['clean']
|
* work as expected, see $this->tidy_config['clean']
|
||||||
*/
|
*/
|
||||||
private function isNodeVisible(DOMElement $node): bool
|
private function isNodeVisible(\DOMElement $node): bool
|
||||||
{
|
{
|
||||||
return !($node->hasAttribute('style')
|
return !(
|
||||||
|
$node->hasAttribute('style')
|
||||||
&& preg_match($this->regexps['isNotVisible'], $node->getAttribute('style'))
|
&& preg_match($this->regexps['isNotVisible'], $node->getAttribute('style'))
|
||||||
)
|
)
|
||||||
&& !$node->hasAttribute('hidden');
|
&& !$node->hasAttribute('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts non-ASCII UTF-8 characters to numeric HTML entities.
|
||||||
|
*
|
||||||
|
* `DOMDocument::loadHTML` will parse HTML documents as ISO-8859-1 if there is no `meta[charset]` tag.
|
||||||
|
* This means that UTF-8-encoded HTML fragments such as those coming from JSON-LD `articleBody` field would be parsed with incorrect encoding.
|
||||||
|
*
|
||||||
|
* @param string $html UTF-8 encoded document
|
||||||
|
*/
|
||||||
|
private static function entitizeNonAscii(string $html): string
|
||||||
|
{
|
||||||
|
$convmap = [
|
||||||
|
0x80, 0x1FFFFF, 0, 0x10FFFF,
|
||||||
|
];
|
||||||
|
|
||||||
|
return mb_encode_numericentity($html, $convmap, 'utf8', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+120
-31
@@ -21,10 +21,11 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testConstructDefault(): void
|
public function testConstructDefault(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('');
|
$readability = $this->getReadability('');
|
||||||
|
$this->assertSame('utf8', $readability->tidy_config['input-encoding']);
|
||||||
$readability->init();
|
$readability->init();
|
||||||
|
|
||||||
$this->assertNull($readability->url);
|
$this->assertNull($readability->url);
|
||||||
$this->assertInstanceOf('DomDocument', $readability->dom);
|
$this->assertInstanceOf(\DOMDocument::class, $readability->dom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testConstructHtml5Parser(): void
|
public function testConstructHtml5Parser(): void
|
||||||
@@ -33,7 +34,7 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
$readability->init();
|
$readability->init();
|
||||||
|
|
||||||
$this->assertSame('http://0.0.0.0', $readability->url);
|
$this->assertSame('http://0.0.0.0', $readability->url);
|
||||||
$this->assertInstanceOf('DomDocument', $readability->dom);
|
$this->assertInstanceOf(\DOMDocument::class, $readability->dom);
|
||||||
$this->assertSame('<html/>', $readability->original_html);
|
$this->assertSame('<html/>', $readability->original_html);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
$readability->init();
|
$readability->init();
|
||||||
|
|
||||||
$this->assertSame('http://0.0.0.0', $readability->url);
|
$this->assertSame('http://0.0.0.0', $readability->url);
|
||||||
$this->assertInstanceOf('DomDocument', $readability->dom);
|
$this->assertInstanceOf(\DOMDocument::class, $readability->dom);
|
||||||
$this->assertSame('<html/>', $readability->original_html);
|
$this->assertSame('<html/>', $readability->original_html);
|
||||||
$this->assertTrue($readability->tidied);
|
$this->assertTrue($readability->tidied);
|
||||||
}
|
}
|
||||||
@@ -60,7 +61,7 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
$this->assertSame('', $readability->original_html);
|
$this->assertSame('', $readability->original_html);
|
||||||
$this->assertFalse($readability->tidied);
|
$this->assertFalse($readability->tidied);
|
||||||
|
|
||||||
$this->assertInstanceOf('DomDocument', $readability->dom);
|
$this->assertInstanceOf(\DOMDocument::class, $readability->dom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testConstructSimpleWithoutTidy(): void
|
public function testConstructSimpleWithoutTidy(): void
|
||||||
@@ -69,7 +70,7 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
$readability->init();
|
$readability->init();
|
||||||
|
|
||||||
$this->assertSame('http://0.0.0.0', $readability->url);
|
$this->assertSame('http://0.0.0.0', $readability->url);
|
||||||
$this->assertInstanceOf('DomDocument', $readability->dom);
|
$this->assertInstanceOf(\DOMDocument::class, $readability->dom);
|
||||||
$this->assertSame('<html/>', $readability->original_html);
|
$this->assertSame('<html/>', $readability->original_html);
|
||||||
$this->assertFalse($readability->tidied);
|
$this->assertFalse($readability->tidied);
|
||||||
}
|
}
|
||||||
@@ -115,7 +116,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testInitDiv(): void
|
public function testInitDiv(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<div>' . str_repeat('This is the awesome content :)', 7) . '</div>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<div>' . str_repeat('This is the awesome content :)', 7) . '</div>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -129,7 +129,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithFootnotes(): void
|
public function testWithFootnotes(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<div>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '</div>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<div>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '</div>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$readability->convertLinksToFootnotes = true;
|
$readability->convertLinksToFootnotes = true;
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
@@ -146,7 +145,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testStandardClean(): void
|
public function testStandardClean(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<div><h2>Title</h2>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<a href="#nofollow" rel="nofollow">will NOT be removed</a></div>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<div><h2>Title</h2>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<a href="#nofollow" rel="nofollow">will NOT be removed</a></div>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$readability->lightClean = false;
|
$readability->lightClean = false;
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
@@ -163,7 +161,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithIframe(): void
|
public function testWithIframe(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<div><h2>Title</h2>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<p>This is an awesome text with some links, here there are <iframe src="http://youtube.com/test" href="#nofollow" rel="nofollow"></iframe><iframe>http://soundcloud.com/test</iframe></p></div>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<div><h2>Title</h2>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<p>This is an awesome text with some links, here there are <iframe src="http://youtube.com/test" href="#nofollow" rel="nofollow"></iframe><iframe>http://soundcloud.com/test</iframe></p></div>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -178,7 +175,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithArticle(): void
|
public function testWithArticle(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<article><p>' . str_repeat('This is an awesome text with some links, here there are: the awesome', 20) . '</p><p>This is an awesome text with some links, here there are <iframe src="http://youtube.com/test" href="#nofollow" rel="nofollow"></iframe></p></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<article><p>' . str_repeat('This is an awesome text with some links, here there are: the awesome', 20) . '</p><p>This is an awesome text with some links, here there are <iframe src="http://youtube.com/test" href="#nofollow" rel="nofollow"></iframe></p></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -193,7 +189,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithAside(): void
|
public function testWithAside(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<article>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<footer><aside>' . str_repeat('<p>This is an awesome text with some links, here there are</p>', 8) . '</aside></footer></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<article>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<footer><aside>' . str_repeat('<p>This is an awesome text with some links, here there are</p>', 8) . '</aside></footer></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -208,7 +203,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithClasses(): void
|
public function testWithClasses(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<article>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<div style="display:none">' . str_repeat('<p class="clock">This text should be removed</p>', 10) . '</div></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<article>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<div style="display:none">' . str_repeat('<p class="clock">This text should be removed</p>', 10) . '</div></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -223,7 +217,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithClassesWithoutLightClean(): void
|
public function testWithClassesWithoutLightClean(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<article>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<div style="display:none">' . str_repeat('<p class="clock">This text should be removed</p>', 10) . '</div></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<article>' . str_repeat('<p>This is an awesome text with some links, here there are: <a href="http://0.0.0.0/test.html">the awesome</a></p>', 7) . '<div style="display:none">' . str_repeat('<p class="clock">This text should be removed</p>', 10) . '</div></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$readability->lightClean = false;
|
$readability->lightClean = false;
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
@@ -239,7 +232,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithTd(): void
|
public function testWithTd(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<table><tr>' . str_repeat('<td><p>This is an awesome text with some links, here there are the awesome</td>', 7) . '</tr></table>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<table><tr>' . str_repeat('<td><p>This is an awesome text with some links, here there are the awesome</td>', 7) . '</tr></table>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -252,7 +244,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithSameClasses(): void
|
public function testWithSameClasses(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<div class="awesomecontent">This text is also an awesome text and you should know that !</div></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<div class="awesomecontent">This text is also an awesome text and you should know that !</div></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -266,7 +257,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testWithScript(): void
|
public function testWithScript(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p><script>This text is also an awesome text and you should know that !</script></p></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p><script>This text is also an awesome text and you should know that !</script></p></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -280,7 +270,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testTitle(): void
|
public function testTitle(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<title>this is my title</title><article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p></p></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<title>this is my title</title><article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p></p></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -294,7 +283,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testTitleWithDash(): void
|
public function testTitleWithDash(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<title> title2 - title3 </title><article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p></p></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<title> title2 - title3 </title><article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p></p></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -308,7 +296,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testTitleWithDoubleDot(): void
|
public function testTitleWithDoubleDot(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<title> title2 : title3 </title><article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p></p></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<title> title2 : title3 </title><article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p></p></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -322,7 +309,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testTitleTooShortUseH1(): void
|
public function testTitleTooShortUseH1(): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability('<title>too short</title><h1>this is my h1 title !</h1><article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p></p></article>', 'http://0.0.0.0');
|
$readability = $this->getReadability('<title>too short</title><h1>this is my h1 title !</h1><article class="awesomecontent">' . str_repeat('<p>This is an awesome text with some links, here there are the awesome</p>', 7) . '<p></p></article>', 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -335,13 +321,14 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
|
|
||||||
public function testAutoClosingIframeNotThrowingException(): void
|
public function testAutoClosingIframeNotThrowingException(): void
|
||||||
{
|
{
|
||||||
error_reporting(\E_ALL | \E_STRICT);
|
$oldErrorReporting = error_reporting(\E_ALL);
|
||||||
ini_set('display_errors', '1');
|
$oldDisplayErrors = ini_set('display_errors', '1');
|
||||||
// dummy function to be used to the next test
|
// dummy function to be used to the next test
|
||||||
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline, array $errcontext) {
|
set_error_handler(static function (int $errno, string $errstr, string $errfile, int $errline) {
|
||||||
throw new \Exception($errstr, $errno);
|
throw new \Exception($errstr, $errno);
|
||||||
}, \E_ALL | \E_STRICT);
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
$data = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
$data = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru-RU" prefix="og: http://ogp.me/ns#">
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="ru-RU" prefix="og: http://ogp.me/ns#">
|
||||||
|
|
||||||
@@ -368,7 +355,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
</html>';
|
</html>';
|
||||||
|
|
||||||
$readability = $this->getReadability($data, 'http://iosgames.ru/?p=22030');
|
$readability = $this->getReadability($data, 'http://iosgames.ru/?p=22030');
|
||||||
$readability->debug = true;
|
|
||||||
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
@@ -377,6 +363,13 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
$this->assertInstanceOf(JSLikeHTMLElement::class, $readability->getTitle());
|
$this->assertInstanceOf(JSLikeHTMLElement::class, $readability->getTitle());
|
||||||
$this->assertStringContainsString('<iframe src="https://www.youtube.com/embed/PUep6xNeKjA" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"> </iframe>', $readability->getContent()->getInnerHtml());
|
$this->assertStringContainsString('<iframe src="https://www.youtube.com/embed/PUep6xNeKjA" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"> </iframe>', $readability->getContent()->getInnerHtml());
|
||||||
$this->assertStringContainsString('3D Touch', $readability->getTitle()->getInnerHtml());
|
$this->assertStringContainsString('3D Touch', $readability->getTitle()->getInnerHtml());
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
if (false !== $oldDisplayErrors) {
|
||||||
|
ini_set('display_errors', $oldDisplayErrors);
|
||||||
|
}
|
||||||
|
error_reporting($oldErrorReporting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -429,7 +422,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
</html>';
|
</html>';
|
||||||
|
|
||||||
$readability = $this->getReadability($data, 'http://0.0.0.0');
|
$readability = $this->getReadability($data, 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
@@ -466,20 +458,61 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
$html = (string) file_get_contents('tests/fixtures/childNodeGoesNull.html');
|
$html = (string) file_get_contents('tests/fixtures/childNodeGoesNull.html');
|
||||||
|
|
||||||
$readability = $this->getReadability($html, 'http://0.0.0.0');
|
$readability = $this->getReadability($html, 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$readability->convertLinksToFootnotes = true;
|
$readability->convertLinksToFootnotes = true;
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function dataForHasSingleTagInsideElement(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'single matching tag, no other content' => [
|
||||||
|
'<div><p>Some text</p></div>', 'p', true,
|
||||||
|
],
|
||||||
|
'single matching tag with comment' => [
|
||||||
|
'<div><!-- comment --><p>Some text</p></div>', 'p', true,
|
||||||
|
],
|
||||||
|
'whitespace-only text around single matching tag' => [
|
||||||
|
'<div> <p>Some text</p> </div>', 'p', true,
|
||||||
|
],
|
||||||
|
'two matching tags' => [
|
||||||
|
'<div><p>One</p><p>Two</p></div>', 'p', false,
|
||||||
|
],
|
||||||
|
'non-whitespace text alongside the single tag' => [
|
||||||
|
'<div>Some text<p>One</p></div>', 'p', false,
|
||||||
|
],
|
||||||
|
'single tag with a different name' => [
|
||||||
|
'<div><span>One</span></div>', 'p', false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dataForHasSingleTagInsideElement
|
||||||
|
*/
|
||||||
|
public function testHasSingleTagInsideElement(string $html, string $tag, bool $expected): void
|
||||||
|
{
|
||||||
|
$dom = new \DOMDocument();
|
||||||
|
$dom->loadHTML('<html><body>' . $html . '</body></html>');
|
||||||
|
$node = $dom->getElementsByTagName('div')->item(0);
|
||||||
|
|
||||||
|
$readability = $this->getReadability('', '', 'libxml', false);
|
||||||
|
|
||||||
|
$method = new \ReflectionMethod($readability, 'hasSingleTagInsideElement');
|
||||||
|
if (\PHP_VERSION_ID < 80100) {
|
||||||
|
$method->setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame($expected, $method->invoke($readability, $node, $tag));
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
$html = (string) file_get_contents('tests/fixtures/keepFootnotes.html');
|
$html = (string) file_get_contents('tests/fixtures/keepFootnotes.html');
|
||||||
|
|
||||||
$readability = $this->getReadability($html, 'http://0.0.0.0');
|
$readability = $this->getReadability($html, 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -493,7 +526,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
$html = (string) file_get_contents('tests/fixtures/wipedBody.html');
|
$html = (string) file_get_contents('tests/fixtures/wipedBody.html');
|
||||||
|
|
||||||
$readability = $this->getReadability($html, 'http://0.0.0.0', 'libxml', false);
|
$readability = $this->getReadability($html, 'http://0.0.0.0', 'libxml', false);
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
$this->assertTrue($res);
|
$this->assertTrue($res);
|
||||||
@@ -532,7 +564,6 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
public function testVisibleNode(string $content, bool $shouldBeVisible): void
|
public function testVisibleNode(string $content, bool $shouldBeVisible): void
|
||||||
{
|
{
|
||||||
$readability = $this->getReadability($content, 'http://0.0.0.0');
|
$readability = $this->getReadability($content, 'http://0.0.0.0');
|
||||||
$readability->debug = true;
|
|
||||||
$res = $readability->init();
|
$res = $readability->init();
|
||||||
|
|
||||||
if ($shouldBeVisible) {
|
if ($shouldBeVisible) {
|
||||||
@@ -542,7 +573,65 @@ class ReadabilityTest extends \PHPUnit\Framework\TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getReadability(string $html, string $url = null, string $parser = 'libxml', bool $useTidy = true): Readability
|
// https://github.com/wallabag/wallabag/issues/8158
|
||||||
|
public function testCharsetAfterTitle(): void
|
||||||
|
{
|
||||||
|
$readability = $this->getReadability('<!DOCTYPE html><html lang="et"><head><title>Tõde ja õigus I</title> <meta charset="utf-8"></head><body><p>See oli läinud aastasaja kolmanda veerandi lõpul. Päike lähenes silmapiirile, seistes sedavõrd madalas, et enam ei ulatunud valgustama ei mäkke ronivat hobust, kes puutelgedega vankrit vedas, ei vankril istuvat noort naist ega ka ligi kolmekümnelist meest, kes kõndis vankri kõrval.</p></body></html>', 'https://et.wikisource.org/wiki/T%C3%B5de_ja_%C3%B5igus_I/I');
|
||||||
|
$readability->convertLinksToFootnotes = true;
|
||||||
|
$res = $readability->init();
|
||||||
|
|
||||||
|
$this->assertTrue($res);
|
||||||
|
$this->assertInstanceOf(JSLikeHTMLElement::class, $readability->getContent());
|
||||||
|
$this->assertInstanceOf(JSLikeHTMLElement::class, $readability->getTitle());
|
||||||
|
$this->assertSame('Tõde ja õigus I', $readability->getTitle()->getInnerHtml());
|
||||||
|
$this->assertStringContainsString('Päike lähenes', $readability->getContent()->getInnerHtml());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array{0: string, 1: string, 2?: bool}>
|
||||||
|
*/
|
||||||
|
public function dataForHtmlLang(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'meta' => [
|
||||||
|
'<html lang="fr"><head><meta charset="utf-8"></head><body><article>' . str_repeat('<p>Tous les êtres humains naissent libres et égaux en dignité et en droits. Ils sont doués de raison et de conscience et doivent agir les uns envers les autres dans un esprit de fraternité.</p>', 7) . '</article></body></html>',
|
||||||
|
'fr',
|
||||||
|
],
|
||||||
|
'head' => [
|
||||||
|
'<html lang="fr"><head><title>Foo</title></head><body><article>' . str_repeat('<p>Tous les êtres humains naissent libres et égaux en dignité et en droits. Ils sont doués de raison et de conscience et doivent agir les uns envers les autres dans un esprit de fraternité.</p>', 7) . '</article></body></html>',
|
||||||
|
'fr',
|
||||||
|
],
|
||||||
|
'headless' => [
|
||||||
|
'<html lang="fr"><body><article>' . str_repeat('<p>Tous les êtres humains naissent libres et égaux en dignité et en droits. Ils sont doués de raison et de conscience et doivent agir les uns envers les autres dans un esprit de fraternité.</p>', 7) . '</article></body></html>',
|
||||||
|
'fr',
|
||||||
|
// tidy would add <head> tag.
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
'fragment' => [
|
||||||
|
'<article>' . str_repeat('<p>Tous les êtres humains naissent libres et égaux en dignité et en droits. Ils sont doués de raison et de conscience et doivent agir les uns envers les autres dans un esprit de fraternité.</p>', 7) . '</article>',
|
||||||
|
'',
|
||||||
|
// tidy would add <html>.
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dataForHtmlLang
|
||||||
|
*/
|
||||||
|
public function testHtmlLang(string $html, string $lang, bool $useTidy = true): void
|
||||||
|
{
|
||||||
|
$readability = $this->getReadability($html, 'http://0.0.0.0', 'libxml', $useTidy);
|
||||||
|
$res = $readability->init();
|
||||||
|
|
||||||
|
$this->assertTrue($res);
|
||||||
|
$this->assertInstanceOf(\DOMDocument::class, $readability->dom);
|
||||||
|
$this->assertSame($lang, $readability->dom->documentElement->getAttribute('lang'));
|
||||||
|
$this->assertInstanceOf(JSLikeHTMLElement::class, $readability->getContent());
|
||||||
|
$this->assertStringContainsString('êtres', $readability->getContent()->getInnerHtml());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getReadability(string $html, ?string $url = null, string $parser = 'libxml', bool $useTidy = true): Readability
|
||||||
{
|
{
|
||||||
$readability = new Readability($html, $url, $parser, $useTidy);
|
$readability = new Readability($html, $url, $parser, $useTidy);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user