Repository: symfony/symfony
File: src/Symfony/Component/DomCrawler/Crawler.php (PHP)

Code before:
     * Parse string into DOMDocument object using HTML5 parser if the content is HTML5 and the library is available.
     * Use libxml parser otherwise.
     */
    private function parseHtmlString(string $content, string $charset): \DOMDocument
    {
        if ($this->canParseHtml5String($content)) {
            return $this->parseHtml5($content, $charset);
        }

        return $this->parseXhtml($content, $charset);
    }

    private function canParseHtml5String(string $content): bool
    {
        if (!$this->html5Parser) {
            return false;
        }

        if (false === $pos = stripos($content, '<!doctype html>')) {
            return false;
        }

        $header = substr($content, 0, $pos);

        return '' === $header || $this->isValidHtml5Heading($header);
    }

    private function isValidHtml5Heading(string $heading): bool
    {
        return 1 === preg_match('/^\x{FEFF}?\s*(<!--[^>]*?-->\s*)*$/u', $heading);
    }

    private function normalizeWhitespace(string $string): string
    {
        return trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $string), " \n\r\t\x0C");
    }
}


Review comment (suggestion):
can the key `1` really be nullable ? The code does not handle null values when using it.

Code after:
        return $this->parseXhtml($content, $charset);
    }

    private function canParseHtml5String(string $content): bool
    {
        if (!$this->html5Parser) {
            return false;
        }

        if (false === $pos = stripos($content, '<!doctype html>')) {
            return false;
        }

        $header = substr($content, 0, $pos);

        return '' === $header || $this->isValidHtml5Heading($header);
    }

    private function isValidHtml5Heading(string $heading): bool
    {
        return 1 === preg_match('/^\x{FEFF}?\s*(<!--[^>]*?-->\s*)*$/u', $heading);
    }

    private function copyFromHtml5ToDom(\Dom\Node $source, \DOMDocument $target): void
    {
        /** @var list<array{0: iterable<\Dom\Node>, 1: ?\DOMNode}> $stack */
        $stack = [[[$source], $target]];

        while ($stack) {
            [$children, $parent] = array_pop($stack);

            foreach ($children as $source) {
                if ($source instanceof \Dom\CharacterData) {
                    $parent->appendChild(match (true) {
                        $source instanceof \Dom\Text => $target->createTextNode($source->data),
                        $source instanceof \Dom\Comment => $target->createComment($source->data),
                        $source instanceof \Dom\CDATASection => $target->createCDATASection($source->data),
                        $source instanceof \Dom\ProcessingInstruction => $target->createProcessingInstruction($source->target, $source->data),
                    });
                    continue;
                }

                if (!$source instanceof \Dom\Element) {
                    continue;
                }

                $element = $target->createElement($source->tagName);

                foreach ($source->attributes as $attr) {
                    try {
                        $element->setAttribute($attr->name, $attr->value);