PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception
Failed to load file {filename} as a DOM Document
Error message
Failed to load file {filename} as a DOM Document What it means
In Html::loadIntoExisting(), the file content is first passed through the security scanner (XXE/external-entity protection), optionally transformed, then handed to DOMDocument::loadHTML(). If loadHTML returns false, or anything throwable happens along the way (including the scanner rejecting the content), the reader throws this exception with the original failure chained as getPrevious(). libxml diagnostics are captured in the reader's libxml messages for inspection.
Source
Thrown at src/PhpSpreadsheet/Reader/Html.php:823
$useErrors = libxml_use_internal_errors($this->suppressLoadWarnings);
} else {
$useErrors = null;
}
try {
$convert = $this->getSecurityScannerOrThrow()->scanFile($filename);
$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 file ' . $filename . ' as a DOM Document', 0, $e ?? null);
}
self::loadProperties($dom, $spreadsheet);
return $this->loadDocument($dom, $spreadsheet);
}
private static function loadProperties(DOMDocument $dom, Spreadsheet $spreadsheet): void
{
$properties = $spreadsheet->getProperties();
foreach ($dom->getElementsByTagName('meta') as $meta) {
$metaContent = (string) $meta->getAttribute('content');
if ($metaContent !== '') {
$metaName = (string) $meta->getAttribute('name');
switch ($metaName) {
case 'author':
$properties->setCreator($metaContent);
break;View on GitHub (pinned to 65b080eef4)
Solutions
- Inspect exception->getPrevious() and the reader's captured libxml messages to find the true cause
- If the scanner rejected it, strip risky constructs (DOCTYPE, ENTITY declarations) before loading
- Repair the markup with ext-tidy (or a sanitizer) and retry; fall back to loadSpreadsheetFromString on cleaned content
Example fix
// before
$spreadsheet = (new Html())->load($uploadPath); // scanner rejects DTD -> 'Failed to load file ... as a DOM Document'
// after
$clean = preg_replace('/<!DOCTYPE[^>]*>|<!ENTITY[^>]*>/i', '', (string) file_get_contents($uploadPath));
$reader = new Html();
try {
$spreadsheet = $reader->loadSpreadsheetFromString($clean);
} catch (Exception $e) {
error_log('HTML load failed: ' . (string) $e->getPrevious());
throw $e;
} Defensive patterns
Strategy: try-catch
Validate before calling
$source = (string) file_get_contents($filename);
if (preg_match('/<!DOCTYPE|<!ENTITY/i', $source) === 1) {
$filename = null; // flag: sanitize before loading
$source = preg_replace('/<!DOCTYPE[^>]*>|<!ENTITY[^>]*>/i', '', $source);
// load the sanitized string via loadSpreadsheetFromString instead of the file
} Try / catch
try {
$spreadsheet = (new Html())->load($filename);
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
$cause = $e->getPrevious();
if ($cause instanceof SecurityException || str_contains((string) $cause, 'Entity')) {
// security scanner rejected the markup — sanitize and retry once
}
throw $e;
} Prevention
- Inspect getPrevious() and the reader's libxml messages before guessing at fixes
- Sanitize untrusted HTML (strip DOCTYPE/ENTITY) ahead of loading
- Clean scraped markup through tidy/sanitizers in ingestion, not at parse time
When it happens
Trigger: Grossly malformed or binary content fed as HTML; content rejected by the security scanner (DOCTYPE/external-entity patterns); scanFile/transform steps returning null; encoding breakage that makes libxml bail.
Common situations: Parsing untrusted or scraped HTML of unknown quality; files containing DTD/ENTITY declarations; upstream content silently changing format (JSON error pages saved as .html).
Related errors
- Failed to load content as a DOM Document
- Security scanner is unexpectedly null
- Cloning the calculation engine is not allowed!
- Locale file not found
- #VALUE!
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/a8566677335a0761.
Report an issue: GitHub.