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

  1. Inspect exception->getPrevious() and the reader's libxml messages to identify the underlying failure
  2. Sanitize the string (strip DOCTYPE/ENTITY, tidy the markup) before calling loadSpreadsheetFromString
  3. 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

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


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/148fe97313c5dd93. Report an issue: GitHub.