| @@ -80,9 +80,9 @@ | ||
| 80 | 80 | |
| 81 | 81 | /** |
| 82 | 82 | * @param \DOMNodeList|\DOMNode|\DOMNode[]|string|null $node A Node to use as the base for the crawling |
| 83 | 83 | */ |
| 84 | - public function __construct($node = null, string $uri = null, string $baseHref = null) | |
| 84 | + public function __construct($node = null, ?string $uri = null, ?string $baseHref = null) | |
| 85 | 85 | { |
| 86 | 86 | $this->uri = $uri; |
| 87 | 87 | $this->baseHref = $baseHref ?: $uri; |
| 88 | 88 | $this->html5Parser = class_exists(HTML5::class) ? new HTML5(['disable_html_ns' => true]) : null; |
| @@ -152,9 +152,9 @@ | ||
| 152 | 152 | * If the charset is not set via the content type, it is assumed to be UTF-8, |
| 153 | 153 | * or ISO-8859-1 as a fallback, which is the default charset defined by the |
| 154 | 154 | * HTTP 1.1 specification. |
| 155 | 155 | */ |
| 156 | - public function addContent(string $content, string $type = null) | |
| 156 | + public function addContent(string $content, ?string $type = null) | |
| 157 | 157 | { |
| 158 | 158 | if (empty($type)) { |
| 159 | 159 | $type = str_starts_with($content, '<?xml') ? 'application/xml' : 'text/html'; |
| 160 | 160 | } |
| @@ -239,9 +239,8 @@ | ||
| 239 | 239 | $disableEntities = libxml_disable_entity_loader(true); |
| 240 | 240 | } |
| 241 | 241 | |
| 242 | 242 | $dom = new \DOMDocument('1.0', $charset); |
| 243 | - $dom->validateOnParse = true; | |
| 244 | 243 | |
| 245 | 244 | if ('' !== trim($content)) { |
| 246 | 245 | @$dom->loadXML($content, $options); |
| 247 | 246 | } |
| @@ -365,9 +364,9 @@ | ||
| 365 | 364 | * Slices the list of nodes by $offset and $length. |
| 366 | 365 | * |
| 367 | 366 | * @return static |
| 368 | 367 | */ |
| 369 | - public function slice(int $offset = 0, int $length = null) | |
| 368 | + public function slice(int $offset = 0, ?int $length = null) | |
| 370 | 369 | { |
| 371 | 370 | return $this->createSubCrawler(\array_slice($this->nodes, $offset, $length)); |
| 372 | 371 | } |
| 373 | 372 | |
| @@ -545,9 +544,9 @@ | ||
| 545 | 544 | * |
| 546 | 545 | * @throws \InvalidArgumentException When current node is empty |
| 547 | 546 | * @throws \RuntimeException If the CssSelector Component is not available and $selector is provided |
| 548 | 547 | */ |
| 549 | - public function children(string $selector = null) | |
| 548 | + public function children(?string $selector = null) | |
| 550 | 549 | { |
| 551 | 550 | if (!$this->nodes) { |
| 552 | 551 | throw new \InvalidArgumentException('The current node list is empty.'); |
| 553 | 552 | } |
| @@ -609,9 +608,9 @@ | ||
| 609 | 608 | * @return string |
| 610 | 609 | * |
| 611 | 610 | * @throws \InvalidArgumentException When current node is empty |
| 612 | 611 | */ |
| 613 | - public function text(string $default = null, bool $normalizeWhitespace = true) | |
| 612 | + public function text(?string $default = null, bool $normalizeWhitespace = true) | |
| 614 | 613 | { |
| 615 | 614 | if (!$this->nodes) { |
| 616 | 615 | if (null !== $default) { |
| 617 | 616 | return $default; |
| @@ -645,9 +644,9 @@ | ||
| 645 | 644 | * @return string |
| 646 | 645 | * |
| 647 | 646 | * @throws \InvalidArgumentException When current node is empty |
| 648 | 647 | */ |
| 649 | - public function html(string $default = null) | |
| 648 | + public function html(?string $default = null) | |
| 650 | 649 | { |
| 651 | 650 | if (!$this->nodes) { |
| 652 | 651 | if (null !== $default) { |
| 653 | 652 | return $default; |
| @@ -777,9 +776,9 @@ | ||
| 777 | 776 | * This method only works if you have installed the CssSelector Symfony Component. |
| 778 | 777 | * |
| 779 | 778 | * @return static |
| 780 | 779 | * |
| 781 | - * @throws \RuntimeException if the CssSelector Component is not available | |
| 780 | + * @throws \LogicException if the CssSelector Component is not available | |
| 782 | 781 | */ |
| 783 | 782 | public function filter(string $selector) |
| 784 | 783 | { |
| 785 | 784 | $converter = $this->createCssSelectorConverter(); |
| @@ -914,9 +913,9 @@ | ||
| 914 | 913 | * @return Form |
| 915 | 914 | * |
| 916 | 915 | * @throws \InvalidArgumentException If the current node list is empty or the selected node is not instance of DOMElement |
| 917 | 916 | */ |
| 918 | - public function form(array $values = null, string $method = null) | |
| 917 | + public function form(?array $values = null, ?string $method = null) | |
| 919 | 918 | { |
| 920 | 919 | if (!$this->nodes) { |
| 921 | 920 | throw new \InvalidArgumentException('The current node list is empty.'); |
| 922 | 921 | } |
| @@ -1150,14 +1149,32 @@ | ||
| 1150 | 1149 | } |
| 1151 | 1150 | |
| 1152 | 1151 | private function parseHtml5(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument |
| 1153 | 1152 | { |
| 1154 | - return $this->html5Parser->parse($this->convertToHtmlEntities($htmlContent, $charset)); | |
| 1153 | + if (!$this->supportsEncoding($charset)) { | |
| 1154 | + $htmlContent = $this->convertToHtmlEntities($htmlContent, $charset); | |
| 1155 | + $charset = 'UTF-8'; | |
| 1156 | + } | |
| 1157 | + | |
| 1158 | + return $this->html5Parser->parse($htmlContent, ['encoding' => $charset]); | |
| 1155 | 1159 | } |
| 1156 | 1160 | |
| 1161 | + private function supportsEncoding(string $encoding): bool | |
| 1162 | + { | |
| 1163 | + try { | |
| 1164 | + return '' === @mb_convert_encoding('', $encoding, 'UTF-8'); | |
| 1165 | + } catch (\Throwable $e) { | |
| 1166 | + return false; | |
| 1167 | + } | |
| 1168 | + } | |
| 1169 | + | |
| 1157 | 1170 | private function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument |
| 1158 | 1171 | { |
| 1159 | - $htmlContent = $this->convertToHtmlEntities($htmlContent, $charset); | |
| 1172 | + if ('UTF-8' === $charset && preg_match('//u', $htmlContent)) { | |
| 1173 | + $htmlContent = '<?xml encoding="UTF-8">'.$htmlContent; | |
| 1174 | + } else { | |
| 1175 | + $htmlContent = $this->convertToHtmlEntities($htmlContent, $charset); | |
| 1176 | + } | |
| 1160 | 1177 | |
| 1161 | 1178 | $internalErrors = libxml_use_internal_errors(true); |
| 1162 | 1179 | if (\LIBXML_VERSION < 20900) { |
| 1163 | 1180 | $disableEntities = libxml_disable_entity_loader(true); |