19declare(strict_types=1);
27use InvalidArgumentException;
55 if (!is_string($from)) {
56 throw new InvalidArgumentException(__METHOD__ .
' the argument is not a string.');
59 return $this->calculate($from);
62 private function calculate(
string $text):
int
64 $text = mb_encode_numericentity(
65 '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>' . $text .
'</body></html>',
66 [0x80, 0x10FFFF, 0, ~0],
70 $document =
new DOMDocument();
73 set_error_handler(
static function (
int $severity,
string $message,
string $file,
int $line):
void {
74 throw new ErrorException(
$message, $severity, $severity, $file, $line);
79 if (!$document->loadHTML($text)) {
80 throw new InvalidArgumentException(__METHOD__ .
' the argument is not a parsable XHTML string.');
82 }
catch (ErrorException
$e) {
83 throw new InvalidArgumentException(__METHOD__ .
' the argument is not a parsable XHTML string: ' .
$e->getMessage());
85 restore_error_handler();
92 $xpath =
new DOMXPath($document);
93 $textNodes = $xpath->query(
'//text()');
94 if ($textNodes->length > 0) {
95 foreach ($textNodes as $textNode) {
97 if ($textNode instanceof DOMCdataSection) {
101 $wordsInContent = array_filter(preg_split(
'/\s+/', $textNode->textContent));
103 $wordsInContent = array_filter($wordsInContent,
static function (
string $word):
bool {
104 return preg_replace(
'/^\pP$/u',
'', $word) !==
'';
107 $numberOfWords += count($wordsInContent);
111 if ($this->withImages) {
112 $imageNodes = $document->getElementsByTagName(
'img');
116 $readingTime = ceil($numberOfWords / $this->wordsPerMinute);
118 return (
int) $readingTime;
130 for ($i = 1; $i <= $numberOfImages; $i++) {
132 $time += 3 * ($this->wordsPerMinute / 60);
134 $time += (12 - ($i - 1)) * ($this->wordsPerMinute / 60);
143 $this->xmlErrorState = libxml_use_internal_errors(
true);
144 libxml_clear_errors();
149 $currentErrors = libxml_get_errors();
150 libxml_clear_errors();
152 $this->xmlErrors = $currentErrors;
157 libxml_use_internal_errors($this->xmlErrorState);
162 return $this->xmlErrors !== [];
168 foreach ($this->xmlErrors as
$error) {
169 $text .= implode(
',', [
174 'msg=' . trim(
$error->message)
transform($from)
@inheritDoc
__construct(bool $withImages)
calculateWordsForImages(int $numberOfImages)
trait DeriveInvokeFromTransform
trait DeriveApplyToFromTransform