{"record":{"id":"ae697d96bbb084d9","repo":"dgtlmoon/changedetection.io","slug":"expression-s-is-not-a-valid-xpath-expression","errorCode":null,"errorMessage":"'%(expression)s' is not a valid XPath expression. (%(error)s)","messagePattern":"'(.+?)' is not a valid XPath expression\\. \\((.+?)\\)","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"warning","filePath":"changedetectionio/forms.py","lineNumber":709,"sourceCode":"                return\n\n            # Does it look like XPath?\n            if line.strip()[0] == '/' or line.strip().startswith('xpath:'):\n                if not self.allow_xpath:\n                    raise ValidationError(\"XPath not permitted in this field!\")\n                from lxml import etree, html\n                import elementpath\n                from changedetectionio.html_tools import SafeXPath3Parser, lxml_guard, lxml_html_parser\n                line = line.replace('xpath:', '')\n\n                try:\n                    # Runs on a Flask request thread - must share the worker's lxml lock.\n                    with lxml_guard():\n                        tree = html.fromstring(\"<html></html>\", parser=lxml_html_parser())\n                        elementpath.select(tree, line.strip(), parser=SafeXPath3Parser)\n                except elementpath.ElementPathError as e:\n                    message = field.gettext('\\'%(expression)s\\' is not a valid XPath expression. (%(error)s)')\n                    raise ValidationError(message % {'expression': line, 'error': str(e)})\n                except:\n                    raise ValidationError(\"A system-error occurred when validating your XPath expression\")\n\n            if line.strip().startswith('xpath1:'):\n                if not self.allow_xpath:\n                    raise ValidationError(\"XPath not permitted in this field!\")\n                from lxml import etree, html\n                from changedetectionio.html_tools import lxml_guard, lxml_html_parser\n                line = re.sub(r'^xpath1:', '', line)\n\n                try:\n                    # Runs on a Flask request thread - must share the worker's lxml lock.\n                    with lxml_guard():\n                        tree = html.fromstring(\"<html></html>\", parser=lxml_html_parser())\n                        tree.xpath(line.strip())\n                except etree.XPathEvalError as e:\n                    message = field.gettext('\\'%(expression)s\\' is not a valid XPath expression. (%(error)s)')\n                    raise ValidationError(message % {'expression': line, 'error': str(e)})","sourceCodeStart":691,"sourceCodeEnd":727,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/forms.py#L691-L727","documentation":"When XPath is allowed and the line (after stripping 'xpath:') is tested, the validator builds an empty HTML tree and runs elementpath.select(tree, expr, parser=SafeXPath3Parser) under an lxml lock. An elementpath.ElementPathError means the expression is syntactically invalid XPath; the original expression and parser error string are interpolated into the ValidationError message.","triggerScenarios":"A line starting with '/' or 'xpath:' containing malformed XPath 3.0 syntax — unbalanced brackets, invalid axis names, bad predicates — e.g. 'xpath://div[[text()' raises ElementPathError during elementpath.select and this ValidationError is returned.","commonSituations":"Typos in axes or predicates, XPath 1.0-only habits clashing with the XPath 3.0 parser, unclosed quote inside a predicate, or copying browser $x() expressions with extra wrapping.","solutions":["Test the expression in a browser console or an XPath tester with XPath 3.0 semantics","Fix the reported syntax error (balance [] () \"\" and correct axis/step names)","Prefer simple axes like //tag[@attr='value'] which parse under any version"],"exampleFix":"# before\nxpath://div[[text()='hi']\n# after\nxpath://div[text()='hi']\n","handlingStrategy":"validation","validationCode":"from lxml import html\nimport elementpath\nfrom changedetectionio.html_tools import SafeXPath3Parser\n\ndef xpath_ok(expr: str) -> tuple[bool, str]:\n    try:\n        tree = html.fromstring('<html></html>')\n        elementpath.select(tree, expr, parser=SafeXPath3Parser)\n        return True, ''\n    except elementpath.ElementPathError as e:\n        return False, str(e)\n    except Exception as e:\n        return False, f'system error: {e}'\n","typeGuard":null,"tryCatchPattern":"try:\n    elementpath.select(tree, expr, parser=SafeXPath3Parser)\nexcept elementpath.ElementPathError as e:\n    flash(f\"'{expr}' is not a valid XPath expression. ({e})\")\n","preventionTips":["Validate XPath in a browser console or XPath tester first","Keep expressions simple: //tag[@attr='value']","Remember the parser is XPath 3.0 (elementpath), not libxml2"],"tags":["xpath","elementpath","wtforms","changedetectionio"],"backgroundTag":"invalid-xpath-expression","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}