Detect non-ambiguous dangling XPath operators - #861
Conversation
|
Thanks for filing this, @ukanga. I think it's worth doing and the tests look reasonable. My immediate reaction is that it feels like the validation should be possible to do with an existing pass over fields that can have expressions. @lindsay-stevens can you please take a look when you get a chance? |
| ), | ||
| None, | ||
| ) | ||
| == "OPS_COMP" |
There was a problem hiding this comment.
Why would dangling comparison operators be more likely than any other dangling operator?
There was a problem hiding this comment.
Probably not; I started with an example I encountered. Let me check whether I can identify more examples and whether it makes sense to address them in this PR.
There was a problem hiding this comment.
It seems we have ambiguous paths and non-ambiguous paths. For the non-ambiguous, the proposed changes seem to align ok.
The * operator is considered ambiguous and, as such, is not addressed in this change.
I also tested the expression ${q1} = and ${q1} > 0 which is not flagged at any point including with ODK_Validate.jar. Is this valid?
<bind
nodeset="/data/q2"
type="int"
relevant=" /data/q1 = and /data/q1 > 0"
/>There was a problem hiding this comment.
The check has been expanded to cover arithmetic (+, -, div, mod), boolean (and, or), and union (|) operators in addition to comparisons. Terminal * is excluded because it can be a valid XPath wildcard such as /data/*.
Closes #860
Why is this the best possible solution? Were any other approaches considered?
Expressions ending with an operator that requires a right-hand operand are
incomplete, but pyxform currently emits them into the generated XForm when
external validation is disabled. The resulting error is only reported later by
ODK Validate or a form engine, without the original XLSForm sheet, row, and
column information.
This change adds targeted validation before XForm generation. It uses the
existing expression lexer to inspect the final non-whitespace token and raises a
PyXFormErrorfor these dangling operators:=,!=,<,>,<=,>=+,-,div,modand,or|Terminal
*is intentionally excluded because it can be a valid XPathwildcard, such as
/data/*. The keyword operators use token context so XPathnode names such as
/data/andremain valid.The validation covers:
relevant,constraint,calculation,required,and
readonly.choice_filter, repeat count, and dynamic defaults.instance_nameexpression.entity_id,create_if,update_if, andlabelexpressions.names in error messages.
It runs when using both
convert(validate=False)andxls2xform --skip_validate.The targeted lexer check addresses unambiguously dangling operators while
leaving broader XPath validation unchanged.
What are the regression risks?
The main risk is incorrectly rejecting literal values or XPath node names that
contain operator text. This is mitigated by checking lexer tokens and the
surrounding token context rather than using a string suffix check.
Tests verify that:
mixed trailing whitespace.
*wildcards and operator-word node names remain accepted.There are no public API or type-signature changes. The intentional behavior
change is that previously emitted invalid expressions now raise a
PyXFormError, including when external validation is skipped.Does this change require updates to documentation? If so, please file an issue here and include the link below.
No documentation updates are required. This is an internal validation
improvement for already invalid expressions.
AI/LLM assistance
This PR was developed with assistance from a coding agent and LLM, including
implementation, tests, and supporting text. The submitted changes remain the
responsibility of the contributor.
Before submitting this PR, please make sure you have:
testspython -m unittestand verified all tests passruff format pyxform testsandruff check pyxform teststo lint code