PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception
Failed to load content as a DOM Document
Error message
Failed to load content as a DOM Document
What it means
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().
Source
Thrown at src/PhpSpreadsheet/Reader/Html.php:953
$useErrors = libxml_use_internal_errors($this->suppressLoadWarnings);
} else {
$useErrors = null;
}
try {
$convert = $this->getSecurityScannerOrThrow()->scan($content);
$convert = static::replaceNonAsciiIfNeeded($convert);
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
} catch (Throwable $e) {
$loaded = false;
} finally {
$this->libxmlMessages = libxml_get_errors();
if (is_bool($useErrors)) {
libxml_use_internal_errors($useErrors);
}
}
if ($loaded === false) {
throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);
}
$spreadsheet = $spreadsheet ?? $this->newSpreadsheet();
$spreadsheet->setValueBinder($this->valueBinder);
self::loadProperties($dom, $spreadsheet);
return $this->loadDocument($dom, $spreadsheet);
}
/**
* Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.
*/
private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet
{
while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
$spreadsheet->createSheet();
}
$spreadsheet->setActiveSheetIndex($this->sheetIndex);
View on GitHub (pinned to 65b080eef4)
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
Example fix
// before
$spreadsheet = (new Html())->loadSpreadsheetFromString($apiHtml); // contains <!ENTITY ...> -> throws
// after
$safe = preg_replace('/<!DOCTYPE[^>]*>|<!ENTITY[^>]*>/i', '', $apiHtml);
$spreadsheet = (new Html())->loadSpreadsheetFromString($safe); Defensive patterns
Strategy: try-catch
Validate before calling
if (!preg_match('/<[a-z!]/i', substr($content, 0, 1024))) {
throw new InvalidArgumentException('Payload does not look like HTML');
} Try / catch
try {
$spreadsheet = $reader->loadSpreadsheetFromString($html);
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
if ($e->getPrevious() !== null) {
// scanner or libxml root cause — log it, then decide sanitize-vs-reject
}
throw $e;
} Prevention
- 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
When it happens
Trigger: $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.
Common situations: 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.
Related errors
- Failed to load file {filename} as a DOM Document
- Cloning the calculation engine is not allowed!
- Locale file not found
- #VALUE!
- Security scanner is unexpectedly null
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/148fe97313c5dd93.
Report an issue: GitHub.