symfony/css-selector · error · ExpressionErrorException
Expected a single string or identifier for :lang(), got
Error message
Expected a single string or identifier for :lang(), got
What it means
ExpressionErrorException thrown by HtmlExtension::translateLang (HTML-aware translator) when any argument token of :lang() is neither a string nor an identifier token. The HTML variant emits an XPath condition matching case-insensitive lang/xml:lang attributes with prefix matching, which still requires a literal string/identifier value.
Solutions
- Quote the argument: ':lang("en-us")'.
- Use a simple identifier: ':lang(en)'.
- Match only one language tag per :lang(); combine selectors for several.
- Catch ExpressionErrorException around the translation call.
Example fix
// before
':lang(01)'
// after
':lang("en")' Defensive patterns
Strategy: validation
Validate before calling
function isValidLangArg(?string $arg): bool {
return null !== $arg && (bool) preg_match('/^("[^"]*"|\'[A-Za-z-]+\'|[A-Za-z][\w-]*)$/', $arg);
} Try / catch
try {
$xpath = $translator->cssToXPath($css); // with HtmlExtension registered
} catch (ExpressionErrorException $e) {
if (str_contains($e->getMessage(), ':lang()')) {
// reject selector
}
} Prevention
- Quote :lang() values when they contain digits or odd characters.
- One language tag per :lang() selector.
- Prefer identifier-shaped language codes (en, en-us).
- Wrap HTML selector translation in try-catch for user input.
When it happens
Trigger: Using Translator with HtmlExtension enabled and translating ':lang(42)', ':lang("a" "b")', or any selector whose :lang() token list contains a non-string/non-identifier token.
Common situations: Same as the base :lang() error but in HTML documents: unquoted numeric or malformed language codes, programmatically generated selectors with wrong token types, CSS4-style wildcard arguments like ':lang("*-Latn")' typed in a way that mis-tokenizes.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Expected a single string or identifier for :lang(), got
- Expected a single string or identifier for :contains(), got
- Got too deeply nested
- Invalid series: " ".
- "*:nth-of-type()" is not implemented.
AI-assisted analysis of symfony/css-selector@08e2905152 (2026-09-14).
Data as JSON: /api/errors/5e9cd2bf67d848e7.
Report an issue: GitHub.
Appendix: source
Thrown at XPath/Extension/HtmlExtension.php:148
.')'
.' and not(@disabled or '.self::DISABLING_FIELDSET.')'
.') or ('
."name(.) = 'option' and not("
.'@disabled or ancestor::optgroup[@disabled]'
.')'
.')'
);
}
/**
* @throws ExpressionErrorException
*/
public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr
{
$arguments = $function->getArguments();
foreach ($arguments as $token) {
if (!($token->isString() || $token->isIdentifier())) {
throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments));
}
}
return $xpath->addCondition(\sprintf(
'ancestor-or-self::*[@lang][1][starts-with(concat('
."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')"
.', %s)]',
'lang',
Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-')
));
}
public function translateSelected(XPathExpr $xpath): XPathExpr
{
return $xpath->addCondition("(@selected and name(.) = 'option')");
}
public function translateInvalid(XPathExpr $xpath): XPathExprView on GitHub (pinned to 08e2905152)