Skip to content

Regression in v5.x: context item (.) inside every/some ... satisfies is changed to the bound variable #106

Description

@klarkcharlz

Summary

In elementpath 4.8.x, inside a quantified expression:

every $x in E1 satisfies E2

the focus / context item (.) stays the outer context item, and $x is only a variable. This matches XPath 2.0 / 3.1.

In elementpath 5.x (pulled in by recent xmlschema releases), evaluation of E2 appears to treat . as the bound variable ($x). This breaks valid XSD 1.1 xs:assert expressions that intentionally use . to refer to the element under assertion while iterating children with $var.

According to the XPath 3.1 spec, quantified expressions bind variables but do not change the focus. Constructs that create a new focus are path (E1/E2), predicate (E1[E2]), and simple map (E1!E2) — not every / some.

Reference: https://www.w3.org/TR/xpath-31/#id-quantified-expressions
Also: https://www.w3.org/TR/xpath-31/#dt-focus

Minimal reproduction

Schema (demo.xsd)

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:t="http://example.com/demo"
           targetNamespace="http://example.com/demo"
           elementFormDefault="qualified"
           xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"
           vc:minVersion="1.1">
  <xs:element name="Parent" type="t:Parent"/>
  <xs:complexType name="Parent">
    <xs:sequence>
      <xs:element name="Child" type="t:Child" maxOccurs="unbounded"/>
      <xs:element name="Sibling" type="xs:string"/>
    </xs:sequence>
    <!--
      Intentionally uses "." (= Parent, the assertion context)
      while iterating Child via $c.
      If "." is wrongly rebound to $c, ./t:Sibling is searched under Child and the assert fails.
    -->
    <xs:assert test="every $c in t:Child satisfies exists(./t:Sibling)"/>
  </xs:complexType>
  <xs:complexType name="Child">
    <xs:sequence>
      <xs:element name="id" type="xs:int"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Instance (demo.xml) — should be valid

<?xml version="1.0" encoding="UTF-8"?>
<Parent xmlns="http://example.com/demo">
  <Child><id>1</id></Child>
  <Child><id>2</id></Child>
  <Sibling>ok</Sibling>
</Parent>

Script (main.py)

from xmlschema import XMLSchema11

schema = XMLSchema11("demo.xsd")
print("elementpath:", __import__("elementpath").__version__)
print("xmlschema:", __import__("xmlschema").__version__)
print("is_valid:", schema.is_valid("demo.xml"))
for err in schema.iter_errors("demo.xml"):
    print("error:", err.reason, "path:", err.path)

Observed behavior

elementpath xmlschema Result
5.1.3 4.3.2 is_valid: Falseassertion test is false at /Parent
4.8.0 3.4.3 is_valid: True

Downgrading only elementpath back to 4.8.0 (without changing the schema/instance) restores correct validation. So the regression is in elementpath.

Expected behavior

demo.xml must be valid: inside satisfies, . remains Parent (assertion context item), and $c is each Child.

Environment

  • OS: macOS/Linux Ubuntu/Windows 11
  • Python: 3.10.x
  • Confirmed broken: elementpath==5.1.3, xmlschema==4.3.2
  • Confirmed working: elementpath==4.8.0, xmlschema==3.4.3

Why this matters

We validate real telecom MOM XSD 1.1 schemas with many asserts of the form:

every $cpri in ./*:Cpri satisfies
  if (...) then not(./*:LteFunction/*:DuCell[...]) else true()

Here . must stay on the parent managed object, while $cpri is the loop variable. With elementpath 5.x these asserts fail on known-valid configuration files (they still pass with Xerces and with elementpath 4.8.0).

Happy to provide more details or a PR test case if useful. Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions