extension PureXML.Schema.ParticleRestriction { /// RecurseAsIfGroup against a CHOICE, split by whether the choice can repeat. /// A non-repeating choice ({max occurs} = 1) gets the literal §3.9.6 pairing /// (no occurrence ambiguity exists, so the spec text governs: particlesHa161, /// particlesM034). A repeating choice is read by effective total range, because /// the literal rule has no allowance for counts accumulated across the choice's /// repetitions and over-rejects W3C-read-valid schemas (particlesZ001); the /// rationale for both regimes is inline in `elementRestrictsChoice`. static func elementRestrictsGroup( _ restricted: PureXML.Schema.Particle, _ base: PureXML.Schema.Particle, _ baseGroup: PureXML.Schema.Group, _ types: [String: PureXML.Schema.ElementType], _ derivation: [String: PureXML.Schema.TypeDerivation], ) -> Bool { if baseGroup.compositor == .choice { return elementRestrictsChoice(restricted, base, baseGroup, types, derivation) } // RecurseAsIfGroup for a sequence/all base (§3.9.6): the element as a // {1,1} synthetic group must be within the base group's range and map to // a base member inside `groupValid`. return rangeWithinWildcard(derivedMin: 1, derivedMax: 1, wildcardMin: base.minOccurs, wildcardMax: base.maxOccurs) && groupValid(PureXML.Schema.Group(compositor: .sequence, particles: [restricted]), baseGroup, types, derivation) } private static func elementRestrictsChoice( _ restricted: PureXML.Schema.Particle, _ base: PureXML.Schema.Particle, _ baseGroup: PureXML.Schema.Group, _ types: [String: PureXML.Schema.ElementType], _ derivation: [String: PureXML.Schema.TypeDerivation], ) -> Bool { // A base choice that cannot repeat ({max occurs} = 1) has no occurrence // ambiguity: every child the type accepts comes from ONE traversal of ONE // branch, so the literal RecurseAsIfGroup applies as written. The derived // element (with its own occurrence range) must be a valid restriction of // some non-pointless branch: for an element branch that is pointwise // NameAndTypeOK (a derived `a{0,1}` does not restrict a branch `a{1,1}`, // particlesHa161); for a group branch the element maps into the branch // with the branch's other members emptiable (a derived `d1+` does not // restrict `(d1*, d2+)` because every traversal emits a `d2`, // particlesM034; its twin `d2+` over the same branch is valid, // particlesM035). The effective-range reading below would accept both. if base.maxOccurs == 1 { return rangeWithinWildcard(derivedMin: 1, derivedMax: 1, wildcardMin: base.minOccurs, wildcardMax: base.maxOccurs) && baseGroup.particles.filter { $0.maxOccurs != 0 }.contains { valid(restricted, $0, types, derivation) } } // A REPEATING base choice is the case the spec's literal rule handles // badly: §3.9.6 pairs the derived element against one branch with no // allowance for counts accumulated across the choice's own repetitions, // which over-rejects e.g. `element{0,∞}` restricting `choice{0,∞}(...)` // (particlesZ001, whose test doc calls the rule "ambiguous"; W3C reads it // valid). First principles: the element validly restricts the repeating // choice iff its leaf is a NameAndTypeOK restriction of some element the // choice admits AND its effective occurrence range is within the choice's. // A must-repeat choice (effective min ≥ 2) still rejects a single element // (particlesL). A documented, bounded under-rejection (exact in-branch // sequencing is not modelled across repetitions); over-rejection of a // W3C-read-valid schema is the non-starter. let neutral = PureXML.Schema.Particle(minOccurs: 1, maxOccurs: 1, term: restricted.term) let admitted = elementLeaves(of: baseGroup).contains { valid(neutral, PureXML.Schema.Particle(minOccurs: 1, maxOccurs: 1, term: $0), types, derivation) } return admitted && rangeWithinWildcard( derivedMin: restricted.effectiveOccurrenceMin(), derivedMax: restricted.effectiveOccurrenceMax(), wildcardMin: base.effectiveOccurrenceMin(), wildcardMax: base.effectiveOccurrenceMax(), ) } }