symfony/css-selector · error · ExpressionErrorException

Expected a single string or identifier for :contains(), got

Error message

Expected a single string or identifier for :contains(), got 

What it means

ExpressionErrorException thrown by FunctionExtension::translateContains when any argument token of :contains() is neither a string nor an identifier token. :contains() translates to an XPath contains(string(.), ...) condition that only accepts literal values, so any other token type (e.g. a dimension, number, hash, or operator token) is rejected.

Solutions

  1. Quote the argument as a string: 'div:contains("42")' instead of 'div:contains(42)'.
  2. Use a bare identifier (letters) if a quoted string is not required: 'div:contains(Warning)'.
  3. Catch ExpressionErrorException around cssToXPath and surface a clear invalid-selector message.
  4. Pre-validate :contains() arguments in generated selectors to ensure they are quoted strings or plain identifiers.

Example fix

// before
'div:contains(42)'
// after
'div:contains("42")'
Defensive patterns

Strategy: validation

Validate before calling

foreach ($args as $arg) {
    if (!preg_match('/^("[^"]*"|\'[\w\s-]*\'|[A-Za-z][\w-]*)$/', $arg)) {
        throw new \InvalidArgumentException(':contains() argument must be a quoted string or identifier.');
    }
}

Try / catch

try {
    $xpath = $translator->cssToXPath($css);
} catch (ExpressionErrorException $e) {
    if (str_contains($e->getMessage(), ':contains()')) {
        // reject selector
    }
}

Prevention

When it happens

Trigger: Translating selectors like 'div:contains(42)' where 42 parses as a number token, 'div:contains("a" "b")' with multiple non-identifier arguments, or 'div:contains()' referencing a token of the wrong type at index 0 when building the condition.

Common situations: Programmatic selector construction that passes numeric values unquoted, mistyping the argument so the tokenizer classifies it as a number/percentage, injection of unusual characters that change token classification.

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


AI-assisted analysis of symfony/css-selector@08e2905152 (2026-09-14). Data as JSON: /api/errors/d320cce420565a0d. Report an issue: GitHub.

Appendix: source

Thrown at XPath/Extension/FunctionExtension.php:133

     */
    public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr
    {
        if ('*' === $xpath->getElement()) {
            throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.');
        }

        return $this->translateNthChild($xpath, $function, true, false);
    }

    /**
     * @throws ExpressionErrorException
     */
    public function translateContains(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 :contains(), got '.implode(', ', $arguments));
            }
        }

        return $xpath->addCondition(\sprintf(
            'contains(string(.), %s)',
            Translator::getXpathLiteral($arguments[0]->getValue())
        ));
    }

    /**
     * @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));

View on GitHub (pinned to 08e2905152)