PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception

Cannot load invalid XML ${fileOrString}: ${filename}

Error message

Cannot load invalid XML ${fileOrString}: ${filename}

What it means

trySimpleXMLLoadStringPrivate() wraps simplexml_load_string plus the security-scanner scan in a try/catch and rethrows with this message, chaining the original Throwable. It fires when the bytes are not well-formed XML, when the XML scanner rejects the content, or on an empty path; the message says whether a 'file' or 'string' was being loaded.

Source

Thrown at src/PhpSpreadsheet/Reader/Xml.php:145

            $continue = true;
            if ($data === '' && $fileOrString === 'file') {
                if ($filename === '') {
                    $this->xmlFailMessage = 'Cannot load empty path';
                    $continue = false;
                } else {
                    $datax = @file_get_contents($filename);
                    $data = $datax ?: '';
                    $continue = $datax !== false;
                }
            }
            if ($continue) {
                $xml = @simplexml_load_string(
                    $this->getSecurityScannerOrThrow()
                        ->scan($data)
                );
            }
        } catch (Throwable $e) {
            throw new Exception($this->xmlFailMessage, 0, $e);
        }
        $this->fileContents = '';

        return $xml;
    }

    /**
     * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
     *
     * @return string[]
     */
    public function listWorksheetNames(string $filename): array
    {
        File::assertFile($filename);
        if (!$this->canRead($filename)) {
            throw new Exception($filename . ' is an Invalid Spreadsheet file.');
        }

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Check the previous exception ($e->getPrevious()) — it holds libxml errors or the scanner message and pinpoints the cause
  2. Ensure the file is really SpreadsheetML: it must start with an XML declaration and carry the office:spreadsheet namespace
  3. Pick the right reader via IOFactory::identify()/load() instead of instantiating Reader\Xml directly
  4. Run xmllint or load the raw string with simplexml yourself to see the exact parse error

Example fix

// before
$xmlReader = new \PhpOffice\PhpSpreadsheet\Reader\Xml();
$spreadsheet = $xmlReader->load('export.xls'); // Cannot load invalid XML file: export.xls

// after: let IOFactory pick the reader for the actual format
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('export.xls');
Defensive patterns

Strategy: validation

Validate before calling

libxml_use_internal_errors(true);
$raw = file_get_contents($path);
$probe = simplexml_load_string($raw);
if ($probe === false) {
    $first = libxml_get_errors()[0] ?? null;
    throw new InvalidArgumentException('Malformed XML: ' . ($first ? $first->message : 'unknown'));
}

Try / catch

try {
    $spreadsheet = $reader->load($path);
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
    $cause = $e->getPrevious(); // libxml or security-scanner detail
    // report $cause?->getMessage() to pinpoint the XML problem
}

Prevention

When it happens

Trigger: Calling load()/loadIntoExisting() on a file that is not XML at all (binary .xls, zipped .xlsx, HTML), XML with encoding/name errors, or content the Xml reader's security scanner flags (e.g. DTD/XXE patterns); passing an empty filename yields the sibling 'Cannot load empty path' message.

Common situations: Using the Excel-2003-XML reader on wrong file types; user uploads renamed to .xml; files with BOM/encoding mismatches; strict security-scanner configuration.

Related errors


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