{"record":{"id":"2d35a6a37c38d604","repo":"PHPOffice/PhpSpreadsheet","slug":"unknown-token-token","errorCode":null,"errorMessage":"Unknown token $token","messagePattern":"Unknown token \\$token","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Writer\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Writer/Xls/Parser.php","lineNumber":574,"sourceCode":"        }\n        // commented so argument number can be processed correctly. See toReversePolish().\n        /*if (Preg::isMatch(\"/[A-Z0-9\\xc0-\\xdc\\.]+/\", $token))\n        {\n            return($this->convertFunction($token, $this->_func_args));\n        }*/\n        // if it's an argument, ignore the token (the argument remains)\n        if ($token == 'arg') {\n            return '';\n        }\n        if (Preg::isMatch('/^true$/i', $token)) {\n            return $this->convertBool(1);\n        }\n        if (Preg::isMatch('/^false$/i', $token)) {\n            return $this->convertBool(0);\n        }\n\n        // TODO: use real error codes\n        throw new WriterException(\"Unknown token $token\");\n    }\n\n    /**\n     * Convert a number token to ptgInt or ptgNum.\n     *\n     * @param float|int|string $num an integer or double for conversion to its ptg value\n     */\n    private function convertNumber(mixed $num): string\n    {\n        // Integer in the range 0..2**16-1\n        if ((Preg::isMatch('/^\\d+$/', (string) $num)) && ($num <= 65535)) {\n            return pack('Cv', $this->ptg['ptgInt'], $num);\n        }\n\n        // A float\n        if (BIFFwriter::getByteOrder()) { // if it's Big Endian\n            $num = strrev((string) $num);\n        }","sourceCodeStart":556,"sourceCodeEnd":592,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Writer/Xls/Parser.php#L556-L592","documentation":"After the Xls formula parser builds a token tree, convert() maps each node to a BIFF8 ptg token. The final branch handles only the 'arg' marker and true/false literals; any other token that reaches it has no binary serialization path and triggers 'Unknown token X' with the offending token embedded in the message. This is the parser's catch-all for formula constructs the Xls writer has not implemented.","triggerScenarios":"A cell formula containing a token that survives parsing but has no ptg conversion - typically exotic operators, identifiers, or constructs added to the Calculation engine but never taught to the BIFF8 serializer; the message names the exact token that failed.","commonSituations":"Spreadsheets authored in Excel or imported from Xlsx that use newer syntax, then re-saved as Xls by an automated conversion service; formulas that calculate fine in memory (so tests pass) but explode only at save() time.","solutions":["Read the token from the message and locate the cell(s) using it (iterate coordinates and compare getValue() against the token).","Rewrite the affected formula using constructs the BIFF8 writer supports.","Save as Xlsx instead - the Xlsx writer serializes the formula string directly and never runs this ptg conversion.","If you cannot change formulas, write computed values only (setPreCalculateFormulas plus replacing '=...' with cached results) before using the Xls writer."],"exampleFix":"// before\n$sheet->getCell('A5')->setValue('=SUMPRODUCT((A1:A3>2)*(B1:B3))');\n(new \\PhpOffice\\PhpSpreadsheet\\Writer\\Xls($spreadsheet))->save('out.xls');\n// WriterException: Unknown token ...\n\n// after: Xlsx writer does not convert formulas to ptg tokens\n(new \\PhpOffice\\PhpSpreadsheet\\Writer\\Xlsx($spreadsheet))->save('out.xlsx');","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    (new \\PhpOffice\\PhpSpreadsheet\\Writer\\Xls($spreadsheet))->save($path);\n} catch (\\PhpOffice\\PhpSpreadsheet\\Writer\\Exception $e) {\n    if (str_starts_with($e->getMessage(), 'Unknown token')) {\n        // message contains the token; scan cells to find and report the offending formula\n        $token = trim(substr($e->getMessage(), strlen('Unknown token')));\n        foreach ($spreadsheet->getAllSheets() as $s) {\n            foreach ($s->getCoordinates() as $coord) {\n                $v = $s->getCell($coord)->getValue();\n                if (is_string($v) && str_contains($v, $token)) {\n                    error_log(sprintf('Xls-incompatible formula in %s!%s: %s', $s->getTitle(), $coord, $v));\n                }\n            }\n        }\n    }\n    throw $e;\n}","preventionTips":["Maintain an allow-list of formula functions your Xls export supports and validate incoming formulas against it.","Test the exact save() path (not just calculation) with representative production formulas before shipping an Xls export feature.","Consider offering Xlsx as the primary format and Xls only as a validated subset."],"tags":["xls","biff8","formula","parser","ptg","unsupported-token"],"backgroundTag":"unsupported-formula-token","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}