Propagate occurrences from a repeating choice to its members#93
Open
veewee wants to merge 2 commits into
Open
Conversation
A group referenced with maxOccurs > 1 (or unbounded) around an xs:choice did not pass that repetition to the choice members: each member kept maxOccurs="1". GroupRef::getElements() multiplies the reference occurrences onto its direct children (goetas-webservices#91), but when that child is a choice only the Choice node's own min/max is updated, never its members. SchemaReader handles an inline repeating choice at parse time (goetas-webservices#88), but a named group is parsed once and shared across refs, so the ref's maxOccurs is unknown then. Choice::getElements() now applies the choice occurrences to its members when the choice is repeatable: - maxOccurs becomes -1 (unbounded) rather than a product, following goetas-webservices#88: a choice picks one branch per occurrence and members carry their own occurrences, so an exact count is not expressible in one integer. - minOccurs becomes 0 for a multi-member choice (any branch can be absent, even when the choice is required), and keeps the choice's own minOccurs for a single-member choice (that member is always selected). Non-repeating choices are unchanged.
This was referenced Jun 11, 2026
Draft
Contributor
Author
|
Hi @goetas , Let me know if there's anything I can do to move this PR forward. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A
<group>referenced withmaxOccursgreater than 1 (orunbounded) around an<xs:choice>did not pass that repetition down to the choice members. Each member keptmaxOccurs="1", so a consumer could not tell that the elements may repeat.Before this change,
deleteandwritewere read asmaxOccurs="1". After it, both areminOccurs="0",maxOccurs="-1"(optional, unbounded).Why the old behavior happened
GroupRef::getElements()already applies the reference occurrences to its direct children (#91). When the group's direct child is a<choice>, that only updates theChoicenode's own min/max; the choice members are never touched.SchemaReaderdoes handle an inline<choice maxOccurs="...">at parse time (#88), but a named group is parsed once and shared across every reference to it, so the referencemaxOccursis not known at parse time and cannot be written onto the shared definition. The reference occurrences only become known when the reference is expanded, which isGroupRef::getElements().The fix
Choice::getElements()now applies the choice occurrences to its members when the choice is repeatable, the same wayGroupRefalready does for group references.Why
maxOccursbecomes-1instead of a productThis keeps the decision from #88. A choice selects one branch per occurrence, and a member can carry its own
maxOccurs, so a single integer can no longer describe the member count across repetitions. A member withmaxOccurs="3"under a choice repeated five times has valid counts that do not reduce to one number. Instead of computing a product that is hard to trust, a repeating choice marks its members as unbounded (-1). That is the safe upper bound: it never rejects a document the schema allows.Why
minOccursbecomes0, except for a single-member choiceWith more than one branch, any single member can be absent because another branch satisfies the occurrence. This holds even when the choice itself is required:
<choice minOccurs="2">overaandbcan be satisfied with twobvalues, soamay appear zero times. The choiceminOccursdoes not transfer to a member, and neither does the member's ownminOccurs, which only applies when that branch is selected. So a member of a multi-member choice getsminOccurs="0".A single-member choice is the exception. The one member is the only branch, so it is always selected, and it keeps the choice's own
minOccursrather than dropping to 0.Scope
Non-repeating choices are unchanged. The change composes with #91: a group reference sets the cloned choice's max, and the choice then applies that to its members when it is expanded.
Tests
ChoiceTestcovers a repeating multi-member choice (members become0 / -1) and a single-member choice (member keeps the choice's min).ElementsTestcovers the group-reference-around-a-choice case.