{"record":{"id":"148fe97313c5dd93","repo":"PHPOffice/PhpSpreadsheet","slug":"failed-to-load-content-as-a-dom-document","errorCode":null,"errorMessage":"Failed to load content as a DOM Document","messagePattern":"Failed to load content as a DOM Document","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Reader/Html.php","lineNumber":953,"sourceCode":"            $useErrors = libxml_use_internal_errors($this->suppressLoadWarnings);\n        } else {\n            $useErrors = null;\n        }\n\n        try {\n            $convert = $this->getSecurityScannerOrThrow()->scan($content);\n            $convert = static::replaceNonAsciiIfNeeded($convert);\n            $loaded = ($convert === null) ? false : $dom->loadHTML($convert);\n        } catch (Throwable $e) {\n            $loaded = false;\n        } finally {\n            $this->libxmlMessages = libxml_get_errors();\n            if (is_bool($useErrors)) {\n                libxml_use_internal_errors($useErrors);\n            }\n        }\n        if ($loaded === false) {\n            throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);\n        }\n        $spreadsheet = $spreadsheet ?? $this->newSpreadsheet();\n        $spreadsheet->setValueBinder($this->valueBinder);\n        self::loadProperties($dom, $spreadsheet);\n\n        return $this->loadDocument($dom, $spreadsheet);\n    }\n\n    /**\n     * Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.\n     */\n    private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet\n    {\n        while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {\n            $spreadsheet->createSheet();\n        }\n        $spreadsheet->setActiveSheetIndex($this->sheetIndex);\n","sourceCodeStart":935,"sourceCodeEnd":971,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Reader/Html.php#L935-L971","documentation":"The string-based sibling of the file variant: Html::loadSpreadsheetFromString() scans the raw string, optionally transforms it, and calls DOMDocument::loadHTML(). False or any throwable mid-pipeline (including the security scanner rejecting entity/DOCTYPE patterns) surfaces as this exception with the cause chained via getPrevious().","triggerScenarios":"$reader->loadSpreadsheetFromString($html) where $html is scraped/user HTML containing DTD or ENTITY markup the scanner flags, or content too broken for libxml; passing an empty string or non-HTML payload from an API.","commonSituations":"Ingesting HTML from emails, rich-text editors, or third-party scrapers of unknown quality; upstream API starting to return JSON where HTML used to be.","solutions":["Inspect exception->getPrevious() and the reader's libxml messages to identify the underlying failure","Sanitize the string (strip DOCTYPE/ENTITY, tidy the markup) before calling loadSpreadsheetFromString","Verify the string is actually HTML (contains markup) before invoking the reader"],"exampleFix":"// before\n$spreadsheet = (new Html())->loadSpreadsheetFromString($apiHtml); // contains <!ENTITY ...> -> throws\n\n// after\n$safe = preg_replace('/<!DOCTYPE[^>]*>|<!ENTITY[^>]*>/i', '', $apiHtml);\n$spreadsheet = (new Html())->loadSpreadsheetFromString($safe);","handlingStrategy":"try-catch","validationCode":"if (!preg_match('/<[a-z!]/i', substr($content, 0, 1024))) {\n    throw new InvalidArgumentException('Payload does not look like HTML');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $spreadsheet = $reader->loadSpreadsheetFromString($html);\n} catch (\\PhpOffice\\PhpSpreadsheet\\Exception $e) {\n    if ($e->getPrevious() !== null) {\n        // scanner or libxml root cause — log it, then decide sanitize-vs-reject\n    }\n    throw $e;\n}","preventionTips":["Strip DOCTYPE/ENTITY declarations from third-party strings before loading","Verify payload shape (contains markup) when the source is an API you do not control","Capture libxml messages on failure for diagnosable logs"],"tags":["html","domdocument","libxml","xxe","string-input"],"backgroundTag":"html-parse-failure","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}